From 73cd0d5f17723bf85d6dfb61d9a35cfc7cc4b739 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Fri, 26 May 2023 11:52:51 -0300 Subject: [PATCH 001/130] [lgthinq][feat] Change project to support hybrid builds (OH3&4) Signed-off-by: nemerdaud --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 + bundles/org.openhab.binding.lgthinq/NOTICE | 13 + bundles/org.openhab.binding.lgthinq/README.md | 67 + .../doc/bridge-configuration.jpg | Bin 0 -> 61730 bytes .../doc/lg-thinq-air.jpg | Bin 0 -> 125908 bytes bundles/org.openhab.binding.lgthinq/pom.xml | 59 + .../src/main/feature/feature.xml | 11 + .../internal/LGThinQBindingConstants.java | 297 ++ .../internal/LGThinQBridgeConfiguration.java | 102 + ...hinQDeviceDynStateDescriptionProvider.java | 40 + .../internal/LGThinQHandlerFactory.java | 110 + .../api/LGThinqCanonicalModelUtil.java | 52 + .../lgthinq/internal/api/LGThinqGateway.java | 138 + .../internal/api/OauthLgEmpAuthenticator.java | 408 +++ .../lgthinq/internal/api/RestResult.java | 39 + .../lgthinq/internal/api/RestUtils.java | 191 + .../lgthinq/internal/api/TokenManager.java | 168 + .../lgthinq/internal/api/TokenResult.java | 105 + .../lgthinq/internal/api/UserInfo.java | 73 + .../internal/api/model/GatewayResult.java | 71 + .../internal/api/model/HeaderResult.java | 39 + .../discovery/LGThinqDiscoveryService.java | 199 + .../errors/AccountLoginException.java | 27 + .../internal/errors/LGThinqApiException.java | 31 + .../errors/LGThinqApiExhaustionException.java | 31 + ...GThinqDeviceV1MonitorExpiredException.java | 32 + .../LGThinqDeviceV1OfflineException.java | 33 + .../internal/errors/LGThinqException.java | 31 + .../errors/LGThinqGatewayException.java | 27 + .../errors/LGThinqUnmarshallException.java | 31 + .../internal/errors/PreLoginException.java | 27 + .../errors/RefreshTokenException.java | 31 + .../internal/errors/TokenException.java | 27 + .../internal/errors/UserInfoException.java | 24 + .../handler/LGThinQAbstractDeviceHandler.java | 499 +++ .../handler/LGThinQAirConditionerHandler.java | 298 ++ .../internal/handler/LGThinQBridge.java | 30 + .../handler/LGThinQBridgeHandler.java | 344 ++ .../handler/LGThinQFridgeHandler.java | 186 + .../handler/LGThinQWasherDryerHandler.java | 453 +++ .../lgthinq/internal/model/DataType.java | 56 + .../internal/model/DeviceParameter.java | 86 + .../internal/model/DeviceParameterGroup.java | 40 + .../lgthinq/internal/model/ThinqChannel.java | 113 + .../internal/model/ThinqChannelGroup.java | 81 + .../lgthinq/internal/model/ThinqDevice.java | 73 + .../internal/type/ThingModelTypeUtils.java | 269 ++ .../type/ThinqChannelGroupTypeProvider.java | 34 + .../type/ThinqChannelTypeProvider.java | 27 + .../type/ThinqConfigDescriptionProvider.java | 28 + .../internal/type/ThinqThingTypeProvider.java | 28 + .../internal/type/ThinqTypesProviderImpl.java | 121 + .../lgthinq/internal/type/UidUtils.java | 61 + .../lgservices/LGThinQACApiClientService.java | 42 + .../LGThinQACApiV1ClientServiceImpl.java | 168 + .../LGThinQACApiV2ClientServiceImpl.java | 184 + .../LGThinQAbstractApiClientService.java | 445 +++ .../LGThinQAbstractApiV1ClientService.java | 152 + .../LGThinQAbstractApiV2ClientService.java | 115 + .../lgservices/LGThinQApiClientService.java | 71 + .../lgservices/LGThinQDRApiClientService.java | 30 + .../LGThinQDRApiV2ClientServiceImpl.java | 91 + .../LGThinQFridgeApiClientService.java | 28 + .../LGThinQFridgeApiV1ClientServiceImpl.java | 73 + .../LGThinQFridgeApiV2ClientServiceImpl.java | 64 + .../lgservices/LGThinQWMApiClientService.java | 33 + .../LGThinQWMApiV1ClientServiceImpl.java | 146 + .../LGThinQWMApiV2ClientServiceImpl.java | 125 + .../lgservices/model/AbstractCapability.java | 132 + .../model/AbstractCapabilityFactory.java | 125 + .../model/AbstractSnapshotDefinition.java | 54 + .../model/CapabilityDefinition.java | 71 + .../lgservices/model/CapabilityFactory.java | 89 + .../lgservices/model/CommandDefinition.java | 112 + .../model/DefaultSnapshotBuilder.java | 284 ++ .../lgservices/model/DevicePowerState.java | 71 + .../model/DeviceRegistryService.java | 21 + .../lgthinq/lgservices/model/DeviceTypes.java | 94 + .../lgservices/model/FeatureDataType.java | 45 + .../lgservices/model/FeatureDefinition.java | 124 + .../lgthinq/lgservices/model/LGAPIVerion.java | 34 + .../lgthinq/lgservices/model/LGDevice.java | 107 + .../lgthinq/lgservices/model/ModelUtils.java | 83 + .../model/MonitoringBinaryProtocol.java | 34 + .../model/MonitoringResultFormat.java | 50 + .../lgthinq/lgservices/model/ResultCodes.java | 172 + .../lgservices/model/SnapshotBuilder.java | 36 + .../model/SnapshotBuilderFactory.java | 64 + .../lgservices/model/SnapshotDefinition.java | 31 + .../model/devices/ac/ACCanonicalSnapshot.java | 186 + .../model/devices/ac/ACCapability.java | 157 + .../devices/ac/ACCapabilityFactoryV1.java | 108 + .../devices/ac/ACCapabilityFactoryV2.java | 108 + .../model/devices/ac/ACFanSpeed.java | 91 + .../lgservices/model/devices/ac/ACOpMode.java | 89 + .../model/devices/ac/ACSnapshotBuilder.java | 72 + .../model/devices/ac/ACTargetTmp.java | 93 + .../ac/AbstractACCapabilityFactory.java | 250 ++ .../AbstractFridgeCapabilityFactory.java | 107 + .../fridge/AbstractFridgeSnapshot.java | 33 + .../fridge/FridgeCanonicalCapability.java | 57 + .../fridge/FridgeCanonicalSnapshot.java | 121 + .../devices/fridge/FridgeCapability.java | 35 + .../fridge/FridgeCapabilityFactoryV1.java | 69 + .../fridge/FridgeCapabilityFactoryV2.java | 68 + .../devices/fridge/FridgeSnapshotBuilder.java | 80 + .../AbstractWasherDryerCapabilityFactory.java | 201 + .../devices/washerdryer/CourseDefinition.java | 64 + .../devices/washerdryer/CourseFunction.java | 63 + .../model/devices/washerdryer/CourseType.java | 39 + .../washerdryer/WasherDryerCapability.java | 225 ++ .../WasherDryerCapabilityFactoryV1.java | 274 ++ .../WasherDryerCapabilityFactoryV2.java | 270 ++ .../washerdryer/WasherDryerSnapshot.java | 318 ++ .../WasherDryerSnapshotBuilder.java | 123 + .../src/main/resources/OH-INF/addon/addon.xml | 11 + .../main/resources/OH-INF/binding/binding.xml | 9 + .../OH-INF/i18n/devicefeatures.properties | 1 + .../resources/OH-INF/i18n/lgthinq.properties | 30 + .../OH-INF/i18n/lgthinq_pt_BR.properties | 29 + .../OH-INF/thing/air-conditioner.xml | 26 + .../main/resources/OH-INF/thing/bridge.xml | 76 + .../main/resources/OH-INF/thing/channels.xml | 301 ++ .../src/main/resources/OH-INF/thing/dryer.xml | 48 + .../main/resources/OH-INF/thing/fridge.xml | 28 + .../main/resources/OH-INF/thing/heat-pump.xml | 25 + .../resources/OH-INF/thing/washer-dryer.xml | 74 + .../binding/lgthinq/handler/JsonUtils.java | 61 + .../lgthinq/handler/LGThinqBridgeTests.java | 213 ++ .../LGThinQWasherDryerHandlerTest.java | 32 + .../model/CapabilityFactoryTest.java | 55 + .../resources/dashboard-list-response-1.json | 206 ++ .../resources/dashboard-list-response-wm.json | 82 + .../test/resources/fridge-data-result.json | 125 + .../test/resources/fridge-data-result2.json | 125 + .../src/test/resources/gtw-response-1.json | 64 + .../resources/login-session-response-1.json | 55 + .../test/resources/prelogin-response-1.json | 5 + .../resources/session-token-response-1.json | 7 + .../test/resources/thinq-fridge-v2-cap.json | 1347 +++++++ .../test/resources/thinq-washer-v2-cap.json | 3235 +++++++++++++++++ .../test/resources/user-info-response-1.json | 52 + .../src/test/resources/wm-data-result.json | 118 + bundles/pom.xml | 1 + 145 files changed, 18709 insertions(+) create mode 100644 bundles/org.openhab.binding.lgthinq/NOTICE create mode 100644 bundles/org.openhab.binding.lgthinq/README.md create mode 100644 bundles/org.openhab.binding.lgthinq/doc/bridge-configuration.jpg create mode 100644 bundles/org.openhab.binding.lgthinq/doc/lg-thinq-air.jpg create mode 100644 bundles/org.openhab.binding.lgthinq/pom.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringBinaryProtocol.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACTargetTmp.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeSnapshot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/addon/addon.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/binding/binding.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/devicefeatures.properties create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/fridge-data-result.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/fridge-data-result2.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/gtw-response-1.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/login-session-response-1.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/prelogin-response-1.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/session-token-response-1.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/thinq-fridge-v2-cap.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/thinq-washer-v2-cap.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/user-info-response-1.json create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json diff --git a/CODEOWNERS b/CODEOWNERS index 085876b540840..0e7d479c9a36f 100755 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -189,6 +189,7 @@ /bundles/org.openhab.binding.leapmotion/ @kaikreuzer /bundles/org.openhab.binding.lghombot/ @FluBBaOfWard /bundles/org.openhab.binding.lgtvserial/ @fa2k +/bundles/org.openhab.binding.lgthinq/ @nemerdaud /bundles/org.openhab.binding.lgwebos/ @sprehn /bundles/org.openhab.binding.lifx/ @wborn /bundles/org.openhab.binding.linky/ @clinique @lolodomo diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index e4acce9bd8d1e..77312c0f4c5f1 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -941,6 +941,11 @@ org.openhab.binding.lgtvserial ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.lgthinq + ${project.version} + org.openhab.addons.bundles org.openhab.binding.lgwebos diff --git a/bundles/org.openhab.binding.lgthinq/NOTICE b/bundles/org.openhab.binding.lgthinq/NOTICE new file mode 100644 index 0000000000000..38d625e349232 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/NOTICE @@ -0,0 +1,13 @@ +This content is produced and maintained by the openHAB project. + +* Project home: https://www.openhab.org + +== Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License 2.0 which is available at +https://www.eclipse.org/legal/epl-2.0/. + +== Source Code + +https://github.com/openhab/openhab-addons diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md new file mode 100644 index 0000000000000..74f513dfea8c0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -0,0 +1,67 @@ +# LG ThinQ Bridge & Things + +This binding was developed to integrate de OpenHab framework to LG ThinQ API. Currently, only Air Conditioners (API V1 & V2) are supported, but this binding is under construction to support others LG ThinQ Device Kinds. +The ThinQ Bridge is necessary to work as a hub/bridge to discovery and first configure the LG ThinQ devices related with the LG's user account. +Then, the first thing is to create the LG ThinQ Bridge and then, it will discovery all Things you have related in your LG Account. + +## Supported Things +LG ThinQ Devices V1 & V2 (currently only Air Conditioners are supported, but it's planned to support the other kinds as well) + +## Discovery + +This Bridge discovery Air Conditioner Things related to the user's account. To force the immediate discovery, you can disable & enable the bridge + +## Binding Configuration + +![LG Bridge Configuration](doc/bridge-configuration.jpg) + +The binding is represented by a bridge (LG GatewayBridge) and you must configure the following parameters: + +| Bridge Parameter | Description | Constraints | +|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| +| User Language | User language configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | en-US, en-GB, pt-BR, de-DE, da-DK | +| User Country | User country configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | US, UK, BR, DE and DK | +| LG User name | The LG user's account (normally an email) | | +| LG Password | The LG user's password | | +| Pooling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | + + + +## Thing Configuration + +For now, only Air Conditioners are supported and must implement versions 1 or 2 of LG API (currently, there are only this 2 versions). We are working hard to release in a next version supportability for others devices like: refrigerators, washing machines, etc. +There is currently no configuration available, as it is automatically obtained by the bridge discovery process. + +## Channels + +LG ThinQ Air Conditioners support the following channels to interact with the OpenHab automation framework: + +| channel | type | description | +|--------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| Target Temperature | Temperature | Defines the desired target temperature for the device | +| Temperature | Temperature | Read-Only channel that indicates the current temperature informed by the device | +| Fan Speed | Number (Labeled) | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | +| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | +| Power | Switch | Define the device's current power state. | + +**Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart ThinQ way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: +1. **Internet Link** - if you OpenHab server doesn't have a good internet connection this binding will not work properly! In the same way, if the internet link goes down, your Things and Bridge going to be Offline as well, and you won't be able to control the devices though OpenHab until the link comes back. +2. **LG ThinQ App** - if you've already used the LG ThinQ App to control your devices and hold it constantly activated in your mobile phone, you may experience some instability because the App (and Binding) will try to lock the device in LG ThinQ API Server to get it's current state. In the app, you may see some information in the device informing that "The device is being used by other" (something like this) and in the OpenHab, the thing can go Offline for a while. +3. **Pooling time** - both Bridge and Thing use pooling strategy to get the current state information about the registered devices. Note that the Thing pooling time is internal and can't be changed (please, don't change in the source code) and the Bridge can be changed for something greater than 300 seconds, and it's recommended long pooling periods for the Bridge because the discovery process fetch a lot of information from the LG API Server, depending on the number of devices you have registered in your account. +About this last point, it's important to know that LG API is not Open & Public, i.e, only LG Official Partners with some agreement have access to their support and documentations. This binding was a hard (very hard actually) work to dig and reverse engineering in the LG's ThinQ API protocol. Because this, you must respect the hardcoded pool period to do not put your account in LG Blacklist. + +## Thanks and Inspirations + +This binding was inspired in the work of some brave opensource community people. I got some tips and helps from their codes: +* Adrian Sampson - [Wideq Project](https://github.com/sampsyo/wideq): I think it is the first reverse engineering of ThinQ protocol made in Python, but only works (currently) for API V1. +* Ollo69 - [LG ThinQ Integration for Home Assistant](https://github.com/ollo69/ha-smartthinq-sensors): Ollo69 took the Adrian code and refactor it to support API V2 in an HA plugin. + +## Be nice! +If you like the binding, why don't you support me by buying me a coffee? +It would certainly motivate me to further improve this work or to create new others cool bindings for OpenHab ! + +[![Buy me a coffee!](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/nemerdaud) + + + + diff --git a/bundles/org.openhab.binding.lgthinq/doc/bridge-configuration.jpg b/bundles/org.openhab.binding.lgthinq/doc/bridge-configuration.jpg new file mode 100644 index 0000000000000000000000000000000000000000..80f2587d3394740d9c97f2e849e587949c53df53 GIT binary patch literal 61730 zcmeFZ1wb9kk}y0t!QCxDASAduBzSO1u;A_*+zA@o-2)+l2MO-(1b2tv5M2H_$-VpD z?%v$D`tRNE`}&+YQ&U}4UDe&yGBs!JChryjjA!Cf;s6vB6d(zHfV(BaAu$&-V*rqn zdITT>000jVLBRn~AoKz}h<}5%LHHpQ%nv*?2(v&z1E}B`WCU127zaE%f`?fU><^k? z5KaLPu!tYmzn>+fWECh_8CjSaS=d2NW>!{SW)@ynP6`$lUKVa%Hdc@tD(h!C5FH>1 z)C%gSREQ|3-{4g+2O87}20{YjVSdvN9=wGAI~eyj7(xdH{ZkG+NDuuR+(G~m04W9X zx%>M%g2)9a?`8lI01+M@0Uiz!0RiFOJwzlF98?r!WE4UyY;+t_B62cPA`%h`YDQWL zN_r|1l80P$^vo>m?Cj*UJObRT{ETeutPmkk_wL<8K}I1!MI~UpPja92-<|H708B(+ z6$&(#5`e~pg29BkYXitZJ>kF>g*57K2?`np77iW(@g5Q~Nbng0Qd2O{urP3Nu&^Mt zCzubwV!~nFXL$mTt*D1UX@|q=8=Z|v^|Z7ZS84cwnoZx{?;a8!J^>-o0~*?gbdT6M zIJvlactu3T#3dx9o++z5f1#?Tu3=zkWNcz;X71ql%E{Tq)y@BPKwwaCNN7xKTzo=e zQgTX8Zrh2jC9s4>yF*!B8w7jyqw!X2swS9PW zeDeMD?EK;qVi&~D-_uXS{)Jtbpk2_gurRO)5WAqDoxuYJ6Bh113p~~nMFc%NY)V#N zM4YG5*`>|*sMwSaaP{qnk?^S5mmVBKO#5Nkzs)ee|Bz+>WZ2*Ang&o{puon1!32bW zGv0N|@$ZhG&5TQ((u*{x5UXCPVX(p3xAHYZd2MdDX2E}Pdk7Oi3l=8 zqgTWouRf|^HHrw6>=p!bXqKDjipS6-$Pkjt`Hdhr}mheNB<1p30>r+r{!7@r`>vtnIcu z7jACWTU^dumExMU%2uqs)JCk5_g}u#i$X&LB0p*8_#i#}_Z_saq0@ZF3t7z~_PB`?dY|yx6>P$t{d^$9aLIrRC&4jK6FXM6{EOZ^39 zqhe|OH1pAfUOI1WJ&}D`R~-Y~GnQlPa8W)-*;ts4Vk}WeFy-<;6J8@>eFCSK#Pf~99M+H;C$zj+|l>vp(LK+X)x>iv-U0>vrv)C-*)?Xp$c!R*a!kj zXnoIuJ8{**&H1oYIv+G4$5VLBUQb^0I-X_S)7R2UB7}>{?j%-=ZW{D(JNw|2V}NI^ z2$M$gaM70g`dEd(-YmB>6|0nDZ*kEYHnNxUbF5SQLipqcDVs3-*W1nOchik?m9PD_nVXk5I_2l%lTy%o&w&c-b8p*3pZhpHo zB`by=#aP+5rCJ0X(@H+TyYGBU9kojbx<)-gI<{dI-2hY zDrbTt#vggG@;-mhXf4?kZBBgjannHyA4U z0cR5IiL5ORR#_k*tgL;!v}qWeJo*`AXhX2Hj-FT#fO^|VM!QEqD=B@ivd+O z3}t#Lr26DpoUM+r1(WlR74CpumFt_s>rJQ`d`PY9|ETr4%gCMxPl+|*dq3zonIP3Y z`0o1M#+a81kov!gGAm~V>^_N^iWdqF5xGZMNO8ZlCr7$omZsTR{yLAb2Lh(}4pJLw z73Vx(=F9Oga)-lw@gi!%r?pCe21*bk~Sv%BOIPljI$A5sgT63U)St*%yVCx_g8R9@v)EEC}-ROdn;k+;@6wd z<106#mx|S3FSv~Zea~8GPUtOhU%S8X5vTUyAr%)R)7&8TRusnNpNT0>!jH-te==W$OI5_X^6s1TTH_f=j`OyrS|+}N-Y3J^Au^xt(<^(h#-3VYupK+ne%3i! zxN3a&_9zql%|c=jm<%V-pfflNN5;=-)dxR6PoA_qQMZCSCB=q)TTM zq2$G+-GEm^vcTmKBV)}SAiBeH93l1X;`u(^9Z-82%Kw80y1w?=p|K(2Rb&sE%OH9F zBMtAkJ7Dfcj7<3saQ==1^w8b@gj^vSDZo4|_fpDH44~Aq0B$CE_gK{5x^o&}+_zN_F$)o_Y>Pw)i}jHZbDbx4kFQ7(`|EeeudG z9{-dxqo7~(p)LHu#@+_^#zfi`_BTaNf5T(x#}zwA@CuUTlnD$0D_>I=)&;*39IOTN zvrqO!aTdYPBOKfT8TGDN$ajE2g;_U>$I#Wl6e7ijK={*3($}#em7Ml%J{zdf6&rWJ zC&l^Ago)kzDHf5LSxD8wlGK1|4Geq75dE~`{TQp(%<2Lm&b?ZPaUX{EJoP4pc0#S! zB^M#onM$q#@*Ap!wVl~_K!Yst&lA8KYJIrry&o(7<5w6hC$-Lv{AzXh$L|o#yZn8! zpxX;}n;*ZxHr@Zf>;k`+2N%do-ZccL0lFLglQSVAL%p+N6vV5wNQ5nUa3bUg$vcm| zl2?ft-PwE-f@GEGP}DK)(camck&~~iXeO8+^_Hi-=doCgP475=cW?r90AjfOSrsRm z@a>l3cMWI@V$Ni%!6#95H}i6CCW3R1;0{T1krR&cjH;rCr=Bh1S``$M_DkK=n;Ytu zmWrWB?Ys~WLt%(@J{6weD!{M@hyN5e+tgRO#+Q_VW#+6~tPpTgUES=X0N zoMa(a8Yp&g1)}J#ile_wdv)6RP1SEtV%gO42$^ zL~n;2&qhT0O_26O80%2k+>!9y5g)w&a=?Ei*7PLnUFS2PPz*-_E?975MK)YlT|GDa z`9gwj62tjfUWAxf^Y=WZ(B1@U+F61v;g^oim_0|-su;E6w%d7F1w+T}LbO*~7vA5$ z@l@0cyk%v#Z1dw;1*B-n(qacSh@XwBC)dRw!y}L$*?VgMBNvf}Olx<*L@xTx+I8dQ z_e?82GYcn^Ah%Wf8ft7Gw(l*s1>?o8-TM~`P-{!QOI*?6Gp_A?KdDa^cm(c%NBpa2 z*YBVvz4!lK(*Mi*7xj`A)H$!ydj}ND9>*>mMYbgj`oJ-jNVW;;3Z zyi4nhGro3bF1r_F>z9)lLhi8k(3;IKA5 z7uMi=p0pGWw|kAIZPRX*ANTb)=`Qklc2}6I4j(qLysSz_b(P%<63H3&?9KyxP+bh^6UF|-;TOQiW z+xQQw==((czBenvgddEz)l!r1G3p!x1}FVOJNp~1e%i@RGow6bXE$&<5MgA(w})9M z)jLKJ%zk?>0A0Lqm*mH>yFs7wGIX74qJ26Lo^+m%NB&dUPsHT^Nj`?6@&%}UB!#!j z5G;<$Ehs7%ViFV_uF!vk!R&|D7{g*XQ2Hd6i)V$8-Z?4y*gco~gjDh(IxbDul9u13 zoS!9K#Z2G$6)ZkR5y&#zL1}MxBY+|1jKxgS2-7UHcH z1C(YPa*uN%n4)1}oGPYw*P9T`hYAHa1_%ikh}r(nF}03)y<%wJ!TUt??A(>DRG2#L z)uQsJJ-ym0^+nsmN?S2Nbx z12_qpp50!KFgKwoHX{hwo!rJjm5Uj;R%Iv(8Gu_>PJ9;uUXzTIJAieg&+b&QgsW#J zi0b$fB>N<0Kxo5hsLr_K8xHEZqzVwMqWAbP4y#l~J`o?@dezDrxeiQfvt=u5d%*jC>-D3^oUh9~+8qityV++h!dF zF?od9YYP%o3|UHsCUmU87!HFd7z658+d?Ay!bD<(st&pj_axh)xu_@%J;h#BmXyby zRUYvu_`(xY`4NzL!|cmz;SIy%_ks5c7lzR9faicbWLd5u&oF2+rs6&IVun!I0}Gu| zieP}*n@F9#)=aG&zMDFYt4RL4P&y#BR((r8ru-~AY|(JhFv1Ki+0sO+WGTMNb^~^W zB$Q@kw;)h54^2rJx8pbv=E93)2>La{nXT7r4hnD z)sO9fDP%+PNj=BsF=X#B9{FS6@ipb)|6n%yGoS#hec0$OOuMi#S-4cTat%@G@S9Qr(Dy$pK%hB-{RwQw7BtMW7>!P`gd78mXykL=uG=isK;)XZYpN^ zS+zs~+L zpa7|a4Q%bfP!W_a2y;3++CtzY5XLbu)iVU)k04BM4=M=4lMuZAPk09coBV(w5hwu1 zUPVa+v<(M@DNOzj*8e-$z|`Ifq+thXs12>GLHW>XKVd@%>;ZwTEMI}T{YVfCQH-pg zD}!fh@E`#s04YEgPyi?Zec%;f23Ue&E(Y*y4PqPsB{2W#FZhXm@GF2^`XHAXU;uK6 z0ycmZp!b6xfV2Tf1E#;#t%ETb0)r$dl&1gyH-C3``~UzDVgcYL>hA6;`|j>02aI*i z0zi}X-}r5^0D${Dh>!Xkjye+n&|d>UW!v9y`iTHg6$AhTQ?`2cdOym6gvFqZ!7$!Y z0RW(?0|53A03d1ptT!+Zk`Lrg0DuZuS2FzokPL?PXiPxaFMnY-7_R$Uz5OZ8Z}s;h zz6Jvg4f%oz7W~2?LLyagNC*h-2IJmgDDCqbE zcsK-DIJh`J;%hJ<4=mh0IJkQ_sK}@||L%0x0>;;%Kf@rvK!Ks9-{Wf}V8@4sM4}*7 z@iRd}Y4A`8h+ve87{r6&x8HLhXLv~T4FT%zw@?oT3v+)xA>K?w`n{< zng)jZ*9Aa{219zyA|_g#;M${&O#WJfXkHjZ`jl8N4W3ra29Er+t#EZ#+R^@Bw$RVs zWB=u*BbS_SlP`E6Z$LOln+>OTHB3}83GL-}q|5`q!>C{B@Ut%g7?i&v_znLk1Ng1a zsdRu3SXju=EJ8;+5*gV4lJBQt=DmjUnQ6n0CtnjHd^QJ2#z@r%5>Or}XR(`1OAG&s z`ENa3rR4=DJZq`T$#`;T`=TC8LJtjOa{c3^2wDM|p8q^ifZ4VZlA_5n$!Q(>^a*{i zVXuP;x_w0PLbKXjjNq{@UUw;Ru18g(tYguP{gEt$0jYSYM3i;|H%nA?!!{RvF7dP? z6S{GMZY{;P1TWdBe+!5{#7F*y%oe19`E zO+}LxYP96n)Z|KI1!!31x9Y2I%cKSxF)e;&j22#;jbG2DT+v7}xj12rp@ASJ7&U$XzK zQw~wt?EaLPZp(KTa(Uy}JpP20{JcKa|A_71>ZmNEFCJNx+y8RZ_-8hdBZoRZ!z&Z~ zPQO9US7TmS!%D>Fq{tb7EoU_-o&Ry3NTiizf>`%g7D2qOPV$1<&EjqQ;8j>b#?fbG z;Yzdq?64N=uyraG%YLYC(n8%}-lJby_uCXmA^NK_e#1ZKhfI~iZEF934*03l7#=bL ziFqXQ1N$wH1gM0=7rhVn2^c(6P_Ra{NJ`T08m{^fEHtT z=#VlReO5BRY)cFpfa0EyFg8nk-?{=6wC*34b8c42&x*l{!3$$=DjWUU7skaR)X2WM zgj%1&SU=jaOOFi)vHh1YqukmAOH^?t>Kf zM+uF($6U6#fH8)PCMp`n&8)vrcZ4iH+*cv^5%JT)O7_5^{O7FcA(nr{`!^iSy`;Zg z{Kp`4;eW&jwFq%c+x$&Jq>iV?=YLNAbNuchVjCUVa;&rYbK$=?_@9bIb6FNZ|KJk+ zb2$|UJ|UR_4L>yR^GhZ;<(ThrJ9WRUZkqtW1u^*z#V^UhedmukP54hJeq=#r8N`1? z1VFqU0p%sN9z9tSvN#sp3Qsn#Ljq$oXP{5Di%bu4&_l-L*|qUw|_tc z0FXKA-;}svc(A%zn}rX$j49RWh_Rnqc4Iuo?==xL;xggYWW#`8-9Q$n;<4>v z@1FeQ!N~WuZ&H>T8zu|5s`g8E$(KBcV#R)x9QM|rl@12V@x214VHbf+fFEo~%)d#i z|3)In1Hh~p-~-ao&`_}8L%y)!1KXfy0Wh%GP;i)7@HiB#y0{1!c=wrED5(^DY^fix za|nZXCJ=wzwt&8EE%Hab18Bw^v6|8A!{ek_1#|18+4}1Cr7ykFJ1eO+h|^CyRWE%s zbqL7E+D=dYwuP*T`1(!A)Wu` zvC!gSVfXQ9a8!qM(&frs2xI#5x=z77Q`yj-2bzOw$8 z?yC(fWtoda`Es1a+o_r46BjUU-uPclk#EEFvIppJCEtrFJ&INd&3jH+_!qdy+{(1Q zR+*SVWU_fs%28i;9I!?rHe}83TVVjlDeL)&(M9NFNjJj(` z{AId(dH#S0zqWO+9Tfoxu`I!yxxDXD-+Cz5)s^vpn^1}RGisU%T;x1|{+c_@KF9Qg znr97tp|W~F@KI`9MoKT1V3-aQs4y;VL4L@>$c5;~`V#Y(iH=8<4MTm2LKZb)eKl7m zl~}CveVPHd$;Z?dj&r%z>#(`UQZn^ST~xhZ`bI}?_l+Gl>#C}}J7fnM#h4PSz33({ zsM=J~A0-?dJ_(asr-FTJo-Jop9mXyO&%$wTnYO^-Z~>+ivW~rf@zsvl z^oVJ@xG_=S5B6kZXNJ!mp-aVR zev+Gg89cRf4A-2I_iZ13_Ut;B~nWw~Z!)u5?yx8b8*n3gjn%m?lH`Y9r+dy zxW6M-*&cO?3GvQTld}K$zWh^zk9Pn#U@4ce(uT;vt0c>`m{V`gz~8i%hCLnF(77Ku zR})tI_;g^I24OSc@47JKbmlrALtnp}B_~EM371$66%Au?PeB6@O{C3g0|U#{tpGe= zdCld?oM(Re2~hyv@l!+1g%W}Av4(4rYPkDM;W7TE5kxQ_IkijZ(wbCg z-+0cq~KY4xP$s7HpO99tutC)xG;CMN^lQ+G5WJPYNg^nH>c7m{DS z#6puNJG~#27(&gZ8H@F&8IOg?wxR5-glw1)Xm<=3`F=LdxXv>#FOT$#t~5&2jt-(laq^@=xX7*Ll8T-*w*94c{+_{(j?F0(auucCPNT`+?tQau}!JvEder*&NvqK+F$ zY;@2{uc$J3%@A85oU&P1t4d|5c68*#Ry##`(nu4ZBvGBd{osOhdOsI3K;!I%Vvjhy z4VJIU2^_W0`OGQEj#1bVibrH1g~Vo(<+o0=Z6{>Fi!orz9<7nk8QC1Rr(dDe7%6Ja zS=Esm+6aYA_sPLd#hcVDoNM^a+8%i*8(w=Ms!-&IAJUujHDe5P+kTAm*e21pr(iEahWF?Q1OvIPd4ALB_`I3yFOx0sJFtJnD^=jk}NOCK% zyA_Or43w&u*~xWEZ7`^TqYA6tu7@5q$aX84@wT7QB34xMk_)j9C63z0Gu${m^rj*< zo-hx&eE8JFKs}UPHS}zz!{{UP;0Wlgff{gqDYcYxs1c`l2kM%3u} zWhrFnmVtjzL&J%p=5$JycHk;gOy#XfW!CWO9g1b;gVh6X1BNOzNL%i!TtO zEIE$&rJ@fRD~ z8gAF+DEoN5Sf^^4vYI;*_lVhjImqA{NV+gK(A!TKKucy6B35+_|2_7zCQTL5em2Al za97EM=sx-LdKO1XTf${cZ>>pBl`cs@PTiZFGir6+6CvmR)3P>N=@+yRXx zU-dn1it^U$I>S``zw+}{UOzwWwD}aLK6O5NjJavZ#$=J3T=6E8%#AvDamCUT5n|RA zs~YZKtW!fAEc5u6qk@42=gaU>!Dj_S}TPX}XV@!s_2RgrtIxeW2qy3!sE@yFK; zY*q5iSa>D-vH1*+S$y}M$!!9K2w$s5s zogulu*E6w3H9;+zYfOOI?)8EOIHj+35Ju;g2lsh<3oX-IidpgE!J1ZwM_ytH;jDiT zT#D$R>qf1^C+t{;B6deC>b|}$r#Nk)clOHpu_H_;>4X>eGxYBault5wVcmUycwk^d zYsCcfqf4G|=a@8P)NZUkzOt{mRGB*b-uLY~Qm5Sclqs=7;tuG(EuJYJ@+e?zT)j#v z{oc~lgjis% zys@@t1V1XBQp)QNu=)3nn@NkapCPc2q;y&3v^lETs0M_A-V5gU-Y3nx)@)|X`x3J)UHDf#y}j{0 zFJ6rZo3+V4Vc2=1lM=8k6#ea-;Tm(`gC(*4WeB9z73-YX&^r#vWzr5+(?ZG)(Y3FO zi9VCPNxH0eh(h0nY>hj&Q|l96MXj)p1>sZlu&kn7@ZTdo z&ya_bTzmwVca-iN)iQeVqSp1}gsXLx+1nB8oD2Bc z&7+(umZZy&>ArSw(!EgyI~%yc)03dn4i$P&pxB}CG-5VkNy1o!Ik&?uSaCVdMREDz zP;QGg`@@4NV_4JS+zw-6AsNVQDx}VoSeuXkL<`OlcS!ZGc)A_B3^HN+>@s^D7FJ4S zdu$arJS95cA`?WEOd}d{hSwibPL5)^CfP<;%zxrQzjL(Dyw1!@psQsqNc%f+mSpAB_XCHTzn5IrYhyKs6R6I3wE?{N{L+Db7O zDN7D7aYFlgA-s29*0)X=dx3b7fw6Wt6W)P?k(oZZ?v&ceg`tg(O=FCbzuXg4=#M2m zO2m-#233xuY=gK?V%lx4(Ja+;Xf@-N54Q%U3%j@QkG0;&M7zCe&17GL*G>fTq=-Lx zRw87n^JPxL~+G(cA>>S)brcA3xDR8hYVZf%_M@0H^5FsMj|Aq;Y|{eXe~hL0aEYdpwWypL7lJ zJQC(yiD;#aW+#fKJ0Ii`XiCQ>t%&ZPO1 zmWijNqsK5izAUCM_$Y0lup3>>{j025rjkABy~v0Hw81Srg*f!?E)&AI74**x6LIQF zc%^6dZg;?~j9aR2y*m^-VO!E+E)HGNAoxd?Bn%AM4x<=EOHQ1s>6aveW2U)eOa&I) zea2bejWFzsp5PW>YC}yP}3Z-xfa+< z$qi*qg|ha}k~tu-P5d#%I>#_GFv&^qy7vjT$_yf9+5AJBK+1ALvWCZzZrm5{3?aCv zYb~beAm1JEfgFrAJjR5)MFkET4iaYgksvQ&fdxYi*f{rD6m@Y~^`f($*fljD{s=OF zk4*|eUGPdO&1cR!k(Kg1byH&lN-3RTT&^EFW>|Q8+lhT$OL%{qxG_${Y(l1kujzCh zptyw^WVeMKKanp&kATE9I3yZw8>*$(k!E26r@TLlMe&tYL`CYAN@L?ataAGCWM*3i z(^qu`_dZ`T)Ow{V>rRfy9wG?bmoXE6(eq){`<34V4kbp_gQr5E{xqQeFwk%?|D->to_6i!nvg(l}N$o_H|-6b4{NJpC+=M zui;2Y=jSaq`_IbSo1RY;te_I#K%MiMy_krrmgV@w?{TGovatD~I4`_p)q={N; z={ej1aJX8yTACs@5%}FsQne``gRX^^PL7pFQh7BJjW29SEbEj$SL_D0JtTB)+udzs z+Eh3+cF|&r51%xSi#2GNj}E=<2`oaKA`co{VtaYnS_5@3`yAz~Hu4fZggS}77&ZF^79=honC8A)bLGPf8fU)ZlTjN zrm2I8^!F0NU2edox2?>G;)6*-qcVYNbbB8f)M~nxfNv8u_tA83{>1>(FwU^98CvnX zmX}ryE#KPo3kvdW-YCr=w!7#wZrqIJwl2T*#7xi78wKN9t&LOOAuLMDqrsdJ9*4_q zi=+d4mA=o?hSk)b$${&7ts5TuaN$H`i)o)S6)H1&v)c)Wg#p&E} z+{}q8q;sj!cGjw0jEz-t=6eARhmBH-N(#fAS2E~rXM1y2 z8!3nA!#E`_;iQVSc3nj39TPQV-#Vrk3G3wB)UP=CY*+NW3zb}?^6!8OFh++!>^;5^ zzT2#D{?c*s!-_5A8`HU&LB1&$4mb|Zk)lkaib!H{V(};Q@?Q?*ak4$g^XOJA_$x-> zS6Uli4czig`12O)trG_xjMNeYBd22T4{pCbEJiE{{1jV`ocg%cb`Qqu4)8mCb_ZaG zRGSm*cNhfXb61nLoM}@(-a3UhBU*Qx{uX}Q{`@?apEI$e@I7m2!5fLzV8L;11_mp( z`=K^dUnpm3$6TPA=uGbbiFU;&?5((RQw<4M(`P!%JRc~gh9)Y-S%pv2o(x=EJz7A_ z&CCDzHmw#_ZU}`7Z}C}bOJURd;uJl$)|WtiC$txoi)vuAY(yn#!U(?=UO6J)zL5lu zKT23Yxa}$D_4^K&0OofVT$QVDOw+cJXoQce8{N|j?|{ug0oDI?A8!T0L4t!bex#`n zLTG4JL3oqCeI8zLE@5_q8|DPv>8Si@1}V(p&C8R`mxZL{A)|w?W=W*jGmlp~*)+a| zp;oRm?W-nOK8eR!8|l@%O-~(s0Dynz$B6tId6=7=%2zp`x&$__D%9a~-2TGJ9S<_3 zLu|EK8i{-N>f3iWk;E@{U*~+uz*Ahbc~8re=#0EyTXk^xo?A=sQ6!&IceaJNK&oiF z-@;D$`ul^iO1rg{}Mc#2Jyj02@%+?*#r0&aJK{*`8nJgm#$lGRN`>&RjRb6=tNZ^A8Bw^*= z0A1fm&r1)^A3maM&Q0F+>vw+F8PbopaIB^|}mm@#gR^MJ;g=ZBG#ovKs@7uK{l%S#tHH;0#j zW0GWN6+wWJ6CeQ-czUa2ko0Y;Wj+xv{i(}qzHvLAL09)QFY5g(X7Qnp*;Mq9##vlZ zff0YFmG}V**-tfjlb&T`f$4QV>@@?lwyOdSZ(yxZth!6i_l2V67s!nVIIRp%>wPY= ztKJx+?N~X{+(e|fs-wvEQ%AH6e^(`LzKXHz=Hp75vlTx)<9zOQUt0P>scs4w*xXL0 zXXRAOV4NtKB`;%RJGe(}DLdz0eSjpJOly2hT!c7}gCTI5U@vM_MU0%;wXq*r=Dsn* zhktteLTOjX&-;s|VDhDA>K3q-?J05)7rsW}bQn6gQ zfBCp|%fe#Sg^X;YcXh)-^Kq%K`x|dVeb1LAPq@OQ<1Z%q8Vx0&lG;uP#@YOyHI(V!ldF*2NXaS429>y8Gvngm;p^T5{yTjqQ{( zH(&PXk1IX0oEIU|<~o=_a+iG>ANHm*H-d-Si%F7x_I)a@bcjhv4~lVK+ZGimsyCy= zW*t2D>IWaroE$oq{!n2N)DFIM)evPtby_P1&7AO&TI`Y~i>eIQMk?|K!9Kf2Bii~R z^nTR3T4An53)`w4HeKtn3igJXuG7Y`PlW}+J(5nW&F&EhoB4Gt=_M5n(ck8*Znwe; zbN#Zzx~2@!P%mg|qB=S`YB>`skhx}7-v*Di>BlWc2lx#Nk-u8=2lw$ppq1btvW2QLF1_JW zCtEArZQ(%f2ft6BW>;W+Pe*qgBRMU^x)p*mv?_zF2c_owXYPQMl>F=~Zb~!vIg3fJ zXF?vHu?vDzlZ{nJWr=22{Is|?aeVUAx&#dtEn@6(hf-_rxyCxQ=UI-b6-UJsHVtn$ z^)t*>^(^+CNd#NH(ftmm?=y%LPAyJOcgmf)l}b09HQv9$KX-cMy3X9Ukr*^^!QyrA zjMllCm1xTt9K?B~A5nc|6=4WCa@;#5e}0Rd9RCXa*ve2z^n!_u)IW`W9(wZWfgN_P zVP+noiOj4lSL%MhLmU1PFAJ_`sFg=2=9tX0~1`+x;B^7Ba+vZF{FtiS= zCgKYYi9+9zF2(k2uDxU3uchDLuHz+m?@=uX7mQCXqO)1vbyvK zyje(WmPS!w9~NQUqB3Ma_3m?!jq4bF{KP`XYLQH6=!Z>nZKnSxIo7`%Dl*@Z>st6G z7_@MLZ{E6}z$SBML_O;nb{rpfl617YhSE@2)=Fg*GE3HK>opzzp_dcmB1C}JfouO( zjF*Nx@pE|8r96W6!qCkpPa_rfPj)4aBk(Ywb3W_NaYpFa5x$8CmK=R7?2zoaNJc(% z3vr?tuc(`du?74p`BQ}p()itPlWXS>IU7GuRpH+3)KM!-$>U^b)@Pj%ZyZE;epHpkFxW)Xm~#@gXZ=PyOmnM1D|f%8y3xH zowukm(X1>sDiNr*XP00&NYvao<6#~0&Ewvp{dTy{Wy`wfBMkLbx}?f~=@7lU_+ zebT0DUhtXC-hAYng@xwt;cx^WJm(M%&xtC5CYWUj+T%ORP%X=QYvuXG$ECM0RbfiN zUX;?V)LGyR|NPN@TVX9-;e69}jqF)>=hy9}!K>nf`9}p8Ar^$h`?Q4@r7(MS&+6xC z72zBn%RuAFX^dA$zve<{uiftiZ)XtTk9}bS>a^<;3#>}6+={BNL!{9wC#?a1hx z#NO?~y4lh}MgIwU@m=T6Bf_hd9W*-@8E(miz1JgM=GwDaZ_rCK>?1OY;kD|XoPHI< zHe0r|BT+12Ru7tWr;$>WMmb`8yc)(`wVFHQVnsc}i8vJjpT=-XTus?A(nMe|nGoDw z;e zcz$ktHtadGgd*l%dJci{Ri$Rr0I7PhTT z_NCqr&UDXZFH;VFlOl(tKtrLnG_4c3Z}jMJCBGKype>Q zM@e^p8*LE!+SxW~rKu5L$+I(S3;DtAyp%`VW_pqQVexN2U1Zj9u<5vFqdLr36BOHQ zdQH7FD%Vd^u3HjJOtroPTx_mGz0R*_8$8QPy=~Tv7)MWC;@_|ttE$O8I(>R3!Uwo>+$beF7;wm zc_H&8!>&3nH>XKob89doI%9T*de}c#X_vngB;-_(+3cXa;M%)*bZ^6nCljM>-(jyX znM0Z`|CmN;D`46f8%rce;5sov0c2RG%s1@F324u5*>I?da4&jJ9ysEfti3M9v9Qc4 zPS4POTR_H-e{L&S_G0(_MyM1;;%T^U!u+R+9#`ByaNm2O_;s*2j*Na zz6QT<&E#*;YaS8!glEciGQT=i+0)qG&z2I%>gMynuBJM?a$zrf`gQ_nKfVqIn}Ma{ zCNAT&T8N$VXct)U{nWBh(yoZ&(B@JmzKqx|D~rl6@rF{@^!Aw|94b4dqRGy(X@{WT zHdBkq;_cvQ?5iqwYh{+PLp(W~$I@_lYmEy&n(CW{8;NKn_M#+KJC)%f;jxIu%yZ`w zP)GBrIEs}IXPU$Cpj5;+HR+YRKYJ*l$hHsKdoU|KL1F4Au?wuY!KSz&moC+7n>=M` zL9dT4atC58Ggxf0gT3$$V8EAqQy?~) zvo-9v`NdI-mofc+6C(5ddopyUHI+?r85s6e72H6vX1gNhhPb+`tqXL#x!n zsaIr&9b3H+RIr%xZP6~$^hqOnCcCILwXf$cm2<;)%}wwyXd%~GE$`4>CJiJpI&}S5Ynb6lr44F@q;_7VTAf~=7Y zFbd+!6|}Z6U#p(c-XbZ=%TXIzx4hh9p1tOxJaZGq=UPoyeC*MHZNkxUdU~79(N?F= zi8@t_39ch1WYIl~V9L^)uC6?lyPekg#hqUfd$%zpxwgN7SGLrikh3+IGQr$7Bk5Wf zB){l?B~HVcy>GF#;ILKoCB{QBy_V=fSl8(r--Bgb$6-N+u(;+j1nm{X)!dZyLnr|l zU8j+&Dta(JdcIHFwlV5=a6Uz)De#a~6sFeeuwMAQPxf7e11Hu7Pf_(5-`@MmwFt-2 zFFLJD^(wt~fiF06x4h5ayS!Y4q0^FE7QA1_uC{909)!kFyWq8l^zo^F^Yga4)bi1w z?Of)iFfPRD5Y#S`H!e86KIfB1>vaQnK%@kkc?Q1P7f$$8+EHI*gkqEy5j%UyxuC1U zG-+{xh?U2g@#hgoZwpz)n;sa3PZOgNIA2kz^d~zfJNmn}Ecv6Y74fofb^h*VrIV)nRI|MkGcEY8w}bNIslyJ$HF0Vzn7vw6KFS2uP8%;luDv zSvA>`xI8Q*IM4h1V!Y|H<7maMHxh@anQV*@G49zq-m)EkAUGR*h{U#)54e(!67eBv zoH|owupqApZ`;j$(5mHSZsJ27p^xmFzEio#jmD1$zjLasD<2&DhNDY?AJbUB{+6;Q zaIMi7DrEc&eDl+d;yd4H$q7+HGiruK`KGJ)!6Fn1glRhGuP;fwQo*ut!aR$JI)%<% z3u-=Ift?wMBQ4>2%TJL>6o42DYUMK{!>ooVa>VhmDEoTgCqO zqlCbFv-nBU=Orp%xCbz+wQ3tvijFZV3AWU!b~e&KOwa38TS{~llHCEQ?%L$Y1ZbR} zCs$FH*HM?Zx!5IeWy(6?_QOAR9LyXw;vm1LJ|v`xE*0%AVI2^{s|p&8iAZ?nnKmOu z88cXIW{Tjndxl2VT@)BnbO+FH$A(&{A!tmghN`%_tmPI2#4=maA3M#@;)E5xwJ@GC z2#D@|W4@ziYfIA07f(gS{oq}CwEoKfN8WotHPv(r!zTp@5+D$S&;x{~l+ddXdXOS2 zC;|$hiF83JN(nvmDpI8>&4x%vA@r((qI3vIQvqor2;vvtd%u6Z?=3#xzt;b)|K9gP z)>-Gwnb~L0o;@>r@7XguCbu=^WY=Q)W>C!*tvpl85Y<`MwUzbGn@6BY_IHU%yR$HF z&#wQXJ5;g=&|*BovZv_|C^}ao~1Q`sQ zzeqQ)ZhCU6uy?L~n7w^0>5L=A-j739z*qMP=oOEY^x^yCx=*gfW!$Y+=iZzc?F!-E zyCiczQzcDNzrhDRt??k&8UKBahx;s=;D0nZxi6xjoTup7*}MBxrs|M0NJ#etLnsQ{ z&3h=)b3h|6gN-)iOfbMC2YK+*+E@L!Wc)d1Kie@)Mf&r~Kv(By^M;b3Tz%B*w^v(U zsYksjyUXl!valh46+Ur#*|2^0N44=fdH74_nuwTI9tf54bQ&U71 z-M5oHSC+WSKRK5siP7W)2WZW1`)mfdeo9o|*|}#I=$bI7L^%|qpI&_^?v4A5DvCO) za9^6`eQmPr&#<*u>6|QCRgr#`kE^c9<50$6zydn5^Ex{2^oqTck@GiY52BKnNG?my zN~Lg-^V|E@FviY+_*~zr#_G+$l|#GHkv8K`(3>)fqMaT&WBQR|uC7JJNe0$IcN&Ch z8|EcGZd7kf6fn9gW!4Y$kDB=1Q;Uc&{kWbel&e2c{?Tp8{ad?8_$^EPH<#lQH?VpF z_ws8cw-va#V{Co>x$_4mw+yM8ULgENs`~uS^=tad*+P(dPQEF{_q6rCxGMAa8SgbN zJOP$s8nY&rnns~Ex0zfk(%!F~sH_w$&kW9wDSCybo%Mb$@o;)c@rrAF-$D!T!u-cg zN}{_0+yN7tdWJ34zMJc^8+W|3#%Hd87rOk+F&NW`P3+OS+AqNAxR5$dHk>PSq*^lh zq3ZfVa?S2wzye63nkKpxLD`=!Kks0=Zp>w+e#;*4MuT4D%G`>a%{D( z0bZUOY7Nt<7rQ44)RwK)il^hp>JE_)#UFkQFLjydPcYvr3G|`^#jTAzl@xYoiW;Vb zd`LMpx01|lzxRyoJEE#y)Dm|leWdLe^Gn_ygnn4$De=Cob4M5*y6^wiNGRL0a0&GD^yX+| zbfxO0ijp{xbHNpa|KZe&D_l(uXHK0G&Em5vkz?G`gthzMKGAQ3P>mKNgjQOs)c5$N z2Tr#iww%Z=8E`-0INlTe_(^Uk{Os0yErnBZMa5}NsO)p&FHO1a7$^HmJFovZ(O>Dm zx|jgwb(^HdlPFAgn9EgKwR3vbcW8P_6VPbs4^K;r_uLl~s_Z#?-VM^!Ij5v~d~W7+ zvd4MLQB5aI7L?GO?{B${Msv?CztMQSlTdc!#e~%SWsi>Q->)c1nzMRXpNF%Y^N_hU z)eUe3$0}VBSXxoMc_URZI9;T39A#3kI`eUyi+>nxk+r)Saek%kHjn;fGUMc4`H6`R zREncN`#py(mH&D@r>tbc-LLN3#P00Pvyzx2E3VQ8^YfHun|Zm!gXGqxvb$b2W5cpZ zV;(`LY`3oP?750;%TMalJCxj*dllHJll3k{C$vqrXsacs;X>a<k<1D9}DO#<bssh&uz-K#F*b2 z_q{3Z?zLL-<Q*(sq zDz&L{k)Y7~!kIO1_5F3{rQB^)P1ol1T?L8~yBhMwPj+Y|GzZ)E>CKyuY?v5eOfopj zZg?p-$ji%&s&mB6Ct0c1?P<(1Ty034jqRA1%)X%bCJkS1cXw2&aF*YTarx-j%cz<+ zIXQVF6KUnfqoDrXsgS()3@2Qol3<&; zqD%;MDc4BQ`!2Nc(?*%wd+#a3qpjWT>N*M3)|G=jFG^+Q{Ki@>6zQ(m?56U%D>6@U zE;MelPm$v%XYklB;M$(n(PYE8Y<`Qm$usvi+c!Ok3>)B&f#xQ~;i5TNiXR6@+#4rh zQ`e6#`~v#pqPvCDUs==v(!I8^Qx#tXT3$QP16zmDRD*{mJ|K7?w$o}xQatI&gaNld z2YyPe1*DM3?xsgHzcX?t_3Z%E$KaaX1RWjWNZ_DS_Q!L~(d{=zBCp%lYah``ey})h z{@$ZZ0YSoaF)0DkDYJfFaT}Q(^9ahu3O6_BDVf_wxPdiQk9bzM|=4?R|CSy8Vf1-Cv@X*d!4*)40?NcRV}w~*Woc|8`lQK2~dojr4s z>2^KyaKKHi;3!N|2wbVhkNp>L)9Ad(*+th+&+G!7700KvIQSWl%jKIp=HDr;yMzrH z9%GR7v?dL6?VY&9?UQN)=phYGsgA>^Bkm z&ymeScpYp180EDF!n{XM+NS=y(PJ(_dYRd!Jf3v43^lW3&=ch+txe>L9A#q#CS-9l zw4>~5Z{)>its(O_0x*+jW2TO_gRNV09FKS}FD{J9(kpIy=!P|(VQCFCcCYrh4;eY2 z65vel)vw9TZ>SN0{^=)MsvuD?!uD^uw zy2U$}j;Y(ED2*3}2UgaxzteEJR$xveY?=Lpah@J>o$^9uxqb51cS5wx+0T68UoV#V zGbis_oHv{cxTGWU`qA3fMql&$hKN2j*NMyV)})V*+=RW4*sVh8I#trJtc91uYsqDLsbB z*U|>;84j^&Shee^YaJeDZvOHNG&D|gfs=9pKi&)wp7p1rf{IC#+g3-~I{uwiMu zWv)OuRz5HA$azyhl(=)(v&r2ZWjSm|C$KO7^XQh;f4l#XhmNiU){V9V+cS;T-P4bs z*|i+p{`s^`=rVEcW->U!`jY7~FPUDO=^E3u8QAi!=7(8*kNprAgeS9Ry}fg?+tE`* zSJ{NkGHs2o<_&vqk5^H{zIj~3FVY9r*<-+SowtRe$YW!ZcgOff>9FxJkVY1^;eG=| z55#fG%T0zya5;AOpadD==`Q^X$lvSObq|7PN1M!&=r%GAkJF-zJ#MPiMLpK!qhqa@ zG=F6}XFQ4<^W^&IbnO&gX{Z`ONS563^i=p2LBw589Z_I_Eoa;?fXJZ zUqepkx?Pwvb~Un9H&uc!$XM?`MSSB(rY=&RZ@71?^%oGWNgV)9s)EuCg&Fd=mTl}o zbyYvgto-nU+>`aP%^V}1`_jjg3Yx6P#msJeEsPTG+-+6Oy)52^`__lhpqf(&S`n|Ew!Z*OQnUt*jy@HnS1}>M6E>mMx zshVEhXS_&i5$$!1!`j{)hHA<%XD3_3Hl0QBaoC zeU9#D*90A&tO|}rwVYNw-$YL!hdR%i2Ps`fIYb-!vCPwC?)3X?sy{w_XM0wz&@HP2 zDU%5fJbd;2k~#15WD?_pLTODNjeMnw7mszHGoP^8CZ&y1!`t-ul#iM7u$Q4q{2BVa zoFu%`Od=Iz@=P;+XhCv3k+F4CUblY%%W32-9mi|e>W}l7G`f2*a{L0ww*`lmse$&E z)nYXXF1JfGU(5(-j(pL>T>jM?6z?ny|Qu5*>0S1jc)SCZG(A_XHCJv`Kk z35>p-#pHV+nee*2#(nEr4)KxIil}dluaH+5Vum@6Qty)`{XHfLAv`132hPKN3{#T> zXlAAR4XD0pb*a(Jq8ByB-!}Ge)2`|;)^B_aacyG9PpGP98jsSRc5Dp2z&dNV+)#{( zW;aT@x4wPuRqUe-^<=YY@>tNtxW;Jg=f_l5AbdBkJ=Z^4&6VvWCD(eJXEeG-POMI# zc%!b>-b6Y^HFbAsJ0f7Z=v|UJbH4;rn=F|r+@Lzz{$*u5`Xt7 zaOre^^`nO;`Y&i#-}!fM|4ruqi;t}q@(+$kUI~*?t!6*7Md_a@uU>Mm8{v#1Uk9lk~*`X$#Q?J_tOa;KS%7`H_)3j=_JXL zskQYq3QX+7#q zh$i8nZ2qB>jPk4J$-;?CU{K9{lXl^i>o=PfvxJo5+)u z2Ju(0KHny&dkqzbPSpw)rc3coO73#()<)go3|46_5c%+tH0y55L_eI_z*6IKZMU(0 z%Y36+ysnCLZ)Fyl8&A){A=tk*8R(JSIc`tpTKiD<<)9MGg?CSCJE= zxRtqhcklYySb|jY;OTju=~)^?MOm7q6~>JVWr|GpAMgj~d8~aGebEf#r!f=%^dPo0qfS2) zVRK|F*K@1F-Mnq6G4+=B<@!!fk2g0{JO`r{tJo(5$=nes3sM!uNh~M1{GN7u*ZYHR z2YKz7509R2!P_y%!w4z1en>a3=~v3HOuw^UTAY54k*VWtUN1c4sJoBfR)5f(H+{Ij zQ)%hPIv<_7**xSC4`bh}D2`)%Q!;~H3Yhz5GS*X_obGK`|8A>9FoEDei_qZzaXI5r zl;cT(OUHVt%zZrNzraHb)!rl#EJUM}yLDSjVK#2Gg-2bZ@*+ncUoUv6k6~Vxi?+(Yy4cV>yJ z%xa>RsuaS@`uvYRkH>3jYqs4zzk~-f`h%66sWeHY>f4VC7#J;dlLE?F!Z3F6mMW+H zG9p`O$<~Xfn4YzW!mpHkGl_Yv&&Tjsv2Sd?^~dw1wIkP-3m;TDMrc=iopw!k=KV)x zn#4aw7it-rbsNZAtK};B-=Fncn;Ka~P8hrotka~uXv1w?Kx4J*SApkgvN`C)VBO0HdejV6wu*{}-tpsiyWKx2^prs9H4lIlF|B8UoS|Mc!@NR)ZbB=23Hp@VulsUOJs`?2vjw-o@9H0cs$ajd7{cCvwFSHK2*#xJ zO@yRqXDHzA<=uK;DRkRR|5ZBAT6szezL|%1?Mugk@Z;#K%F&A7>yT~-l^5!4WMH!b zo(%zU4>$Ys?x!mXitxtY_rscQcsL<4Zg+a4;qfF=I;PbH;uY~StI-g-ExB240 zZTIC$XT1;8oo5X#j1P4{>}vIViwf!oN_L6KObINV%^x=`?0H)hdk3XiClnbNrt6RT zlE#nNvRa4L?RJ|Rj+>~}*`1#9^dJi6$HpHQW zn{hI#lf9teI3F!xJp999GXF}h*wu>rnLiSv_Qphd4IV$1q@#F*uCSfWOc{&Hj_PFj zro(_8&z13-KATzBt>`}48WFl8BtD_AV3~P($yPgWSwA?C7|7^9=zU@;15mob|9rON zW@~ZZ!PJ|JY@j5HzWuYGvtA1iQnUHFtzUM%ZlHROM+r?xd}5pm{(gz0M)}IYvpiMr zvprX9QxhcNsjsGY#LJvAKz4X`9x0Ww@Ki)qOGu!qQ zJE9NMjXu&bu6<({>x$}%9>4d|EAwaHV_zHZnI4x_>%Ffp-{t>r3S{ST)CZZ>Xt;}u)L=8)Kvzr!2^*#zSsI{ z0lxm>)k?af?@egrc-(pABD6FucoH({dc>dejDmSW7=&(I;x6u;roy3SbG&<%n~p=3 zOq1GlvPPzZQmv$y+Jv@!ZikV#pMJ4H->{*?(k?ytC~-K@&@uAbXOX}Scw~ck!-*BiaZ>`TcffDa{7CZU!a%3}i)Ao%eKYZ_YP{z^J zm}W3XtfD*Bn&|&|otwI4#5NnFuCjubk$TII=@`&`qJLBq4@JnnXpOB zPT9&@UcvLpHNSfHR@<*u2d!x(rFT7TFT>RWFWtuN9*qVJki7SbT~C}43MfzC=w!{B zWC$GO@@&-m#Q5^!;3Gnet%gAav;ad9l2WBoU<5#MHtmLzESTlFK2ov z%_qcdIc3`ROd0Z0UGnARJG-*&RQ3;=uP#4VU)?mcpQhZ)iwf|?R6NSe(lc9DeXG;T z699SQb?~9(n+11CMMgjntIS>Bm>kRx#{KcaU>m9vpnU-6MMI2d(7B}6D?XPAPIEM~ z)h`M|w+PdR&mIn~U8~sq(RCwNzaf;tzxn6fmboO!LcPOmR22zAP>oF!gn8+MSv`Z( zy-snJ_pI&?sAcKky@N^qYIo(=cg-|hFNs%NQ`|$g)MRdbVSDcv(4Z65wh$`t zTXL|P&RfmYSExF6O-f7g=i`l}APWn5Uo9h=laoAYCeSpS6lV4@tk?X-xPjBXj7rg< zT6f~dyTgfXrAPUlx1S<{A$C|Z-ElqI%(++Gnpdad>Tq`!Rcm_#=m9i(}JzctJCdA&t`)1$TbS3Mu9 z-Mlne`Fi?--{*hEMB7(4WqaVb>WAzzR4vwyt}N#JQc14c61?V3!BxU1G!sA5>0*vE zOICKQ_EpoZbA9d~)18tYuU&UDfOD9Uua0|VapE}q=q!V%rZ42;?Z{*q&G#NXaTTf0 z2_EVa0d_9Wrdz&rl{qF|cTe0wOnSNb|J1lfZd`qlO4}z!@UQrw;xBq`&1m5+ZPz;Z z7tl3u_c$M2?@GR6XAG^~kDH=IcMp-x?Gtf%7OIL(jHN@-vwpl&8Yf?kdH5+G@hh0| zYG$dS>KjXb+Gq4$`SNjGromgaefQ^=b(6cV)_BDe>?J+fZ8rJ0&)Mg?@gQrT210J* zm$!t^M`uea2R$B-w(qzed8>tc#5m35P7(8MF7B73KgtV}92nrH*V{o#&A~^mr?LV< z?>e?5uuEy|B{NQ?ChZw{k0@J)^v@Jk-aPxQq4uqOkWyx%MBRax{HE?c?cDmT=V?Qx zp>FcHw8-lP+mTc1k17@$ZkRrlO!mh+#C)SsZT6_SUEKAos=ecA#eD* zKTKE3X7hR5cYXpoC40ilp}iikJEh(OiHE`JcZ{VfgNh!-)4N&6BlAM;^(CY&4qt$M zMSDuzo%NV{e{xpi)bpA4w?jakpl0z63+9wJk;=7W-iMQ(whHr_)QE1s0Pf?b910!3 z(R6qS6Dq{k~G8q&Vlb;lDgm* z1}m;p;p6)*b^cv3K<&@>je^`z6T0y*PHVVF#%6%$RzWmuGWk^2DO-603syIDIQqv8 zZ52U>F=d5U4LS^5lW?^dX{TgZmz!;lgY6k)&R}8Ht+OJEY1ZKjKD$~}r{gK$ViIaB zcwz`y=WW0zF=j>cj!VeJO_nHon10_~!fiIYt-0S<{gE>7i+X1XGY2cKwzsqkbMUjB z_}UKeU6-+f8h~9^LZWIf_+qJuQi$ZM<@FpzkLLlNl%#GEy%1$?x5v*bif+8Pp2_gJ zZW6ZJ`UMNODs8CYPztN3sTr^5=eaQRWJ#g>!wiDDqBlitmCtXe=K)Ak&jSF{P)~BE zp3zJVJViZa0R*rJ$Pk7%wa%t)6d};p!{+STu+PlAiT}@0&YA+h03t{{k9Hx0fo7#w z4SbUygfo^)p(O9|ZVx?`u#^&N-O@scSf0xt$W(`p%U=_OickZRu`<66wIaeDVJOnO zj7$63Gd7&zJVMI8wQL%>@^COBn)dRFi^b+Q?4Xz2vUh8v;1He)OdK8G5;w`CcsP;*R4j^HDuDmsj9eQ`s z-j(}&EpgkE@x12)HWvsI_xQ@riy`PRdlCK1AalD|T6+)Bu@WMF0p~2j6xM?XW(C5o zf{pOHsTT6za6;~^JN!5zR;@g^+P-($6-R7ig6@&Ez%nWEvY2!fHz25?rMBWe_Q}o> zih4F}`q3mCN&@z6ubyzG6V#216IAaw0iW*9o{XSZo3?ge^20OD0ad)>v7hr|Sh7@v=?)7M>S)^1CzV4$- zX{MZ~ss1Z*?RkV47OUH603sU57DHMtl#0PSGzY79=5aZ?g>UR&hQ}rtK+unKraqTg z?hX_gJbhr)HX>gT!8Y~^PT;o339zy2R_3r0jEyXxE%R}EKGjRoODNxG6Olnv9%DPT zO1;~8uh*59N3+n&6qdxX%ORo1v_OLRS#7658X8dUo~_UAj3*5g(4U;8fvddgUu37-jHP-_N?;&eebs)1w|4G3(_t7STRT zLF-&la%kAf506HG&gWg9HX*N@FoXgtdudtuLm5wl;3)tlmfud23z{b=v?Z`ggRZ$W z)hPgJcMy@%ppaNrwkcBAU99uX{xt`>ShbvE5QA1Y;_v;w$CtK5vlG*fjvHyppu|D= z&T=8ho&B!GpnNO>^mp91g!}hxJ(yshEx|L9}l#_?>yDU9M{@5%~=3lar>&ARx3wdT`8H8=d-G z&A|SOm_4o()YbVc^qIO=gATxDuq0{@5x5P3FW#@>g7T=++Il~}8=%t`HEHIkg1Qhv zenvnqNz8TxQoU>;!`~+D0j9R6;RzioNgYKpn5`M2u}p?lnwBv+weswxPlKr5zw-{ z4i`Fi6Hx`};B=)e$VRM|Bg3hjY~(r2~YLB z1DF=e(jq%l5;_X8Q16*2TuCnYRUU7iPlS$*<%l2+v_j`DaDCs*aUl&-Cxt`dT$9`w zyW>vsJeNyHk$BA*j>W6-MA`-D4N0JOr6gDOligPVpJWBlBs8oOn?@^t_B1N1Gn47e z+k#CEgI4m+U99wd4HoEy2P0+B3;8StlBq{S&?aDpt{Chr%xa&n0b^m|5e{EMt8^lU z-B__;LL?YNix!=5?}5DttA(5{2zZPj4jw}TSQXl{MN0%D83Q{GDBf9Ji~N z!on><_p8u__7?)GnsmowPEC0Ga^f{0U3s{P~^hx zLuP|f4-%k8+j2|}zNE$!*_l3Y>$p%-kdY$cDxn>&5f5Hqo30 z3kV-0w?oOMy?QtAawAww!s{qdiE_K4Y7oKfsxK(vjM&OM;A9=NAH7mCd?^A*NAk{E9r?-7$%=t|T^2jf_JgS?0aF8Zt_Ehv$k z*9iA3Rml1l!mPjz)*5*dTL2^7I(8${=p!kMmagsSq@(ZYZIQTdRy>0bo z!K|SFU9s3=wdMqH7spNB!5McJ8=qf|y!8G}iX}*9ec2hKZf6&e9wcBktfG7#p;8>= zk;G?U=lp}VpFdH*&BrhhtP7PD(O{z)VwGv`7d>vHC7;YpCuaz-!W&#tRG0(}bOqa- zMe;u*u`7gHI~770lt=KUXuOJU3Q12rl?JZqfwjSP3()0Fqe&NZcxi6CK1sfQPix6Vl?j~ERPWCsmSquOvPMBCp8$izsD*dz`*h(Fqf)%HTkj85ZMcCI4*cCX4N9leO zZ0-PSlEPpho%X${+Kv?@FciUipMbp>h3$`unXNwg0P9(*x1;#YAJ&XLCrLe@OYVr{ z-%ZFL37_R6mqNo?k6dFucx!*r>ccC7-(K$?mg>r@f}()=LMV|pGPTgf8Kj$?K#}>P zX$Owr7jtjP6uyq;H4`CNB_y=@fu2P)(;!z>;-D>;hKmU)?<*JuwR~8dGHi#F9jpdld_`8AOHIWojr#B;#;nFiR=r)^P z7UFfDzsm4wM!Zl{q0yEZ0`gfK17R`v$gkP{pUu!|D?)cy+Rn_M2%+vyA`%R-ZFKz@ z5mUoCH=jry?cR4N7Xg4&M>N2(RNxZdhcl4;>_e~>F!QIR#W6tF z#k^uX&XNQcuBJuEq_L7@J$Ax7aDIk5-*Un?I!!crbN88R0f}Yrq!%Vd7AdqJOz}guJ;0S5wXz7k4 z^oZYbMxrA_JFk1tLV~k3g=pCd9Y_hF;Zy5C_$_xl^G=~iiWJcUMm8;~%I@UDyvhiY zM}$4sA~n1-*F&EZc!DHht-pQc#X$t&j`=wsgfFm{mnVQa5$w8Pu4f83T=;c7_H5Z^ z^2nPFZ4pn;P>P;`cKw+I`|PX050}y~1Y1W3+HCV)MIj>PoC!kR)`?>h7|t>N1wftp zOsJZ+i#_ndG~6yp)>zOzQT*WU7_*GzXx;~`^JnqCm&}}DzBR=IpA4RxpOG6>NfrP< zx+hN83d{UO4Ka@V@ft<=ArjYF0|WeWymbzIyg64^9z@v53&8lo#Q6_<%~>uoT~j#F z-k;O0lKjZ#hx0RQ@uTuss3NhobJX2JBY|-w@aN%iJ9{MN5%Y8`4-Wq#5=apQ? z?<5)9aOD>x&7>WJIeupL&J=HBF$*4ZggZ+l4(O#Vg7WTyM<}qOj&wS$p7%uce8xtKa#r3Y0h==BSv838z&!0}3grt(o{RJ-VL6b}P*Mar-+oF2?i@N-&)**|^(vuIJm!TTHamnBDyZoZxVZ+@ez zQcAVNLWYwk{FP-&7_G~7WeT610Yhz`GfkW_CR&u7!4P2?ejt`N99RQRP@%VBf3JaGnNc$n09@}aONZQ z9Uv`08^Be&XN_qK)`3ylEaCPv^ljQ4smUe2ihU$++)e#TAbjP8O_zEB?pUXxoTssK zuDPK(nUO&0s~>z_8Hah`5T0fdo>v1^co6vwx}Gc`q$v6% zoiSePP&1Wkm4HFcp9B)Z-U(ghAK3q`atBsS#^|i6eJ;R&~Omx1Kuo!_M_C+5d;27UB0@P_wO2McUD;|mdmLwwiozP4-=E~(F zZPw6}+E-pdb=PQT?dh|^s$j15N7OLiaCqqs41H{@jJYF^yykoHl8>xUwdhyH zh#4#n+jIi=DJhuEx=QT*j7uZTe#At*tfoTy2^MIhF$;5T`779`XKF;_8cY+&~o_iNqqWX#ap+APJu;p6fVjk{Qo7T%VqU29ZxJ46!V=t( zq=|{(E!x8O3X*t}7Rz;b>}X%}Dg;&w+V};Q5jC^d9?g)dvv`2qTzC&yfiBAs@r;b}rm02)rE?=XaVBK{Q6m?NNKp1y2 z5_K$Ew@Xs7cFubDChS=Z(Z)$iIT0O(Ftc8_-Mb#KPVcp0?; zVL{)^1i;(4p_|8x3-F3f7ZmGzaFC@>#f@@}PbB1PV$*coY>phITPg{r|+@??2q zj|)JnQlrRlX#~IO;d^;`tm!uCI#{Ucg}Pa~UT6+zK>QSfT?}Ek-b>&G5I(#Rn%vG9 z6~_A z718efcvuOxJcJxO%Y}~Ar${0&B9bt)Fvl4<(PN2o1B?+69-#qUg|WVzu@*) zQ*+8cOwGo&)-0-Fn9gvrA*4AV@0;o$>-Qs$V1Q9KD z$f4n5oFe=j{oP-{2@{L$si34;QNxPjf;)+>-Jo91s+bruz`giwXu^auFw^**Hvy5x z$u!pJji%ddF?fMyowIs6;AETG-zs@0#QMZxoyETU!>#d)i`D;Ak{wN1gnj|^g@s*- z6QaA<<(6xZS4T$zRUR!?L_9N4l)5HLVAGtZS7z)f+kaJbgg_T%g2?z}Gj9XI7q?zH z#@L;zPGREgOTh)LyF+|}6U5k!FN<17+O3ZjU((+fLrn2zeA$xj%P3^P`e-%o4&aop zis<|T6qnOlRh^DiB!2JbsWNprKQIQp{-7vu;1eD8EY-HiENV-LQJ9}8530>)AwpS& z=Uv#e^>lE?Z88`f(M6p+CdpFCA}qEJe%C@>+Y&QoaNw1tZUWmNUVHZAQP25sSb_-X zGGIht8R!K^GqOc4f*4Z)8L-oc{|2j7DRNky`_iH3Rc%EPY+eN`w-wph`{tQ#&MaA% zf|N6RJSx!(9FfpS0gUIj)Ci$m`1!n~Wpu8CQesYRfrb>qDtwrC6HZXJIGa3wc$kG_Z6m;r+53@JW0=|n8 z{K(OzeCOgk&zC!L>5OL&8QjEXwWo2`LrYV_?Q?{*Qj$&!gw65OlDa5g{d_ATN(4Ks zZfpOb`Y9~KaB*fry4JmHoxm`aPLPz)lwr9nhnupNpcQR$x~5Jz(sIA>CUFRahSD8X z_#V6~n8-UCS>7$vtX270@n+35)YbRke8EiK(ZceMsF=!6`Zr(yHSIsl_}?XQl4|@7 zTR9`sM%0?mKsri`7~(!JBVpOqHz-gkavBa?PF?|2v#|k9{(Ehvp35+r1SAx(u2wT0 z;4jgT^)poXZ>HlSv4nO)`0{hf!Wz@hr5}~X1QTPT4re}x)k=2#9D8NOOn7GF=YYMY@$>Gli4z(=9jcORIu#xkJCc&`kZU?HzKRi#$s7v-Ldn^iX{A z^BWm8kZiVC-qaDZrmfKQBj!lqU%7x0ag?9Rk8)Gs-0=nTgl&@@;n%*ULK$0VA2jju7?xF2E9BpL6 z?ucN+6`^9YsHsSUHzmWu=~Z;%Pw#um>P50Yt?AW(Dh1zTFQNtju#zlR!O=n8Rs-V{ z{U9IruqwkXO)=t>T&fnw4(3OiP;cUnE?hrV-(iY;6(txa69Xc_2w{FZ zIGt_S7+2ZRr{Zu{&Jw+g942I3eBfuILaCIa1E}Q8BA51){BUsS zk7kz+e~wF9H#7c;FI%WJs!WC|thsIp6Htu4>1eFM0yAm`2)@wvhzKHztwzd-EG9n%mX1YaiFEt|ycS;k)=DfQOX;v! zFIlrq2@U&mLGqf?aT4n!ouWv34i9wE`WS*D$oq(7(j+(La%jbNJ^y6=4rD4i&=U5q-ltQ4Ipo^?X40XUUJry?GT z`{$#Wyg{sJ#F79uavS@p9UkU8%#Qqkj?kZgN3c{9F$_107={Q+G?X8mfnse3#X8Oj z@A{WO$q*I@s%Ww59A#ZVO(wehF_U;i31NpMo>voVil9dQGPfsn=GS9C9bp;cq^8*! z*|h*|nXs(vg-ahr&7`Q|?rGYMVqooh2t4_uHYF|mIX}vnp0BC2AcG7HfiQ*%LoTWE zJrHsj8YgA+V#g6jd8=2E9>7cA$Jb3IB*P|S7&WAn zZt+9pz-4UPRG(&IHwvd}NLLQ$cebNhv|uir(gi&;I1Q;0m}@5-U@7TgjHms!JId6< zc(X_{^jQ=;zxo+L!5mGw{ijdOg*w7n`QA{hzZ)?@?_R4Y_Bf_0126+Z4)|O)XPxm= z8S&8|q6|8azN8nzxrC$1E@=wrG}8xm(}n^%v?)tWYYMu;$CpcM zvLlZ>=GpYiIvRkJb4#yne6!$IMwReks;QXnUqO zd3o$=_5TLoz<{!B|G)Ww2nSFQY3fLKwCak94`IZpt_Ax3R4_mf>7dgJ;5XBT9B=C# zN(<69Ydh?nv6(7NTDw;7T^`sg&U}1m?L`K^NtSX2>taSDz$2f{355w$ zh>#jq*<(;v-6%}T(jC0P%=8P; zly_Lk5{0G87{KrhpLcoZcn|=tiYsNtbLIkj%mO|;n^q=6_d*pwAH8Z$fBbtm^-M$Z z-^RUZCS~p*#?afMP8G=1)V=TZHE+Ascz=e)!f_O8_^IE5AO3Rs&S1(7LA$r*Voqmy z7-<2^yOAuydsi+1fP(>(nxNLhhWkX%aB+0OX^B^1JwJZ|;(se=mGgm-CC^l~e9`2E ze4+rZh2nIg>V25tFa^5BBkPENQ7nMZBEeuHYNovq&|z8XR}|EdUgTf+=Kp%L|0{p~ z^>-2w;goD@^kxW?l8r8L$cYy(A{$NO0eU-~eeR!cRjO>yYHq~__NgKEm4B& zra`G{dRD^}on>rE1{~dyoX<5i>kYsYcpSg!=6=VJ>Jn@B%nA)TnXG_UH3*p@ftE;6 zLM){wx|k&#EzH~7@ljA<26!#*Jp{`DZbYKcNL43}cZaEkJz5Z*-6Yz8DgdLF@hAgz zhr%o?m&O?x11f6VkkOjwT>YU1#R!ZeITT&?!{U5a>+Hj6?Gs#oWT47R6tD^cyJF7; z!(W3V>{_8lD;%UOOzlT8#x0yWK!fP@4reUwr#o@&1I|{gn?`4smSUyVRZ8?Gx!8e= zKcOG^jC7;1w?0;K6e>sBD66*MkNk=<9{LQU8Ia%+2#vhXNzCL0dH&@f^u)a@6Lt?Y<)_FJk{IfU8DRf;_I{+i)RB+G6#|G7NfWgpA)5k)d{?Hau>D}g|X!0Ux7hn91K6}91a067;6@PkJ3Of(a6=6B`llZb!>WZz(}Mmf4;~GGFkl?O`e#sve+2RuW&eoezb%WV zR`1V$R8&)NXn_9z04zNf?LT5pC6OV974~OPP$C|h2Kpl$Lx8A-K>&ZwgGu>w#r{?t ztc4CY1pd@y)an~T0o1PHwdQltnZ z5PI)OuS#zMDhh<&F;o>nLhntgC`t!GkdA_drUEuVuu!}&+~@tiS3d83f84j$@2y|f zS?iqaHZ%Loo|(P(nLXNQlHel!jsBzVf9eMe0O+6q3Lu(vfdJ%R!TnEl|7C-JmPo=H znn{BA>mRB8Kb3etyX9Zg@?St}1JNXr{uff6^y>et%yBpBka`bArvv>J?Z?RcP865| zP532ge_#$k>2v@(?9addtxeRiK%;@*?~?>rZO^$E;r>`$xtx`5+Sc zzr^8ZYZ%o36?Bn1>t(vN?R4~b)c&Dh6v zDgK(Aztk_JcJRMK_8;kgj^{sfNk#~i8ZQ8VsQDe>IN& zqKp0s`d`BOpCk&B++)$SAOHjfgGv6eKWPS$ykkwA0jyI7F)*sA>1P1%?_odQV#)0?7t ziW;|mkC=WOb{x4AY|leH7@$y)%T*ex>lr26dPsHEGNGj>xJ79mMm|#Tpc7*9HIFVx z93EO25s?7HoJ(lhLhV1NwT4C|-hFeT+U>kcP8L+jcxaux<4QyrogT`LBlY2Wxhcey zA(&Q`W1^ctyKjRDnJ{$?kn}g=I$!v^T7VRRyl2Z~8;Npq zE@}#l%8;BS`5iC+)A5*etVEPIj^Zu`@^R^`(FAiXfd9bX7xiF(PAI9=@ZlC;B6TV` zUkNm1Hzy?+1qv8sARA1&oPlzrf_WMyxF3C%Hb3FQtZN~q((pBZuu+b8WMiN*l%R~` zH0Y)TzZ11JCU=<*56lg3sNTsjm_>Q9LM;lA!!1De^SDXxOD^KczQy2>N+eI{Xm>Do z*r4-WE4|jsX5R*~0>jE%d{N|@6Xt}<{%RiPn_m8$N2quAJWdnmcrYguB>?AyzzNmV z5blk|-BQs^FSc&-eWUf6QY7Aix=I0{>i5#l z0%yK_aEHkS5Z+7zaSIIf@sA(uA8|-Twb|x__E8ZVxIM{27wE9hhT^s3+?-1 z9TtKDllN&oNu}nQ>J03<8dLYhM#sX&<6_owb16JWF$Y6Obze-AN>6K6DzxhIKJ$*_ z!@@1chS=_?tqw`1EdL7n>CFEM4J%gYU6OrYPgZ5vLO_yPS#ng?yFlVMJl9 zp~hqcHs^JQvA8fOvq7IB*ffI6WK(*%M=eXGD4Q~BmO2FDZw?a@9`2L~@*A8pmOSq= zY~`r_lY*_oqYHihLt%zlsy#18UvBD+M^^hMbiMO~`X)(9n;3|zXG@nT^WK^mzMp*cVf|NY0n@GsVB|c zM5H_f&84l=UJJV%0V0fX;{@yaS9hKJE6jtTn|X&!aD~R5z6Ktw7J@sp1g#@F2%4mL5GFoI)kz ze0_54)-<7(j8VI%*B(ZyELoer5WDxorZs6rIHLAm_q|N)Is{+sy zalQK`fT0@$G!z^|H)DM!&qR-O($#`O;oBauYj&?xxy|d<(uX*rOx-w;CUOk;bDPLbp!()S@>4WJ*%CIdEj&|Hma(2TZ`K-vr308brVdr(C46&~bMXqe!8! zb--QnEzwes>+_CwWcO4l;AG1>5wsFmY7!r_e*NdGph^KP2RumbS5INUszl=_n&=V) zCe@secGAy(-_mi}&N~_-Eo>5@4OZ;?iTw~P*r{bP8n`6Rw;kxU(U=OQ`+bo9au#{VNVoCb+Hgt(ciPiT=z>+TyYH)b&25T&2W`shV+-1u@#zaHQG=_f<8qJ)yBZwvCZ%|RLY zpKE56cb4A+iSMbvh(bj0nWm?_(R~-l^Mx#>EbEdgmPWWuprMKA^cwEshx`ZMNn1Jl z-^k3~IYfRB-8hu~@y5U6;MoI-E2F5RFC#j-+jk9G(usJz7|R3h$wHTaq5r8iOBtvu5W~OI6D{*$aW;@mvmT2__p)b56%YG&sZp zDm%4w(1{92uL&)i>sb^j7(iStU6-mF99JX2D?uFJT;yr-ZamIu>uN^Sp zSdde91-Qlw-uwIJ-YUt}kr!SU9RttZ!`0ReIhA=Bl&LB1RnMz@vAx(x_~00!con8r z4%+4Z$v%L)soo|H>|Fm|ULTQ{PsWn3>{@eG!q<`Gp~jyw=>1hjl9~)p*JU?tdXXn| z{9wwTvL)L0RXZz>^||dDhD{zt`Hr@gE=+QI)f7#9iBBrsrdY31pT5Zr3dP4Q{gb5a z*j2jvlB*^*bByGA2{)66m$5pBPPG&lI3_!I;(FhF87~mWN_>&rjyFpu&yVuecJ|U5WaFM@7G99xV(N3lk8+q;53NA1bZu&zs=4c?hS82WR;j3}; zP~EIS`67NgK(K``#yxK+W9ZSlsmkG!2^~Yz>&qtvDT->N6Eyfn*Bu{4{D|y{c)(p) zf64dTC!MMD-<0lNd7*Rp$)3U1*Dl{DXN(;;s%8eOAFgUfrCk9Aq(nX~iI87AHJ;P;GdyD$JU%XBa)+X;Ul$T6of@0vptkFee_5HJUllO8jr3_NZ z5c=_F61BDW`ocX=f0qAI&9h_!9Wu#v>7i2kK+(Z-g%$@G%kWS#`Mu?+djDutq=rQ| zwh;qQj4z?^dCR-W&cTLmF_?4}r#X3na<-!|gID{>>s`S=6-O?=0knGK!Zn8@w>1kh zN^RyIFH_7LpVysA5H1C8zL@q)f7YXKkSJDbJZI~&By|gG6BUk`!G#23jfL?1MWsxL zyI%@Usv%D_x;UI9HJdlV!Dcs46BNlBE4gR2pOL zzQ^C7%>JnoO_(Sb;f~+bDO@^JwfqvkNPS@qUgw4HIVbD>cq(Fj68i$_nf!jg#QEL3 zz$3{U%0@p3g%>8HnV&z^8!{!9Y6@_vJoX_ll!S5ytz^0CJ_gl2v64L;e4DJjVlw!( zJoLp!&C9x|&l3ENry=3C3DpSLHHcA38YtPVm(gOfJ|KIM+y**gMYhk6CqZvm>@5cPmV-UMG&r3nrWd^Jpx{XDI3y-o8)X< zGfcaA=rZ%f*Hk|%gB}FOl;S5hHzaSWmgf~`#r~+t8^0_X?m}b9^wG~WrwdoWCVlmS zytBz`(*FFX0AG5dM^Fv+q|8fE@8@_GhWpHA^(Siwo2Ec)iZ9EbUj};LIHMGcx3*_( zzAjxS6yLj6a`QrvCCW>LMfB6VTB7BI$4i>Y1qCD6r$a`3jq{*NabN^5kpiyad`{_7 zfnG6I_e{LmsfPU|qLJp*aHY&eom+|ktW+*UO!j*(linDwcu^Nk4{uq3!t~<;T*nhfVNP-Miu3rm=2*=WoOvN0#THe>8&7vAJW zy`^owlM#U5(>vFA*td?6J6V;!`^G|x^Ilf*2}6!TH5OktUR#;7aeVi9N_*hs_J-}M zFV@s0?5rsButs&-1aD>Ij%B_=) zJn-c*-hI`tk9x6K{h^M}AI}_IQ(1lTfBRu)eNw3C%rMiVo4kzo*!FL@m*n>i{|2D( zlO22#eXKnd7a4^{4a~&!$Ru2_bT@G#{EuzSsBI7vj0{}ViJLWU8H(N} zS$gM7c056RIauyH1U#l48tj{TSYsm z8jGcOfhWq>DFw?>hVWXP&X5K+xiVw7#bY_n+0eA^q{qcb7SEkG&5H#IvOG7V;khHs zgXqGjKi;0~8wPKR*v21(%3;Uwnk;7A;|v_k>ttT$*U9uQ*SPIjm~OGOT{7mcM7k2- z>que96~#JJa$_=bapHtc(q`ViZCl4Pt2*9ImUT2)EG)W+N2hxtBH^cbuq5&3*5T{P z)MBSkJ7)6xjz4Fv_@GbGuGbDsAT`lEB`qq0cs}|`sE=@sG3M~ixAxn++fmUcc+_AY z%3|_cwHNZlSypTCK9r-4@vdpo1s!GauuE|PnjslW&J9|{pZ8CH<)Jdh+}VGR>Y0?G zNuQH7xiIk5IEgX|*Aem}>Kg^_LB@X1g0Ui&_(*?sBl4%& z1R8xnk*@jyM^g`vPoFqo15%A}w2kF;SjgvFD=)8150b&p#KD={a%qBQ`Mf+@T+{18 zjiM60tm!i2p)0v8j4O;itaDmX@m*XgPu5>Oj}^y)2lqE*wfV4ogm=7M^vDx>*|dD@qj=-7{m zwW{dFqsN_FZ{@z-wMGUV0BR7`kKOIeDasSu;MJ&Fg=3vzbs;$Zx>9s&4k*HH{;3 zVs&tlL&paDl78=v$j-@>iIS%Es{HViKc|j8rtM?KkSxaq?yGKhNB!L_HvQkDdB8^&H{5`{|k>E%tb>e zEN~iEBkU}!ZQ;cu6NM*+Mc|BZ+yj0dY%u*P3u^Jl!V10bDeMvDK>}3?asd+|;Hxtq zM~Q?-@8Z~Yja-IP0jHJ7E^g`GAKmw}#OAcdvMLru!wm7~dp2)36deseACY-L_a0A` z>~8qrxj)N&pp%9n+5d~}l^L-0qxhh_8c!W<-i$o6NbDagSMWKvnUwGq(coa(((I9! z6J5b`FNF%0hMmZ_$i%?cR{3nCLmX~o&=U&{gedRT8Q)XD(lZ8?Y`&KfEOQjvj8oTb zK@vVquud0y&Mf6NK+ImdXcPHFqZ>`pt^_`vrOS5_hXJ9%<(VDx@?EkVJC$M2>v%k< zd1>}Hn9Na^k(Q1r0i{pQ!Dpek4ny--H(0&O!OzD08f$o^qC1>kOlp_0KtNvHFG~K%O2p(d0s0_j+88*ncu> zH&}sA%g*jhluYdj3|@g@NR=4c;-seQ0D>G!-nk^hD(~+@?q}?`X2^n;rcg*x#VXuG z-81c!Ra}s2+rIqDR7nvTKcD-u*+R%vP1+#XB``-ZRjk>>Uh5PsA@*!UhVcEmTUe5iYlgU2Ms&+1MAs?jZglr>} zj10ymXIz|NUMD2MvtcZk*i6fiQ@p%a>FCr7g2G+}8}RkTN2MoI@&>80Fs}1o%GtlF zD{E@-)t*}@wOq>v_S%7_<8|?wvC0XqhGBnxy3ah1Tu(}KP!y%28kdtQX1Xbf>{jKV zQ)K!@G9|$?PgV!#*aRXke?8O+?Yko>_as}ujIR#7(?#QW_$=Rf-BxXbGhJn#p|4q* zsHNRhpEfOfDvPgHF9_-JI;u>{TF|}0 zH0=gHlo1jT2MMj5+C)g}=k#ObrXuY0b8-Y)78dO5pF40Do@K4{xR>8i-a5O4>#6_n z^>NyOu1wgz0!zfxu11f9M3@I;#?HB~zkA`1x!2CkGrHp6%dAeCCC$vPysjX3#|~R$ zK?stbj5jskdl0hEcd-eKkYe`++pxtm(rs66{b=q}Iv*YSU&{QV>i@lDc6p;aT8IC>>?rp6 zjbD5nR+pyHxyK7!f#j_)o2Ndzyv5?sV zyz}7eXxNBklvFE%^Px_}zwh>JnIn6(f;USwmm$VkatmtKe8dCBFQOj?IKg&MD7TI+ zNA@0KPHq}hoW)Nm9_O@V0x+$M`gVs-q%t0Xb`I6XQ{HXigQ_}3dmj~WTI_jzyk?RGdBe-HP!&dgw3?O$7c#QUW z_Gwm=)5ZuyQVRYxhp!g@TFdDUK9_#9QpVRL$wxD(Ht;Il+c^WXYef*l*Hpr5O#HT|Z}@RnBiVMunDO^@Od zvrJ|y+Ix?TPlJ;DI%|kE$zRNpdlAL1 zqd_%V--x(ecxG+ZaG*W(NVrAa2E`}}EHwP6e=rjuS7^X2VRtv^_2~7#=Z2di7cKdH za~bnViSruC>(GN*u~*t77PbMhcP1v3Lf|DGi5>SVjsk1rXc0+I$4^`0PJOPq>`t!S ztx2mN<`k9rV!W0@ywf9`@25@DTju7-6=LoYp{1^@ru2?U^bS0oES0TyT;4bp9m8KE zVB-5?#YuPjqMmI-R(__uTJ>Na_|&_HCg#ad_SJCVg+#KPfhtGspEm1mRg6Jil%pwx zreR@4SeEV;R$vH+z#2aa&D-rTBKsCo?T<4Cpf2_WD%^kUo-x%H=ijeIdU zq8#OKKw|QJPh?GEu=@Cn>^ZU&fzs-LTkA2kF*oa7YpcNa(it@B(qEicLTH04%ygmQK`)%vzh3_A(etB@kQMuv&QbIQX$dDU-Gpq~pb#*r1)1w`z&BRkO zxI~r5N&9Nx2MW5iaYA==AC5uWC`YOpyVGw{a@7aGEnM~2b!ay%JX*@XNP!$7jC!>O zfFSDjqD!rI^&K5^I`Yb>dDrgNfg4!ig%V4_1)m4$e&DmSGt6+FJW5;$Gq@Y|&Wi z#txVX<9{Wr&laYp0v;fa4zDrMnAy(DmtbP8WM7Nc7JGWrIJYy(De6<+U}u`U+ut?j zekIpTgwJl-xro`BHy_V!;=-zz790PRI~AKT8Km8~5Nsi{+E4pj$XY;i;&s3tEi6ir zZQ6L+3G|(TLJSi!{K+?y5>$)8DGrp!@`r4z%j`7r5@*$p$n=FMqj{3YguLxs-zZYO z@Gi}$KrHutmt}YfQrG;}jV@__#ZF`6_x+_rMtd(pD=a{ZZCtEJx27w@W72QY&XJ^< z0-eAgjV~BqqHG1FUF4V7osxDTbKAj(m4EcN69P_AubqDKO6;DWNBhNydybZi3YZz2t&|i#ugcxC^7(I*{dwYrH7Wp1+WQJqAPX0csJD}7F zl+?xyd#k~vT(Vnp0C7DIqZH!C$dc^68ATCEBL~|C*TjadNtY1A!_VN8+o4u2hFMklg<<0Mun1c zj*UvHHd4L&DeyUKeAOr^K{1B5Sgio3HOa9h@odSyX|qv6D&8FMHI`dJD{w`4t)(J1 zNLAx4g@}p|S^`$Do@Xt?Ei$SUbKLGAeqg3sq*ao6z44Ehe{ZC@``-Eg4DkQqWN0q{ zC;$Bm71q2O2?OzL1IioonOP5iJksa)c@k)tyiWGG@UkFCgrC6)2-tUQSJ)UHrEg}s zrjxNCk_YeD*V#@$U1lk!&8ye@XeOpRiFRRLpzn57a$vFjI`6ACVL|WmRvQX|h}@yD zu(&Dt_6u*fCG@Mh;pON#cEc#*1i(@`H;S{w>Hy>rab<0LvYIk;RA9o(xoP@_l8~ny z7n3X86`7O9ucjrQN+5T|lvE~O@*(lUiM5bH*mwh{aamqp0JYuh2g7$xnFz0h%-u&j z+!r7sL2quyc@TF9QkDkY*VP;oz!pz0BgJ?V8!H)GB(@~eF zE0*~U@US9H^Rtg_UV2FeXyo%PiSJAHpS$n9jk@>*{OLCA)fuV>mzb~q|C0Po0EwAY zsa9YD1lv{}lA6-v@WZ7B$(#^ZeU)Ny7cAPlc^|tcmZZq6NN}|A7IA4QhOcjgM|H=y z`3hypApGr~yI9G@#&xS@*zXPk5M)R&)eXgYwm0nz)mTdKv#!R&QQV0t`cOMVIFb?{ z#G9BK8xYz;(?Q}*Kg}tz~XI0;TBs4dtr);;@~Tk z1cApXZh|m6dzTJ~o_R8@4l(gXoohIvn*K7!U>`ZZwY-q9Hj7DJ-cVeJf0F}fy;KMF zCd|<&B5!FJjM1uY*85qiyz@}`@X^zXa*pX-mLFJ>GXCE{A?t93J zpSlI&2Hv%%5q-B?rd+7c{?i!B;?;XFG?PwOl(fc%pNPQeMlEwy{BRG(B7~?F@SU4hCp`02Wa+2EeDUWHa45NDV%1ygBgankiVbeyGGxjN72Ekz{RXLz-(<=9B`AhGG z#y%v7h)}2AjE3@H%e3Z{CAX--X}jqag?9R1gr%U+881)Hz`X#AI&~pOE70{pjdM|q zDetlbjlwW+ZXX`TR3I4QB<|;iGpQcFVwGbdA5ZB3i6xK&Sojqe~Ku7J(a}te8qMu{SK{vJ{CDDe8ro<)ABR9YJQA WJ$+PzEQPUfJOc2F+^St-D@Aua?9QIy&&8k_mX3gx`^Lz348;D#@8Kn&2;NU>2;1Bx!3!+l+ zwzu}Rw}S^b`QC!7pfq)Uui@6A6cj9V^t6>x8cKi~f^hR)ZQXoe{1D{k?(40mA`ic5 zYyv0Tf^Z-Ph!hfoxUFq{Jdrv&8qmKjpXUKJC^I}*S^t*o|6W0AXYXSRK{#+wEobZL z?F(Ql0E-6rdSc-W08`uEvbFDzF$7V)fS_}x zfBXjOu=P;k0tD#;Uuld(5GE6X*d2jvvwzcWBEays-~Lyczx?-m1yX?UaB;CeP+;JP zPlS&TgW;beBqSgrIY&Zr{@nTVq-2!jq+}Fi=g*VVl2cGo)6me6T)0R_OHD^fO+$?p zf`bQmVEDxN_{7wt=SivmKbPM<5CsvQ3Em?-90~}R0tb%*=XW1y3=V{ghaGi)7aV+C zJQx8X5vXMZ6`%_5Uu!`nJ^|tHWrzfzamn$>K`8&!1CE9e# z!3RPj5+N8G#qKb}qCuiTmR$D=2q)1{70w*7#nl{GcVaIzj%G(-aFKP-`;yDXo)0CH z<0K*ILQw<_nh}l0NlBrIz9G7B>*%T-f<_i3oDy{(qNmG@8%q$9gJoodk%Wq3avXY5 z(b4zsulijgn3jX^P|tynh~a1)I5{zl5`rfZgJggR#et#06*xx~(TA3UQ)1%>2A+Y} z$?YIGWaoRy3OaYB12F%sAVL8G2?rNY$Z_Bh4%VLlre%MQgCB@ZB>#H}L&8F_6jI~Q zanBuL6T|;nKpYaNgrSkZ6nG-& z3Sd42hlmlEN_8L_cHjhd1R8>e5(9N-1^^*QID`Wa{R;qmutWf#6npf&aB>vT z02FVTt~i-r!ND zgm*J&AnR7|y!bGii5{ckp zi|-(E{Y+v3z$tGLs)`83jyed!0aqAsGA{UG=P3}EFmgK}1uzo9!YP3aIYH^&olq#z z^b&-7E*!)Nk@Wp{a!?;VV8<3YID||>fVjgYLWH4U5DqL9gewt&9M%#P$b$$pF?{Hg zN+XCKw#U;DzU~N^L$)1X41@m0W?>LlAiYomYMiqlG1diPNRU#%`~n9ZMhQd$lSs7l zuE-;B<#;upLk8J)5G3m#LVjifM2>z+cn(uVfGf5b=HC8nM@bC3 zSONyKlLbiV7xMCuXH_U{++ocof^fmm0Z{<7g680Wh5_KL1PZP;!zppD<_ki2p%nsf zN)#bl<`$rYoe^VGPa=c}1-uY&1uPMD7P;UNq^+|tbEr}&1g9hxy&MYRjOc)s;6s_W zQ7~K-1TqC+qLhOuNl=JTkWqnhRd^`qVjvH2V@D7y6oNw+NbVFsFtp#re27#f4H1fj z<-$+4b0`k2AmOqy5Qq48@a;Ag!Fa+X-Y33JgVXBJ_u** zM!HoP7|(JbNx&f>0SQBcnFI;PMFHJlo`mB913?dA&BQw7a`5q>fSOr0TY25_!wd+I zGV#y~A_C(U5h{lWwIc#@AgtervDYwczJlQbAxMyPfPBzAL>~lb0amN=B_u7(;|(V+F(}1dIVQ5C~E`)a)I~=MV$|UqLwV zP!MY10TcjZuL$h#?}GXZk4u8B{Qtj%VoiXBg3Es@3}iuJ9CAXUbcwDa3{eQQM#C%L z(Er2)UL&|_>exeZ&V>>|a$^l6eIO$KQxWHCs3V7TqR>Q;j&hK(y)E$me=3BehVx~! zR$%skpl~6lMs?{b8@d0)1VL{rmglGkbqUzveL83xjd*-a>))K@w#hq#4w@|^cZ?ZlDM(BxUsQl zQF;s|h|T{|@FFU?&2)3g#$1%F(~+=n8bMV0OOX&$=Sgl35U~Ri2_{<-n#}|P2)fKM z5)CBOmqRh{+-rrKhLIU?@Sz%Yh{HcM*54sDC6Le9w3yqHroI z1JT6%pzCd|>J#6lkpb{}*7hyM#q4YHe|4ZD zag{2(kAl+C7?VX3B>;^jvkF;bl2YtkMHxxP;v?1tRS{uwurrlFS=}S);B;dHOmrQ_ zO9*8a&hh1jMZC}S3O7+AgfFpOpp=H0TIO6^{enH&9=#T$r&fK)^2P zQYE-R7c;i#CYGf!rs3k|m^L&~qzKtK2{YK{X%es_GSD~#YtMJ$>Gk^s4w_C$fkH^! zxupKlb<67wq+u1B%01_WYadCN`+dIlT7|b~fg27%WgHsO2s8=?)F#0xSx}VYs&THN zwhmkZRjMN-8ZK12`P&lvx=9aiNsKFJK95IC@@yShRk?7ix{G3l>rxlYrhP{$wd#skV0pT+P-yZFM>K~7Gv<>){{3O!*we)qd01a)265IngqTlS zp9uNqq`AUF<=~;-jANm2awK%0D8`T-mJ`Y3t&%;_cD^s7k{9*<_;b@AH+Rf1MdoT$ z=#wB*og7U*#LchP3GoOvf`m{ImCS-7P*(PO;1FCX*}y&-Zs1}Of7fi#dOp$pn(=x| zhf9RH!OjHR!Fdv!2)Ncx%kM5jFr@oc>&E}P+nb5=MpTFNxO9UPu zW2mTNbBg$8?}Jw57n~mC1{()lBP<%x1j?&*us%!7;Y8h9@SM{$ z7#{50auIK=$-6T4Enw(Z^pl#PC%0E+FV=stRR5UvYIMfEeS6+}w8ec!{aQ7P97OYw zm|YQVPuFj@&@(~0J*>j=F1=b>DYrl(?!3>r%}4xRm)9yAeOrra+E34YrFn`*KcPP^ zjkhdAnT1f??M%zep#k%3=bmUHeQn_DEX$gotfeEtFHdHTzhJmdqALT&@1pY(zf2O~ zu6altVoi3LWOVJOdG9Avjh#+GrdruF9t{3Ud2MfdCB5(SWxS2+Mh!<#eUBz;E*t!Y zo_#g_4LR-j`)%q9+-)C!W$cui4;JLqz-m;Nxz{14ArZa(_fqdWGvyXG1vOC~`*iR)vhy89)+>e81dL}s~&xD%2+X@8iXiaH$;u7s3Y z<_4R8S;##3WfCIKZW(g-$_L{g1&A=xf$TP0ji&g+&D=%~mlYS;8EpE&={2BPztL#? zMo@5jzINd72jSq|C${foTue4Tc1{``Tjsv}&X?Q#S~ADsNVHY*B*@hM76{gd6bK9z zo#u_J{jNs_N4T$qHD`rF?E9RHTsgJ%bhF~Lda9k$O};;oz1tPRq1P{#reu&ab@T3n ztnczVjlLe!&P3c3$+s?AroXQLZXRTrwtak|;lt5&wCv<=z?Q6T-ix&@labrMp>lz* z=Hc`A+osZ~eMWm;$7C)z7TXxD?Eh`N$mnI?=V>jMj_&gHe?HyJ7!{lScDJ~T>9hSc zrMaAC(@kvFen`Qtm_y&;^DJ>+)@FOtK+RjzBX%WIa6%^J_Vk)WcT2NeBj*J%k0MF6 zpsiT>lrLTi_JN(dG8zlJC(ke?k|I*KY>aZ0r-}T2wAL<$MEndGZ~TJtyq#wfd^P6v zl(96|c3t@-<_pPP(*sK$7~?p-XJ7NGkNVNc2hHgBU-BH~fTzhA%4=?wX%#9ixaE*6 zIF<+rJSl2#@8_KFE|WyOHe50eh48Hv?NkNFx3YeG@zL~IuUMAeJ5o3B*-_`HkV(C* zw%BIYL2d*AMdx>upI;As3|nPrHIXKMSw!c*)%<$0 z7Zmg%)%KXFdf;}R(d(WcGgks$%h*T!xL|j@(02S1HR4+}0UToS-_n-$?M%<+!(x!Mglf5-5u)0U zMAkQtm5Q1!N@^y#`{dXgPy1eGuV&%uAuuQ?*VeE)-$-<%c&Yw{pXS{IX7KSdDc6(Vkab?}YsVQI6o>*Q_g1In z+ZqzXwIS{MAP}+fOB-m+d1|Yf<#15nY4bLdbemD;ve@?KpcwD!UUSZp*>-I~+H355 zR?QJjFfCMzp5qEeO6;3GAtMgK&zkY|Of;pQn<96u<+)tZ9-Sb$U}pb#`G$&s{N^y>u*ncvXP z*e|6RB>TFSnO8wGLveM2@7O89tAbEqE0c(}#dV3N^Yv-QCF-NW2X3F=iZ^NW}=AoOkbg<`3)n&Fyo+jP^uXhsx+4}iRp_;h{6X#cG&;G+*u4{Xm>K&#W%{*WSsjU!}%wr+;k<_6nztOI3 zscGF(f~o4I;X2WB)7)xa(4(i0#&{D(>z5oh4oldEJKAG>y7)yh6NZM~Yd_ZfmH9}@ z;X99Twd8maUE=K$C;wARnf`H^T;3LyCiiJ1#HbnQImlzgz}PJ^b^v_z_%9zhZ3%I8 zcu(i}n76;|TwrgUeWFoZ5~ueYGX7Nh*d@jiNjn8*SDJo0L`M0f_GH$??Pp^1&c(5! z(|lz5=)ULOO2>DH;_`7eD?jmOrv){l#tgEPX8H)h|flX6Wodh)|R zU8I(LrZ-Su`(X7a-A+S`%ge29V3=2LI&bQy$JI_1nuAlq$JAJVtyRQJmjurFOA zZ=d)w=wIwMi61oPF{x=Ye)p)?t3HH4GDx|)p&6|ErsEP_xrAnp-cLw8W1sg-a~)_^ zQFLh_i`qIba>_1yLX=2!oy~2%>ZrfZqn5=-C0~k^A3;0++5YqqZ?)LcN^#)4I|1x6 zt8qc&p7;WLZExj=`XkfarI2}72vs+iPVGJCRP5=f?_pW68zKjcBjYNa8QB(hx69bt z%RG)u(N?Q7Yi?E6 zR*jx!LKzRQWQxh!+S7Zai5wNa8l-PCT<4fGrpN*VklSQQx4fidypJwX_v)k7^~Mg< zciUT^ZC(fcd?X4H%vhKSOd2S+oRpO3Y)>^k%?Cyi(&*1;6gN`Ow(h4OA1b*Hft`Tt z^LmRA#C=W&HN{Y08_7r8_(@YZr{2a@Ks05m?D@E;;kSFYR33*4xG?;Xs`6qgPBB`@ zzLwDTe92Vft)76U;D4#Px_6e$RJW?egk;|+IOJ6j0JKBM8aIbN*qoYl?m7UIaT%o0~GX=A#lPXEvM%?}ug3<(G3(BUF`;@w`3rrxeep z4oN@p=G^5~WHc@fG$2rhz$FqCWjI_*a4r`S3A4(YEHrotOu@c~cg_Y0AN}-F9e*a< zDB|OV?EIA5DtYvEzOQ0;Qbofl^Ems|a@|m=4VyhfhmtM)whA}KzKu)E7^&BVI~A?7 zg%mZ%%`SOdqBwud^5PR=M&mS1KXS9;SCwszv{OM9$vQ*Edzl{#J4>i3L&;Qs|Ewq& zc(bAd-{8pYTtznfd}!DU@$m9b*(|UNI`BTpJ*1p-R4^O!Zo5+B<-Fbp$=J?)L*D-0 z66yZ&2MhTVKSkXhyQ)ICO+^G?S=6Ndu`lHtS*C4{FZ+y_Z;q+DNXVk7HP#ADd7L;H_VrxL z*nYdb3o{H6t)mSxO5>7&Ut6mqtp&7#=v_u16!TOLrYrZjmSq*W2)<{#?_!qJP;sH% z)-r$oVUSoXH`hdOU|lHke%ZX04KX~FG-Aehz(+~iMF74SLRJgr2<%Eh#L%$FsZh+> zgfF*Z%T+BpWL#(OX!rq!_vCnU-tZ#?8Ko$q9Y_VgWqd>jKhwUV(>A;lL)9fwnI<)< z7^rp-%e5BElKs3@dTP;CK+EtOTS+B%mf`@Lcx9&LqWAaRT3NpjGM^1{N~UoL;6sac zC@73rID6p8%qx3J%PNCc@rp8^FE9nHu4GenyV)-aR7$kQ>#X`ZFjuyzsm_kP9CzcZ zYYl~PI|~Su6|**L9wpeTl=iz$Jbf3)Dx{N8age~UA$5N7Pz5>SpR+A7;LJnzuq+ef z;>T4ZLy?kM`9kwm^3jSxq;VaDjDq8mAYmZAw5=uFE)|eS4?1+YuFdzyANr@a zcRq{BD%@PS>K}Am9iAtfO|v6ZE5;jOZj_hM`BQ%ky&Yb3pIbTjMXd)?2g6(&!#s<$Pp zB*#O;G@Rjb*ys(@7!zERh$(xRI$u8{8+&8Xeg(THiPNv&ar<`5>|y45*lWjH#kpjf z;TEaAN7qaW-Y2N%j@8yr|8jY;=?P}BVrB$}p3+^M?HiSan{@SHYFbmBNPqdFfq}k% zdU4>C!IZBjb!ibkvz<@Q)uLLSOKeN{$re-Iy?ZW=feq~jUbBI{?l}a|yV0f>9%TYx zw*rNUFw>nh3-mYLi1nV)ETzqv+8eb?JX^Pw=EOwYt~Bv;4CvQwWKLDIac=L=iT8Ig zx|%=o2D~lDc-q<75sPfitAG5-@39bhD#3ON zFK#{$>5}HLi#|Q}kO^RE+LzufsXQ)~q4wR})uz4al=OsCLOt2efm?{AtuVK3ed?Nl z9heI`e1hC!^9qZ9hGMs<+$KoMjm{^VdOww3%0@eyZ*{!I%r9upWukVxTfm;b{tYxL zDN#@;Jw`hE(AMOK|8`r{F^xG*Kc?iVt4^XbkCq_GkYr53#)cG;#CT~1k55S4!n4^e z-V<7+nLi^52{)C#0qsNGj*4-LtH%0DrV9n5LKavDIX>2ooPdila#9uc#JaMT^mE%t6wZ?{YD3Jh+ zoLH2h0ZbB`GPuLh_oE33EJCV}EG_p(;Sf|?5z<*=uXbZ8E@0atrsCQvGW!YZLvAsV zOM<5&Pc2na>*3Hj@BFbbdP-Hqh{#C#h6=jj#>f@1O2ML5)22_GdP}9&J0o9Pxa09V zj@eM&HqUfiGLBt;sxy?fA2TQ=$r)WLfyj~UiV%q^h-U0+$H$0y$52BK0)Sk-|lp5*y z#(DE*X`yUTOKO+o`hxKhD{kLTW+`91%9POEw6200`WChD7tSaeuyq2?TEN5cP-V|4 zkznr`%uOn^X3O3xkHO~+JjrzvLe1p7g`lG$uu`ZfXqF2jhUv?zwrEtqgI}E5yIyN( z1yQS~PfzK9-Oo)#ogbz_|3G)Jl)aU;Nl&}oXzv`i-gYuL%H6d3^wWO&p8CH16U@%F zcSVgaoLcGyqF|}p));oEFI>nv37noF5=TjrN_eS=c;!YbBX|+&qkOqJVz2I*?oNQ{ z=qTS~0Uv=sEpH$3Og?rjEf{di`jk0Y)v-TJ$;~p#?JWmUZyG0otr?PN1g^&XGoHa{ zY3sL>dR_~tw-+wjqgvFPKOerzd58!EtHv4I9fJP1rY{{_$!+8iZ=^_uz}{yx1ooNb z2-cOcW5z8=IE=`J-peu9V`AIr&gQY3!z~D^sR&Y}w7GB}e76_LEgVD1GK` z7Oh-WEK?1zGrLjdG~5*c0AEoSf{$ z6xLKlHGqj2XUG?WO9T(6gy4OlNCM?vEi>J5v)G*>ctpLK-P%M2;?zjP7`zE!yp=1^{^lWn8MPp3<(Wt!P zQbAd9L#%$A^Cu5_RCkHeV^Of-2ew{0r|p0RHa35$cBldgto?#wosELdto6 z_72G2bJ{m>!N*RTX#OI^pvj0`yH3N%2KZ(LK6^;4Z%dASpz?9a*-Tk^nPbf3WrgCu znZPVUQE2>l$PBKG}ECnG^L;pDF< z*BM=>s_s-GK*+o4liMMqqhXL7jGhw5>7pD2P737OPj-9I-U&ow@`_BiSW6jC6C`i) zhC1^L@QcJPGV?Qgg0qXp@x)Ll3T!JQW>JX(*v)OaL>y>rE}hB8h;t{Mic+XEbI^FL z?en~UC-8$ePV3Y8=1DO&a=$GU@9NQBOQm}V||N65;lzUF&YmKge9h5R>I4hq*Tns*V- zU>HYnK6M^5DvB~eM#FH4U>Tc(G^zN0JNQ{n%s08mND5FB9EylQEbR3qa)74uH^$z*GlEBGKr>X3{o zdtu|kJKFdkg+L@X5+>N9Gxus5gBR*EmsKHf7=yqHMD>3tIK<3~lyHu}sqTS8k&@)# zz!?NiW8%UH@WA0oP>FMPD3XE_9NdHplJg4)2{W@0va(sxBIJTu;`4(Hi|ab#vk4-2@v7^3;syL#dkk^TLRUOxtcDd(@dS+D<-6V% z4W#0#4LCCl6cW5ml%OXQz>Aa9D2o5oC4Rq4Y{&~Q*dqb%LB`75U*yFJ`$jkKAS6&W zphYL#CVb5;Lrsv3f}=%w>Oo5ZJ;!C+>v%qJZykkr*GQ(t^%W)`+XOjqyJMRnDiLn` z$kl5+gY(uXFfofs9!DZ$^7@7R@2U=Q7+p>di&{Qi^7*7Tq}mwYfqX^c;}KJQ!ztXb z2Vr(-*)@jw3%c>_f;uwFf;Xoh7Y(SqEpm&;yG1ETgo=y1b-H1DK+oYMP^`d45@%;M zG@vY~GpulM#dC!f_I%oo)cGR|feT|S!>kAgWgJ7CgRMYWw=;hMLj&=++)T#G<#3S; zZ3D`(g?mKy>nB%qG2y4-@)~RnaEF3(iFV=-5|~+|nJg3%n7k6iy?PS_2I(YXhKik; zl#yhd)-J-NsyZ){nM7^F#x4bglav<+F}f-Vri#Em)F3IXJRgV1%7$zA|XR!WC4?FT&jVsf{9*XP+6Q}Z0&fM5GAjm06q zp^oKuyO#G3TAoN6y#8LD`zkfy?!IGdvH9zcrT4o=0S7HoLD~ynOfNmXy8pfUkyi4_ z&-uYpc~4l6^GY9PF9pvS)F@Xf=Kf4T-yNH1aBVjEwee{BZZ72o zEk2KP0wzbKNNdkRLnX6oywS}Rsg)P+g6djrV$5%dCNssEJqK=>NPUx97nZ*qx}D5i zdJ603ZinY3+?4)w&+uNe4}IK#t!&0muH%oXzadvGnrzFcwpJXmC^JbC-}T$-u=<6X_<#`(~S7ToX&f9 zardR5&;JxkEbRDRSJ?G==8lpj4IfWLnoG<`PXT}H0!?atxAH4_%H?2N-@Jpxms%Us zUnoV$XB7lNv+YERT$rmOn&K6)7+q3D5%2?$@t+$OarR{Qr`k>=@ekEs#Q(UBMHJ8{ zf(t{GGjrKrF#Z3XbpA|B;-4GfKhrdSb^{FmqBn6qsVbr<-cE=aLoxs)yD)d$XKGME zMs`(>4l<|5OE*s_hcOV{VlIk6GpIWKx!J!>9I_LnCq)P+4n-(1g9l-9EP|vH{Qp4+ zej4p%PLELFW5WJ-dY|#$QXT4)4ad?0x+rkDZ6|mpk;sWTA1nVBbN+o%W#1XI+`Kbc z;a9Ul`0hNle0||rg~}=AZ^%302gfJXntj{l-J}D*w?|Xa(n9p#jDJI>d8g7p=J@3K zDK+W_P46lCjvVZb8U4}{|Jm&Dv|^0m)jajS_LPj^kB7gsWPUYX|FYP8aOJ#yW3T?n zJ@36VONS7jCB8Mjm7CS*WMxYyKd}>^3Z%n)2sUVG)xFQ3Kjjn1m>j%*RUl)`K?sZe z1AN^Q+L;^_$WsoHi2LEQGv>ICt^bIn`HKB_e48;@dqzr^X0nq0HZQ{JQ4goX2fbhA z|Bw@J@~W4@a?R#n#r`e-{|4OsOTF_y%>ZQANq+p(t}_Gv;uRrP&=~`?KZK|r#SY>b zD#mhBrqVhW^uX0SfxtV*FhqzcaBlucM zupjQ%-LbRk=`JqD#F!AjK(HR})!p{yFGLOL;6D()XB0K0?f`{lYI`&P52}PeR8xTM z4^=|^pz9dBOxx?pvkp_JFL%HO>7bZ-O7V;TRF15kiX63_tk%Ik$# znpd8{;xw@|(ru#0NZ%SiV&Ye~_NEz7k6Y;j=a~C3&g2xuxrMa4gw*!tJYZ`R0qlSNZEya~ zr~DVs-}L`dgVj0k=H;2TsWw)`MyFDeUj|0It;R^)d#N{Fi>c%G=1e4SwuI~3(NZ#V zaqn+fWEVgV0;KO>$lWta&p(v^f*fb+xV1T>!ka1KJ2kqMU@7dqTbTX_Nr**ukCvPv z!?8%K%^5=8j58#eIluRAc)A8t*FTY4SmZY>d)JxF|3azxx1wOIB48&l=?@ZX{J)q4 zkpI)pNo+HZ26=P;P@0~#eG=Qu#>i{g^rdNyu@nNjoA%$c_E?G z8-R{$brw+v9;-*BeMMY0L`?k;#f!sDbi|v;c10OD1e!u7ZrcZ{Z&MWPr&{5W zM=%jp=B`~bjv=pTCR>HLThwWB?)d|EB{F7h{S?JknQnx~-CQ>mpJDRk>=WN06M5X< zBwjM`-4^u0mD9-Y>8n?N5EhqnyQ|0g8&kART@B#PjeC?DT;eek zPPs5kDISZ{r^aO3XKz)2@2q(4C0 z-+Ruxa*pG0&y&7{r#L7!3Vey17t*QvYQLOk_;lilc4QJMD~c2*8s%E#xyvM|LXb>| zLqOJCNT@TkF`l5CxcLF$*)Zg$$(+)ijb^MNUrR|E6U4uzC{a%4l%W|G=T#{%z~R-W zkRd3(MM52+K~*Mz@!iZ2&C(Gc3xCMeJwG5Yf?*zwgnc4oxVe(8Ace~~qw0lL^&uqk zu}j<*7!-}Ms4fDTgsCimH0<3iK2)A@FKldnzzGl7=MSzT}&gs&9($;yGML z2v&O4hMaZD5JS{&$8d}}Xu<{=-mFv0z0*;^4B|^lB(NoFwSehqz!uoMVYPCryD!1o zV2b3Dq6l6|oP;DHw`U|CMNgP6L}_s`(%>06w93j)l!HN$l+VAQX>6nO$XMm z?~Ns`KGT(Y6AyDt4{u2C^E~J{aSyY(oWM3SOhOfUlGMw_rR3PFT>u8Vs^ZhCjM|AW3J3~S z*JhK(H0~Q`a9`D+3182UmB@_pi5L`*J2%bwV8U7pNsRr5caU9RE7ZBJ_tyaizQ;SxNJT;P}0H?HAp&@g&ipO}AO0I6*DJj5;LmwVa>y7%@g^_Sn+ z5QnwA*KUdR&1cj^(RaL%AMQC4qCW=jWMd%&Arj=%qDSXY`G~G>C|QC2%YnWx0s{ZI z0&(hBBCr39VKO3jHbCsaZ|TQ%j#z=*WQD*>@qNx46a5nt-VFqrH72ag-zt|eu@$zF z_bBp3GxUrT*MKjs3g7$j(Bb^$E^G1KWO4yNt&@FEj9-H(pGr!}1D8}$uhJH0n{^at zQwQ-+!cELi^_kJp!94}t2qreh4K?{Yuk~~#J_KWi**L4vpE@Tat{eb|@Xcy|wV(gc zls7IKd&DUHn&uv{Oc|Iq_8Y$5Rl9-N6C;VfR!C{n+)e5DM8s=VX7Ce^UaRBRc%F|@ zVmFhVd&dSCeQ%MfRV6Szjpx!hFdSpS$REbgP&fRd@jHsa+zi}y=kc7W7oaiw*jB^* zK(;6*_WITv8!`r0<7zDwdJQI?eq*C@PBvG|-0775K|m(jkm?FYjJjIz6sZf@Ap3>h z3qfa*MB1QxiBaNEc@W|1tsIHCc0c49c&BF~!$Ij=zzB8YDDriU>NoGA)B4ubuJCA1Q6(W}~Q^pOH z)HdJ`cQUr9Qx#CN~@^G^=xAr2Nz8>yA zyskQ?yL+C)RKMPd=B8TTsOCee{+MjE)BPK_Dh2Zx@-pRHr6dDshD!R-75K#u7}fl# zQIh+lNk%@8dFvcppLCi#A83~zUs+{c4cO#fba?4T@E&tp-NfuViOkls)n}Hh*O_I; zzvuX0r>b^YlBbNSdTo)to!{8eX%t7LHe)b0S76rOdUQG7RO6ca)+!bCUc}%<`hq?~ zJ#$h8zr$ZlxjP#KiyK^?YK6Hc`kC#XTD)A14-y=2SfyGgr@gwI_Jg739R70QSWaZI z_hM^7QVm;7X`u~!Qg!OWBA3AIN-}XsxCnEPmyG&}ibulTE6eVzHK8;KmD7xaG!^9U zS+w_my_Go#PQAnP*n`pF6wgxew)F=~Z2nW0i|ysRkUtjjIPZRlvV!UmopzUMY6 zte`J&)A^;N4GgbT^Srd+t{ZXP(H(Wxu@0lv*B%M-(=TKEdAwkw_TAk+TUWoTweYvs zM&qoVa+O|0Jz&3>A^N&8M7>u*Rp8fKmQ8g$6efn`U)0v$}P>{mEw}~*4&{5Zo1}Pd~IVXUqlZ47Jq1=FVL=ia81R% z7IU7HP>@MOiZXbpY125s&$Yd6e9-=SWaXL=Z~yltCOm!^W>OkV#;=`WQ6iK@FrIhF(#vsQIbx5XeT}Ax4InO8F|k1^cw$WG&?&hMX5&b%vFPW zb_APZYwuN@ytGq8!0CKs6yJ=6XFlnb>=v~ zCI9uR#HM0tFHonX)&62J=-cqmDI@|bZzF@3E{&cf-1(wCsBSI5GGI7sL^k}iY_Z%j z-%*)i((q1 z!iQ&eS%I!XLKG&eHw87igUgbAa94?HyZ;j&0T*w`6F84YL4@tQrdG}JrysIgH%4Dy9Ejw4; zNpON2(HHVyl~2?R+BH}1GQ7Nh>GS)?{BO%;$O6Qc))U@oYFlr#D#;%msG|qp&Zv-? zDRrmbQTBEJ4c#MQb?esJ)o$A!x^9njMx-bu$9=VaEvn*^+rm&;g+P=fb$be#7ad&S z_?ovbUv)TCTJ+d85`04Hm6>{Uvg_)_p{Xav!mn!*c;Li&X*}R$F<2|Eky6lWOhqa( z#+fdDQ1i*|c;}CqUstM2BgSrS`3T@=f73LmS2g7^l`KPMTu&d@?_soGa_=v{@q&1{ zsi;U^Rl;-VmG={L4ZdqVH<@C7G;D9QNSfR7e%gpW$>OI&gVb)4O&o zXk5d2P)nh;=D_df{={m{>g~LVlMc)EsOk^bvhsN5?vWbl&!?o0Wq7Bacc#+2D6qmB zHAeok@}MB@wZrhL%=2M(ZgSyyx*!-z|HIpF2h8Q(6>P2c3@57w7N=S?yRIfPOaz&A zE)>j>Th>oxN}T9I_q<4)1DSAN*)IqBYvzyCAP4VU7@MD6EYG1$(|t-GwqzOeJ?q`4 zfOlO#kdwKUJJ+9m{voe@BqDy-eAL})kRz3*N8J*rpR&sxiiXv6Dk zm16A)QLRbjy-&OB5;h-JB}TkXl5V5HFVMH@9ZFnGy6U7v`USUg9!Y69+Kz9hxN1l9a$-7QA;a`9J8@ezx#lNcj zSUEYwQPf9}ddWM&IN{x{G^(rLme+(-$C{~u%I|bZ8Zp zjt~z%qegPEOgre8LP+$1O7J7E%XGe7*jJFg$?Ymlg`4x2WGVXx(W3?&vh;&`)2!;w zrrx#%u0ic;$z4AA1_C4ZzZli=n{bok=0bU)?(8;3|7_i zt)CR$FO5_@vSz4wNg^00n{mU zGb7LO;n7;wF+>~VcC5|4@SOMlw>w7=BDOd`B|X4et&YC zy_Rh5($nH@)FneCwdcR@h$a1Cq}>Fd#{ zeQQi64VpC^6H=c?-|Qrx`qTFQhDrx|d7t!+%JZe$9Wt}Cs>aPk{Cv>jC={{k-Qr#P zk^gaJLPE!ozv6dF%gB-GKI8bzD;0uET`mzAO}mbrRwtW1k%ptxNVgj6mWPtgjiq7B zZUr|ckSKfYX=52&o@-BviigvjIR!oi-ud4KttyNKvY>K^cI@U{IQp>v#1Up{+h zYC=anmLd8Y4PQ>?;O{YU)ec)lU)`z`FHcBO#ebTp=X0*cpXF=0or%LRMInpOtpk@; zkYc*7<)Y*6h)~_x5KqzURpq>yP|UPV)wTA)qh2~JHA+BDjV-=I3tvOmlqk0# zd^1=rR==fsVp(IH8|s9lXJt-*&~i@EyTX*WRg+`2tintQk086dSRy%)Buwfo!*+4Y z@V`6n9^v#Wq~Um^XLopvBaHK=YP49JgV@D;-_nh4Cm2t5b|4z9#ak+uEzcY1qn*`0 z*QWcY!@FyvrWBN-R|iXajBY%5*0aw|H-49Kbvn(b?5b&x!=92s_=!+0OG#=wW*J%G zT2*MME_nx~KW+2!m3)HUh6()*#+V!hq6Bll%X$|1emrBfUIsUc72i0_qeQHC;;9|D zXL^=O-!-29;L=$5{q}ZxWU0{?;x9CZ*Sr5Tb?^ug)V|MtL*SpWt-yuBoz)7TpM~RYT|xgtNHofUGtYn!+49;*WBDvD-K@2H=7(TdGHMx9YMi4 z%IQD%#lz}#?_t~$tQ0{u*$IdU_er(lp3_LMeEjEDl#NMHreeVKaQNr6p z#BxUg=11qQpqg-y|9BaRrn`JDI_iD#t@O*qy^HBTR!_vK*cH&+DO=;u4iD0OLVo)E z5{rBEb^pUx)UVC3!oiG_s#s^$$TZr@@18#?j}6|US?L6)ZK`&vU)HyH8GP2`S&U|x zUc;+QmaWj+qbtoLi^Z{867>2%ti5$uTurkt3JC#%4=#hdyIUZ*4-7uI1c$+bh9tPV zTX1)mK(N8xf(3U%f;%~r_uF@Wd+&SB`QtwKd3x5^Z&i17byu(I?y4qIqsxB>n6KWG z!FcPav?JOPBx|l+Su~#S9Yc$?S>((gLb=jf;j%TFF^OpFo}hX)GwkLN>k6EAUSQ~7 z>Jxf3t^kxS)J6frpvbQAVcI3ovz)B(QE?h`)+}^iNRCyz zTBnL=L5FiN&eM4(Q+HtcRGYC4?)HNny>?jVE2xn*BPv>oa7!`;k`T4CkKPQdv&jEe^_&y=+!`MhtFHUq6t{hn^Neu72KTZ0ncpx)wa+r& z7U776iaOHNMnR$cvyZWfgP^cQ$)>=2j5GimouSaEfrWydn+U3nBA)$TBIsx_z$g@U zrf6@cySF(Mg=@9eI4qm5WZc(e)dtrC_bOtBaV|YiE(YC3J@za8P2R8%8_iX9-gf=( zaSGKlp?b4`ZuHf#c8(gDb>g>y$5l#IEa~G4)?8M{i{)@yc&QQIL) zI+&9*8dBJHXx2uk@v4Rlemp}JmS~fCoJX}HCSMZ$zswD1;^!TmtNEpzRbQF-hTz2_ zJL~{r6|X=<%aR z!{$wfi?(;c8xx5%U8%~fed`Vb6epF^K6UJ$b~zNERVJ%^yCDKmZDjP>Dtsw0l0OJJ z1vfuqFTdv+9GCzmt~*57kJV;GYBM+WjP=o$g`sRtwo2c`(ZGxZ9GZ#RCe(+a$1}12 z@J#z#5?S0%f-{|V{zKk<*f~(uP?hRXe=2F`4J-@xsRX{5{FxM4N__n0YV!66JB6Cu zgO!0m7u{59-c(8OK$`O%L@TI*p#*16lO|m`&E!ynjq?C8&ZQI>$^~ z+lODK8tq9C+nHKzsjH{4Ru^V>kDo6M1*br*<_939NeA}!*k8h1J+MyJX6xD{X5@Zm z-|Vh_r${NH(2k+}g8-3w4hH@~P!0aMX?fJor^K$g25GJ98u@NQ0OuF+a);;l^Ag~t z*!}D&>-ryrG2*{$efWp1dOATs{`OAdFJ;s1YRmk%o$oZgQqn=A0uwwA%ec{*Ji6F+ zEIVDt2`2t-yxeQHwqknjQrt>C!9)+mE!rQTAFcgY-!#Rjvgsc!6DhuCM z8D4uOzhl=$c01g8CyF8Uq>Q~e0A(&n$#_6@pJ8F+Mhb3Lwt-v1QE#4c5|zWuOa^~L zZEZX4BUd{EmGwe@tR~Y~-W6&VR`aluRzif_*$@DdQN!S9bzqgkSxR|$3m52}R_!CQCS2PrScRo$Wg#&^$UvS$g>_033U zbXltXv(lOOY;E`MF1staWztHtcok0aeZBVmWwRCVD|UP)-az+m?Y}sF!b{DUV2z=c z(0IpE$G!>~5*=a16d;CL>j=r=wxy8t!8~zX`hwh8vOgs3&5+C*u6&E?t0&iC_yl6l zPUD}L*AperEaG#u-I#r*`QF&J9=6~h>BeJC`^f}iP1iaf&zC$`EG9uCJ0Hti*AUmP z5T&0!4PF>jX`OdYZi;+#e|%Gz+%)V!qvklXpNvmPk*gat9`pm$tWe!4xl=j&_|e4I zD0hnJv;TnaAB4NV>bV52o<*~;YCd<=)_L;o`)x($2nCO(xtbcYUV7tcc)>i@`m zhcy}Yfu>u>(WH8k5%c_mx#35xl&gl0Dk}I)^yKXt(r52fB>zL}+^bGCu5s~!g8#mw<4tNH#`n@vPPHQzAe!&V=zkfML@?suQ|`Gc$ZXr1A# zN^t?1$TpyMG1X(4r|nXxS-;s)Y+uTxkmE^z{o|5W3a&n0_Q^HdxwwUkJ$x(52YsI) z--tWPyG!0R(3#jUMFk#wfhlCgo7LM{Ufd}$JZvXfC3v9UBb{Nm-wo93Av25JctXsP zx41Sd?CBiOvNXd}oAB;cF)_m=*LnctSM1bsV^vmxo>A{6UAgqej5=+fSIpJt@|OOe z)Wd@5%ep@=vcwIw2DLZ=jNun+e$}7}G-h zjzZ!l76{27zoFvNIv`rR7e&7$ltYD)A82V$HO-c&0?Ub#Z>WI#~%3zbdvYN33;4k}?gV8>NFz42{;Vw`XvO}_{vw zx^qX0*EiU3y*D|)#2-YtrvCd8K6nQWIk6G$s->t)ntn5vxqIycJ}|c!AY^4}@y@CW z(dkpIi#T-Z+9^{%^VY|x96VA46bsUA`{mnBbMb*TgwrmQvK}OM`SxtCmU` zoH7m`ejV1D9ND&Rt*nuts+8iYhC8pe5~t-*LEh9vj#gZg_i=V6%SXGZsH2hwQV3Kk zRa&m-iHjJ;nd7sG6n+YWW-z(rmz?)LteiXtJ_J34o=aXJ<8N9~$YX6grL+eG!p#yF zzJy%!qR`ZGWi@6Vr}=yTk#5C^nw>p08M(Y=vTEE0fcE`F_hO-{&9b%n-r}fj>2+~- zY1g^vUulYuhTT#qm&+*bzi`F9A}C74%wtw^wpg5WW$jGVIxp#M z#QUO9!*G(w-#CY#>X=$*IX>2pTZdV!lgfJ=3K7I#vkcBdv(o-0w3B-4c{FU;K$jS- znX)a{fYn+SGa+qJXGE65&>oGJZn~CSxw5e4{YaqvZEiTDpD^KUSs|@EUs(}FH1p@d zcUu?mr(%}UUR@*+gC0+euR)9)~T>~dn&DiXPR_nw){F2mT(G0KZ_un$T)^G9*Qg#im)(amA7C(r|($fsK)F+rVT~%L}cqx1$kU#1&b4?~e=dCbNb9 zKoh5z*goe(kEjYOQ=f0Svk|{DMn9Ezj5$t;7N-Fo*(zrnnTYMmmN%$Ya9h*phpao! z-znS>{DO~t=JcW*FEtZc5_pho8PE*9v*cFMLbfn7^#N-1fo{sf@ka*`fqmUfycK$w z|G;l&l5(~(_OS?8^Tv}s{jCx#2A8ekxL}fJ8n8F!xG-Lnvl`OR@Guw5mVDpw8;|e4 z;;wNe09#B!9IKW>JTZ%A0A(NHf{R{5IH{Iba2oGgO)_aa-FpI?wO;F<4{g=vYk_{* zH?NdOVhIk*;soqf=Ee|$dPK6hrtpLnQSM?KaK8vst0CtnOjBRPsdA--O`R<6DB6K& z0PE#0 zY3*n2px5T)zUZ>z3#dwkugzOFL?ipGTC3`_jQV_8`d9>{v-Fr?D(KOyXtkd5mQ~D3 zb^Y8@FqBFm?ptn{U^KPnli0?*%a-mWs;Ei$kZ7r(lVr9$SGn%twzJTyghnQU6S!S` zThZ3_&2%Oi^Yy|5=CI?4&4f@NM3h7O*jk1_q)j_k+(Ljle`xdPK)2-1DY>;09ss&r z8nOcQ!~4-4{|xXy4Q@`A2^(=F1#D{G!LoN0uUv#;YnvrF>|)a4{W(dN4oG>2Q3fz9f0Q53X^jk1*R>*&h__3u?;(x$$7zK=wFLA zfe8<18!CZhpFP6U4E#{JYAPAcomgQ0r0G_fPq3w`EiJ<7`~Cgj#mlB#n=H+y+76qB zSW+U|4X6>kjsT%Pvn3XnFN5Ik!x^g7VIyAi)R@#Mii-Z*{&I;S85udkO9J%#+8+6A z?yVh+b4i0{-05e!ZvI_%amjhD+)GM0De)V*&;Z&;nuj%J9cgzb@{PrIW4OGL_+1uf zt$&xrn+GNm4x@U4x6*apyJt)}FYl(&3}^@ZZxX&Nck6gRbquE<*~%outt#_q{#=`A zCb5N*?=D*X^ya8fR~5)liBt-um9e^ldCD3(^~uO{{mec$9d3j8vT<^TSG40DFzC=n zsWQ(QpI;inz3!FrRo`gxksA(M4;`|6e=iHG#YFI>u8 z)zyKTeaUjA)Ul5)k1EBv!L)bhTcmX}4f(fygz6xAVH)kvK)V5DOIS1S+n{%(yuZ4M z*5%(Dr8XLCjh)er_TM)3WqqB;i_g}=9Ctl0(utO7&Qw8bZ~i^3(`v0^@1>*ZY%^>1 zo)OEi=M@ixUNF3pjBF6!ifIo+pF*EcS7*1I8 zdkfrG*Qb~;N=#1D-y6mApvu7N2v`#HXE~KOV}@^DaiZW-1|DUnz91aiqZqstJP{m7i7OJH-K8gJH1R8h zmA@|q7P0b`lUWa=fSyqS->W5V{O^O-ts1?34B-5{?*@egqXRuQ4#7 zL>hQpSjd|rF@Pnh<5rwTNb-4jjNKGwwxJ;9(l5@$-8ffhH+| zn;UJiVk^B-BP>U#zqK+*eY+h zQAU~fRd7}1kD&sA%2|FMOgrqTi1xL6$ufy7FC1-M5|xX#@6>hWVD^$>;Y{V9?=r2} z%BfOW6Omw1USJ}PIgH;A)dzkz3J$GJY@RbyHV6&m~5Um&tueM&1tL&r-Bv}a-R&n zem8?IPu8bNlQ*$8Wv?cP6&av)}*I)EWCdKd&5#7G@3jK@m*VQ6NVL4QWA2g z^ZFaHEM$aw$!aQHehHC<&C2gkDn-&N)x&30WQ-;rV^1bKrqcwjFoq)Hvk&Qa#z_Bh z0f6r={l|NNyY%n2^AJn^7a%0c#ZlfNeUrf|e!Yn{=wgOd8Qx4>x^l{eTo}$esLSQI z!A?1WgU@wzVP&g;JnE-jED{T|p5~3=w;-I!K1IyNwB20WZr-|J6q#O!VG-Q;oHXEtSAP?1Oju)B~5nvN8b}mBIP+{DcD4;->-(4Oz%#A zi2{0#xs(E_v2X^lY8}}=#m8NqJwF*${j6FbcF(aN9{!DtDdXCi4tSY5Fwgz4T zY~M=X(MMRpn+E`SBSyDF>A*leIh5OT#;I#cwlYD`k=&Ak&8*=a(L&eW@;T`6JYc91 z8uL(FKDU(?vGV$gf+5YCc%jbl`*mLlEibTRDc23Tf5dFZAk#KDQK3d&tX1-TUjOYof zS*!#tn%T_fmqdRMzWnt|;{hNbzI^c#<MH;0N^{&+p8Mi$Za=RnR)*BKCT>Rxa!)f2q-3 zAv{vco|>rb*iZ&BeHH!51v=R-1768)wzI;`$7&2jgH4_9_HK*XXQMjAUzSX&jq!@u z2?_j;6CcKcHKRoaf8o|is?D^S`HQ2~m_Jyglyj3(4`bDXD_7W;d5=oOfGpINntl*zvi(;w=FhxVD`nJD*{nq(ZXCzzn|(fpl(J zxIl}Cu(|MI&cVVKj)D~aOn9iNiMrV%#XZmBX)8u<``EnJrVi{K3 z>#<6U$0B@i&(xz zxwM5|vb7Qt2<~Me1d~k-DzbQ4gh^~6=+G|pSyjx=4cJY#y827{#A9iH<-1RA;5#TY z^ye=qt5LJMv7_R_vQv~Q*oA00%8}Y#hc>xh2SBu@N3e&>OTxgIA9auT+c@V~`9D;E zFIbx7b=-5}JeN%}zP!Wa`pQeuqt1=M$oI!V#~UuTowtaH(Pr>pwsO?MkgIR$*L=#jvef%TZ64HdiEz$<}Qd8YiYzK$zln~2m5 zL;fIqI-ad(k)$cJRoZPUf6cT>SiQHV4MTBG+6AT{%2wBav7~&$`;N#dQ70$$S-_`C z*~2nle80%&B36yF&bEA5`I^dRo>=>kvNpS;{k6|-))91`T`1j7S-VlYauz)2T}}|f zsCL(&F7vB)PjBsJb}BZZ)-a&3>1ORQh23zyJ-`4=^=a7?v&8gQ<33M;(ZW>&t6k%n zy~bp++*UQV$x11GMDRT6IF?zG*`T&2Y@u|%JiAGRZB&J|Vnl$AvpkJu`#ni_m|oSW zBl~lf8(P5y0q+7rfhWF`bTGNpN!3)rgm%?WLy^w~Xf0ox1`hrEz>h3P!#{QliUi49 zORiUE=CL(3-w7leQdU8WzVcMnc67p*vZiP6b%}IZjW`|i(Yn4?c!OJ$0h6CazsVa< zc;J*lBtAW8rf0z=iP}Qlrh%Hhp;QFFN=uYIpirHYar4aUf-?D!B5l7R zQw=W)spPMNUDsatj!sRhW4MP}{w`8?NBtc|^&%PJY*3!*D$rCG>$@-WM)W3(>;;oo z5)-HmO^{kTM(8`>f_A0A9g3159m8~JWevF=$sX3)#R)n14$=VQi}Y>2Qp$O~R8C$j zy2_+c;J|b|DUG&Q5aQc^y}8a9ETQ5V9juhySL>B^X@w*p9ZVK#bFNZYS-}#6#B@aa zjW(J^gT)K?(`mzMf$0)l$mD^oay##(+TgfTO7R(dCQv;-(`qo9i1&s%mLRp<$+Zw@ zkJjwB@QSEKJ#6y;EosjCgn~~1-{)m5?PSv+G&<;#tH2CR04ZQ&rh#pUAl>@+3-EP5 z__X(BS9D2JZ*=WP(E4Y_F)K=vT;60B9ji<*c5-s{CMwq#+L2A^;AH8S&*X4*%L+e5 z1@Z5ZE{I6~oFfDG0tO)9aA~}tdV@=C`Vt9``>(Qf0sV)beeqY%4*RQ3ePf&pNG2;W zBRkK@y^!cC@p(QdnGaZhbg*N}k6bmCKO6z&Kp&KUp;>mLKHSFN!Z- zQc-$&ozv2~xDYg3GyhXu)wnokcK?bm;^10GQ0!|22X3_>&WT#*uv-gWmOwM?r+~%#E=&ku$@v|9I;2gF>B`tGhG#Y z3G@TRx)Zpa`a5w!kt7)M_abSdOh+VKmfPgeFl<8Y_QFi}QB7fCPH94)iCNy0Mr586 zbANgJ8K+0SX|QgP64gS6+f;6M)ayofl0w9E3#~Q(dz8F+ZpN}k9JuV_5UP=oD z^eS;TAAmpy+hj9{>zed_w(g<`uK{_Jfb#^?W=It>4v|1)y$#5FWVj@O zN28VK%`ExL`QJ;_^r85WPvoykQi(y9V2z(1I96Cd|0Oc=5}=>ekXPM}r5A2wlIa$D z#jxBNJ`D@vwl(@&ue{!`L%+PCUk5RWODNn;lR(lm5hEk*tQIf6EI-x_0-^gxx;HN( z2NB960K1=K%_L0sbuZt;o3J6yU(fZI$jDg8$f&PgzCii=6#)r=OU?ZXkA_DI{+Iy$ z6rYEKPesecC#`AjnqSv5P4`yob3*+aQ@7sD-vlxmU<>!e8Dx6?z%$FBg1#*V7mv84 zhVB1wbw`$b@#K>y4saWZ%&rl$*Ub^Q9N2_b<^dTYE`h~DQnNrdmx6!ZnGRzJ#JPK8 z(iX|Nt8@Oj(n91>X$|Uj<$k~#T>|rklrH4jVsK!M_8x<%7LdW$^Q2Fps_*9oBY+w; zuf^lYWMde2lm%&DF@KN+aag}6cl3u=_|M_o9;N+ZZT_HM>^>mVBX_TdTsPRBxE0oE zpSCGd5Ofr6F-^7&hLyK(+Z@K=RrB9(n^f};s@QwLx*_&(6g*uG*x8b8NDd&#(gMjZHa3uTfS*VR1+VfEG5hY&?;vpaD zTQKl6z13GPX1GSdc^XS}1lZdDA|H8$y5SS#LM|<4QYw!)NBddH+hUhOf0x^p77POf znGws8N7178az$a-E%0OWneVGWSVt@ZYxrD@#~^FM1sa#~NHu*xl1J{IoUo19%@~YJ zxe~A)McBsVX0%%A*Y>CH8~$N!?QWW4dBc=?rEP#u2T>SsvMBjnv73~=T;hOGu;Ym_ z?JLH(fl-(9v^IgZW1{*1Bsl&5l7?e2p7G$hFbMJ9S_FzC3-L}_Xj~F++!uU4ij4E2 zxxAG7J}{BC2(iDCYdwgv!vKG%f!7LL8vk)&Y#aQh@!S@3@Z5~`uU8x8BJKT703`M} z{>fFcDzqK`x*B@^*Ux@0ad{$b^P9ov*&dyH$VsnD1L3pL3B}KI@V#6K2s+?+G(nA# zfaf^u%gW$eeNnN!~L`3Wel; zuwk#WDk>0keVDMps`uWET$P|y<^ACxN!Ouhb#>5zUda-C(;*-fCrHJ~3EHJW3`v$` z$4S(TYqQ!US*4Bcg{<57AB4O|bMxYod-00H!-}h<8^#7EHS)sl37WC_crGKTQoYkx zq(?JvB1TSrnrUje7E<#fzx<)xw4bp;&1|{}D36j!sgA*!?3LM?9!g#@2kCzhF0cO} zh`^b1_2uc_fjn+uSJuFdC!S_SUh*PQE(hv)Y<>^g9KQODWD|Mr^!mQZF-u3{65LgvW`7UIbsn&+T{Swhak4r3!by8>WOC+8 zOLl^{^=#0N13CsH9GfZseGc-+eKCvpQf4OgF;9g)sjPTS8C}%F z-mdfZjsMB_3FD-eT4>J3EgJi47bm^aSi*Nt+!Sns_vT{yv6uZ`4U&gp1vbhSu6XJv zGYw$2W35+iZ}G1b$+yb}Au=N}lc8J-c{>QSgkUiX z_EJDY7S8!~3g^Pu_0$};G9d3^-aO;e^O^0BwMpDoh)A)u~-Q$Fgay;T2um95P|Mvf||M(6Fi<0@P zmE~yutrg&(0RIVqWBxk~{40&Hp2_8)8_aZh*Umvx;Ms zisK3VqWbP*g%+#5yvvK6=YJIHhse;YR{ZxJ`Ux8%C}ZT;o19PL&U zeDWbbHCJ_;Ht>emun|)L*Y!W!PM-`jO)^F%!InLZ4fZA9unr{<3!b(aOWj#-KUD2% zFCL+W6L!LIL^|r$Vi6M<1mBSzQdT%|Usp)`93wc9FMInodA%l@vQ=#EjV;jA?^>7BO$N{f0#(8(_n6JDm%#$8i{C-N(bPRJ(C0kR9L@o_ z=gvGu*X?eK9z@G8Q8&oNKFUXmPf0+xCf%{Kyc*VC?o+Vcyon|Bt$=^{r8^;}|Dy)} z`&Sd8vv)pS(5=%ezwLqSqr1V#j`@pNqW5qZ$6uJyUs%Uq*!zEA9&p&&f5P1U!uJ0k z*uSYw{Do!xg|+>q_HWq4UuxNZsfql}^e=4n=>G`#COm%;gZRI_=YLPS;V;pGutgZ+w-WzLUH@AKU*K%#9K;d^ z{2uT%`Vp2nqQ#6^2Ak<~Txix1ebr7^q{~E}&!_K5Dxjb=C-imdqqIA}G#8O3-{Gvg ztp)|{_nPd0j}wgg5-9NQ^ji-m9_1?plU#0vamT*@&jDNlfy%6zc|$2pJD8Gg6G$xS zBLuiD_(&Ncgl+eKNn$3;F)x>6iLT*KKcySu|NkZb(vAPm>Hl7gf06wc=0CIezus~d zVD~RR;jF?F;F~Y#f2P<0AZ`Hg{U~OP!9da_d|mlkzF)33+KjZ9KggW+or=R@G^rab zImhOiyP=prRK*^35l~GFk*kgPP!l^fcy#-O@u4PWBK|+*K>_;_wlYoG*8n|#G-8&QsAq|{GG4<6fNjPv(u7|fYeirGHr(1&_^K<{ZhpFtNE#k3 zMJ6u>*$NedoagV~7Z@>cNyVVL51dd~5Y^Wa6A&Jt<0!uSi|O)xtC zW3=9UbX_xdw9O)*QpAGtB9xXqo!@*%@Q%WKgZ;Z4PG?!eToLk;2$hxZU`_B>&-9&i#eFZLYTlG zz0KSCc4O&aFSr0fj5#Q#Uax$HAN&3#?p{uXgJ`K~A~aXoQN0HkpEWt94@ZWHm`j;1 z3(aL?O$X6vqxb8`T}iatVNEkA|BckI8?epK-9Pq zc_b|35?}q-^olic`xrR|wXIspY?w`rE7X*o&`LyS6 zC61Hk^BtloNT~|kg0D@UJ+A=+^{;h8qD}D+jSPrM`oT_IL-ucgwrE;q%@ulS{tcyA zIQDM`n^`>N;Q>E4=z6_3Ts=`f>Q?5CNy_%Z>&BE8ERUQMHeW&qDZ>6r%GoY)={1{m zGl)TOOeKfG`n>AZ!I)6zSF;Im!H$s~yh^&z5qcL|x#~6bFvQ)bTv0Aj1c^c2U>z#` z8cK7?<|bDMEQ`UHN6SfTvz7Gi_(oy#wb5PvMNHTR9*{s7!^lxAGMXfG_NGA2<9oZe z5x{*}oy8)^3Q`t>W&{ZuIJXgthy`5d-)ZE2CDrxhZtABAp8{&&8B|7sx7N`qVuRJK zEfQ-nMTLvsG`>ehS=8rq|V!ytXVQ9Xap%yDfF_S=qls)!6 z5TSmPD~-Vv2b9um6`nJ}B2QzxEMUO>ychz;1sk~vw1=d~-3+k^jaWQMkMA4ST0^xK z#uiAqVtm&X4wu8tO7ys{_rEuAq3Y+i8&jM+D zEGQDOlpr3v5@45KM6 z;5s^n2i9hp-}Vw>Ia3IUq+%w2ntWNCi%<=IHoM2#?1WtqVWlmR?q>+VzW2kr6(W1p z>8A^WT_d+Xy~$oLfSaY2E*AMh)I=|YF>G3oya~+@Q@aT)VCo_=E@5I-sB>l}*V_Yn zy3{nB@sAvXk*bXIKAd)LXA45ixqW)wLOD7c%wM#Pw&DxR&m2yf;_Y6fbifEr@4G3{ z;Ju6vVqw7FUO-Vgliwy|qpwaCkyMzYkZ*%RXD&hum?mcYJ+L58_3sjI{u1lun2z|N z-6iqjwxEW-$wIa~w19XT>I4;-drTS3NK>#i?LXgU==q^+4+`mt+=l zZ5DonRoH7O{aB7r2h(FGjU(=gFm?CSO3i$hD7R3c@FK5WdglsD5HS@>{4mh+xK0$c zrJfWYAfwID{T-i+dj5q{g^Lg>l@ZxPvd_P2m-^EN+ou4Rf0gx4zSB#YnJ~4{0a#~q zSmUR?$W*QqNE#q&A+YXk*D`fT;ni4|E_Db7z4(lOSfI*C1ksT8Q339OCUOO|>$@7H zu=t{K9=BQX*~dj*sIR@?U1BgWhW2KjTb?_zOEyTDbfE#L6L(MU#A`xTOgO1(o@%4T zvgvBHS=3tx{fCpD5_f!$G22s%3yl>^IRt|1qawpz-t zNL36lF)ZG;>AL<2ea2hsJm`8pr8jz#=6d*UpfQ8#&UOWnV_I;v{2+I>6ncU>qoG>D z7SVfWJ~JNli(5xut8YRO)pPj;A=hjElbXcdk_PVK4FZ3TkhxDjw%Tu%ntYPHH%D(g zQJ6n{SN~|jbaPAnik%eEHR~3Y$_(8xWg$VL&bG+kpvN$xMXPoJ7Z)?%CN6W#a3s+? z6#CPwcyn)Q!ZaKiz!2N?zDxKVkTX6ufp|5MXz7+2q!YlZsBGdxD!lu^W#bz3tHLVj zYWY*+-VBZbXJ$j#_pq z%87OFeS~R}m14S4ueCuz5#2Xj-UR(OGdqnx32wFDqNk1|IKL)|x7uMAijGkHL5&-H zsXnaVIOP70)IdPXW{-P4t~2ddmgu$#H{uR{H;0RNqAfQxWZAeP0#z3%$_e5)2QrPb z-INFmQZN}PIaETLv-+i&E25pw<1aoIW-1B+?oGu%jUq79EwdCwmKSAF$x^#l4vfi4gc0&5>^mC_tBu%%6t4bpCw8!t(D={+y9G{Aw7 zw@9*D#K{)J7fE;S{Fw(jk}m8-_L}YEp(I6E<(@7(z5Fp~J$$Dt>kPi#?a(hf2}r4D zG|@elC^b!9BXn`>VzWnN7_$-f%E7vI51;I2aK^Q>U7V0it7H@ZXZVfR%nTk73uMB2 zK#A(1$}5)C`gtW!jlRE1%sg(Vpy9%yH4fFKmM{UeRFE8gf>0CPAB6nfVs*VpYgMOa zv1XesA1#tXl`55|$ab5Zyb#O%rRUgscgmEF8MRBNolrN=cNz>|MbItsKj@xetQprh${ijG#c<3X-YW7XLh+S4E;$TMGEx_^} zkC7&#?M#}{uU28d94Rz5zM|rGXSV6ryP#;n#bgGpr|)}9idSd4d@3V+H#U=BeL4BO zl&~=6DSQSB8@H<$BJk-JSWeAVMfY-)MCzi&YKKMBtp*;E0_Jib>I0sp{~&1k{!$FM zkyB24PDn}>uS%ERV=oamooR)5y2lZv#7G8VdO>UZ=o-F<}2tT zss_hEmyg@&djxLIj|3!p*nStDYym>V?b}U^fYE!?-PmqCP_#VI)`mi8nx7>1zg0Pc z#*3qI+(h{JP2cMIi`xq2D<=5)60wUQaR<|S=vRz)T>4lodIOei3$4B4hs03RW3ILG z&bWX+t zp%(^*Lfy?7+N%B_Ts*`Zvf&1Y)gkR9fsHoXj@S!%?^LMA@`~jGrxhEc?=Naf;+YYB zZpO#S-_gK=MErp90Mua>LCzKQ6zDub{T7-62zlFZQ+7#yS=EWvA*?pNNn~Lm_gC2g zSU-Dst$2TBC#xAhlhoP=RV zwzrPbWU}=2^;*)wq%`K4a=Wt0g?VqLill#A{KTn93T(MsM^##FVd{uG9H59O5LxJ& zdn!5Gj(l4~i7n3{rJM01J_hs3O_MKh{Aqd6%O4A>6I$>sMo*$qbpVH5-d~_K1ZU&L zN80VB_iEj~J4QGt6&KyGDro{a`?Rp&Vc{u?JWKO6;BFo7Z;Se9* zD~TZBISx;RpYodi9!n+)?3JfD?8qD^E9$tiZqfr>=ixbUJhb$jrJi_b6>_iT%jF=-Lca6rY6eaF52K2uk7rsrTe47Q13_U7=#omYChN<}% z@f%JZscKg9l|sxocB4*){T~O4=dD&=6O>74V{(95oD`?UX|k>8K#unZOQS(FS}e`h zyTL;)6pxeCPqUa~jg4c&Y&UHwrv$5h^D+&Iqe_fRn#x*DizRWtI0*CZ-1Fv^^qG`G zk|neA0jx>*BqOH!c2+*Z({-%3#9B^NdA0)JU~`)1+nG}Xu>;5C8lk={^zL>Gk@(}1 zZ31Gbm&*3uMd_!A{9%w|#zF1kg&zuGl9GUDN34vdJqJ&)Q@IxAYyHNFJkx|i9EVF; zInjP3RUNBO-9}=+$y4>9C>0&RT7R4L2m};Hvp)!6-v>@NFYq;)7ElV@AkXhJnGhpow0i(_?JgT>pFd51=^+-QXZDZ}-x z)-1_e5pcUe2x*he)6tvkw;aGK?&9TQNOR-Ms|frydDCEO5-C^LD2CZcctlvWhdOa< z+#tpl9v?n8w~=P!%cS?GYbY#x3@X)@qEEoDP{YRLx-Ky@`d_r#jhOiN%w*zcm-ng6 z#Qi zed&?XRNS>DW(f~%h%5$3s|IneX!K=yyzjB(4Te{5;?0;+wXye3l~xR0Qb$;AWc$YT zEN1V7B6oi#l^CYcbAKEuqf5BzG~9;@}P?AmvuD9rsa39M#b2htHEjZ z3^X>1sw0J_w~<8hzx_hYY{JqJNDSK>TP>hCXOD zE)L!-Sh|K*qO=8Hp@Whn?IhV$pL@D}^5AR6QY=*QgD<<-4MR4lvG}xQ7E+xYm|8<&lbfv-iYSANWM%y!h2A{-RB~N~+{Zy#&=%n%cgwRnEU%-L#s{wM_BSynCz{DK+zpZw zTp$7cCIzjIL0>tVR3DLnsY z&*Lo%W@Ry+sQP5bL6B>_i8zINJ05)!+0WG+F2zlfZRsvzc}Jz179Obf%i-3*y-IJE zAjMtnCFW^!-x23&olD;W`LAPwgJ5>X3pyD1gKU11WX1$nqJ7f>fl05Y`&(B$`*ET5 z@bQYF%hwm`V3AO~j5LR7+i$_rC9fn4iw0-nxIM_ofxerJlNq1`1ewOw?r5$!`9i@r z#t3Vk%v_r7?{FN-bDlr!+^`MP9TdJJ8f>Q=#VRCP{4!wXBvj$Vwm$fJLZy{Kp=+X) z>loWUPe&2Jk+A0`5~*hv3SBSV~w)UBnkI2 zGofqHh;(ssQ{xudZJLgDw%uxlQvEFB@#@-;z@@CJH6w%er8nnc2<$2ZadE7N_GWDm0{!Bl~zj% z8eQg3zFaoa_L}a7IN6Z6mTbXknqo#ehFohSmcjvcLjyMR3fgG~rkN6kvfWI~m*Yhg zRN4=U!H>*DJB^f2eceJSTJ;x9R$3fL&7&Jj8mKd!j_vIHNl_iiZFpn)D8Gv9axwOc zo|ldE``yFXipDO>7E=7)k?L~aN_XfrB^Zww2)Cr#qM-^dnZ#@DJHc4jEsLjHuph&I zG0TL2A8>5OdM^D2pk~8Z7;6N+PPOMw8q3M3!fzSoMj1m*GpZqvTCJCT-W-0@#L6m; zR`Tykee!jrg7iHy+5`wQvox#gV3}Ouhe}ylcX=oz9(SR4owD;fnkjNIKfXJd=YST3 zsneIV^$jqcS2`(ZaU&DT^$phC_&!u7`grHXYRI{uDXC;W5xhyGP>W1j&fSc4^-b>! z=g^}#7`_u16qxla1G)gX*_y`aG-p&{j))mSHY= z)ySkz^)s?z!)YR|TO!~z?f%!5ihsa;2(JTO5#2IBKLc;Of@_K+)y%=B$s@E+@^)L* zsa0&AL^t`y>jkkztp>G@D>wVCJ(l2+ zSH9w@oM9LHTFK`Zqit^H+^&qP;!kErP@?{<)kYu$}5!YZEJ(T%E9RI*r)anP`0F9>m?f%q2hV2 znM7?6KXP*4Hr%KS?~fv&J8-KYX0h70${}5_OB|A+4!;vosa#T7jjJy>W(4;s+hU4S zGkszs;;_S&6KEm*mN0^u-bTszljI7!4^?HXeO5SeZDUVUJQ~ z3kp5?>_C|7ECtIVVscap*+v_6t*`hsL|(np60V(Ha-%+im$**bvUDb&x(oN=D>w~} zd$9A=pSa=X@F;uUM3n|MYROw!19nSr0&*1Ow9EwPi!mvsU2NCm7XWdY9LRB}-sqxO z=_N+gniG&?L9GbFnd^=B(-{V+lES58YEZtN$(FPq=oxUsFOIbmdCgjr845$5Rp;#(&%bNNl{ANJCIf0ha|b0yrL zCY-}glV!UjZB5sk;#NXMw%-!Os$mVfoZ_B1-NmXSwar_~6Z|OJ+at;6KInHV%r|X` zVgqP6I`@(}2C?MhfPkxW53H$fDB-bmR)&q%nh*nU^nb#d*Cd{PG z0+O{28Y=~ITwz7BsdvVUh*-yoK*3&5Y)a44s*ndo&E>E5^h1d$4>0D7^3LiwIR1Me zPe>IxB&AT=*JWO6z6Wee45HI6Jlet}ON#M<_Gi)Dd7@?chLZBDI z?T0vdjWux%f(n#ur8kon+E7|yY_A1v+;_)(;hIrte4-_!0nv9&)94SJEl3)PxI-yW z6;6yY=++&t2wtm2e6+2zHfhXD&HP*oS(>sS>IO`$RgoY9fN6!tfw%O z+LCAzGJsrMSCmJ#YYt7!QLLMjk)1@NcQljrX{OyObQno}Y6z))_iK7kG?Zc1osnjK zT4mh{Rf;JUxLIB!WOj~7c@Lu}sY&fh_r)6ND0$ntpfjmIlt+B1y|ycd-Xp3Vo|#R# zR!*neIpnwTJW*97zc(raYMO2T~@!nzLUZqvrnQn?}IX@jHJA|1yc#c9f?Ns z#R`~{32IF)R%S^ly(&7ZQSz(M40h9sOst>VJ8&m*N0k9GlG)||0C=1g=tdneJ0$DM za@$tgK)3}W`T#D%y$~}^DQerBK{0fc*ZK;HWUVS`(KO<$s99AkxAN+O*_tzKY-(q} zC$225vyxu>Myj?6-cyQss}i!7JKN5gh0M3Xxpfg`TtcVsLS?eZTIejbJe%ETm(Lw= zil|s$d}d*zqOrUfp|Xs)FgOXmBkA$&iHYgTwID%dY%luB`!G1S?1m=0VNOU)H%WJ? zdJ-`~9&p>!wgyvWLD|+5^qW7{TJwnBHuD(m6_*=B#E|2Ifp^I~hXV>ykw=;(rT`avTrF9g=rlqoR1B)^?G15r7=hv)awCAcJb}wm>n@`3iTEN(} zdGGF1%Lmql^2U|b)qx7-?7PZ5Dtj4397-FfCGg^t!L4Ch+oMwd04qyg_-#cx6*@Jo zo8nu}&!U|%J5RHQ%5`jrtDvET8=Lc^wq*vFme$3HN~lFf1~Q#Bdt&a0HQOvK{ww|v zq$vW$D^+cTB(gO2!c>+MQf!Tz<2Wv~P*Ohd>?go4o8R0Bd0%FL*yq3H4y8AdWu$;A zNr{QcwI{ZiY`oh9)HX%nua303GH{t)xtV}woZ<%L4utzrE`xKE&KGI6!fqcJn3%zH z?smF2=F>$luE6Pu+^V7CsIt%PRdDFM!ZV63u+?69&~Hi;dHHj~+R758FU&dKW+k=J zTY^aPk}ezKNu)GROkfa)(rO%c%2D0P33UdOlYv2neVblWoTmZL7AA9}bI{`34bA73GbZ@5(WNs~@+d9@Kr`-{ zOIls7fobJ)c|7nHB_h*hCg&eH#r(PiW|KVkX*z)80SdA#oCVvk=7aR9ivHP997NKb z`b~l#0l-C`K6o9yk(*6^x=C5354(Yb+A|56Wn=|~3okS)iALISRwk6fL6BJu_M4)p zEvDpxIR-Gf+>>g;1zwaEDWXDxiAZs*y^l|&+KQ}S|04;0>()%X& zlUss4FUt)tPy&KQVs}Xm(Tzzb{#taxtgYC-=FGOl0FinZ(*|zDvo~ z>AZ8sO_F@~sflHFS6s@%x5IbpT= z4<|N9q%R#-x;rPiTTTm2tXWXvgjrbyCeu#wM{F=q&Qxr7#+8Eq0G;q!b+sYEidBqO zE?WNp6bnjHsfD8nX@-lgs|1KFp+R|8WL~c}25=QJ*|y<3c)Ov?A9^0LXlnplylnz8 z9UYUzD-QDbRQ4L4`nx%&xQ4?O7mhB<3V0cPyVd+4r9(wRl;VRT>F=yv>my-}-GZ}v zutI#}2}<}?RcrN(F*<=##mi3g9AH00xoBLtO!taDSS|M|VN-KL~d`aWuiKHae*|(dNM)Q56`>^{hUY67)+XHfR2f901 zCq`8XWN}b`<|QOB5_0KogU&ijKZu@)$$30z+XijkGOLr&2V5z5Ka^dvIGRR1DyQLz z%&c)?Ii?TzirdU8VJJ(Pwj4@~#l0k!@ljCQcU%dnH%n8BO^(xi+@y_AgUB$3vSp!c zQQ4w=r#2&zKKL{9D9$7*!z`b#caPB=_$Ufxh|IZrjr6rP&Ij!;gr$7;-a>A>@*odzayda#;nLyg4W?yNUeRHH=0 zsvp&CkVh@>Tx9pefbuE4U;H`XDLSgs9fRE#9~WX4F-hJbDyp0FA8z1bEM=KtXkhyy z__vd?n=4P6<}t!ck-pSh$~jGz+Zd&@1&oNmE*)uy2lH4}i%&SK+li_n`9kEA?S~z7;)!2&g*@9@JPZeS^3TLl?>#@h&X5Qp;j@& zQgvKTDT*jHGM$PGuwzAroAjJ=v7UL}*nDl+o=(YJlEMMJvgWTg=+crk^j0E@vnbx% zH>>zSLusOW3GfQ#9{%BK+#=rQ%Ar22WB{R}+TNW%a}UW9h0DNmIIe5{5igw64@_qq zuIk!97+SuvYGdAFZ!{{?YRoh^lcGJ))QU(VNtCc@=TfTcCNf-+s#K(Q(}Rszdmw%& zKW#kbVLVNy^&Xmj7=I|O0ZqxEEh$z7%n%}8N^m9hl4$w4ZEYsIK)leyb5d!~WVTV6 zXdvk_Jt46~-(EOMTDvIDH&S-6&-<0`<%30~mRe1>9#*SK{&sDHB7{1|OjSaZlix}6 z)&9tJ2|^Vtfp50Ttg~l*qyGSy&N(otg%psaGTjnVZX&A20l_ zo|jD2%(_An>xN+=+5n&d6sMgT_Dgf}X&gMMv=i$LQ_m9Vnq@PL&AOwQaNyXws6I)p zu82(3ql%izYBp^hb_w!T{ND+XcM(VP0-xb zs6C}IQjxW_c}<>Abauz}=>&v3y~c1#zU~K#kp4$@^vzzuK10J-rwB^jPKqkq?e9Uk zL!uVKS8Fn`wp|`laY^U87c{pMAulw9znXCd-J`6Or)~9#dD%n@ZywfiZN!4=6oEm+ zEhdMi8+?+tq#m-p`L*5&`_<`*riTqEu{7Tr4vNR+F+C+sCmZdCePsUt3qmDlky~u3 zF5R}6^+r(5a0Df2``W!;=$N&nUIc$U@RXBM*|Q2|TGH!NCy;9UHhTxVC~*mK2gy)^ zsV9pE?ALl?y|108l*x)_Cw~f{9@BHycJF)zPQ7>8wex_~yJ4or!QH2pKJ+h(xnHXa z@YsBr5dhEvt+tNc*7ea&BYuDkh#7711893HCx^i6;0t782RxS-nTDGHjv7V41fE zqpFW`x?*Z)>*f{Ot)11qXWa&3@h>XbbYlA6Oz75ZfMtb$jOWvX9Hr<+IAz^_ref7kw~7F@=ttBOegIdOPv=VhhYAQo>=?P z_b+n4Ru3b?=chR>YdiGiM3eo)<+AdPK$&VLGxYS88YDwiVzcEJA6$?X%%}h70-^Y`%p8$DN?;XU?sS4yzz8#PF zg<}-UN-#O^{n(zG$~KV9+8RxU_)OVw8(j2^sPwFy$ZQ7McRcW`iBhl>7iz{-eo|g; zT}hU(=5c9Qu(hQooOi_3+=fz9%W7H9_VYGMYAo8XJT*7l5|dK$!EY7IycL2;7h{bg zO_|9@n6~moT|omFo?WV!T~(et?upTgW$eqQo02K;jGH7b67u=gajkcsgiAB!PQ7hZ z`!rlS%K9RADS1r{(;QAOZ!oMSEX*oZ6f2l?U>i;9CwzS+wgJ(#J;TJCJ-ooAR@STRJ>;HzIp8g5s@YYC8dsDsDLmB?H!CcM9BHy0Y1{xoyb^z$ zAsI?xCTLBhDJWr7Y`89Rs^#6j5ir5+T-y6t=9nY`i_3q#6Vm=vqSG{=ROqYsVroSH z09;^({{Uz-lh=1VFx$Ko%1o=Ddf<9&!wyqQRq7}mx85RchROOX=Qd92)hFE(b}mEV zGYI5R{xDwIIWCEdx6+F13XGnk!OY7gPOo*5rhS;oi&8ACOd<3SH3vqV)J!(#o{(Z$ zTOCD{{PaYVyX~}j#eN-w-Aj;4d+x(ckg&HFq~?vqRo&SZ^xRgfX^jRN1CL5O+&PDM_a2yW%jSB21z#dfl$8j_2!oKgk zL$Q`#oKA9CFUplS=h3B*o^wdc6LU+pXhP+jVPOMY@>;N9;&oe`>~XyP7^ZZr`JZ3ebCcqQeYW%jp0I``a=$2 zre65O>q)yb^`ujzd?I$w2KY?Ee(d9t&*%gK*k-;Z|BojqoVi6MglGcJ`_Cb!mJeaVf;MZiW_|3I#eK zJi)OtoDZGEU^_UR*g?=|Wqj_&QgCXi`VL_i{baodGOzc-kc=#7-GZXE()&7kIKq42 zAdywln2W>~_PC@Za@rk)_yg-F-aCn*=t8|z`@}iP8AY^%+W^X``s_KS=*ChTrdd5p ziT?oe4SSUtXVzo$7|UO>N9PfhNB)oh0H}y`?GlgvLm4$Ssza5hAcjB84}@it{{X3X z-eV{>HZI37i59juB+wld_{KtO6y}_Cr;NCMLMG-yXs2EYAe!or{_gElPfR9KUFL&| zAXQ6N6iC`|iFdMjT(rHVNv^82vXP{aRkIUxKX#m?(pEQwtu#GpOl3)Ziex^K#*^|e zTKjorx9r#6B1X6uLV9la!<5#RaMP70GNVY~^QB4TKlz4U{_!Z=hoYYd$xa+hrfmJi zDL&vMB`b2?Z<^!rh7>-kaE@gpe2iFaC2JF?2?G*JRd-N{nITNb5RrtV}!Iti8 zjO|xo$-8q^?+ZIeo7oMur!u|`9~f<*YfMh_wi|V;-elTH-@HY%8t>kdxW)oJnhW_M zwWGx)&BxDQVLkx*$@h-oS;`kPBakF+B$ zHg$$!&TDi(nliu`p-ivy4~%8hZe_LfI-i_nw)Xa=zJ&r7u;SCqK>0-SaVVQ@L!BxSl~I{?jxd`# zI>$PQc`q{KV9Pj_S`_kQCWk|OB0(AU&BXG=tjBCgiYzY4nLt)Ms~JVaJt~wq`p8R@ z2}*}IJQ;xGz3eiT1-_J$tlDoC#6*=BFKMZ3E42DRBGJIs_{pO?ES0yOt*6sURX10D zluI_LnPe2rDBCXR>eiLtbUc@5P+CmWYg*VzUTj%d7aDDe=j02>n3q#-2b?1s(&5h; zSwG>S%ALA@#xgRuzKEMn=VD`tG0Mm8!(O2GLGGCSufi0!iAoAA+$(%|%15jS_(g{1 zT3v3URSRyKZK~)?jgf20PS9c1=4h+ULbk-O?Gx|r(;s>s?pNx^KZQ?a#kTUL9|T)1 zi;Z`P1rN5Q8>^c;s|k8x^-L!aidj!Y4#7i-3rW~x=<`|w$aq?(JZVj(AF@ULa7sZd zR-Z-hvM-0FVF}0kg250DWZp$8pPaI?36b zZ{HH~wbV{=ezL!WN_nnH%(M6w9QO#y(&y`B-1c<8b~4aU71dYpjHKclLS;K) zRC(tEY3t>O-I&T5w7!%qC6=v~iz4uFi7BS!gy?=<8MkK1y3c!|NIr#ejGs1@2GUBU z-))@)8z!mI7hqvKS0glQ>Nq}WF+DA0mK{i4y@6xqJ>A|+Xg1TzN*1z*k!W8)53*s& ztl(k8Hk37(LR+&$l664e8DZC+b>E|;0=1KisD&!ZMTu7{ccU2@K6J}x+k;Oje;CSs zxE)a~{zae4BP0(&&wOO9#%bUFoFDV({Dv~m{e$hQGPwT$0SEs8kujAMv$To!kr@~K z!(RBG{jvDO&32%}U$Xc^KCBoW${P*_>?+krtZ2+P)*|fD=ZSY*LTn`7LaQ0Z^d<*F zxnoVI8#@#}Vl1iW$lO$@VMu9K5U@(H9ol(g??b)H{aEMlsqDJq(rq{_suGrtZi_45 zj5d&^CpSdYkGRynI$Hpbr+@r3v%hp{`PKZtlspN(xCi0x5xM})k%8HycRh?0UeIQRg zKQ8$trtG1yLv{ONW?j6QCgZ4Ztus!ftf&)dCyL@B4arQp+@$Mim}v~%t%$n(YZ*3u z#3=@1T_M2ep|*_$dy;wMC(ZeC%2cY)w4Z!9fGsXX?BL!TU&%mzIG@S2?~6}t%a2Vs zj&#?eCi`HeteW$Ahh15qO0h*ZkU%_@geA0@59sGR)T%t~rCxC`%@_#I4lf6tNekDd({enmT03u^5ozhAk(jy~&-M@rStl^$N7@2nlQrV}ZVZJ7R zb{X|V9|?fyi_LqnA9@|`SL(+9(;i7QkJWv*twJMW_b4gJ}&9p!4ADnl&#(CL4 zIBmyVW#)>~g#e2t^Ait*+Rdb!JK+m6Qm85wvKlvaz#Y3SQ-^s!I7lk0zXcLE5kCR$jwj`y5WoE6(j2=S?J^pDOwW?%gy^No3Dc&JkgZAmRY zjb_!4FJV3a`pNf>&sm?!>6CRijMpKp=&dV1OOl_dr9~d)@3TNz1w@*j~UC&Vy zGmc9tV<|Yi>ukBA+cg!meB<(kArmhNCjwt-s?i`QTEm_ZIzO&5htDY#S6DG2ls~-7 zrrF-K=%33OGIZXgqqUZ0{e%SIxnHx_P2= zB`8<6+wWs@$-O@j3C|i!xC<|*{46Z|6)SpCoyno45%+-5NUrovlZ#4sqm~v*?yJh9qK#_jL z!ImKtO-m#)U0v!^zF7yJoOBT`+pcz?npS^LPS8!jU4X};GK}SCGwt@KT@Qkdl@Q~lOK2egwM+V?o;s9eEtie%XDk@ut7Y$+L zCgE~=;oc?>x*HpkApVG+So_fTa=%hJ{3?4CRp^v#C=X2S=n+V$gx29?!ar=^<`Fyl zGYxu!-3U(xDnKyWkZl|2leEA$jw2aYl^>Q5`C<8?)uBpAI8j_tsNiP29lP+x6fG}G z%}uN+4NeQ3mr%cYO~iH+;18@l{mU=?gMh1&7<*;ym}SN4Hs+ru2dKtR52n&`Qr~UL z`HlC)bt)wCg7l_ZQrUR(+D}rWC(RBPRH{+VO=KRmei%phjGXP0#?t;u_r%b*nd2y)5qAIHdmoJD_EW))#7E0%VdX1Kv6xn_a9@t4+451SwnBPb;bn!iAZu(=3G?4%c}G&NyaS3JI^4t%=1- zJ!Tf;bqpY8*-qxh5tF2)oKrI`r?wti4Gp#I=1n2VnYNQ^Wzj2~Xap^2#ecig269#W z=wSs}g^Mc-Iw;~dk$g)@zS5BLoB*Xl{9 ztB9zR@{F85)z@C9LVtEK;U>qjlV8jtVTMthdS&+8N_W!IjCP#5!>B_}J23iwxPfv< zpbtzS(^!)MK61c?x93?$Utqxf4Lq^;q3-2=tPWoap2ekH+|mL;^oRanpajyIHQIMV zte|aBu&>#w(mqjys!M~y}YL{=@7HAV%();d= zr^=6XAM(TVLv|Ff2vtH)6f~inBvh<#hV3ar&MH>h%rz)4arKM65qW_L@E>@4`jGmkoHlMZAM+vnxv=7OM7m_9$G`~ zP8B;THFNd7Njn@IVEf@I4g|i@3uWXRmNpP!%%ddOO)s01P1Zd$`Uk2R$@8qK=9V+0 z1K%07dTjGvM%ux3DiirX`_p3d@hoiT>xONmx#o+-0TU^<00_Ws7{U59+^3+#z$oSl z73+p()b!WVQE$DaDX9j@$8AIs#H!Zjt+|&<^iCp|4xa`Edlsv%-F2u-$!*zDEm?&%TW#pyK&(ad#sYv$zS$4J!YSsa zrKJ-1@uhoIe;ZxXB9Zk04jXc!Nk16EQ zL+?NeZctYYX$ehHA4?dv8I}%TeYlx<(vsuET#Nt_+YGH1tQORVDlW(1VzgqEt!*Eae^;d|tYD_H z?U9MSROW@um$U90l=8j80oe)g9OK{IgAcRhx-+Tx!|i#Bv^fkZU}pzzHcNZLK`ZKn zyvfd`wS}g>T7QUy%t@$*)Kc>*;>9Q3&|$2i$}BNFob3h)UzVT6UXc>HTwb&Ed6WJi z7azQzj0IoYc{EUuIg)>Lo`VRLded#G!1SV&XyVo7rL^&mx zZC>7JvWK*iL(=mI^2Wsv=ZZh2<(mN{fj2CH!7t7piNCBuC1zc+*0j9MI1{+~C#n`^ zLZ#OyMJ;JOC3QePpSrMLl~QG=m9Hw{?{9j9`;Rm$gpr(1iAlKfaq>eVX_d02sbo1L zL}6r9qYL<{gmeP8||F z@^%sDLtUreGHMib>!e)soZWU1=1^CRuB^%O;HNpT9DI=5B~}^Mbvpv2Yy`CzEW9<3C851bu3@A5}ExCKl+ZT=Mfs2VxU?k4++aaV1C$FhHU5m7<@Y zG&0oeysPcDEh)m?g(M_;IN=S|nZ!*@uSlh7AgG>5bmaL|B4DXW6Pn7hE=Wf05IW&Z z{{VMw43o!8`r(BAWZcPL-A;)3_Bfeq-wls3HR+y1(%jA-Y$zd^$jAb(|l-~RJyRA0C`8hx^876s+Uzy z8g|1oXwpuqxi&>Fw65qVVCMN^C(I_of?;ETajx@36- zs}%!E9y)J@S)yA;yQ?%pIZyIn%F5R?t`)@!De7+<6U7e9xN}lzO^4NvcE45z(t4w} znO#{zLL6e3%F_h;yOq-ml1Tbo%@s7Qt)mp9;(4LWr!(TxR8^Dpj(G_9o*@Z9tCKS> zW4xWP6Di`7sSGp51}B`~60%gX;=r7JJE}X_=1uaST>{!Y3ziOsfXT z^wpw~)=xA`2RJsn>iB|1OfB^bPd>YJ8&sokh9$8QY$dXNExmPtx+Z21mgi81RkKuc ze;W1=fW-91xr} zXtXP2l}Gg-YA}-NRAD93sF-GD2t(|U4s7U~p}A>fAgrLRN^sl-V+l@`e4`0Ym3*S5 znP)hVvJ5RWQ*S3(rHjNr>-+>Kz#dWW?ua_7x+WwY@^nG;P7x^5u2M6p=d}USE%vB3 z+Ze80TI24TEJ9voNnMj|DjKUv_z-#wOHE|pzSo-XlGMDU*H@J%;SWx-!$T-Ow6`?- zO0R3K&XPdbRgHN>0&1+IB;MbxWP8ee+>m6Z6caA;s)tqe4)5eUd5kTmke=CUs9SDc z98S>!YFe&j=Z!b1rFp9UZ-&@?z|7J`CFb@Ku?su59+;S#m_}l01k0g?d1|Hg8&gi0 zE@bO6^JiW`YN}lQnLg}cVFa-DtBg1+UErkLV3i?xDr4;!7TP?h#B){~?REsx8D$NV zF5oHz3`%jnwT}ARjQ-KGB3<0#ETP1bII9y9!%H;ck)kVF8=|6JO}Djf=qIigU3<3k ziOnsxhkLSdsoTxd23^ve(>J!3#T&A4P#j}vz68Y4ElX^sySzys93eU(rkkj3p@Ii4 z)5jgRXr^VvQI@!mmcA{+X@c~ER92;T<{VX2g`X)1aahz7WrrC#7HK}Dhu!Q-k zikwei{)m|S(D!D$H}B+%tkEhI3ERsG^-K;^;Zp_}X=2k7tp`dr6`fedr+iFY9Caf> zCz>UdWF_TR(B2?^)xXXnclJt#KW3kFT2jV7X2ud8&MID!U2B}!$;y*DT(sFJ66~ks>l>JKga&A1(%)W6cpKMZA zV44Muh*9?*XnnUD)6|%ha^Sy}I-#>D=vGU4R!ZEs%O6j#mJ-avBuSuHmVF#6DcN5; zeKtUK!kLLx>P|Cy70bBt!);Hq+vr8fa@8Chi`&RyDU+N_=|533p!GP83h9Z5A8F}Q zr&Oe==E`e**YZbs$_|m{=Pmo=#&6?cAMJ}MnQ@lX+7<(JPAR_*I>&u*tzY7*r>-rO ziFSV0W4(`==z?3bI{r^av#6lWm26JiCYbMwYf|5NW|hTCi%%sFP3)KQTlM#!N2UqW zdG}R~G?rmaFr*oy!#_%d{{V(QCOc?0rjn9#@@=G=s+Ae9TXxIiI;$#D=|s1H7$qs< z5&a%0pp{ygQ1adLc@yu2Pc&@I8nZ87IE^L!(eH#AhdzYYe0cf;=Y%}=X1%bxL9w%N zgv-k|T$Pgrn`psH`oq)I1Tx!#%+&GEIcVxM4v6={bBhS`ZEfTcE}O$#DuRaWAt)Nk zKhN{U01P_^^hC$rhof{PB}pnKgJ=;~F;myX}Oa{Z5mu=@I-upLp z9z%hQwCimNeESC6y?7VUprs??AirPvl(6r@oFZ_}dx7cS118NcG@!2V`o67Oj}hv| z9c}i8a01)RIfIGX5A(+zvoC4fnBuWI%&W~;A0FM1*nQNoK7rmRJeM~m&-Zk~Wz}Ih zMeF7nDX0ktgCma<#Yig2dJ_a`WcL-V>JzQ+<%MR6X-4I+;%XJ^Cjua?v;7${y7jQH z-cMXNH8#>aU8E`GOf;~jl%SJAQOy7`IImPHy+#O_$TSO}R#qwJ%+qNw|wRm@Ueu zH=J!M4~120Y>S9H1|ypou!Pj>8|V@^&d4pxn`%|gOj8~t?i_fYC?=t{8x>op1P`AS zH<`6A+c&hD=_h)L^S&2Am|+bwmL?EXhRW=vN9_Q>EbJR`g2_&84Ocp)ie`YstNFae zpe!UOHq&Pmryi1TG1`EQ5*(N#Y4Tg<#KNCShiok7*uz_DzUcTvOsl=iG>c2Oi9p8e zfAbDZNlWHmbOh&|(pXNwk%D5|swz>vOI`f1U!@D0pzKJ(6rre#hh+97m_*C6=~5g~ z%^lF(w9A5Fw?WN5MGc|ksv3FibrV_&ZHJzA$^&jKB=HTfrGysBRZAy!!w&?74AX_P zYky(Xp2x}cl(|AQ@{Hn|{_g3x@e_%P1RU7FoMOU_f_&?u zskrY%RX-yZT+<$$Xc{YOPCf}ehTC?updgGv-XSPZYIO~c z01%0mTT9eIY@te~+Q*2G^+b4zf7Oa6QM7tXa^VJH_cQY(Qr5(c$%p-afQ0w~>kj~z zn&H{JagCx7>l`X?Z~d`qltH+S#w{fo$5ay2?bn8HV#lVRbRqdy0Z2BRa0`gTpx!CU zBl=E3G(k3AV!RT!a&A1(iAkqJIZ2^Od8Ha>Jq4Znk4!qttpvI%+w!iB6^*GQ`J^i- zHnQ$YY-e4SRZ5#*l7r}zdWnLjQW|Rp$JQkry>J^)`efxCdwNl^JpF_+E>pPiBWzu9 zIw=qa-My0nB&I_SyxJRaD$NR!SeA6lu3A=Js;j!oRs)(lR&mbPd8I1l`9ipeZc(Pn zot=4ZSit71UTVa&%H{i6xsRhM1lELVAADsW^Goby*O5ZD-6|EsR79AVOC_nJWM->W z8dOBn?>Avm%2HO?;*82=-g)4FwV}Tvu*}MwvdgB4RWLh@HswhUD!>W_Cyq0%x`ng$ zfat(d6c&?$l4?7F%O83l<$>6tym7@)OGk?cr4s5tosr&Dsder+MIF-{)G>-XFHyNL z9F|hkO^U=taB(*%cog;!r5h;;sV8K84pG^p{idpVOcDJZf9=y2f1~f+pG+jbB&J#_ z3Ab!2m4{t$(JiLYgu0BDy9*TrypdS{0HvSx(;xKD{&dIvGyedcF!C13mwBV4g&Oi- z4a>B`re0VovXTYdu~lU96TG~buV`JrJeaAFa?bYFl|e0<(-3YWmL@%ageRB4C)OSU zv2Xgb#{U3CP^qTVRI7v^y#Zj}*|wDkoJz&Kkn!z{U|KS&RWzx!2Dib{1u6h33es)l z9h_jo@{Uaoj*Cf!oUnmC7s3Oru4K`fB>EVg zWO~H>U>49_3kB9ggT0|OxO#MpN-A-~ckqt*67<@Btm-xEZ#+?5Cth&lwNP7WRkSY)W{ew2*BjC%>saTKwNtKKMC8WL8y^ za(>Z!6AN)hrAG4>m7D2>IGaeg z(iY&(w6&V3wCPPw_)B3*U7smt_{uGZd~lOeHEyIx5TQwB)Yoo}BG4)AHx!2*1m9^t zJE7Tyvh&S2gn(t&q>?zR895BF>JTRLif!E@IbjPoggk{P?oDD%#umcmJ7C*a)mLR> zDVtK^D`$h2QJ^$8@k28jN_p17Qk1fLBbE=X55jwzC|c!EO^9Y{(lA>GoY4<|dP$+O< z4%kzRG(dp6B^IxtiSJRrj5YpQUz#X<;T8iGMJ`wv7-!Yn37&67Z4qvsQepo9Ti_u) zz5zb4@Flg1p;r+dsP>qrdne0I=%^KzUaGvqx+I)Q)38`oMar|V@mVlJlCS8pc4;{v zN%op2`K$*UT`HTFog_Ge?*=8NrAU{u>P*y@9i7@lh=wcBzIJfe?1oAGS? zFojLXJSSu}tr(+;Y1s*v%53)Y2^LaRXzPBez6R(E$x|aJC;PLS52NSJ3vCWLDB2vD3+PyW@KN}1L!Yv6qtFpB_2W^ zVQrG2H(l2q@q{#Psd3~FjA_m^Rk?(hK0~e!ZKXJ`IrICX?|dfOv{8V-Cy-%SM5C!w zD+|ucEGDWRdO~qE5%gP4ApL2%#ecV&4T>QM#cI(?1Qw;L5^h_hQA_y_EwEj5me@(9 zQ;Lt8o;Vi-Bnmcxf=2E>U9eg~D4S64iXTv)H`hHv3NB(RL5X^OgJ|3^jYM-O2p_064-MP6_Md?hFqMP^6@%aS)`ThR_x$t0|>nC`huD ziU2ltQ_Ba}hoL>z@w7pvsl0X@^@qQ}r?I4NnU)qw-kp#XP^Hw;?U1Efb+y?zQ)DgP z_`Lv4qYF}QqS8H~6Th-lOMcBh=)|SO-i3@)0m;%8V^ISRUc~39kBm0q!7{2naF(1w zY`akbq^KQfXptL^_un zm_`Qt4&`w~#GLIV9<^3yEYS|3SEk#`N>ox_Wvy1-IMrTAFcN5l zCsm=UpW_yvt@^BuW6QX8z$>7-%0h4ha1lis&u+&sKi}?!rIMK`CiI`0UEu?a?J$W6 z1TCzZ*{%BTE6-dDDIBVSQc_kchN5@!oss6YfrKhy>gOETb`Ipg{S0!!^`Yobc9XdF z8}*03z^Aatv|4!@O}d;=U^h1=w{Na;TJU+GlOs7=*HRVqDZmJ!79+KplbCENVMMJr z3*u#@3axFBA(q!AG8BY_nl~ONH~y4(oZtFU;&XrLM9jfyY@uN(NN`Zy49sR0`x&J+ zs{*vXFOr;B8jwkf_Ke^=!VG9gPN_v!ywM4mnTJ55Wc)-pyk*vO2=X|LB!W_*dQ^i*amKSUldoLr93MuS_0#05j6+hP zg-YcKG|@8d>*n#GKUk5!5H=WGtB4QFR;reM^zx0zj74&L-K8>rs?8`0oylGuG>bz$ zB<=@bJV$&sne3c|K2|9)CEm?=)2e!+c&95#_U3TgW$y8R6>jT_u1)JcdIhAOaTF!S zk2+%M$&vp6c~C;9CJ+774LqkD%*?Rc$W5&fX+;i^8V$PVsM#@T^rlT4=9~!jaXk^F z>34|&!g@{q=q;dznpP-jGzXV|JQvW=M5UW9s%GX^oyUJQ@UfDn8cV@O%4XcMt2+WJ zDbsve%>>lxD>#xk$v+ql$<9hSIKS&p%%LR%&`Cbg=79X%+^bTyxKuus+Tsw@q_pIu zwoPu9CDP?@^Ok(07f&_+0BNFO6mfG)uBws(IZPY))U%3=XGr=o4uJEH{!Zw?i-4qR z#;jr3$HIs{v^@##`TGs}!@#Go5*C_rM+l$893npqaER!QbaY0$eG#nRL~Em>HPO&x zT^$ju-$9I9=#6Ij3~M*h8qG$teFhd*K`J{TJiY0;zM9yX#6L;Ry-JnLIh?iF`*SIWmMHx8)q~$YG=kw5x)XQFRO) z?DNV_>r@>AEi)+b7~4>?KM{&gWk!-lEz1n*zES@GF3HJS{{XyBs@djF0s>BUar1&x zeLg}5^rXgl;5 zizz}y5`nk~!Yng=FzVugX=@^p6v3D!+yB%Dg6O#x|5i+K?GnsDdq4D`u-Nn zgOH3wS(!CQlK1<*aHks~m;B*^xnj$R%`4eFk=kmiRckoG+`l)U-yicyJnE}-%Q_Du z=M-VqGWrqqb6xIbBZ+v&3jRuX{Z3Fuk28rL@gLFoLAuIo+x?W8N=A4q0SYmMnt6Yh@rk+OQdR4pFmNBtI0{YQV_2F6X5Wd$TIz*)4PHuY_V zJO2O=DV0m>DTWwrGYxWpDtSZ^F#FFgx244rV?5(&lxOWGm752q^UZXoTQN({;)U7LP~wg>+Y}Df ztykvOYi{wmEB2b{MgJe-;+A*RNav(#jInRCB=3i}7eltxwoY}!+tA*pzMZL;j?S;~ zCO-o?0KnIxao6I8RWiK&4~)Nx$%m9DX?F}w{YdGaMSiyxOdmo=Lj>FfNWbFOTgRdc zkJx|376q5rxje!VILnG}zKL(UW)#5B835?1bixc2KK|JPVYLP%C)BO4)IS><%4UOV-&tuIAYF zTPP3{QX(|t{U@c+KjmYW4u``TCO#&g%^AP%5$YRrC~*9CDAA4W%`;5-3zW~dqTi;&(_DvbxaihAGs@cQ)-&$RG;QOVgW*5Jj zYiPE>S95ijlyZ7o<2i`Gzmeq4vObm_EW2~)%-2x9Guxy_`bK^2Alc2 zOcwZj5T^*w9SKZB?-gO(!&#OcD@`j2w~U}c-GP^1(Z{)&Uw(63$H=prP}fn*tf6V1 z-tp9{zsy~!S>CPbcPEnh#Y^pOCw$(;IQ61C88MTmmxMoY*+}X(vFfti)ja9;z)LYp zWcs|~n9Aiu6Uge3J88#f#)q}an-PJP*@l>idk0hu@LZdaIGehFJ1 z+#j%%Yph=4^qtvQx}jWUu!k{JdZ7I3I}CvgYDk;A+hdCdb@2=j@Fc0&5{=8!-UQ5T z`TB6;!`?}H2h3HU{_ybgr))WSMTRUsslIPn<@`Nyq+aX&JyX_>8hvYxnSAcK>H5!C z$z)SKc4rwm^MjN+cky37E}!1oZ!Ej6xajyof@K;1Y!8?Hq0O{fU0QvxmriR2j^?wk zCs(@GSbJ`2kU%F7N5UzQw{X;x)1&?NbU$=EL$Ll=t~cb)^y}5DMr;A)`#(5}>Rw%~ zE`6`iJacSsR$cdgLpkBPD_%9xB+tWE*&D;Q0~bs!sR25zXu>n> zu@d1U*BusgQ!}Hq#w7gA^M6_U_+|x*O(qJPuqq{xfzB@KMpQ57ZyzWJJ$@8vlCH1Y zmUVOtj*v?urR}Owv914Ibc5>^`*)L8c}Y6T*WTGC*y2aJMxNNpC$=|r?Y^r)3NBb! z+$gol{iJZAEu@UCooSTup@S79zt$LCC#jkRRHrzteNo#GXX6xoXKDC~r54ofM+E0- zSN*EqvCgA0^G=!ygI8jdv6i|?Gfo)zCqu)>>tDmX$HGxw=3i0+9u>yD*{Da% z$XUd5<}`%Cp^ZzI>Oe^2SM?g^i3OHD826?Rdv^7)e_=+&XEMoBdJwl;2P zd9#n5t6`~*@9rTlkf&O8xQ%-EBH^YCN*&soa6{ERho32E>&=$wogCm}9t zbhrLbzp@?8_Pe|SSBJCi;O{2iTa z;%trJ$|fILJw4gmW0SWJ>CLv%=WUhknR>H~hs(#u4m16R;VAQr^`blVLp}f?7Q@d5 z{0sQVOoc9!K~T~6KoA&+ieF0o0RDr%C#axr?-P!oLYB4dq5Cke_E)huJN*A(kpcAt zPnpz@)Yr4qQ`y#zP~js2G^p^s@KaPgDts6X!p%$@^IZYdf`EWze-pdg!LzZ@gT ztbe7l^hS@m*0XdW5Xa+b>}u%hnk)BEF=`e06~`Zx*z+?3p0pEg#+Y1xi;eDCEr_C# zqBP}N_M`PLAZ-;ZKXYf?b~W(8etDJcw~=165WU|!jPEKK~>ECM}1y&g^6(+uHorm&=)7yRK7HWSOEGRBPGLeyxQaNX0M$KmDTORXo% zU9Uy(V6qST@>j(k!(R$CEi3#IlQ|c)$CMmrFlsRQ(*YWprAspxTm{1(nm}QT(Hqr2)z7cILAy?X_6+;X;Mn|L`cT4R&3=l=@&?i)nem&|8mA7UW7kE6Q zTQ}?DxS;1V0uJOqB+K)={PwrH7^e^a3^XnO(SHI}6zc2!NZMf{8nWwofdD>A1HCXG zg1A2%h3JFl>P*YCX=LsbBhcY2OEUGZx;CDL#98}uAJ+$5Qi-%zDVGdkWi%3-^<;s5 zpG3s&CA=Ca<@sT+?JbePwJqO?&+Gt=9s{hhPkDwCKqE&vin~7G;TWvSZR{Q#7K^x`J&_Q< z^rI**ZsyYU1=)(_S)cUx=AQ-ZH#GhvD55bvAcbzG%d2?NO0uJZKdx!Z&Q@skL+Zp zeqBEt&VDO!Uzg713ZLHmpLphvM_sGhRq|d;n8D#GB^8Fy^#DJO;;F#qKqc;%y@9X) zG4*%FQvpilfl70(jXNmcfa;L?rgNE5Z>^2yto`RqZf;ElgqWC!H*>$X=T+jK7>>-? ziz{a8_c=2sjoY4G{0mrnaFV^oe0Et^{^0hA-f_YD@$<6mJu32A;WzcOK*FhEW_D`D z%QKbz@V|g*^*59In3Lxxxg+D><$7;rQlBH{p^;-#8UZl%kwJx@PM|#g6PJgdof{e% zI`!_`hsBhwJ9YMv(vLz zR`mAoBZv0FNA^6t2aWsBs0#b5Pj?oA^gY4Hu0XZ_N&b zH>voe5xG*HjP543g;r`J^Mm_;0f|)furnsAu=q!R0o&o!s(7ylT~R2i0=WR3t*|nt zS99T-p*bS(L^vR*`J#X}2J31Ea7yMAr`J%z=TLC%v?;~iL9O3U*^a3vN1wg&-^%{+ z?DW$qswny=jQ&H}#FaDK^z1#){hw_7FpfeH14AGE`Ky%ehms3=STj4prpI4lqtkG) z@sm0qp&|_SO^h&&yWPd&Y|cGLR;Uktk1X%q8%U{8(@ zq`e%MO;6zsEtmjq>Njnyxr6Oka>qpd8XRRBn(t=Q^zGTlU+SY?=Y8t<{2S^KDhQn5 ze)yUA5Vhhk6vOlNFZGx)?Jph}XPTFcur`Bf;GisjX}7j?%C};0lEnNuO%ZZvyWKpA z!5(*S&vaj$YK7K4^cV0Ndd79?Suo6fKC}cZd>y&~lO|^(pby)n$uzF06<+D4DNK_2 zgwhyz@!r_+WBJa%fQjE0kZWPwh20s*2$4GLHy6hwD(P-Fa5r9$g;KQ*^t26VL#n!3 zJ!n(9Fd}JiqF!>)j4URJ&nEUh&tHER?%$?*GVR_b`3j4kYroepQY8dUTriq{w8Z&Z zy~>3#jSI@_(g!hFrRS>`^7lPw==$&4{XZ1H%=PbX{x2b@?vc2z;QSRCC5&q?pTDnY zh%<}zV(p{@FghiL`_H47wr4F4rp3+BQjHz|Tk8KNhPa0I?2V;wwq|5hnC&U_xtt3( zE0;jxBP268c-PEuNhHp+QVS?vvD3^mX{1m*M8B3YD$3eFGpdx_0CmM_O6FvON`GHpDqGR&3 zz+u<=vHE)DlDMv*@YH#7%VeZtUJGeDobe+2p@gu@UPBQ;M3nZ27nmK+$IDqo*VhuI z?|H3dHSA{X*UHvLfKeLs9=R4MjwN%`#_c~@Ykf&|-R8V^>vZTlO+qtsO1}bwU#oQx zM{En1diT%L57jlsYzZKr(%`$L8h1<0y2bK>kD{}J>+dwbo>Uun(s2vcG&3pSR`V}I z?A=nyyF1E`zdf`}mD%)_^QmO6?0E9}WZJ(zAy_Og=)WZXvj$@gIqQVI-rs)RkKzlc z4Bp5J+NcZ`s|-Gp4eAxX3wyQqRe)*QTKSbV>b14@D{F>V*1~fj_pC^Biddw>MdMG& z5}QuC23$9Az_Z+oZ@7L`W&yYl)4dvBEZFbom(tg6J-sP;sajpr(ID7(ygP;YbL#C# zF1Zcydhyz!w~b{LR?|9GKLf-(C%nzmKPyD&Sb3W#pJZ4)DNY+{O#Q5Ib=#NWq+j62 z@NHxIXUr$8)Q7bT zi-XkuQu&XPs>KnXWzwr%Chr%a`5KL!V$!~$BQRXZ`jmL;j@nh@2TmU5J8WL90I8>b z%!6aV(&K#7JJ-_e@($oHjmIL~FYMp?@jPdlc>D~+PqUu=V(%6WbvmHL=n82FyJA!s z%XXki+5&^x&!w)xuly4)pk@7Y(SJ+@SoUE5>Y zyfZOg#K*TGx&Vmt2CT3A>(7Y5CIC1>nMP87?*F?w6JeFY*@O`RmXUoLO2``99y5V@ zoH{)tFs4wlFhd?U#k3H|3JKkaBD5eMW#!U-IAD)tlkIwR3M%B1yu$>ChO8@!3{8oJ zN3Mz8f-f?@j{2Y$4>Fp%ykd1!RvpNu(Q}y^sad~{@;DM$O<7ip5080YzjcJ15{G>g z+bS7+nYg27R91hcyHzsz`r4=^<&*lH%ZB!7{%^DtLcuVLB7MjdCWK?_pMY{=%Q;NS z+hsJ*m9xxnf??qcMK-P7W3ApEDwJ3K3CqHE#fDU4xht8>EFL*Fl;afUT8bx}vI=Tl zx_2sOb-a5gjJltYd1K%x>?~e+%I@tmC+n+o-cz^tyUJ8fcQXz?Ku>K6?aT7XDD{?K z!?L?n&7-inJtpwJxkS?Wb4Si9w}0w}jq6181-E)5dxB>Phac(MQu8V+PZ<>>o6 zQrMwa%#0#}Qd@1`a>pKS@V z6@{8WweT{%{Dt#3fwH{G;h9{>?#Y9wWi{)wfL*0qG4JxF2M^1#-Fgo*OEODVFQGOz z-MX0?pHSP-zkfONPk74|S4}2~fwFMrtD5!I3N7oskYsPys}F+_zH|V|BVY>gid+?6 z!h5b0%=4{*oX?f5m8+Fi)Nfnm9`*aGGM9LqKgTw$G_5ql=KNYmUq@J{Mu(sS{7>l9 z5u~Q|b$oPIbyih8*_PO**{GS>mJqk-u7lzdt{cSl{|g}};vM3()Ib*RrSgC2Uh!sX z{4dl}Qw`MLT~yvGcqVcPIfVQm%|#g!7AkWpDV1a@{}=Gd$C2~Y>KCXzS)#^sR6eUT zuQa2yz>qIm;CB2u2!-goP>?KcIjP|@1||49&$VkE@! zPfb8jv$4QQ27=TC>HniR{J%kHkev&mP=J3$#tNOTldz4 z#(&!CXaBKRi=9rQJE@PEtsm5VAp{kgki`Lfaknf$Vga)9e*wBWNY*aB)b#ZjadY43 z>GOX9Ityp0k^PG+(ZjBK2OqK#)c}n!eu9p{b5XaXoZG2!{RuDw0aY^ z5uxSdZzIG=x|8w%I}B-cwA9f+Sy>xQ$SI|RV}2!6bv)8if+lZ-kNb2mOy~5%l}7R1 zI4J^9&mYptANC))dq&e)Xwl3uK}va7S03q2&mq9CfeaGgY3~_GjPg} zb%s0enTPUy*dn3y+u*fbnA|3i3fyX_rCjj2-xZxnCwA}XgG>(%1)){xgG-D_dtq6aCt>JY4MM*=Zho}m0xVjJyU+_^*}@|WKe3)+ z8GcWOZDNMzfMzs$5;PH-)=Vkl_GcT^{-@CtKt`%*^}{Gv-a{~t!BhXHh%+X=-|^oN zimk!m0qpm_n`<*$e+=;#A||kqFVdZGmM{+(pfagGnsxB&O6P=>ZhX`?AwHHh3bv&0 zgFX_|H^X2?5O`3v$YS)p&bHodFfxNDDecAwM+VJ!AOqzx%xCx2Z_0-HatuvbE(KPF)!L=Klg(k3ejJ-=@=hr=qu3 z+L>^}KHqXYDqUqWA6o7(DwDQXuY6a%yibP(`b6rxE#vi8!Gnu%y~(t0^h9g}>+Vj$ zi9`xL;l*#F#3p`zcnkk{$CAqy&<@K1%v0xN0WN_}N>@cK4^M_M*+)C1VEr-R-YYIx zPg)i~M82@qA5R&@u}LFEu(%$DzknvCU)Ef15B~y8qKV1x`mPi&rq;1?GyBkneWjoZ zG%n|SVCdHoeG_5u+~Ve*T{3R`TQtv_Dp9wfS}X6*fO#wJZ~b2i?_C&d;2}sVGg!dn+Jx&yH2o zNxi(!bLSVlnXL-5V9>f^LQ1(fL7T2_|0pnAk^WPwquh$^LXKJ4*1S+z1cRwljc#sn zcOS_!pyQwx@&IV3Jq`Xz=K4mn&Dsk*NzP9S!Kvu)vT==LMpIA#l~mR;-oE2JOhri| z1lF1yl4F!<{|uL;fBz~W=wLMA)C`A&Op7a?>+0cw0R|eg5k2$H_*b* zyzSD~w)45h*@XDQb?@2E4*d`aD!N}*-V5z?YT(F5s9$@GH*!rbN;xI~RNJ}W~Sa4%eCbWmSL)&P6tt_F2Kz!8w$ zgDPjG$CP6Nn$<~RcYy^nEE*OrY-W~tD|-#gT>}rdcJ9QEy|^1MSaz}LTEKX`AUS0YR?mG>kA7#e=xG2N4TYPl`IS8jTpx{)>YXN>5`_dZ|G zB(RbnKunr0>~38SOGEQhPX%>&4qx0h!R%vTOcDB=Yj5lvdi?V>65<+=#+l~h_qql- z(d*fYgD6-oKWiCl(NVK5?{uAjJGPAkbwUH_6hXP6QcuJ10uj4%hgk!J1^p`&oI0T| z3=!6AM_q&lS-|iajQseFFPEKC_WA8oqVcruyHqD&LJe|}A`?0Eb=hBvyc7{wp<(|x zfmCBICVL}6L_f-mWY!%fmn?-6f~5gz#*U;EED<&+3)#z5ZG) z5T-R1NVA)*#5ciOH+eP+SK0ZE)|n!ny4voXX1L&4UBfUk1;Ux}!0T8!C8W3K5Tj;vf_> zM`jZnE!1i>@I!giHM(I!roli?QC*m!BH*Wi$2k8=bTan^`DlBZRu*Z4fzMLUWy1Ju z*2hyHPRDMMpB97~`T|0h;T&bm;fg1T{+hr6VZM+KUzYUAwVY*|{G}^Ftp@Jzy~HG* z(X4?nD>QV9scZyw9&M$^d?;Z?$vWCrCf2`z)KfDfbQdTI-|irGK;$xdoH9FM!0(Dh zU;&CK>BHrNWXUDb2gH>XM3}t;!_@ozV*DBng*oj;`@Ft=kJ?!kh`YOD3bPeb^|LZq zqO{ma1H5Q8{SBVk2%iX&^0df^!fsc+8z4*%=2{l&(}l>WW1duL7_TLbb`~xg(yui`<)cV7;Hq}NX2R;z(Zb85rx={y&5u`# zoDGa1D0%pW>|FXP_Zv-yiIMay{(WFOgJBdH$-C(xn-gmkhz)81av-~06VE4;+(q8P zszPg?=(wH{*?j<&SCfcA44!??DwI^mD*Z2pl&x5ekKW5x_F>*C`p-n!?NW?{2`pg} z1-L4_T>}o6B+L?Y!xe#-@hI1_^zg2565_jQWai&Wh!%O9pR%mqZ1`!oJ@`AW^W%5X z`+PG9gDz7A(he}22x+?qs&4+lC}vNK!C6Z4HNu;admPtU7Z3Dk+Qw;EDB&jT`WX#t zgW2yEpw-Peau*#-G{>(UYSxBnlm;UWb|uS>UtsCT9t?P{4n`10f9oAjj z%ZDM^K8Gb>5-%I**3>KDx@11QoCcu>wvL^}b-M@Zc`IfXKNNSwh4gyZpv#jgSkNI2_!>W(M45SZF)To zGay@CS)+UY2(uIo*&>IoZ}mx>KSaWWtvGNgEXMco>TtBY`1fPhS&T{Nv+(;)0?gM< zp&?j2TcI$(-)$fW#RSQ-LjnB4MsTZ>aFF@s*gHw>3GgmB8nLF|HLO85YjZbWik+Pv zIm?>s>nVxi5^kYj{Swb5?BmpY)yBo!c_K8GIy zPctrD{B6)U&Hjo^%9%JHm(Q^j!>txyah~|5l7k)pj-(ApNCndff(;^+pG?I1SQg`) zwJz?cz=M;vZ;PgUXjlY>h*myEE*1|ATJJjH4iLY{J<-<|3fTkP(-0m~bOMU_ASn{) z`V89#{1h~*;bLx*{^0VZa@8qiw&yK(-uq>=D*5QQ!C-z|;Gvs(c7}VH0SWje>Gy^W zqpy2oFFSfY07WLbDc%p0QaV2nFE{`FmVAlq*O7wZfxG-3h*8V)=~=8c7|Y0sQ;y3q zU0Fi37E?L>rF6ps*Mhth#5;8s`BcU$0%sCY73ACM%G;=_urR)RZ+MIC_RTrmbJ~+f zuYf#RCqCDsCXf(7NaCA0U{x9`KetzH86(Glvy2j(g#jf*g1zEF6p_eFKCs`BxZm^S z1xCE|oM~+)Ng*P9tD5Z<gNdI4>75m8*{y@51@tpxH8mY|$uaOU+M56%^XOK^It5p7=(^Ex$G z)=Y&U3~A$R0newgPw{3P>>eR&H(_3tF<15!TZ?Ch`5}9lB^GGz)R5dA=L*{2Dc2IZ zZ$=M8a_Lq%S8u+}hdviC(bnby2M0ZqQHOUeR0ghm>-C_aSo!RHyf77r7S(qoZ!>|I z%i&*j43+TxD2{zfw})n?W`*X0v*i)7B|g4~3r~=6W3W3<^GjU>Ha!n}${E8VD9KBK zo$k=fdLNKfd1|4}=0So`z~$BWZ_0ZJlyg4=yd1f~zOi;uZjN@v-*>Y^O2615|Av7? z$;=v8dM}yA(q!}U{-gB(`#c7*G&!sL$|od(8^~T1 z3ND_I)Hol<`3ug}`bYxo`0hu-q)7?_rH^ExoHEwE^JdXzMhSNH7?{4@cM!maz8w<< zJe2w>cfOE6WLTk!YsQ;E8i1B-8U4Ik8JN#q7KJc#&oMv)K#VV$eM%CR^GA!+Va&jc z!TtULAZZPAL38jg0OBlF(6l1H_*l8wJ);%>xM$NMDy#O* z?EyHI&*`sEttRa8>hGK+2WL>8KAfHB0`y)$y4Gzsb>k0tYK!Lu)!!9@pMga6{q&Q6 zkFddc8IeDkrb2XBX(NSwa3ktk4`IGXJb74B!+KxY95K(z)Y-^bt(@?ORzDO-;xHbH z_S?!_nBCx?sx!XOSF5teW<8$#5-OCsgKyH>=x(to?&xE7GG|Q$P zI%$b8ztT&5AzZ0WY@8B=3*hPi+)tx$xY%@>on_{yEadsk<)bR%ip=0KdG_*F6buT< z)p(;{0KQy0qoQi7&S(_e&aDcEI=xzP8eU*4L@*jXDA$1%)bE^Gy5CRV`GaeJU_$VPb-D)jIKAV z`3F$g41+**8xg;w@=^-`Rmt3EemRygpGg*2ST zmzSKY_zc&~6vS-C@@a!p;*Mc%k^jCES1gz*nO3L?%@PP3-e52xfYc+-Dul}`t~~hd z!N(w#=5aMCm}AU|S3mXP9l;nRi?3j4@pOW5W-7!r-OnZ`^rGnGbwhs^5oeGbT-A}D zvqqw(?Hq>57mh|p;Fx}d2yZ1Gf=Y6_L)7!3s~uloFU&>X67!1Dlwj|V zJMQy2PUumpoj=hhUW7{nt`>$uD0v3Y7X^`n$XaQAtBI!^x!gA-79T7c^aVr30))ex zn-EjV94jHh5OSWc0=RsKI$R`DAL&R6}_l3IaoDd3Nrh;?zy*`BN5Ax*3#n=1`xa4>3cd^S=ut zZa4N3!H*cPC)2 zp;1-ZE8wif@6(4L@Vn*YWC z=5OIYFHU~Jq)^YFreKZ0v0|OYY-3Hxna4Ho^6T`b95J4I{;~%@y9J3Ut!RJI&#GDl zqT&uc_$QRIH!_HR#+Bw#&(}ZMn@)?3_R3mv`T>DTbd~0bqugT~&t5&#TA%g^FjkcF za-+e55~r$Djy?f*xE*NGwpWudA6iR0nrGoTtk{g{gd~7yEX-1|_3i}L*9X!J5-a!v zpI!1zqyO4c<74j(eA)vw0|;Z`Om)7pw~re>wcNM3_ZRShEH9zYMnXfYRtz?r)P!2m z*D8}}9Y#AjI}MaFoXxmQ1|I^+SRE`Pejv=RXyK$G#K);ac=3yH7nTTFZAuk3xl;ex zNhGWYs5;awI=otd`;|J2j&j1a8zE&~_BCpR7h$v+QS(g!$|e?1^@B>1y8-ueI16vb zX=4ecnIFXqoIpT!WDd@yJ+1TY7a=D}%Km^Uf)?W+WZk&0u(&ccFcKU7;@pF3+AzTR zPo-E@2`Ev5i%Cd04Piy+GF^$4Vqn~mKyZe7R0|;ZTw(&I*?wRLEL3}f};3~Sfy}@K{eVuRQpu{k5%y? zwUI9x_i8i9?{Ue)C+8(#qo5lSN*w@+dsu1X*g>_=_FodCJCrjGdwe3RWJE9E71b~} zv5<#tW-H;ClAE(P8|Z}ZXkVyE3MJb~bGZ6gE}O}kNj)uoHfTH-&X)PmF~tN~<$RsK z+pP4pFvl>hh3c)P#5_)s*i2MdLZ@|udT%J`)QP&zrnq+*Z_zpOgH+Wv8 z|CO%7Te128Yyp-Enl<9xax&@Y&inq^sMSnSFOlHof%0(^-Q`UZDm&GMzYSx2^ealQyHkYy`S;ueXV`O`v{$`m-{2EqD5}Bn^9`<{jUf!g zw3yGmvHLS1=@i5#LEz*w>*rxHd?cv&c(m7`&$uuG--#$lx`0pS4nRyRzYNM*72BW~ z{w|FC!)<-3H&g^_E(#ECdxL+q)l=*>$%jvD=*gvvf`4|c@%XMtU8?>!&dV#7^Q;O| zRD-Sh%)zMd6VbX=6KljDBq2HCb|@un8p5^2#ijTOVvNr|k8Pt9M;by05(EuKQ}X1J z)x`pq?HxAfL3DF?PT^J%)oYrtyMb`SpGcuhXJ1q4(ffw3a5&x9k4u;*VvBX%)l}X{^XY_8>(bBwUv1oq=Bw4@l~qdvS@LtLbNx4?%kIief3d z)iMw3;emjaz9)YH{_!v?|5Yw;-;P27Llk<9#CVwln6-|Y?`CAsiI9Q6I?}hV>IjbBCZU9i>L9NdcFEC$7DLI(-;qO1o+R zfNRwxCq1H295j%{ca855ins$42fq2a(i5ZiOFH5Psvkxl8Ch0riPXdH2udtuyWE_9 z0OiyG=)$Kap(hVC_YWkuY<{}j(<|9r$@bm5X)ELnt%M^@o^1}o)uBZoaV@OYfQJNV zR6qrBK|=Mb^(&8YO#s;xKg@zp)45RXuYiM~Zjw_ElBao~eP1WAiO8pjAVJPtTq29U z7%wN_cs3&42Eg|_D{qk5~2&?!WN?+jqasjMJL99Q30((SYKDA zS3p_-dn-3z5M`B5bNxK|-G#6Vw3F;eRQAQ5rRe92EE;~uNHC`;tt3t;6zdYeFwLk? zG%}m8CWCJRr2n!DOAszw1SkSb%_1g=M(+46xiEyC75hH34XNd&+WG4t&-2ubKl5BC zH+U>g&j4a!-Vtg`~($=^vA~JfDaQu;S@yv|A{ImlIt@|Un2f!m@U#6-~#*Obv zU^pO+@*pPH!GqNNxFD9)8u~n}%@Nyyc>ewHN)4v{QW&!X8;96uC~jQ{W3EvL ze0k=Rh?U710;THU%z}ZUIjnW1<@FNOcL$YhAs%>k*obQ+9b-C8pnMyk%ziAVIwc2Q z-RCJR9lL+dkM?58mmLYS531$gN+UH5?XV0_#acJ4eRJmrVxFtt+ts;BMAfSydfM=S`Fn#NSkBuh+uiUx1R=f{+MDX;BaQX_eOIW1K0erAf>7SUE zHv?3etg@_Xyup%z6bQKp*aY2)lxw}QZ8m*dNXpEc*DF>NVaoiHo(DcStpX2w(<8)D zfQA7ip$2f3HkHRfCxF;xXA1(TQ`WbTjbJC=b*aQIQv}i|I-dE39vf7bYZjsCfwgzx zSsgHHj;aO;vc!x~j9jjgXZPE()O}4_jDzBnd`8*1VK1yhbbNm_{%oc(Vp%aFnAae5 z$qmWs4;Rp;V~}|3weB8=VrFhI1Ad>i7 zC8$G~7|iLYrf`5$C!?0q9uDarcD#5C;hh4lE$mvMn&B7(-o(J~_{b;lmn+URkEjzB zF&U<^CKC@keW9vg+ZcMoE_0=NK#;~-@uQN#f?oHSW@$h0^G`vwC4yb53hcO?BxU!^@GHOuWIyl}Z&2zBn$1p&Xk*$~AV63FCZrY^q=^GS$V1qL2i94;dY69$8 zBIItrz~q7woOsGVqCvHnIn3X_Zmn#)ohCF%Qb?qN-1tVTke<*TD)t071Tt8>RyeS= zG`&rd4?{4B!YvYA7hVk-+|vufv2#c*H}jy8s371kjnFfeZxMz!CCEkzYfgmNnZ8O{ z20)R|>xuVG5=6sjexL5K4AP$Yue1>6;p%Quw@~?J$X;JjjZx+(=6*2<)f!ZJ(pXj=M78aS^J&W^+HK2|y-T;?dW^F7P$jTc1b^TZ3S_Kbndejn2` zqg+h=blc2(f%u@REKflz42Kj4*e6YPZBU901L1W-L=^C1o!kn7n}@8a9)?}i8Qa}w zrUe=es)bfIK-XY2u4R5ty2WFV66o$APaCQY1F%Oj{If*Q6egaId zwW5g_xhyn78Gq;L$bSJu+~s57=HPFTs>w&#Y0Q7oJl9dHLUYJ7i$&Nf{SZt_UZC;n zlg2fJ6;BBINz{$`mvWBi_bCtFtlIa>D|{Z_Z4DI2e?b6X6wy@^p%>iWp8=FhYSf)t zI%6L`!islXfaCffXnyL5PcaoNs|m7Wj57GiwIkVTL@SQ&hun?n{MK4z@+D9mLK7%) z4Usk=@ivoDFj#8aCQzxlEqhhBQ$$VsDi{M2Jr`Sap(5w_03{I<+LtstqUtmf-)x4B zD}=A8OJ6TB6b8#gYppYBON1yXDt@xO*;eL&d(GH`w&+SS&GjCxkOdl~EExE{8L**ids$bXim*zyei`-b5s8Zc0N7+(Yt93(W*>2fSeH4x22K8 zG-k$Wl<#(75>}x_Dj2p*#r|F@avD0o&XUN{C1UbW#4&}Z{oc#2G2SYLZLjb~z7%d# z&lyWZ{NN)c&P#$4iREM?Hdp;5L^#-^%~`OxpuRSMOOlm8mXgCcQX60iYvnbJBxulB z4WZ^Evv_sVeVH1x63=VtR~UP_SpCx3KZ*#3<#?<m2zA&I2z=z~F zjEoYOO=MCDbK!N4AXDhIU>8~xS`CWFIHa%U5a=4E6(>tL3%|+I2QE^VL`=Sdu+^Eh z;zRZ-Ob}l!B!9aUxq4VN?WZ3j$tqW9W@`^g%w=Jy#CNj`Re$glR<#pcFQ4^e70ysr zMq=(=wG0<%{GMuB&L_BM&%;?tj*^#Xe`BLar+;o7s-l}xBAZc-#}JhLlFS2Y#T5Yt z*p{5JtD@)^FC$@#HO0ykcFXqjTI&=^$f^Bm843AjRfRwn262@6YnX_1yvlnA zEoR$c6b;15H>A@AwOSsVQ-5PU1%gv?4A8 z>;vFmG+3s!>sR~&y(yO6D!hP|J+9M4g=qhXs z^me)>PFUaIY%hz%#$P6dG2c9xeoMMS@p-9#!}0rZJVg6AiMHvhBV9g#fTVZbWdB+< zrm-s>ThkAzIyc4Y&e96@vb#oN9h_I0yLz$j!F$H4I7iS2j`4P@`}t81U;yf71C^Ub zO!h(ksA&=Ll72U_H!OS;qg_L*<#FcsSwY~|0zpn#-V;_q($2A7mGttFVoR#zyOTSc zx9;wlmOI$gff|5#DcXl^ADO=)$4B0b$#i6y8CeYS+1Lf#9p<17GV*~Ctg$Iz{_h<} zoaZZ$yKQF}FU33E7W+>}iR9Ah>;nnRd+|`AamE zt1r9*Ok1*&m^6kt0cyaPCH@u0_C-(^C{rDzTES6@NkZYiA)Vf9F7)}eA-LkCUqO#E z9$Ke=cw3A+rq9!`{A#6uIb3^a%so88dTY7MKCUKDe=x|-!6M;zkCk5@(t3SPcUt|P z`FqZ$PpppoiKcv7KaGqGTi|*U?fcg#HC0cRSj@87bbVqcydm(IYi>y26uMAo{78W3 zC?&ekm4N;N;-?RlnI|TV!4+k<#Tk*`(<@t-S9opuF8^OkkL0Oa<)YG~!yt&G;35lu;>484M;7^)2{HE*q`H|~u>^PeOCiD{thAoQ+ z_9uy*Y8Vy9QvF?p3(j--?Vz^}i+c9kdu_AMF8ggfPq4Eum|Baz2Fgzd?w?xcuZZo% zbGyIAJFS~^B+1hJLJ8-r84{g;jbS2j1XaW$+3aTB$%B{T zQ)k0mU_iSpavI1;{5@s=V?khJG>M}gef4qUlKN3au^M;(lAiGR+Mr2;q7_+6%LITC zDk>Xd-EE^06Y8yP>boP6tR3REu{xi8K40+Jvsr%e{611vAMmFrAi{+(GzsDE3N)c9 z7+i}~#a&Gjac>p6!Vq0JtMVQ+9L(&#*FgVlvd1<3_p0bq<@FV&Rb-b?hsFh>0P>HF~&)c#6%^P(MPaVgHV zpDVv>Y+BhhPZ}JZMGz7;!4fY7fw$K~4Z|rz4PD%QpY8h1$(vG1dzXgF2b zG0{EbLoj$oR}ro6eg4FxJeTVG-Y8AYu!F7tXwKZaj4;m}Tqq5&L2>}6&#wYaYGydx=0Azm2Ip(oHrN@W1-sCRt{0Po z3=#B*LuFyalD@>7T9vpy8XiD(tAw4ErV5!8q{ZT;Fw-g#H}YOYSJCW+dTGz3oF|sG zr0uk%lQ@C!xyNIFe)>zrMB6^s^E?4*^+BnK%FeI214goW(w`J1lZxELJSRQu zV!h-gG4kJnUoY$Clc72O7%+a}TCDA--e(BC$99?!pUQThvz3}$^7mw^J{?lr+@1{g z1=-skIn`);zp7&HAZb0B49WPwiQ;qZkTCk+ZWJ@xp6*T-dM+~?_wqq1!`EUkW zNA$`OvcKZ6mOf~H@gL%@V2Of^EzfyjGWI3Nj6n&jH2C_^j`ak3r;=$ zkMm+Loi}McWt5Ks@-gutThb#g;vtl1y5d;EcDez&3B>DvqhCqr{{3pS?+11X%=77r z)FnW>IinTpocgu#Nde{zZHC4n6?VFHF_r~H^Md^BJ(hm5i8Dq>7tSghD7noFD><2`g|VB?N${ zcE+D?1wT+; z?j8ULX7ZQ&K8}3nC(^YJk2F$15TK)r=_wH#KT=;L_{;d_g6?{-rZX3CMr))!hL*IZ zt(dVJnFu9(2V%?n@a{it9QogZ66jtxiE-3_s318kalOk048VtPs<9H6E@FvlJ?=UrJonfvhbr! z#fz%eDOGoA&sO=rIlEui(1=%TpjEXN7ru$}>~z(0s1DWWvD=n;?J5zc&XXG#)=b;X zZiBWh7?JdDvXj5i_x`BBp*gU-sOLO;GG+btr=Ypq{EKowu*@dB;@g_WP znrE7!8o1X0CV)9pt~*cw_pH{DEZGbFHISVVWj8y!S+*^1C4|G8;Ni-tL^2ent_i*( z%& zu0_!2lTHO1;>4=l8*jkp>#(eHY)ZIw(r1C20YDj_$fq`Y31F~KLUFJ)4JLEQNl1;1 ze?%k1X?W418qdDwfwy?{%_RejAsLTyN|LAM%`^c@MjnqEzjs$gXP4f#r|YIDi3RTD zq^*^?9wk=5{||3(9uL*~_m7_$!^~L6SVPP(*6i7mWyU&VCzb3w*`k$1V+_WYAzP^I zQ7R-+Drs;q`y{_l=+OC%~=UnGj zj*;|4iAL^DC$)KxTN*lMcdUAy;K6CX@nRxYFEmBIsJjUFebFjpASA{0Qu!T)yPFoM zyY8#pB04@Vg^uoSU+fStB`V84x*Kw!IkZW@@->mcQKbRK9nRB@7ZEls6AYn1taS8HehSFoZMrJ=5JMt)qOHIl0 z9w~_3Vk7ZoH>EEtA5S^F_jBvVMC9b1L77q?vd!J2S02LCB7SwcL?DpJongFPiedtu z%U`MLFveupMK12NtPr00m!hUesCN=G@Cq61+`htFLthP?Q9kC;(y&3bLcz-YTAa2# zDC$a$(jD>n8y4jb6JnVpjA7E?iB$6?y6RwSp`(Rk69)_>;9ff7Kce}@x;V+SVf|&Y zx+u?Qr;I^GDE%A4nfNI)6UEz$ugmd94w@>sHj+A$lOY7nMnmxK?!EdKxikkLTIdK? zTa$r9oy@l=ZD5}pVXHq@Xo`5(S$#G$Dw%zT3nSB1b6e@fGFbi__mHp)doiG!#&;66 zA9WbV;Iy%IsPJa<@3LaA3nNfl8w;GbM18fp){RXRxILJOLV<_>dizaU!BU{}E0TTY z@D=-3x>8rygU%&mwTp`CB8T0OWrL?J4ymB`=;*tOqdDJC#$4M+DW7xf&BkE(?sFBL z%J-9JUK-HMR)n?kxJ%4IqHJmh)zotpZlg7y!YB?gcNl*|HaGrqC@e4TkcvOxU1d>^ zlYlkd=Mq!kw69c`u_ukmKrTaP9@9SDlt9AZOR6dXW~Zjw&&`d~%QyHX!!A{PM&>g! zl-l1b1op~cEIHagROjxNsN|cuP|uBFznN<4WH5@a-Z*Nrn_EAdCpbU)R)qfEBmsWp zg)J1{tbEPC%G0#g--jNIA=i$s3h;-40uOm`CWPo(shD-n;pu3x_$o zI80I~M+t0qgpmy2&b9kv&t%E9CJ)+x&c6k)3WXK_+ zV4QR8g5!p?gqioe9%mP~F>dH>!tUi(x^7BJOy05cY6WVE$!6rck!ozbmchu(kkNMy z?J$+hvlR|KHMqNtiHzEY!@MiYbsr^WXW(#>$v~5KHwoVpEb7CPpj>-4$wIu>ElgDV zU5UR;owVyEw-o_u0)gakF6k5#)|`5P?##a1S)9G0?01zf=bQ~U)bI3)gZJX&9g$y) zvuf?=Dvn*aB~9jrp-*;M+t`XFIoKcKElRN`e-B(lnD6F9?p0xzVTM+I;=r^CnZ{Uq zS19;!A!BbdJqi9vLU#@odIlWm;N&FGwyL2rEtw*YpmVtmhd2d%h(a|TL>c^eK86R(V_t#Wj05T9xxqF8#C#HhP35fs(6`DnI2MKsz`Z023tME_t9$+Q8v#dy## zbyKhjGP5U7pqvz_x2i=j2ZaLah+9*o6S5|!u47U9ov*a$T+&G`f=8J`?bFo=ewlzI zS>I9~vT8hBeCX~Qj#AyDWJu%Hk6RAf+SKZc&CY4Nus$*#M_m#K9It6I!OHbDVrYxM zq1p8qJ(S*q9%ie8Ayc}QwD<>%@CEQgadERu_!EBKTIx#7 zM=q7ib7m3pCzKT|+;A9<#FTp+Qp_(Qp8 zmGPJ+g1|eiHKX0iPX(61x}KE4$~yG+s-_aT&y(MBYktZ76IlM7r*W z4#|o2->I7rx#U`M>$B!3?xy6=oNoZ}P*w=Y2YxXdooSNPZ_2ux+AZZcg&( zta8`6>tLqfbm#1duG?ZK?zGIueL^Fa5vgBxj{XhNTckC7`@I#T_F9GiR*Uw#OcQG1 zAJh}_>!5;Xx59*^S~Vs*gG2e!>c?gS8i?=|rw5EFg07}dNd=ix@74#?Q zALow`Wu=K8)1H?0d<(uY2sy) zZ^JqMW;dPp-x2G4w?F{pUCcQR8DO zACf96Pc=b<&Ze+pV$LqnuHhau;YV$^M(|!Xe_b0VeOl+<#3S%h{XlI5arADBHd0;P zkYQlytK)oB7>rd% zypZu<7`ij&^Y%+9)>?v-trE|ATMCL?^4e7#tvJvIebK#7lgH2kBw|p`N!WWM3 z-&aM|^gY7sV!}N(68Q579#t@=CD9!NQ( zJoH_Ea>q%RuYE;pgR5(Q>e6(K(Y$zeO(%!oUwq-InJ9Of%W&>@_KZnxAG)U|QfEQ9 zBK9>0!$$7jh?Oy{&*te*0B!V}!a zgV-l+)=lJA5Zpday?sfwg0;~>O>Br-Sh|?SN>&(-gAw5{vwMIHNAV*OcMD>|S3j|}?)2mea9_k}Q1KrphhGuTMPfn>GL@;p z>PNleLT|`mYRfUNoYIfi;1JxSLvbH}!C|I8`l5yzmOf8QJMUw4A#eY?KqS$I~ z?iAlG{5(qRuIVK9gt(6H)u8$d9;5qEec-Jv&)wE|4(mqKWoCO{rl5MtvW=pwF2L;) zoNsa4@T)skW_H|*lGZOY{6NS8j(4_fa>AGR*r^a>qkX67KKw$>pXX zF!e;Ybp_1OBl_8iU6KokKnIz=9y5Ic@>`f2$=ta~J2e5**Ri-qPtR{;axWUT3r6F5 zwni6fZ#RMU!%s)}42hA2XbCYp2ERG@#_FByC1&kUuzyB#D8TXezw@x~c`fU#e}&kl zydU0Z!M87UEa1&=&QmR2^PH+aAH@3=GwCw5vC)%r@&)&{QJOR%ThGB4oJHfs7m>K6 zS|}ashyBp~=$*NR^4m`7@i6w+TKD<{J`C47hnzdpUFdM14JMLaE2=V1?TR(2`5oq) zUHU{KZGWdZH3V9(Q5thB)-D~rZfdEmdou*p-T3X1lWxN!W}@*t4JNFheN{cZ&TCK% zTf-Y|f{EhIYW$iX=YV*){0OGUR>Sp@d3#?(sB&K_3F`Jv-JHj~zHDpD!HF$?wr|+> zTOZBkpr-qb!8bkcie-Kba)58x%I5}S&o`~|4rOK=BC1$Pqjc}V&7KQH9<&%POY<1Ha`DXpvf{<|X*lv;z> zAkH1u)N^=0jG>$RAfC)~3~}_DukPz)3Y=NcqJ$ND;@Mwq!kC-_Em`BB zxSt_1d1ZkLMfAEbr3r%1+}dQLeK>na3D=HT2GgZs6;!`Zg2@!ImnC;-#F9CPT`WWJF^#-o+?*-XZKTU<&jTeV&7XBQ zG5ZiGP9+%=N+>Vnf=TL5K6}3l`W?N>+g2?Q>1#&}H+*KG7tgMTOT2)oxKQ;V=<~TJ z7YGf0OLZZ{&^h?a$(>oWFULb)5e!PV48wL~3|H^M<6!XvM?Kes=6-8+R}xc&Sfh4> zo0+Fk8Yfq?`gx)g$3wf!o!@Ke9PewYZILJp)#7NiM4@Fx6O9VBQW{6UGI=vU3Mv+2 zL{NKlgLtn59BRUuN=P*esr^;rZTwrdDAbAI*q?qe@Z<@mpz^1lrZUT_JNsKsB+=k+ z1yMAMK;MV=Exkpvoob*OZx3FNWp80|C-_h|{OQ%^>n1cs{$%sQ(9#9m_uX_G`=^(D zh;wgGGp!CNmwXmVM1;<|xm`PPz}3phC)i-${D(%B(Tl5JGVNoWi>T3S%%Nh_=gld? zLEp2|tqonqBw{~6*}f?nNuYh4{X#(swBm%jt~J|H=zekP zEt@mSz3+sp;(}VRl4e$nw~ut9gl#!-BS^4xu@u6V9)GH^xO61$`D=6kno|hGO$iAp zM#!Beixa_-33;a8U3W{h3+<~l4#=k}gq|Jlu@X*)pF z5h_5m8&?SM+VEt_KL4G)$cB;`XVbIC<hV;U;_&4qNR`%2hk z@y`D5`-6Sy=a$cQy_fo)tNWL!OSI}d^9=u?O>~T)VPsIJ8`XsrzngYetQTB`|K2^V zAB;9}VZe;Cat|hia7!gSuO%uRe56@0?Cweo8x0rA;Wue%sHIQf&D6E};U-f9?-}N| z=fsyCmF8`1Qe0o8e;DodLm6%nZ=+QZ-TF**haq?)jTAPuDraPI>ph2iHN1f#?e*v) zIf?hB@@7iIRSa%Z4=N^}mL`-ZqXNzkdWVI$>udk?Yg2&e)V*aK=D3Nuau3RT<8;QJ*@UwSjV<-FJu;YR^DS}Bq2G}w44K}vUoCvgLp3cCpLZ|wN8e;RsH=hnU zB$(fD@ldFr3MD_SHmP}PXk~kgawY#&*@8ONW6zkJBP9^Lcd4(xrN5qYmoxsu=NjdU zCSOITrdIp?y6tv`o=bA7>Yd}(P2Lgdn7md|_dRZ9eq}}dNyXOUJE*D71h%1?RzB!B zcqWEN>`_qEzADj@73;5-NCcDP9ZnjsT9T@;$6g%76p*XK}8A z%3d`q@i7C|-KD+qvfC<^3I}ew(Nydg_S6+NKG7IcP2QCs8t=Ej;k)450X>ddfdYJAe5=gPI!=HJdF4TrTog29E?4o`mnx=Dcz5Nf;YD&#d0y&2Gq2 z+L?>Z#V!*jaA^32fJnR3N84e!<}@vixyf7tGR^8ttw9E#mvqKmjQF)iD-Z9UtD{~A zsn$d+nWH251a;+QrN;5dIO-%q`;%g;v)*BwB3HaukKc29aBtQ}8LK5W<5P*8g>&^%2O3DzL~@u9s!wu zH5`EyL{4rU zKLS#pKM#x0<*JKLY&!U1%p2t$)8|n6gd<$l^fYDBnMFv`}fj6YubIZ`1@CLQy$S zZYIvL3A$Rl=Ct!h`rdr2%-g__Ayylu`jTgNWpO&+Nfk+uW^~CGxbI$>1=qyw7U0*C z$|EnZ9Z6O%1wRuW{^TBynT831yLvRr-s}&vK`NujPsT&`!kaWcC{?}m)^^}e_Af!~ zOE>R3k!Iw@pQC1L&n0k|^u5~K9NB#?!JCSui*o7ahosQhTs%o$zCs&4i7&{4jRNdM zWul92u1B(;euAWr;~1S|b+;dOwrs{(8!;9wb6qsgZA_x=GsiGSqBHO9BkFM}GitgB z+blwdnI9$bTte}BU^#BGMPkFGi>nAp2qt-t>s`LRm)3H&;Z-Gx0yj%%L+jDLix^1d)k;;?rHw>xQ~Yx`+cLe>g2=Ck za=)4F%b!J(C6QDUeK(Yk)!DbdjAnQ6hZ%GmnKtUvQ)JmxkJ`+}9j(Y|OFU12_d?QY z(HPpzGgk)ByfA1UJT?&c$JaIC2$Dn98&o^!j9O|IMD z8i;?Q>{iN)Asg;clUok>M805hFgv!tcwUPmL7E9i3L6#^=0lj?XA6QQWLBj=io2`2 z_$j0WAO-KRn*>~@TQc!)^NMxghJv>yM+`04yzve~okU%|6LhxlV<(=7a|!Kc!#92T z=BbKzFO3hHjXxaum3}92di(MermMX}P4vDGPkf-Pvux;b?;WXQ&}{TIw0fGG)4{3a z)+Lv`D79L|@RFSPC2#RdMWsH%_$RP2PKt~nMg84MrTmEv49Yo$eK2{aSdoELSW*Zh zjraVT8#?T%OSKl;yWC!OmXI_LHKK!?1lWd*_eu54aopzk&!VxnjQVOk zOr39To0@gge3R?vzUmcK1i_+Fzbt;a3Mbll=|fR3*>}HVf5f+PT~w0ecES#A*PH$` zE9;Lw#I<)_E?4Y1c^l8$>?QBAuR9`RD&Q)eNrQ)wE~EA^S{ara+K6FWa%=aHD)|xv zHE8RjT^=MVMB7QVMAfbd$6Q8e(}zp0P;OnGP1R9>=n87|NOPwg>^yknZ=yLW{8Wl< z%wOl``Z@em;C9vT6%oU8@~22&6iZ_mW}LiaHc8m*K-1o``0`uf3`VJ;ryTXT#$JhX zBx>@c4b6G0R0=g`-`|^dCK*l?G~i?ZW-OLAEB!+||Je5B(S4oT@U{xSIPbnsa&X9` z*u+r@{lg$RDzQTlIWsRthabIvsq9nW)!HJX>fp1_kO#Eh1Qyp8+1)W8eWrUrE6!&4 z=iX_X^^1l*;Ge9TW$g}Kk0!Zbwv6I55UB4i<%W?h9@KdrI&#W8ErY1pnEG+gm;ze~ z?3^Gh(y_x|sej4_rBkVY*(Z)2sz1Xip3tmWc4#;@EfDigLZhR@Y(!9Cr*MTd;{STiIkbgre=G|T^HQD9@$r~kQ78Qk+#^ZCH zNCk`F@Yxg*hd?>*PI?G$u$|0L^oNez?ZTZ~m8&pkwqHaulJ_M3*7I`M=%%P8vV96p zxC1Rk+P!;!#=%8}oL~6_<;(GXM>fOo^9LlGq#*I?M&qnQX2(FsCMnGZ6Wgi%%$wnG z*(I%p@?5p8{_PnbZxh=6MK$&!gVF~?g&sX-`;AVpHfr=7@_h2D+ItnwmnW!^Xt-&V zs)@^3LC&+o3FqHAyYFwZI1(9hrnQV&s$iigv*^NWal9sbUP4pFLHoNR^ejb&joYeB z#J#E#-_9ex?dgL??ixtg;Qj2)FoylG*`IuK$VftLkvV?HX`OoDZ%B*GV8aDfMjSRN z8=b3P+Wmf#c8urJvAX&v#%_uVfv+s>XbBG6O)xeS^Hbb5n&iSSJRHN)BQ{!3?T+$J z^LF26I};xmW%2myK92{Oq-(*CkH2HK8`@Ni?SWY(tFyz{z-3~N=w4rOzb5n(20fOW z_YRj86~Yz`L*8YSps^?1L^xERzEV?l#x1z~Z)oW1ZxOJTH#Zv$fFn*BH)9gf=UuZMG>fg75F4W5Cx)w-q8ezM4*vz za3VxSgF^(^nP5!}GJ;6~#5fEQ$AkcXR`36{!C55)1mVCJ75oPq8V8|p5D}CJIe_{9 zW5Zzp0SdxWaQ_cNhyaHOG&F1f-zZTmv=|Z?8Up-T%Vc5uPa6RP5RxDuaL{`WkSFn9 zl+=HH|NDywVbNeLfcTH_$p|b^0jnW`r3;`e@Ck+w|X+#K!8uUT~#9#+!DgPx&1O`n2 zx~7rAp#G~8-~gO{zC#3>#Zl2XEahLs zf0_WP0{#drkS!XQLxyO;H7Ep@#)0C%Cz{0;OGW_g02&OLg%EIHQgAdFOMU-n7Bm55 z|In}s|K5SVun>U?bp7v|f1g4C)B1OrRRCm6;KgVh6(IhzfpJm)Df|f$tRf5o0D!SE zi8wfjCqM}QFaZ+yLx}Z91Al}e zSo%c~AQ%FUCSWKyFig-doWvRnjR~h#=!wMGJ!_H0fPtXW62*4!O6f! z2%vYCd_Y4qOF>kY&Hve82w?w!Kfn(R7)t~P{}v+*(Eq7K{h=fysH~14Dhh#t;h6vQ z&N_nw|3SkXFbXsQPQ+nQe`-j7x&v|mf<=Q?Kqde@1f&E2&>$QZ_!MYG0bKwM0>A_~ z08b_W^I)P0AWj0qgJ3KGR3I)GP#*z{CQyOCNC;qH7$O1?(^z_kSlWaTfD%pyR?6z) z4@q(ZLW*3TOynDSu>+27&@*9E5@ZJqw6062t)>;eF(z-PxKdoErjLIsL+9Cf%O9S zr^0X;;A;>L^WW&rgda6;nHd5;xW8U|UIGS5;OIir+>|*e{=hsSCWQDmBrR+4@L#td z5h;X=@=KKnVCq__!3bDDGc3FbFh`LPSl}Ih_>ZU_y$)04L<7@xW*Pu9#<`K94)aEc z&}{8r{~FQg5Z>Hn&f=VHE=(9!RC_~b85@uoTG~yKWo!dsIW81Aw*bu3@cbh_aMmzw z1t(C!4=IgU3J41X01Pco37)son)^u7Q%Nc%WhD!&*}e0R|Ar!dvCsliruj>eT7hM1E)9hP{skvPAc$G<0E7Uoj#pS-QFk*a z(})P{Sy_wL3I11Bfe8pW3Wud2&?JPI{udWONzoe6Zg!_V9|vU^A_sQ!m*V485>w={ z890Q{1R75%WLsN?2b6@aF#jBZ;Y&UQCJDlzCo;HR99hW`TML&a1Mh~aE4yeY`3wdE zP+mmjzgBLZVA%iyLP=MmA5R3ZO&WA9PvAfVfE=%!g+YfZi7cpQ0!w=t_R0u=3PX~U zIt|w3ZTk5z&F+jc1R`x*Y7x<2)Fjh{1y#ML>XBJkvjc^IAtN}ANtw9(G4`uR)Mk_* zTn_R4TB|~id8X_ix%R$+2ZVXE>HP$vz~XIgJ0;)-V?VL5n%mOzcJxTTpo|re zqNb=@2Q&&&mO^ItOp*yHr%tz55n;Hag)FRsf8EfZ;M|1*Wum1dYH&Cp`3ToX-9kD0 zXdD#nW|-rIF;{`I0VSd5*S$fR$pRo^GP_Lll7d#@)+2%jVMA&nEy%HGWud$CTs5Z6_wmGK{IRV$o ziLzG^)o}?BOhj|Is<64uLc8MzE}DCbhJ6`kLDfXd`>J?c2G&3&Jvd95hN)msz*c4R z)0^9AqU|$Tl^C=z3m%2=6t`-wb#opF5iR@fec)T~DKT4!j8h+{6cbS)hp^H#QhC2N zfJex-m3?aHG)!c*612Q=?qqT2$zzZ38T-A*NyiOVwOol~9+cj5l)x!l4dCTaFar;p)BSUR31TQG6Dfq0eZ|c~xZwb~ zY=I37T4&R+lu5$c{*kJwXQNHA0iFbmm91Eg01rSO`N2SkRcHI#ixK5a0w64m=1|Cj zdXYP2xNYW#79xj?$T?q0Bc6t-ZNJ82`Pug-Qy^BxfZ}+q^9QPp?z>;@r7%GN6>BTa zbJ044JO}d`7@OlTi!TLHECb zdfiGu09(zmT!K*5Oxb*ELW+gHF#_Ztf0B;@wJj`=m5qQa$K%;oOe}y(?0!zIMk(e~ z!kUL`C?6c4BM^jvaA}ZHJsAKHeEAGFD_O;|RWyzSa0e;1bB_`D}Xi5QOVfM={M*Ag2}>1@_QF@ zj`%k|)k-IHAmFSR#^jfMNa&#gzv`Qs61%bRn`Nv78lP+-iI<|1?-pKYS5L?x;vg~t zvd#Y+`rThnA#{l0&1#1RK7tbnDrMHX_Kl9Xu*Vr&bY0tU>;Bt)Uw_O!4lm<1>*9WV59tXQEH#8O#VaGTe~h)U~wL<|K1Cn6w_ zK5}1N{#x*yLKyMSmRWO|0D>hW;3vYUqPuTcQ^&W1!5jdAi2(*YSLd&Lc4T0#=FKiS z+*RRpFe(UQFDFUgTb}+^7kUnj!w^A|iU8g*p!$3M8}KY)Yw@;0136%sG?V*|IMCE^t1-utdo*!Hqg2NDSHVC{K4q|OEehgj% zr%og}IC}d;B{X!Dwf}h~95`=`+1&Mo=M8SIoP4NNH1cT}ar{iTa6@CNv3voVFMA%Z z{lm!1PekEVLzy(MY}t{=zg^S3`W3C5a;scI(Mu1(U9chX3O%FU8Vrw@!>v708^xBp zF^MK!W+JC^s|3W-z0!=5Qbr%kUL)Xnxkjv4Fy(3Zz47Rd6H!lOJC?e9-|`gHGCW<) z@ak!f^O$WuFZfvZ9YnI^1<`XGVfN;dPe(`C-&G&Ckt|HEFP9LcaMT9y*z2P^=2Ov$ zj(YX+;v!Ko7@EnE1?7=PNmsmdx|>Webh+9S=(n9*=9nu70u&XdERu3fvcl-{?VXsi z+PQ|P(G3xq&Q%eUx5D1j{RbRF4AT`%Kc0v3Q|ZT7CWY@mw80xhleFaG6CI)@U43iO zPJ$*K;g03^@nFHF27O(wlrnwC{o~1Lx@-Ddvn@hmr(yx3VT>axInzh;m?ykP;aj`kmolGKnc*!@J?^;5 z2Yh9-$F}eBXvqA=4h&e1j^}6%l1F#-MTK+6nkxMiUar+pBGt(hoXv<>b0}mio~!m0 zNXU6VMaoRU-5pfGbLO2a!=Kw-^RG(v7@P{S4qEIv|nUrdb&zj9$Os8!52c+IZ4NyxF_ zQ|L2EkIzD9%%4N~t^vjqlN^i@48fl$Xy23O5+{^vStxTQA zEvlM-Lmk~uf3n|rxh;Nd*S*F2*{8;5H8z|dsx&#=nbV>JLDDgmY(GJs4QmlnUqO4uE{Ca|LeA% zME$m83U%VB3LleE06#HtS}tW0bfSu^t8+)$oB9UdP{~Zwuw%Uht{_>;blGS@AhGMS zT=!7qi28%0i^n>udnNMFsg6sgyLBSFOm*a*6~r<(2D)Q-PL9gF)RU6S?G4xiN-7Q)aH7nc+_iBW7% z5OZ7~lRWHX#V>JRSkZFEMD$AXPLUT+1uj7|BSO%|rpYQC6*&{9nWdE&S*2*T<%HXM z(NObVX30cMyh!RIdhULIHcuEGV}cG2rg?I9^-D|dJ1wBFHA`s5g=qZ5`xW$B7;*(4 zP1TFkIV9(Zf68kh#ITX)nD9z!XbKCCslp|7SpJn4Wgz#Y=aWi4&pTh9!2^#t7vnX3 z15=Pgc=n|B-ghKvo@k4nHZd_2G0#lTlx9P;tPRqFz zR|FIqYn<%ZtHNi!;P4G)5};YYR~E$}$_w?;rj{^gs}!D& zSZoOZzzlJ0O&I0FP%6!LA=IL1WmmPs+x#Coz3=5bTWlIIba|@)DWkKJ4jUcEwM@O> zijKGbiVDHfPKHD`l~3p&J$ux$(K|i$rS%H>hqSYTo`M(bMaNMS8cpH?{B8G0dBM$- zCV>K6d{sNxeqqtx*(I_5_n9N19f`v)c^j;q6J!J)YE>y*Nz-jiU91>S+e^!R%+Z*q zhf6AZ)uX|)cw-?r*j@0M@2ddKEOc&_qUFj!>66))J8oq+v2^hwGsfHZ`GySH;E0`2 zGrZBM0OHG;C*p}_^47z7VwXQT6@1Gdp494_^&i>4X&rgyq{iK+=1m%zVQY_-g2}{J z$d5GJ_(MG_#vp}~{*yw1?C8(-?-WV{hX*N?qql?4KiLzUl|6wU_4kX}9z8NSG*FgR z{b0}U3BIk}4pxd5BaQb}xGLq(zc3ob8Zz#4?unK9E&O;T7)0JKtgWP8E=%v>Z>%91|r4Rg~4?_e}}2(+UJDx&U+W~-#gJUq$(v|y3iIG(mYF)hsZw5%+Y@B9I?{%_{uie~*z-zb{+WehziAh+JVYX|dEE_5fx zrOwT9d#!k`th4|=8>$ZUXq%YWjm4P&8DFumavK7vPA*4Q{5i95<%*(|xtb2c!0BD+ z_;Zopd7G^z?p4cty@<@+V4x-V~Lnqe^S3NEm zL`eSn$k8)AYL~v)({Xu@uY7KA{3z{jXvFJ<2ammQZy`xq3i68kY+vxjcQg%tz~d)$#xvoYVlnfDPh29V_)> zzd)Jx#5UTgN?ba%IMy)}WnVXAnU})AU(~9;;*dOiIZ2mYK>V{+R<+~pIlYH-^XYfY z#c!X8n$t-BN)W=m1J9%VSW5KonSMq$+%_=b5}c{%?-CJ7p>L_Bb)K=FxEI)!X!?bj zxRgFEDcNmIKk3i(deQPJ^(Di8a5vj7a|IVV<9CJEAbxo)Q~i;s2^vFJJU+rvKPKG9 z->8*IfAvw2*uB;9C}lb`GPbMxvqzA<-wdafCg~^rVELzeX=d<~q;em=_&Q%p@T1=}?}m2B2Fl9r4dq z3O~Y}vZN}JGaUWKRz^b%->waOkjU&eE0t*Z;nHy338|4Xg0`zn)J+3qa%$e~C4AS?O7LyEmScIW zXy`gJ^P)g!=KIvC&d75U$5Wo}-(Pl$&ZsY$6bWnqen%ycvQTute!S?)FR?|tXFMrM zDYGjZ_!H-*g)uuQ*<6hczQoQ~?E(%p5l>v?oQeY!FJ>Dib~<3IzvW>&#hVM!^3@^41GnwEVrzm*}QN{YI& z!3sk=oXLxEQd_FMX<3Sw{QW$KgK7LT#v|(m3=<5wl_svHZ?r*BIZhiCN%vix#-m6e zk|bG@<~AY>3^QB^rO$Pw?(~mIU0i5U)}Gk+du|S|mREklv$rjnV8e3?k<`VLnxWq1 zw-!tnD5QU*OPMR0-vY5UoKJDML~3wT&2L&Co&3%K-+U< z@xi`q)$<9%jF2+^5zDBl34N5rDdCStg@0Zdzv9)4rf)iaurJCD15s7YXKr#&vYg}E zP*0ssf6p!PSOY_b%lSQ+mNtW<-wliuPOcfqJvJMj@x!(d$}q2@DPSQ#!RqPZL?5-X zIuT)A4fb2=beXlV>WxOZzR=N?9IPq%sIZ7^Mf7f-vL?g1<=L>-^L`yDcSm$ep}j~O zK3$;_xBjZ!H(h|MO?SdOYxADxz3UAP9qFw%9GUGaK1e*=fqV# zSCSbva#s2ZA5))i7T!Q<7r2<9cdu9lEYAj)VEXVogXM=2o7IjhHWjnkOasHY$Y|GH z#@2iz(Iwc+!s3b1G!INDMlLORb^V)Wu1%G~@4%;T^nK5rysZ}Pj%Oe4FEI5xV~}|| zI5`Pbk;os&lgaD`gW%&kFCZ!Me8og$cs*%${gYVO7j*LL-6Fqps}zPWnslzlZj_GG zp2_0%?3L0OiWVA5^^cO1CT^XKPiV}V@L666zC;U96S?%PO?gdFI?nI?YO|WajNX&C zNuA57KFjX|1Qz|}g$JItxRT1n6!Q43e>AIRu2x8yoTQ8x3(OD}O`9TR$=oq6vET*6 zZNd!3NoS{=^g0l6y?~XWvTAfP zqU@w@6vDtM>$ORsYGvj_VVAA9C>t@{Rntm=#93x?>iWaEs7i6nm7s6N&ICM9c^O$x zefwK5NDxdgV6vjGC-pF`NA$oX{pWHK_}G@u_HUk;`4!Zr?5?AQdp!DZ&S#m+#p`{U z<61)C9TswZD6a=O zt6#tQ3S-9k9=SRgcG6sz@@`3eKv}}9m@F^fcKf`^f!`gi*0uiEjx-l?;HGZ7saADM7!9j=9i^By-FP+0 z^K&gdphh}*nrXSyClpe3Xwo%@A^ZK1+3pJj)U@Xdj7JvRc0R;LJ>R-ol)JI(JLTwC zegb+~=KD+z=cYdTS9h^#=fUMYA5UevUDFq9Yq|qBlDhPln*Ewd*7SO!%E6gcv(0nz z>k#_mjqOxP-sZSDr_POSy*&EbZ(PQ)_le6Q&-m;YHT;!U)TWJnM$hks`CWa%DQ9Vx zenWfQVXZ|n%Y3IQ|H}(T>J;7l_lxZmo;QC(ll})hQWgRRvl=*_w^gfMKT{$Vo$Qsp zri%QCZaF+5GL=?rD8;ysTRyMef2}Xmb(0)KI(t#N>i*;BTe z)*{(X51j5h?lwSOYrRYkpdrNPq~5d~+)~5jCw7WVpS<=Sa*Knl=V}+1Up*mKXZm0* zQ0zgBRABh-xwxnGZ1>MQbPndXxXu?hbhlCu#k&2nN>y3;j+yS z+JC*Ot2!KiIiL-Nt&4-(AL+fiZd<-tr1f#rxP?!E_WzOf7C><|P1xw-uEAXb!CiuT zAhfutVnRId_NTqtsO${_h@lrMG0=E^_=P2v@wE{XQ_s# zE4D4mriz+V0eifnwYzq&wBr0Tv_ArYR>h`2a$3=O-f9}z{QF+yk8|R4?PD6A-slZC zPOgZ_Ehnxhru76umnK-Q=dT=NJRWkRLd49;p^7V1-?=Boay^O-lMi!``I<$0%^4zk z!)v>Q@-J~8X;wydm-gO@t8=8NEebe{rnZ9|dNz;K+Cj$sl?(8*R5nc)bZOM9uX4&C z{1wr1$FW|4*I=c~V*HdU57!IU>XybG{P!PgZ%Bx;;C3Bo!Y}zB;$3$Wnr64+QLJXX zDHq03tWa5(-=chjj${#Ses6}b?Hs<5xTmj3r8amcM&w>cGf4c1TFQk3h86Cp@sbjB6KRF>m6N0`-~Tj|Hx6U>x7%2W$PE{*V+O=rZ-6V*Q# z-qO(9g#T`!aN0sDSEm)9>tlk464{{T!}o`+LXHy|*a z&S@_=w(>!V#O3ekx^G$m5j$r5s;O^OP{{V@fz?Ho_H1eE}Ny}#c0HbnksXKa?O>=_nK-m^# zi4Y@9>g}SU^}kKlCgoq>INzQ2z#xhFW#*_!?^cHMh z^TPr;Aha9agG=2p{{gUa)w((#OF=TgC(1$Km;+Ys8{PcorOz!H-R6P=>6?=J)Zz_L zk9tQWlFG&A+jik2!vt~Xp%Ex*hFCcLnM^$VcfO2Pe<;YLnU{Cg#jSXaINbffU=nn)`TGV1I>-3ji0kdvjqI2)cF&y zSwA(mhC^?K%x3O51&IjKzCKq65OV;hg}xUQ^vv|-y-6_Ky1%j>MM3#+N*Ea#ay5TJ zJ|WY~4h;<%4DsI@hG&53HE)0#W#OsHN(?{Wy~6{86QwQPa1Ub#V0W-&cIaci_oL%io5GNQbb-oc%Zh2cX^M+QJ z2h`phzCGp>hczd6gQ@j@oD(}bAePDTXBL!c_gi2dEpEg0pa%Ohg;arnJA3Mcb=|sz zTp`aJGvBEF`a>)ywNY}{aBVSc!2SU+reN{pSx;o%IYWuky@tzhqx1!dc_whR4pC*< z){Of{XCvqmZd$AEavF9XavB%?`cviYPsD7i?vlA-)j(13CJ<5g-UvMk=HN+FF33No zu3^ct0slf!6(3SA2rT{qSe44W!61@j)*JDE038TF8v|VnX)1owPei%l)Cmh#Zu}$| z{bXcl*esq1{O$q$RG4a_a`A|TH$hZfI>%+;)M7|2S7P*ELmK`#wHWdG{Fm%C4GjNZ zj_WjTTo!11>gF&0vR4AmV+tNH`{9{go{fy&k$3gTy*+16ti&Hg0l7~<<6W_BnzGN? ze3CwbtFurH-PFosH{^0Lzx5Ji`| z-jA1w7h^!nqgqz z5(+rQ_irp+?m$>+85NtVwTiP<5iqNff$yh$;Y2~gd|A(*B+1P}NFm{Sl7@}EXk)B5 zn&H|mzgfA_2!>6;CSS}HcZUd$m1mvT|T~H05v!z z-=bd;C_b(IgydM=zyJ?_rMw~4UavJi85e6CxLr^m=ureC@plI{nO%(NK6?8inlGepLu zpMAi@?EevdPgtubBKThGF~OaaliSLo*|(jmuYXCXE9(;*6E{!zrjY`XN0D(9P$*JN zA+}#tB3%J)Br_Q8c|UUz&Iq@V)(@GLoUFm3vWO=@FQLRbYQq#(1C}f(`yYTjjg$J( zen{VPd6>day9&5gJ_LIJ;`D^FjA;rHElf?(V(1;V1lj5S>~KbBzM*$T5xv&yBw`@D z`#$YRv7iznAw>#v&=FdDM`X}S6Hub&$vc0pcced^3?p9FKEQ0&2IS|l$#EjHv64Fj zN0p_93_#B;e-mB-%wwy57u>cj37kyq;l%pwwoTaRDR}DL^;|i2nHjouehuqRSX(NF zrjttRRv7j8-4d^kg-SaDeLhN3{!8b6%9TjcX>;*6@1(I>A;$J!lKtC)dWSk8Qfm6$ zW&E#5Pk0^?!)eiezuMSHs4%*?VlHoHW7m>DBDb0&yN5_7Z#CGJoSz{tm5tRWpu*a>jk7ykuN+1^%{TU?Kz0UF-eGX^p^m-s7eWI{|aj zAVlC1w~ELLA*UH`M$Gje>`)W{pe;8r{0BDuA%+owg}lFVtnAqc>JW`}0&UXyy4u;* znKhJ6`gOT9hq2!wXCf7{wVJuX8(;`Gme7#l7p3_~esD!4)}?wLJ_+@93wY%qs=+wR zAZc=|KA-M0U{rVH$BYyItS8jWsLU6PjO2nUi4=p&8U>G!BHN_uDmCejJm`1ev1jw;BOA&l^JOaM7w)fV!`UZK-KBc%Y z-98^w%WnWw+8FZT+|94BKv<^H>hPR4p6K?Tecaz73cRLx`})=Tqk4Z{@#@K~_u|zp z4t}_ZU~2ogFS5z^?pkdFrdpon_6&hmAJZ=*jfX&ga9;5Ci92x~@K9dNHgi6NL8;R> z18ku}{{YgU>D>ELC@Cz3@$>;Y_7CudZa3iF_*1$>4`?Cx))uP20KM*#tjI)HlU^KdumCwwvTsH=Nh8vneyjD=>rDt0*mz0xam4G5QWo2C5AKL9w=!LJV4fNonV?~|M{e_9QApph7z>jVKIc%j8@*f?zwerpK~+T=aC zVBR{NxTU&oO{)Sz500#T5wjeiUH!38(>f^e19`ic+eKjZ-s`i(`$h|B$JAPH8&Tx# zh3Lg<)oJO`qQDWfbK)OBX`e-te-8SC@L6ofTSp|&?l3UYSMNvf9GH{-_Jnh1qZUHZ z`0Nve(~w6Mpz^i@T8-*41@6P&zGd$uNVI-^k>G<;XNch}Y#=(``DE8FY_K^)Mf$@1 zS@gc-e#~rs0IorEGX(6Roqae9H%w6ASNXeuzqZFu$tTYuQ-7ZFyw%L*GuAgJ3AUk~ z1L0^QOvLP6y^vPRXP+1+kjqUQv~$>9#p@rys~%FSHik6#+#cUMb3&C0auq++l5V&;z1H8Y^?KT53<$O3>6!3&KeElhJ?@LvNsfU|GloT|H2ZDqW%7Oh2id8Sst9z@Dx53^rtsHg#u z6qP~*1m)|xrZ-iBaqVK>lku{pxR+hH1;zp;XM{iIur9(u)a;|-_cb8x9&d5V0W6mJ zY^U;W4kr8DzoTu0;!z2oX}@ME_7PQXrz)ycNPFygJm{+|!-16D2@I^d25b4oJM&*Z zTcRG-TTUFA3{s3g#Qas)69X01f_GQM9TGZ>Qa4O5M|{;#H#kIw#8cLqnwq$C@O;=Y zEzS)|hS#`*qGQ|bs|aepnqFZL88Nk$S#sPE<>s45E*iZKZb^;KxO<|zh`Yy{$#8$- z&?igCf(ek}FoY4T^#2ic>(`Td>3M6iHbCkAbHM*w>1|ra_8oZ$LO-VUChwxwqKOLW zbAG~3W8VeD+2_>+w-~$O?Js^oPsiR%FPN{XcXGCG1PqPVveeq{EWP_ zd4mAbqaG5xgZzDBl4{tQ-+Yr}D40@~5f~yLWshyX91?-9u~=-DhAl(s&E2_)=Dc#Mwz(N zlV19VhCM~|=+|%kN-Mqwm=xC~Xy2)EmizYRkB~q`AmXV>Tkx9nPk#RZv8J4_AC3(J zObo=#CPP$_as`RmjL)sZZ__xNC)Ce?JlPU4#`RQTY;gUzY9(?%H8e$%lTZJy?4-PS zJ*Vv$Z}fzotSUXD%ZZL~K4K^Z07`ys+J_nK`%4Q==$DU9OV3+5r4b)ChZmI2le%#x zeLQ+aU32F!wu2($J{0s1T8 zFf!vc(-Oe^{TCcMISNyT{{U6!5@{d|2BuF#G)Wj5MdgcHl~j$K+OFhW=e-}}P;rP8 zqRb(80T}w9_ryGZ>s4W}&!iqscv*MwVy)e&!TREVKbd%II?miJ?8h%v>RzdG`cR*& zo18LpbWf_srK&rnj`n4cX0%i{HsV*T1CgA<1$2U(U2Vp^1gE!J;|buq|dw=7kiSpQ32kJH`tk^Y*g`%-)C^X6*lp&OhQu)08@cUVFf#6vzs3n=|H3Y<>MTk*U{mxUM)KK*Fntmgp5sR2LuV{rl;+}n}lPuUtHi0EIh!&w-fWh1TO z8$p{cf+c~;5v3zWRD{O3N2ns3d%OHF9AY!{31>=58xW?@vA$2tpuuzjuJLQ#lYQgH zd2*ZlEQU`E`i%3)1ylUtnR=KRe_qSvZ6xB;L%-uk&lL`_9bBl_M!bJ|okba@s`IJb z3c;zctJmEEKG6tN1e;FjulIgIvjD@KmA;|3_zf0`6|X{R*YY?v7YXg4=CCs+XwKbc zUaHf6bnqE3#v%z-=?c?6(= zpWTNvS}~$BWzb{l8<-##1fj-uqOHNZF;L~ATZI0YB97pKa4dOwg1~lpTKdaxzI;*k;FHWP;}(^M*i>}Ol*!u zThWPM9A{=&7R5_u!yaYzoX!srnVEwy0V(D7I3i~`w0WtCUq<2Q5s6HSCGdRI4Q?(akSDu-lJZey_^aR_yS z`?wfeS`Uz7qubhg0)d`~$B<6Fi=u|}-vT3;Ow}U4Kc*(uBuwOoU&s zIOkf)et}xVmcxmK1oxV?zhfj;2CE);yG=KvHz*sSYR{O3ov<}`mq{H;(y_Uodr!L2 z^AC{xk@y566FT{3E>pY6a>jr1l@_QaU;{ z8?TnPSuu0sVW{8UGtIuVN!@L~8`$*$E(m7WeBW-T6F&+r9<=Et&$tI)fpKd$z}uVK z`MRptuNXHnS^h9%4oL@P?ie{ARAl`5A3(JsckUs#_eRbuVX5k(*Yu_xDl!?#xtVTD z1AR)l2C3E;z((OT+&>U{a3- zd;b7wd%3mOqY2cj>GNu5i$1xnz9y|_8wR;juZ>Mm|9+nY*9qpn%Da7&m%y8GXh**r zv~%PPD#1oDe)rmV|7HN9AKi;IeCxLLe}2e?0o=N2ao$~sOf7XGj!@U~=KOuWp)?H4 zf@xp2hp+V}55XSLmyxsHz$^K?*P&+H+8685!>7nIiFPU7obY^8 zSf`~D+FY!HS8EGFZlpLD8%9g>pf(4k+Y=iz z@8!y+WX5O_z(M;uWtT*&sp$4a@m4Gk8%;H=s;X4_Lq79#@}X=Oem9zFF5cCBFq8rG z*}woet9P4{oSc&KzuV8MNlDoZ!uZ)}pG_m1&Zy>^{8eYpMC4(~*Su*9DBd#o;Wsp7qbbf; zgUXNY!_k1Vfmfml_IrejB(D3&1L3O}wXpAw)V5@yFp|# z^k{)RNign1S=FCDDmY1yKE{!dZ^^g-4TqbMdNju)wcya>uYSAVn%STBNgw2=ORBJ` zfNmg|rOfJCRF*j?FpK&bMXP*MVQUlFywv83W~eEBF4b(8zdX)!&eB@EXKYj{V&BXe zMaR8rOqcA;w-WP{+&5p~4=ie52Y1X|!{oPm9a+7vX{B>B_wlrBT@&TSp`tx_{{U0u z*EEmgM;WNz?1h>5N4Lp052^?2x2*dW6SxU?EhbOzO3xW4yNxAllVtB+?<0OM1LcA$89FcHeWN)20CTf z`stztl1LKwzU#lwFcF_ky!{$pJRv+0VKLthdRrQo2hjhv3?WWGOLZ$3UgL(VS>kdI z{=zfh7DUmLc-&oTe;;dU`h)R=5(~vtsK?$E$yC@lo*P9Q@zUU-wk7t%3+;e3u-$9Q;O_EKtEyFVefqLmOqjt zdu3<3Mi>{We@mdrqwj*;+V7ae`rj5!pD8?lhZh~s@mJXJ{xdKNWr{=S>EKhZh{tbKxNKI zNJQ|@Kh|^N>ic){?p;(8%hRhcxaSi}Na+>5M%tsyDqFYGF5j|gNE5pvP1}X|9IW+Bhk`qZ%xdZhKI^H3&ipPVu=9hXOVM0U2EAEuwjsy_Jul%YIB*)Z-|t9&bAt zs31=xi7cRsIX^a?E+e9-={HQLQLS49zkm9^t;I$k7!keNQs;G?F z>KDBKb)q9i2HSOrfRZHp5oCAtVKSg3ff^FXf|L&s>#;I}Su#$}%Jfb?2yzg{fgH)` z+6%?)!#Y3Mjn11b)dDAnoT*CG{33RR{{)OFyL9y38<-UwKCH35g z>s|I;3m$9dYh{Ag5qW-J0a*LqJxBJ4+@KddBiz7Qa}>G>wi?U)c#Mm+uW5-aDD|`W z(m441z2&QCe2c3&>&nN?uLQR?^%^K~(U*^JGd-?Bv{u!9xmF@}Ldq}meGl|)ghbwg zH^e!k{2EpijW|=Ti1z$@FE9hQ)%K~Wf?*2fyW!nl zUsd^C2@l-_k34^A_=STP+zFX^POr>G)k`s^x9u;^G`YT5AzxhJk_8iZ#^kDA6qSY# zRt(fJYeBBp9hZ&IZ*w^BnI+keUum!`EiO6Zhu(+qStmAsKMJGLJY|{jo)n*MJgs*+ z(GQMtl==MkeMh^%hh;h}!{Gz%MHBJ{3TfhJGlv(?T^vqk*(r2mA@_F097#mqYr-d2T(k5uuMx z$;ye;`G(pVn7b7v=-7TANGG`2E3U;fX+$*%-Lp6kc^&pWC3YXw%XB?M4otbjV*(xz zY_U;)qvj_3v4zq|t`ul^7gsv-Vd)*|ik#R0iMBW4voxZ}y11E{-d)ahy;9SxSk0-m zo$XHaIP+yo-^-wPcH%<>Nz-Zc1^#dX;%6Qo>pxjfgJ;+U{*lk%gjotD-Wk`#-t?&B z`QCQV&lkDueM~5Tr6I3xGdA7e*v}PQqZY+{!OZHLWz^qq*F8==bgIm0p zCDb3#51quh#xak-7zElUi9o_N@!b)DbbKWL1#id2BHJ&f`{JRT9DB zCmbE3q+$09{PD50#ys7X{)&u+@?%0vq{lx%8HdzN)>Y>$jm}MiqXMw|n@UB1wlLxJ z+pb1Tmh`tpjMG7qobpYZ9(u57%qBhiW)iT0VS?})nn=fPt06W=cyRblm$KgVl+OH5 zI+2Ic)XGkCj#FKz#bl~pwx-K)}M!nv`?wPZR+6b;=A!SpEu ztt8Buz>DpEcJ;<1Z^4`W_?-?GQ#Uu8*7L3(w?+9mR}i`H9qsPBk&P*?nK7~Lio)2M zoa2KeObQ$Qiq{FwGnxu~n{$`e8y|t>Bsa_pV`%22ei``VE4oU;c*hOhBeK3i z`pV0yMEHPb6u7{54K9v*|& zQ%(f1WXfIAu;G9INp`=&Qg{k2ms+JNXrIvIW1W+*Bj|Y$TeMdDtOYU*DD;#& zuH6eN5c#EB_VA2nYMT{(I?6hfwn9;A6DS+(i~ch07_d#fHWB2F7^C6pK=)$O)RD@Q zyv$vQM3uPFX7MRPvV*nlHp-jOZw^?`2KnNe4aNi1v?r_o_20PR)cN4?KJ|BUq4OHD zfD>WR(Gg+jn_4h6>WlKbLeE*on%|7Y4vd2@sn*03U@Qdz6_WH1!lGahS25$DCf=ho zgxEL0nua{AfrNX4+eQC3cInupk}JU!{hkJv6=$v94Hk~feXfo0P0yTs!ah4ABTc|N zw5*0=NJ`=PCn64f($Lid{ zG=4qZj(|cU)|AraaU@PNKL`E@euW^K>&5T>Dnw$B=5B-)GRdBU=i#4eKbhUKK&|1J zSV?N5V(3ym3CMrAYY?Oyg+2KBrsI~PUVY0>g6qRjKi)t`c@lqzg^(9eGWsRI5L#m) zM$Mo$YLLr(80#5^wnWBm!%B7!XLGxvlv?yuVgopY`b6uwFmKKBZpPu=pzvA1mmh@}%h z3w%GQTEstu+$Uu8hF&K(^qOJV^9K8=W6)vLH1VZya$Ktr=}4J;R%*5VBD^7c617-2 zCgLorI;g`g&e3~EOARkKxpR8xQ(3WpBvfuOHokmo`kJfJTekhoP`}8yV?+@kVm~<{ zcwET&i7X+r$!_2WapXI~DtWwuu=EC;s_p!{*sYh})yrX%^jLDOmOX_7q6D~(0Su!x z>4y^UcSLAbmqnv|=!dpO;5TvoReuPYly0?jh7#Q{DJp3wO$%o-uNJd- zo0nYXO~|ZZuo{SA^N~*K9z~a`xOxQhx1f*%&#CA*rAxr5EmVZncz^SB0aoPJ)aL2mAKTC|BSW)})6zE}T)#1{vIdJ7D8gH5D z9&>Emt>obqmvgt`=al^wH8a9IRej=FAWl4fuP;kh3g|_S#m^&8>$Ag%$|tv=Au?uq zMO@~|3TXtl^89YPYunvpN)Fv{vgKq6V+%#(Az=3<=YlDw$8<^^oku0bAAEhuT0q6)0rfq2Ef zND=rcHVGnUh;r(pQLir87co!0L;W}ERYy>X=u412SZ(nP3&b5fx}G-)#jJyHNw(3} z<~7htcX?SvaYZy?@$4kBx54dKeJ5irb~9Yx|0t=G(r*DVMzLnGcBzq@h?qoJayI@E zx6UVCEEg8jR5k)?=^R=`+e`REzQYnP;P~6sjP8w&ZYRun@vjefNnOvRLK2~Fh|buf z6Fh5QVdaW4I8hY$Ad_VZvD{Bdi~h3tASoIU6t+Tx%vldBrLsr!wfQm>=5igB#Zie z5V8dxrX$92j~jI_n&QII zCvoCG;@tOXdRs4pihm#4qr-~D+yRKJtgH}hlrF?SFssHxh-l9BNmhBrF#c`}=Ai8n zddl2HuIPky}}=D=1v$ES+(fV7t^8e{Y<@k|-Fd2;E# zfiwBs4cNl>W4cgVn}?s_cRg!>|%5r>{z`ewemdWyID_e@W{P zCq-{v$YQ`ma@6wfWAM4@{vQkqC*?sZ!^qJoQ~F2|@2@ihEj9J*l0#&a%>jx$$LxEw z>m32xV2)wWi64nvku2D5EVErrx>>a%*6(Oh%WoZdS<-=smleBA~N5u zpe0o;^eM*V%;+WSV`iD8O!qHRaI^I}1a)>J>xBJQS|p;kDf3+ykWD0iKN)3F(E#u` z|E1#pJH-o`RY?U^jkJMe46o8-PKSMkyruJtiLmKR;yiwd|D#sCLh9r1Vs*OJz8cp| z&qbEXzQ6?zYD`#=EB^|)d@`Jo?c*(LGc<#xx(N(Gq_hnwcoS zHH^)9Qh*MLR-zkb`148e$;KzPzwgKWX`5TWADOmNevLD;K)MH3KYV9f;7!CWlawLr zoWNcXG7;fVsQ@fgn~{6TOE&Bzg%>VceWmJ(s$O1QqMrAN_`q5G3BTK&Nd9Xngd-_+vn0&I@x7jkL#TcF4I4xI1 z{sA;!sJSowl?2w0v6$d?fR-yj3j&syD@0Mf=&-)SckuZ|xy>tNZx-Zc^S+F4Ax?Ob zr2YHEza^A!5Zl*vGCiE%Qz-HO3HuPhE!xH&5x#MyYU zR*cEV%&&ffEH^FnR4jkN?BTL-L=~xd@!h^fPVr2p(%O%^V(M;oJ0*Z9b|aaF2nw5p z`U;;Md7A}-;ZUXwsds=O#lH|lvT8o!ljK6;Y6ZCvT&5#-9$J<1&wl+Glt|r2Bmy{d z)WGd1v2)Aamy1~7nEL#ME0tA@(ldo7elxlLmvMdxNb$job~7pvQ<|QL#!0rir3mwU8WC zVP*hGAU`YWmmqWJZ#}emUaYTR33DPFx^>$dQpsyLnMEWGs^cjShRj-Nf%?XND9GIi z;bXu3#iS>vCDjqBA{bPhJ zvtakj*d22>CGXZpHT1hu_v4~Xys&iCP#Efzcm@q~3rD6b%HHptg$hgQpL8RahlN!I zM8t9!*@%Ke%tUh)Xh`VV$hfUWfzHw?p(R;q6#2_`1IyC|!vf9Vx=G~$25p%#ku{TE z8d{N{3PX84i7sU)9`t(G3g(TIF=MlDh((?>Z2^uR%XQ1Y0jt$Y!P&A!a5`St8Mdh2 zwOF03n3 zypW|~W@T=Jz@`lYU~#yWJhH7(RmWPz57@>Yjw;K0x`AKc@A6{oSNNFm*^0uW<8ayU zeidYPs9J1G7R8z@A(^nWk*OR{HuU~NZ(N&pT(67=L z1<5tO|D42QVHy>eRx}5#Axl(R?N1PlKI5xikg!ZoG#+pAO3)Hep(dpK`8M^N6{Yrx zxX`JanhQ=4a7Kf5jw+)4IZyG&2Dz(mM>y;B?`|MvD=C}5;$AFJ;D91Z24gDWvlkOVc$z62TUBxFrbP@7m+2P$t?LVa~WA^CTBUCktMdYf3_?7cwiT zW*Xu9|Jd%)rrM?(UdIPfx=m>hOK)>&#E5MK-K1 z4isCQJP;P$UzbPW)lRyaSFt!8TF9NSQtW2|YY3L1YDSScO7)|qE+?1=pwlzFsS{^y zT)O2MrM6F_o;pt=rqt!}5$(YV(ppbJ3nO`xuD_e`eeO%VkJM%Jqr?}Uz0pWv5kvX( z(>iD_36Ysq-({rV5{X~>!0Dy=M{Pbe=0aFEXK@bEYGpoRpsMt?q;3QDh@8ptVmqlJ zQUH?4N9@d_q2jYB0y0E+4C@WRP}Bw)r!-k1lif8Ur70#UW|> zh9AWlA+BXqsS+>en%H_J@6S$)3bHE^&jmM2NoP!!L^H(aI8bBOEpj%d>^5Yz9r_rm z7Ofk_d@6o1@Xhu5+b5G!+{(#HJ8dt5R>M&ASXA0R>BRO$UN&`yI(r=jt78^*zGg>( zVrPFPy94D}IwVrg6J}eRRYycgg=3K!s0zRgZIx4b(NEGBYo63knLZK_j_$vfH_lv3 zG8iRqnXnM1K~%|-$KEDJdn!FYP=bYFUM3>LB`}RGkLshtUim{fC_73Cog#ZQJ+1ma zLlndth;GR>&(F&BF}$9(7!&5@rMVI_!Y3#rAk@>4tPeBwGsguN64dJ$zNW9};||{u znKTRP#(OiyIQ=T9OBo(4)@5my+!2}}6+z*0sStBg(mDv+7S=cGr2V-FQz&DzS(i?*P@+F^%Cxr9^< z_e*!~xmHcHZAU9?p$&Q>W$I6%JMho9Z~XbDqW@Fc^CQx}qJ_{r{j|wmv13EbGW+Jx zGEZuR$H*8i=C)&E-^%*;nrA`)i=9_^^^nwXxT&n)cKI-Wr$b$)0_HX%UDSqF&RHYN zo?+4o+Nnf|(rX7k9_HD81 z-7+l@=ke6d-dDbys^1f!D&aHIGas^MDzhSsLm(efpn}9ajAo`PJ$?rp7^ zePkFDU~^Ob&jgt4kR_iGGp{faQ=7=qy`ad;OOp$OtV^X9y~b9zfu(f~J9!+-eu8Nf z=9I;#PMdW1J_(1y00IZ!FwbgUSN~l_M8hwyhEf>3C5cU!(4MW1Qo&Pz>em+;<2!xqo56Ur( z_=45h+nVe3;7IdsJ|z3*dLTLLf#!ZaS{JMzH8Bmeubb z@O_t&_G6*l)Gd1#Qb#J8cMV@MgNam(9;qi{39m@OW~k+rDOIHB&dJ)Ez*UYcFjFq@ zt7Jl!rho2-E!@cWZMtAXjWWIOu_@T~Iu1Ik9Jg#;xagsaamL|nt*gb|zeIzBS#J3A zY)8JJsgRLbouglKoHiTq6)hI5`w!gH^Y!Hkk!gC5Qj*vM$l~fx8XIoY5(^ViAa%Zc z;Jz)5Pa%gjC0y5XC{^Qcq02cl@P%cvEXkW#f9KdfccKs?47%!asSgUq>X}P5h0WGu^IU039A9FYwrY7(w?#$>}Biu(gVzQD?uy&p{tMk=66( z4P)=v6{#)igLT}kY{_q@(Wr>y*U~jHK-}ldOwY@KWV)}=5M9qoo0KzLrF{d|+q0=G zKSh6wJOUt!f(7KF@HvJkPEg~By=5?ss&iOLj&)$)tbdtq9<9jRUZ1USgGH-5^CIdx z^eo=0LM#IP0T#xIKF!SIdc5iJWBPNhec|XMWmO5`t+vvjchQk5s4Ubz+G1MIx>ik! zMvdbcx-{hP{EGlVs~W}4LLl;{TGU@8es^R%0^lMU|I_V6RC|^zuWn%`mOjIkFWC7k z(&VyI;*twL-P6k<$V5bPr8BasJ}pF!+uJGR#+%%0Iwo$HQ-K_Q&>!CpUYF;(j9-e# zA*?a*ps>z}f5Eu|GZg61yoc?yj!a*7A=Q5sJpJ2cEA^Z6?sPdHiX3xljVMNkVQqsY z%WnS&8THpd6ZUNn$t|AxMYkwi;{2jh66MkNmjhrigq9H#c7mGnQ+D_&?wppWDwQLv z?qUF?cy&K34 zYk+cRTsfKZjc>y@A9-p7Z+O7nz!@!?DFyT0f_snLSbb`HOkcaikf^~tM9vjK;?vwm z<{{|Gl@y%Zfw9rP@xu$6wy%*34TcwZ?#78<^;-7{AfW~eUcnotM>`WjS1+V#n0%Mk zs%V{mPB3C;!VnZ%>LbhBrunY7q2#yGLn8Y9T6PbP>PV-uuqdn%vgErPetA7tf37cE zEByWl9}^mmdRj?&HuZ5V%t&wAXi92>`3j0Ut3Gy14H{|){wDORDu$PQ)JQ-a0=X+U?D!lWjsM0 zR;-DcU3{dZ%~yc(_XOQF;xci|X;w2151Js16WD3iVbSc`$T!17aad`oVPwmdUd5~wmuFj|1j`g9dc|P~+hV|%Hzq~N zHMD9nl?c-tuy5PLrQIkJvnM|ek&x=lsc5X_pV&Dk1&Ps{v5U>AqZokT z1_mxH!_0zZ{~rKdK%&2K(!--Sa>fwXGMZFuR|-&6=K1n5*ptB&F_*(Co2$jzMb+7N z4Pw5oElC5C#HPTa`!WzEy5eosM8#xAU+h`9Fy@>96d=S?i*oeyk}SQ9F}Iew)C$G9 zSOuy>wV$g*ba|vDSfpwPTR2WjM(oV#2~VwNsfq%ctx{KD;n8f>Q-#GXL`%?#F2ptr z4w6Q1<9D^$U1#2aXfpB?J&%s?Uu=T(g7z;l6_iDkvV+l8;Y9!*eShE4m%RA!-;4W> zK8_h+`U?HvqX1NMLUqu3LPD1+QB%^|oyHdKYd_qodXQ3cU%WwXk<52sM%An>vXF&= z750lNhOI*w-SA@_A(+a5gH$NXZE(OKRC;~Y_+^jKFlL^U^xb0vul7TA=iyp99M`(_ zD7Co1xu}OFE!MLxuPgByf0OQC5k56?$=&nGCqyduw525=tuQfMJyW6|P;ZLg(@oJ*K3Qw7J_A9^zXrhfv?0^=q-EOy7mrSZ~Ko4_w zgrSYwFYEz!adBYV%hjKG_L@9@IzsY(U|xQM6c@H2K5i`{BKU8~E`wVw8K-sZ#D!_< zt5LSl^@2jT&1LKkDUkB3SK13op%ls3P}D(109*jEtR+<|WX)`eD?k{z2$HO8zAUR0 zoB^$rG8#H&4hY?XQ(2-;(?@wh3#&DdR1A|M;a8eaSp{)a>>8^s5SEXlX3(l7fJrthfYAZLyNF*~wRUQlhx>AR-tb(6P8Vpj`%yFkxN5>f8p* zVO1{7U{F^Yu*6E(lu7mq1{q0cqz1(?Z0q){i9GLyC~^f&eTw zfd&T#UG(c{2`Itp!i7>+vgLTP$1OoZEeTN@twz=xlyHjHnPnyDN~qYFnV~9k-im6o z3=|Ewh!+xKC1AX&735ivOI&2QT2W}-=U36 zHdwpGyy+vgU2a)snWv-_4w!>h$?}SOgnAkFJ35|R^TF#7 zGq_P$LYswwQNonb@cv=1@_bPQ&m~t_Rgtv0Xdgg6u_)F#0-<_x9wVS>5pJ6n>0`_x zwHt7zIu4*`Ec~e!JwhhsFHl=i!xCRXq8l6}I*W)4}@0%~PEC8pU zJivU9+Gz3o@r9*7Fw}UMfu@#mA0}AwaQXWY6k$eNk!)dlnWx;zsaA!^Y!oL{=Bye? z?Ezvl!cB;L^3Zp_AxN--G)2{ppf$^|>6S*Dt@ZUR1szF(Xq7VUE$0m76E6w?fF@m* z-1L0KFIS3t`bh1}O$r+}ZCC>}GU2E$tI%i}<=D1b0d;{OV$N;Y91woc&7#|LWmnJ! z95jZCCPI{HG=?@@EAB+YN<(Q)lw(S})7EdDH<&+Epg#G!51``Sp z9jAQa6K3O3iy7Z=C4(bosmfkg7`oIl5Ns~0aF}e_TWBz5UwDCUTF?f@IfxW&g%I1T zqex0(A_HgwZy;z=SVoW0&8mYdP1z@DgJpzbE;EDih&LLIOvC*#Z|TRn%N3N+_64LO;3Dh_oFxZFJnGJ*NU3lvPk_ zG7x6FfR0DImCSKqi)q36hE{ZRFgi9~dd|B|nxYb0N zvY{{#?N^S@Uw+Vfo{b$Y_1ZKb{ut0q3Ehg=I10l`^%s?ToYM~Nh|dJyHPaqtcafpK z8IWnDsa%EH7xsp#H==2>fZD7r#Rg3{YK>^si^B=0mN&Ia&B4)lmExtaQRs2wh$|e+ zO)<&o4fyejD@XUA+zUda60H9KaA{c!bs{E9@6TwKlzG)!)HEQqf)(Dk1Tll5IaZ+V zf7yD;TU~220RI3ZuhFO|E~vxORq@r-t2xho2H?7}z>3(IuZcoAbCE(A5UW*V@yDG* zE(q=o!>Q^$gEm}N6r=2K$qJs^y|g&IC~DCjGYuL#)UTKA2DwIXP<9}6(Hk*9b4ncu zreE|S_XEUSH_^NwC*CsbdbND6RL5%=jxrb{`e4MzJzd$cXA88cr^an?Jf=g$S(mlW z_aD0bp`-cb-i9+_sOXh(t-lHQj`%KD+=-~mmy`s+BxbD?{rA9l{abFn64Tjmw z?MtiH@Jfb(s`dz&3bBR(>u7`vfUv|Gk)@flhA<>K4BK{faNISWLY#*xIYyH$uR6C- z0YE4KTnPr2BY}@-PUtq&LV}%LOu0bA7Kr5&>X(>}lEx`4j!0J<8PF!hiqyp>w79A+ zHJPqAzOBkaZ`eqrXz-ykGWH_w$T(i;PfSq(^_#z6WF>}C+_|PUJI@{O)Xv3}M zBnLLLIaLQI7cL=|s}91%wrHLS88t7bS{LzV2elaQ=A#fX}sy*~NWwU0Muma4=mAO!1 zZfKw~cyKJhyH_jTE@yV;T&Rn7YS3%D5?vIX+GxH~^_lp*ASv4ZP!>~l&;aw2bVo3> zM#E?!_YrZO-e<+1yUceTLm8RsAR(}7wm2OIlfGd!F1J*%VP;=0LQKbsO5LTz&GSjYx zfdyGNW|A$jhJX0t#KG9i!f@fXqC9h-#343w^~X}( zn5a>4sw2grT`g-)@a27udY#E!GgrAdjxtwl?)cjyjF}R z9|*Q%KQ?*qoAD3T5aRB&voi63pw)9}R7?x29cl{Nx#|Xh29wgcd#7;WhG#j^Hnw+d zfC|wApjC6Q#{>>7u81J^gzc6R>E z+r10Cn+Hc>f7B*xErhQoYHw-PAFGF^I(I+};GT?dtI}>ke~O5~MLWl8oKs6;`MfJZ z3e#TWkp%V5J*kb4m&?q6s@b1AOvdQITzVm0Fwa2s(KvIZNI~ZR00M2+W16v@*2jt9 z%;YLB0v?Pd0`svI=8|&;Q%Qt^{1E=#;!KIebqlpnN=Hv-AC6=7Ob$!V=dhc8(Fo`xj%?!NZ86KPCwn zbPNcNsQv!{)S*q6l|QJQif)^iTgw%&;e*rpQW|rcd`%DX=c@({l(3hhX>-Gbk46Xp zUW%^-!vGpGaU8`#^VJ9lT69M50E9#wO_54MLN( zN*=1uSgDftLi-gsJ z07vZO{n4(t3)Bfsgg3e~-3!%+;mFf|?s1NozEl~ z(SrL$wUlcu$Z%j3R>2%D#pOy|mcoOdVd~XzZ`Ka#9O@N@R~D?!?a_+wl8nHaDRDyb zhS{>DPH&+Nt5T_qibtS9u9FTx6>pE;(|G_bskw+I(OW^uRlIZ{5LsxvaO1o%R;seJ zEymDaaG-aavNV5(f(XE z3#;6?zySNgC=T#p`@OxI&hHG;^93?+D!PQRXIMtGG{)J&Iq55lfsh)rXry{4Fhx}> z{$IW(&@$508w*Wqn5e~OOU1zZQv_BB9(s#B=IuMm@GT-<0qFEl>gEl^SlM{^_k{7M z={zrX@lv85JtdZ|`>SCkxrizsRaAC&|u(7w8V##JtE_e`aQTUhkEt?0%@j}B0DG8V$c49UcXbD&_W ze2AWPV382Qg{p@0vzQB%V5B!ewyi8=70QUa3y=#G#^Cez(7GF}3UIEa zXIsxM6wDWd7ab-NW9{HDg}L2ou`7iz5xJbj67(W&w)&Mm2U+|JN9?hUO*z2#%0c!A$K1?(%Mn3?%nF86ewD_P)_Zv+KerLR(CGXRBLd6 z9CM0W#Ui;>bHmo#q+P}yARfT9oQP5lD*|QiUaQT)N9nn8YcA-gEU$?M-p4|Jv=vBD z-b>_JShuQ|pg!M7hloi{<^KS>;cT~syqEpPcrpIn1HgD1oF=^QudX@ce|WpUycfqmv}t3Lt-0sdUpZO8A#1cR+qUO0Ny z76T7awMERk4Lz+mlmw>`>}f4VusJx~9IC8WJjMwK8hc$_23?F-D-6+TpS5E2Xfs_MgZ*L1~F zz!?oK2A+hwK-zFu`b&|4P`-0Bud4iif!Bii z&X{dLNaPc##lu(9WY(d5*l|AG{CH#NP36VO<}^*2sx_32)Pn7 z)h(^&SnasIRjc2HOqL=-BgAaL2Ps!KVazc#m#Iisq+*HXY;D4?xTnBIzza>UqW3aE5WTqr_CsR7i^b}PNAs}~bI_EeHA;>A?SgbM(g91CtuRD3lI z;?Yq{DzPb_ynx3H34rhr9a;2WARj=^S)T>ZZGf@rU67PF(g|>Lj91sRd=0S6Fz;6O zhbRK0@FN_+aE`VYuWjp%i4r*g_JZ^^DR>te9)^9^XMBLFXN)&J4Q^9SgfT%YwFj)U zNb~bTPlSY=jbmuhJ^pg>WIm|1>S@ssQJKg^U|sPy<)*& z(yLHWDJO}V3AR}gy2u$pWmZ+?P=-Ym)LJrmy7tA-AqQsImY@;rAki@RReM6JRRt)> zU`7Da0-@hiwv|%i(9Vjx#nGdhfLTLa-2I-6>PJL3L(|`uECrWv2K_~Nmvi#6i+HRq zy(7#GMJV8lya?xRwxRhipu|+Hm{x`gU z#18AL%ooV-DOt|^&IQHmXldCo1xA)E04Q{P!gfy3EysHqz4)wyW^qMQtBZY!ezYKg z$R)OKM@U^3y~}Jc`Zx5%Y&aK%$ZA8g}g?tf%nxbJ*f+dh!>1h!mNX-jyE4am* zgJnLooR16OBB>QQEKfC8(qw|B-DOWj>_?Tjdza-aI#R18XAxq?%3*1NXr{Xsn3x4| z5)@~<`pOn)Ibx%4CiRs?FzgUDXgcFj8Bd{TD1UK1NcIxPApTYHELKx{8n5aYsUEyx z_Cj7rEO|54M`i133+0;K>KYznxpXmZ{+~ApRL{c)w~0+?9kl5A7;j4T@^9Y|ylTN^ zfyb;^&KxHs8*oR*JwnM$8vfaAmUe+h*Birek8kfxJg4sdroT@pv2k++A3)!j`G+p= z{KzOhp{+yh7@5sNe8X&vm8F$o*iesni*LDFqLY2G;sCo?7-=}CIp|92zZ|Zvm;mJI za}NfXY)5|MQudR@T?AlNE1*)soJ^vK9fkhtWk`X_O%>N)Fv&fhTI=5_m)0ZfN|%*P z8usrPQM}vg9WGME=W@X037y)OBHs%JfvQ_BwM|Ds0ZyPbpiDRgMAD^Q8tl`c=?yiR zbmVT+qsaqRW%mC74k1G>;NlYHmd}YzqFZ>U*sqD7a2gm(4aa*5i~Z&?I$m;ip@@_L zMX>P07*-b_0RV-8O|sw-EJ$TbWx~9O?iM(+@Ut)5A0QF6 zXd98vX;%7NUt3sOB!MC>p#hKY(v&j=Xy$t8o*n0Gi2dgS*uvr`&!^TGZ2!;wAl7+nb{O;jcIF z(HF)W{jA15WAgmQ)pPgE)3r;2y3{FTFuzNggXjKi*KyD{Vg{b$zx67I#RwbN3oR+m zE*sCcQ?S;}f!t}{8v=K>vuYrq3l*&ujB((VDj2G64Rx#$-urQ?b8$Q*J^`S>c^DSc z^IfYw2tAWO+N)vMqm2^w!QlP$ms@dH&%YgIfa+LJZ1C_s;E>Fsv+HD8W|VvoN7Pz+ zN`Cu2AR0SELBC3a-+jNqWLThqYxF7x#XUv{0>v*--z7{1Et(ubcx(1oSQM=SkmK-> z7wEh4EuPDm4GG)t9S`eq3x8JCeGwX^5lz5*pNORinaXyKv?p>1l^Lzg-l<)f8JvvL z68b_jUoBcv!CVD#3tnn|V2Uk^%~S;31AG=ZN+4PRPXcq52MAXxyO#>fJUjqu%pKj0 zSmA=p8aCN#G%$;x3l_?2dE>MjmYf8si07L!-vvKohpFoo z!fyPpyuCfPmw<3?)@DTE0+|fN<(pD zil|;FcCPLXapo?{uJXO$D3;n%Xu@SBG2>=Ak(Ajhq#CJCFNOy}Wu8)S;so@nrCHi2 zRY&w(thnOy5UG;Xjr064NHdz54?V21I+&bHXHGY zR;H}AC?lb^P&2_>Ji(`7(m|dw@fgNh0g-iGf?LHDt8ctiN>knd%Ohe-7YpeIiXKRa zI^dO+QjKC{wx*O6B~EhJ40bkkloJlZ2Uln?Mm+DdHT;+_>9)~bA~x$ZdfYW23@)pw z+ublxGztwPuxN0kmqX9cLclBwF;|*GyGvfis2|(&XH+dj{oFZ7F(`FlF36$%Li(I* z#7qoD0H^csYx^ZPRSVdum z(&@wgm=;G7Gcg~c1T9N|0`xN+!6>>=*ipIyg~ZD(2$(#X#?M6+snx!sUS|YEYN9TJ8`z zFR)zt!)#<Ilus*;3lBp1mWrmDqyc`oU7q^KaPZW3Y2l5;QjP8C>&)|D78GGGm_h$8dVt}ir={<#Kdp>ExRw4s0w9X-n3 zI^zLBYX+%A6Nqv8$lc+Q2XELNgJlwkn|Cx)!lt;*XQxVY+F6OcI2Eq7x{Dwnj#`8Q zy2C2ADm@b2F5RUKV0&L#J3_J~m^uW78D*y~p{!7FRx2#qaw$BDaH%E0FI3ZA(t+&7 zuUOh#B`z0a;YtOC;+Sm8GCMkpJM?B`GmA-9`MdTV*hKjup zTs^@P!du+X{=>r<(1zlaUWt84ob})6yl%yB~M`Onjsi4u|<4?CB|WdP8S;c5=NWvWrf-#C%XR8L;3i z;p-XKFD$01%Zy6fq^-Mc{Xng)(EC2s^32M~MqVqW4|s*hy$3~_8Y=?%jW%L;DSC;}4$SaetWz-#;q0!ShdyF0^!n$BBMU~7|l>$hA=$R5g z8wj>l!B&#aVvB$!v)~Y`F$<3HM@ouRm=}aihYb%E2yq}lsh;ox9yglBW-7MaWU4fb zNn&?pr47;yb>f5}K}D;s@WcR3mDE5`jSUMHVj5`{lPsVQKwLH+Nos%_p>U~Jy>Ba& zgOOVzf`T$6pu(=T_<>XxObVU-vp#uqT&m5 zTd+XtCg0U70+q3$cb4#KA~t7f;7`O7N|K!$QA52=$w;8PQkX0DjFf1`h&*Ad-&7&I z^bG7Rgm7E!GAdi6jQT}h!-%WD`pVYgp+a7zMZ8Q6foeLd#7ovGmEQ5w9eR|fhOXK15N=9GKaEu+yAjpEKxuoCf?f(JT<-4z^{T z_tB8rst_*jSU@jy1Spp6oZ|e-Nu=Yz!@@IT4u!}OMWC9fGQfl>3@L^P9qS0>gV4T( zTOlOM?^qg^>u2elC`%jSFmU>~q#J{wD0NdW)QFcINOEUkuaF%Ti`5RW+Y{)jY`V_a zR06Oud1OW4i9l9=LL1SjLS?R{6U|I6gjNNa(pvE~zs04#{EFzsF8mO~3v95fuK80i zR+y(w5ah{A6;=-elA!Loj6h|&y;d#qumf6BcuVQ`K}J}sf7?%iq!_8=ZTqSW!&|MM|Ch0Mho#{%k{Qa z$5i{FuZV3-B1^n9KMZ%e55zV8NBcTV-E$eBCbSno$T<_1Qnbj(z<}ru`Ir&9kX+GP zx@)o&3MIzi)0(a0%(fGOOHF~+{U9Lq9_p%ejs*iKj8k~JF`a700NQDldO)x{9@-bm z)s4#s%OW7U1x*{p+;q0B!gfJI*Bf9|ugeV;(`9st$sNvVN)YMjLxs1*+1!it=V34N%P)RP7Zdi8LE8o%l3GW8;MCf1 z3Mf6~tZ?My#YW6L5hk6o9IL^a7zbOB{HqEQC2k6+0K6lwPT^jiLy`j{E3&ZwOAH~@ zw~)TO4hR^rvTbT$f+s=rAuKc=(WyW=nAmD=F^0V;q6rkfYeIso(P@xIB0gs516Wde z(c1J)gNwd(%Wtg89wvJQ-8zkx8nu;zO%2#p1r=zE!`%l)b-a#N73O4h7KN?0(M;7$ zn>v*i)jZT2xe)Z4+I`=IQZS&V+?511_#pJyO{zdcRV>EJEhwR7&n75vAw|S=bzH#j zu4pEfkXNfkva5KpkXF1pOH3YG3}LZ`H<3HGVlW^KTMcy3*A0}EDzX)NfT%#R+CuY} z*r*WDEgFs&{{YHV(DXQEQm#c+SnRo4yVf-d9;G2Y1zYZc78P6_0S6<%wgJP7V?c?DNUrOUX*L*VT3&zETwc|hUOjMMx_heWw)|fG98ss zYdYI-&)C6QImT1N7UfS=Zl}nBwmtNi0-e~JV7SA)ed?>To1@b@xGZp$% z`Ge2?sDG36J4Hi77HGa;L4Z|{A%%N_*BC13KV)b0$pzs!T|V-RFNv!LdVbRKEof-5 z@Kg08)(jXyS~dUy!$o2)&R85R=iLxCHLA%T>QqF%@?l ztYoYdy;;=;M66Y(0aSWGEEN_FBh_z2YfOULn^73%Q&(o8U@DBS&Byi9;G{0Z<|EYM z{Fpw0EO(0OgB;XVNGD`aIj++TJ9cIQtEi*u?xer^F+`ngFS#C(cS4Z1Z%KEb9mJw1*gAyz<9U~0CFs|#@1he zQu3Bsx`cX3$Z5B`Ynq6&MsF?4Rh}pVOjVe%Z6QHfR>l@Wv>RpaKPiQ_a{$w@i8rR0 zXd}p%5fD&eU|QpyMdHX}@DO!H;4i4+`POdKu=v#;&<_kE`<89e$uoDci;5U6I9;R> z0bQ0LyFRcD@kQ_S+`pZs$nGhnlxIL{rmHj>AAzHxLAJ28FVxgHp>pqdR9~r(35;4( z0_jAr5m|#NzCdEhM$LybfD0R|G&nBgoc0`pB(72f>Y}%mD%%|J65LRlsZ@$~VM=nn z+o5`mfI7$**}+9)C0d)~t6{1y91Gq#Y|y;650dFKHP*5O`#n0No)L0X9c@Ct)KnM6 zYPdYwFPrMc?7^ariqD{5#K?stv>H6A)0Numm0mSK7M}-dgHCS-diR6h5K#BHwm}%S zyvpeJf}fiFM}04_5w5`Sh}HSz!|T9uP3MlY>@_Ys2d&aL0o_lCH9R2K%pFR+3tuoE z%fO%9rE=h+5hYsN9W&!&%>89QxUag8%P{aoReId0>c`Aw+pOnZp)|Tk#rR}D$+h~# z!u;W`DVU2>R#R1#HUXBQ_dbmP^?)(mQl$m1hmr3TXE_a}G_xbVk@h?mwde_Zo?~1H zDj=mfNEMc8Zp)>W4P!JEtb#Shrfc3G;snGd z(A+?0MSL3aW7Y;`Cv|o6RbN%=-;61s=jX_%Q+>?)r99-FD#BFPs3&0L3A?P9SESK? z#m&eP3=~03gPU3wu!$5}e4T5hqQSi(*tx-?3dbaanxJMus3yE+R*%azG}1pw8%PS& zz32pDG-1)!A!*-;P{DFJw*_^x7BpRz05N>4^aa!wsAh~g-O^7Jh2UTs zMYbR~#LKU5C{m#Z;+jWXjw;B$vZ{uT`g2UtT_Y)#WV1ONDJW2-hcvuqlP`5>z+eR` zuuM*j)1gDHTL(1yOZX{fhhni?nsySYM@SHNtSnu3CSrVAOT)3`Z66=V*udks5T1Da;{{XX! z%tkqN{UeK@zO|U_tA1kApBoiIE3*B}tOsH`)1v&G`$Shfgz38>hPxItfDVmm?VSoW ziiJTnbuP>|-*WV)IviUB&-n+-_M482dl0^ELUG3Mi!&&}D)4UdR|t;1W8ygaSjC}s zyE>|Z(6Upd$J!h8kp5_+uiQVVl)IQJCNwtqKk|VFDsMF22p_g_^cdLZJeeIPO-|Q3}RUepb#AdK8&fD7aRv zJ;{Iu)=1EIn^#B>R=rZH7|9Kx;loDaIT0J{pkO-Ef~wq^*yF;k;VlpvD6s-uLF!bd zm~Gi9AnluwS8j&Hx*D!=QsX0|z{5znO6nGS?3@Q~H;Qm;F|MhALBQScsfo(Qp+P%u zl+-PoN`?Vft!~o0C`_Wm+Q?p;?Hz;`EsAj0Yjot9Ji!>2l!kVM0aDb!M$Dru$!P_Y zE&&x=Vu0kb@>Cn6eH=uMBYq`4Gr{%JIdL#% z1;-IBhY5(!frEe6-_*Zt_e?ED+6;2bp@b^kG^;7a`Y^57fp{Z=301!qn=INDG~n zt47XE(CA>N9=Fv@F08DEX7fn44RC6TccLsDkab#l)xraI*y+pWAWM>~p~HUUOk}`m z_jgKfvA`NmolbKQ!s5`!b=IM(vz5*D?FZSZDaigi%Q|_6RffP|ASQeNfx;IfX1vJHKvThT=M%R3vw|=vH*Id*2vW~xP42Ox43{=`0nBPbufpe5( zPE5-$VaZiTsuKPgRsfd@EPAL{WKNR9#iPQ?3IVMYvXRD{F!d1%TPT~dr)hOMV3 zs`^wN{bO`m;iaZ_uLSzzj|C&4x+y3Kw5}{q2tXKM7=$FgLExm(7gb{Qp#l-*r#^X- z0lNWGtUzKSg}2ECtPO~jpbGi9>^KVG6z=VVcXVYsP}S_!EfnI&JP44o0M%|_WZRJ$Zwg^x(gk(YGaE)2#X70H4!LV_GPhEWBsN*Q zebiaFG-03$`Y1A$N{2F$k))!$s@kzUlm=L>p^GY(V1lSY*aRNd^|g;|r!#7y@-y15 zA^_~fTKIrf=ER`Z;)3nSq`Csu)T}nlKn4}MM-_^eVFIXno@g`9USlEB(5|Jdoh4-N zEIXQ6DA-}_?l^=P)hSC&-DKT!5|mq@OL-Q!)vu@@S(ihi<7~5%#Ivu7MdTZ*<_J~~ zhT=K5pi9o9q1Wim80=<1&nV^G0+!Nhs5e=^W|Row1(fQG-rhpYC2 z*Vz5y5m;B+$e!*q4RV=<6bf*ueWLeZfUR4KF9BhhO4HP(Py#jT31RpO<1*ILJ(eeT z+<+QKcA}#`GJsLlh17QXhv9|z{hrZ0zD}&lAOYYJGO7AYUbXi`O=0JzCUM0tAYVl3 zGE{(YW%FfZhR|BGII!W`H|st#-NOsnmMmI|J|AiDWHtQN&WGguM{jIEV!DRmsl(oA z5YI48wH>+&>=>^1#H!Q_05!3Z13w1fBbx0ERo-o?`-+sOnMd_%0OsY)e5vhndUjm^ z1Dh~HPX7Q&w_f><1_2WZ5}ho8SPV5(c-|0*I&8A^IY^;ds#BrlXmvq~?=8A$_OM2_ z7%*a)>~pk@BfG2vN=sYK)Cr4cMx;SN*@ZTTJMCL|(NF1N7*L@z3$%BX7j1Bq!ZxbP zue^L0E!^-d9%a*<4E;fJmkr6B3e2L~8fHI+gnPNAk3?gFEZ@EUB)EOZw5Xwek64-$ zl)JDqkcK!h^n*962U=bT(`A7)VkT~jTb8gY*=iK;QrA_TXb3_s!$K%%`|ijVvqh?o zB)aR)imENl)X;t!-JpNnEO^7x7>DmoGv&F_cRzgaLro={T#bEbZv3QzM9+( z9mHsde|AtT%LC?2H0o7&rGEx>5xuq6Ick2&au6pb@ll{x?;9nh8#@_8dIUej`HtcIBsxilxv%WdYzVmpwe-C?pG6sctT~kcyS4>w0c8D zs{p$KC=~&yNJZR=tI`)6aOFi5+2tG>WlQj2RTStF%f7HYHReqy4K0;Mxu~%+656Mm ztxrdzaV%yJ)9yj(v*nGxH{Atusm~agjjO|qMXzp3hkV>f>mxPpwdl>=h{XBH>>&3^ zgL@4p%)})JukH&uN@E^wEG;IU%CfDSVMP;C#hr@@6I6`@~xHZe&GFR}FmyrA|PdKYX6O zrHHy#RbGzQ!_Z%OM+3@Az&fsaCDvvB(^Z@9G)ioEGN!RjLx>i?hP2d78^Eh3hfJms z0(I(NtkFV>Xb8o#XbP%P%W#7ryGpG6F%GyERnuWtSV(FDqXrTNLc>iDey^#48ErrjI;FGluBCDVpy#}55@=lq(S8|= zG9|PRL;Ju8l(Bh1Vo_VFi=_=XyV<=g16)j?OGbNQ$ggFSWvCi0!8UB6^m&8HLRQ2A zh2=*(2DSWQQ1(jH7FEbW3PjmLmXA&t-PrBdt8LxQ*jq!>tkA9$g6Q_Ee4ZQu6q?=4~!K^UYN&@fZ(bF zfK=AcmUU2ahMnqGJ=NNj2C`9Bb@LphvjDdG0=;jfY(jz3UB@`;hs??$vUAKve&Z!L zzI|Y!bQCPL)5K#20Tx#$<4{#mPysFaoWj?dtP+W<0Irp$;P4|u1T-r3i(rA0s$FhR zJVYoP3cCXIs$W(EIXZO5vbQZ^{s-QqC>W`Eg z?ib|`t@wh0^e&RB#s;0j15JKNC2H|0r^@#4xlF(g00*RK7iMhY7y^;$5cNtP7GhWU zKI82(2GKte&_{H)F{Y1DDiEuHkkB{<6ijEp0vSrxx#YqdT-5-KxOkb7vedVz*O%5< z1X{4mYkjgzSf)eFFkF-hZYonnTcvmos^W~_NvrM}SBvjeD{F3|#{uJ+P*qr+Y3{ky zSi;p94G^l+%z3!ppsN}hlsAoZlr&c3akIC6616E9yn>hEX)cSc%R|ERj8_+a{^K>j z<_?k%zv4TX2ps)4Seu5W2qm15MFL%ehZ5n4UJ*|FOR#4U)0yZ;wCQ-JwnJ7?fKk?6 z2+;8FEK=J!pwI?OO5kKs?O6%iyEGtT@*8giNY)n})c|&D^&pg@C|+qH7hoC&txcf2 zyojBIR$M*xTL+G|f&gG>b($*lOe_wElA5w5(x(DBtIVm=?p{+`jViA5aB87oX>IVg zx-$SMG#KhB=wb3@cslHizSVy@xNBY&%Q7U(s_1GiYkj3dRtmeaqu~h{R+P6#ZysV1 zk3Cs)V_Lv2mrQF4K!SsdfQp`03R#tGFGYcALu4(Wh8R_{2^k&@oCfIGJY_P8SJR;` zr6?CwRIJ9VTLG)B!VHCF4?StDT2K&OCMRGSda%`8(&CM9-QITrp|Eb$4ij;3%H^$D z!W>R;@k>sP>J@v?1$16&e6es8S!>oXUm@W-j(ntJt^6ew)DZ>`~*Z5JljyLy8=$H1g1eCZ{Xxo2BdE0=3B=9|a&LaH zxw6sA`fJi>j7w}b_@NTn-CGTRJXg=!EE>9Bq~k?utj{epKdEb!?kt6^Fs>n3u>c}o zqF1pmSk!V58wJvZV{pDgC&NEuY z?j`N(vkViD$v8ineukvtpz0z_S-+vE7O}c1$Ju~~Rdz;oy7uoAe>EkpjuW!Zvm1jJ zhBG;I8w4pVJT(G39-h@S0|?|X;`dhc7XOg zSdqTtxThC1t6sX}28*yI39^H6PpDmmcxb4H7a7xP{3vwbxyi$Ux!zO~vVc;Gs0c7{ zvkiRnPHkDG${K)9Z7>?LrMNY15s5cc_a#KOSd}CUzFn!cG`;1V+-OW%v@+&{pAl5H zx&kpQ943R2s146OK^9Zi>Rkq{~i)@XW+yHFK z8;L9qwt5*Jgf(g$#$zou&=d_Q7(KdL+-`^3J_L!Gt{H+8fUHIh!WY&tX2Td5Ap3JQ z7BG4XxU1M@7);(M zb$_{==tE!4S91Q)(p12@y5AnsjGnU6UTnF3M?Wz*P3%#F?!P3!W8QE*vo}db9Ub?W z25#$n_xfc6&-!(TC5`O-%fp|Qj~kro-!=8h>$G~&{+(qMSi0r;fQpT()5&)R*>*Fz6?)fC{S46)uW#yjcuYmAmFBK(+%pT39Ycq>Wp4F7!su zIZTz=0Yq%U0($#OFe}u?gOp*XuvFfL-xCis!2bYD&3~C2 zf7{HAJV3gvN?aKb@BEp935!s(TQfVEXmR~Dl<((1-96mHbS zFb4~j+6`_V?c6&~}M?FID+7kL&O=^x280Op%w5b3>jAGyXU42O@KRWG&Y=6Wr87I*$T9) z4>a&9=AeAy1E4GJQ{Y%z2Wh!(%$mz~@DT_#U;$X*c8=#%a(!z4^W3Mx`Yj!MK)R#E zY-AU_M?vBlUQVN#@Zh4@?K2^X(gwsq*484erz`!5i}udF7~GH1RS|5azIhD+EQw0(BY+c}k8$ z2HcrZ+0-$;%zU@9T|Nx=muJMVuNcVmTtuwjqZqF1zGrxW(%N;TU56QNyj;R|S7=|>+{`_Hd4cq=&S1SS zKI8NoAk^f;BP?#E{lcJ&Mn^A5kXE}uM0O~7eL|1$aQXW{Jp>yrC9?uh%By1A-;xm; z#N^WStoudkK}0H89Jj_I6VMEHqP^qabEFolEW}@k_J$o12EzK%`!eHWj-k4`ec%r_ zT#9QO=gr3hr4d%pE#jP4FlwD-gC?oi8K7g(&3IM_-FU6~mr{{J!lEIp=jWZq zM&6-{nLYc*Wa=OCYi z15hQwaGX;a$zc0jQDGb@WK=udAO(s^+7Y=$}mxYuaPwzGhs@{pNkf`?x)L!Df+7q|uy@ zccf(6dC(;}gAM6x-l05xJ!OvSvjhAX+qn3dFk|VoYNFMYCcPnW0KoEh2jrqx9|LA% z4nyBa07|Z(Y*q|c1!pj;;xo0oyIUPiWt-|ma_2j!fxbvXj<0A8loGLOXvNADG+^-r zgL1Ach%$D=U&*_^B75n*R(;sRO5Q8?k6{j4{^Cp#*YK4ph?}yj-VfYDal(&7Uhujw z<)a&h0j78}_JKl$-kE8`=9z8kyvlW%!Nt77QXwM+RlEB_#LUuC#{pO|tg^LP)!$h| zU~&!~{(M7lq*Yz%_H20|5I6;!-1z4RT=DTrec%~D$sV{B_l1Vw$FQNVSexD;IjX_+ zW4G(<=Brp6Ox+n^8ha>t#zyB!R-Fpnk9l&FlfL@Ef!rnYJ*?aOQ|4zZvrC2*W$Hr067r+aeP^Ef5!+NI+_LbEksaF!AFHtLT3^{9u z`i^sBns|T#Bts1zPg4UK$h8%-=tEyvajAtlaa;u8$j$Jt4AvHIwmj_fs0yMR&;A6$k zCc3mk)s-$@jOMOwM}zrU#LV0 zpRYdu0NOQF&4ItF+^9cCBhWv%GLUQ|!nU&V{WIjWzSS}mqmj+aZsD({A5(E?`d(r| z%wY#oWCMl=A`Qn=5tkgt57lZeKV^9K-x}R9?iz*8&j) z5-hRGVI^?^bCw~2#Y3oywb0e->kC=LP%s&E>J5BMF)a{%*qE6cq~m*E>RHWlhe1z< zyUqx!U(rM_OGA+91=fQnmb*f><I;Zs zgH1{Tj2)P$RI^g+AY1fz^d3HmnNr{*2A`tB-ycdE7I6oNf~<<1#FF2uD3v`UnB{@# z5k8Ai&SjJfQ53C5>>rXlevj;z554)ta4Hlvc=v}?jwrg7fO_e(;NoC}?^m=W0XP(U zMN&aCT=wk;A$yk5ZWlqnOvK_SoxHB=Yu$guHLZcZlHj|m^vt_|q2305hrGvMq2^}( z$L4q6)2zGlzccht?tj&OV(qs6oW=hDOXhy{{mC6_OHdxdOH-Fr9q*{| zSPlS!1}Xml@}HwE%73N+?ckNUU*}8l68MxFAVXC!<%tyLTxB|yj*=V}_cI)|SIm5{ zIqZ~bB&z9C?lIg4)cP^+Ru}gw-m-m(Sitme?j^;JT?_e_yAr!ZkU-|u7lv<>BwOv~ zQ^{XLJ*N+7&_KQ9lpS@4F-LF~h!yK^9M#7Z5TcJyqra-uL)HM?wpe1IRGXAg1hH8r zN2&Xh4)6CR9KccXn6-`ZnW#%IR9^3_G1euf-&O!wFKZ91a)fy*p0^8iF3%)=qlSwP zgV)b^KtK3|zFAn5ohG0L`Rfw*^E46Ymp9$ZTl-2Q)l^rt*dP$UUktwVqW*{!^NN$^ zp=wax2gImB)dYKzsHAR|*J}6NJu>F4;>2;($79YJ$iM+bfmkuG#K=~=LLeyTp>pkt zd?c#mhIJCimoy{vUqUWX`wzJd@?8Vh{W8l{i+$40P>Sl}SYf#mFlX;6s6tT>MdgRu z&YHbyQQzKF(O>+UT&z_LN>v`PsrV(+T{=p_#*u)1;)YjJkT4xW!yzc{DaCTq)$UXX zZ|$)e7O=McAI@Jg2(&u6L}C1gM^Fa8xLsRj4u!SVpfu!xxC|{=Jx!8;6GqP%U!=$s ze=$G>^jS-JQQ8fcC~`*;#9T%TTz=%GKnS&AEqaNQPE}_v0vdv+3A3Ki8Y@v-$+2>f zJV3AhWX}DKT+rra_Q4m`_nG7>_!WC?I;iAcU-e-Kx!uT_QGl8)mW)v*6$-Ak{YB_x zEtlIwH!!jf8%s(Q=9LqR*~eYGPPQG>61Ip8>aAU>=z#QjVn**h-1+M~KUIGc$n_*1 z#8GtM2jOu-0qAS^2gE|J>2vJ;;X^l&ubN=G9YCKETnCmuAtH!Yt!FZk`+I5 z+=b)5YvOPQ@FmKl(M+oDAR2(6peU$dV7L^2DwQpJ#7a_G-!WU1X|2rfH|H<`HU9w4 OjeT9ih(rGXU;o*s*XOPP literal 0 HcmV?d00001 diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml new file mode 100644 index 0000000000000..7ed34afa28691 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -0,0 +1,59 @@ + + + + 4.0.0 + + org.openhab.addons.bundles + org.openhab.addons.reactor.bundles + 4.0.0-SNAPSHOT + + + org.openhab.binding.lgthinq + + openHAB Add-ons :: Bundles :: LG Thinq Binding + + !net.sf.ehcache.*,!net.spy.* + + + + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + compile + + + org.apache.httpcomponents + httpclient-osgi + 4.5.13 + compile + + + org.apache.httpcomponents + httpcore-osgi + 4.4.10 + compile + + + com.github.tomakehurst + wiremock-jre8 + 2.32.0 + test + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml new file mode 100644 index 0000000000000..c66d97eb54904 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml @@ -0,0 +1,11 @@ + + + mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + + openhab-runtime-base + openhab.tp-jackson + mvn:org.apache.httpcomponents/httpclient-osgi/4.5.13 + mvn:org.apache.httpcomponents/httpcore-osgi/4.4.10 + mvn:org.openhab.addons.bundles/org.openhab.binding.lgthinq/${project.version} + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java new file mode 100644 index 0000000000000..952949c28f2cf --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -0,0 +1,297 @@ +/** + * 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.lgthinq.internal; + +import java.io.File; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.core.OpenHAB; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; + +/** + * The {@link LGThinQBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQBindingConstants { + + public static final String BINDING_ID = "lgthinq"; + + public static final String CONFIG_DESCRIPTION_URI_CHANNEL = "channel-type:thinq:config"; + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); + public static final String PROPERTY_VENDOR_NAME = "LG Thinq"; + public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, + String.valueOf(DeviceTypes.AIR_CONDITIONER.deviceTypeId())); + public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, "201"); + public static final String WM_CHANNEL_REMOTE_START_GRP_ID = "remote-start-grp"; + public static final String WM_CHANNEL_DASHBOARD_GRP_ID = "dashboard"; + public static final ChannelTypeUID WM_RS_RINSE_CHANNEL_TYPE_UUID = new ChannelTypeUID(BINDING_ID, "washer-rinse"); + public static final ChannelTypeUID WM_RS_SPIN_CHANNEL_TYPE_UUID = new ChannelTypeUID(BINDING_ID, "washer-spin"); + public static final ChannelTypeUID WM_RS_TEMP_LEVEL_CHANNEL_TYPE_UUID = new ChannelTypeUID(BINDING_ID, + "washer-temp-level"); + public static final ChannelTypeUID WM_RS_START_STOP_CHANNEL_TYPE_UUID = new ChannelTypeUID(BINDING_ID, + "rs-washer-start-stop"); + + public static final ChannelGroupTypeUID WM_CHANNEL_GROUP_TYPE_REMOTE_START_UID = new ChannelGroupTypeUID(BINDING_ID, + WM_CHANNEL_REMOTE_START_GRP_ID); + public static final ThingTypeUID THING_TYPE_WASHING_TOWER = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.WASHING_TOWER.deviceTypeId()); + public static final ThingTypeUID THING_TYPE_DRYER = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.DRYER.deviceTypeId()); + + public static final ThingTypeUID THING_TYPE_HEAT_PUMP = new ThingTypeUID(BINDING_ID, + DeviceTypes.HEAT_PUMP.deviceTypeId() + "HP"); + + public static final ThingTypeUID THING_TYPE_DRYER_TOWER = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.DRYER_TOWER.deviceTypeId()); + + public static final ThingTypeUID THING_TYPE_FRIDGE = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.REFRIGERATOR.deviceTypeId()); + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, + THING_TYPE_WASHING_MACHINE, THING_TYPE_WASHING_TOWER, THING_TYPE_DRYER_TOWER, THING_TYPE_DRYER, + THING_TYPE_FRIDGE, THING_TYPE_BRIDGE, THING_TYPE_HEAT_PUMP); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_AIR_CONDITIONER, + THING_TYPE_WASHING_MACHINE, THING_TYPE_WASHING_TOWER, THING_TYPE_DRYER, THING_TYPE_DRYER_TOWER, + THING_TYPE_HEAT_PUMP); + public static final String THING_STATUS_DETAIL_DISCONNECTED = "Device is Disconnected"; + // Max number of retries trying to get the monitor (V1) until consider ERROR in the connection + public static final Integer MAX_GET_MONITOR_RETRIES = 3; + public static String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; + public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; + public static String BASE_CAP_CONFIG_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-cap.json"; + public static final String V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; + public static final String V2_USER_INFO = "/users/profile"; + public static final String V2_API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; + public static final String V2_CLIENT_ID = "65260af7e8e6547b51fdccf930097c51eb9885a508d3fddfa9ee6cdec22ae1bd"; + public static final String V2_SVC_PHASE = "OP"; + public static final String V2_APP_LEVEL = "PRD"; + public static final String V2_APP_OS = "LINUX"; + public static final String V2_APP_TYPE = "NUTS"; + public static final String V2_APP_VER = "3.0.1700"; + public static final String V2_SESSION_LOGIN_PATH = "/emp/v2.0/account/session/"; + public static final String V2_LS_PATH = "/service/application/dashboard"; + public static final String V2_DEVICE_CONFIG_PATH = "service/devices/"; + public static final String V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/%s"; + public static final String V1_START_MON_PATH = "rti/rtiMon"; + public static final String V1_MON_DATA_PATH = "rti/rtiResult"; + public static final String V1_CONTROL_OP = "rti/rtiControl"; + public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; + public static final String GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; + public static final String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; + public static final String PRE_LOGIN_PATH = "/preLogin"; + public static final String SECURITY_KEY = "nuts_securitykey"; + public static final String SVC_CODE = "SVC202"; + public static final String OAUTH_SECRET_KEY = "c053c2a6ddeb7ad97cb0eed0dcb31cf8"; + public static final String OAUTH_CLIENT_KEY = "LGAO722A02"; + public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss +0000"; + public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; + public static final String V2_EMP_SESS_PATH = "/emp/oauth2/token/empsession"; + public static final String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com" + V2_EMP_SESS_PATH; + public static final String API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; + + // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, + // and the build id of the thinq app it can also just be a random + // string, we use the same client id used for oauth + public static final String CLIENT_ID = "LGAO221A02"; + public static final String MESSAGE_ID = "wideq"; + public static final String SVC_PHASE = "OP"; + public static final String APP_LEVEL = "PRD"; + public static final String APP_OS = "ANDROID"; + public static final String APP_TYPE = "NUTS"; + public static final String APP_VER = "3.5.1200"; + + public static final String DEVICE_ID = "device_id"; + public static final String MODEL_NAME = "model_name"; + public static final String DEVICE_ALIAS = "device_alias"; + public static final String MODEL_URL_INFO = "model_url_info"; + public static final String PLATFORM_TYPE = "platform_type"; + public static final String PLATFORM_TYPE_V1 = "thinq1"; + public static final String PLATFORM_TYPE_V2 = "thinq2"; + static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); + + public static final int SEARCH_TIME = 20; + // delay between each device's scan for state changes (in seconds) + public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 10; + + // ====================== FRIDGE DEVICE CONSTANTS ============================= + // CHANNEL IDS + // public static final String CHANNEL_MOD_OP_ID = "op_mode"; + // public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; + // public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; + // public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; + // public static final String CHANNEL_COOL_JET_ID = "cool_jet"; + public static final Double FRIDGE_TEMPERATURE_IGNORE_VALUE = 255.0; + public static final Double FREEZER_TEMPERATURE_IGNORE_VALUE = 255.0; + public static final String CHANNEL_FRIDGE_TEMP_ID = "fridge-temperature"; + public static final String CHANNEL_FREEZER_TEMP_ID = "freezer-temperature"; + public static final String CHANNEL_REF_TEMP_UNIT = "temp-unit"; + public static final String TEMP_UNIT_CELSIUS = "CELSIUS"; + public static final String TEMP_UNIT_FAHRENHEIT = "FAHRENHEIT"; + public static final String TEMP_UNIT_CELSIUS_SYMBOL = "°C"; + public static final String TEMP_UNIT_FAHRENHEIT_SYMBOL = "°F"; + public static final String FRIDGE_TEMP_NODE_NAME_V2 = "fridgeTemp"; + public static final String FRIDGE_TEMP_NODE_NAME_V1 = "TempRefrigerator"; + public static final String REFRIGERATOR_SNAPSHOT_NODE_V2 = "refState"; + + // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= + // CHANNEL IDS + public static final String CHANNEL_MOD_OP_ID = "op_mode"; + public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; + public static final String CHANNEL_POWER_ID = "power"; + public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; + public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; + public static final String CHANNEL_COOL_JET_ID = "cool_jet"; + public static final String CHANNEL_AIR_CLEAN_ID = "air_clean"; + public static final String CHANNEL_AUTO_DRY_ID = "auto_dry"; + public static final String CHANNEL_ENERGY_SAVING_ID = "energy_saving"; + + public static final Map CAP_AC_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", + "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", + "@AC_MAIN_OPERATION_MODE_HEAT_W", "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", + "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", "@AC_MAIN_OPERATION_MODE_AI_W", "AI", + "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", + "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); + + public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( + Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), + Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), Map.entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), + Map.entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), Map.entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Auto"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); + + public static final Map CAP_AC_COOL_JET = Map.of("@COOL_JET", "Cool Jet"); + // ======= RAC MODES + public static final String CAP_AC_AUTODRY = "@AUTODRY"; + public static final String CAP_AC_AUTODRY_NODE = "AutoDry"; + public static final String CAP_AC_ENERGYSAVING = "@ENERGYSAVING"; + public static final String CAP_AC_AIRCLEAN = "@AIRCLEAN"; + // ==================== + public static final String CAP_AC_COMMAND_OFF = "@OFF"; + public static final String CAP_AC_COMMAND_ON = "@ON"; + + public static final String CAP_AC_AIR_CLEAN_COMMAND_ON = "@AC_MAIN_AIRCLEAN_ON_W"; + public static final String CAP_AC_AIR_CLEAN_COMMAND_OFF = "@AC_MAIN_AIRCLEAN_OFF_W"; + + // ====================== WASHING MACHINE CONSTANTS ============================= + public static final String WM_COURSE_NOT_SELECTED_VALUE = "NOT_SELECTED"; + public static final String WM_POWER_OFF_VALUE = "POWEROFF"; + public static final String WM_SNAPSHOT_WASHER_DRYER_NODE_V2 = "washerDryer"; + public static final String WM_CHANNEL_STATE_ID = "state"; + public static final String WM_CHANNEL_PROCESS_STATE_ID = "process-state"; + public static final String WM_CHANNEL_COURSE_ID = "course"; + public static final String DR_CHANNEL_DRY_LEVEL_ID = "dry-level"; + public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; + public static final String WM_CHANNEL_TEMP_LEVEL_ID = "temperature-level"; + public static final String WM_CHANNEL_DOOR_LOCK_ID = "door-lock"; + public static final String DR_CHANNEL_CHILD_LOCK_ID = "child-lock"; + public static final String FR_CHANNEL_DOOR_ID = "some-door-open"; + public static final String WM_CHANNEL_RINSE_ID = "rinse"; + + public static final String WM_CHANNEL_SPIN_ID = "spin"; + + public static final String WM_CHANNEL_REMOTE_START_START_STOP = "rs-start-stop"; + public static final String WM_CHANNEL_REMOTE_START_RINSE = "rs-rinse"; + public static final String WM_CHANNEL_REMOTE_START_TEMP = "rs-temperature-level"; + public static final String WM_CHANNEL_REMOTE_START_SPIN = "rs-spin"; + public static final String WM_CHANNEL_REMOTE_START_ID = "remote-start-flag"; + public static final String WM_CHANNEL_STAND_BY_ID = "stand-by"; + public static final String WM_CHANNEL_REMAIN_TIME_ID = "remain-time"; + public static final String WM_CHANNEL_DELAY_TIME_ID = "delay-time"; + + public static final Map CAP_WDM_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), + Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), + Map.entry("@WM_STATE_RESERVE_W", "Reserved"), Map.entry("@WM_STATE_DETECTING_W", "Detecting"), + Map.entry("@WM_STATE_RUNNING_W", "Running"), Map.entry("@WM_STATE_RINSING_W", "Rinsing"), + Map.entry("@WM_STATE_SPINNING_W", "Spinning"), Map.entry("@WM_STATE_COOLDOWN_W", "Cool Down"), + Map.entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), Map.entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), + Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_END_W", "End"), + Map.entry("@WM_STATE_DRYING_W", "Drying"), Map.entry("@WM_STATE_DEMO_W", "Demonstration"), + Map.entry("@WM_STATE_ADD_DRAIN_W", "Add Drain"), Map.entry("@WM_STATE_LOAD_DISPLAY_W", "Loading Display"), + Map.entry("@WM_STATE_FRESHCARE_W", "Refreshing"), Map.entry("@WM_STATE_ERROR_AUTO_OFF_W", "Error Auto Off"), + Map.entry("@WM_STATE_FROZEN_PREVENT_INITIAL_W", "Frozen Preventing"), + Map.entry("@FROZEN_PREVENT_PAUSE", "Frozen Preventing Paused"), + Map.entry("@FROZEN_PREVENT_RUNNING", "Frozen Preventing Running"), + Map.entry("@AUDIBLE_DIAGNOSIS", "Diagnosing"), Map.entry("@WM_STATE_ERROR_W", "Error")); + + public static final Map CAP_WDM_PROCESS_STATE = Map.ofEntries( + Map.entry("@WM_STATE_DETECTING_W", "Detecting"), Map.entry("@WM_STATE_STEAM_W", "Steam"), + Map.entry("@WM_STATE_DRY_W", "Drying"), Map.entry("@WM_STATE_COOLING_W", "Cooling"), + Map.entry("@WM_STATE_ANTI_CREASE_W", "Anti Creasing"), Map.entry("@WM_STATE_END_W", "End"), + Map.entry("@WM_STATE_POWER_OFF_W", "Power Off"), Map.entry("@WM_STATE_INITIAL_W", "Initializing"), + Map.entry("@WM_STATE_PAUSE_W", "Paused"), Map.entry("@WM_STATE_RESERVE_W", "Reserved"), + Map.entry("@WM_STATE_RUNNING_W", "Running"), Map.entry("@WM_STATE_RINSING_W", "Rising"), + Map.entry("@WM_STATE_SPINNING_W", "@WM_STATE_DRYING_W"), Map.entry("WM_STATE_COOLDOWN_W", "Cool Down"), + Map.entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), Map.entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), + Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_ERROR_W", "Error")); + + public static final Map CAP_DR_DRY_LEVEL = Map.ofEntries( + Map.entry("@WM_DRY24_DRY_LEVEL_IRON_W", "Iron"), Map.entry("@WM_DRY24_DRY_LEVEL_CUPBOARD_W", "Cupboard"), + Map.entry("@WM_DRY24_DRY_LEVEL_EXTRA_W", "Extra")); + + public static final Map CAP_WM_TEMPERATURE = Map.ofEntries( + Map.entry("@WM_TERM_NO_SELECT_W", "Not Selected"), Map.entry("@WM_TITAN2_OPTION_TEMP_20_W", "20"), + Map.entry("@WM_TITAN2_OPTION_TEMP_COLD_W", "Cold"), Map.entry("@WM_TITAN2_OPTION_TEMP_30_W", "30"), + Map.entry("@WM_TITAN2_OPTION_TEMP_40_W", "40"), Map.entry("@WM_TITAN2_OPTION_TEMP_50_W", "50"), + Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_TAP_COLD_W", "Tap Cold"), + Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_COLD_W", "Cold"), + Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_ECO_WARM_W", "Eco Warm"), + Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_WARM_W", "Warm"), + Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_HOT_W", "Hot"), + Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_EXTRA_HOT_W", "Extra Hot")); + + public static final Map CAP_WM_SPIN = Map.ofEntries( + Map.entry("@WM_TERM_NO_SELECT_W", "Not Selected"), Map.entry("@WM_TITAN2_OPTION_SPIN_NO_SPIN_W", "No Spin"), + Map.entry("@WM_TITAN2_OPTION_SPIN_400_W", "400"), Map.entry("@WM_TITAN2_OPTION_SPIN_600_W", "600"), + Map.entry("@WM_TITAN2_OPTION_SPIN_700_W", "700"), Map.entry("@WM_TITAN2_OPTION_SPIN_800_W", "800"), + Map.entry("@WM_TITAN2_OPTION_SPIN_900_W", "900"), Map.entry("@WM_TITAN2_OPTION_SPIN_1000_W", "1000"), + Map.entry("@WM_TITAN2_OPTION_SPIN_1100_W", "1100"), Map.entry("@WM_TITAN2_OPTION_SPIN_1200_W", "1200"), + Map.entry("@WM_TITAN2_OPTION_SPIN_1400_W", "1400"), Map.entry("@WM_TITAN2_OPTION_SPIN_1600_W", "1600"), + Map.entry("@WM_TITAN2_OPTION_SPIN_MAX_W", "Max Spin"), + Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_NO_SPIN_W", "Drain Only"), + Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_LOW_W", "Low"), + Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_MEDIUM_W", "Medium"), + Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_HIGH_W", "High"), + Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_EXTRA_HIGH_W", "Extra High")); + + public static final Map CAP_WM_RINSE = Map.ofEntries( + Map.entry("@WM_TERM_NO_SELECT_W", "Not Selected"), Map.entry("@WM_TITAN2_OPTION_RINSE_NORMAL_W", "Normal"), + Map.entry("@WM_TITAN2_OPTION_RINSE_RINSE+_W", "Plus"), + Map.entry("@WM_TITAN2_OPTION_RINSE_RINSE++_W", "Plus +"), + Map.entry("@WM_TITAN2_OPTION_RINSE_NORMALHOLD_W", "Normal Hold"), + Map.entry("@WM_TITAN2_OPTION_RINSE_RINSE+HOLD_W", "Plus Hold"), + Map.entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_0_W", "Normal"), + Map.entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_1_W", "Plus"), + Map.entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_2_W", "Plus +"), + Map.entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_3_W", "Plus ++")); + + // This is the dictionary os course functions translations for V2 + public static final Map> CAP_WM_DICT_V2 = Map.of("spin", CAP_WM_SPIN, "rinse", + CAP_WM_RINSE, "temp", CAP_WM_TEMPERATURE, "state", CAP_WDM_STATE); + + public static final String WM_COMMAND_REMOTE_START_V2 = "WMStart"; + // ============================================================================== +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java new file mode 100644 index 0000000000000..86538ae3b1fab --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java @@ -0,0 +1,102 @@ +/** + * 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.lgthinq.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinQBridgeConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQBridgeConfiguration { + /** + * Sample configuration parameters. Replace with your own. + */ + public String username = ""; + public String password = ""; + public String country = ""; + public String language = ""; + public String manualCountry = ""; + public String manualLanguage = ""; + public Integer poolingIntervalSec = 0; + public String alternativeServer = ""; + + public LGThinQBridgeConfiguration() { + } + + public LGThinQBridgeConfiguration(String username, String password, String country, String language, + Integer pollingIntervalSec, String alternativeServer) { + this.username = username; + this.password = password; + this.country = country; + this.language = language; + this.poolingIntervalSec = pollingIntervalSec; + this.alternativeServer = alternativeServer; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getCountry() { + if ("--".equals(country)) { + return manualCountry; + } + return country; + } + + public String getLanguage() { + if ("--".equals(language)) { + return manualLanguage; + } + return language; + } + + public Integer getPoolingIntervalSec() { + return poolingIntervalSec; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setCountry(String country) { + this.country = country; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setPoolingIntervalSec(Integer poolingIntervalSec) { + this.poolingIntervalSec = poolingIntervalSec; + } + + public String getAlternativeServer() { + return alternativeServer; + } + + public void setAlternativeServer(String alternativeServer) { + this.alternativeServer = alternativeServer; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java new file mode 100644 index 0000000000000..ed1237dc34c48 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java @@ -0,0 +1,40 @@ +/** + * 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.lgthinq.internal; + +import org.openhab.binding.lgthinq.internal.handler.LGThinQAbstractDeviceHandler; +import org.openhab.core.events.EventPublisher; +import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.thing.type.DynamicStateDescriptionProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link LGThinQAbstractDeviceHandler} is a main interface contract for all LG Thinq things + * + * @author Nemer Daud - Initial contribution + */ +@Component(service = { DynamicStateDescriptionProvider.class, LGThinQDeviceDynStateDescriptionProvider.class }) +public class LGThinQDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { + @Activate + public LGThinQDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // + final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // + final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { + this.eventPublisher = eventPublisher; + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java new file mode 100644 index 0000000000000..fd11e2393626c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -0,0 +1,110 @@ +/** + * 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.handler.*; +import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; +import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseThingHandlerFactory; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerFactory; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.lgthinq") +public class LGThinQHandlerFactory extends BaseThingHandlerFactory { + + private final Logger logger = LoggerFactory.getLogger(LGThinQHandlerFactory.class); + private final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + + @Nullable + @Reference + protected ThinqChannelTypeProvider thinqChannelProvider; + @Nullable + @Reference + protected ThinqChannelGroupTypeProvider thinqChannelGroupProvider; + @Nullable + @Reference + protected ItemChannelLinkRegistry itemChannelLinkRegistry; + + @Override + public boolean supportsThingType(ThingTypeUID thingTypeUID) { + return LGThinQBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + } + + @Override + protected @Nullable ThingHandler createHandler(Thing thing) { + ThingTypeUID thingTypeUID = thing.getThingTypeUID(); + if (THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID) || THING_TYPE_HEAT_PUMP.equals(thingTypeUID)) { + return new LGThinQAirConditionerHandler(thing, stateDescriptionProvider); + } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return new LGThinQBridgeHandler((Bridge) thing); + } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID) || THING_TYPE_WASHING_TOWER.equals(thingTypeUID)) { + return new LGThinQWasherDryerHandler(thing, stateDescriptionProvider, + Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), + Objects.requireNonNull(itemChannelLinkRegistry)); + } else if (THING_TYPE_DRYER.equals(thingTypeUID) || THING_TYPE_DRYER_TOWER.equals(thingTypeUID)) { + return new LGThinQWasherDryerHandler(thing, stateDescriptionProvider, + Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), + Objects.requireNonNull(itemChannelLinkRegistry)); + } else if (THING_TYPE_FRIDGE.equals(thingTypeUID)) { + return new LGThinQFridgeHandler(thing, stateDescriptionProvider); + } + logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); + return null; + } + + @Override + public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, + @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { + if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, null); + } else if (THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID) || THING_TYPE_HEAT_PUMP.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID) || THING_TYPE_WASHING_TOWER.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } else if (THING_TYPE_DRYER.equals(thingTypeUID) || THING_TYPE_DRYER_TOWER.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } else if (THING_TYPE_FRIDGE.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } + return null; + } + + @Activate + public LGThinQHandlerFactory(final @Reference LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + this.stateDescriptionProvider = stateDescriptionProvider; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java new file mode 100644 index 0000000000000..d6a9701229c8b --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java @@ -0,0 +1,52 @@ +/** + * 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.lgthinq.internal.api; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link LGThinqCanonicalModelUtil} class + * + * @author Nemer Daud - Initial contribution + */ +public class LGThinqCanonicalModelUtil { + public static final ObjectMapper mapper = new ObjectMapper(); + public static final String LG_ROOT_TAG_V1 = "lgedmRoot"; + + public static GatewayResult getGatewayResult(String rawJson) throws IOException { + Map map = mapper.readValue(rawJson, new TypeReference<>() { + }); + Map content = (Map) map.get("result"); + String resultCode = (String) map.get("resultCode"); + if (content == null) { + throw new IllegalArgumentException("Enexpected result. Gateway Content Result is null"); + } else if (resultCode == null) { + throw new IllegalArgumentException("Enexpected result. resultCode code is null"); + } + + return new GatewayResult(Objects.requireNonNull(resultCode, "Expected resultCode field in json"), "", + Objects.requireNonNull(content.get("rtiUri"), "Expected rtiUri field in json"), + Objects.requireNonNull(content.get("thinq1Uri"), "Expected thinq1Uri field in json"), + Objects.requireNonNull(content.get("thinq2Uri"), "Expected thinq2Uri field in json"), + Objects.requireNonNull(content.get("empUri"), "Expected empUri field in json"), + Objects.requireNonNull(content.get("empTermsUri"), "Expected empTermsUri field in json"), "", + Objects.requireNonNull(content.get("empSpxUri"), "Expected empSpxUri field in json")); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java new file mode 100644 index 0000000000000..3d5125ec5ec71 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java @@ -0,0 +1,138 @@ +/** + * 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.lgthinq.internal.api; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_EMP_SESS_PATH; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_EMP_SESS_URL; + +import java.io.Serializable; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * The {@link LGThinqGateway} hold informations about the LG Gateway + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqGateway implements Serializable { + private String empBaseUri = ""; + private String loginBaseUri = ""; + private String apiRootV1 = ""; + private String apiRootV2 = ""; + private String authBase = ""; + private String language = ""; + private String country = ""; + private String username = ""; + private String password = ""; + private String alternativeEmpServer = ""; + private int accountVersion; + + public LGThinqGateway() { + } + + public LGThinqGateway(GatewayResult gwResult, String language, String country, String alternativeEmpServer) { + this.apiRootV2 = gwResult.getThinq2Uri(); + this.apiRootV1 = gwResult.getThinq1Uri(); + this.loginBaseUri = gwResult.getEmpSpxUri(); + this.authBase = gwResult.getEmpUri(); + this.empBaseUri = gwResult.getEmpTermsUri(); + this.language = language; + this.country = country; + this.alternativeEmpServer = alternativeEmpServer; + } + + @JsonIgnore + public String getTokenSessionEmpUrl() { + return alternativeEmpServer.isBlank() ? V2_EMP_SESS_URL : alternativeEmpServer + V2_EMP_SESS_PATH; + } + + public String getEmpBaseUri() { + return empBaseUri; + } + + public int getAccountVersion() { + return accountVersion; + } + + public String getApiRootV2() { + return apiRootV2; + } + + public String getAuthBase() { + return authBase; + } + + public String getLanguage() { + return language; + } + + public String getCountry() { + return country; + } + + public String getLoginBaseUri() { + return loginBaseUri; + } + + public String getApiRootV1() { + return apiRootV1; + } + + public void setEmpBaseUri(String empBaseUri) { + this.empBaseUri = empBaseUri; + } + + public void setLoginBaseUri(String loginBaseUri) { + this.loginBaseUri = loginBaseUri; + } + + public void setApiRootV1(String apiRootV1) { + this.apiRootV1 = apiRootV1; + } + + public void setApiRootV2(String apiRootV2) { + this.apiRootV2 = apiRootV2; + } + + public void setAuthBase(String authBase) { + this.authBase = authBase; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java new file mode 100644 index 0000000000000..0aa92c651c96f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java @@ -0,0 +1,408 @@ +/** + * 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.lgthinq.internal.api; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link OauthLgEmpAuthenticator} main service to authenticate against LG Emp Server via Oauth + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class OauthLgEmpAuthenticator { + private static final Logger logger = LoggerFactory.getLogger(OauthLgEmpAuthenticator.class); + private static final OauthLgEmpAuthenticator instance; + private static final Map oauthSearchKeyQueryParams = new LinkedHashMap<>(); + private static final ObjectMapper objectMapper = new ObjectMapper(); + static { + instance = new OauthLgEmpAuthenticator(); + oauthSearchKeyQueryParams.put("key_name", "OAUTH_SECRETKEY"); + oauthSearchKeyQueryParams.put("sever_type", "OP"); + } + + public static OauthLgEmpAuthenticator getInstance() { + return instance; + } + + private OauthLgEmpAuthenticator() { + } + + static class PreLoginResult { + private final String username; + private final String signature; + private final String timestamp; + private final String encryptedPwd; + + public PreLoginResult(String username, String signature, String timestamp, String encryptedPwd) { + this.username = username; + this.signature = signature; + this.timestamp = timestamp; + this.encryptedPwd = encryptedPwd; + } + + public String getUsername() { + return username; + } + + public String getSignature() { + return signature; + } + + public String getTimestamp() { + return timestamp; + } + + public String getEncryptedPwd() { + return encryptedPwd; + } + } + + @NonNullByDefault + static class LoginAccountResult { + private final String userIdType; + private final String userId; + private final String country; + private final String loginSessionId; + + public LoginAccountResult(String userIdType, String userId, String country, String loginSessionId) { + this.userIdType = userIdType; + this.userId = userId; + this.country = country; + this.loginSessionId = loginSessionId; + } + + public String getUserIdType() { + return userIdType; + } + + public String getUserId() { + return userId; + } + + public String getCountry() { + return country; + } + + public String getLoginSessionId() { + return loginSessionId; + } + } + + private Map getGatewayRestHeader(String language, String country) { + return Map.ofEntries(new AbstractMap.SimpleEntry("Accept", "application/json"), + new AbstractMap.SimpleEntry("x-api-key", API_KEY_V2), + new AbstractMap.SimpleEntry("x-country-code", country), + new AbstractMap.SimpleEntry("x-client-id", CLIENT_ID), + new AbstractMap.SimpleEntry("x-language-code", language), + new AbstractMap.SimpleEntry("x-message-id", MESSAGE_ID), + new AbstractMap.SimpleEntry("x-service-code", SVC_CODE), + new AbstractMap.SimpleEntry("x-service-phase", SVC_PHASE), + new AbstractMap.SimpleEntry("x-thinq-app-level", APP_LEVEL), + new AbstractMap.SimpleEntry("x-thinq-app-os", APP_OS), + new AbstractMap.SimpleEntry("x-thinq-app-type", APP_TYPE), + new AbstractMap.SimpleEntry("x-thinq-app-ver", APP_VER)); + } + + private Map getLoginHeader(LGThinqGateway gw) { + Map headers = new HashMap<>(); + headers.put("Connection", "keep-alive"); + headers.put("X-Device-Language-Type", "IETF"); + headers.put("X-Application-Key", "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"); + headers.put("X-Client-App-Key", "LGAO221A02"); + headers.put("X-Lge-Svccode", "SVC709"); + headers.put("X-Device-Type", "M01"); + headers.put("X-Device-Platform", "ADR"); + headers.put("X-Device-Publish-Flag", "Y"); + headers.put("X-Device-Country", gw.getCountry()); + headers.put("X-Device-Language", gw.getLanguage()); + headers.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Accept-Encoding", "gzip, deflate, br"); + headers.put("Accept-Language", "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7"); + headers.put("Accept", "application/json"); + return headers; + } + + public LGThinqGateway discoverGatewayConfiguration(String gwUrl, String language, String country, + String alternativeEmpServer) throws IOException { + Map header = getGatewayRestHeader(language, country); + RestResult result; + result = RestUtils.getCall(gwUrl, header, null); + + if (result.getStatusCode() != 200) { + throw new IllegalStateException( + "Expected HTTP OK return, but received result core:" + result.getJsonResponse()); + } else { + GatewayResult gwResult = LGThinqCanonicalModelUtil.getGatewayResult(result.getJsonResponse()); + ResultCodes resultCode = ResultCodes.fromCode(gwResult.getReturnedCode()); + if (ResultCodes.OK != resultCode) { + throw new IllegalStateException(String.format( + "Result from LGThinq Gateway from Authentication URL was unexpected. ResultCode: %s, with message:%s, Error Description:%s", + gwResult.getReturnedCode(), gwResult.getReturnedMessage(), resultCode.getDescription())); + } + + return new LGThinqGateway(gwResult, language, country, alternativeEmpServer); + } + } + + public PreLoginResult preLoginUser(LGThinqGateway gw, String username, String password) throws IOException { + String encPwd = RestUtils.getPreLoginEncPwd(password); + Map headers = getLoginHeader(gw); + // 1) Doing preLogin -> getting the password key + String preLoginUrl = gw.getLoginBaseUri() + PRE_LOGIN_PATH; + Map formData = Map.of("user_auth2", encPwd, "log_param", String.format( + "login request / user_id : %s / " + "third_party : null / svc_list : SVC202,SVC710 / 3rd_service : ", + username)); + RestResult resp = RestUtils.postCall(preLoginUrl, headers, formData); + if (resp == null) { + logger.error("Error preLogin into account. Null data returned"); + throw new IllegalStateException("Error login into account. Null data returned"); + } else if (resp.getStatusCode() != 200) { + logger.error("Error preLogin into account. The reason is:{}", resp.getJsonResponse()); + throw new IllegalStateException(String.format("Error login into account:%s", resp.getJsonResponse())); + } + + Map preLoginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + logger.debug("encrypted_pw={}, signature={}, tStamp={}", preLoginResult.get("encrypted_pw"), + preLoginResult.get("signature"), preLoginResult.get("tStamp")); + return new PreLoginResult(username, + Objects.requireNonNull(preLoginResult.get("signature"), + "Unexpected login json result. Node 'signature' not found"), + Objects.requireNonNull(preLoginResult.get("tStamp"), + "Unexpected login json result. Node 'signature' not found"), + Objects.requireNonNull(preLoginResult.get("encrypted_pw"), + "Unexpected login json result. Node 'signature' not found")); + } + + public LoginAccountResult loginUser(LGThinqGateway gw, PreLoginResult preLoginResult) throws IOException { + // 2 - Login with username and hashed password + Map headers = getLoginHeader(gw); + headers.put("X-Signature", preLoginResult.getSignature()); + headers.put("X-Timestamp", preLoginResult.getTimestamp()); + Map formData = Map.of("user_auth2", "" + preLoginResult.getEncryptedPwd(), + "password_hash_prameter_flag", "Y", "svc_list", "SVC202,SVC710"); // SVC202=LG SmartHome, SVC710=EMP + // OAuth + String loginUrl = gw.getEmpBaseUri() + V2_SESSION_LOGIN_PATH + + URLEncoder.encode(preLoginResult.getUsername(), StandardCharsets.UTF_8); + RestResult resp = RestUtils.postCall(loginUrl, headers, formData); + if (resp == null) { + logger.error("Error login into account. Null data returned"); + throw new IllegalStateException("Error loggin into acccount. Null data returned"); + } else if (resp.getStatusCode() != 200) { + logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); + throw new IllegalStateException(String.format("Error login into account:%s", resp.getJsonResponse())); + } + Map loginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + Map accountResult = (Map) loginResult.get("account"); + if (accountResult == null) { + throw new IllegalStateException("Error getting account from Login"); + } + return new LoginAccountResult( + Objects.requireNonNull((String) accountResult.get("userIDType"), + "Unexpected account json result. 'userIDType' not found"), + Objects.requireNonNull((String) accountResult.get("userID"), + "Unexpected account json result. 'userID' not found"), + Objects.requireNonNull((String) accountResult.get("country"), + "Unexpected account json result. 'country' not found"), + Objects.requireNonNull((String) accountResult.get("loginSessionID"), + "Unexpected account json result. 'loginSessionID' not found")); + } + + private String getCurrentTimestamp() { + SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT, Locale.US); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.format(new Date()); + } + + public TokenResult getToken(LGThinqGateway gw, LoginAccountResult accountResult) throws IOException { + // 3 - get secret key from emp signature + String empSearchKeyUrl = gw.getLoginBaseUri() + OAUTH_SEARCH_KEY_PATH; + + RestResult resp = RestUtils.getCall(empSearchKeyUrl, null, oauthSearchKeyQueryParams); + if (resp.getStatusCode() != 200) { + logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); + throw new IllegalStateException(String.format("Error loggin into acccount:%s", resp.getJsonResponse())); + } + Map secretResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + @NonNull + String secretKey = Objects.requireNonNull(secretResult.get("returnData"), + "Unexpected json returned. Expected 'returnData' node here"); + logger.debug("Secret found:{}", secretResult.get("returnData")); + + // 4 - get OAuth Token Key from EMP API + Map empData = new LinkedHashMap<>(); + empData.put("account_type", accountResult.getUserIdType()); + empData.put("client_id", CLIENT_ID); + empData.put("country_code", accountResult.getCountry()); + empData.put("username", "" + accountResult.getUserId()); + String timestamp = getCurrentTimestamp(); + + byte[] oauthSig = RestUtils.getTokenSignature(gw.getTokenSessionEmpUrl(), secretKey, empData, timestamp); + + Map oauthEmpHeaders = new LinkedHashMap<>(); + oauthEmpHeaders.put("lgemp-x-app-key", OAUTH_CLIENT_KEY); + oauthEmpHeaders.put("lgemp-x-date", timestamp); + oauthEmpHeaders.put("lgemp-x-session-key", accountResult.getLoginSessionId()); + oauthEmpHeaders.put("lgemp-x-signature", new String(oauthSig)); + oauthEmpHeaders.put("Accept", "application/json"); + oauthEmpHeaders.put("X-Device-Type", "M01"); + oauthEmpHeaders.put("X-Device-Platform", "ADR"); + oauthEmpHeaders.put("Content-Type", "application/x-www-form-urlencoded"); + oauthEmpHeaders.put("Access-Control-Allow-Origin", "*"); + oauthEmpHeaders.put("Accept-Encoding", "gzip, deflate, br"); + oauthEmpHeaders.put("Accept-Language", "en-US,en;q=0.9"); + oauthEmpHeaders.put("User-Agent", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.44"); + logger.debug("===> Localized timestamp used: [{}]", timestamp); + logger.debug("===> signature created: [{}]", new String(oauthSig)); + resp = RestUtils.postCall(gw.getTokenSessionEmpUrl(), oauthEmpHeaders, empData); + return handleTokenResult(resp); + } + + public UserInfo getUserInfo(TokenResult token) throws IOException { + UriBuilder builder = UriBuilder.fromUri(token.getOauthBackendUrl()).path(V2_USER_INFO); + String oauthUrl = builder.build().toURL().toString(); + String timestamp = getCurrentTimestamp(); + byte[] oauthSig = RestUtils.getTokenSignature(oauthUrl, OAUTH_SECRET_KEY, Collections.EMPTY_MAP, timestamp); + Map headers = Map.of("Accept", "application/json", "Authorization", + String.format("Bearer %s", token.getAccessToken()), "X-Lge-Svccode", SVC_CODE, "X-Application-Key", + APPLICATION_KEY, "lgemp-x-app-key", CLIENT_ID, "X-Device-Type", "M01", "X-Device-Platform", "ADR", + "x-lge-oauth-date", timestamp, "x-lge-oauth-signature", new String(oauthSig)); + RestResult resp = RestUtils.getCall(oauthUrl, headers, null); + + return handleAccountInfoResult(resp); + } + + private UserInfo handleAccountInfoResult(RestResult resp) throws IOException { + Map result = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + if (resp.getStatusCode() != 200) { + logger.error("LG API returned error when trying to get user account information. The reason is:{}", + resp.getJsonResponse()); + throw new IllegalStateException( + String.format("LG API returned error when trying to get user account information. The reason is:%s", + resp.getJsonResponse())); + } else if (result.get("account") == null || ((Map) result.get("account")).get("userNo") == null) { + throw new IllegalStateException( + String.format("Error retrieving the account user information from access token")); + } + Map accountInfo = (Map) result.get("account"); + + return new UserInfo( + Objects.requireNonNullElse(accountInfo.get("userNo"), + "Unexpected result. userID must be present in json result"), + Objects.requireNonNull(accountInfo.get("userID"), + "Unexpected result. userID must be present in json result"), + Objects.requireNonNull(accountInfo.get("userIDType"), + "Unexpected result. userIDType must be present in json result"), + Objects.requireNonNullElse(accountInfo.get("displayUserID"), "")); + } + + public TokenResult doRefreshToken(TokenResult currentToken) throws IOException, RefreshTokenException { + UriBuilder builder = UriBuilder.fromUri(currentToken.getOauthBackendUrl()).path(V2_AUTH_PATH); + String oauthUrl = builder.build().toURL().toString(); + String timestamp = getCurrentTimestamp(); + + Map formData = new LinkedHashMap<>(); + formData.put("grant_type", "refresh_token"); + formData.put("refresh_token", currentToken.getRefreshToken()); + + byte[] oauthSig = RestUtils.getTokenSignature(oauthUrl, OAUTH_SECRET_KEY, formData, timestamp); + + Map headers = Map.of("x-lge-appkey", CLIENT_ID, "x-lge-oauth-signature", new String(oauthSig), + "x-lge-oauth-date", timestamp, "Accept", "application/json"); + + RestResult resp = RestUtils.postCall(oauthUrl, headers, formData); + return handleRefreshTokenResult(resp, currentToken); + } + + private TokenResult handleTokenResult(@Nullable RestResult resp) throws IOException { + Map tokenResult; + if (resp == null) { + throw new IllegalStateException("Error getting oauth token. Null data returned"); + } + if (resp.getStatusCode() != 200) { + logger.error("Error getting oauth token. HTTP Status Code is:{}, The reason is:{}", resp.getStatusCode(), + resp.getJsonResponse()); + throw new IllegalStateException(String.format("Error getting oauth token:%s", resp.getJsonResponse())); + } else { + tokenResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + Integer status = (Integer) tokenResult.get("status"); + if ((status != null && !"1".equals("" + status)) || tokenResult.get("expires_in") == null) { + throw new IllegalStateException(String.format("Status error getting token:%s", tokenResult)); + } + } + + return new TokenResult( + Objects.requireNonNull((String) tokenResult.get("access_token"), + "Unexpected result. access_token must be present in json result"), + Objects.requireNonNull((String) tokenResult.get("refresh_token"), + "Unexpected result. refresh_token must be present in json result"), + Integer.parseInt(Objects.requireNonNull((String) tokenResult.get("expires_in"), + "Unexpected result. expires_in must be present in json result")), + new Date(), Objects.requireNonNull((String) tokenResult.get("oauth2_backend_url"), + "Unexpected result. oauth2_backend_url must be present in json result")); + } + + private TokenResult handleRefreshTokenResult(@Nullable RestResult resp, TokenResult currentToken) + throws IOException, RefreshTokenException { + Map tokenResult; + if (resp == null) { + logger.error("Error getting oauth token. Null data returned"); + throw new RefreshTokenException("Error getting oauth token. Null data returned"); + } + if (resp.getStatusCode() != 200) { + logger.error("Error getting oauth token. HTTP Status Code is:{}, The reason is:{}", resp.getStatusCode(), + resp.getJsonResponse()); + throw new RefreshTokenException(String.format("Error getting oauth token:%s", resp.getJsonResponse())); + } else { + tokenResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + if (tokenResult.get("access_token") == null || tokenResult.get("expires_in") == null) { + throw new RefreshTokenException(String.format("Status error get refresh token info:%s", tokenResult)); + } + } + + currentToken.setAccessToken(Objects.requireNonNull(tokenResult.get("access_token"), + "Unexpected error. Access Token must ever been provided by LG API")); + currentToken.setGeneratedTime(new Date()); + currentToken.setExpiresIn(Integer.parseInt(Objects.requireNonNull(tokenResult.get("expires_in"), + "Unexpected error. Access Token must ever been provided by LG API"))); + return currentToken; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java new file mode 100644 index 0000000000000..41aa94301d63e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java @@ -0,0 +1,39 @@ +/** + * 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.lgthinq.internal.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link RestResult} result from rest calls + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class RestResult { + private final String jsonResponse; + private final int resultCode; + + public RestResult(String jsonResponse, int resultCode) { + this.jsonResponse = jsonResponse; + this.resultCode = resultCode; + } + + public String getJsonResponse() { + return jsonResponse; + } + + public int getStatusCode() { + return resultCode; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java new file mode 100644 index 0000000000000..c44be29cb74c2 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java @@ -0,0 +1,191 @@ +/** + * 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.lgthinq.internal.api; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigInteger; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.*; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.ws.rs.core.UriBuilder; + +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.NameValuePair; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClientBuilder; +import org.apache.http.message.BasicNameValuePair; +import org.apache.http.util.EntityUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link RestUtils} rest utilities + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class RestUtils { + private static final Logger logger = LoggerFactory.getLogger(RestUtils.class); + private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; + private static final RequestConfig requestConfig; + static { + int timeout = 5; + requestConfig = RequestConfig.custom().setConnectTimeout(timeout * 1000) + .setConnectionRequestTimeout(timeout * 1000).setSocketTimeout(timeout * 1000).build(); + } + + public static String getPreLoginEncPwd(String pwdToEnc) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + logger.error("Definitively, it is unexpected.", e); + throw new IllegalStateException("Unexpected error. SHA-512 algorithm must exists in JDK distribution", e); + } + digest.reset(); + digest.update(pwdToEnc.getBytes(StandardCharsets.UTF_8)); + + return String.format("%0128x", new BigInteger(1, digest.digest())); + } + + public static byte[] getOauth2Sig(String messageSign, String secret) { + byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8); + SecretKeySpec signingKey = new SecretKeySpec(secretBytes, HMAC_SHA1_ALGORITHM); + + try { + Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); + mac.init(signingKey); + return Base64.getEncoder().encode(mac.doFinal(messageSign.getBytes(StandardCharsets.UTF_8))); + } catch (NoSuchAlgorithmException e) { + logger.error("Unexpected error. SHA1 algorithm must exists in JDK distribution.", e); + throw new IllegalStateException("Unexpected error. SHA1 algorithm must exists in JDK distribution", e); + } catch (InvalidKeyException e) { + logger.error("Unexpected error.", e); + throw new IllegalStateException("Unexpected error.", e); + } + } + + public static byte[] getTokenSignature(String authUrl, String secretKey, Map empData, + String timestamp) { + UriBuilder builder = UriBuilder.fromUri(authUrl); + empData.forEach(builder::queryParam); + + URI reqUri = builder.build(); + String signUrl = (empData.size() > 0) ? reqUri.getPath() + "?" + reqUri.getRawQuery() : reqUri.getPath(); + String messageToSign = String.format("%s\n%s", signUrl, timestamp); + return getOauth2Sig(messageToSign, secretKey); + } + + public static RestResult getCall(String encodedUrl, @Nullable Map headers, + @Nullable Map params) throws IOException { + UriBuilder builder = UriBuilder.fromUri(encodedUrl); + if (params != null) { + params.forEach(builder::queryParam); + } + URI encodedUri = builder.build(); + + try (CloseableHttpClient client = HttpClientBuilder.create().build()) { + HttpGet request = new HttpGet(encodedUri); + if (headers != null) { + headers.forEach(request::setHeader); + } + HttpResponse resp = client.execute(request); + return new RestResult(EntityUtils.toString(resp.getEntity(), "UTF-8"), + resp.getStatusLine().getStatusCode()); + } + } + + @Nullable + public static RestResult postCall(String encodedUrl, Map headers, String jsonData) + throws IOException { + try { + StringEntity entity = new StringEntity(jsonData); + return postCall(encodedUrl, headers, entity); + } catch (UnsupportedEncodingException e) { + logger.error( + "Unexpected error. Character encoding from json informed not supported by this platform. Payload:{}", + jsonData, e); + throw new IllegalStateException( + "Unexpected error. Character encoding from json informed not supported by this platform.", e); + } + } + + @Nullable + public static RestResult postCall(String encodedUrl, Map headers, Map formParams) + throws IOException { + List pairs = new ArrayList<>(); + + formParams.forEach((k, v) -> pairs.add(new BasicNameValuePair(k, v))); + + try { + UrlEncodedFormEntity fe = new UrlEncodedFormEntity(pairs); + return postCall(encodedUrl, headers, fe); + } catch (UnsupportedEncodingException e) { + logger.error( + "Unexpected error. Character encoding received from Form Parameters not supported by this platform. Form Parameters:{}", + pairs, e); + throw new IllegalStateException( + "Unexpected error. Character encoding received from Form Parameters not supported by this platform.", + e); + } + } + + @Nullable + private static RestResult postCall(String encodedUrl, Map headers, HttpEntity entity) + throws IOException { + try (CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build()) { + HttpPost request = new HttpPost(encodedUrl); + headers.forEach(request::setHeader); + request.setEntity(entity); + int hardTimeout = 6000; // milliseconds + TimerTask task = new TimerTask() { + @Override + public void run() { + if (request.expectContinue()) + request.abort(); + } + }; + new Timer(true).schedule(task, hardTimeout); + HttpResponse resp = client.execute(request); + if (request.isAborted()) { + logger.warn("POST to LG API was aborted due to timeout waiting for connection or data"); + } + return new RestResult(EntityUtils.toString(resp.getEntity(), "UTF-8"), + resp.getStatusLine().getStatusCode()); + } catch (java.net.SocketTimeoutException e) { + if (logger.isDebugEnabled()) { + logger.warn("Timeout reading post call result from LG API", e); + } else { + logger.warn("Timeout reading post call result from LG API"); + } + // In SocketTimeout cases I'm considering that I have no response on time. Then, I return null data + // forcing caller to retry. + return null; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java new file mode 100644 index 0000000000000..7b0968b98a25e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java @@ -0,0 +1,168 @@ +/** + * 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.lgthinq.internal.api; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.*; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link TokenManager} Principal facade to manage all token handles + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class TokenManager { + private static final int EXPIRICY_TOLERANCE_SEC = 60; + private final OauthLgEmpAuthenticator oAuthAuthenticator; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final Map tokenCached = new ConcurrentHashMap<>(); + private static final TokenManager instance; + static { + instance = new TokenManager(); + } + + private TokenManager() { + oAuthAuthenticator = OauthLgEmpAuthenticator.getInstance(); + } + + public static TokenManager getInstance() { + return instance; + } + + public boolean isTokenExpired(TokenResult token) { + Calendar c = Calendar.getInstance(); + c.setTime(token.getGeneratedTime()); + c.add(Calendar.SECOND, token.getExpiresIn() - EXPIRICY_TOLERANCE_SEC); + Date expiricyDate = c.getTime(); + return expiricyDate.before(new Date()); + } + + @NonNull + public TokenResult refreshToken(String bridgeName, TokenResult currentToken) throws RefreshTokenException { + try { + TokenResult token = oAuthAuthenticator.doRefreshToken(currentToken); + objectMapper.writeValue(new File(getConfigDataFileName(bridgeName)), token); + return token; + } catch (IOException e) { + throw new RefreshTokenException("Error refreshing LGThinq token", e); + } + } + + private String getConfigDataFileName(String bridgeName) { + return String.format(THINQ_CONNECTION_DATA_FILE, bridgeName); + } + + public boolean isOauthTokenRegistered(String bridgeName) { + File tokenFile = new File(getConfigDataFileName(bridgeName)); + // TODO - check if the file content is valid. + return tokenFile.isFile(); + } + + private String getGatewayUrl(String alternativeGtwServer) { + return alternativeGtwServer.isBlank() ? GATEWAY_URL_V2 : (alternativeGtwServer + GATEWAY_SERVICE_PATH_V2); + } + + public void oauthFirstRegistration(String bridgeName, String language, String country, String username, + String password, String alternativeGtwServer) + throws LGThinqGatewayException, PreLoginException, AccountLoginException, TokenException, IOException { + LGThinqGateway gw; + OauthLgEmpAuthenticator.PreLoginResult preLogin; + OauthLgEmpAuthenticator.LoginAccountResult accountLogin; + TokenResult token; + UserInfo userInfo; + try { + gw = oAuthAuthenticator.discoverGatewayConfiguration(getGatewayUrl(alternativeGtwServer), language, country, + alternativeGtwServer); + } catch (Exception ex) { + throw new LGThinqGatewayException( + "Error trying to discovery the LG Gateway Setting for the region informed", ex); + } + + try { + preLogin = oAuthAuthenticator.preLoginUser(gw, username, password); + } catch (Exception ex) { + throw new PreLoginException("Error doing pre-login of the user in the Emp LG Server", ex); + } + try { + accountLogin = oAuthAuthenticator.loginUser(gw, preLogin); + } catch (Exception ex) { + throw new AccountLoginException("Error doing user's account login on the Emp LG Server", ex); + } + try { + token = oAuthAuthenticator.getToken(gw, accountLogin); + } catch (Exception ex) { + throw new TokenException("Error getting Token", ex); + } + try { + userInfo = oAuthAuthenticator.getUserInfo(token); + token.setUserInfo(userInfo); + token.setGatewayInfo(gw); + } catch (Exception ex) { + throw new TokenException("Error getting UserInfo from Token", ex); + } + + // persist the token information generated in file + objectMapper.writeValue(new File(getConfigDataFileName(bridgeName)), token); + } + + public TokenResult getValidRegisteredToken(String bridgeName) throws IOException, RefreshTokenException { + @NonNull + TokenResult validToken; + TokenResult bridgeToken = tokenCached.get(bridgeName); + if (bridgeToken == null) { + bridgeToken = objectMapper.readValue(new File(getConfigDataFileName(bridgeName)), TokenResult.class); + } + + if (!isValidToken(bridgeToken)) { + throw new RefreshTokenException( + "Token is not valid. Try to delete token file and disable/enable bridge to restart authentication process"); + } else { + tokenCached.put(bridgeName, bridgeToken); + } + + validToken = Objects.requireNonNull(bridgeToken, "Unexpected. Never null here"); + if (isTokenExpired(validToken)) { + validToken = refreshToken(bridgeName, validToken); + } + return validToken; + } + + private boolean isValidToken(@Nullable TokenResult token) { + return token != null && !token.getAccessToken().isBlank() && token.getExpiresIn() != 0 + && !token.getOauthBackendUrl().isBlank() && !token.getRefreshToken().isBlank(); + } + + /** + * Remove the toke file registered for the bridge. Must be called only if the bridge is removed + */ + public void cleanupTokenRegistry(String bridgeName) { + File f = new File(getConfigDataFileName(bridgeName)); + if (f.isFile()) { + f.delete(); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java new file mode 100644 index 0000000000000..13f232af90140 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java @@ -0,0 +1,105 @@ +/** + * 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.lgthinq.internal.api; + +import java.io.Serializable; +import java.util.Date; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link TokenResult} Hold information about token and related entities + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class TokenResult implements Serializable { + private String accessToken = ""; + private String refreshToken = ""; + private int expiresIn; + private Date generatedTime = new Date(); + private String oauthBackendUrl = ""; + private UserInfo userInfo = new UserInfo(); + private LGThinqGateway gatewayInfo = new LGThinqGateway(); + + public TokenResult(String accessToken, String refreshToken, int expiresIn, Date generatedTime, + String ouathBackendUrl) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.expiresIn = expiresIn; + this.generatedTime = generatedTime; + this.oauthBackendUrl = ouathBackendUrl; + } + + // This constructor will never be called by this. It only exists because of ObjectMapper instantiation needs + public TokenResult() { + } + + public LGThinqGateway getGatewayInfo() { + return gatewayInfo; + } + + public void setGatewayInfo(LGThinqGateway gatewayInfo) { + this.gatewayInfo = gatewayInfo; + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public int getExpiresIn() { + return expiresIn; + } + + public Date getGeneratedTime() { + return generatedTime; + } + + public String getOauthBackendUrl() { + return oauthBackendUrl; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + @SuppressWarnings("It is implicitly used by the ObjectMapper instantiator") + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public void setExpiresIn(int expiresIn) { + this.expiresIn = expiresIn; + } + + public void setGeneratedTime(Date generatedTime) { + this.generatedTime = generatedTime; + } + + @SuppressWarnings("It is implicitly used by the ObjectMapper instantiator") + public void setOauthBackendUrl(String ouathBackendUrl) { + this.oauthBackendUrl = ouathBackendUrl; + } + + public UserInfo getUserInfo() { + return userInfo; + } + + public void setUserInfo(UserInfo userInfo) { + this.userInfo = userInfo; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java new file mode 100644 index 0000000000000..788fcf973c8db --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java @@ -0,0 +1,73 @@ +/** + * 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.lgthinq.internal.api; + +import java.io.Serializable; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link UserInfo} User Info (registered in LG Account) + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class UserInfo implements Serializable { + + private String userNumber = ""; + private String userID = ""; + private String userIDType = ""; + private String displayUserID = ""; + + public UserInfo() { + } + + public UserInfo(String userNumber, String userID, String userIDType, String displayUserId) { + this.userNumber = userNumber; + this.userID = userID; + this.userIDType = userIDType; + this.displayUserID = displayUserId; + } + + public String getUserNumber() { + return userNumber; + } + + public void setUserNumber(String userNumber) { + this.userNumber = userNumber; + } + + public String getUserID() { + return userID; + } + + public void setUserID(String userID) { + this.userID = userID; + } + + public String getUserIDType() { + return userIDType; + } + + public void setUserIDType(String userIDType) { + this.userIDType = userIDType; + } + + public String getDisplayUserID() { + return displayUserID; + } + + public void setDisplayUserID(String displayUserID) { + this.displayUserID = displayUserID; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java new file mode 100644 index 0000000000000..d4c4b6265eaa3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java @@ -0,0 +1,71 @@ +/** + * 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.lgthinq.internal.api.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link GatewayResult} class + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class GatewayResult extends HeaderResult { + private final String rtiUri; + private final String thinq1Uri; + private final String thinq2Uri; + private final String empUri; + private final String empTermsUri; + private final String oauthUri; + private final String empSpxUri; + + public GatewayResult(String resultCode, String resultMessage, String rtiUri, String thinq1Uri, String thinq2Uri, + String empUri, String empTermsUri, String oauthUri, String empSpxUri) { + super(resultCode, resultMessage); + this.rtiUri = rtiUri; + this.thinq1Uri = thinq1Uri; + this.thinq2Uri = thinq2Uri; + this.empUri = empUri; + this.empTermsUri = empTermsUri; + this.oauthUri = oauthUri; + this.empSpxUri = empSpxUri; + } + + public String getRtiUri() { + return rtiUri; + } + + public String getEmpTermsUri() { + return empTermsUri; + } + + public String getEmpSpxUri() { + return empSpxUri; + } + + public String getThinq1Uri() { + return thinq1Uri; + } + + public String getThinq2Uri() { + return thinq2Uri; + } + + public String getEmpUri() { + return empUri; + } + + public String getOauthUri() { + return oauthUri; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java new file mode 100644 index 0000000000000..e028e693c1bd8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java @@ -0,0 +1,39 @@ +/** + * 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.lgthinq.internal.api.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link HeaderResult} class + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class HeaderResult { + private final String returnedCode; + private final String returnedMessage; + + public HeaderResult(String returnedCode, String returnedMessage) { + this.returnedCode = returnedCode; + this.returnedMessage = returnedMessage; + } + + public String getReturnedCode() { + return returnedCode; + } + + public String getReturnedMessage() { + return returnedMessage; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java new file mode 100644 index 0000000000000..e14fcc8e714bb --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -0,0 +1,199 @@ +/** + * 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.lgthinq.internal.discovery; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.internal.handler.LGThinQBridgeHandler; +import org.openhab.binding.lgthinq.lgservices.LGThinQAbstractApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.DiscoveryResult; +import org.openhab.core.config.discovery.DiscoveryResultBuilder; +import org.openhab.core.config.discovery.DiscoveryService; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The {@link LGThinqDiscoveryService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService { + + private final Logger logger = LoggerFactory.getLogger(LGThinqDiscoveryService.class); + private @Nullable LGThinQBridgeHandler bridgeHandler; + private @Nullable ThingUID bridgeHandlerUID; + private final LGThinQApiClientService lgApiClientService; + + public LGThinqDiscoveryService() { + super(SUPPORTED_THING_TYPES, SEARCH_TIME); + lgApiClientService = new LGThinQAbstractApiClientService<>(AbstractCapability.class, + AbstractSnapshotDefinition.class) { + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException { + } + + @Override + public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException { + return 0; + } + + @Override + protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, + String controlKey, String command, String keyName, String value) throws Exception { + throw new UnsupportedOperationException("Not to use"); + } + + @Override + protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, + String controlKey, String command, @Nullable String keyName, @Nullable String value, + @Nullable ObjectNode extraNode) throws Exception { + throw new UnsupportedOperationException("Not to use"); + } + + @Override + protected Map handleGenericErrorResult(@Nullable RestResult resp) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not to use"); + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not to use"); + } + }; + } + + @Override + protected void startScan() { + } + + @Override + public void setThingHandler(@Nullable ThingHandler handler) { + if (handler instanceof LGThinQBridgeHandler) { + bridgeHandler = (LGThinQBridgeHandler) handler; + bridgeHandlerUID = handler.getThing().getUID(); + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return bridgeHandler; + } + + @Override + public void activate() { + if (bridgeHandler != null) { + bridgeHandler.registerDiscoveryListener(this); + } + } + + @Override + public void deactivate() { + ThingHandlerService.super.deactivate(); + } + + public void removeLgDeviceDiscovery(LGDevice device) { + try { + ThingUID thingUID = getThingUID(device); + thingRemoved(thingUID); + } catch (LGThinqException e) { + logger.error("Error getting Thing UID"); + } + } + + public void addLgDeviceDiscovery(LGDevice device) { + String modelId = device.getModelName(); + ThingUID thingUID; + ThingTypeUID thingTypeUID; + try { + // load capability to cache and troubleshooting + lgApiClientService.loadDeviceCapability(device.getDeviceId(), device.getModelJsonUri(), false); + thingUID = getThingUID(device); + thingTypeUID = getThingTypeUID(device); + } catch (LGThinqException e) { + logger.debug("Discovered unsupported LG device of type '{}'({}) and model '{}' with id {}", + device.getDeviceType(), device.getDeviceTypeId(), modelId, device.getDeviceId()); + return; + } catch (IOException e) { + logger.error("Error getting device capabilities", e); + return; + } + + Map properties = new HashMap<>(); + properties.put(DEVICE_ID, device.getDeviceId()); + properties.put(DEVICE_ALIAS, device.getAlias()); + properties.put(MODEL_URL_INFO, device.getModelJsonUri()); + properties.put(PLATFORM_TYPE, device.getPlatformType()); + properties.put(PROPERTY_MODEL_ID, modelId); + + DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID) + .withProperties(properties).withBridge(bridgeHandlerUID).withRepresentationProperty(DEVICE_ID) + .withLabel(device.getAlias()).build(); + + thingDiscovered(discoveryResult); + } + + private ThingUID getThingUID(LGDevice device) throws LGThinqException { + ThingTypeUID thingTypeUID = getThingTypeUID(device); + return new ThingUID(thingTypeUID, + Objects.requireNonNull(bridgeHandlerUID, "bridgeHandleUid should never be null here"), + device.getDeviceId()); + } + + private ThingTypeUID getThingTypeUID(LGDevice device) throws LGThinqException { + // Short switch, but is here because it is going to be increase after new LG Devices were added + switch (device.getDeviceType()) { + case AIR_CONDITIONER: + return THING_TYPE_AIR_CONDITIONER; + case HEAT_PUMP: + return THING_TYPE_HEAT_PUMP; + case WASHERDRYER_MACHINE: + return THING_TYPE_WASHING_MACHINE; + case WASHING_TOWER: + return THING_TYPE_WASHING_TOWER; + case DRYER_TOWER: + return THING_TYPE_DRYER_TOWER; + case DRYER: + return THING_TYPE_DRYER; + case REFRIGERATOR: + return THING_TYPE_FRIDGE; + default: + throw new LGThinqException(String.format("device type [%s] not supported", device.getDeviceType())); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java new file mode 100644 index 0000000000000..5199b3e4a1c84 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java @@ -0,0 +1,27 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link AccountLoginException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class AccountLoginException extends LGThinqException { + public AccountLoginException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java new file mode 100644 index 0000000000000..cf3d44f71a61c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java @@ -0,0 +1,31 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqApiException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqApiException extends LGThinqException { + public LGThinqApiException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqApiException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java new file mode 100644 index 0000000000000..bda7a0ae53bda --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java @@ -0,0 +1,31 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqApiExhaustionException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqApiExhaustionException extends LGThinqException { + public LGThinqApiExhaustionException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqApiExhaustionException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java new file mode 100644 index 0000000000000..73a257442ac75 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java @@ -0,0 +1,32 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqDeviceV1MonitorExpiredException} - Normally caught by V1 API in monitoring device. + * After long-running moniotor, it indicates the need to refresh the monitor. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqDeviceV1MonitorExpiredException extends LGThinqException { + public LGThinqDeviceV1MonitorExpiredException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqDeviceV1MonitorExpiredException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java new file mode 100644 index 0000000000000..2becaf759e4bc --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java @@ -0,0 +1,33 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqDeviceV1OfflineException} - Normally caught by V1 API in monitoring device. + * When the device is OFFLINE (away from internet), the API doesn't return data information and this + * exception is thrown to indicate that this device is offline for monitoring + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqDeviceV1OfflineException extends LGThinqApiException { + public LGThinqDeviceV1OfflineException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqDeviceV1OfflineException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java new file mode 100644 index 0000000000000..9592dc76331e4 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java @@ -0,0 +1,31 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqException} Parent Exception for all exceptions of this module + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqException extends Exception { + public LGThinqException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java new file mode 100644 index 0000000000000..605863197f174 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java @@ -0,0 +1,27 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqGatewayException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqGatewayException extends LGThinqException { + public LGThinqGatewayException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java new file mode 100644 index 0000000000000..3c72f60297373 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java @@ -0,0 +1,31 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqUnmarshallException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqUnmarshallException extends LGThinqException { + public LGThinqUnmarshallException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqUnmarshallException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java new file mode 100644 index 0000000000000..9ebe61308c346 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java @@ -0,0 +1,27 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PreLoginException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class PreLoginException extends LGThinqException { + public PreLoginException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java new file mode 100644 index 0000000000000..60fcb3304e701 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java @@ -0,0 +1,31 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PreLoginException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class RefreshTokenException extends LGThinqApiException { + public RefreshTokenException(String message, Throwable cause) { + super(message, cause); + } + + public RefreshTokenException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java new file mode 100644 index 0000000000000..42d3ffcbd7dc1 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java @@ -0,0 +1,27 @@ +/** + * 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PreLoginException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class TokenException extends LGThinqException { + public TokenException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java new file mode 100644 index 0000000000000..f092ceefbdf2f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java @@ -0,0 +1,24 @@ +/** + * 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.lgthinq.internal.errors; + +/** + * The {@link PreLoginException} + * + * @author Nemer Daud - Initial contribution + */ +public class UserInfoException extends LGThinqException { + public UserInfoException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java new file mode 100644 index 0000000000000..fab2fc7564c1f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -0,0 +1,499 @@ +/** + * 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.lang.reflect.ParameterizedType; +import java.util.*; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.*; +import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; +import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.type.*; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQAbstractDeviceHandler} is a main interface contract for all LG Thinq things + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class LGThinQAbstractDeviceHandler + extends BaseThingHandler { + private final Logger logger = LoggerFactory.getLogger(LGThinQAbstractDeviceHandler.class); + protected final String lgPlatformType; + private final Class snapshotClass; + @Nullable + protected C thinQCapability; + private @Nullable Future commandExecutorQueueJob; + private final ExecutorService executorService = Executors.newFixedThreadPool(1); + private @Nullable ScheduledFuture thingStatePollingJob; + private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); + private Integer fetchMonitorRetries = 0; + private boolean monitorV1Began = false; + private String monitorWorkId = ""; + protected final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(30); + private String bridgeId = ""; + private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; + // Bridges status that this thing must top scanning for state change + private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, + ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + ThingStatusDetail.CONFIGURATION_ERROR); + + protected final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + @Nullable + protected ThinqChannelTypeProvider thinqChannelProvider; + @Nullable + protected ThinqChannelGroupTypeProvider thinqChannelGroupProvider; + + public LGThinQAbstractDeviceHandler(Thing thing, + LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing); + this.stateDescriptionProvider = stateDescriptionProvider; + normalizeConfigurationsAndProperties(); + lgPlatformType = "" + thing.getProperties().get(PLATFORM_TYPE); + this.snapshotClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()) + .getActualTypeArguments()[1]; + } + + private void normalizeConfigurationsAndProperties() { + List.of(PLATFORM_TYPE, MODEL_URL_INFO, DEVICE_ID).forEach(p -> { + if (!thing.getProperties().containsKey(p)) { + thing.setProperty(p, (String) thing.getConfiguration().get(p)); + } + }); + } + + protected static class AsyncCommandParams { + final String channelUID; + final Command command; + + public AsyncCommandParams(String channelUUID, Command command) { + this.channelUID = channelUUID; + this.command = command; + } + } + + /** + * Return empty string if null argument is passed + * + * @param value value to test + * @return empty string if null argument is passed + */ + protected final String emptyIfNull(@Nullable String value) { + return Objects.requireNonNullElse(value, ""); + } + + /** + * Return the key informed if there is no correpondent value in map for that key. + * + * @param map map with key/value + * @param key key to search for a value into map + * @return return value related to that key in the map, or the own key if there is no correspondent. + */ + protected final String keyIfValueNotFound(Map map, @NonNull String key) { + return Objects.requireNonNullElse(map.get(key), key); + } + + protected void startCommandExecutorQueueJob() { + if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { + commandExecutorQueueJob = getExecutorService().submit(getQueuedCommandExecutor()); + } + } + + protected void stopCommandExecutorQueueJob() { + if (commandExecutorQueueJob != null) { + commandExecutorQueueJob.cancel(true); + } + } + + protected void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { + if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { + // start the thing polling + startThingStatePolling(); + } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE + && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { + // comunication error is not a specific Bridge error, then we must analise it to give + // this thinq the change to recovery from communication errors + if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR + || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { + // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if + // the bridge is out + stopThingStatePolling(); + } + + } + lastThingStatus = newStatus; + } + + @Override + protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { + handleStatusChanged(newStatus, statusDetail); + super.updateStatus(newStatus, statusDetail, description); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + updateThingStateFromLG(); + } else { + AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); + try { + // Ensure commands are send in a pipe per device. + commandBlockQueue.add(params); + } catch (IllegalStateException ex) { + getLogger().error( + "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, + "Device Command Queue is Busy"); + } + + } + } + + protected ExecutorService getExecutorService() { + return executorService; + } + + public abstract void onDeviceAdded(@NonNullByDefault LGDevice device); + + public String getDeviceId() { + return Objects.requireNonNullElse(getThing().getProperties().get(DEVICE_ID), "undef"); + } + + public abstract String getDeviceAlias(); + + public abstract String getDeviceUriJsonConfig(); + + public abstract void onDeviceRemoved(); + + public abstract void onDeviceDisconnected(); + + public abstract void updateChannelDynStateDescription() throws LGThinqApiException; + + public abstract LGThinQApiClientService getLgThinQAPIClientService(); + + protected void createDynSwitchChannel(String channelName, ChannelUID chanelUuid) { + if (getCallback() == null) { + logger.error("Unexpected behaviour. Callback not ready! Can't create dynamic channels"); + } else { + // dynamic create channel + ChannelBuilder builder = getCallback().createChannelBuilder(chanelUuid, + new ChannelTypeUID(BINDING_ID, channelName)); + Channel channel = builder.withKind(ChannelKind.STATE).withAcceptedItemType("Switch").build(); + updateThing(editThing().withChannel(channel).build()); + } + } + + public C getCapabilities() throws LGThinqApiException { + if (thinQCapability == null) { + thinQCapability = getLgThinQAPIClientService().getCapability(getDeviceId(), getDeviceUriJsonConfig(), + false); + } + return Objects.requireNonNull(thinQCapability, "Unexpected error. Return of capability shouldn't ever be null"); + } + + protected abstract Logger getLogger(); + + protected void initializeThing(@Nullable ThingStatus bridgeStatus) { + getLogger().debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); + String thingId = getThing().getUID().getId(); + Bridge bridge = getBridge(); + + if (!thingId.isBlank()) { + try { + updateChannelDynStateDescription(); + } catch (LGThinqApiException e) { + getLogger().error( + "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values.", + e); + } + if (bridge != null) { + bridgeId = bridge.getUID().getId(); + LGThinQBridgeHandler handler = (LGThinQBridgeHandler) bridge.getHandler(); + // registry this thing to the bridge + if (handler == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } else { + handler.registryListenerThing(this); + if (bridgeStatus == ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error-no-device-id"); + } + // finally, start command queue, regardless of the thing state, as we can still try to send commands without + // property ONLINE (the successful result from command request can put the thing in ONLINE status). + startCommandExecutorQueueJob(); + if (getThing().getStatus() == ThingStatus.ONLINE) { + try { + getLgThinQAPIClientService().initializeDevice(bridgeId, getDeviceId()); + } catch (Exception e) { + if (logger.isDebugEnabled()) { + logger.error( + "Error initializing device {} from bridge {}. Is the device support pre-condition setup ?", + thingId, bridgeId, e); + } else { + logger.error( + "Error initializing device {} from bridge {}. Is the device support pre-condition setup ?", + getDeviceId(), bridgeId); + } + } + // force start state pooling if the device is ONLINE + startThingStatePolling(); + } + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + getLogger().debug("bridgeStatusChanged {}", bridgeStatusInfo); + super.bridgeStatusChanged(bridgeStatusInfo); + // restart scheduler + initializeThing(bridgeStatusInfo.getStatus()); + } + + private class UpdateThingStateFromLG implements Runnable { + @Override + public void run() { + updateThingStateFromLG(); + } + } + + protected void updateThingStateFromLG() { + try { + @Nullable + S shot = getSnapshotDeviceAdapter(getDeviceId(), getCapabilities()); + if (shot == null) { + // no data to update. Maybe, the monitor stopped, then it'a going to be restarted next try. + return; + } + fetchMonitorRetries = 0; + if (!shot.isOnline()) { + if (getThing().getStatus() != ThingStatus.OFFLINE) { + // only update channels if the device has just gone OFFLINE. + updateDeviceChannels(shot); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.device-disconnected"); + onDeviceDisconnected(); + } + } else { + // do not update channels if the device is offline + updateDeviceChannels(shot); + if (getThing().getStatus() != ThingStatus.ONLINE) + updateStatus(ThingStatus.ONLINE); + } + + } catch (LGThinqApiExhaustionException e) { + fetchMonitorRetries++; + getLogger().warn("LG API returns null monitoring data for the thing {}/{}. No data available yet ?", + getDeviceAlias(), getDeviceId()); + if (fetchMonitorRetries > MAX_GET_MONITOR_RETRIES) { + getLogger().error( + "The thing {}/{} reach maximum retries for monitor data. Thing goes OFFLINE until next retry.", + getDeviceAlias(), getDeviceId(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } catch (LGThinqException e) { + getLogger().error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", + getDeviceAlias(), getDeviceId(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } catch (Throwable e) { + getLogger().error( + "System error in pooling thread (UpdateDevice) for device {}/{}. Filtering to do not stop the thread", + getDeviceAlias(), getDeviceId(), e); + } + } + + protected abstract void updateDeviceChannels(S snapshot); + + protected String translateFeatureToItemType(FeatureDataType dataType) { + switch (dataType) { + case UNDEF: + case ENUM: + return "String"; + case RANGE: + return "Dimmer"; + case BOOLEAN: + return "Switch"; + default: + throw new IllegalStateException( + String.format("Feature DataType %s not supported for this ThingHandler", dataType)); + } + } + + protected void stopThingStatePolling() { + if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { + getLogger().debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePollingJob.cancel(true); + } + } + + protected void startThingStatePolling() { + if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { + thingStatePollingJob = pollingScheduler.scheduleWithFixedDelay(new UpdateThingStateFromLG(), 10, + DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); + } + } + + private void stopDeviceV1Monitor(String deviceId) { + try { + monitorV1Began = false; + getLgThinQAPIClientService().stopMonitor(getBridgeId(), deviceId, monitorWorkId); + } catch (LGThinqDeviceV1OfflineException e) { + getLogger().debug("Monitor stopped. Device is unavailable/disconnected", e); + } catch (Exception e) { + getLogger().error("Error stopping LG Device monitor", e); + } + } + + protected String getBridgeId() { + if (bridgeId.isBlank() && getBridge() == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + getLogger().error("Configuration error um Thinq Thing - No Bridge defined for the thing."); + return "UNKNOWN"; + } else if (bridgeId.isBlank() && getBridge() != null) { + bridgeId = getBridge().getUID().getId(); + } + return bridgeId; + } + + abstract protected DeviceTypes getDeviceType(); + + @Nullable + protected S getSnapshotDeviceAdapter(String deviceId, CapabilityDefinition capDef) + throws LGThinqApiException, LGThinqApiExhaustionException { + // analise de platform version + if (PLATFORM_TYPE_V2.equals(lgPlatformType)) { + return getLgThinQAPIClientService().getDeviceData(getBridgeId(), getDeviceId(), getCapabilities()); + } else { + try { + if (!monitorV1Began) { + monitorWorkId = getLgThinQAPIClientService().startMonitor(getBridgeId(), getDeviceId()); + monitorV1Began = true; + } + } catch (LGThinqDeviceV1OfflineException e) { + stopDeviceV1Monitor(deviceId); + try { + S shot = snapshotClass.getDeclaredConstructor().newInstance(); + shot.setOnline(false); + return shot; + } catch (Exception ex) { + getLogger().error("Unexpected error. Can't find default constructor for the Snapshot subclass", ex); + return null; + } + + } catch (Exception e) { + stopDeviceV1Monitor(deviceId); + throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); + } + int retries = 10; + @Nullable + S shot; + try { + while (retries > 0) { + // try to get monitoring data result 3 times. + + shot = getLgThinQAPIClientService().getMonitorData(getBridgeId(), deviceId, monitorWorkId, + getDeviceType(), getCapabilities()); + if (shot != null) { + return shot; + } + Thread.sleep(500); + retries--; + + } + } catch (LGThinqDeviceV1MonitorExpiredException | LGThinqUnmarshallException e) { + getLogger().debug("Monitor for device {} is invalid. Forcing stop and start to next cycle.", deviceId); + return null; + } catch (Exception e) { + // If it can't get monitor handler, then stop monitor and restart the process again in new + // interaction + // Force restart monitoring because of the errors returned (just in case) + throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); + } finally { + stopDeviceV1Monitor(deviceId); + } + throw new LGThinqApiExhaustionException("Exhausted trying to get monitor data for the device:" + deviceId); + } + } + + protected abstract void processCommand(AsyncCommandParams params) throws LGThinqApiException; + + protected Runnable getQueuedCommandExecutor() { + return queuedCommandExecutor; + } + + private final Runnable queuedCommandExecutor = () -> { + while (true) { + AsyncCommandParams params; + try { + params = commandBlockQueue.take(); + } catch (InterruptedException e) { + getLogger().debug("Interrupting async command queue executor."); + return; + } + + try { + processCommand(params); + } catch (LGThinqException e) { + getLogger().error("Error executing Command {} to the channel {}. Thing goes offline until retry", + params.command, params.channelUID, e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + }; + + @Override + public void dispose() { + logger.debug("Disposing Thinq Thing {}", getDeviceId()); + if (thingStatePollingJob != null) { + thingStatePollingJob.cancel(true); + stopThingStatePolling(); + stopCommandExecutorQueueJob(); + thingStatePollingJob = null; + } + } + + protected Channel createDynChannel(String channelName, ChannelUID chanelUuid, String itemType) { + if (getCallback() == null) { + throw new IllegalStateException("Unexpected behaviour. Callback not ready! Can't create dynamic channels"); + } else { + // dynamic create channel + ChannelBuilder builder = getCallback().createChannelBuilder(chanelUuid, + new ChannelTypeUID(BINDING_ID, channelName)); + Channel channel = builder.withKind(ChannelKind.STATE).withAcceptedItemType(itemType).build(); + updateThing(editThing().withChannel(channel).build()); + return channel; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java new file mode 100644 index 0000000000000..85f2dbf264825 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -0,0 +1,298 @@ +/** + * 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ScheduledFuture; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.LGThinQACApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQACApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinQACApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQAirConditionerHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQAirConditionerHandler extends LGThinQAbstractDeviceHandler { + + private final ChannelUID opModeChannelUID; + private final ChannelUID fanSpeedChannelUID; + private final ChannelUID jetModeChannelUID; + private final ChannelUID airCleanChannelUID; + private final ChannelUID autoDryChannelUID; + private final ChannelUID energySavingChannelUID; + + private final Logger logger = LoggerFactory.getLogger(LGThinQAirConditionerHandler.class); + @NonNullByDefault + private final LGThinQACApiClientService lgThinqACApiClientService; + private @Nullable ScheduledFuture thingStatePollingJob; + + public LGThinQAirConditionerHandler(Thing thing, + LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing, stateDescriptionProvider); + lgThinqACApiClientService = lgPlatformType.equals(PLATFORM_TYPE_V1) + ? LGThinQACApiV1ClientServiceImpl.getInstance() + : LGThinQACApiV2ClientServiceImpl.getInstance(); + opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); + fanSpeedChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); + jetModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_COOL_JET_ID); + airCleanChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_AIR_CLEAN_ID); + autoDryChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_AUTO_DRY_ID); + energySavingChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_ENERGY_SAVING_ID); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + protected void updateDeviceChannels(ACCanonicalSnapshot shot) { + + updateState(CHANNEL_POWER_ID, + DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF); + updateState(CHANNEL_MOD_OP_ID, new DecimalType(BigDecimal.valueOf(shot.getOperationMode()))); + updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(BigDecimal.valueOf(shot.getAirWindStrength()))); + updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(BigDecimal.valueOf(shot.getCurrentTemperature()))); + updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(BigDecimal.valueOf(shot.getTargetTemperature()))); + try { + ACCapability acCap = getCapabilities(); + if (getThing().getChannel(jetModeChannelUID) != null) { + Double commandCoolJetOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); + updateState(CHANNEL_COOL_JET_ID, + commandCoolJetOn.equals(shot.getCoolJetMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(airCleanChannelUID) != null) { + Double commandAirCleanOn = Double.valueOf(acCap.getAirCleanModeCommandOn()); + updateState(CHANNEL_AIR_CLEAN_ID, + commandAirCleanOn.equals(shot.getAirCleanMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(energySavingChannelUID) != null) { + Double energySavingOn = Double.valueOf(acCap.getEnergySavingModeCommandOn()); + updateState(CHANNEL_ENERGY_SAVING_ID, + energySavingOn.equals(shot.getEnergySavingMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(autoDryChannelUID) != null) { + Double autoDryOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); + updateState(CHANNEL_AUTO_DRY_ID, + autoDryOn.equals(shot.getAutoDryMode()) ? OnOffType.ON : OnOffType.OFF); + } + + } catch (LGThinqApiException e) { + logger.error("Unexpected Error gettinf ACCapability Capabilities", e); + } catch (NumberFormatException e) { + logger.warn("command value for capability is not numeric.", e); + } + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + ACCapability acCap = getCapabilities(); + if (getThing().getChannel(jetModeChannelUID) == null && acCap.isJetModeAvailable()) { + createDynSwitchChannel(CHANNEL_COOL_JET_ID, jetModeChannelUID); + } + if (getThing().getChannel(autoDryChannelUID) == null && acCap.isAutoDryModeAvailable()) { + createDynSwitchChannel(CHANNEL_AUTO_DRY_ID, autoDryChannelUID); + } + if (getThing().getChannel(airCleanChannelUID) == null && acCap.isAirCleanAvailable()) { + createDynSwitchChannel(CHANNEL_AIR_CLEAN_ID, airCleanChannelUID); + } + if (getThing().getChannel(energySavingChannelUID) == null && acCap.isEnergySavingAvailable()) { + createDynSwitchChannel(CHANNEL_ENERGY_SAVING_ID, energySavingChannelUID); + } + if (getThing().getChannel(fanSpeedChannelUID) == null && !acCap.getFanSpeed().isEmpty()) { + List options = new ArrayList<>(); + acCap.getFanSpeed() + .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); + stateDescriptionProvider.setStateOptions(fanSpeedChannelUID, options); + } + if (isLinked(opModeChannelUID)) { + List options = new ArrayList<>(); + acCap.getOpMode().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_OP_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(opModeChannelUID, options); + } + } + + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqACApiClientService; + } + + @Override + protected Logger getLogger() { + return logger; + } + + protected void stopThingStatePolling() { + if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { + logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePollingJob.cancel(true); + } + } + + protected DeviceTypes getDeviceType() { + if (THING_TYPE_HEAT_PUMP.equals(getThing().getThingTypeUID())) { + return DeviceTypes.HEAT_PUMP; + } else if (THING_TYPE_AIR_CONDITIONER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.AIR_CONDITIONER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for AirConditioner/HeatPump"); + } + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void onDeviceDisconnected() { + // TODO - HANDLE IT, Think if it's needed + } + + protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + switch (params.channelUID) { + case CHANNEL_MOD_OP_ID: { + if (params.command instanceof DecimalType) { + lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); + } + break; + } + case CHANNEL_FAN_SPEED_ID: { + if (command instanceof DecimalType) { + lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); + } + break; + } + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + case CHANNEL_COOL_JET_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnCoolJetMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getCoolJetModeCommandOn() + : getCapabilities().getCoolJetModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in CoolJet Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_AIR_CLEAN_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnAirCleanMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getAirCleanModeCommandOn() + : getCapabilities().getAirCleanModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in AirClean Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_AUTO_DRY_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnAutoDryMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getAutoDryModeCommandOn() + : getCapabilities().getAutoDryModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in AutoDry Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_ENERGY_SAVING_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnEnergySavingMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getEnergySavingModeCommandOn() + : getCapabilities().getEnergySavingModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in EvergySaving Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_TARGET_TEMP_ID: { + double targetTemp; + if (command instanceof DecimalType) { + targetTemp = ((DecimalType) command).doubleValue(); + } else if (command instanceof QuantityType) { + targetTemp = ((QuantityType) command).doubleValue(); + } else { + logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); + break; + } + lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + ACTargetTmp.statusOf(targetTemp)); + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java new file mode 100644 index 0000000000000..2ffa5c7fcbb48 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java @@ -0,0 +1,30 @@ +/** + * 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.lgthinq.internal.handler; + +import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; + +/** + * The {@link LGThinQBridge} + * + * @author Nemer Daud - Initial contribution + */ +public interface LGThinQBridge { + void registerDiscoveryListener(LGThinqDiscoveryService listener); + + void registryListenerThing(LGThinQAbstractDeviceHandler thing); + + void unRegistryListenerThing(LGThinQAbstractDeviceHandler thing); + + LGThinQAbstractDeviceHandler getThingByDeviceId(String deviceId); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java new file mode 100644 index 0000000000000..94a0ad5e9b4d6 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -0,0 +1,344 @@ +/** + * 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQBridgeConfiguration; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.LGThinQACApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQBridgeHandler} + * + * @author Nemer Daud - Initial contribution + */ +public class LGThinQBridgeHandler extends ConfigStatusBridgeHandler implements LGThinQBridge { + + private Map lGDeviceRegister = new ConcurrentHashMap<>(); + private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); + + static { + var logger = LoggerFactory.getLogger(LGThinQBridgeHandler.class); + try { + File directory = new File(THINQ_USER_DATA_FOLDER); + if (!directory.exists()) { + directory.mkdir(); + } + } catch (Exception e) { + logger.warn("Unable to setup thinq userdata directory: {}", e.getMessage()); + } + } + private final Logger logger = LoggerFactory.getLogger(LGThinQBridgeHandler.class); + private LGThinQBridgeConfiguration lgthinqConfig; + private TokenManager tokenManager; + private LGThinqDiscoveryService discoveryService; + private LGThinQApiClientService lgApiClient; + private @Nullable Future initJob; + private @Nullable ScheduledFuture devicePollingJob; + + public LGThinQBridgeHandler(Bridge bridge) { + super(bridge); + tokenManager = TokenManager.getInstance(); + lgApiClient = LGThinQACApiV1ClientServiceImpl.getInstance(); + lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); + } + + final ReentrantLock pollingLock = new ReentrantLock(); + + /** + * Abstract Runnable Polling Class to schedule sincronization status of the Bridge Thing Kinds ! + */ + abstract class PollingRunnable implements Runnable { + protected final String bridgeName; + protected LGThinQBridgeConfiguration lgthinqConfig; + + PollingRunnable(String bridgeName) { + this.bridgeName = bridgeName; + } + + @Override + public void run() { + try { + pollingLock.lock(); + // check if configuration file already exists + if (tokenManager.isOauthTokenRegistered(bridgeName)) { + logger.debug( + "Token authentication process has been already done. Skip first authentication process."); + try { + tokenManager.getValidRegisteredToken(bridgeName); + } catch (IOException e) { + logger.error("Error reading LGThinq TokenFile", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, + "@text/error.toke-file-corrupted"); + return; + } catch (RefreshTokenException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, + "@text/error.toke-refresh"); + return; + } + } else { + try { + tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), + lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword(), + lgthinqConfig.getAlternativeServer()); + tokenManager.getValidRegisteredToken(bridgeName); + logger.debug("Successful getting token from LG API"); + } catch (IOException e) { + logger.debug( + "I/O error accessing json token configuration file. Updating Bridge Status to OFFLINE.", + e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.toke-file-access-error"); + return; + } catch (LGThinqException e) { + logger.debug("Error accessing LG API. Updating Bridge Status to OFFLINE.", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/error.lgapi-communication-error"); + return; + } + } + if (thing.getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } + + try { + doConnectedRun(); + } catch (Exception e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.lgapi-getting-devices"); + } + + } finally { + pollingLock.unlock(); + } + } + + protected abstract void doConnectedRun() throws IOException, LGThinqException; + } + + @Override + public void registerDiscoveryListener(LGThinqDiscoveryService listener) { + if (discoveryService == null) { + discoveryService = listener; + } + } + + /** + * Registry the OSGi services used by this Bridge. + * Eventually, the Discovery Service will be activated with this bridge as argument. + * + * @return Services to be registered to OSGi. + */ + @Override + public Collection> getServices() { + return Collections.singleton(LGThinqDiscoveryService.class); + } + + @Override + public void registryListenerThing(LGThinQAbstractDeviceHandler thing) { + if (lGDeviceRegister.get(thing.getDeviceId()) == null) { + lGDeviceRegister.put(thing.getDeviceId(), thing); + // remove device from discovery list, if exists. + LGDevice device = lastDevicesDiscovered.get(thing.getDeviceId()); + if (device != null) { + discoveryService.removeLgDeviceDiscovery(device); + } + } + } + + @Override + public void unRegistryListenerThing(LGThinQAbstractDeviceHandler thing) { + lGDeviceRegister.remove(thing.getDeviceId()); + } + + @Override + public LGThinQAbstractDeviceHandler getThingByDeviceId(String deviceId) { + return lGDeviceRegister.get(deviceId); + } + + private LGDevicePollingRunnable lgDevicePollingRunnable; + + class LGDevicePollingRunnable extends PollingRunnable { + public LGDevicePollingRunnable(String bridgeName) { + super(bridgeName); + } + + @Override + protected void doConnectedRun() throws LGThinqException { + Map lastDevicesDiscoveredCopy = new HashMap<>(lastDevicesDiscovered); + List devices = lgApiClient.listAccountDevices(bridgeName); + // if not registered yet, and not discovered before, then add to discovery list. + devices.forEach(device -> { + String deviceId = device.getDeviceId(); + if (lGDeviceRegister.get(deviceId) == null && !lastDevicesDiscovered.containsKey(deviceId)) { + logger.debug("Adding new LG Device to things registry with id:{}", deviceId); + if (discoveryService != null) { + discoveryService.addLgDeviceDiscovery(device); + } + } + lastDevicesDiscovered.put(deviceId, device); + lastDevicesDiscoveredCopy.remove(deviceId); + }); + // the rest in lastDevicesDiscoveredCopy is not more registered in LG API. Remove from discovery + lastDevicesDiscoveredCopy.forEach((deviceId, device) -> { + logger.trace("LG Device '{}' removed.", deviceId); + lastDevicesDiscovered.remove(deviceId); + + LGThinQAbstractDeviceHandler deviceThing = lGDeviceRegister.get(deviceId); + if (deviceThing != null) { + deviceThing.onDeviceRemoved(); + } + if (discoveryService != null && deviceThing != null) { + discoveryService.removeLgDeviceDiscovery(device); + } + }); + } + }; + + @Override + public Collection getConfigStatus() { + List resultList = new ArrayList<>(); + if (lgthinqConfig.username.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("USERNAME").withMessageKeySuffix("missing field") + .withArguments("username").build()); + } + if (lgthinqConfig.password.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("PASSWORD").withMessageKeySuffix("missing field") + .withArguments("password").build()); + } + if (lgthinqConfig.language.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("LANGUAGE").withMessageKeySuffix("missing field") + .withArguments("language").build()); + } + if (lgthinqConfig.country.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("COUNTRY").withMessageKeySuffix("missing field") + .withArguments("country").build()); + + } + return resultList; + } + + @Override + public void handleRemoval() { + if (devicePollingJob != null) + devicePollingJob.cancel(true); + tokenManager.cleanupTokenRegistry(getBridge().getUID().getId()); + super.handleRemoval(); + } + + @Override + public void dispose() { + if (devicePollingJob != null) { + devicePollingJob.cancel(true); + devicePollingJob = null; + } + } + + @Override + public T getConfigAs(Class configurationClass) { + return super.getConfigAs(configurationClass); + } + + @Override + public void initialize() { + logger.debug("Initializing LGThinq bridge handler."); + lgthinqConfig = getConfigAs(LGThinQBridgeConfiguration.class); + lgDevicePollingRunnable.lgthinqConfig = lgthinqConfig; + // generateWasherDryerThingTypes(); + if (lgthinqConfig.username.isEmpty() || lgthinqConfig.password.isEmpty() || lgthinqConfig.language.isEmpty() + || lgthinqConfig.country.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.mandotory-fields-missing"); + } else { + updateStatus(ThingStatus.UNKNOWN); + startLGThinqDevicePolling(); + } + } + + // private void generateWasherDryerThingTypes() { + // // TODO - i18n labels and descriptions + // // Creating static channels + // List channels = new ArrayList<>(); + // channels.add(new ThinqChannel()) + // ThinqDevice device = new ThinqDevice(THING_TYPE_WASHING_MACHINE.getId(),"Washer Machine", "LG Thinq Washer + // Machine", + // + // ) + // } + + @Override + public void handleConfigurationUpdate(Map configurationParameters) { + logger.debug("Bridge Configuration was updated. Cleaning the token registry file"); + File f = new File(String.format(THINQ_CONNECTION_DATA_FILE, getThing().getUID().getId())); + if (f.isFile()) { + // file exists. Delete it + if (!f.delete()) { + logger.error("Error deleting file:{}", f.getAbsolutePath()); + } + } + super.handleConfigurationUpdate(configurationParameters); + } + + private void startLGThinqDevicePolling() { + // stop current scheduler, if any + if (devicePollingJob != null && !devicePollingJob.isDone()) { + devicePollingJob.cancel(true); + } + long poolingInterval; + int configPollingInterval = lgthinqConfig.getPoolingIntervalSec(); + // It's not recommended to polling for resources in LG API short intervals to do not enter in BlackList + if (configPollingInterval < 300 && configPollingInterval != 0) { + poolingInterval = TimeUnit.SECONDS.toSeconds(300); + logger.info("Wrong configuration value for pooling interval. Using default value: {}s", poolingInterval); + } else { + if (configPollingInterval == 0) { + logger.info("LG's discovery pooling disabled (configured as zero)"); + return; + } + poolingInterval = configPollingInterval; + } + // submit instantlly and schedule for the next polling interval. + scheduler.submit(lgDevicePollingRunnable); + devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, poolingInterval, poolingInterval, + TimeUnit.SECONDS); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java new file mode 100644 index 0000000000000..8b6e4246ba51a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -0,0 +1,186 @@ +/** + * 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ScheduledFuture; + +import javax.measure.quantity.Temperature; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQFridgeApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQFridgeApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinQFridgeApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.types.Command; +import org.openhab.core.types.State; +import org.openhab.core.types.StateOption; +import org.openhab.core.types.UnDefType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQFridgeHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + * @author Arne Seime - Complementary sensors + */ +@NonNullByDefault +public class LGThinQFridgeHandler extends LGThinQAbstractDeviceHandler { + + private final ChannelUID fridgeTempChannelUID; + private final ChannelUID freezerTempChannelUID; + private String tempUnit = TEMP_UNIT_CELSIUS; + private final Logger logger = LoggerFactory.getLogger(LGThinQFridgeHandler.class); + @NonNullByDefault + private final LGThinQFridgeApiClientService lgThinqFridgeApiClientService; + private @Nullable ScheduledFuture thingStatePollingJob; + + public LGThinQFridgeHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing, stateDescriptionProvider); + lgThinqFridgeApiClientService = lgPlatformType.equals(PLATFORM_TYPE_V1) + ? LGThinQFridgeApiV1ClientServiceImpl.getInstance() + : LGThinQFridgeApiV2ClientServiceImpl.getInstance(); + fridgeTempChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_FRIDGE_TEMP_ID); + freezerTempChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_FREEZER_TEMP_ID); + ChannelUID doorChannelUID = new ChannelUID(getThing().getUID(), FR_CHANNEL_DOOR_ID); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + protected void updateDeviceChannels(FridgeCanonicalSnapshot shot) { + updateState(CHANNEL_FRIDGE_TEMP_ID, new QuantityType(shot.getFridgeStrTemp())); + updateState(CHANNEL_FREEZER_TEMP_ID, new QuantityType(shot.getFreezerStrTemp())); + updateState(FR_CHANNEL_DOOR_ID, parseDoorStatus(shot.getDoorStatus())); + + updateState(CHANNEL_REF_TEMP_UNIT, new StringType(shot.getTempUnit())); + if (!tempUnit.equals(shot.getTempUnit())) { + tempUnit = shot.getTempUnit(); + try { + // force update states after first snapshot fetched to fit changes in temperature unit + updateChannelDynStateDescription(); + } catch (Exception ex) { + logger.error("Error updating dynamic state description", ex); + } + } + } + + private State parseDoorStatus(String doorStatus) { + if ("CLOSE".equals(doorStatus)) { + return OpenClosedType.CLOSED; + } else if ("OPEN".equals(doorStatus)) { + return OpenClosedType.OPEN; + } else { + return UnDefType.UNDEF; + } + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + FridgeCapability refCap = getCapabilities(); + // temperature channels are little different. First we need to get the tempUnit in the first snapshot, + + if (isLinked(fridgeTempChannelUID)) { + updateTemperatureChannel(fridgeTempChannelUID, + TEMP_UNIT_CELSIUS.equals(tempUnit) ? refCap.getFridgeTempCMap() : refCap.getFridgeTempFMap()); + } + if (isLinked(freezerTempChannelUID)) { + updateTemperatureChannel(freezerTempChannelUID, + TEMP_UNIT_CELSIUS.equals(tempUnit) ? refCap.getFreezerTempCMap() : refCap.getFreezerTempFMap()); + } + } + + private void updateTemperatureChannel(ChannelUID tempChannelUID, Map mapOptions) { + List options = new ArrayList<>(); + mapOptions.forEach((value, label) -> options.add(new StateOption(value, label))); + stateDescriptionProvider.setStatePattern(tempChannelUID, + "%.0f " + (TEMP_UNIT_CELSIUS.equals(tempUnit) ? TEMP_UNIT_CELSIUS_SYMBOL + : (TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? TEMP_UNIT_FAHRENHEIT_SYMBOL : "%unit%"))); + stateDescriptionProvider.setStateOptions(tempChannelUID, options); + } + + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqFridgeApiClientService; + } + + @Override + protected Logger getLogger() { + return logger; + } + + protected void stopThingStatePolling() { + if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { + logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePollingJob.cancel(true); + } + } + + protected DeviceTypes getDeviceType() { + return DeviceTypes.AIR_CONDITIONER; + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void onDeviceDisconnected() { + // TODO - HANDLE IT, Think if it's needed + } + + protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + // TODO - Implement commands + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java new file mode 100644 index 0000000000000..ff3c360a05a5a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -0,0 +1,453 @@ +/** + * 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; +import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; +import org.openhab.binding.lgthinq.lgservices.*; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.*; +import org.openhab.core.items.Item; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQWasherDryerHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQWasherDryerHandler + extends LGThinQAbstractDeviceHandler { + + private final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final ChannelUID courseChannelUID; + private final ChannelUID remoteStartStopChannelUID; + private final ChannelUID remainTimeChannelUID; + private final ChannelUID delayTimeChannelUID; + private final ChannelUID spinChannelUID; + private final ChannelUID rinseChannelUID; + private final ChannelUID stateChannelUID; + private final ChannelUID processStateChannelUID; + private final ChannelUID childLockChannelUID; + private final ChannelUID dryLevelChannelUID; + private final ChannelUID temperatureChannelUID; + private final ChannelUID doorLockChannelUID; + private final ChannelUID standByModeChannelUID; + private final ChannelUID remoteStartFlagChannelUID; + + public final ChannelGroupUID channelGroupRemoteStartUID; + public final ChannelGroupUID channelGroupDashboardUID; + + private final List remoteStartEnabledChannels = new CopyOnWriteArrayList<>(); + + private final ItemChannelLinkRegistry itemChannelLinkRegistry; + private final Map> cachedBitKeyDefinitions = new HashMap<>(); + @Nullable + private WasherDryerSnapshot lastShot; + + private final Logger logger = LoggerFactory.getLogger(LGThinQWasherDryerHandler.class); + @NonNullByDefault + private final LGThinQWMApiClientService lgThinqWMApiClientService; + + public LGThinQWasherDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider, + ThinqChannelTypeProvider channelTypeProvider, ThinqChannelGroupTypeProvider channelGroupTypeProvider, + ItemChannelLinkRegistry itemChannelLinkRegistry) { + super(thing, stateDescriptionProvider); + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + this.thinqChannelGroupProvider = channelGroupTypeProvider; + this.thinqChannelProvider = channelTypeProvider; + this.stateDescriptionProvider = stateDescriptionProvider; + lgThinqWMApiClientService = lgPlatformType.equals(PLATFORM_TYPE_V1) + ? LGThinQWMApiV1ClientServiceImpl.getInstance() + : LGThinQWMApiV2ClientServiceImpl.getInstance(); + channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_GRP_ID); + channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_DASHBOARD_GRP_ID); + courseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_COURSE_ID); + dryLevelChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_DRY_LEVEL_ID); + stateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STATE_ID); + processStateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_PROCESS_STATE_ID); + remainTimeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMAIN_TIME_ID); + delayTimeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_DELAY_TIME_ID); + temperatureChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_TEMP_LEVEL_ID); + doorLockChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_DOOR_LOCK_ID); + childLockChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_CHILD_LOCK_ID); + rinseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_RINSE_ID); + spinChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_SPIN_ID); + standByModeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STAND_BY_ID); + remoteStartFlagChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMOTE_START_ID); + remoteStartStopChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_START_START_STOP); + } + + @Override + protected void initializeThing(@Nullable ThingStatus bridgeStatus) { + super.initializeThing(bridgeStatus); + ThingBuilder builder = editThing() + .withoutChannels(this.getThing().getChannelsOfGroup(channelGroupRemoteStartUID.getId())); + updateThing(builder.build()); + remoteStartEnabledChannels.clear(); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing. Washer/Dryer Thing v3.4.3"); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + WasherDryerCapability wmCap = getCapabilities(); + + List options = new ArrayList<>(); + wmCap.getStateFeat().getValuesMapping() + .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_STATE, v)))); + stateDescriptionProvider.setStateOptions(stateChannelUID, options); + + List optionsCourses = new ArrayList<>(); + wmCap.getCourses().forEach((k, v) -> optionsCourses.add(new StateOption(k, emptyIfNull(v.getCourseName())))); + stateDescriptionProvider.setStateOptions(courseChannelUID, optionsCourses); + + List optionsTemp = new ArrayList<>(); + wmCap.getTemperatureFeat().getValuesMapping() + .forEach((k, v) -> optionsTemp.add(new StateOption(k, keyIfValueNotFound(CAP_WM_TEMPERATURE, v)))); + stateDescriptionProvider.setStateOptions(temperatureChannelUID, optionsTemp); + + List optionsDoor = new ArrayList<>(); + optionsDoor.add(new StateOption("0", "Unlocked")); + optionsDoor.add(new StateOption("1", "Locked")); + stateDescriptionProvider.setStateOptions(doorLockChannelUID, optionsDoor); + + List optionsSpin = new ArrayList<>(); + wmCap.getSpinFeat().getValuesMapping() + .forEach((k, v) -> optionsSpin.add(new StateOption(k, keyIfValueNotFound(CAP_WM_SPIN, v)))); + stateDescriptionProvider.setStateOptions(spinChannelUID, optionsSpin); + + List optionsRinse = new ArrayList<>(); + wmCap.getRinseFeat().getValuesMapping() + .forEach((k, v) -> optionsRinse.add(new StateOption(k, keyIfValueNotFound(CAP_WM_RINSE, v)))); + stateDescriptionProvider.setStateOptions(rinseChannelUID, optionsRinse); + + List optionsPre = new ArrayList<>(); + wmCap.getProcessState().getValuesMapping() + .forEach((k, v) -> optionsPre.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_PROCESS_STATE, v)))); + stateDescriptionProvider.setStateOptions(processStateChannelUID, optionsPre); + + List optionsChildLock = new ArrayList<>(); + optionsChildLock.add(new StateOption("CHILDLOCK_OFF", "Unlocked")); + optionsChildLock.add(new StateOption("CHILDLOCK_ON", "Locked")); + stateDescriptionProvider.setStateOptions(childLockChannelUID, optionsChildLock); + + List optionsDryLevel = new ArrayList<>(); + wmCap.getDryLevel().getValuesMapping() + .forEach((k, v) -> optionsDryLevel.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); + stateDescriptionProvider.setStateOptions(dryLevelChannelUID, optionsDryLevel); + } + + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqWMApiClientService; + } + + @Override + protected Logger getLogger() { + return logger; + } + + private ZonedDateTime getZonedDateTime(String minutesAndSeconds) { + if (minutesAndSeconds.length() != 5) { + logger.error("Washer/Disher remain/delay time is not in standard MM:SS. Value received: {}. Reset to 00:00", + minutesAndSeconds); + minutesAndSeconds = "00:00"; + } + String min = minutesAndSeconds.substring(0, 2); + String sec = minutesAndSeconds.substring(3); + + return ZonedDateTime.of(1970, 1, 1, 0, Integer.parseInt(min), Integer.parseInt(sec), 0, ZoneId.systemDefault()); + } + + @Nullable + private String getItemLinkedValue(ChannelUID channelUID) { + Set items = itemChannelLinkRegistry.getLinkedItems(channelUID); + if (items.size() > 0) { + for (Item i : items) { + return i.getState().toString(); + } + } + return null; + } + + @Override + protected void updateDeviceChannels(WasherDryerSnapshot shot) { + lastShot = shot; + updateState("dashboard#" + CHANNEL_POWER_ID, + (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); + updateState(stateChannelUID, new StringType(shot.getState())); + updateState(processStateChannelUID, new StringType(shot.getProcessState())); + updateState(dryLevelChannelUID, new StringType(shot.getDryLevel())); + updateState(childLockChannelUID, new StringType(shot.getChildLock())); + updateState(courseChannelUID, new StringType(shot.getCourse())); + updateState(temperatureChannelUID, new StringType(shot.getTemperatureLevel())); + updateState(doorLockChannelUID, new StringType(shot.getDoorLock())); + updateState(remainTimeChannelUID, new StringType(shot.getRemainingTime())); + updateState(delayTimeChannelUID, new StringType(shot.getReserveTime())); + updateState(standByModeChannelUID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); + updateState(remoteStartFlagChannelUID, shot.isRemoteStartEnabled() ? OnOffType.ON : OnOffType.OFF); + updateState(spinChannelUID, new StringType(shot.getSpin())); + updateState(rinseChannelUID, new StringType(shot.getRinse())); + Channel rsStartStopChannel = getThing().getChannel(remoteStartStopChannelUID); + final List dynChannels = new ArrayList<>(); + // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. + if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { + ThingHandlerCallback callback = getCallback(); + if (rsStartStopChannel == null && callback != null) { + // === creating channel LaunchRemote + dynChannels + .add(createDynChannel(WM_CHANNEL_REMOTE_START_START_STOP, remoteStartStopChannelUID, "Switch")); + // Just enabled remote start. Then is Off + updateState(remoteStartStopChannelUID, OnOffType.OFF); + // === creating selectable channels for the Course (if any) + try { + WasherDryerCapability cap = getCapabilities(); + CourseDefinition courseDef = cap.getCourses().get(shot.getCourse()); + if (WM_COURSE_NOT_SELECTED_VALUE.equals(shot.getSmartCourse()) && courseDef != null) { + // only create selectable channels if the course is not a smart course. Smart courses have + // already predefined + // the functions values + for (CourseFunction f : courseDef.getFunctions()) { + if (!f.isSelectable()) { + // only for selectable features + continue; + } + // handle well know dynamic fields + FeatureDefinition fd = cap.getFeatureDefinition(f.getValue()); + ChannelUID targetChannel = null; + ChannelUID refChannel = null; + if (!FeatureDefinition.NULL_DEFINITION.equals(fd)) { + targetChannel = new ChannelUID(channelGroupRemoteStartUID, fd.getChannelId()); + refChannel = new ChannelUID(channelGroupDashboardUID, fd.getRefChannelId()); + dynChannels.add(createDynChannel(fd.getChannelId(), targetChannel, + translateFeatureToItemType(fd.getDataType()))); + if (CAP_WM_DICT_V2.containsKey(f.getValue())) { + // if the function has translation dictionary (I hope so), then the values in + // the selectable channel will be translated to something more readable + List options = new ArrayList<>(); + for (String v : f.getSelectableValues()) { + Map values = CAP_WM_DICT_V2.get(f.getValue()); + if (values != null) { + // Canonical Value is the KEY (@...) that represents a constant in the + // definition + // that can be translated to a human description + String canonicalValue = Objects + .requireNonNullElse(fd.getValuesMapping().get(v), v); + options.add(new StateOption(v, keyIfValueNotFound(values, canonicalValue))); + stateDescriptionProvider.setStateOptions(targetChannel, options); + } + } + } + // update state with the default referenced channel + updateState(targetChannel, new StringType(getItemLinkedValue(refChannel))); + } + } + } + } catch (LGThinqApiException e) { + throw new RuntimeException(e); + } + remoteStartEnabledChannels.addAll(dynChannels); + + } + if (isLinked(remoteStartStopChannelUID)) { + updateState(WM_CHANNEL_REMOTE_START_START_STOP, new StringType("")); + } + } else if (remoteStartEnabledChannels.size() > 0) { + ThingBuilder builder = editThing().withoutChannels(remoteStartEnabledChannels); + updateThing(builder.build()); + remoteStartEnabledChannels.clear(); + } + } + + @Override + protected DeviceTypes getDeviceType() { + if (THING_TYPE_WASHING_MACHINE.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHERDRYER_MACHINE; + } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHING_TOWER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); + } + } + + private Map getRemoteStartData() throws LGThinqApiException { + if (lastShot == null) { + return Collections.EMPTY_MAP; + } + WasherDryerCapability cap = getCapabilities(); + Map rawData = lastShot.getRawData(); + Map data = new HashMap<>(); + CommandDefinition cmd = cap.getCommandsDefinition().get(cap.getCommandRemoteStart()); + if (cmd == null) { + logger.error("Command for Remote Start not found in the Washer descriptor. It's most likely a bug"); + return Collections.EMPTY_MAP; + } + Map cmdData = cmd.getData(); + cmdData.forEach((k, v) -> { + data.put(k, rawData.getOrDefault(k, v)); + }); + String course = lastShot.getCourse(); + String smartCourse = lastShot.getSmartCourse(); + data.put(cap.getDefaultCourseFieldName(), course); + data.put(cap.getDefaultSmartCourseFeatName(), smartCourse); + CourseType courseType = cap.getCourses().get("NOT_SELECTED".equals(smartCourse) ? course : smartCourse) + .getCourseType(); + data.put("courseType", courseType.getValue()); + for (Channel c : remoteStartEnabledChannels) { + String value = Objects.requireNonNullElse(getItemLinkedValue(c.getUID()), ""); + String simpleChannelUID = getSimpleChannelUID(c.getUID().getId()); + switch (simpleChannelUID) { + case WM_CHANNEL_REMOTE_START_RINSE: + data.put(cap.getRinseFeat().getName(), value); + break; + case WM_CHANNEL_REMOTE_START_TEMP: + data.put(cap.getTemperatureFeat().getName(), value); + break; + case WM_CHANNEL_REMOTE_START_SPIN: + data.put(cap.getSpinFeat().getName(), value); + break; + default: + logger.warn("channel [{}] not mapped for this binding. It most likely a bug.", simpleChannelUID); + } + } + + return data; + } + + @Override + protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + String simpleChannelUID; + simpleChannelUID = getSimpleChannelUID(params.channelUID); + switch (simpleChannelUID) { + case WM_CHANNEL_REMOTE_START_START_STOP: { + if (command instanceof OnOffType) { + if (OnOffType.ON.equals(command)) { + if (lastShot != null && !lastShot.isStandBy()) { + lgThinqWMApiClientService.remoteStart(getBridgeId(), getCapabilities(), getDeviceId(), + getRemoteStartData()); + } else if (lastShot != null && lastShot.isStandBy()) { + logger.warn( + "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); + } + } else { + logger.warn("Command Remote Start OFF not implemented yet"); + } + } else { + logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); + } + break; + } + case WM_CHANNEL_STAND_BY_ID: { + if (command instanceof OnOffType) { + if (lastShot == null || !lastShot.isStandBy()) { + logger.warn( + "Command {} was sent to StandBy channel, but the state of the WM is unknown or already waked up. Ignoring", + command); + break; + } + lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); + } else { + logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } + + /** + * Returns the simple channel UID name, i.e., without group. + * + * @param uid Full UID name + * @return simple channel UID name, i.e., without group. + */ + private static String getSimpleChannelUID(String uid) { + String simpleChannelUID; + if (uid.indexOf("#") > 0) { + // I have to remove the channelGroup from de channelUID + simpleChannelUID = uid.split("#")[1]; + } else { + simpleChannelUID = uid; + } + return simpleChannelUID; + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + /** + * Put the channels in default state if the device is disconnected or gone. + */ + @Override + public void onDeviceDisconnected() { + updateState(CHANNEL_POWER_ID, OnOffType.OFF); + updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); + updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); + updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java new file mode 100644 index 0000000000000..0212268a464e3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java @@ -0,0 +1,56 @@ +/** + * 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.lgthinq.internal.model; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.types.StateOption; + +/** + * The {@link DataType} class. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DataType { + private final String name; + private final boolean isNumeric; + private final boolean isEnum; + @Nullable + private final List options; + + public DataType(String name, boolean isNumeric, boolean isEnum, @Nullable List options) { + this.name = name; + this.isNumeric = isNumeric; + this.isEnum = isEnum; + this.options = options; + } + + public @Nullable List getOptions() { + return options; + } + + public String getName() { + return name; + } + + public boolean isNumeric() { + return isNumeric; + } + + public boolean isEnum() { + return isEnum; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java new file mode 100644 index 0000000000000..b7acaac796966 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java @@ -0,0 +1,86 @@ +/** + * 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.lgthinq.internal.model; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.core.ConfigDescriptionParameter.Type; +import org.openhab.core.config.core.ParameterOption; + +/** + * The {@link DeviceParameter} class. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DeviceParameter { + + private final String name; + private final Type type; + private final String label; + private final String description; + private final String defaultValue; + @Nullable + private final List options; + + private final boolean isReadOnly; + @Nullable + DeviceParameterGroup group; + + public DeviceParameter(String name, Type type, String label, String description, String defaultValue, + @Nullable List options, boolean isReadOnly) { + this.name = name; + this.type = type; + this.label = label; + this.description = description; + this.defaultValue = defaultValue; + this.options = options; + this.isReadOnly = isReadOnly; + } + + @Nullable + public List getOptions() { + return options; + } + + @Nullable + public DeviceParameterGroup getGroup() { + return group; + } + + public boolean isReadOnly() { + return isReadOnly; + } + + public String getName() { + return name; + } + + public Type getType() { + return type; + } + + public String getLabel() { + return label; + } + + public String getDescription() { + return description; + } + + public String getDefaultValue() { + return defaultValue; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java new file mode 100644 index 0000000000000..0addfd9b9cb4e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java @@ -0,0 +1,40 @@ +/** + * 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.lgthinq.internal.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link DeviceParameterGroup} class. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DeviceParameterGroup { + + private final String groupName; + private final String groupLabel; + + public DeviceParameterGroup(String groupName, String groupLabel) { + this.groupName = groupName; + this.groupLabel = groupLabel; + } + + public String getGroupName() { + return groupName; + } + + public String getGroupLabel() { + return groupLabel; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java new file mode 100644 index 0000000000000..14cc669161ab2 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java @@ -0,0 +1,113 @@ +/** + * 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.lgthinq.internal.model; + +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link ThinqChannel} class. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ThinqChannel { + + @Nullable + ThinqDevice device; + private final DataType type; + @Nullable + private final String unitDisplayPattern; + private final String name; + private final String label; + private final String description; + private final boolean isDynamic; + private final boolean isReadOnly; + private final boolean isAdvanced; + @Nullable + private final ThinqChannelGroup channelGroup; + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ThinqChannel that = (ThinqChannel) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + public ThinqChannel(DataType type, @Nullable String unitDisplayPattern, String name, String label, + String description, boolean isDynamic, boolean isReadOnly, boolean isAdvanced, + @Nullable ThinqChannelGroup channelGroup) { + this.type = type; + this.unitDisplayPattern = unitDisplayPattern; + this.name = name; + this.label = label; + this.description = description; + this.isDynamic = isDynamic; + this.isReadOnly = isReadOnly; + this.isAdvanced = isAdvanced; + this.channelGroup = channelGroup; + if (channelGroup != null && !channelGroup.getChannels().contains(this)) { + channelGroup.getChannels().add(this); + } + } + + public @Nullable ThinqChannelGroup getChannelGroup() { + return channelGroup; + } + + public boolean isAdvanced() { + return isAdvanced; + } + + public String getLabel() { + return label; + } + + public boolean isReadOnly() { + return isReadOnly; + } + + public @Nullable String getUnitDisplayPattern() { + return unitDisplayPattern; + } + + public @Nullable ThinqDevice getDevice() { + return device; + } + + public DataType getType() { + return type; + } + + public boolean isDynamic() { + return isDynamic; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java new file mode 100644 index 0000000000000..253816cfdcfe1 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java @@ -0,0 +1,81 @@ +/** + * 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.lgthinq.internal.model; + +import java.util.List; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * The {@link ThinqChannelGroup} class. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ThinqChannelGroup { + private final List channels; + private ThinqDevice device; + private final String name; + private final String description; + private final String label; + + public ThinqChannelGroup(List channels, ThinqDevice device, String name, String description, + String label) { + this.channels = channels; + this.device = device; + this.name = name; + this.description = description; + this.label = label; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + ThinqChannelGroup that = (ThinqChannelGroup) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + + public String getLabel() { + return label; + } + + public List getChannels() { + return channels; + } + + public ThinqDevice getDevice() { + return device; + } + + public void setDevice(ThinqDevice device) { + this.device = device; + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java new file mode 100644 index 0000000000000..1a02ddec4fc76 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java @@ -0,0 +1,73 @@ +/** + * 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.lgthinq.internal.model; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ThinqDevice} class. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ThinqDevice { + private final String type; + private final String label; + private final String description; + + private final List channels; + private final List configParameter; + private final List groups; + + public String getType() { + return type; + } + + public String getDescription() { + return description; + } + + public List getChannels() { + return channels; + } + + public List getGroups() { + return groups; + } + + public ThinqDevice(String type, String label, String description, List channels, + List configParameter, List groups) { + this.type = type; + this.label = label; + this.description = description; + this.channels = channels; + this.configParameter = configParameter; + this.groups = groups; + this.channels.forEach(c -> { + c.device = this; + }); + this.groups.forEach(g -> { + g.setDevice(this); + }); + } + + public String getLabel() { + return label; + } + + public List getConfigParameter() { + return configParameter; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java new file mode 100644 index 0000000000000..04a38f1c32b29 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java @@ -0,0 +1,269 @@ +/** + * 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.lgthinq.internal.type; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.core.thing.DefaultSystemChannelTypeProvider.SYSTEM_POWER; + +import java.math.BigDecimal; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.model.*; +import org.openhab.core.config.core.*; +import org.openhab.core.library.CoreItemFactory; +import org.openhab.core.thing.*; +import org.openhab.core.thing.type.*; +import org.openhab.core.types.StateDescriptionFragmentBuilder; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link ThingModelTypeUtils} class. + * + * @author Nemer Daud - Initial contribution + */ +@Component +public class ThingModelTypeUtils { + private static final Logger logger = LoggerFactory.getLogger(ThingModelTypeUtils.class); + + private ThinqThingTypeProvider thingTypeProvider; + private ThinqChannelTypeProvider channelTypeProvider; + private ThinqChannelGroupTypeProvider channelGroupTypeProvider; + private ThinqConfigDescriptionProvider configDescriptionProvider; + private LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + + @Reference + public void setThingTypeProvider(ThinqThingTypeProvider thingTypeProvider) { + this.thingTypeProvider = thingTypeProvider; + } + + @Reference + public void setChannelTypeProvider(ThinqChannelTypeProvider channelTypeProvider) { + this.channelTypeProvider = channelTypeProvider; + } + + @Reference + public void setChannelGroupTypeProvider(ThinqChannelGroupTypeProvider channelGroupTypeProvider) { + this.channelGroupTypeProvider = channelGroupTypeProvider; + } + + @Reference + public void setConfigDescriptionProvider(ThinqConfigDescriptionProvider configDescriptionProvider) { + this.configDescriptionProvider = configDescriptionProvider; + } + + @Reference + public void setStateDescriptionProvider(LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + this.stateDescriptionProvider = stateDescriptionProvider; + } + + protected ChannelTypeUID createDynTypeChannel(final String channelTypeId, final String channelLabel, + final String itemType, final Boolean readOnly) { + final StateDescriptionFragmentBuilder sdb = StateDescriptionFragmentBuilder.create(); + final ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, channelTypeId + "-Type"); + String normLabel = channelLabel.replace(" ", ""); + final ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID, normLabel, itemType) + .withStateDescriptionFragment(sdb.withReadOnly(readOnly).build()) + .withConfigDescriptionURI(URI.create(String.format("channel-type:lgthinq:%s-type", normLabel))).build(); + channelTypeProvider.addChannelType(channelType); + return channelTypeUID; + } + + @NonNull + private URI getConfigDescriptionURI(ThinqDevice device) { + try { + return new URI(String.format("%s:%s", "thing-type", UidUtils.generateThingTypeUID(device))); + } catch (URISyntaxException ex) { + String msg = String.format("Can't create configDescriptionURI for device type %s", device.getType()); + throw new IllegalStateException(msg, ex); + } + } + + protected ChannelGroupTypeUID createAndRegistryGroupTypeChannel(final ThinqChannelGroup channelGroup, + final List channelDefinitions) { + ChannelGroupTypeUID groupTypeUID = UidUtils.generateChannelGroupTypeUID(channelGroup); + ChannelGroupType groupType = channelGroupTypeProvider.getChannelGroupType(groupTypeUID, Locale.getDefault()); + if (groupType == null) { + + groupType = ChannelGroupTypeBuilder.instance(groupTypeUID, channelGroup.getLabel()) + .withChannelDefinitions(channelDefinitions).build(); + channelGroupTypeProvider.addChannelGroupType(groupType); + } + + return groupTypeUID; + } + + public void generate(ThinqDevice device) { + if (thingTypeProvider != null) { + ThingTypeUID thingTypeUID = UidUtils.generateThingTypeUID(device); + ThingType thingType = thingTypeProvider.getThingType(thingTypeUID, Locale.getDefault()); + if (thingType == null) { + HashMap> groupChannelsMap = new HashMap<>(); + logger.debug("Generating ThingType for device '{}' with {} channels", device.getType(), + device.getChannels().size()); + device.getChannels().forEach(c -> { + // Only generate Channel that is not dynamic. Dyn channel will be created depending on the + // thing handler decision. + if (!c.isDynamic()) { + // generate channel + ChannelTypeUID channelTypeUID = UidUtils.generateChannelTypeUID(c); + ChannelType channelType = channelTypeProvider.getChannelType(channelTypeUID, + Locale.getDefault()); + if (channelType == null) { + channelType = createChannelType(c, channelTypeUID); + channelTypeProvider.addChannelType(channelType); + } + + ChannelDefinition channelDef = new ChannelDefinitionBuilder(c.getName(), channelType.getUID()) + .build(); + groupChannelsMap.computeIfAbsent(c.getChannelGroup(), k -> new ArrayList<>()).add(channelDef); + } + }); + groupChannelsMap.forEach(this::createAndRegistryGroupTypeChannel); + } + thingType = createThingType(device, channelGroupTypeProvider.internalGroupTypes()); + thingTypeProvider.addThingType(thingType); + } + } + + private ThingType createThingType(ThinqDevice device, List groupTypes) { + String label = device.getLabel(); + String description = device.getDescription(); + + List supportedBridgeTypeUids = List.of(THING_TYPE_BRIDGE.toString()); + ThingTypeUID thingTypeUID = UidUtils.generateThingTypeUID(device); + + Map properties = new HashMap<>(); + properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME); + properties.put(Thing.PROPERTY_MODEL_ID, device.getType()); + + URI configDescriptionURI = getConfigDescriptionURI(device); + if (configDescriptionProvider.getConfigDescription(configDescriptionURI, Locale.getDefault()) == null) { + generateConfigDescription(device, configDescriptionURI); + } + + List groupDefinitions = new ArrayList<>(); + for (ChannelGroupType groupType : groupTypes) { + int usPos = groupType.getUID().getId().lastIndexOf("_"); + String id = usPos == -1 ? groupType.getUID().getId() : groupType.getUID().getId().substring(usPos + 1); + groupDefinitions.add(new ChannelGroupDefinition(id, groupType.getUID())); + } + + return ThingTypeBuilder.instance(thingTypeUID, label).withSupportedBridgeTypeUIDs(supportedBridgeTypeUids) + .withDescription(description).withChannelGroupDefinitions(groupDefinitions).withProperties(properties) + .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).withConfigDescriptionURI(configDescriptionURI) + .build(); + } + + private void generateConfigDescription(ThinqDevice device, URI configDescriptionURI) { + List params = new ArrayList<>(); + List groups = new ArrayList<>(); + + for (DeviceParameter param : device.getConfigParameter()) { + String groupName = null; + if (param.getGroup() != null) { + groupName = param.getGroup().getGroupName(); + groups.add(ConfigDescriptionParameterGroupBuilder.create(groupName) + .withLabel(param.getGroup().getGroupLabel()).build()); + } + ConfigDescriptionParameterBuilder builder = ConfigDescriptionParameterBuilder.create(param.getName(), + param.getType()); + builder.withLabel(param.getLabel()); + builder.withDefault(param.getDefaultValue()); + builder.withDescription(param.getDescription()); + builder.withReadOnly(param.isReadOnly()); + if (param.getOptions() != null) + builder.withOptions(param.getOptions()); + + builder.withGroupName(groupName); + params.add(builder.build()); + } + configDescriptionProvider.addConfigDescription(ConfigDescriptionBuilder.create(configDescriptionURI) + .withParameters(params).withParameterGroups(groups).build()); + } + + private ChannelType createChannelType(ThinqChannel channel, ChannelTypeUID channelTypeUID) { + /* + * + * + * + * + * + * + * + * + * + */ + DataType dataType = channel.getType(); + if (dataType.getName().equals("system.power")) { + return SYSTEM_POWER; + } else { + String itemType = dataType.getName(); + StateDescriptionFragmentBuilder stateFragment = StateDescriptionFragmentBuilder.create(); + if (channel.getUnitDisplayPattern() != null) { + stateFragment.withPattern(Objects.requireNonNull(channel.getUnitDisplayPattern())); + } + stateFragment.withReadOnly(channel.isReadOnly()); + + if (dataType.isNumeric()) { + final BigDecimal min, max; + if (CoreItemFactory.DIMMER.equals(itemType) || CoreItemFactory.ROLLERSHUTTER.equals(itemType)) { + // those types use PercentTypeConverter, so set up min and max as percent values + min = BigDecimal.ZERO; + max = new BigDecimal(100); + stateFragment.withMinimum(min).withMaximum(max); + } + } else if (dataType.isEnum() && dataType.getOptions() != null) { + stateFragment.withOptions(Objects.requireNonNull(dataType.getOptions())); + } + + String label = channel.getLabel(); + URI configUriDescriptor; + try { + configUriDescriptor = new URI(CONFIG_DESCRIPTION_URI_CHANNEL); + } catch (URISyntaxException e) { + throw new IllegalStateException( + "Error creating URI configuration for a Thinq channel. It's most likely a bug.", e); + } + final ChannelTypeBuilder channelTypeBuilder; + channelTypeBuilder = ChannelTypeBuilder.state(channelTypeUID, label, itemType) + .withStateDescriptionFragment(stateFragment.build()).isAdvanced(channel.isAdvanced()) + .withDescription(channel.getDescription()).withConfigDescriptionURI(configUriDescriptor); + String category = discoverCategory(channel); + if (category != null) { + channelTypeBuilder.withCategory(category); + } + return channelTypeBuilder.build(); + } + } + + @Nullable + private String discoverCategory(ThinqChannel c) { + switch (c.getType().getName()) { + case "washer-temp-level": + return "Temperature"; + case "washerdryer-delay-time": + case "washerdryer-remain-time": + return "Time"; + default: + return null; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java new file mode 100644 index 0000000000000..5af844701adb8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java @@ -0,0 +1,34 @@ +/** + * 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.lgthinq.internal.type; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.type.ChannelGroupType; +import org.openhab.core.thing.type.ChannelGroupTypeProvider; + +/** + * The ThinqChannelGroupTypeProvider interface. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface ThinqChannelGroupTypeProvider extends ChannelGroupTypeProvider { + + public void addChannelGroupType(ChannelGroupType channelGroupType); + + public void removeChannelGroupType(ChannelGroupType channelGroupType); + + public List internalGroupTypes(); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java new file mode 100644 index 0000000000000..2c770f35b75b0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java @@ -0,0 +1,27 @@ +/** + * 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.lgthinq.internal.type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeProvider; + +/** + * The ThinqChannelTypeProvider interface. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface ThinqChannelTypeProvider extends ChannelTypeProvider { + public void addChannelType(final ChannelType channelType); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java new file mode 100644 index 0000000000000..9820b790ccf4e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java @@ -0,0 +1,28 @@ +/** + * 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.lgthinq.internal.type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.config.core.ConfigDescription; +import org.openhab.core.config.core.ConfigDescriptionProvider; + +/** + * The ThinqConfigDescriptionProvider interface. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface ThinqConfigDescriptionProvider extends ConfigDescriptionProvider { + + public void addConfigDescription(ConfigDescription configDescription); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java new file mode 100644 index 0000000000000..5c6ed67d3fb80 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java @@ -0,0 +1,28 @@ +/** + * 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.lgthinq.internal.type; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.binding.ThingTypeProvider; +import org.openhab.core.thing.type.ThingType; + +/** + * The ThinqThingTypeProvider interface. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface ThinqThingTypeProvider extends ThingTypeProvider { + + public void addThingType(ThingType thingType); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java new file mode 100644 index 0000000000000..3e1fe08898da5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java @@ -0,0 +1,121 @@ +/** + * 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.lgthinq.internal.type; + +import java.net.URI; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.config.core.ConfigDescription; +import org.openhab.core.config.core.ConfigDescriptionProvider; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.binding.ThingTypeProvider; +import org.openhab.core.thing.type.*; +import org.osgi.service.component.annotations.Component; + +/** + * Provider class to provide model types for custom things (not in XML). + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@Component(service = { ThinqChannelTypeProvider.class, ChannelTypeProvider.class, ChannelGroupTypeProvider.class, + ThinqChannelGroupTypeProvider.class, ThinqConfigDescriptionProvider.class, ConfigDescriptionProvider.class, + ThinqThingTypeProvider.class, ThingTypeProvider.class }) +public class ThinqTypesProviderImpl implements ThinqChannelTypeProvider, ThinqChannelGroupTypeProvider, + ThinqConfigDescriptionProvider, ThinqThingTypeProvider { + + private final Map thingTypesByUID = new ConcurrentHashMap<>(); + private final Map channelTypesByUID = new ConcurrentHashMap<>(); + private final Map channelGroupTypesByUID = new ConcurrentHashMap<>(); + + private final Map configDescriptionsByURI = new ConcurrentHashMap<>(); + + @Override + public Collection getChannelTypes(@Nullable final Locale locale) { + return Collections.unmodifiableCollection(channelTypesByUID.values()); + } + + @Override + public @Nullable ChannelType getChannelType(final ChannelTypeUID channelTypeUID, @Nullable final Locale locale) { + return channelTypesByUID.get(channelTypeUID); + } + + /** + * Add a channel type for a user configured channel. + * + * @param channelType channelType + */ + @Override + public void addChannelType(final ChannelType channelType) { + channelTypesByUID.put(channelType.getUID(), channelType); + } + + @Override + @Nullable + public ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID, @Nullable Locale locale) { + return channelGroupTypesByUID.get(channelGroupTypeUID); + } + + @Override + public Collection getChannelGroupTypes(@Nullable Locale locale) { + return Collections.unmodifiableCollection(channelGroupTypesByUID.values()); + } + + @Override + public void addChannelGroupType(ChannelGroupType channelGroupType) { + channelGroupTypesByUID.put(channelGroupType.getUID(), channelGroupType); + } + + @Override + public void removeChannelGroupType(ChannelGroupType channelGroupType) { + channelGroupTypesByUID.remove(channelGroupType.getUID()); + } + + @Override + public List internalGroupTypes() { + return new ArrayList<>(channelGroupTypesByUID.values()); + } + + @Override + public void addConfigDescription(ConfigDescription configDescription) { + configDescriptionsByURI.put(configDescription.getUID(), configDescription); + } + + @Override + public Collection getConfigDescriptions(@Nullable Locale locale) { + return Collections.unmodifiableCollection(configDescriptionsByURI.values()); + } + + @Override + public @Nullable ConfigDescription getConfigDescription(URI uri, @Nullable Locale locale) { + return configDescriptionsByURI.get(uri); + } + + @Override + public void addThingType(ThingType thingType) { + thingTypesByUID.put(thingType.getUID(), thingType); + } + + @Override + public Collection getThingTypes(@Nullable Locale locale) { + return Collections.unmodifiableCollection(thingTypesByUID.values()); + } + + @Override + public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) { + return thingTypesByUID.get(thingTypeUID); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java new file mode 100644 index 0000000000000..b550bb8f54e00 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java @@ -0,0 +1,61 @@ +/** + * 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.lgthinq.internal.type; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.BINDING_ID; + +import org.openhab.binding.lgthinq.internal.model.ThinqChannel; +import org.openhab.binding.lgthinq.internal.model.ThinqChannelGroup; +import org.openhab.binding.lgthinq.internal.model.ThinqDevice; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ChannelTypeUID; + +/** + * Utility class for generating some UIDs. + * + * @author Nemer Daud - Initial contribution + */ +public class UidUtils { + + /** + * Generates the ThingTypeUID for the given device. + */ + public static ThingTypeUID generateThingTypeUID(ThinqDevice device) { + return new ThingTypeUID(BINDING_ID, device.getType()); + } + + /** + * Generates the ChannelTypeUID. + */ + public static ChannelTypeUID generateChannelTypeUID(ThinqChannel channel) { + return new ChannelTypeUID(BINDING_ID, String.format("%s_%s", channel.getDevice().getType(), channel.getName())); + } + + /** + * Generates the ChannelTypeUID for the given channel group. + */ + public static ChannelGroupTypeUID generateChannelGroupTypeUID(ThinqChannelGroup grpChannel) { + return new ChannelGroupTypeUID(BINDING_ID, + String.format("%s_%s", grpChannel.getDevice().getType(), grpChannel.getName())); + } + + /** + * Generates the ChannelUID for the given datapoint with channelNumber and datapointName. + */ + public static ChannelUID generateChannelUID(ThinqChannel dp, ThingUID thingUID) { + return new ChannelUID(thingUID, String.valueOf(dp.getName()), dp.getName()); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java new file mode 100644 index 0000000000000..496ce946cebf2 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java @@ -0,0 +1,42 @@ +/** + * 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.lgthinq.lgservices; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; + +/** + * The {@link LGThinQACApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGThinQACApiClientService extends LGThinQApiClientService { + void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; + + void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; + + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGThinqApiException; + + void turnCoolJetMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + + void turnAirCleanMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + + void turnAutoDryMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + + void turnEnergySavingMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java new file mode 100644 index 0000000000000..fbd94ca5d2e2c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java @@ -0,0 +1,168 @@ +/** + * 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.lgthinq.lgservices; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQACApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQACApiV1ClientServiceImpl extends + LGThinQAbstractApiV1ClientService implements LGThinQACApiClientService { + private static final LGThinQACApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV1ClientServiceImpl.class); + + static { + instance = new LGThinQACApiV1ClientServiceImpl(ACCapability.class, ACCanonicalSnapshot.class); + } + + protected LGThinQACApiV1ClientServiceImpl(Class capabilityClass, + Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { + // Nothing to do on V1 ACCapability here + } + + public static LGThinQACApiClientService getInstance() { + return instance; + } + + /** + * Get snapshot data from the device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @param capDef + * @return return map containing metamodel of settings and snapshot + * @throws LGThinqApiException if some communication error occur. + */ + @Override + @Nullable + public ACCanonicalSnapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull CapabilityDefinition capDef) throws LGThinqApiException { + throw new UnsupportedOperationException("Method not supported in V1 API device."); + } + + @Override + public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException { + // TODO + return 0; + } + + // // TODO - Analise this to get power consumption + // @Nullable + // private RestResult getConfigCommands(String bridgeName, String deviceId, String keyName) throws Exception { + // TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + // UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); + // Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + // token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + // + // String payload = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"Config\"," + // + " \"cmdOpt\": \"Get\"," + " \"value\": \"%s\"," + " \"deviceId\": \"%s\"," + // + " \"workId\": \"%s\"," + " \"data\": \"\"" + " }\n" + "}", keyName, deviceId, + // UUID.randomUUID().toString()); + // return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); + // } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", "Operation", + "" + newPowerState.commandValue()); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting device power", e); + } + } + + @Override + public void turnCoolJetMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "Jet", modeOnOff); + } + + public void turnAirCleanMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "AirClean", modeOnOff); + } + + public void turnAutoDryMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "AutoDry", modeOnOff); + } + + public void turnEnergySavingMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "PowerSave", modeOnOff); + } + + protected void turnGenericMode(String bridgeName, String deviceId, String modeName, String modeOnOff) + throws LGThinqApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", modeName, modeOnOff); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting " + modeName + " mode", e); + } + } + + @Override + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", "OpMode", "" + newOpMode); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", "WindStrength", + "" + newFanSpeed); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting fan speed", e); + } + } + + @Override + public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGThinqApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", "TempCfg", + "" + newTargetTemp.commandValue()); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting target temperature", e); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..e9816528b8d9d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -0,0 +1,184 @@ +/** + * 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.lgthinq.lgservices; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQACApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQACApiV2ClientServiceImpl extends + LGThinQAbstractApiV2ClientService implements LGThinQACApiClientService { + private static final LGThinQACApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV2ClientServiceImpl.class); + + static { + instance = new LGThinQACApiV2ClientServiceImpl(ACCapability.class, ACCanonicalSnapshot.class); + } + + protected LGThinQACApiV2ClientServiceImpl(Class capabilityClass, + Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + public static LGThinQACApiClientService getInstance() { + return instance; + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + try { + RestResult resp = sendBasicControlCommands(bridgeName, deviceId, "Operation", "airState.operation", + newPowerState.commandValue()); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting device power", e); + } + } + + @Override + public void turnCoolJetMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "airState.wMode.jet", modeOnOff); + } + + public void turnAirCleanMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "airState.wMode.airClean", modeOnOff); + } + + public void turnAutoDryMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "airState.miscFuncState.autoDry", modeOnOff); + } + + public void turnEnergySavingMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "airState.powerSave.basic", modeOnOff); + } + + protected void turnGenericMode(String bridgeName, String deviceId, String modeName, String modeOnOff) + throws LGThinqApiException { + try { + RestResult resp = sendBasicControlCommands(bridgeName, deviceId, "Operation", modeName, + Integer.parseInt(modeOnOff)); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting cool jet mode", e); + } + } + + @Override + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { + try { + RestResult resp = sendBasicControlCommands(bridgeName, deviceId, "Set", "airState.opMode", newOpMode); + handleGenericErrorResult(resp); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { + try { + RestResult resp = sendBasicControlCommands(bridgeName, deviceId, "Set", "airState.windStrength", + newFanSpeed); + handleGenericErrorResult(resp); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGThinqApiException { + try { + RestResult resp = sendBasicControlCommands(bridgeName, deviceId, "Set", "airState.tempState.target", + newTargetTemp.commandValue()); + handleGenericErrorResult(resp); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting operation mode", e); + } + } + + /** + * Start monitor data form specific device. This is old one, works only on V1 API supported devices. + * + * @param deviceId Device ID + * @return Work1 to be uses to grab data during monitoring. + * @throws LGThinqApiException If some communication error occur. + */ + @Override + public String startMonitor(String bridgeName, String deviceId) + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } + + @Override + public void stopMonitor(String bridgeName, String deviceId, String workId) + throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } + + @Override + public @Nullable ACCanonicalSnapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull String workId, DeviceTypes deviceType, @NonNull ACCapability deviceCapability) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } + + @Override + public void initializeDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + super.initializeDevice(bridgeName, deviceId); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "control", "allEventEnable", "Set", + "airState.mon.timeout", "70"); + handleGenericErrorResult(resp); + } catch (Exception e) { + logger.debug("Can't execute Before Update command", e); + } + } + + @Override + public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not supporte for this device"); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java new file mode 100644 index 0000000000000..8052afe10801c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -0,0 +1,445 @@ +/** + * 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.lgthinq.lgservices; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQBindingConstants; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.*; +import org.openhab.binding.lgthinq.lgservices.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The {@link LGThinQACApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class LGThinQAbstractApiClientService + implements LGThinQApiClientService { + private static final Logger logger = LoggerFactory.getLogger(LGThinQAbstractApiClientService.class); + protected final ObjectMapper objectMapper = new ObjectMapper(); + protected final TokenManager tokenManager; + protected Class capabilityClass; + protected Class snapshotClass; + + protected LGThinQAbstractApiClientService(Class capabilityClass, Class snapshotClass) { + this.tokenManager = TokenManager.getInstance(); + + this.capabilityClass = capabilityClass; + this.snapshotClass = snapshotClass; + } + + static Map getCommonHeaders(String language, String country, String accessToken, + String userNumber) { + Map headers = new HashMap<>(); + headers.put("Accept", "application/json"); + headers.put("Content-type", "application/json;charset=UTF-8"); + headers.put("x-api-key", V2_API_KEY); + headers.put("x-client-id", V2_CLIENT_ID); + headers.put("x-country-code", country); + headers.put("x-language-code", language); + headers.put("x-message-id", UUID.randomUUID().toString()); + headers.put("x-service-code", SVC_CODE); + headers.put("x-service-phase", V2_SVC_PHASE); + headers.put("x-thinq-app-level", V2_APP_LEVEL); + headers.put("x-thinq-app-os", V2_APP_OS); + headers.put("x-thinq-app-type", V2_APP_TYPE); + headers.put("x-thinq-app-ver", V2_APP_VER); + headers.put("x-thinq-security-key", SECURITY_KEY); + if (!accessToken.isBlank()) + headers.put("x-emp-token", accessToken); + if (!userNumber.isBlank()) + headers.put("x-user-no", userNumber); + return headers; + } + + /** + * Even using V2 URL, this endpoint support grab informations about account devices from V1 and V2. + * + * @return list os LG Devices. + * @throws LGThinqApiException if some communication error occur. + */ + @Override + public List listAccountDevices(String bridgeName) throws LGThinqApiException { + try { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); + return handleListAccountDevicesResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Erros list account devices from LG Server API", e); + } + } + + @Override + public File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); + try { + if (!regFile.isFile() || forceRecreate) { + try (InputStream in = new URL(uri).openStream()) { + Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + } catch (IOException e) { + logger.error("Error reading resource from URI: {}", uri, e); + throw new LGThinqApiException("Error reading IO interface", e); + } + return regFile; + } + + /** + * Get device settings and snapshot for a specific device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGThinqApiException if some communication error occur. + */ + @Override + public Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException { + try { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) + .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); + return handleDeviceSettingsResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Errors list account devices from LG Server API", e); + } + } + + private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { + return genericHandleDeviceSettingsResult(resp, logger, objectMapper); + } + + @SuppressWarnings("unchecked") + static Map genericHandleDeviceSettingsResult(RestResult resp, Logger logger, + ObjectMapper objectMapper) throws LGThinqApiException { + Map deviceSettings; + Map respMap = Collections.EMPTY_MAP; + String resultCode = "???"; + if (resp.getStatusCode() != 200) { + try { + respMap = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + resultCode = respMap.get("resultCode"); + if (resultCode != null) { + logger.error( + "Error calling device settings from LG Server API. The code is:{} and The reason is:{}", + resultCode, ResultCodes.fromCode(resultCode)); + throw new LGThinqApiException("Error calling device settings from LG Server API."); + } + } catch (JsonProcessingException e) { + // This exception doesn't matter, it's because response is not in json format. Logging raw response. + } + logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGThinqApiException(String.format( + "Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); + + } else { + try { + deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + String code = Objects.requireNonNullElse((String) deviceSettings.get("resultCode"), ""); + if (!ResultCodes.OK.containsResultCode(code)) { + logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, + getErrorCodeMessage(code)); + throw new LGThinqApiException( + String.format("Status error getting device list. resultCode must be 0000, but was:%s", + deviceSettings.get("resultCode"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + + } + return Objects.requireNonNull((Map) deviceSettings.get("result"), + "Unexpected json result asking for Device Settings. Node 'result' no present"); + } + + @SuppressWarnings("unchecked") + private List handleListAccountDevicesResult(RestResult resp) throws LGThinqApiException { + Map devicesResult; + List devices; + if (resp.getStatusCode() != 200) { + logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGThinqApiException(String + .format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + String code = Objects.requireNonNullElse((String) devicesResult.get("resultCode"), ""); + if (!ResultCodes.OK.containsResultCode(code)) { + logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, + getErrorCodeMessage(code)); + throw new LGThinqApiException( + String.format("Status error getting device list. resultCode must be 0000, but was:%s", + devicesResult.get("resultCode"))); + } + List> items = (List>) ((Map) devicesResult + .get("result")).get("item"); + devices = objectMapper.convertValue(items, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream.", e); + } + + } + + return devices; + } + + protected static String getErrorCodeMessage(@Nullable String code) { + if (code == null) { + return ""; + } + ResultCodes resultCode = ResultCodes.fromCode(code); + return resultCode.getDescription(); + } + + /** + * Get capability em registry/cache on file for next consult + * + * @param deviceId ID of the device + * @param uri URI of the config capability + * @return return simplified capability + * @throws LGThinqApiException If some error occurr + */ + public C getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + try { + File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); + JsonNode rootNode = objectMapper.readTree(regFile); + return CapabilityFactory.getInstance().create(rootNode, capabilityClass); + } catch (IOException e) { + throw new LGThinqApiException("Error reading IO interface", e); + } catch (LGThinqException e) { + throw new LGThinqApiException("Error parsing capability registry", e); + } + } + + public @Nullable S getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId, + DeviceTypes deviceType, @NonNull C deviceCapability) throws LGThinqApiException, + LGThinqDeviceV1MonitorExpiredException, IOException, LGThinqUnmarshallException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" + + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" + + " ]\n" + " }\n" + "}", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + Map envelop = null; + // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with + // offline flag. + try { + envelop = handleGenericErrorResult(resp); + } catch (LGThinqDeviceV1OfflineException e) { + try { + // As I don't know the current device status, then I reset to default values. + S shot = snapshotClass.getDeclaredConstructor().newInstance(); + shot.setPowerStatus(DevicePowerState.DV_POWER_OFF); + shot.setOnline(false); + return (S) shot; + } catch (Exception ex) { + logger.error("Unexpected Error. The default constructor of this Snapshot wasn't found", ex); + throw new IllegalStateException( + "Unexpected Error. The default constructor of this Snapshot wasn't found", ex); + } + } + if (envelop.get("workList") != null + && ((Map) envelop.get("workList")).get("returnData") != null) { + Map workList = ((Map) envelop.get("workList")); + if (logger.isDebugEnabled()) { + try { + objectMapper.writeValue(new File(String.format( + LGThinQBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", + deviceId)), workList); + } catch (IOException e) { + logger.error("Error saving data trace", e); + } + } + + if (!ResultCodes.OK.containsResultCode("" + workList.get("returnCode"))) { + String code = Objects.requireNonNullElse((String) workList.get("returnCode"), ""); + logger.debug("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, + getErrorCodeMessage(code)); + LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( + String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); + logger.warn("{}", e.getMessage()); + throw e; + } + + String monDataB64 = (String) workList.get("returnData"); + String monData = new String(Base64.getDecoder().decode(monDataB64)); + S shot; + try { + if (MonitoringResultFormat.JSON_FORMAT.equals(deviceCapability.getMonitoringDataFormat())) { + shot = (S) SnapshotBuilderFactory.getInstance().getBuilder(snapshotClass).createFromJson(monData, + deviceType, deviceCapability); + } else if (MonitoringResultFormat.BINARY_FORMAT.equals(deviceCapability.getMonitoringDataFormat())) { + shot = (S) SnapshotBuilderFactory.getInstance().getBuilder(snapshotClass).createFromBinary(monData, + deviceCapability.getMonitoringBinaryProtocol(), deviceCapability); + } else { + logger.error("Returned data format not supported: {}", deviceCapability.getMonitoringDataFormat()); + throw new LGThinqApiException("Returned data format not supported"); + } + shot.setOnline("E".equals(workList.get("deviceState"))); + } catch (LGThinqUnmarshallException ex) { + // error in the monitor Data returned. Device is irresponsible + logger.debug("Monitored data returned for the device {} is unreadable. Device is not connected", + deviceId); + throw ex; + } + return shot; + } else { + // no data available yet + return null; + } + } + + @Override + public void initializeDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + } + + /** + * Perform some routine before getting data device. Depending on the kind of the device, this is needed + * to update or prepare some informations before go to get the data. + * + */ + protected abstract void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException; + + /** + * Get snapshot data from the device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @param capDef + * @return return map containing metamodel of settings and snapshot + * @throws LGThinqApiException if some communication error occur. + */ + @Override + @Nullable + public S getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull CapabilityDefinition capDef) + throws LGThinqApiException { + // Exec pre-conditions (normally ask for update monitoring sensors of the device - temp and power) before call + // for data + beforeGetDataDevice(bridgeName, deviceId); + + Map deviceSettings = getDeviceSettings(bridgeName, deviceId); + if (deviceSettings.get("snapshot") != null) { + Map snapMap = (Map) deviceSettings.get("snapshot"); + if (logger.isDebugEnabled()) { + try { + objectMapper.writeValue(new File(String.format( + LGThinQBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", + deviceId)), deviceSettings); + } catch (IOException e) { + logger.error("Error saving data trace", e); + } + } + if (snapMap == null) { + // No snapshot value provided + return null; + } + S shot = (S) SnapshotBuilderFactory.getInstance().getBuilder(snapshotClass).createFromJson(deviceSettings, + capDef); + shot.setOnline((Boolean) snapMap.get("online")); + return shot; + } + return null; + } + + public abstract double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException; + + /** + * Start monitor data form specific device. This is old one, works only on V1 API supported devices. + * + * @param deviceId Device ID + * @return Work1 to be uses to grab data during monitoring. + * @throws LGThinqApiException If some communication error occur. + */ + @Override + public String startMonitor(String bridgeName, String deviceId) + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String workerId = UUID.randomUUID().toString(); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + return Objects.requireNonNull((String) handleGenericErrorResult(resp).get("workId"), + "Unexpected StartMonitor json result. Node 'workId' not present"); + } + + @Override + public void stopMonitor(String bridgeName, String deviceId, String workId) + throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + handleGenericErrorResult(resp); + } + + protected Map getCommonV2Headers(String language, String country, String accessToken, + String userNumber) { + return getCommonHeaders(language, country, accessToken, userNumber); + } + + protected abstract RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, + String controlKey, String command, String keyName, String value) throws Exception; + + protected abstract RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, + String controlKey, String command, @Nullable String keyName, @Nullable String value, + @Nullable ObjectNode extraNode) throws Exception; + + protected abstract Map handleGenericErrorResult(@Nullable RestResult resp) + throws LGThinqApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java new file mode 100644 index 0000000000000..96d21dff2a14c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -0,0 +1,152 @@ +/** + * 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.lgthinq.lgservices; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V1_CONTROL_OP; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The {@link LGThinQAbstractApiV1ClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class LGThinQAbstractApiV1ClientService + extends LGThinQAbstractApiClientService { + private static final Logger logger = LoggerFactory.getLogger(LGThinQAbstractApiV1ClientService.class); + + protected LGThinQAbstractApiV1ClientService(Class capabilityClass, Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + @Override + protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, String keyName, String value) throws Exception { + return sendControlCommands(bridgeName, deviceId, controlPath, controlKey, command, keyName, value, null); + } + + protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) + throws Exception { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + + ObjectNode payloadNode = JsonNodeFactory.instance.objectNode(); + payloadNode.put("cmd", controlKey).put("cmdOpt", command); + if (keyName == null || keyName.isEmpty()) { + // value is a simple text + payloadNode.put("value", value); + } else { + payloadNode.putObject("value").put(keyName, value); + } + // String payload = String.format( + // "{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"%s\"," + " \"cmdOpt\": \"%s\"," + // + " \"value\": {\"%s\": \"%s\"}," + " \"deviceId\": \"%s\"," + // + " \"workId\": \"%s\"," + " \"data\": \"\"" + " }\n" + "}", + // controlKey, command, keyName, value, deviceId, UUID.randomUUID()); + if (extraNode != null) { + payloadNode.setAll(extraNode); + } + return sendControlCommands(bridgeName, deviceId, payloadNode); + } + + protected RestResult sendControlCommands(String bridgeName, String deviceId, Object cmdPayload) throws Exception { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + ObjectNode payloadNode = null; + if (cmdPayload instanceof ObjectNode) { + payloadNode = ((ObjectNode) cmdPayload).deepCopy(); + } else { + payloadNode = objectMapper.convertValue(cmdPayload, ObjectNode.class); + } + ObjectNode rootNode = JsonNodeFactory.instance.objectNode(); + payloadNode.put("deviceId", deviceId); + payloadNode.put("workId", UUID.randomUUID().toString()); + rootNode.set("lgedmRoot", payloadNode); + + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, rootNode.toPrettyString()); + if (resp == null) { + logger.error("Null result returned sending command to LG API V1"); + throw new LGThinqApiException("Null result returned sending command to LG API V1"); + } + return resp; + } + + @Override + protected Map handleGenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { + Map metaResult; + Map envelope = Collections.emptyMap(); + if (resp == null) { + return envelope; + } + if (resp.getStatusCode() != 200) { + logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGThinqApiException( + String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + envelope = (Map) metaResult.get("lgedmRoot"); + String code = "" + envelope.get("returnCd"); + if (envelope.isEmpty()) { + throw new LGThinqApiException(String.format( + "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); + } else if (!ResultCodes.OK.containsResultCode(code)) { + if (ResultCodes.DEVICE_NOT_RESPONSE.containsResultCode("" + envelope.get("returnCd")) + || "D".equals(envelope.get("deviceState"))) { + logger.debug("LG API report error processing the request -> resultCode=[{}], message=[{}]", + code, getErrorCodeMessage(code)); + // Disconnected Device + throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); + } + logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, + getErrorCodeMessage(code)); + throw new LGThinqApiException( + String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", + metaResult.get("returnCd"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + } + return envelope; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java new file mode 100644 index 0000000000000..52bd48e115979 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -0,0 +1,115 @@ +/** + * 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.lgthinq.lgservices; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; + +import java.util.Collections; +import java.util.Map; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The {@link LGThinQAbstractApiV2ClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class LGThinQAbstractApiV2ClientService + extends LGThinQAbstractApiClientService { + private static final Logger logger = LoggerFactory.getLogger(LGThinQAbstractApiV2ClientService.class); + + protected LGThinQAbstractApiV2ClientService(Class capabilityClass, Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + @Override + protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, String keyName, String value) throws Exception { + return sendControlCommands(bridgeName, deviceId, controlPath, controlKey, command, keyName, value, null); + } + + @Override + protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) + throws Exception { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) + .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId, controlPath)); + Map headers = getCommonV2Headers(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + ObjectNode payload = JsonNodeFactory.instance.objectNode(); + payload.put("ctrlKey", controlKey).put("command", command).put("dataKey", keyName).put("dataValue", value); + if (extraNode != null) { + payload.setAll(extraNode); + } + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, payload.toPrettyString()); + if (resp == null) { + logger.error("Null result returned sending command to LG API V2"); + throw new LGThinqApiException("Null result returned sending command to LG API V2"); + } + return resp; + } + + protected RestResult sendBasicControlCommands(String bridgeName, String deviceId, String command, String keyName, + int value) throws Exception { + return sendControlCommands(bridgeName, deviceId, "control-sync", "basicCtrl", command, keyName, "" + value); + } + + @Override + protected Map handleGenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { + Map metaResult; + if (resp == null) { + return Collections.EMPTY_MAP; + } + if (resp.getStatusCode() != 200) { + logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGThinqApiException( + String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { + }); + String code = (String) metaResult.get("resultCode"); + if (!ResultCodes.OK.containsResultCode("" + metaResult.get("resultCode"))) { + logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, + getErrorCodeMessage(code)); + throw new LGThinqApiException( + String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", + metaResult.get("resultCode"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + + } + return Collections.EMPTY_MAP; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java new file mode 100644 index 0000000000000..d239768f24d3f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -0,0 +1,71 @@ +/** + * 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.lgthinq.lgservices; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.*; +import org.openhab.binding.lgthinq.lgservices.model.*; + +/** + * The {@link LGThinQApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGThinQApiClientService { + + List listAccountDevices(String bridgeName) throws LGThinqApiException; + + Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException; + + void initializeDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; + + /** + * Retrieve actual data from device (its sensors and points states). + * + * @param deviceId device number + * @param capDef + * @return return snapshot state of the device + * @throws LGThinqApiException if some error interacting with LG API Server occur. + */ + @Nullable + S getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull CapabilityDefinition capDef) + throws LGThinqApiException; + + double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException; + + void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; + + String startMonitor(String bridgeName, String deviceId) + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; + + C getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; + + File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) + throws LGThinqApiException, IOException; + + void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; + + @Nullable + S getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId, + DeviceTypes deviceType, @NonNull C deviceCapability) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException, LGThinqUnmarshallException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java new file mode 100644 index 0000000000000..3398a492d4d0e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java @@ -0,0 +1,30 @@ +/** + * 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.lgthinq.lgservices; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; + +/** + * The {@link LGThinQDRApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGThinQDRApiClientService extends LGThinQApiClientService { + void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException; + + void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..da8a41e1edece --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java @@ -0,0 +1,91 @@ +/** + * 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.lgthinq.lgservices; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; + +/** + * The {@link LGThinQDRApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQDRApiV2ClientServiceImpl + extends LGThinQAbstractApiV2ClientService + implements LGThinQDRApiClientService { + + private static final LGThinQDRApiV2ClientServiceImpl instance; + static { + instance = new LGThinQDRApiV2ClientServiceImpl(WasherDryerCapability.class, WasherDryerSnapshot.class); + } + + protected LGThinQDRApiV2ClientServiceImpl(Class capabilityClass, + Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { + // TODO - Analise what to do here + } + + @Override + public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException { + return 0; + } + + public static LGThinQDRApiV2ClientServiceImpl getInstance() { + return instance; + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + public void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException { + try { + RestResult result = sendControlCommands(bridgeName, deviceId, "control-sync", "WMStart", "WMStart", + "WMStart", ""); + handleGenericErrorResult(result); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending remote start", e); + } + } + + @Override + public void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException { + try { + RestResult result = sendControlCommands(bridgeName, deviceId, "control-sync", "WMWakeup", "WMWakeup", "", + ""); + handleGenericErrorResult(result); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending remote start", e); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java new file mode 100644 index 0000000000000..cf1c6d561e889 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java @@ -0,0 +1,28 @@ +/** + * 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.lgthinq.lgservices; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; + +/** + * The {@link LGThinQFridgeApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGThinQFridgeApiClientService + extends LGThinQApiClientService { + +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java new file mode 100644 index 0000000000000..3e6060ba36f93 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java @@ -0,0 +1,73 @@ +/** + * 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.lgthinq.lgservices; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; + +/** + * The {@link LGThinQFridgeApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQFridgeApiV1ClientServiceImpl + extends LGThinQAbstractApiV1ClientService + implements LGThinQFridgeApiClientService { + + private static final LGThinQFridgeApiClientService instance; + static { + instance = new LGThinQFridgeApiV1ClientServiceImpl(FridgeCapability.class, FridgeCanonicalSnapshot.class); + } + + protected LGThinQFridgeApiV1ClientServiceImpl(Class capabilityClass, + Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { + // Nothing to do for V1 thinq + } + + @Override + public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException { + return 0; + } + + public static LGThinQFridgeApiClientService getInstance() { + return instance; + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + @Nullable + public FridgeCanonicalSnapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull CapabilityDefinition capDef) throws LGThinqApiException { + throw new UnsupportedOperationException("Method not supported in V1 API device."); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..1d9fa377b3f8c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java @@ -0,0 +1,64 @@ +/** + * 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.lgthinq.lgservices; + +import java.io.IOException; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; + +/** + * The {@link LGThinQFridgeApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQFridgeApiV2ClientServiceImpl + extends LGThinQAbstractApiV2ClientService + implements LGThinQFridgeApiClientService { + + private static final LGThinQFridgeApiClientService instance; + static { + instance = new LGThinQFridgeApiV2ClientServiceImpl(FridgeCapability.class, FridgeCanonicalSnapshot.class); + } + + protected LGThinQFridgeApiV2ClientServiceImpl(Class capabilityClass, + Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { + // TODO - Analise what to do here + } + + @Override + public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException { + return 0; + } + + public static LGThinQFridgeApiClientService getInstance() { + return instance; + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet."); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java new file mode 100644 index 0000000000000..e539d20a4cb16 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java @@ -0,0 +1,33 @@ +/** + * 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.lgthinq.lgservices; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; + +/** + * The {@link LGThinQWMApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGThinQWMApiClientService extends LGThinQApiClientService { + void remoteStart(String bridgeName, WasherDryerCapability cap, String deviceId, Map data) + throws LGThinqApiException; + + void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java new file mode 100644 index 0000000000000..760a5529d9b8d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java @@ -0,0 +1,146 @@ +/** + * 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.lgthinq.lgservices; + +import java.io.IOException; +import java.util.Base64; +import java.util.List; +import java.util.Map; + +import org.apache.commons.lang3.ArrayUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; + +/** + * The {@link LGThinQWMApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQWMApiV1ClientServiceImpl + extends LGThinQAbstractApiV1ClientService + implements LGThinQWMApiClientService { + private final Logger logger = LoggerFactory.getLogger(LGThinQWMApiV1ClientServiceImpl.class); + private static final LGThinQWMApiClientService instance; + static { + instance = new LGThinQWMApiV1ClientServiceImpl(WasherDryerCapability.class, WasherDryerSnapshot.class); + } + + protected LGThinQWMApiV1ClientServiceImpl(Class capabilityClass, + Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { + // Nothing to do for V1 thinq + } + + @Override + public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException { + return 0; + } + + public static LGThinQWMApiClientService getInstance() { + return instance; + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + @Nullable + public WasherDryerSnapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull CapabilityDefinition capDef) throws LGThinqApiException { + throw new UnsupportedOperationException("Method not supported in V1 API device."); + } + + @Override + public void remoteStart(String bridgeName, WasherDryerCapability cap, String deviceId, Map data) + throws LGThinqApiException { + try { + CommandDefinition cmdStartDef = cap.getCommandsDefinition().get(cap.getCommandRemoteStart()); + if (cmdStartDef == null) { + logger.warn("No command definition found for remote start v1. Ignoring command"); + return; + } + Map cmdPayload = prepareCommandV1(cmdStartDef, data); + RestResult result = sendControlCommands(bridgeName, deviceId, cmdPayload); + handleGenericErrorResult(result); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending remote start", e); + } + } + + @Override + public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException { + try { + + RestResult result = sendControlCommands(bridgeName, deviceId, "", "Control", "Operation", "", "WakeUp"); + handleGenericErrorResult(result); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending remote start", e); + } + } + + private Map prepareCommandV1(CommandDefinition cmdDef, Map snapData) + throws JsonProcessingException { + // expected map ordered here + String dataStr = cmdDef.getDataTemplate(); + for (Map.Entry e : snapData.entrySet()) { + String value = String.valueOf(e.getValue()); + if ("Start".equals(cmdDef.getCmdOptValue()) && e.getKey().equals("Option2")) { + value = String.valueOf(Integer.parseInt(value) | 1); + } + dataStr = dataStr.replace("{{" + e.getKey() + "}}", value); + } + Map cmd = objectMapper.readValue(cmdDef.getRawCommand(), new TypeReference<>() { + }); + + logger.debug("Prepare command v1: {}", dataStr); + if (cmdDef.isBinary()) { + cmd.put("format", "B64"); + List list = objectMapper.readValue(dataStr, new TypeReference<>() { + }); + // convert the list of integer to a bytearray + byte[] bytes = ArrayUtils.toPrimitive(list.stream().map(Integer::byteValue).toArray(Byte[]::new)); + String str_data_encoded = new String(Base64.getEncoder().encode(bytes)); + cmd.put("data", str_data_encoded); + } else { + cmd.put("data", dataStr); + } + + return cmd; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..eeca4e477ea16 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java @@ -0,0 +1,125 @@ +/** + * 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.lgthinq.lgservices; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_COMMAND_REMOTE_START_V2; + +import java.io.IOException; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * The {@link LGThinQWMApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQWMApiV2ClientServiceImpl + extends LGThinQAbstractApiV2ClientService + implements LGThinQWMApiClientService { + + private static final LGThinQWMApiClientService instance; + static { + instance = new LGThinQWMApiV2ClientServiceImpl(WasherDryerCapability.class, WasherDryerSnapshot.class); + } + + protected LGThinQWMApiV2ClientServiceImpl(Class capabilityClass, + Class snapshotClass) { + super(capabilityClass, snapshotClass); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { + // TODO - Analise what to do here + } + + @Override + public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException, IOException { + return 0; + } + + public static LGThinQWMApiClientService getInstance() { + return instance; + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet."); + } + + @Override + public void remoteStart(String bridgeName, WasherDryerCapability cap, String deviceId, Map data) + throws LGThinqApiException { + try { + ObjectNode dataSetList = JsonNodeFactory.instance.objectNode(); + ObjectNode nodeData = dataSetList.putObject("dataSetList").putObject("washerDryer"); + // 1 - mount nodeData template + CommandDefinition cdStart = cap.getCommandsDefinition().get(WM_COMMAND_REMOTE_START_V2); + if (cdStart == null) { + throw new LGThinqApiException( + "Command WMStart doesn't defined in cap. Do the Device support Remote Start ?"); + } + // remove data values (based on command template values) that it's not the real name + data.remove("course"); + data.remove("SmartCourse"); + for (Map.Entry value : data.entrySet()) { + Object v = value.getValue(); + if (v instanceof Double) { + nodeData.put(value.getKey(), (Double) v); + } else if (v instanceof Integer) { + nodeData.put(value.getKey(), (Integer) v); + } else { + nodeData.put(value.getKey(), value.getValue().toString()); + } + } + + RestResult result = sendControlCommands(bridgeName, deviceId, "control-sync", WM_COMMAND_REMOTE_START_V2, + "Set", null, null, dataSetList); + handleGenericErrorResult(result); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending remote start", e); + } + } + + @Override + public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException { + try { + ObjectNode dataSetList = JsonNodeFactory.instance.objectNode(); + dataSetList.putObject("dataSetList").putObject("washerDryer").put("controlDataType", "WAKEUP") + .put("controlDataValueLength", wakeUp ? "1" : "0"); + + RestResult result = sendControlCommands(bridgeName, deviceId, "control-sync", "WMWakeup", "Set", null, null, + dataSetList); + handleGenericErrorResult(result); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending remote start", e); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java new file mode 100644 index 0000000000000..3b690fe6188e5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java @@ -0,0 +1,132 @@ +/** + * 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.lgthinq.lgservices.model; + +import static org.openhab.binding.lgthinq.lgservices.FeatureDefinition.NULL_DEFINITION; + +import java.lang.reflect.ParameterizedType; +import java.util.*; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * The {@link AbstractCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractCapability implements CapabilityDefinition { + // default result format + protected Map> featureDefinitionMap = new HashMap<>(); + + protected String modelName = ""; + Class realClass; + + @Override + public String getModelName() { + return modelName; + } + + @Override + public void setModelName(String modelName) { + this.modelName = modelName; + } + + protected AbstractCapability() { + this.realClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + } + + protected DeviceTypes deviceType = DeviceTypes.UNKNOWN; + protected LGAPIVerion version = LGAPIVerion.UNDEF; + private MonitoringResultFormat monitoringDataFormat = MonitoringResultFormat.UNKNOWN_FORMAT; + + private List monitoringBinaryProtocol = new ArrayList<>(); + + @Override + public MonitoringResultFormat getMonitoringDataFormat() { + return monitoringDataFormat; + } + + @Override + public void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat) { + this.monitoringDataFormat = monitoringDataFormat; + } + + public void setFeatureDefinitionMap(Map> featureDefinitionMap) { + this.featureDefinitionMap = featureDefinitionMap; + } + + @Override + public List getMonitoringBinaryProtocol() { + return monitoringBinaryProtocol; + } + + @Override + public void setMonitoringBinaryProtocol(List monitoringBinaryProtocol) { + this.monitoringBinaryProtocol = monitoringBinaryProtocol; + } + + @Override + public void setDeviceType(DeviceTypes deviceType) { + this.deviceType = deviceType; + } + + @Override + public void setDeviceVersion(LGAPIVerion version) { + this.version = version; + } + + @Override + public DeviceTypes getDeviceType() { + return deviceType; + } + + @Override + public LGAPIVerion getDeviceVersion() { + return version; + } + + private Map rawData = new HashMap<>(); + + @JsonIgnore + public Map getRawData() { + return rawData; + } + + public Map> getFeatureValuesRawData() { + switch (getDeviceVersion()) { + case V1_0: + return Objects.requireNonNullElse((Map>) getRawData().get("Value"), + Collections.emptyMap()); + case V2_0: + return Objects.requireNonNullElse( + (Map>) getRawData().get("MonitoringValue"), Collections.emptyMap()); + default: + throw new IllegalStateException("Invalid version 'UNDEF' to get capability feature monitoring values"); + } + } + + public void setRawData(Map rawData) { + this.rawData = rawData; + } + + @Override + public FeatureDefinition getFeatureDefinition(String featureName) { + Function f = featureDefinitionMap.get(featureName); + return f != null ? f.apply(realClass.cast(this)) : NULL_DEFINITION; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java new file mode 100644 index 0000000000000..c94bc264aeb88 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java @@ -0,0 +1,125 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * The {@link AbstractCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractCapabilityFactory { + protected ObjectMapper mapper = new ObjectMapper(); + private static final Logger logger = LoggerFactory.getLogger(AbstractCapabilityFactory.class); + + public T create(JsonNode rootNode) throws LGThinqException { + T cap = getCapabilityInstance(); + cap.setModelName(rootNode.path("Info").path("modelName").textValue()); + cap.setDeviceType(ModelUtils.getDeviceType(rootNode)); + cap.setDeviceVersion(ModelUtils.discoveryAPIVersion(rootNode)); + cap.setRawData(mapper.convertValue(rootNode, Map.class)); + switch (cap.getDeviceVersion()) { + case V1_0: + // V1 has Monitoring node describing the protocol data format + JsonNode type = rootNode.path(getMonitoringNodeName()).path("type"); + if (!type.isMissingNode() && type.isTextual()) { + cap.setMonitoringDataFormat(MonitoringResultFormat.getFormatOf(type.textValue())); + } + break; + case V2_0: + // V2 doesn't have node describing the protocol because it's they unified Value (features) and + // Monitoring nodes in the MonitoringValue node + cap.setMonitoringDataFormat(MonitoringResultFormat.JSON_FORMAT); + break; + default: + cap.setMonitoringDataFormat(MonitoringResultFormat.UNKNOWN_FORMAT); + } + if (MonitoringResultFormat.BINARY_FORMAT.equals(cap.getMonitoringDataFormat())) { + // get MonitorProtocol + JsonNode protocol = rootNode.path(getMonitoringNodeName()).path("protocol"); + if (protocol.isArray()) { + ArrayNode pNode = (ArrayNode) protocol; + List protocols = mapper.convertValue(pNode, new TypeReference<>() { + }); + cap.setMonitoringBinaryProtocol(protocols); + } else { + if (protocol.isMissingNode()) { + logger.error("protocol node is missing in the capability descriptor for a binary monitoring"); + } else { + logger.error( + "protocol node is not and array in the capability descriptor for a binary monitoring "); + } + } + } + return cap; + } + + /** + * Return constant pointing to MonitoringNode. This node has information about monitoring response description, + * only present in V1 devices. If some device has different node name for this descriptor, please override + * it. + * + * @return Monitoring node name + */ + protected String getMonitoringNodeName() { + return "Monitoring"; + } + + protected abstract List getSupportedDeviceTypes(); + + protected abstract List getSupportedAPIVersions(); + + /** + * Return the feature definition, i.e, the defition of the device attributes that can be mapped to Channels. + * The targetChannelId is needed if you intend to get the destination channelId for that feature, typically for + * dynamic channels. + * + * @param featureName Name of the features: feature node name + * @param featuresNode The jsonNode containing the data definition of the feature + * @param targetChannelId The destination channelID, normally used when you want to create dynamic channels (outside + * xml) + * @param refChannelId + * @return the Feature definition. + */ + protected abstract FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, + @Nullable String targetChannelId, @Nullable String refChannelId); + + protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode) { + return newFeatureDefinition(featureName, featuresNode, null, null); + } + + protected abstract T getCapabilityInstance(); + + protected void validateMandatoryNote(JsonNode node) throws LGThinqException { + if (node.isMissingNode()) { + throw new LGThinqApiException( + String.format("Error extracting mandatory %s node for this device cap file", node)); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java new file mode 100644 index 0000000000000..45f660d5c7438 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java @@ -0,0 +1,54 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * The {@link AbstractSnapshotDefinition} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractSnapshotDefinition implements SnapshotDefinition { + + protected Map otherInfo = new HashMap<>(); + + @JsonAnySetter + public void addOtherInfo(String propertyKey, Object value) { + this.otherInfo.put(propertyKey, value); + } + + @Nullable + public Object getOtherInfo(String propertyKey) { + return this.otherInfo.get(propertyKey); + } + + private Map rawData = new HashMap<>(); + + @JsonIgnore + public Map getRawData() { + return rawData; + } + + public void setRawData(Map rawData) { + this.rawData = rawData; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java new file mode 100644 index 0000000000000..7dff468ad5517 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java @@ -0,0 +1,71 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; + +/** + * The {@link CapabilityDefinition} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface CapabilityDefinition { + String getModelName(); + + void setModelName(String modelName); + + MonitoringResultFormat getMonitoringDataFormat(); + + void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat); + + List getMonitoringBinaryProtocol(); + + void setMonitoringBinaryProtocol(List monitoringBinaryProtocol); + + DeviceTypes getDeviceType(); + + LGAPIVerion getDeviceVersion(); + + void setDeviceType(DeviceTypes deviceType); + + void setDeviceVersion(LGAPIVerion version); + + Map getRawData(); + + Map> getFeatureValuesRawData(); + + /** + * This method get the feature based on its name in the JSON device's definition. + * Ex: For V2: "MonitoringValue": { + * ... + * "spin" : { + * ... + * valueMapping{ + * ... + * } + * } + * } + * getFeatureDefinition("spin") will return the FeatureDefinition object representing "spin" feature configuration. + * + * @param featureName name of the feature node in the json definition + * @return return FeatureDefinition object representing the feature in case. + */ + FeatureDefinition getFeatureDefinition(String featureName); + + void setRawData(Map rawData); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java new file mode 100644 index 0000000000000..2a2e9898e5329 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java @@ -0,0 +1,89 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapabilityFactoryV1; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapabilityFactoryV2; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapabilityFactoryV1; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapabilityFactoryV2; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapabilityFactoryV1; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapabilityFactoryV2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link CapabilityFactory} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CapabilityFactory { + Map>> capabilityDeviceFactories = new HashMap<>(); + + private CapabilityFactory() { + List> factories = Arrays.asList(new ACCapabilityFactoryV1(), + new ACCapabilityFactoryV2(), new FridgeCapabilityFactoryV1(), new FridgeCapabilityFactoryV2(), + new WasherDryerCapabilityFactoryV1(), new WasherDryerCapabilityFactoryV2()); + factories.forEach(f -> { + f.getSupportedDeviceTypes().forEach(d -> { + Map> versionMap = capabilityDeviceFactories.get(d); + if (versionMap == null) { + versionMap = new HashMap<>(); + } + for (LGAPIVerion v : f.getSupportedAPIVersions()) { + versionMap.put(v, f); + } + ; + capabilityDeviceFactories.put(d, versionMap); + }); + }); + } + + private static final CapabilityFactory instance; + static { + instance = new CapabilityFactory(); + } + private static final Logger logger = LoggerFactory.getLogger(CapabilityFactory.class); + private static final ObjectMapper mapper = new ObjectMapper(); + + public static CapabilityFactory getInstance() { + return instance; + } + + public C create(JsonNode rootNode, Class clazz) throws LGThinqException { + DeviceTypes type = ModelUtils.getDeviceType(rootNode); + LGAPIVerion version = ModelUtils.discoveryAPIVersion(rootNode); + logger.info("Getting factory for device type:{} and version:{}", type.deviceTypeId(), version); + Map> versionsFactory = capabilityDeviceFactories + .get(type); + if (versionsFactory == null || versionsFactory.isEmpty()) { + throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); + } + AbstractCapabilityFactory factory = versionsFactory.get(version); + if (factory == null) { + throw new IllegalStateException( + "Unexpected capability. The type " + type + " and version " + version + " was not implemented yet"); + } + return clazz.cast(factory.create(rootNode)); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java new file mode 100644 index 0000000000000..53290c86e2f8c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java @@ -0,0 +1,112 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link CommandDefinition} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CommandDefinition { + private static final Logger logger = LoggerFactory.getLogger(CommandDefinition.class); + /** + * This is the command tag value that is used by the API to launch the command service + */ + private String command = ""; + private Map data = new HashMap<>(); + + // =========== Used only for thinq V1 commands ============= + private String cmdOpt = ""; + private String cmdOptValue = ""; + private boolean isBinary; + // This is the template in the device definition of data that must be send to the LG API complementing the command + private String dataTemplate = ""; + /* + * holds the how command (in text) as defined in the node command definition. Ex: For Remote Start (WM): + * { + * "cmd":"Control", + * "cmdOpt":"Operation", + * "value":"Start", + * "data": + * "[{{Course}},{{Wash}},{{SpinSpeed}},{{WaterTemp}},{{RinseOption}},0,{{Reserve_Time_H}},{{Reserve_Time_M}},{{LoadItem}},{{Option1}},{{Option2}},0,{{SmartCourse}},0]", + * "encode":true + * } + */ + private String rawCommand = ""; + + // ========================================================= + + public String getRawCommand() { + return rawCommand; + } + + public void setRawCommand(String rawCommand) { + this.rawCommand = rawCommand; + } + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } + + public String getCmdOpt() { + return cmdOpt; + } + + public void setCmdOpt(String cmdOpt) { + this.cmdOpt = cmdOpt; + } + + public String getCmdOptValue() { + return cmdOptValue; + } + + public void setCmdOptValue(String cmdOptValue) { + this.cmdOptValue = cmdOptValue; + } + + public boolean isBinary() { + return isBinary; + } + + public void setBinary(boolean binary) { + isBinary = binary; + } + + public String getDataTemplate() { + return dataTemplate; + } + + public void setDataTemplate(String dataTemplate) { + this.dataTemplate = dataTemplate; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java new file mode 100644 index 0000000000000..7710226bcf77d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java @@ -0,0 +1,284 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link DefaultSnapshotBuilder} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class DefaultSnapshotBuilder implements SnapshotBuilder { + protected final Class snapClass; + protected static final ObjectMapper objectMapper = new ObjectMapper(); + private static final Logger logger = LoggerFactory.getLogger(DefaultSnapshotBuilder.class); + + public DefaultSnapshotBuilder(Class clazz) { + snapClass = clazz; + } + + private static final Map>> modelsCachedBitKeyDefinitions = new HashMap<>(); + + /** + * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) + * + * @param binaryData V1: decoded returnedData + * @param capDef + * @return returns Snapshot implementation based on device type provided + * @throws LGThinqApiException any error. + */ + @Override + public S createFromBinary(String binaryData, List prot, CapabilityDefinition capDef) + throws LGThinqUnmarshallException, LGThinqApiException { + try { + Map snapValues = new HashMap<>(); + byte[] data = binaryData.getBytes(); + BeanInfo beanInfo = Introspector.getBeanInfo(snapClass); + S snap = snapClass.getConstructor().newInstance(); + PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); + Map aliasesMethod = new HashMap<>(); + for (PropertyDescriptor property : pds) { + // all attributes of class. + Method m = property.getReadMethod(); // getter + if (m.isAnnotationPresent(JsonProperty.class)) { + String value = m.getAnnotation(JsonProperty.class).value(); + aliasesMethod.putIfAbsent(value, property); + } + if (m.isAnnotationPresent(JsonAlias.class)) { + String[] values = m.getAnnotation(JsonAlias.class).value(); + for (String v : values) { + aliasesMethod.putIfAbsent(v, property); + } + + } + } + for (MonitoringBinaryProtocol protField : prot) { + String fName = protField.fieldName; + int value = 0; + for (int i = protField.startByte; i < protField.startByte + protField.length; i++) { + value = (value << 8) + data[i]; + } + snapValues.put(fName, value); + PropertyDescriptor property = aliasesMethod.get(fName); + if (property != null) { + // found property. Get bit value + Method m = property.getWriteMethod(); + if (m.getParameters()[0].getType() == String.class) { + m.invoke(snap, String.valueOf(value)); + } else if (m.getParameters()[0].getType() == Double.class) { + m.invoke(snap, (double) value); + } else if (m.getParameters()[0].getType() == Integer.class) { + m.invoke(snap, value); + } else { + throw new IllegalArgumentException( + String.format("Parameter type not supported for this factory:%s", + m.getParameters()[0].getType().toString())); + } + } + } + snap.setRawData(snapValues); + return snap; + } catch (IntrospectionException | InvocationTargetException | InstantiationException | IllegalAccessException + | NoSuchMethodException e) { + throw new LGThinqUnmarshallException("Unexpected Error unmarshalling binary data", e); + } + } + + /** + * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) + * + * @param snapshotDataJson V1: decoded returnedData; V2: snapshot body + * @param deviceType device type + * @return returns Snapshot implementation based on device type provided + * @throws LGThinqApiException any error. + */ + @Override + public S createFromJson(String snapshotDataJson, DeviceTypes deviceType, CapabilityDefinition capDef) + throws LGThinqUnmarshallException, LGThinqApiException { + try { + Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { + }); + Map deviceSetting = new HashMap<>(); + deviceSetting.put("deviceType", deviceType.deviceTypeId()); + deviceSetting.put("snapshot", snapshotMap); + return createFromJson(deviceSetting, capDef); + } catch (JsonProcessingException e) { + throw new LGThinqUnmarshallException("Unexpected Error unmarshalling json to map", e); + } + } + + @Override + public S createFromJson(Map deviceSettings, CapabilityDefinition capDef) + throws LGThinqApiException { + DeviceTypes type = getDeviceType(deviceSettings); + Map snapMap = ((Map) deviceSettings.get("snapshot")); + if (snapMap == null) { + throw new LGThinqApiException("snapshot node not present in device monitoring result."); + } + LGAPIVerion version = discoveryAPIVersion(snapMap, type); + return getSnapshot(snapMap, capDef); + } + + protected abstract S getSnapshot(Map snapMap, CapabilityDefinition capDef); + + protected DeviceTypes getDeviceType(Map rootMap) { + Integer deviceTypeId = (Integer) rootMap.get("deviceType"); + // device code is only present in v2 devices snapshot. + String deviceCode = Objects.requireNonNullElse((String) rootMap.get("deviceCode"), ""); + Objects.requireNonNull(deviceTypeId, "Unexpected error. deviceType field not present in snapshot schema"); + return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); + } + + protected abstract LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type); + + /** + * Used + * + * @param key + * @param capFeatureValues + * @param cachedBitKey + * @return + */ + private Map getBitKey(String key, final Map> capFeatureValues, + final Map> cachedBitKey) { + // Define a local function to search for the bit key + Function>, Map> searchBitKey = data -> { + if (data.isEmpty()) { + return Collections.emptyMap(); + } + + for (int i = 1; i <= 3; i++) { + String optKey = "Option" + i; + Map option = data.get(optKey); + + if (option == null) { + continue; + } + + List> optionList = (List>) option.get("option"); + + if (optionList == null) { + continue; + } + + for (Map opt : optionList) { + String value = (String) opt.get("value"); + + if (key.equals(value)) { + Integer startBit = (Integer) opt.get("startbit"); + Integer length = (Integer) opt.getOrDefault("length", 1); + + if (startBit == null) { + return Collections.emptyMap(); + } + + Map bitKey = new HashMap<>(); + bitKey.put("option", optKey); + bitKey.put("startbit", startBit); + bitKey.put("length", length); + + return bitKey; + } + } + } + + return Collections.emptyMap(); + }; + + Map bitKey = cachedBitKey.get(key); + + if (bitKey == null) { + // cache the bitKey if it doesn't was fetched yet. + bitKey = searchBitKey.apply(capFeatureValues); + cachedBitKey.put(key, bitKey); + } + + return bitKey; + } + + /** + * Return the value related to the bit-value definition. It's used in Waser/Dryer V1 snapshot parser. + * It was here, in the parent, because maybe other devices need the same functionality. If not, + * We can transfer these methods to the WasherDryer Snapshot Builder. + * + * @param key Key trying to get the value + * @param snapRawValues snap raw value + * @param capDef capability + * @return return value associated or blank string + */ + protected String bitValue(String key, Map snapRawValues, final CapabilityDefinition capDef) { + // get the capability Values/MonitoringValues Map + // Look up the bit value for a specific key + if (snapRawValues.isEmpty()) { + logger.warn("No snapshot raw values provided. Corrupted data returned or bug"); + return ""; + } + Map> cachedBitKey = getSpecificCacheBitKey(capDef); + Map bitKey = this.getBitKey(key, capDef.getFeatureValuesRawData(), cachedBitKey); + if (bitKey.isEmpty()) { + logger.warn("BitKey {} not found in the Options feature values description capability. It's mostly a bug", + key); + return ""; + } + // Get the name of the option (Option1, Option2, etc) that contains the key (ex. LoadItem, RemoteStart) desired + String option = (String) bitKey.get("option"); + Object bitValueDef = snapRawValues.get(option); + if (bitValueDef == null) { + logger.warn("Value definition not found for the bitValue definition: {}. It's mostly a bug", option); + return ""; + } + String value = bitValueDef.toString(); + if (value.isEmpty()) { + return "0"; + } + + int bitValue = Integer.parseInt(value); + int startBit = (int) bitKey.get("startbit"); + int length = (int) bitKey.get("length"); + int val = 0; + + for (int i = 0; i < length; i++) { + int bitIndex = (int) Math.pow(2, (startBit + i)); + int bit = (bitValue & bitIndex) != 0 ? 1 : 0; + val += bit * (int) Math.pow(2, i); + } + + return Integer.toString(val); + } + + protected synchronized Map> getSpecificCacheBitKey(CapabilityDefinition capDef) { + return Objects.requireNonNull( + modelsCachedBitKeyDefinitions.computeIfAbsent(capDef.getModelName(), k -> new HashMap<>())); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java new file mode 100644 index 0000000000000..385eca34fcd21 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java @@ -0,0 +1,71 @@ +/** + * 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.lgthinq.lgservices.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link DevicePowerState} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum DevicePowerState { + DV_POWER_ON(1), + DV_POWER_OFF(0), + DV_POWER_UNK(-1); + + private final int powerState; + + public double getValue() { + return powerState; + } + + DevicePowerState(int i) { + powerState = i; + } + + public static DevicePowerState statusOf(double value) { + switch ((int) value) { + case 0: + return DV_POWER_OFF; + case 1: + case 256: + case 257: + return DV_POWER_ON; + + default: + return DV_POWER_UNK; + } + } + + public static double valueOf(DevicePowerState dps) { + return dps.powerState; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case DV_POWER_ON: + return 257;// "@AC_MAIN_OPERATION_ALL_ON_W" + case DV_POWER_OFF: + return 0; // "@AC_MAIN_OPERATION_OFF_W" + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java new file mode 100644 index 0000000000000..fe295323a49f0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java @@ -0,0 +1,21 @@ +/** + * 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.lgthinq.lgservices.model; + +/** + * The {@link DeviceRegistryService} + * + * @author Nemer Daud - Initial contribution + */ +public class DeviceRegistryService { +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java new file mode 100644 index 0000000000000..94af3ceb14795 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java @@ -0,0 +1,94 @@ +/** + * 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.lgthinq.lgservices.model; + +/** + * The {@link DeviceTypes} + * + * @author Nemer Daud - Initial contribution + */ +public enum DeviceTypes { + AIR_CONDITIONER(401, "AC", ""), + + HEAT_PUMP(401, "AC", "AWHP"), + WASHERDRYER_MACHINE(201, "WM", ""), + + WASHING_TOWER(221, "WM", ""), + DRYER(202, "DR", "Dryer"), + DRYER_TOWER(222, "DR", "Dryer"), + REFRIGERATOR(101, "REF", "Fridge"), + UNKNOWN(-1, "", ""); + + private final int deviceTypeId; + private final String deviceTypeAcron; + private final String deviceSubModel; + + public String deviceTypeAcron() { + return deviceTypeAcron; + } + + public int deviceTypeId() { + return deviceTypeId; + } + + public String deviceSubModel() { + return deviceSubModel; + } + + public static DeviceTypes fromDeviceTypeId(int deviceTypeId, String deviceCode) { + switch (deviceTypeId) { + case 401: + if ("AI05".equals(deviceCode)) { + return HEAT_PUMP; + } + return AIR_CONDITIONER; + case 201: + return WASHERDRYER_MACHINE; + case 221: + return WASHING_TOWER; + case 202: + return DRYER; + case 222: + return DRYER_TOWER; + case 101: + return REFRIGERATOR; + default: + return UNKNOWN; + } + } + + public static DeviceTypes fromDeviceTypeAcron(String deviceTypeAcron, String modelType) { + switch (deviceTypeAcron) { + case "AC": + if ("AWHP".equals(modelType)) { + return HEAT_PUMP; + } + return AIR_CONDITIONER; + case "WM": + if ("Dryer".equals(modelType)) { + return DRYER; + } + return WASHERDRYER_MACHINE; + case "REF": + return REFRIGERATOR; + default: + return UNKNOWN; + } + } + + DeviceTypes(int i, String n, String submodel) { + this.deviceTypeId = i; + this.deviceTypeAcron = n; + this.deviceSubModel = submodel; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java new file mode 100644 index 0000000000000..4d1d4ef0d26ec --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java @@ -0,0 +1,45 @@ +/** + * 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.lgthinq.lgservices.model; + +/** + * The {@link FeatureDataType} + * Feature is the values the device has to expose its sensor attributes + * + * @author Nemer Daud - Initial contribution + */ +public enum FeatureDataType { + ENUM, + RANGE, + BOOLEAN, + BIT, + REFERENCE, + UNDEF; + + public static FeatureDataType fromValue(String value) { + switch (value.toLowerCase()) { + case "enum": + return ENUM; + case "boolean": + return BOOLEAN; + case "bit": + return BIT; + case "range": + return RANGE; + case "reference": + return REFERENCE; + default: + return UNDEF; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java new file mode 100644 index 0000000000000..d7043951d8084 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java @@ -0,0 +1,124 @@ +/** + * 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.lgthinq.lgservices; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; + +/** + * The {@link FeatureDefinition} defines the feature definitions extracted from the capability files in + * the MonitoringValue/Value session. All features are read-only by default. The factory must change-it if + * a specific one can be represented by a Writable Channel. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FeatureDefinition { + public static final FeatureDefinition NULL_DEFINITION = new FeatureDefinition(); + String name = ""; + String channelId = ""; + String refChannelId = ""; + String label = ""; + Boolean readOnly = true; + FeatureDataType dataType = FeatureDataType.UNDEF; + Map valuesMapping = new HashMap<>(); + + /** + * Return the optional referenced channel Id. In some cases, the feature has a reference from another channel. + * In other words, in some cases, it copies or use value hold for other channels. + * + * @return the optional referenced field for this feature + */ + public String getRefChannelId() { + return refChannelId; + } + + /** + * Set the optional reference field for this channel In some cases, the feature has a reference from another + * channel. + * In other words, in some cases, it copies or use value hold for other channels. + * + * @param refChannelId the optional referenced field for this feature + */ + public void setRefChannelId(String refChannelId) { + this.refChannelId = refChannelId; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public FeatureDataType getDataType() { + return dataType; + } + + public Boolean isReadOnly() { + return readOnly; + } + + public void setReadOnly(Boolean readOnly) { + this.readOnly = readOnly; + } + + public void setDataType(FeatureDataType dataType) { + this.dataType = dataType; + } + + public Map getValuesMapping() { + return valuesMapping; + } + + public void setValuesMapping(Map valuesMapping) { + this.valuesMapping = valuesMapping; + } + + public String getChannelId() { + return channelId; + } + + public void setChannelId(String channelId) { + this.channelId = channelId; + } + + @Override + public boolean equals(@Nullable Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + FeatureDefinition that = (FeatureDefinition) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java new file mode 100644 index 0000000000000..d7f51103a4e77 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java @@ -0,0 +1,34 @@ +/** + * 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.lgthinq.lgservices.model; + +/** + * The {@link LGAPIVerion} + * + * @author Nemer Daud - Initial contribution + */ +public enum LGAPIVerion { + V1_0(1.0), + V2_0(2.0), + UNDEF(0.0); + + private final double version; + + LGAPIVerion(double v) { + version = v; + } + + public double getValue() { + return version; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java new file mode 100644 index 0000000000000..a3aacfd72695b --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java @@ -0,0 +1,107 @@ +/** + * 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.lgthinq.lgservices.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link LGDevice} + * + * @author Nemer Daud - Initial contribution + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@NonNullByDefault +public class LGDevice { + private String modelName = ""; + @JsonProperty("deviceType") + private int deviceTypeId; + private String deviceCode = ""; + private String alias = ""; + private String deviceId = ""; + private String platformType = ""; + private String modelJsonUri = ""; + private boolean online; + + public String getModelName() { + return modelName; + } + + @JsonIgnore + public DeviceTypes getDeviceType() { + return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public int getDeviceTypeId() { + return deviceTypeId; + } + + public void setDeviceTypeId(int deviceTypeId) { + this.deviceTypeId = deviceTypeId; + } + + public String getDeviceCode() { + return deviceCode; + } + + public void setDeviceCode(String deviceCode) { + this.deviceCode = deviceCode; + } + + public String getModelJsonUri() { + return modelJsonUri; + } + + public void setModelJsonUri(String modelJsonUri) { + this.modelJsonUri = modelJsonUri; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getPlatformType() { + return platformType; + } + + public void setPlatformType(String platformType) { + this.platformType = platformType; + } + + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java new file mode 100644 index 0000000000000..63f7969c56bfb --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java @@ -0,0 +1,83 @@ +/** + * 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.lgthinq.lgservices.model; + +import static org.openhab.binding.lgthinq.lgservices.model.DeviceTypes.fromDeviceTypeAcron; + +import java.util.Map; +import java.util.Objects; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link ModelUtils} + * + * @author Nemer Daud - Initial contribution + */ +public class ModelUtils { + public static final ObjectMapper objectMapper = new ObjectMapper(); + + public static DeviceTypes getDeviceType(Map rootMap) { + Map infoMap = (Map) rootMap.get("Info"); + Objects.requireNonNull(infoMap, "Unexpected error. Info node not present in capability schema"); + String productType = infoMap.get("productType"); + String modelType = infoMap.get("modelType"); + Objects.requireNonNull(infoMap, "Unexpected error. ProductType attribute not present in capability schema"); + return fromDeviceTypeAcron(productType, modelType); + } + + public static DeviceTypes getDeviceType(JsonNode rootNode) { + Map mapper = objectMapper.convertValue(rootNode, new TypeReference<>() { + }); + return getDeviceType(mapper); + } + + public static LGAPIVerion discoveryAPIVersion(JsonNode rootNode) { + Map mapper = objectMapper.convertValue(rootNode, new TypeReference<>() { + }); + return discoveryAPIVersion(mapper); + } + + public static LGAPIVerion discoveryAPIVersion(Map rootMap) { + DeviceTypes type = getDeviceType(rootMap); + switch (type) { + case AIR_CONDITIONER: + case HEAT_PUMP: + Map valueNode = (Map) rootMap.get("Value"); + if (valueNode.containsKey("support.airState.opMode")) { + return LGAPIVerion.V2_0; + } else if (valueNode.containsKey("SupportOpMode")) { + return LGAPIVerion.V1_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); + } + + case WASHERDRYER_MACHINE: + case DRYER: + case REFRIGERATOR: + if (rootMap.containsKey("Value")) { + return LGAPIVerion.V1_0; + } else if (rootMap.containsKey("MonitoringValue")) { + return LGAPIVerion.V2_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); + } + default: + throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringBinaryProtocol.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringBinaryProtocol.java new file mode 100644 index 0000000000000..0c5c8903a660c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringBinaryProtocol.java @@ -0,0 +1,34 @@ +/** + * 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.lgthinq.lgservices.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link MonitoringBinaryProtocol} + * + * @author Nemer Daud - Initial contribution + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@NonNullByDefault +public class MonitoringBinaryProtocol { + @JsonProperty("startByte") + public int startByte; + @JsonProperty("length") + public int length; + @JsonProperty("value") + public String fieldName = ""; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java new file mode 100644 index 0000000000000..04e9c004ce8ee --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java @@ -0,0 +1,50 @@ +/** + * 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.lgthinq.lgservices.model; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link MonitoringResultFormat} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum MonitoringResultFormat { + JSON_FORMAT(""), + BINARY_FORMAT("BINARY(BYTE)"), + UNKNOWN_FORMAT("UNKNOWN_FORMAT"); + + final String format; + + MonitoringResultFormat(String format) { + this.format = format; + } + + public String getFormat() { + return format; + } + + public static MonitoringResultFormat getFormatOf(@NonNull String formatValue) { + switch (formatValue.toUpperCase()) { + case "BINARY(BYTE)": + return BINARY_FORMAT; + case "JSON": + return JSON_FORMAT; + default: + return UNKNOWN_FORMAT; + + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java new file mode 100644 index 0000000000000..7886807c35df0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java @@ -0,0 +1,172 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +/** + * The {@link ResultCodes} + * + * @author Nemer Daud - Initial contribution + */ + +public enum ResultCodes { + + OK("Success", "0000", "0001"), + DEVICE_NOT_RESPONSE("Device Not Response", "0111", "0103", "0104", "0106"), + PORTAL_INTERWORKING_ERROR("Portal Internal Error", "0007"), + LOGIN_FAILED( + "Login/Session Failed, Duplicated or Terms Not Agreed. Try to login and correct issues direct on LG Account Portal", + "0004", "0102", "0110", "0114"), + BASE64_CODING_ERROR("Base64 Decoding/Encoding error", "9002", "9001"), + NOT_SUPPORTED_CONTROL("Commnad/Control/Service is not supported", "0005", "0012", "8001"), + CONTROL_ERROR("Error in device control", "0105"), + LG_SERVER_ERROR("LG Server Error", "8101", "8102", "8103", "8104", "8105", "8106", "8107", "9003", "9004", "9005", + "9000", "8900", "0107"), + PAYLOAD_ERROR("Malformed or Wrong Payload", "9999"), + DUPLICATED_DATA("Duplicated Data/Alias", "0008", "0013"), + ACCESS_DENIED("Access Denied. Verify your account/password in LG Account Portal.", "9006", "0011", "0113"), + NOT_SUPPORTED_COUNTRY("Country not supported.", "8000"), + NETWORK_FAILED("Timeout/Network has failed.", "9020"), + LIMIT_EXCEEDED_ERROR("Limit has been exceeded", "0112"), + CUSTOMER_NUMBER_EXPIRED("Customer number has been expired", "0119"), + INVALID_CUSTOMER_DATA("Customer data is invalid", "0010"), + GENERAL_FAILURE("General Failure", "0100"), + INVALID_CSR("Invalid CSR", "9010"), + INVALID_PAYLOAD("Invalid Body/Payload", "0002"), + INVALID_CUSTOMER_NUMBER("Invalid Customer Number", "0118", "120"), + INVALID_HEAD("Invalid Request Head", "0003"), + INVALID_PUSH_TOKEN("Invalid Push Token", "0301"), + INVALID_REQUEST("Invalid request", "0116"), + NOT_REGISTERED_SMART_CARE("Smart Care not registered", "0121"), + DEVICE_MISMATCH("Device/Group mismatch or device/model doesn't exist in your account.", "0115", "0006", "0009", + "0117", "0014"), + NO_INFORMATION_FOUND("No information found for the arguments", "109", "108"), + OTHER("Error processing request."), + UNKNOWN("UNKNOWN", ""); + + public static final Map OTHER_ERROR_CODE_RESPONSE = Map.ofEntries( + + Map.entry("0109", "NO_INFORMATION_DR"), Map.entry("0108", "NO_INFORMATION_SLEEP_MODE")); + + private final String description; + private final List codes; + + public boolean containsResultCode(String code) { + return codes.contains(code); + } + + public String getDescription() { + return description; + } + + public List getCodes() { + return codes; + } + + ResultCodes(String description, String... codes) { + this.codes = Arrays.asList(codes); + this.description = description; + } + + public static ResultCodes fromCode(String code) { + switch (code) { + case "0000": + case "0001": + return OK; + case "0002": + return INVALID_PAYLOAD; + case "0003": + return INVALID_HEAD; + case "0004": + case "0102": + case "0110": // Email duplicated + case "0114": + return LOGIN_FAILED; + case "0100": + return GENERAL_FAILURE; + case "0116": + return INVALID_REQUEST; + case "0108": + case "0109": + return NO_INFORMATION_FOUND; + case "0115": + case "0006": + case "0009": + case "0117": + case "0014": + case "0101": + return DEVICE_MISMATCH; + case "0010": + return INVALID_CUSTOMER_DATA; + case "0112": + return LIMIT_EXCEEDED_ERROR; + case "0118": + case "0120": + return INVALID_CUSTOMER_NUMBER; + case "0121": + return NOT_REGISTERED_SMART_CARE; + case "0007": + return PORTAL_INTERWORKING_ERROR; + case "0008": + case "0013": + return DUPLICATED_DATA; + case "0005": + case "0012": + case "8001": + return NOT_SUPPORTED_CONTROL; + case "0111": + case "0103": + case "0104": + case "0106": + return DEVICE_NOT_RESPONSE; + case "0105": + return CONTROL_ERROR; + case "9001": + case "9002": + return BASE64_CODING_ERROR; + case "0107": + case "8101": + case "8102": + case "8203": + case "8204": + case "8205": + case "8206": + case "8207": + case "8900": + case "9000": + case "9003": + case "9004": + case "9005": + return LG_SERVER_ERROR; + case "9999": + return PAYLOAD_ERROR; + case "9006": + case "0011": + case "0113": + return ACCESS_DENIED; + case "9010": + return INVALID_CSR; + case "0301": + return INVALID_PUSH_TOKEN; + default: + if (OTHER_ERROR_CODE_RESPONSE.containsKey(code)) { + return OTHER; + } + return UNKNOWN; + + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java new file mode 100644 index 0000000000000..0218eb42692ad --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java @@ -0,0 +1,36 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; + +/** + * The {@link SnapshotBuilder} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface SnapshotBuilder { + S createFromBinary(String binaryData, List prot, CapabilityDefinition capDef) + throws LGThinqUnmarshallException, LGThinqApiException; + + S createFromJson(String snapshotDataJson, DeviceTypes deviceType, CapabilityDefinition capDef) + throws LGThinqUnmarshallException, LGThinqApiException; + + S createFromJson(Map deviceSettings, CapabilityDefinition capDef) throws LGThinqApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java new file mode 100644 index 0000000000000..ecdcfa7aede3d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java @@ -0,0 +1,64 @@ +/** + * 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.lgthinq.lgservices.model; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACSnapshotBuilder; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeSnapshotBuilder; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshotBuilder; + +/** + * The {@link SnapshotBuilderFactory} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class SnapshotBuilderFactory { + private final Map, SnapshotBuilder> internalBuilders = new HashMap<>(); + + private static final SnapshotBuilderFactory instance; + static { + instance = new SnapshotBuilderFactory(); + } + + private SnapshotBuilderFactory() { + }; + + public static SnapshotBuilderFactory getInstance() { + return instance; + } + + public SnapshotBuilder getBuilder(Class snapDef) { + SnapshotBuilder result = internalBuilders.get(snapDef); + if (result == null) { + if (snapDef.equals(WasherDryerSnapshot.class)) { + result = new WasherDryerSnapshotBuilder(); + } else if (snapDef.equals(ACCanonicalSnapshot.class)) { + result = new ACSnapshotBuilder(); + } else if (snapDef.equals(FridgeCanonicalSnapshot.class)) { + result = new FridgeSnapshotBuilder(); + } else { + throw new IllegalStateException( + "Snapshot definition " + snapDef + " not supported by this Factory. It most likely a bug"); + } + internalBuilders.put(snapDef, result); + } + return result; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotDefinition.java new file mode 100644 index 0000000000000..247706a6f16f8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotDefinition.java @@ -0,0 +1,31 @@ +/** + * 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.lgthinq.lgservices.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link SnapshotDefinition} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface SnapshotDefinition { + DevicePowerState getPowerStatus(); + + void setPowerStatus(DevicePowerState value); + + boolean isOnline(); + + void setOnline(boolean online); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java new file mode 100644 index 0000000000000..aa9779dfaa126 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java @@ -0,0 +1,186 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link ACCanonicalSnapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class ACCanonicalSnapshot extends AbstractSnapshotDefinition { + + private int airWindStrength; + + private double targetTemperature; + + private double currentTemperature; + + private boolean coolJetModeOn; + + private double airCleanMode; + private double coolJetMode; + private double autoDryMode; + private double energySavingMode; + + private int operationMode; + @Nullable + private Integer operation; + @JsonIgnore + private boolean online; + + private double energyConsumption; + + @JsonIgnore + public DevicePowerState getPowerStatus() { + return operation == null ? DevicePowerState.DV_POWER_UNK : DevicePowerState.statusOf(operation); + } + + @JsonIgnore + public void setPowerStatus(DevicePowerState value) { + operation = (int) value.getValue(); + } + + @JsonIgnore + public ACFanSpeed getAcFanSpeed() { + return ACFanSpeed.statusOf(airWindStrength); + } + + @JsonProperty("airState.windStrength") + @JsonAlias("WindStrength") + public Integer getAirWindStrength() { + return airWindStrength; + } + + @JsonProperty("airState.wMode.jet") + @JsonAlias("Jet") + public Double getCoolJetMode() { + return coolJetMode; + } + + @JsonProperty("airState.wMode.airClean") + @JsonAlias("AirClean") + public Double getAirCleanMode() { + return airCleanMode; + } + + @JsonProperty("airState.miscFuncState.autoDry") + @JsonAlias("AutoDry") + public Double getAutoDryMode() { + return autoDryMode; + } + + @JsonProperty("airState.powerSave.basic") + @JsonAlias("PowerSave") + public Double getEnergySavingMode() { + return energySavingMode; + } + + public void setAirCleanMode(double airCleanMode) { + this.airCleanMode = airCleanMode; + } + + public void setAutoDryMode(double autoDryMode) { + this.autoDryMode = autoDryMode; + } + + public void setEnergySavingMode(double energySavingMode) { + this.energySavingMode = energySavingMode; + } + + public void setCoolJetMode(Double coolJetMode) { + this.coolJetMode = coolJetMode; + } + + public void setAirWindStrength(Integer airWindStrength) { + this.airWindStrength = airWindStrength; + } + + @JsonProperty("airState.energy.onCurrent") + public double getEnergyConsumption() { + return energyConsumption; + } + + public void setEnergyConsumption(double energyConsumption) { + this.energyConsumption = energyConsumption; + } + + @JsonProperty("airState.tempState.target") + @JsonAlias("TempCfg") + public Double getTargetTemperature() { + return targetTemperature; + } + + public void setTargetTemperature(Double targetTemperature) { + this.targetTemperature = targetTemperature; + } + + @JsonProperty("airState.tempState.current") + @JsonAlias("TempCur") + public Double getCurrentTemperature() { + return currentTemperature; + } + + public void setCurrentTemperature(Double currentTemperature) { + this.currentTemperature = currentTemperature; + } + + @JsonProperty("airState.opMode") + @JsonAlias("OpMode") + public Integer getOperationMode() { + return operationMode; + } + + public void setOperationMode(Integer operationMode) { + this.operationMode = operationMode; + } + + @Nullable + @JsonProperty("airState.operation") + @JsonAlias("Operation") + public Integer getOperation() { + return operation; + } + + public void setOperation(Integer operation) { + this.operation = operation; + } + + @JsonIgnore + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } + + @Override + public String toString() { + return "ACSnapShot{" + "airWindStrength=" + airWindStrength + ", targetTemperature=" + targetTemperature + + ", currentTemperature=" + currentTemperature + ", operationMode=" + operationMode + ", operation=" + + operation + ", acPowerStatus=" + getPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() + ", acOpMode=" + + ", online=" + isOnline() + " }"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java new file mode 100644 index 0000000000000..1a6b15dbd2c09 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java @@ -0,0 +1,157 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import java.util.Collections; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; + +/** + * The {@link ACCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACCapability extends AbstractCapability { + + private Map opMod = Collections.emptyMap(); + private Map fanSpeed = Collections.emptyMap(); + private boolean isJetModeAvailable; + private boolean isAutoDryModeAvailable; + private boolean isEnergySavingAvailable; + private boolean isAirCleanAvailable; + private String coolJetModeCommandOn = ""; + private String coolJetModeCommandOff = ""; + private String autoDryModeCommandOn = ""; + private String autoDryModeCommandOff = ""; + + private String energySavingModeCommandOn = ""; + private String energySavingModeCommandOff = ""; + + private String airCleanModeCommandOn = ""; + private String airCleanModeCommandOff = ""; + + public String getCoolJetModeCommandOff() { + return coolJetModeCommandOff; + } + + public void setCoolJetModeCommandOff(String coolJetModeCommandOff) { + this.coolJetModeCommandOff = coolJetModeCommandOff; + } + + public String getCoolJetModeCommandOn() { + return coolJetModeCommandOn; + } + + public void setCoolJetModeCommandOn(String coolJetModeCommandOn) { + this.coolJetModeCommandOn = coolJetModeCommandOn; + } + + public Map getOpMode() { + return opMod; + } + + public void setOpMod(Map opMod) { + this.opMod = opMod; + } + + public Map getFanSpeed() { + return fanSpeed; + } + + public void setFanSpeed(Map fanSpeed) { + this.fanSpeed = fanSpeed; + } + + public void setJetModeAvailable(boolean jetModeAvailable) { + this.isJetModeAvailable = jetModeAvailable; + } + + public boolean isAutoDryModeAvailable() { + return isAutoDryModeAvailable; + } + + public void setAutoDryModeAvailable(boolean autoDryModeAvailable) { + isAutoDryModeAvailable = autoDryModeAvailable; + } + + public boolean isEnergySavingAvailable() { + return isEnergySavingAvailable; + } + + public void setEnergySavingAvailable(boolean energySavingAvailable) { + isEnergySavingAvailable = energySavingAvailable; + } + + public boolean isAirCleanAvailable() { + return isAirCleanAvailable; + } + + public void setAirCleanAvailable(boolean airCleanAvailable) { + isAirCleanAvailable = airCleanAvailable; + } + + public boolean isJetModeAvailable() { + return this.isJetModeAvailable; + } + + public String getAutoDryModeCommandOn() { + return autoDryModeCommandOn; + } + + public void setAutoDryModeCommandOn(String autoDryModeCommandOn) { + this.autoDryModeCommandOn = autoDryModeCommandOn; + } + + public String getAutoDryModeCommandOff() { + return autoDryModeCommandOff; + } + + public void setAutoDryModeCommandOff(String autoDryModeCommandOff) { + this.autoDryModeCommandOff = autoDryModeCommandOff; + } + + public String getEnergySavingModeCommandOn() { + return energySavingModeCommandOn; + } + + public void setEnergySavingModeCommandOn(String energySavingModeCommandOn) { + this.energySavingModeCommandOn = energySavingModeCommandOn; + } + + public String getEnergySavingModeCommandOff() { + return energySavingModeCommandOff; + } + + public void setEnergySavingModeCommandOff(String energySavingModeCommandOff) { + this.energySavingModeCommandOff = energySavingModeCommandOff; + } + + public String getAirCleanModeCommandOn() { + return airCleanModeCommandOn; + } + + public void setAirCleanModeCommandOn(String airCleanModeCommandOn) { + this.airCleanModeCommandOn = airCleanModeCommandOn; + } + + public String getAirCleanModeCommandOff() { + return airCleanModeCommandOff; + } + + public void setAirCleanModeCommandOff(String airCleanModeCommandOff) { + this.airCleanModeCommandOff = airCleanModeCommandOff; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java new file mode 100644 index 0000000000000..b0a143b8ad7bd --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java @@ -0,0 +1,108 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link ACCapabilityFactoryV1} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACCapabilityFactoryV1 extends AbstractACCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(ACCapabilityFactoryV1.class); + + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V1_0); + } + + @Override + protected Map extractFeatureOptions(JsonNode optionsNode) { + Map options = new HashMap<>(); + optionsNode.fields().forEachRemaining(o -> { + options.put(o.getKey(), o.getValue().asText()); + }); + return options; + } + + @Override + protected String getDataTypeFeatureNodeName() { + return "type"; + } + + @Override + protected String getOpModeNodeName() { + return "OpMode"; + } + + @Override + protected String getFanSpeedNodeName() { + return "WindStrength"; + } + + @Override + protected String getSupOpModeNodeName() { + return "SupportOpMode"; + } + + @Override + protected String getSupFanSpeedNodeName() { + return "SupportWindStrength"; + } + + @Override + protected String getJetModeNodeName() { + return "Jet"; + } + + @Override + protected String getSupSubRacModeNodeName() { + return "SupportRACSubMode"; + } + + @Override + protected String getSupRacModeNodeName() { + return "SupportRACMode"; + } + + @Override + protected String getAutoDryStateNodeName() { + return "AutoDry"; + } + + @Override + protected String getAirCleanStateNodeName() { + return "AirClean"; + } + + @Override + protected String getOptionsMapNodeName() { + return "option"; + } + + @Override + protected String getValuesNodeName() { + return "Value"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java new file mode 100644 index 0000000000000..3aea1e60112fe --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java @@ -0,0 +1,108 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link ACCapabilityFactoryV2} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACCapabilityFactoryV2 extends AbstractACCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(ACCapabilityFactoryV2.class); + + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V2_0); + } + + @Override + protected String getOpModeNodeName() { + return "airState.opMode"; + } + + @Override + protected String getFanSpeedNodeName() { + return "airState.windStrength"; + } + + @Override + protected String getSupOpModeNodeName() { + return "support.airState.opMode"; + } + + @Override + protected String getSupFanSpeedNodeName() { + return "support.airState.windStrength"; + } + + @Override + protected String getJetModeNodeName() { + return "airState.wMode.jet"; + } + + @Override + protected String getSupSubRacModeNodeName() { + return "support.racSubMode"; + } + + @Override + protected String getSupRacModeNodeName() { + return "support.racMode"; + } + + @Override + protected String getAutoDryStateNodeName() { + return "airState.miscFuncState.autoDry"; + } + + @Override + protected String getAirCleanStateNodeName() { + return "airState.wMode.airClean"; + } + + @Override + protected String getOptionsMapNodeName() { + return "value_mapping"; + } + + @Override + protected String getValuesNodeName() { + return "Value"; + } + + @Override + protected String getDataTypeFeatureNodeName() { + return "dataType"; + } + + @Override + protected Map extractFeatureOptions(JsonNode optionsNode) { + Map options = new HashMap<>(); + optionsNode.fields().forEachRemaining(o -> { + options.put(o.getKey(), o.getValue().path("label").asText()); + }); + return options; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java new file mode 100644 index 0000000000000..74faa4efcace7 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java @@ -0,0 +1,91 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACCanonicalSnapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACFanSpeed { + F1(2.0), + F2(3.0), + F3(4.0), + F4(5.0), + F5(6.0), + F_AUTO(8.0), + F_UNK(-1); + + private final double funStrength; + + ACFanSpeed(double v) { + this.funStrength = v; + } + + public static ACFanSpeed statusOf(double value) { + switch ((int) value) { + case 2: + return F1; + case 3: + return F2; + case 4: + return F3; + case 5: + return F4; + case 6: + return F5; + case 8: + return F_AUTO; + default: + return F_UNK; + } + } + + /** + * "0": "@AC_MAIN_WIND_STRENGTH_SLOW_W", + * "1": "@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", + * "2": "@AC_MAIN_WIND_STRENGTH_LOW_W", + * "3": "@AC_MAIN_WIND_STRENGTH_LOW_MID_W", + * "4": "@AC_MAIN_WIND_STRENGTH_MID_W", + * "5": "@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", + * "6": "@AC_MAIN_WIND_STRENGTH_HIGH_W", + * "7": "@AC_MAIN_WIND_STRENGTH_POWER_W", + * "8": "@AC_MAIN_WIND_STRENGTH_NATURE_W", + */ + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case F1: + return 2; + case F2: + return 3; + case F3: + return 4; + case F4: + return 5; + case F5: + return 6; + case F_AUTO: + return 8; + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java new file mode 100644 index 0000000000000..28a5ff759e712 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java @@ -0,0 +1,89 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACCanonicalSnapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACOpMode { + COOL(0), + DRY(1), + FAN(2), + AI(3), + HEAT(4), + AIRCLEAN(5), + ENSAV(8), + OP_UNK(-1); + + private final int opMode; + + ACOpMode(int v) { + this.opMode = v; + } + + public static ACOpMode statusOf(int value) { + switch ((int) value) { + case 0: + return COOL; + case 1: + return DRY; + case 2: + return FAN; + case 3: + return AI; + case 4: + return HEAT; + case 5: + return AIRCLEAN; + case 8: + return ENSAV; + default: + return OP_UNK; + } + } + + public int getValue() { + return this.opMode; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case COOL: + return 0;// "@AC_MAIN_OPERATION_MODE_COOL_W" + case DRY: + return 1; // "@AC_MAIN_OPERATION_MODE_DRY_W" + case FAN: + return 2; // "@AC_MAIN_OPERATION_MODE_FAN_W" + case AI: + return 3; // "@AC_MAIN_OPERATION_MODE_AI_W" + case HEAT: + return 4; // "@AC_MAIN_OPERATION_MODE_HEAT_W" + case AIRCLEAN: + return 5; // "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W" + case ENSAV: + return 8; // "AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W" + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java new file mode 100644 index 0000000000000..9661ccda96ab1 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java @@ -0,0 +1,72 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.*; + +/** + * The {@link ACSnapshotBuilder} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACSnapshotBuilder extends DefaultSnapshotBuilder { + public ACSnapshotBuilder() { + super(ACCanonicalSnapshot.class); + } + + @Override + public ACCanonicalSnapshot createFromBinary(String binaryData, List prot, + CapabilityDefinition capDef) throws LGThinqUnmarshallException, LGThinqApiException { + return super.createFromBinary(binaryData, prot, capDef); + } + + @Override + protected ACCanonicalSnapshot getSnapshot(Map snapMap, CapabilityDefinition capDef) { + ACCanonicalSnapshot snap; + switch (capDef.getDeviceType()) { + case AIR_CONDITIONER: + case HEAT_PUMP: + snap = objectMapper.convertValue(snapMap, snapClass); + snap.setRawData(snapMap); + return snap; + } + throw new IllegalStateException("Snapshot for device type " + capDef.getDeviceType() + + " not supported for this builder. It most likely a bug"); + } + + @Override + protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { + switch (type) { + case AIR_CONDITIONER: + case HEAT_PUMP: + if (snapMap.containsKey("airState.opMode")) { + return LGAPIVerion.V2_0; + } else if (snapMap.containsKey("OpMode")) { + return LGAPIVerion.V1_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); + } + default: + throw new IllegalStateException("Discovery version for device type " + type + + " not supported for this builder. It most likely a bug"); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACTargetTmp.java new file mode 100644 index 0000000000000..ef752dc851ad3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACTargetTmp.java @@ -0,0 +1,93 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACTargetTmp} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACTargetTmp { + _17(17.0), + _18(18.0), + _19(19.0), + _20(20.0), + _21(21.0), + _22(22.0), + _23(23.0), + _24(24.0), + _25(25.0), + _26(26.0), + _27(27.0), + _28(28.0), + _29(29.0), + _30(30.0), + UNK(-1); + + private final double targetTmp; + + ACTargetTmp(double v) { + this.targetTmp = v; + } + + public static ACTargetTmp statusOf(double value) { + switch ((int) value) { + case 17: + return _17; + case 18: + return _18; + case 19: + return _19; + case 20: + return _20; + case 21: + return _21; + case 22: + return _22; + case 23: + return _23; + case 24: + return _24; + case 25: + return _25; + case 26: + return _26; + case 27: + return _27; + case 28: + return _28; + case 29: + return _29; + case 30: + return _30; + default: + return UNK; + } + } + + public double getValue() { + return this.targetTmp; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + return (int) this.targetTmp; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java new file mode 100644 index 0000000000000..7b8f5352b54da --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java @@ -0,0 +1,250 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.lgservices.model.DeviceTypes.HEAT_PUMP; + +import java.util.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link AbstractACCapabilityFactory} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractACCapabilityFactory extends AbstractCapabilityFactory { + private final Logger logger = LoggerFactory.getLogger(AbstractACCapabilityFactory.class); + + @Override + public final List getSupportedDeviceTypes() { + return List.of(DeviceTypes.AIR_CONDITIONER, HEAT_PUMP); + } + + protected abstract Map extractFeatureOptions(JsonNode optionsNode); + + @Override + protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, + @Nullable String targetChannelId, @Nullable String refChannelId) { + JsonNode featureNode = featuresNode.path(featureName); + if (featureNode.isMissingNode()) { + return FeatureDefinition.NULL_DEFINITION; + } + FeatureDefinition fd = new FeatureDefinition(); + fd.setName(featureName); + fd.setDataType(FeatureDataType.fromValue(featureNode.path(getDataTypeFeatureNodeName()).asText())); + JsonNode options = featureNode.path(getOptionsMapNodeName()); + if (options.isMissingNode()) { + return FeatureDefinition.NULL_DEFINITION; + } + fd.setValuesMapping(extractFeatureOptions(options)); + return fd; + } + + protected abstract String getDataTypeFeatureNodeName(); + + private List extractValueOptions(JsonNode optionsNode) throws LGThinqApiException { + if (optionsNode.isMissingNode()) { + throw new LGThinqApiException("Error extracting options supported by the device"); + } else { + List values = new ArrayList<>(); + optionsNode.fields().forEachRemaining(e -> { + values.add(e.getValue().asText()); + }); + return values; + } + } + + private Map extractInvertedOptions(JsonNode optionsNode) { + if (optionsNode.isMissingNode()) { + logger.warn("Error extracting options supported by the device"); + return Collections.EMPTY_MAP; + } else { + Map modes = new HashMap(); + optionsNode.fields().forEachRemaining(e -> { + modes.put(e.getValue().asText(), e.getKey()); + }); + return modes; + } + } + + @Override + public ACCapability create(JsonNode rootNode) throws LGThinqException { + ACCapability acCap = super.create(rootNode); + + JsonNode valuesNode = rootNode.path(getValuesNodeName()); + if (valuesNode.isMissingNode()) { + throw new LGThinqApiException("Error extracting capabilities supported by the device"); + } + // supported operation modes + Map allOpModes = extractInvertedOptions( + valuesNode.path(getOpModeNodeName()).path(getOptionsMapNodeName())); + Map allFanSpeeds = extractInvertedOptions( + valuesNode.path(getFanSpeedNodeName()).path(getOptionsMapNodeName())); + + List supOpModeValues = extractValueOptions( + valuesNode.path(getSupOpModeNodeName()).path(getOptionsMapNodeName())); + List supFanSpeedValues = extractValueOptions( + valuesNode.path(getSupFanSpeedNodeName()).path(getOptionsMapNodeName())); + supOpModeValues.remove("@NON"); + supOpModeValues.remove("@NON"); + // find correct operation IDs + Map opModes = new HashMap<>(supOpModeValues.size()); + supOpModeValues.forEach(v -> { + // discovery ID of the operation + String key = allOpModes.get(v); + if (key != null) { + opModes.put(key, v); + } + }); + acCap.setOpMod(opModes); + + Map fanSpeeds = new HashMap<>(supFanSpeedValues.size()); + supFanSpeedValues.forEach(v -> { + // discovery ID of the fan speed + String key = allFanSpeeds.get(v); + if (key != null) { + fanSpeeds.put(key, v); + } + }); + acCap.setFanSpeed(fanSpeeds); + + // ===== get supported extra modes + boolean isSupportDryMode = false, isSupportEnergyMode = false; + + JsonNode supRacSubModeOps = valuesNode.path(getSupSubRacModeNodeName()).path(getOptionsMapNodeName()); + if (!supRacSubModeOps.isMissingNode()) { + supRacSubModeOps.fields().forEachRemaining(f -> { + if ("@AC_MAIN_WIND_MODE_COOL_JET_W".equals(f.getValue().asText())) { + acCap.setJetModeAvailable(true); + } + }); + } + + // set Cool jetMode supportability + if (acCap.isJetModeAvailable()) { + JsonNode jetModeOps = valuesNode.path(getJetModeNodeName()).path(getOptionsMapNodeName()); + if (!jetModeOps.isMissingNode()) { + jetModeOps.fields().forEachRemaining(j -> { + String value = j.getValue().asText(); + if (CAP_AC_COOL_JET.containsKey(value)) { + acCap.setCoolJetModeCommandOn(j.getKey()); + } else if (CAP_AC_COMMAND_OFF.equals(value)) { + acCap.setCoolJetModeCommandOff(j.getKey()); + } + }); + } + } + // get Supported RAC Mode + JsonNode supRACModeOps = valuesNode.path(getSupRacModeNodeName()).path(getOptionsMapNodeName()); + + if (!supRACModeOps.isMissingNode()) { + supRACModeOps.fields().forEachRemaining(r -> { + String racOpValue = r.getValue().asText(); + switch (racOpValue) { + case CAP_AC_AUTODRY: + Map dryStates = extractInvertedOptions( + valuesNode.path(getAutoDryStateNodeName()).path(getOptionsMapNodeName())); + if (!dryStates.isEmpty()) { // sanity check + acCap.setAutoDryModeAvailable(true); + dryStates.forEach((cmdValue, cmdKey) -> { + switch (cmdKey) { + case CAP_AC_COMMAND_OFF: + acCap.setAutoDryModeCommandOff(cmdValue); + break; + case CAP_AC_COMMAND_ON: + acCap.setAutoDryModeCommandOn(cmdValue); + } + }); + } + break; + case CAP_AC_AIRCLEAN: + Map airCleanStates = extractInvertedOptions( + valuesNode.path(getAirCleanStateNodeName()).path(getOptionsMapNodeName())); + if (!airCleanStates.isEmpty()) { + acCap.setAirCleanAvailable(true); + airCleanStates.forEach((cmdKey, cmdValue) -> { + switch (cmdKey) { + case CAP_AC_AIR_CLEAN_COMMAND_OFF: + acCap.setAirCleanModeCommandOff(cmdValue); + break; + case CAP_AC_AIR_CLEAN_COMMAND_ON: + acCap.setAirCleanModeCommandOn(cmdValue); + } + }); + } + break; + case CAP_AC_ENERGYSAVING: + acCap.setEnergySavingAvailable(true); + // there's no definition for this values. Assuming the defaults + acCap.setEnergySavingModeCommandOff("0"); + acCap.setEnergySavingModeCommandOn("1"); + break; + } + }); + } + + JsonNode infoNode = rootNode.get("Info"); + if (infoNode.isMissingNode()) { + logger.warn("No info session defined in the cap data."); + } else { + // try to find monitoring result format + MonitoringResultFormat format = MonitoringResultFormat.getFormatOf(infoNode.path("model").asText()); + if (!MonitoringResultFormat.UNKNOWN_FORMAT.equals(format)) { + acCap.setMonitoringDataFormat(format); + } + } + return acCap; + } + + protected abstract String getOpModeNodeName(); + + protected abstract String getFanSpeedNodeName(); + + protected abstract String getSupOpModeNodeName(); + + protected abstract String getSupFanSpeedNodeName(); + + protected abstract String getJetModeNodeName(); + + protected abstract String getSupSubRacModeNodeName(); + + protected abstract String getSupRacModeNodeName(); + + protected abstract String getAutoDryStateNodeName(); + + protected abstract String getAirCleanStateNodeName(); + + protected abstract String getOptionsMapNodeName(); + + @Override + public ACCapability getCapabilityInstance() { + return new ACCapability(); + } + + protected abstract String getValuesNodeName(); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java new file mode 100644 index 0000000000000..1a514d5db9939 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java @@ -0,0 +1,107 @@ +/** + * 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.lgthinq.lgservices.model.devices.fridge; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_CELSIUS_SYMBOL; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_FAHRENHEIT_SYMBOL; + +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link AbstractFridgeCapabilityFactory} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractFridgeCapabilityFactory extends AbstractCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(AbstractFridgeCapabilityFactory.class); + + private void loadTempNode(JsonNode tempNode, Map capMap, String unit) { + tempNode.forEach(v -> { + // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' + capMap.put(v.path("index").asText() + " " + unit, v.path("label").textValue() + " " + unit); + }); + } + + @Override + public FridgeCapability create(JsonNode rootNode) throws LGThinqException { + FridgeCapability frCap = super.create(rootNode); + + JsonNode node = mapper.valueToTree(rootNode); + if (node.isNull()) { + logger.error("Can't parse json capability for Fridge. The payload has been ignored"); + logger.debug("payload {}", rootNode); + throw new LGThinqException("Can't parse json capability for Fridge. The payload has been ignored"); + } + /** + * iterate over valueMappings like: + * "valueMapping": { + * "1": {"index" : 1, "label" : "7", "_comment" : ""}, + * "2": {"index" : 2, "label" : "6", "_comment" : ""}, + * "3": {"index" : 3, "label" : "5", "_comment" : ""}, + * "4": {"index" : 4, "label" : "4", "_comment" : ""}, + * "5": {"index" : 5, "label" : "3", "_comment" : ""}, + * "6": {"index" : 6, "label" : "2", "_comment" : ""}, + * "7": {"index" : 7, "label" : "1", "_comment" : ""}, + * "255" : {"index" : 255, "label" : "IGNORE", "_comment" : ""} + * } + */ + + JsonNode fridgeTempCNode = node.path(getMonitorValueNodeName()).path(getFridgeTempCNodeName()) + .path(getOptionsNodeName()); + JsonNode fridgeTempFNode = node.path(getMonitorValueNodeName()).path(getFridgeTempFNodeName()) + .path(getOptionsNodeName()); + JsonNode freezerTempCNode = node.path(getMonitorValueNodeName()).path(getFreezerTempCNodeName()) + .path(getOptionsNodeName()); + JsonNode freezerTempFNode = node.path(getMonitorValueNodeName()).path(getFreezerTempFNodeName()) + .path(getOptionsNodeName()); + loadTempNode(fridgeTempCNode, frCap.getFridgeTempCMap(), TEMP_UNIT_CELSIUS_SYMBOL); + loadTempNode(fridgeTempFNode, frCap.getFridgeTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + loadTempNode(freezerTempCNode, frCap.getFreezerTempCMap(), TEMP_UNIT_CELSIUS_SYMBOL); + loadTempNode(freezerTempFNode, frCap.getFreezerTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + return frCap; + } + + @Override + protected List getSupportedDeviceTypes() { + return List.of(DeviceTypes.REFRIGERATOR); + } + + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V1_0, LGAPIVerion.V2_0); + } + + protected abstract String getMonitorValueNodeName(); + + protected abstract String getFridgeTempCNodeName(); + + protected abstract String getFridgeTempFNodeName(); + + protected abstract String getFreezerTempCNodeName(); + + protected abstract String getFreezerTempFNodeName(); + + protected abstract String getOptionsNodeName(); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeSnapshot.java new file mode 100644 index 0000000000000..2528deb6c13d6 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeSnapshot.java @@ -0,0 +1,33 @@ +/** + * 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.lgthinq.lgservices.model.devices.fridge; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; + +/** + * The {@link AbstractFridgeSnapshot} + * + * @author Nemer Daud - Initial contribution + * @author Arne Seime - Complementary sensors + */ +@NonNullByDefault +public abstract class AbstractFridgeSnapshot extends AbstractSnapshotDefinition { + public abstract String getTempUnit(); + + public abstract String getFridgeStrTemp(); + + public abstract String getFreezerStrTemp(); + + public abstract String getDoorStatus(); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java new file mode 100644 index 0000000000000..8f3fe9dd831b0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java @@ -0,0 +1,57 @@ +/** + * 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.lgthinq.lgservices.model.devices.fridge; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link FridgeCanonicalCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FridgeCanonicalCapability extends AbstractCapability + implements FridgeCapability { + + private static final Logger logger = LoggerFactory.getLogger(FridgeCanonicalCapability.class); + private static final ObjectMapper mapper = new ObjectMapper(); + + private final Map fridgeTempCMap = new LinkedHashMap(); + private final Map fridgeTempFMap = new LinkedHashMap(); + private final Map freezerTempCMap = new LinkedHashMap(); + private final Map freezerTempFMap = new LinkedHashMap(); + + public Map getFridgeTempCMap() { + return fridgeTempCMap; + } + + public Map getFridgeTempFMap() { + return fridgeTempFMap; + } + + public Map getFreezerTempCMap() { + return freezerTempCMap; + } + + public Map getFreezerTempFMap() { + return freezerTempFMap; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java new file mode 100644 index 0000000000000..4b8579f8651a3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java @@ -0,0 +1,121 @@ +/** + * 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.lgthinq.lgservices.model.devices.fridge; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link FridgeCanonicalSnapshot} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->fridge, but this POJO expects + * to map field below washerDryer + * + * @author Nemer Daud - Initial contribution + * @author Arne Seime - Complementary sensors + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class FridgeCanonicalSnapshot extends AbstractFridgeSnapshot { + + private boolean online; + private Double fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; + private Double freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; + private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default + + private String doorStatus = ""; + + @JsonProperty("atLeastOneDoorOpen") + public String getDoorStatus() { + return doorStatus; + } + + public void setDoorStatus(String doorStatus) { + this.doorStatus = doorStatus; + } + + @Override + @JsonAlias({ "TempUnit" }) + @JsonProperty("tempUnit") + public String getTempUnit() { + return tempUnit; + } + + private String getStrTempWithUnit(Double temp) { + return temp.intValue() + (TEMP_UNIT_CELSIUS.equals(tempUnit) ? " " + TEMP_UNIT_CELSIUS_SYMBOL + : (TEMP_UNIT_FAHRENHEIT).equals(tempUnit) ? " " + TEMP_UNIT_FAHRENHEIT_SYMBOL : ""); + } + + @Override + @JsonIgnore + public String getFridgeStrTemp() { + return getStrTempWithUnit(getFridgeTemp()); + } + + @Override + @JsonIgnore + public String getFreezerStrTemp() { + return getStrTempWithUnit(getFreezerTemp()); + } + + public void setTempUnit(String tempUnit) { + this.tempUnit = tempUnit; + } + + @JsonAlias({ "TempRefrigerator" }) + @JsonProperty("fridgeTemp") + public Double getFridgeTemp() { + return fridgeTemp; + } + + public void setFridgeTemp(Double fridgeTemp) { + this.fridgeTemp = fridgeTemp; + } + + @JsonAlias({ "TempFreezer" }) + @JsonProperty("freezerTemp") + public Double getFreezerTemp() { + return freezerTemp; + } + + public void setFreezerTemp(Double freezerTemp) { + this.freezerTemp = freezerTemp; + } + + @Override + public DevicePowerState getPowerStatus() { + throw new IllegalStateException("Fridge has no Power state."); + } + + @Override + public void setPowerStatus(DevicePowerState value) { + throw new IllegalStateException("Fridge has no Power state."); + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java new file mode 100644 index 0000000000000..5237deb13cc24 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java @@ -0,0 +1,35 @@ +/** + * 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.lgthinq.lgservices.model.devices.fridge; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; + +/** + * The {@link FridgeCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface FridgeCapability extends CapabilityDefinition { + + public Map getFridgeTempCMap(); + + public Map getFridgeTempFMap(); + + public Map getFreezerTempCMap(); + + public Map getFreezerTempFMap(); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java new file mode 100644 index 0000000000000..54aac176bc4ac --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -0,0 +1,69 @@ +/** + * 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.lgthinq.lgservices.model.devices.fridge; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link FridgeCapabilityFactoryV1} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FridgeCapabilityFactoryV1 extends AbstractFridgeCapabilityFactory { + @Override + protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, + @Nullable String targetChannelId, @Nullable String refChannelId) { + // TODO - Implement feature definition + return FeatureDefinition.NULL_DEFINITION; + } + + @Override + public FridgeCapability getCapabilityInstance() { + return new FridgeCanonicalCapability(); + } + + @Override + protected String getMonitorValueNodeName() { + return "MonitoringValue"; + } + + @Override + protected String getFridgeTempCNodeName() { + return "fridgeTemp_C"; + } + + @Override + protected String getFridgeTempFNodeName() { + return "fridgeTemp_F"; + } + + @Override + protected String getFreezerTempCNodeName() { + return "freezerTemp_C"; + } + + @Override + protected String getFreezerTempFNodeName() { + return "freezerTemp_F"; + } + + @Override + protected String getOptionsNodeName() { + return "valueMapping"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java new file mode 100644 index 0000000000000..195624d216827 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -0,0 +1,68 @@ +/** + * 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.lgthinq.lgservices.model.devices.fridge; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link FridgeCapabilityFactoryV2} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FridgeCapabilityFactoryV2 extends AbstractFridgeCapabilityFactory { + @Override + protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, + @Nullable String targetChannelId, @Nullable String refChannelId) { + return FeatureDefinition.NULL_DEFINITION; + } + + @Override + public FridgeCapability getCapabilityInstance() { + return new FridgeCanonicalCapability(); + } + + @Override + protected String getMonitorValueNodeName() { + return "MonitoringValue"; + } + + @Override + protected String getFridgeTempCNodeName() { + return "fridgeTemp_C"; + } + + @Override + protected String getFridgeTempFNodeName() { + return "fridgeTemp_F"; + } + + @Override + protected String getFreezerTempCNodeName() { + return "freezerTemp_C"; + } + + @Override + protected String getFreezerTempFNodeName() { + return "freezerTemp_F"; + } + + @Override + protected String getOptionsNodeName() { + return "valueMapping"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java new file mode 100644 index 0000000000000..6620e22b5fa5a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java @@ -0,0 +1,80 @@ +/** + * 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.lgthinq.lgservices.model.devices.fridge; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.REFRIGERATOR_SNAPSHOT_NODE_V2; +import static org.openhab.binding.lgthinq.lgservices.model.DeviceTypes.REFRIGERATOR; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.*; + +/** + * The {@link FridgeSnapshotBuilder} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FridgeSnapshotBuilder extends DefaultSnapshotBuilder { + public FridgeSnapshotBuilder() { + super(FridgeCanonicalSnapshot.class); + } + + @Override + public FridgeCanonicalSnapshot createFromBinary(String binaryData, List prot, + CapabilityDefinition capDef) throws LGThinqUnmarshallException, LGThinqApiException { + return super.createFromBinary(binaryData, prot, capDef); + } + + @Override + protected FridgeCanonicalSnapshot getSnapshot(Map snapMap, CapabilityDefinition capDef) { + FridgeCanonicalSnapshot snap; + if (REFRIGERATOR.equals(capDef.getDeviceType())) { + switch (capDef.getDeviceVersion()) { + case V1_0: { + throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + } + case V2_0: { + Map refMap = Objects.requireNonNull( + (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), + "washerDryer node must be present in the snapshot"); + snap = objectMapper.convertValue(refMap, snapClass); + snap.setRawData(snapMap); + return snap; + } + } + } + + throw new IllegalStateException("Snapshot for device type " + capDef.getDeviceType() + + " not supported for this builder. It most likely a bug"); + } + + @Override + protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { + if (REFRIGERATOR.equals(type)) { + if (snapMap.containsKey(REFRIGERATOR_SNAPSHOT_NODE_V2)) { + return LGAPIVerion.V2_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); + } + } + throw new IllegalStateException( + "Unexpected capability. The type " + type + " is not supported by this builder. It most likely a bug"); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java new file mode 100644 index 0000000000000..4115c48526490 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java @@ -0,0 +1,201 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * The {@link AbstractWasherDryerCapabilityFactory} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractWasherDryerCapabilityFactory extends AbstractCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(AbstractWasherDryerCapabilityFactory.class); + + protected abstract String getStateFeatureNodeName(); + + protected abstract String getProcessStateNodeName(); + + protected abstract String getPreStateFeatureNodeName(); + + // --- Selectable features ----- + protected abstract String getRinseFeatureNodeName(); + + protected abstract String getTemperatureFeatureNodeName(); + + protected abstract String getSpinFeatureNodeName(); + + // ------------------------------ + protected abstract String getSoilWashFeatureNodeName(); + + protected abstract String getDoorLockFeatureNodeName(); + + protected abstract MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode); + + protected abstract Map getCommandsDefinition(JsonNode rootNode) + throws LGThinqApiException; + + protected abstract String getCommandRemoteStartNodeName(); + + protected abstract String getCommandStopNodeName(); + + protected abstract String getCommandWakeUpNodeName(); + + @Override + public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { + WasherDryerCapability wdCap = super.create(rootNode); + JsonNode coursesNode = rootNode.path(getCourseNodeName(rootNode)); + JsonNode smartCoursesNode = rootNode.path(getSmartCourseNodeName(rootNode)); + if (coursesNode.isMissingNode()) { + throw new LGThinqException("Course node not present in Capability Json Descriptor"); + } + + Map allCourses = new HashMap<>(getCourseDefinitions(coursesNode)); + allCourses.putAll(getSmartCourseDefinitions(smartCoursesNode)); + // TODO - Put Downloaded Course + wdCap.setCourses(allCourses); + + JsonNode monitorValueNode = rootNode.path(getMonitorValueNodeName()); + if (monitorValueNode.isMissingNode()) { + throw new LGThinqException("MonitoringValue node not found in the V2 WashingDryer cap definition."); + } + // mapping possible states + wdCap.setState(newFeatureDefinition(getStateFeatureNodeName(), monitorValueNode)); + wdCap.setProcessState(newFeatureDefinition(getProcessStateNodeName(), monitorValueNode)); + // --- Selectable features ----- + wdCap.setRinseFeat(newFeatureDefinition(getRinseFeatureNodeName(), monitorValueNode, + WM_CHANNEL_REMOTE_START_RINSE, WM_CHANNEL_RINSE_ID)); + wdCap.setTemperatureFeat(newFeatureDefinition(getTemperatureFeatureNodeName(), monitorValueNode, + WM_CHANNEL_REMOTE_START_TEMP, WM_CHANNEL_TEMP_LEVEL_ID)); + wdCap.setSpinFeat(newFeatureDefinition(getSpinFeatureNodeName(), monitorValueNode, WM_CHANNEL_REMOTE_START_SPIN, + WM_CHANNEL_SPIN_ID)); + // ---------------------------- + wdCap.setDryLevel(newFeatureDefinition(getDryLevelNodeName(), monitorValueNode)); + wdCap.setSoilWash(newFeatureDefinition(getSoilWashFeatureNodeName(), monitorValueNode)); + wdCap.setCommandsDefinition(getCommandsDefinition(rootNode)); + if (monitorValueNode.get(getDoorLockFeatureNodeName()) != null) { + wdCap.setHasDoorLook(true); + } + wdCap.setDefaultCourseFieldName(getConfigCourseType(rootNode)); + wdCap.setDefaultSmartCourseFeatName(getConfigSmartCourseType(rootNode)); + wdCap.setCommandStop(getCommandStopNodeName()); + wdCap.setCommandRemoteStart(getCommandRemoteStartNodeName()); + wdCap.setCommandWakeUp(getCommandWakeUpNodeName()); + // custom feature values map. + wdCap.setFeatureDefinitionMap( + Map.of(getTemperatureFeatureNodeName(), new WasherDryerCapability.TemperatureFeatureFunction(), + getRinseFeatureNodeName(), new WasherDryerCapability.RinseFeatureFunction(), + getSpinFeatureNodeName(), new WasherDryerCapability.SpinFeatureFunction())); + wdCap.setMonitoringDataFormat(getMonitorDataFormat(rootNode)); + return wdCap; + } + + protected Map getGenericCourseDefinitions(JsonNode courseNode, CourseType type) { + Map coursesDef = new HashMap<>(); + courseNode.fields().forEachRemaining(e -> { + CourseDefinition cd = new CourseDefinition(); + JsonNode thisCourseNode = e.getValue(); + cd.setCourseName(thisCourseNode.path("_comment").textValue()); + if (CourseType.SMART_COURSE.equals(type)) { + cd.setBaseCourseName(thisCourseNode.path("Course").textValue()); + } + cd.setCourseType(type); + if (thisCourseNode.path("function").isArray()) { + // just to be safe here + ArrayNode functions = (ArrayNode) thisCourseNode.path("function"); + List functionList = cd.getFunctions(); + for (JsonNode fNode : functions) { + // map all course functions here + CourseFunction f = new CourseFunction(); + f.setValue(fNode.path("value").textValue()); + f.setDefaultValue(fNode.path("default").textValue()); + JsonNode selectableNode = fNode.path("selectable"); + // only Courses (not SmartCourses or DownloadedCourses) can have selectable functions + f.setSelectable( + !selectableNode.isMissingNode() && selectableNode.isArray() && (type == CourseType.COURSE)); + if (f.isSelectable()) { + List selectableValues = f.getSelectableValues(); + // map values acceptable for this function + for (JsonNode v : (ArrayNode) selectableNode) { + if (v.isValueNode()) { + selectableValues.add(v.textValue()); + } + } + f.setSelectableValues(selectableValues); + } + functionList.add(f); + } + cd.setFunctions(functionList); + } + coursesDef.put(e.getKey(), cd); + }); + CourseDefinition cdNotSelected = new CourseDefinition(); + cdNotSelected.setCourseType(type); + cdNotSelected.setCourseName("Not Selected"); + coursesDef.put(getNotSelectedCourseKey(), cdNotSelected); + return coursesDef; + } + + protected Map getCourseDefinitions(JsonNode courseNode) { + return getGenericCourseDefinitions(courseNode, CourseType.COURSE); + } + + protected Map getSmartCourseDefinitions(JsonNode smartCourseNode) { + return getGenericCourseDefinitions(smartCourseNode, CourseType.SMART_COURSE); + } + + protected abstract String getDryLevelNodeName(); + + protected abstract String getNotSelectedCourseKey(); + + @Override + public final List getSupportedDeviceTypes() { + return List.of(DeviceTypes.WASHERDRYER_MACHINE, DeviceTypes.DRYER); + } + + protected abstract String getCourseNodeName(JsonNode rootNode); + + protected abstract String getSmartCourseNodeName(JsonNode rootNode); + + protected abstract String getDefaultCourse(JsonNode rootNode); + + protected abstract String getRemoteFeatName(); + + protected abstract String getStandByFeatName(); + + protected abstract String getConfigCourseType(JsonNode rootNode); + + protected abstract String getConfigSmartCourseType(JsonNode rootNote); + + protected abstract String getConfigDownloadCourseType(JsonNode rootNode); + + protected abstract String getMonitorValueNodeName(); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java new file mode 100644 index 0000000000000..f12fc7dc0e21e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java @@ -0,0 +1,64 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CourseDefinition} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CourseDefinition { + private String courseName = ""; + // Name of the course this is based on. It's only used for SmartCourses + private String baseCourseName = ""; + private CourseType courseType = CourseType.UNDEF; + private List functions = new ArrayList<>(); + + public String getCourseName() { + return courseName; + } + + public String getBaseCourseName() { + return baseCourseName; + } + + public void setBaseCourseName(String baseCourseName) { + this.baseCourseName = baseCourseName; + } + + public void setCourseName(String courseName) { + this.courseName = courseName; + } + + public CourseType getCourseType() { + return courseType; + } + + public void setCourseType(CourseType courseType) { + this.courseType = courseType; + } + + public List getFunctions() { + return functions; + } + + public void setFunctions(List functions) { + this.functions = functions; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java new file mode 100644 index 0000000000000..3e83c4c155ba0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java @@ -0,0 +1,63 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CourseFunction} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CourseFunction { + private String value = ""; + private String defaultValue = ""; + private boolean isSelectable; + private List selectableValues = new ArrayList<>(); + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public boolean isSelectable() { + return isSelectable; + } + + public void setSelectable(boolean selectable) { + isSelectable = selectable; + } + + public List getSelectableValues() { + return selectableValues; + } + + public void setSelectableValues(List selectableValues) { + this.selectableValues = selectableValues; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java new file mode 100644 index 0000000000000..eb7f345f25e7b --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java @@ -0,0 +1,39 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CourseType} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum CourseType { + // TODO - review DownloadCourse value, in remote start debugging + COURSE("Course"), + SMART_COURSE("SmartCourse"), + DOWNLOADED_COURSE("DownloadedCourse"), + UNDEF("Undefined"); + + private final String value; + + CourseType(String s) { + value = s; + } + + public String getValue() { + return value; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java new file mode 100644 index 0000000000000..5d1bfd32ca05d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java @@ -0,0 +1,225 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; + +/** + * The {@link WasherDryerCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WasherDryerCapability extends AbstractCapability { + private String defaultCourseFieldName = ""; + private String doorLockFeatName = ""; + private String childLockFeatName = ""; + private String defaultSmartCourseFieldName = ""; + private String commandRemoteStart = ""; + private String remoteStartFeatName = ""; + private String commandWakeUp = ""; + private String commandStop = ""; + private FeatureDefinition state = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition soilWash = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition spin = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition temperature = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition rinse = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition error = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition dryLevel = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition processState = FeatureDefinition.NULL_DEFINITION; + private boolean hasDoorLook; + private boolean hasTurboWash; + private Map commandsDefinition = new HashMap<>(); + private Map courses = new LinkedHashMap<>(); + + static class RinseFeatureFunction implements Function { + @Override + public FeatureDefinition apply(WasherDryerCapability c) { + return c.getRinseFeat(); + } + } + + static class TemperatureFeatureFunction implements Function { + @Override + public FeatureDefinition apply(WasherDryerCapability c) { + return c.getTemperatureFeat(); + } + } + + static class SpinFeatureFunction implements Function { + @Override + public FeatureDefinition apply(WasherDryerCapability c) { + return c.getSpinFeat(); + } + } + + public Map getCommandsDefinition() { + return commandsDefinition; + } + + public FeatureDefinition getDryLevel() { + return dryLevel; + } + + public String getCommandStop() { + return commandStop; + } + + public void setCommandStop(String commandStop) { + this.commandStop = commandStop; + } + + public String getCommandRemoteStart() { + return commandRemoteStart; + } + + public void setCommandRemoteStart(String commandRemoteStart) { + this.commandRemoteStart = commandRemoteStart; + } + + public String getCommandWakeUp() { + return commandWakeUp; + } + + public void setCommandWakeUp(String commandWakeUp) { + this.commandWakeUp = commandWakeUp; + } + + public void setDryLevel(FeatureDefinition dryLevel) { + this.dryLevel = dryLevel; + } + + public FeatureDefinition getProcessState() { + return processState; + } + + public void setProcessState(FeatureDefinition processState) { + this.processState = processState; + } + + public void setCommandsDefinition(Map commandsDefinition) { + this.commandsDefinition = commandsDefinition; + } + + public Map getCourses() { + return courses; + } + + public void setCourses(Map courses) { + this.courses = courses; + } + + public FeatureDefinition getStateFeat() { + return state; + } + + public boolean hasDoorLook() { + return this.hasDoorLook; + } + + public void setHasDoorLook(boolean hasDoorLook) { + this.hasDoorLook = hasDoorLook; + } + + public void setState(FeatureDefinition state) { + this.state = state; + } + + public FeatureDefinition getSoilWash() { + return soilWash; + } + + public void setSoilWash(FeatureDefinition soilWash) { + this.soilWash = soilWash; + } + + public FeatureDefinition getSpinFeat() { + return spin; + } + + public void setSpinFeat(FeatureDefinition spin) { + this.spin = spin; + } + + public FeatureDefinition getTemperatureFeat() { + return temperature; + } + + public void setTemperatureFeat(FeatureDefinition temperature) { + this.temperature = temperature; + } + + public FeatureDefinition getRinseFeat() { + return rinse; + } + + public void setRinseFeat(FeatureDefinition rinse) { + this.rinse = rinse; + } + + public FeatureDefinition getError() { + return error; + } + + public void setError(FeatureDefinition error) { + this.error = error; + } + + public String getDefaultCourseFieldName() { + return defaultCourseFieldName; + } + + public void setDefaultCourseFieldName(String defaultCourseFieldName) { + this.defaultCourseFieldName = defaultCourseFieldName; + } + + public String getDefaultSmartCourseFeatName() { + return defaultSmartCourseFieldName; + } + + public void setDefaultSmartCourseFeatName(String defaultSmartCourseFieldName) { + this.defaultSmartCourseFieldName = defaultSmartCourseFieldName; + } + + public String getRemoteStartFeatName() { + return remoteStartFeatName; + } + + public void setRemoteStartFeatName(String remoteStartFeatName) { + this.remoteStartFeatName = remoteStartFeatName; + } + + public String getChildLockFeatName() { + return childLockFeatName; + } + + public void setChildLockFeatName(String childLockFeatName) { + this.childLockFeatName = childLockFeatName; + } + + public String getDoorLockFeatName() { + return doorLockFeatName; + } + + public void setDoorLockFeatName(String doorLockFeatName) { + this.doorLockFeatName = doorLockFeatName; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java new file mode 100644 index 0000000000000..0f97cc4f5cf61 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -0,0 +1,274 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link WasherDryerCapabilityFactoryV1} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WasherDryerCapabilityFactoryV1 extends AbstractWasherDryerCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(WasherDryerCapabilityFactoryV1.class); + + @Override + public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { + WasherDryerCapability cap = super.create(rootNode); + cap.setRemoteStartFeatName("RemoteStart"); + cap.setChildLockFeatName("ChildLock"); + cap.setDoorLockFeatName("DoorLock"); + return cap; + } + + @Override + protected String getStateFeatureNodeName() { + return "State"; + } + + @Override + protected String getProcessStateNodeName() { + return "ProcessState"; + } + + @Override + protected String getPreStateFeatureNodeName() { + return "PreState"; + } + + @Override + protected String getRinseFeatureNodeName() { + return "RinseOption"; + } + + @Override + protected String getTemperatureFeatureNodeName() { + return "WaterTemp"; + } + + @Override + protected String getSpinFeatureNodeName() { + return "SpinSpeed"; + } + + @Override + protected String getSoilWashFeatureNodeName() { + return "Wash"; + } + + @Override + protected String getDoorLockFeatureNodeName() { + // there is no dook lock node in V1. + return "DUMMY_DOOR_LOCK"; + } + + @Override + protected MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode) { + String type = rootNode.path("Monitoring").path("type").textValue(); + return MonitoringResultFormat.getFormatOf(Objects.requireNonNullElse(type, "")); + } + + @Override + protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + boolean isBinaryCommands = MonitoringResultFormat.BINARY_FORMAT.getFormat() + .equals(rootNode.path("ControlWifi").path("type").textValue()); + JsonNode commandNode = rootNode.path("ControlWifi").path("action"); + if (commandNode.isMissingNode()) { + logger.warn("No commands found in the DryerWasher definition. This is most likely a bug."); + return Collections.EMPTY_MAP; + } + Map commands = new HashMap<>(); + for (Iterator> it = commandNode.fields(); it.hasNext();) { + Map.Entry e = it.next(); + String commandName = e.getKey(); + CommandDefinition cd = new CommandDefinition(); + JsonNode thisCommandNode = e.getValue(); + JsonNode cmdField = thisCommandNode.path("cmd"); + if (cmdField.isMissingNode()) { + // command not supported + continue; + } + cd.setCommand(cmdField.textValue()); + cd.setCmdOpt(thisCommandNode.path("cmdOpt").textValue()); + cd.setCmdOptValue(thisCommandNode.path("value").textValue()); + cd.setBinary(isBinaryCommands); + String strData = Objects.requireNonNullElse(thisCommandNode.path("data").textValue(), ""); + cd.setDataTemplate(strData); + cd.setRawCommand(thisCommandNode.toPrettyString()); + int reservedIndex = 0; + // keep the order + if (!strData.isEmpty()) { + Map data = new LinkedHashMap<>(); + for (String f : strData.split(",")) { + if (f.contains("{")) { + // its a featured field + // create data entry with the key and blank value + data.put(f.replaceAll("[{\\[}\\]]", ""), ""); + } else { + // its a fixed reserved value + data.put("Reserved" + reservedIndex, f.replaceAll("[{\\[}\\]]", "")); + reservedIndex++; + } + } + cd.setData(data); + } + commands.put(commandName, cd); + } + return commands; + } + + @Override + protected String getCommandRemoteStartNodeName() { + return "OperationStart"; + } + + @Override + protected String getCommandStopNodeName() { + return "OperationStop"; + } + + @Override + protected String getCommandWakeUpNodeName() { + return "OperationWakeUp"; + } + + @Override + protected String getDryLevelNodeName() { + return "DryLevel"; + } + + @Override + protected String getNotSelectedCourseKey() { + return "0"; + } + + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V1_0); + } + + @Override + protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, + @Nullable String targetChannelId, @Nullable String refChannelId) { + JsonNode featureNode = featuresNode.path(featureName); + if (featureNode.isMissingNode()) { + return FeatureDefinition.NULL_DEFINITION; + } + FeatureDefinition fd = new FeatureDefinition(); + fd.setName(featureName); + fd.setLabel(featureName); + fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); + fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); + // All features from V1 are ENUMs + fd.setDataType(FeatureDataType.ENUM); + JsonNode valuesMappingNode = featureNode.path("option"); + if (!valuesMappingNode.isMissingNode()) { + + Map valuesMapping = new HashMap<>(); + valuesMappingNode.fields().forEachRemaining(e -> { + // collect values as: + // + // "option":{ + // "0":"@WM_STATE_POWER_OFF_W", + // to "0" -> "@WM_STATE_POWER_OFF_W" + valuesMapping.put(e.getKey(), e.getValue().asText()); + }); + fd.setValuesMapping(valuesMapping); + } + + return fd; + } + + @Override + public WasherDryerCapability getCapabilityInstance() { + return new WasherDryerCapability(); + } + + @Override + /* + * Return the default Course ID. + * OBS:In the V1, the default course points to the ID of the course list that is the default. + */ + protected String getDefaultCourse(JsonNode rootNode) { + return rootNode.path("Config").path("defaultCourseId").textValue(); + } + + @Override + protected String getRemoteFeatName() { + return "RemoteStart"; + } + + @Override + protected String getStandByFeatName() { + return "Standby"; + } + + @Override + protected String getConfigCourseType(JsonNode rootNode) { + if (rootNode.path(getMonitorValueNodeName()).path("APCourse").isMissingNode()) { + return "Course"; + } else { + return "APCourse"; + } + } + + @Override + protected String getCourseNodeName(JsonNode rootNode) { + JsonNode refOptions = rootNode.path(getMonitorValueNodeName()).path(getConfigCourseType(rootNode)) + .path("option"); + if (refOptions.isArray()) { + AtomicReference courseNodeName = new AtomicReference<>(""); + for (JsonNode node : refOptions) { + return node.asText(); + } + } + return ""; + } + + @Override + protected String getSmartCourseNodeName(JsonNode rootNode) { + return "SmartCourse"; + } + + @Override + protected String getConfigSmartCourseType(JsonNode rootNote) { + return "SmartCourse"; + } + + @Override + protected String getConfigDownloadCourseType(JsonNode rootNode) { + // just to ignore because there is no DownloadCourseType in V1 + return "XXXXXXXXXXX"; + } + + @Override + protected String getMonitorValueNodeName() { + return "Value"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java new file mode 100644 index 0000000000000..a9f06640cf990 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java @@ -0,0 +1,270 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import java.util.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ValueNode; + +/** + * The {@link WasherDryerCapabilityFactoryV2} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WasherDryerCapabilityFactoryV2 extends AbstractWasherDryerCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(WasherDryerCapabilityFactoryV2.class); + + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V2_0); + } + + @Override + public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { + WasherDryerCapability cap = super.create(rootNode); + cap.setRemoteStartFeatName("remoteStart"); + cap.setChildLockFeatName("standby"); + cap.setDoorLockFeatName("loadItemWasher"); + return cap; + } + + @Override + protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, + @Nullable String targetChannelId, @Nullable String refChannelId) { + JsonNode featureNode = featuresNode.path(featureName); + if (featureNode.isMissingNode()) { + return FeatureDefinition.NULL_DEFINITION; + } + FeatureDefinition fd = new FeatureDefinition(); + fd.setName(featureName); + fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); + fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); + + JsonNode labelNode = featureNode.path("label"); + if (!labelNode.isMissingNode() && !labelNode.isNull()) { + fd.setLabel(labelNode.asText()); + } else { + fd.setLabel(featureName); + } + // all features from V2 are enums + fd.setDataType(FeatureDataType.ENUM); + JsonNode valuesMappingNode = featureNode.path("valueMapping"); + if (!valuesMappingNode.isMissingNode()) { + + Map valuesMapping = new HashMap<>(); + valuesMappingNode.fields().forEachRemaining(e -> { + // collect values as: + // + // "POWEROFF": { + // "index": 0, + // "label": "@WM_STATE_POWER_OFF_W" + // }, + // to "POWEROFF" -> "@WM_STATE_POWER_OFF_W" + valuesMapping.put(e.getKey(), e.getValue().path("label").asText()); + }); + fd.setValuesMapping(valuesMapping); + } + + return fd; + } + + @Override + public WasherDryerCapability getCapabilityInstance() { + return new WasherDryerCapability(); + } + + @Override + protected String getCourseNodeName(JsonNode rootNode) { + String courseType = getConfigCourseType(rootNode); + return rootNode.path(getMonitorValueNodeName()).path(courseType).path("ref").textValue(); + } + + @Override + protected String getSmartCourseNodeName(JsonNode rootNode) { + return "SmartCourse"; + } + + private String getConfigNodeName() { + return "Config"; + } + + @Override + /* + * Return the default Course Name + * OBS:In the V2, the default course points to the default course name + */ + protected String getDefaultCourse(JsonNode rootNode) { + return rootNode.path(getConfigNodeName()).path("defaultCourse").textValue(); + } + + @Override + protected String getRemoteFeatName() { + return "remoteStart"; + } + + @Override + protected String getStandByFeatName() { + return "standby"; + } + + @Override + protected String getConfigCourseType(JsonNode rootNode) { + return rootNode.path(getConfigNodeName()).path("courseType").textValue(); + } + + protected String getConfigSmartCourseType(JsonNode rootNode) { + return rootNode.path(getConfigNodeName()).path("smartCourseType").textValue(); + } + + protected String getConfigDownloadCourseType(JsonNode rootNode) { + return rootNode.path(getConfigNodeName()).path("downloadedCourseType").textValue(); + } + + @Override + protected String getStateFeatureNodeName() { + return "state"; + } + + @Override + protected String getProcessStateNodeName() { + return "ProcessState"; + } + + @Override + protected String getPreStateFeatureNodeName() { + return "preState"; + } + + @Override + protected String getRinseFeatureNodeName() { + return "rinse"; + } + + @Override + protected String getTemperatureFeatureNodeName() { + return "temp"; + } + + @Override + protected String getSpinFeatureNodeName() { + return "spin"; + } + + @Override + protected String getSoilWashFeatureNodeName() { + return "soilWash"; + } + + @Override + protected String getDoorLockFeatureNodeName() { + return "doorLock"; + } + + @Override + protected MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode) { + // All v2 are Json format + return MonitoringResultFormat.JSON_FORMAT; + } + + @Override + protected Map getCommandsDefinition(JsonNode rootNode) { + JsonNode commandNode = rootNode.path("ControlWifi"); + List escapeDataValues = Arrays.asList("course", "SmartCourse", "doorLock", "childLock"); + if (commandNode.isMissingNode()) { + logger.warn("No commands found in the DryerWasher definition. This is most likely a bug."); + return Collections.EMPTY_MAP; + } + Map commands = new HashMap<>(); + for (Iterator> it = commandNode.fields(); it.hasNext();) { + Map.Entry e = it.next(); + String commandName = e.getKey(); + if (commandName.equals("vtCtrl")) { + // ignore command + continue; + } + CommandDefinition cd = new CommandDefinition(); + JsonNode thisCommandNode = e.getValue(); + cd.setCommand(thisCommandNode.path("command").textValue()); + JsonNode dataValues = thisCommandNode.path("data").path("washerDryer"); + if (!dataValues.isMissingNode()) { + Map data = new HashMap<>(); + dataValues.fields().forEachRemaining(f -> { + // only load features outside escape. + if (!escapeDataValues.contains(f.getKey())) { + if (f.getValue().isValueNode()) { + ValueNode vn = (ValueNode) f.getValue(); + if (f.getValue().isTextual()) { + data.put(f.getKey(), vn.asText()); + } else if (f.getValue().isNumber()) { + data.put(f.getKey(), vn.asInt()); + } + } + } + }); + // add extra data features + data.put(getConfigCourseType(rootNode), ""); + data.put(getConfigSmartCourseType(rootNode), ""); + data.put("courseType", ""); + cd.setData(data); + cd.setRawCommand(thisCommandNode.toPrettyString()); + } else { + logger.warn("Data node not found in the WasherDryer definition. It's most likely a bug"); + } + commands.put(commandName, cd); + } + return commands; + } + + @Override + protected String getCommandRemoteStartNodeName() { + return "WMStart"; + } + + @Override + protected String getCommandStopNodeName() { + return "WMStop"; + } + + @Override + protected String getCommandWakeUpNodeName() { + return "WMWakeup"; + } + + @Override + protected String getNotSelectedCourseKey() { + return "NOT_SELECTED"; + } + + @Override + protected String getMonitorValueNodeName() { + return "MonitoringValue"; + } + + @Override + protected String getDryLevelNodeName() { + return "dryLevel"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java new file mode 100644 index 0000000000000..3e7fb57c9813a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java @@ -0,0 +1,318 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link WasherDryerSnapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class WasherDryerSnapshot extends AbstractSnapshotDefinition { + private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; + private String state = ""; + private String processState = ""; + private boolean online; + private String course = ""; + private String smartCourse = ""; + private String downloadedCourse = ""; + private String temperatureLevel = ""; + private String doorLock = ""; + private String option1 = ""; + private String option2 = ""; + private String childLock = ""; + private Double remainingHour = 0.00; + private Double remainingMinute = 0.00; + private Double reserveHour = 0.00; + private Double reserveMinute = 0.00; + + private String remoteStart = ""; + private boolean remoteStartEnabled = false; + private String standByStatus = ""; + + private String dryLevel = ""; + private boolean standBy = false; + private String error = ""; + private String rinse = ""; + private String spin = ""; + + private String loadItem = ""; + + public String getLoadItem() { + return loadItem; + } + + @JsonAlias({ "LoadItem" }) + @JsonProperty("loadItemWasher") + public void setLoadItem(String loadItem) { + this.loadItem = loadItem; + } + + @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) + @JsonProperty("courseFL24inchBaseTitan") + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } + + @JsonProperty("dryLevel") + @JsonAlias({ "DryLevel" }) + public String getDryLevel() { + return dryLevel; + } + + public void setDryLevel(String dryLevel) { + this.dryLevel = dryLevel; + } + + @JsonProperty("processState") + @JsonAlias({ "ProcessState", "preState", "PreState" }) + public String getProcessState() { + return processState; + } + + public void setProcessState(String processState) { + this.processState = processState; + } + + @JsonProperty("error") + @JsonAlias({ "Error" }) + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + @Override + public DevicePowerState getPowerStatus() { + return powerState; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + throw new IllegalArgumentException("This method must not be accessed."); + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } + + @JsonProperty("state") + @JsonAlias({ "state", "State" }) + public String getState() { + return state; + } + + @JsonProperty("smartCourseFL24inchBaseTitan") + @JsonAlias({ "smartCourseFL24inchBaseTitan", "SmartCourse" }) + public String getSmartCourse() { + return smartCourse; + } + + @JsonProperty("downloadedCourseFL24inchBaseTitan") + @JsonAlias({ "downloadedCourseFLUpper25inchBaseUS" }) + public String getDownloadedCourse() { + return downloadedCourse; + } + + public void setDownloadedCourse(String downloadedCourse) { + this.downloadedCourse = downloadedCourse; + } + + @JsonIgnore + public String getRemainingTime() { + return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); + } + + @JsonIgnore + public String getReserveTime() { + return String.format("%02.0f:%02.0f", getReserveHour(), getReserveMinute()); + } + + @JsonProperty("remainTimeHour") + @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + public Double getRemainingHour() { + return remainingHour; + } + + public void setRemainingHour(Double remainingHour) { + this.remainingHour = remainingHour; + } + + @JsonProperty("remainTimeMinute") + @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + public Double getRemainingMinute() { + return remainingMinute; + } + + public void setRemainingMinute(Double remainingMinute) { + this.remainingMinute = remainingMinute; + } + + @JsonProperty("reserveTimeHour") + @JsonAlias({ "reserveTimeHour", "Reserve_Time_H" }) + public Double getReserveHour() { + return reserveHour; + } + + public void setReserveHour(Double reserveHour) { + this.reserveHour = reserveHour; + } + + @JsonProperty("reserveTimeMinute") + @JsonAlias({ "reserveTimeMinute", "Reserve_Time_M" }) + public Double getReserveMinute() { + return reserveMinute; + } + + public void setReserveMinute(Double reserveMinute) { + this.reserveMinute = reserveMinute; + } + + public void setSmartCourse(String smartCourse) { + this.smartCourse = smartCourse; + } + + @JsonProperty("temp") + @JsonAlias({ "WaterTemp" }) + public String getTemperatureLevel() { + return temperatureLevel; + } + + public void setTemperatureLevel(String temperatureLevel) { + this.temperatureLevel = temperatureLevel; + } + + @JsonProperty("doorLock") + @JsonAlias({ "DoorLock", "DoorClose" }) + public String getDoorLock() { + return doorLock; + } + + public void setDoorLock(String doorLock) { + this.doorLock = doorLock; + } + + @JsonProperty("ChildLock") + @JsonAlias({ "childLock" }) + public String getChildLock() { + return childLock; + } + + public void setChildLock(String childLock) { + this.childLock = childLock; + } + + public void setState(String state) { + this.state = state; + if (state.equals(WM_POWER_OFF_VALUE)) { + powerState = DevicePowerState.DV_POWER_OFF; + } else { + powerState = DevicePowerState.DV_POWER_ON; + } + } + + public boolean isRemoteStartEnabled() { + return remoteStartEnabled; + } + + @JsonProperty("remoteStart") + @JsonAlias({ "RemoteStart" }) + public String getRemoteStart() { + return remoteStart; + } + + public void setRemoteStart(String remoteStart) { + this.remoteStart = remoteStart.contains("ON") || remoteStart.equals("1") ? "ON" + : (remoteStart.contains("OFF") || remoteStart.equals("0") ? "OFF" : remoteStart); + remoteStartEnabled = this.remoteStart.equals("ON"); + } + + @JsonProperty("standby") + @JsonAlias({ "Standby" }) + public String getStandByStatus() { + return standByStatus; + } + + public void setStandByStatus(String standByStatus) { + this.standByStatus = standByStatus.contains("ON") || standByStatus.equals("1") ? "ON" + : (standByStatus.contains("OFF") || standByStatus.equals("0") ? "OFF" : standByStatus); + ; + standBy = this.standByStatus.contains("ON"); + } + + public boolean isStandBy() { + return standBy; + } + + @JsonProperty("rinse") + @JsonAlias({ "RinseOption" }) + public String getRinse() { + return rinse; + } + + public void setRinse(String rinse) { + this.rinse = rinse; + } + + @JsonProperty("spin") + @JsonAlias({ "SpinSpeed" }) + public String getSpin() { + return spin; + } + + public void setSpin(String spin) { + this.spin = spin; + } + + @JsonProperty("Option1") + public String getOption1() { + return option1; + } + + public void setOption1(String option1) { + this.option1 = option1; + } + + @JsonProperty("Option2") + public String getOption2() { + return option2; + } + + public void setOption2(String option2) { + this.option2 = option2; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java new file mode 100644 index 0000000000000..d07a1e02d56e0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java @@ -0,0 +1,123 @@ +/** + * 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.lgthinq.lgservices.model.devices.washerdryer; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.*; + +/** + * The {@link WasherDryerSnapshotBuilder} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WasherDryerSnapshotBuilder extends DefaultSnapshotBuilder { + public WasherDryerSnapshotBuilder() { + super(WasherDryerSnapshot.class); + } + + @Override + public WasherDryerSnapshot createFromBinary(String binaryData, List prot, + CapabilityDefinition capDef) throws LGThinqUnmarshallException, LGThinqApiException { + WasherDryerSnapshot snap = super.createFromBinary(binaryData, prot, capDef); + snap.setRemoteStart( + bitValue(((WasherDryerCapability) capDef).getRemoteStartFeatName(), snap.getRawData(), capDef)); + snap.setDoorLock(bitValue(((WasherDryerCapability) capDef).getDoorLockFeatName(), snap.getRawData(), capDef)); + snap.setChildLock(bitValue(((WasherDryerCapability) capDef).getChildLockFeatName(), snap.getRawData(), capDef)); + return snap; + } + + @Override + protected WasherDryerSnapshot getSnapshot(Map snapMap, CapabilityDefinition capDef) { + WasherDryerSnapshot snap; + DeviceTypes type = capDef.getDeviceType(); + LGAPIVerion version = capDef.getDeviceVersion(); + switch (type) { + case WASHING_TOWER: + case WASHERDRYER_MACHINE: + switch (version) { + case V1_0: { + snap = objectMapper.convertValue(snapMap, snapClass); + snap.setRawData(snapMap); + } + case V2_0: { + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), + "washerDryer node must be present in the snapshot"); + snap = objectMapper.convertValue(washerDryerMap, snapClass); + setAltCourseNodeName(capDef, snap, washerDryerMap); + snap.setRawData(washerDryerMap); + return snap; + } + } + case DRYER_TOWER: + case DRYER: + switch (version) { + case V1_0: { + throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + } + case V2_0: { + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), + "washerDryer node must be present in the snapshot"); + snap = objectMapper.convertValue(washerDryerMap, snapClass); + setAltCourseNodeName(capDef, snap, washerDryerMap); + snap.setRawData(snapMap); + return snap; + } + } + } + throw new IllegalStateException( + "Snapshot for device type " + type + " not supported for this builder. It most likely a bug"); + } + + private static void setAltCourseNodeName(CapabilityDefinition capDef, WasherDryerSnapshot snap, + Map washerDryerMap) { + if (snap.getCourse().isEmpty() && capDef instanceof WasherDryerCapability) { + String altCourseNodeName = ((WasherDryerCapability) capDef).getDefaultCourseFieldName(); + String altSmartCourseNodeName = ((WasherDryerCapability) capDef).getDefaultSmartCourseFeatName(); + snap.setCourse(Objects.requireNonNullElse((String) washerDryerMap.get(altCourseNodeName), "")); + snap.setSmartCourse(Objects.requireNonNullElse((String) washerDryerMap.get(altSmartCourseNodeName), "")); + } + } + + @Override + protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { + switch (type) { + case DRYER_TOWER: + case DRYER: + return LGAPIVerion.V2_0; + case WASHING_TOWER: + case WASHERDRYER_MACHINE: + if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { + return LGAPIVerion.V2_0; + } else if (snapMap.containsKey("State")) { + return LGAPIVerion.V1_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine WASHERDRYER_MACHINE API version."); + } + default: + throw new IllegalStateException("Discovery version for device type " + type + + " not supported for this builder. It most likely a bug"); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/addon/addon.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/addon/addon.xml new file mode 100644 index 0000000000000..dd2c29a681574 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/addon/addon.xml @@ -0,0 +1,11 @@ + + + + binding + LG ThinQ Binding + Controlling LG ThinQ enabled devices + cloud + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/binding/binding.xml new file mode 100644 index 0000000000000..94c1032b3fb02 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/binding/binding.xml @@ -0,0 +1,9 @@ + + + + lgthinq Binding + This is the binding for lgthinq. + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/devicefeatures.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/devicefeatures.properties new file mode 100644 index 0000000000000..bafc5d9a1a836 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/devicefeatures.properties @@ -0,0 +1 @@ +x=1 diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties new file mode 100644 index 0000000000000..233bde0b5b4c4 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -0,0 +1,30 @@ +# binding +binding.lgthinq.name = LG Thinq Binding +binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 & v2) + +# thing types +thing-type.lgthinq.401.label = LG Thinq Air Conditioner +thing-type.lgthinq.401.description = LG Thinq Air Conditioner V1 & V2 + +# channel types +channel-type.lgthinq.current-temperature.label = Temperature +channel-type.lgthinq.current-temperature.description = Current Temperature +channel-type.lgthinq.fan-speed.label = Fan Speed +channel-type.lgthinq.fan-speed.description = Air Conditioner Fan Speed +channel-type.lgthinq.operation-mode.label = Operation Mode +channel-type.lgthinq.operation-mode.description = Air Contirioner Operation Mode +channel-type.lgthinq.cool-jet.label = Cool Jet +channel-type.lgthinq.cool-jet.description = Cool Jet Mode + + +# ERRORS +error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). +error.toke-file-corrupted = Error Openning LGThinq Token File. Try to delete it (in data directory) to the bridge automatically recreate it. +error.toke-file-access-error = Error Handling Token Configuration File. +error.toke-refresh = Error refreshing LGThinq Token. Try to delete it (in data directory) to the bridge automatically recreate it. +error.lgapi-communication-error = Generic Error in the LG API communication process. +error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. +error.lgapi-getting-devices = Error getting devices from LG API in scanner process. + +# OFFLINE Statuses +offline.device-disconnected = Device is disconnected. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties new file mode 100644 index 0000000000000..d787bd6f204e5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties @@ -0,0 +1,29 @@ +# binding +binding.lgthinq.name = LG Thinq Binding +binding.lgthinq.description = Binding para integra\u00E7\u00E3o OpenHab com LG Thinq API (v1 & v2) + +# thing types +thing-type.lgthinq.401.label = LG Thinq Ar Condicionado +thing-type.lgthinq.401.description = LG Thinq Ar Condicionado V1 & V2 + +# channel types +channel-type.lgthinq.current-temperature.label = Temperatura +channel-type.lgthinq.current-temperature.description = Temperatura Atual +channel-type.lgthinq.fan-speed.label = Velocidade Ar +channel-type.lgthinq.fan-speed.description = Velocidade Ar +channel-type.lgthinq.operation-mode.label = Modo de Opera\u00E7\u00E3o +channel-type.lgthinq.operation-mode.description = Modo de Opera\u00E7\u00E3o do Ar Condicionado +channel-type.lgthinq.cool-jet.label = Jato Frio +channel-type.lgthinq.cool-jet.description = Modo Jato Frio + +# ERRORS +error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). +error.toke-file-corrupted = Error Openning LGThinq Token File. Try to delete it (in data directory) to the bridge automatically recreate it. +error.toke-file-access-error = Error Handling Token Configuration File. +error.toke-refresh = Error refreshing LGThinq Token. Try to delete it (in data directory) to the bridge automatically recreate it. +error.lgapi-communication-error = Generic Error in the LG API communication process. +error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. +error.lgapi-getting-devices = Error getting devices from LG APIevice is disconnected in scanner process. + +# OFFLINE Statuses +offline.device-disconnected = Dispositivo desconectado. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml new file mode 100644 index 0000000000000..c1e93657411ad --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + LG ThinQ Air Conditioner + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml new file mode 100644 index 0000000000000..e33ec88472420 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml @@ -0,0 +1,76 @@ + + + + + + A connection to a LGThinQ Gateway + + + + + The User Language registered in LG Account + + + + + + + + + + + + + + + + + The User Country registered in LG Account + + + + + + + + + + + + + + + + + + Fill this only if selected "Other" in the Language above + + + + Fill this only if selected "Other" in the Country above + + + + Username from LG Thinq Personal Account + + + + Password from LG Thinq Personal Account + password + + + + Pooling interval to discover new devices from LG Account. + 86400 + + + + Only used for proxy/test gateway server. + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml new file mode 100644 index 0000000000000..c029ef26b58ab --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -0,0 +1,301 @@ + + + + + Number:Temperature + + Current temperature. + Temperature + + + + + Switch + + Temperature + + + + Switch + + Switch + + + + Switch + + Switch + + + + Switch + + Switch + + + Switch + + Switch + + + Number:Temperature + + Target temperature. + Temperature + + + + Number + + AC Wind Strength + Wind + + + + + + + + + + + + + Number + + AC Operation Mode + + + + + + + + + + + Switch + + Remote start + + + + + Switch + + Remote Start/Stop + + + + Switch + + Standby Mode + + + + String + + Rinse + + + + + String + + Rinse + + + + String + + Spin Speed + + + + + String + + Spin Speed + + + + String + + Washer State Operation + + + + + String + + Remaining Time + + + + + + String + + Delay Time + + + + + String + + Washer Course + + + + + + + + + String + + Washer Smart Course + + + + + + + + String + + Washer Downloaded Course + + + + + + + + String + + Target Temperature Level + Temperature + + + + String + + Target Temperature Level + Temperature + + + String + + Door Lock + + + + + + + + + + String + + Dryer Operation State + + + + + String + + Process State + + + + + String + + Course + + + + + String + + Dryer Dry + + + + + String + + Dryer Child Lock + + + + + + + + + + DateTime + + Dryer Remaining Time + + + + + String + + Dryer Error + + + + + + + + + + + Contact + + Door status (at least one if combined fridge/freezer) + + + + + + + + + String + + Temperature Unit + + + + + + + + + + Number:Temperature + + Freezer setpoint temperature + Temperature + + + + + Number:Temperature + + Fridge setpoint temperature. + Temperature + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml new file mode 100644 index 0000000000000..6ef935f9beaed --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + LG ThinQ Dryer + + + + + + + + + + + Remote Start Actions and Options. + + + + + + + + This is the Displayed Information. + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml new file mode 100644 index 0000000000000..429e85047ac03 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + LG ThinQ Fridge + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml new file mode 100644 index 0000000000000..c3a2026b334ef --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + LG ThinQ Heat Pump + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml new file mode 100644 index 0000000000000..4c0a61efeca35 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + LG ThinQ Washing Machine + + + + + + + + + + Remote Start Actions and Options. + + + + + + + + + + + This is the Displayed Information. + + + + + + + + + + + + + + + + + + + + + + + LG ThinQ Washing Tower + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java new file mode 100644 index 0000000000000..93c7b07f87978 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -0,0 +1,61 @@ +/** + * 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.lgthinq.handler; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link JsonUtils} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class JsonUtils { + public static T unmashallJson(String fileName) { + InputStream inputStream = JsonUtils.class.getResourceAsStream(fileName); + try { + return new ObjectMapper().readValue(inputStream, new TypeReference<>() { + }); + } catch (IOException e) { + throw new IllegalArgumentException( + "Unexpected error. It is not expected this behaviour since json test files must be present."); + } + } + + public static String loadJson(String fileName) { + try { + ClassLoader classLoader = JsonUtils.class.getClassLoader(); + URL fileUrl = classLoader.getResource(fileName); + if (fileUrl == null) { + throw new IllegalArgumentException( + "Unexpected error. It is not expected this behaviour since json test files must be present: " + + fileName); + } + byte[] encoded = Files.readAllBytes(new File(fileUrl.getFile()).toPath()); + return new String(encoded, StandardCharsets.UTF_8); + } catch (IOException e) { + throw new IllegalArgumentException( + "Unexpected error. It is not expected this behaviour since json test files must be present.", e); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java new file mode 100644 index 0000000000000..134474dc9bf69 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java @@ -0,0 +1,213 @@ +/** + * 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.lgthinq.handler; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.io.File; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.lgthinq.internal.LGThinQBindingConstants; +import org.openhab.binding.lgthinq.internal.LGThinQBridgeConfiguration; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.handler.LGThinQBridgeHandler; +import org.openhab.binding.lgthinq.lgservices.*; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +/** + * The {@link LGThinqBridgeTests} + * + * @author Nemer Daud - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@WireMockTest(httpPort = 8880) +class LGThinqBridgeTests { + private static final Logger logger = LoggerFactory.getLogger(LGThinqBridgeTests.class); + private final String fakeBridgeName = "fakeBridgeId"; + private String fakeLanguage = "pt-BR"; + private String fakeCountry = "BR"; + private String fakeUserName = "someone@some.url"; + private String fakePassword = "somepassword"; + private final String gtwResponse = JsonUtils.loadJson("gtw-response-1.json"); + private final String preLoginResponse = JsonUtils.loadJson("prelogin-response-1.json"); + private final String userIdType = "LGE"; + private final String loginSessionId = "emp;11111111;222222222"; + private final String loginSessionResponse = String.format(JsonUtils.loadJson("login-session-response-1.json"), + loginSessionId, fakeUserName, userIdType, fakeUserName); + private final String userInfoReturned = String.format(JsonUtils.loadJson("user-info-response-1.json"), fakeUserName, + fakeUserName); + private final String dashboardListReturned = JsonUtils.loadJson("dashboard-list-response-1.json"); + private final String dashboardWMListReturned = JsonUtils.loadJson("dashboard-list-response-wm.json"); + private final String secretKey = "gregre9812012910291029120912091209"; + private final String oauthTokenSearchKeyReturned = "{\"returnData\":\"" + secretKey + "\"}"; + private final String refreshToken = "12897238974bb327862378ef290128390273aa7389723894734de"; + private final String accessToken = "11a1222c39f16a5c8b3fa45bb4c9be2e00a29a69dced2fa7fe731f1728346ee669f1a96d1f0b4925e5aa330b6dbab882772"; + private final String sessionTokenReturned = String.format(JsonUtils.loadJson("session-token-response-1.json"), + accessToken, refreshToken); + + private String getCurrentTimestamp() { + SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.format(new Date()); + } + + @Test + public void testDiscoveryACThings() { + setupAuthenticationMock(); + LGThinQApiClientService service1 = LGThinQACApiV1ClientServiceImpl.getInstance(); + LGThinQApiClientService service2 = LGThinQACApiV2ClientServiceImpl.getInstance(); + try { + List devices = service2.listAccountDevices("bridgeTest"); + assertEquals(devices.size(), 2); + } catch (Exception e) { + logger.error("Error testing facade", e); + } + } + + private void setupAuthenticationMock() { + stubFor(get(GATEWAY_SERVICE_PATH_V2).willReturn(ok(gtwResponse))); + String preLoginPwd = RestUtils.getPreLoginEncPwd(fakePassword); + stubFor(post("/spx" + PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) + .willReturn(ok(preLoginResponse))); + URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + OAUTH_SEARCH_KEY_PATH) + .queryParam("key_name", "OAUTH_SECRETKEY").queryParam("sever_type", "OP").build(); + stubFor(get(String.format("%s?%s", uri.getPath(), uri.getQuery())).willReturn(ok(oauthTokenSearchKeyReturned))); + String fakeUserNameEncoded = URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8); + stubFor(post(V2_SESSION_LOGIN_PATH + fakeUserNameEncoded) + .withRequestBody(containing("user_auth2=SOME_DUMMY_ENC_PWD")) + .withHeader("X-Signature", equalTo("SOME_DUMMY_SIGNATURE")) + .withHeader("X-Timestamp", equalTo("1643236928")).willReturn(ok(loginSessionResponse))); + stubFor(get(V2_USER_INFO).willReturn(ok(userInfoReturned))); + stubFor(get("/v1" + V2_LS_PATH).willReturn(ok(dashboardListReturned))); + String currTimestamp = getCurrentTimestamp(); + Map empData = new LinkedHashMap<>(); + empData.put("account_type", userIdType); + empData.put("country_code", fakeCountry); + empData.put("username", fakeUserName); + + stubFor(post("/emp/oauth2/token/empsession").withRequestBody(containing("account_type=" + userIdType)) + .withRequestBody(containing("country_code=" + fakeCountry)) + .withRequestBody(containing("username=" + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8))) + .withHeader("lgemp-x-session-key", equalTo(loginSessionId)).willReturn(ok(sessionTokenReturned))); + // faking some constants + Bridge fakeThing = mock(Bridge.class); + ThingUID fakeThingUid = mock(ThingUID.class); + when(fakeThingUid.getId()).thenReturn(fakeBridgeName); + when(fakeThing.getUID()).thenReturn(fakeThingUid); + String tempDir = System.getProperty("java.io.tmpdir"); + LGThinQBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; + LGThinQBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; + LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing); + LGThinQBridgeHandler spyBridge = spy(b); + doReturn(new LGThinQBridgeConfiguration(fakeUserName, fakePassword, fakeCountry, fakeLanguage, 60, + "http://localhost:8880")).when(spyBridge).getConfigAs(any(Class.class)); + spyBridge.initialize(); + TokenManager tokenManager = TokenManager.getInstance(); + try { + if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { + tokenManager.oauthFirstRegistration(fakeBridgeName, fakeLanguage, fakeCountry, fakeUserNameEncoded, + fakePassword, ""); + } + } catch (Exception e) { + logger.error("Error testing facade", e); + } + } + + @BeforeEach + void setUp() { + String tempDir = System.getProperty("java.io.tmpdir"); + File f = new File(tempDir + File.separator + "token.json"); + f.deleteOnExit(); + } + + @Test + public void testDiscoveryWMThings() { + stubFor(get(GATEWAY_SERVICE_PATH_V2).willReturn(ok(gtwResponse))); + String preLoginPwd = RestUtils.getPreLoginEncPwd(fakePassword); + stubFor(post("/spx" + PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) + .willReturn(ok(preLoginResponse))); + URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + OAUTH_SEARCH_KEY_PATH) + .queryParam("key_name", "OAUTH_SECRETKEY").queryParam("sever_type", "OP").build(); + stubFor(get(String.format("%s?%s", uri.getPath(), uri.getQuery())).willReturn(ok(oauthTokenSearchKeyReturned))); + stubFor(post(V2_SESSION_LOGIN_PATH + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8)) + .withRequestBody(containing("user_auth2=SOME_DUMMY_ENC_PWD")) + .withHeader("X-Signature", equalTo("SOME_DUMMY_SIGNATURE")) + .withHeader("X-Timestamp", equalTo("1643236928")).willReturn(ok(loginSessionResponse))); + stubFor(get(V2_USER_INFO).willReturn(ok(userInfoReturned))); + stubFor(get("/v1" + V2_LS_PATH).willReturn(ok(dashboardWMListReturned))); + String dataCollectedWM = JsonUtils.loadJson("wm-data-result.json"); + stubFor(get("/v1/service/devices/fakeDeviceId").willReturn(ok(dataCollectedWM))); + String currTimestamp = getCurrentTimestamp(); + Map empData = new LinkedHashMap<>(); + empData.put("account_type", userIdType); + empData.put("country_code", fakeCountry); + empData.put("username", fakeUserName); + + stubFor(post("/emp/oauth2/token/empsession").withRequestBody(containing("account_type=" + userIdType)) + .withRequestBody(containing("country_code=" + fakeCountry)) + .withRequestBody(containing("username=" + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8))) + .withHeader("lgemp-x-session-key", equalTo(loginSessionId)).willReturn(ok(sessionTokenReturned))); + + Bridge fakeThing = mock(Bridge.class); + ThingUID fakeThingUid = mock(ThingUID.class); + when(fakeThingUid.getId()).thenReturn(fakeBridgeName); + when(fakeThing.getUID()).thenReturn(fakeThingUid); + String tempDir = System.getProperty("java.io.tmpdir"); + LGThinQBindingConstants.THINQ_USER_DATA_FOLDER = "" + tempDir; + LGThinQBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; + LGThinQBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; + LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing); + + final LGThinQWMApiClientService service2 = LGThinQWMApiV2ClientServiceImpl.getInstance(); + TokenManager tokenManager = TokenManager.getInstance(); + try { + if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { + tokenManager.oauthFirstRegistration(fakeBridgeName, fakeLanguage, fakeCountry, fakeUserName, + fakePassword, "http://localhost:8880"); + } + List devices = service2.listAccountDevices("bridgeTest"); + assertEquals(devices.size(), 1); + // service2.getDeviceData(fakeBridgeName, "fakeDeviceId", new WasherDryerCapability()); + } catch (Exception e) { + logger.error("Error testing facade", e); + } + } + + // @Test + // void TestWakeUp() throws LGThinqApiException { + // setupAuthenticationMock(); + // LGThinQWMApiClientService service = LGThinQWMApiV1ClientServiceImpl.getInstance(); + // service.wakeUp("xxx", "yyyy", true); + // } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java new file mode 100644 index 0000000000000..03b6207c5f66f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java @@ -0,0 +1,32 @@ +/** + * 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.lgthinq.internal.handler; + +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; +import org.openhab.core.library.types.DateTimeType; + +/** + * The LGThinQWasherDryerHandlerTest test class. + * + * @author Nemer Daud - Initial contribution + */ +class LGThinQWasherDryerHandlerTest { + + @Test + void updateDeviceChannels() { + String time = String.format("%02.0f:%02.0f", 0.00, 0.0); + DateTimeType dt = new DateTimeType(time); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java new file mode 100644 index 0000000000000..0f2d668aef2a3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java @@ -0,0 +1,55 @@ +/** + * 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.lgthinq.lgservices.model; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.File; +import java.io.IOException; +import java.net.URL; + +import org.junit.jupiter.api.Test; +import org.openhab.binding.lgthinq.handler.JsonUtils; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link CapabilityFactoryTest} + * + * @author Nemer Daud - Initial contribution + */ +class CapabilityFactoryTest { + private ObjectMapper objectMapper = new ObjectMapper(); + + @Test + void create() throws IOException, LGThinqException { + ClassLoader classLoader = JsonUtils.class.getClassLoader(); + assertNotNull(classLoader); + URL fileUrl = classLoader.getResource("thinq-washer-v2-cap.json"); + assertNotNull(fileUrl); + File capFile = new File(fileUrl.getFile()); + JsonNode mapper = objectMapper.readTree(capFile); + WasherDryerCapability wpCap = (WasherDryerCapability) CapabilityFactory.getInstance().create(mapper, + WasherDryerCapability.class); + assertNotNull(wpCap); + assertEquals(40, wpCap.getCourses().size()); + assertTrue(wpCap.getRinseFeat().getValuesMapping().size() > 1); + assertTrue(wpCap.getSpinFeat().getValuesMapping().size() > 1); + assertTrue(wpCap.getSoilWash().getValuesMapping().size() > 1); + assertTrue(wpCap.getTemperatureFeat().getValuesMapping().size() > 1); + assertTrue(wpCap.hasDoorLook()); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json new file mode 100644 index 0000000000000..6c1056fbe2375 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json @@ -0,0 +1,206 @@ +{ + "resultCode":"0000", + "result":{ + "langPackCommonVer":"125.6", + "langPackCommonUri":"https://objectcontent.lgthinq.com/f1cae877-1d1e-4c12-8010-acbcdcce2df1?hdnts=exp=1706183232~hmac=257aa8146a089de87496cb13aa0b43761a19e7db225558dfb8996919746b465b", + "item":[ + { + "modelName":"RAC_056905_WW", + "subModelName":"", + "deviceType":401, + "deviceCode":"AI01", + "alias":"Bedroom", + "deviceId":"abra-cadabra-0001-5771", + "fwVer":"2.5.8_RTOS_3K", + "imageFileName":"ac_home_wall_airconditioner_img.png", + "imageUrl":"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", + "smallImageUrl":"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", + "ssid":"dummy-ssid", + "macAddress":"74:40:be:92:ac:08", + "networkType":"02", + "timezoneCode":"America/Sao_Paulo", + "timezoneCodeAlias":"Brazil/Sao Paulo", + "utcOffset":-3, + "utcOffsetDisplay":"-03:00", + "dstOffset":-2, + "dstOffsetDisplay":"-02:00", + "curOffset":-2, + "curOffsetDisplay":"-02:00", + "sdsGuide":"{\"deviceCode\":\"AI01\"}", + "newRegYn":"N", + "remoteControlType":"", + "modelJsonVer":7.13, + "modelJsonUri":"https://aic.lgthinq.com:46030/api/webContents/modelJSON?modelName=modelJSON_401&countryCode=KR&contentsId=abra-cadabra-0001-5771&authKey=thinq", + "appModuleVer":12.49, + "appModuleUri":"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155", + "appRestartYn":"Y", + "appModuleSize":6082481, + "langPackProductTypeVer":59.9, + "langPackProductTypeUri":"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea", + "langPackModelVer":"", + "langPackModelUri":"", + "deviceState":"E", + "online":false, + "platformType":"thinq1", + "regDt":2.0200909053555E13, + "modelProtocol":"STANDARD", + "order":0, + "drServiceYn":"N", + "fwInfoList":[ + { + "partNumber":"SAA38690433", + "checksum":"00000000", + "verOrder":0 + } + ], + "guideTypeYn":"Y", + "guideType":"RAC_TYPE1", + "regDtUtc":"20200909073555", + "regIndex":0, + "groupableYn":"Y", + "controllableYn":"Y", + "combinedProductYn":"N", + "masterYn":"Y", + "pccModelYn":"N", + "sdsPid":{ + "sds4":"", + "sds3":"", + "sds2":"", + "sds1":"" + }, + "autoOrderYn":"N", + "modelNm":"RAC_056905_WW", + "initDevice":false, + "existsEntryPopup":"N", + "tclcount":0 + }, + { + "appType":"NUTS", + "modelCountryCode":"WW", + "countryCode":"BR", + "modelName":"RAC_056905_WW", + "deviceType":401, + "deviceCode":"AI01", + "alias":"Office", + "deviceId":"abra-cadabra-0001-5772", + "fwVer":"", + "imageFileName":"ac_home_wall_airconditioner_img.png", + "imageUrl":"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", + "smallImageUrl":"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", + "ssid":"xxxxxxxxx", + "softapId":"", + "softapPass":"", + "macAddress":"", + "networkType":"02", + "timezoneCode":"America/Sao_Paulo", + "timezoneCodeAlias":"Brazil/Sao Paulo", + "utcOffset":-3, + "utcOffsetDisplay":"-03:00", + "dstOffset":-2, + "dstOffsetDisplay":"-02:00", + "curOffset":-2, + "curOffsetDisplay":"-02:00", + "sdsGuide":"{\"deviceCode\":\"AI01\"}", + "newRegYn":"N", + "remoteControlType":"", + "userNo":"xxxxxxxxxxx", + "tftYn":"N", + "modelJsonVer":12.11, + "modelJsonUri":"https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f", + "appModuleVer":12.49, + "appModuleUri":"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155", + "appRestartYn":"Y", + "appModuleSize":6082481, + "langPackProductTypeVer":59.9, + "langPackProductTypeUri":"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea", + "deviceState":"E", + "snapshot":{ + "airState.windStrength":8.0, + "airState.wMode.lowHeating":0.0, + "airState.diagCode":0.0, + "airState.lightingState.displayControl":1.0, + "airState.wDir.hStep":0.0, + "mid":8.4615358E7, + "airState.energy.onCurrent":476.0, + "airState.wMode.airClean":0.0, + "airState.quality.sensorMon":0.0, + "airState.energy.accumulatedTime":0.0, + "airState.miscFuncState.antiBugs":0.0, + "airState.tempState.target":18.0, + "airState.operation":1.0, + "airState.wMode.jet":0.0, + "airState.wDir.vStep":2.0, + "timestamp":1.643248573766E12, + "airState.powerSave.basic":0.0, + "airState.quality.PM10":0.0, + "static":{ + "deviceType":"401", + "countryCode":"BR" + }, + "airState.quality.overall":0.0, + "airState.tempState.current":25.0, + "airState.miscFuncState.extraOp":0.0, + "airState.energy.accumulated":0.0, + "airState.reservation.sleepTime":0.0, + "airState.miscFuncState.autoDry":0.0, + "airState.reservation.targetTimeToStart":0.0, + "meta":{ + "allDeviceInfoUpdate":false, + "messageId":"fVz2AE-2SC-rf3GnerGdeQ" + }, + "airState.quality.PM1":0.0, + "airState.wMode.smartCare":0.0, + "airState.quality.PM2":0.0, + "online":true, + "airState.opMode":0.0, + "airState.reservation.targetTimeToStop":0.0, + "airState.filterMngStates.maxTime":0.0, + "airState.filterMngStates.useTime":0.0 + }, + "online":true, + "platformType":"thinq2", + "area":45883, + "regDt":2.0220111184827E13, + "blackboxYn":"Y", + "modelProtocol":"STANDARD", + "order":0, + "drServiceYn":"N", + "fwInfoList":[ + { + "checksum":"00004105", + "order":1.0, + "partNumber":"SAA40128563" + } + ], + "modemInfo":{ + "appVersion":"clip_hna_v1.9.116", + "modelName":"RAC_056905_WW", + "modemType":"QCOM_QCA4010", + "ruleEngine":"y" + }, + "guideTypeYn":"Y", + "guideType":"RAC_TYPE1", + "regDtUtc":"20220111204827", + "regIndex":0, + "groupableYn":"Y", + "controllableYn":"Y", + "combinedProductYn":"N", + "masterYn":"Y", + "pccModelYn":"N", + "sdsPid":{ + "sds4":"", + "sds3":"", + "sds2":"", + "sds1":"" + }, + "autoOrderYn":"N", + "initDevice":false, + "existsEntryPopup":"N", + "tclcount":0 + } + ], + "group":[ + + ] + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json new file mode 100644 index 0000000000000..50f4050ce40ba --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json @@ -0,0 +1,82 @@ +{ + "resultCode":"0000", + "result":{ + "langPackCommonVer":"125.6", + "langPackCommonUri":"https://objectcontent.lgthinq.com/f1cae877-1d1e-4c12-8010-acbcdcce2df1?hdnts=exp=1706183232~hmac=257aa8146a089de87496cb13aa0b43761a19e7db225558dfb8996919746b465b", + "item":[ + { + "modelName":"RAC_056905_WW", + "subModelName":"", + "deviceType":201, + "deviceCode":"WM01", + "alias":"Bedroom", + "deviceId":"washer-0001-5771", + "fwVer":"2.5.8_RTOS_3K", + "imageFileName":"washmachine-1.png", + "imageUrl":"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", + "smallImageUrl":"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", + "ssid":"dummy-ssid", + "macAddress":"74:40:be:92:ac:08", + "networkType":"02", + "timezoneCode":"America/Sao_Paulo", + "timezoneCodeAlias":"Brazil/Sao Paulo", + "utcOffset":-3, + "utcOffsetDisplay":"-03:00", + "dstOffset":-2, + "dstOffsetDisplay":"-02:00", + "curOffset":-2, + "curOffsetDisplay":"-02:00", + "sdsGuide":"{\"deviceCode\":\"WM01\"}", + "newRegYn":"N", + "remoteControlType":"", + "modelJsonVer":7.13, + "modelJsonUri":"https://aic.lgthinq.com:46030/api/webContents/modelJSON?modelName=modelJSON_401&countryCode=KR&contentsId=abra-cadabra-0001-5771&authKey=thinq", + "appModuleVer":12.49, + "appModuleUri":"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155", + "appRestartYn":"Y", + "appModuleSize":6082481, + "langPackProductTypeVer":59.9, + "langPackProductTypeUri":"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea", + "langPackModelVer":"", + "langPackModelUri":"", + "deviceState":"E", + "online":false, + "platformType":"thinq2", + "regDt":2.0200909053555E13, + "modelProtocol":"STANDARD", + "order":0, + "drServiceYn":"N", + "fwInfoList":[ + { + "partNumber":"SAA38690433", + "checksum":"00000000", + "verOrder":0 + } + ], + "guideTypeYn":"Y", + "guideType":"RAC_TYPE1", + "regDtUtc":"20200909073555", + "regIndex":0, + "groupableYn":"Y", + "controllableYn":"Y", + "combinedProductYn":"N", + "masterYn":"Y", + "pccModelYn":"N", + "sdsPid":{ + "sds4":"", + "sds3":"", + "sds2":"", + "sds1":"" + }, + "autoOrderYn":"N", + "modelNm":"RAC_056905_WW", + "initDevice":false, + "existsEntryPopup":"N", + "tclcount":0 + } + ], + "group":[ + + ] + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/fridge-data-result.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/fridge-data-result.json new file mode 100644 index 0000000000000..576a4cb45b8ce --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/fridge-data-result.json @@ -0,0 +1,125 @@ +{ + "resultCode": "0000", + "result": { + "appType": "NUTS", + "modelCountryCode": "WW", + "countryCode": "NO", + "modelName": "2REB1GLTL1__2", + "deviceType": 101, + "deviceCode": "KI0104", + "alias": "My fridge", + "deviceId": "UUID", + "fwVer": "", + "imageFileName": "home_appliances_img_fridge.png", + "ssid": "WifiSSID", + "softapId": "", + "softapPass": "", + "macAddress": "", + "networkType": "02", + "timezoneCode": "Europe/Oslo", + "timezoneCodeAlias": "Europe/Oslo", + "utcOffset": 1, + "utcOffsetDisplay": "+01:00", + "dstOffset": 2, + "dstOffsetDisplay": "+02:00", + "curOffset": 1, + "curOffsetDisplay": "+01:00", + "sdsGuide": "{\"deviceCode\":\"KI01\"}", + "newRegYn": "N", + "remoteControlType": "", + "userNo": "NO00000000000", + "tftYn": "N", + "deviceState": "E", + "snapshot": { + "fwUpgradeInfo": { + "upgSched": { + "upgUtc": "0", + "cmd": "none" + } + }, + "static": { + "deviceType": "101", + "countryCode": "NO" + }, + "meta": { + "allDeviceInfoUpdate": false, + "messageId": "coDTKiAHSaGPecyqOkLRFg" + }, + "mid": 1.288660304E9, + "online": true, + "refState": { + "dispenserCapacity": 255.0, + "dispenserUnit": "IGNORE", + "freezerTemp": 255.0, + "sabbathMode": "IGNORE", + "tempUnit": "CELSIUS", + "ecoFriendly": "OFF", + "activeSaving": "IGNORE", + "voiceMode": "IGNORE", + "smartSavingRun": "IGNORE", + "atLeastOneDoorOpen": "CLOSE", + "expressMode": "IGNORE", + "freshAirFilter": "IGNORE", + "convertibleTemp": 255.0, + "waterFilter": "IGNORE", + "dispenserMode": "NOT_DEFINE_VALUE value:255", + "displayLock": "UNLOCK", + "expressFridge": "OFF", + "selfCareMode": "IGNORE", + "drawerMode": "IGNORE", + "fridgeTemp": 5.0, + "pantryMode": "IGNORE", + "craftIceMode": "IGNORE", + "dualFridgeMode": "IGNORE", + "monStatus": "NORMAL", + "smartSavingMode": "IGNORE", + "smartCareV2": "ON" + }, + "timestamp": 1.676199284675E12 + }, + "online": true, + "platformType": "thinq2", + "area": 254946, + "regDt": 2.0221216190446E13, + "blackboxYn": "Y", + "modelProtocol": "STANDARD", + "receipeVersion": 0, + "activeSaving": "IGNORE", + "smartCareV2": "ON", + "order": 0, + "nlpAlias": "none", + "drServiceYn": "N", + "fwInfoList": [ + { + "checksum": "0000AC5B", + "order": 2.0, + "partNumber": "SAA42468501" + }, + { + "checksum": "0000D2B2", + "order": 1.0, + "partNumber": "SAA42473902" + } + ], + "regDtUtc": "20221216170446", + "regIndex": 0, + "groupableYn": "N", + "controllableYn": "N", + "combinedProductYn": "N", + "masterYn": "Y", + "initDevice": false, + "firebaseLogKey": "FIREBASE:LOG:KEY:BLABLA", + "manufacture": { + "inventoryOrg": "CP3", + "macAddress": "00:00:00:00:00:00", + "manufactureModel": "GC-B411EQAF.AMCQEUR", + "manufacturedAt": "2022-06-16T01:06:21+00:00", + "registeredAt": "2022-06-27T04:18:34.022045+00:00", + "salesModel": "GLM71MCCSF.AMCQEUR", + "serialNo": "SerialNumber" + }, + "upgradableYn": "N", + "autoFwDownloadYn": "N", + "tclcount": 0 + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/fridge-data-result2.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/fridge-data-result2.json new file mode 100644 index 0000000000000..0a712df1e4daa --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/fridge-data-result2.json @@ -0,0 +1,125 @@ +{ + "resultCode": "0000", + "result": { + "appType": "NUTS", + "modelCountryCode": "WW", + "countryCode": "NO", + "modelName": "2REB1GLTL1__2", + "deviceType": 101, + "deviceCode": "KI0104", + "alias": "My fridge", + "deviceId": "UUID", + "fwVer": "", + "imageFileName": "home_appliances_img_fridge.png", + "ssid": "WifiSSID", + "softapId": "", + "softapPass": "", + "macAddress": "", + "networkType": "02", + "timezoneCode": "Europe/Oslo", + "timezoneCodeAlias": "Europe/Oslo", + "utcOffset": 1, + "utcOffsetDisplay": "+01:00", + "dstOffset": 2, + "dstOffsetDisplay": "+02:00", + "curOffset": 1, + "curOffsetDisplay": "+01:00", + "sdsGuide": "{\"deviceCode\":\"KI01\"}", + "newRegYn": "N", + "remoteControlType": "", + "userNo": "NO00000000000", + "tftYn": "N", + "deviceState": "E", + "snapshot": { + "fwUpgradeInfo": { + "upgSched": { + "upgUtc": "0", + "cmd": "none" + } + }, + "static": { + "deviceType": "101", + "countryCode": "NO" + }, + "meta": { + "allDeviceInfoUpdate": false, + "messageId": "yOfRuCbKRqyBQfll-PbaoA" + }, + "mid": 1.291752295E9, + "online": true, + "refState": { + "dispenserCapacity": 255.0, + "dispenserUnit": "IGNORE", + "freezerTemp": 255.0, + "sabbathMode": "IGNORE", + "tempUnit": "CELSIUS", + "ecoFriendly": "OFF", + "activeSaving": "IGNORE", + "voiceMode": "IGNORE", + "smartSavingRun": "IGNORE", + "atLeastOneDoorOpen": "CLOSE", + "expressMode": "IGNORE", + "freshAirFilter": "IGNORE", + "convertibleTemp": 255.0, + "waterFilter": "IGNORE", + "dispenserMode": "NOT_DEFINE_VALUE value:255", + "displayLock": "UNLOCK", + "expressFridge": "OFF", + "selfCareMode": "IGNORE", + "drawerMode": "IGNORE", + "fridgeTemp": 6.0, + "pantryMode": "IGNORE", + "craftIceMode": "IGNORE", + "dualFridgeMode": "IGNORE", + "monStatus": "NORMAL", + "smartSavingMode": "IGNORE", + "smartCareV2": "ON" + }, + "timestamp": 1.676202376697E12 + }, + "online": true, + "platformType": "thinq2", + "area": 254946, + "regDt": 2.0221216190446E13, + "blackboxYn": "Y", + "modelProtocol": "STANDARD", + "receipeVersion": 0, + "activeSaving": "IGNORE", + "smartCareV2": "ON", + "order": 0, + "nlpAlias": "none", + "drServiceYn": "N", + "fwInfoList": [ + { + "checksum": "0000AC5B", + "order": 2.0, + "partNumber": "SAA42468501" + }, + { + "checksum": "0000D2B2", + "order": 1.0, + "partNumber": "SAA42473902" + } + ], + "regDtUtc": "20221216170446", + "regIndex": 0, + "groupableYn": "N", + "controllableYn": "N", + "combinedProductYn": "N", + "masterYn": "Y", + "initDevice": false, + "firebaseLogKey": "FIREBASE:LOG:KEY:BLABLA", + "manufacture": { + "inventoryOrg": "CP3", + "macAddress": "00:00:00:00:00:00", + "manufactureModel": "GC-B411EQAF.AMCQEUR", + "manufacturedAt": "2022-06-16T01:06:21+00:00", + "registeredAt": "2022-06-27T04:18:34.022045+00:00", + "salesModel": "GLM71MCCSF.AMCQEUR", + "serialNo": "SerialNumber" + }, + "upgradableYn": "N", + "autoFwDownloadYn": "N", + "tclcount": 0 + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/gtw-response-1.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/gtw-response-1.json new file mode 100644 index 0000000000000..7125637ff95d4 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/gtw-response-1.json @@ -0,0 +1,64 @@ +{ + "resultCode":"0000", + "result":{ + "countryCode":"BR", + "languageCode":"pt-BR", + "thinq1Uri":"http://localhost:8880/api", + "thinq2Uri":"http://localhost:8880/v1", + "empUri":"http://localhost:8880", + "empSpxUri":"http://localhost:8880/spx", + "rtiUri":"localhost:8880", + "mediaUri":"localhost:8880", + "appLatestVer":"4.0.14230", + "appUpdateYn":"N", + "appLink":"market://details?id=com.lgeha.nuts", + "uuidLoginYn":"N", + "lineLoginYn":"N", + "lineChannelId":"", + "cicTel":"4004-5400", + "cicUri":"", + "isSupportVideoYn":"N", + "countryLangDescription":"Brasil/Português", + "empTermsUri":"http://localhost:8880", + "googleAssistantUri":"https://assistant.google.com/services/invoke/uid/000000d26892b8a3", + "smartWorldUri":"", + "racUri":"br.rac.lgeapi.com", + "cssUri":"https://aic-common.lgthinq.com", + "cssWebUri":"http://s3-an2-op-t20-css-web-resource.s3-website.ap-northeast-2.amazonaws.com", + "iotssUri":"https://aic-iotservice.lgthinq.com", + "chatBotUri":"", + "autoOrderSetUri":"", + "autoOrderManageUri":"", + "aiShoppingUri":"", + "onestopCall":"", + "onestopEngineerUri":"", + "hdssUri":"", + "amazonDrsYn":"N", + "features":{ + "supportTvIoTServerYn":"Y", + "disableWeatherCard":"Y", + "thinqCss":"Y", + "bleConfirmYn":"Y", + "tvRcmdContentYn":"Y", + "supportProductManualYn":"N", + "clientDbYn":"Y", + "androidAutoYn":"Y", + "searchYn":"Y", + "thinqFaq":"Y", + "thinqNotice":"Y", + "groupControlYn":"Y", + "inAppReviewYn":"Y", + "cicSupport":"Y", + "qrRegisterYn":"Y", + "supportBleYn":"Y" + }, + "serviceCards":[ + + ], + "uris":{ + "takeATourUri":"https://s3-us2-op-t20-css-contents.s3.us-west-2.amazonaws.com/workexperience-new/ios/no-version/index.html", + "gscsUri":"https://gscs-america.lge.com", + "amazonDartUri":"https://shs.lgthinq.com" + } + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/login-session-response-1.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/login-session-response-1.json new file mode 100644 index 0000000000000..f16aa0192270d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/login-session-response-1.json @@ -0,0 +1,55 @@ +{ + "account":{ + "loginSessionID":"%s", + "userID":"%s", + "userIDType":"%s", + "dateOfBirth":"05-05-1978", + "country":"BR", + "countryName":"Brazil", + "blacklist":"N", + "age":"43", + "isSubscribe":"N", + "changePw":"N", + "toEmailId":"N", + "periodPW":"N", + "lgAccount":"Y", + "isService":"Y", + "userNickName":"faker", + "termsList":[ + + ], + "userIDList":[ + { + "lgeIDList":[ + { + "lgeIDType":"LGE", + "userID":"%s" + } + ] + } + ], + "serviceList":[ + { + "svcCode":"SVC202", + "svcName":"LG ThinQ", + "isService":"Y", + "joinDate":"30-04-2020" + }, + { + "svcCode":"SVC710", + "svcName":"EMP OAuth", + "isService":"Y", + "joinDate":"30-04-2020" + } + ], + "displayUserID":"faker", + "notiList":{ + "totCount":"0", + "list":[ + + ] + }, + "authUser":"N", + "dummyIdFlag":"N" + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/prelogin-response-1.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/prelogin-response-1.json new file mode 100644 index 0000000000000..e5162aa26e8dc --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/prelogin-response-1.json @@ -0,0 +1,5 @@ +{ + "encrypted_pw": "SOME_DUMMY_ENC_PWD", + "signature": "SOME_DUMMY_SIGNATURE", + "tStamp": "1643236928" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/session-token-response-1.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/session-token-response-1.json new file mode 100644 index 0000000000000..d7501de38a598 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/session-token-response-1.json @@ -0,0 +1,7 @@ +{ + "status": 1, + "access_token": "%s", + "expires_in": "3600", + "refresh_token": "%s", + "oauth2_backend_url": "http://localhost:8880/" +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/thinq-fridge-v2-cap.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/thinq-fridge-v2-cap.json new file mode 100644 index 0000000000000..4978bb744d0e1 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/thinq-fridge-v2-cap.json @@ -0,0 +1,1347 @@ +{ + "Info": { + "productType": "REF", + "country": "WW", + "modelType": "BF", + "model": "T20_B PJT THOR (Larder)(PP/C1) 유럽", + "modelName": "2REB1GLTL1__2", + "networkType": "WIFI", + "version": "1.02" + }, + "Module": { + "WPM": { + "GRM_CEN01_Main": "201", + "GRM_CEN02_UserSaving": "202", + "GRM_CEN04_RefViewer": "202", + "GRM_CEN05_ImgViewer": "202", + "GRM_FOD01_Main": "201", + "GRM_FOD02_EditFoodInfo": "201", + "GRM_FOD03_EditFoodIcon": "201", + "GRM_FOD04_AddFood": "201", + "GRM_ENM01_Main": "201", + "GRM_ENM02_DoorOpenings": "201", + "GRM_ENM03_PowerConsume": "201", + "GRM_ENM04_SetSaving": "202", + "GRM_ECO01_Main": "202", + "GRM_ECO02_Active": "202", + "GRM_ECO03_SavingRatio": "202", + "GRM_ECO04_SavingDetail": "202", + "GRM_ECO05_ViewTip": "202", + "GCM_SDS01_SdsMain": "201", + "GRM_FOT01_Main": "201", + "GRM_SET01_Main": "201", + "GRM_SET02_PushList": "201", + "GRM_SMC01_Main": "202", + "GRM_SMC02_SafeStore": "201", + "GRM_SMC03_ActiveCooling": "201", + "GRM_SMC04_SmartFreshStorage": "201", + "GRM_SMC05_ActiveIcePlus": "201", + "GRM_PHO01_Main": "201", + "GRM_SHO01_Main": "201", + "GRM_SBS01_Main": "201", + "GRM_SBS02_Local": "201", + "GRM_SET04_WeatherLocation": "201" + }, + "Menu": [ + "GRM_SMC01_Main", + "GCM_SDS01_SdsMain", + "GRM_SET01_Main" + ] + }, + "Config": { + "targetRoot": "refState", + "ignoreValue": { + "key": "IGNORE", + "index": -99 + }, + "replaceStateValue": "@RE_STATE_REPLACE_FILTER_W", + "wifiDiagnosis": "true", + "hasInsideView": false, + "fota": "true", + "hasdoor": "Y", + "blackBox": "N", + "supportFoodManager": true, + "ecoFriendlyDefaultIndex": { + "fridgeTemp": { + "tempUnit_C": 1, + "tempUnit_F": 1 + }, + "freezerTemp": { + "tempUnit_C": 1, + "tempUnit_F": 1 + }, + "convertibleTemp": { + "tempUnit_C": 1, + "tempUnit_F": 1 + }, + "expressMode": 0, + "expressFridge": 0 + }, + "sabbathDefaultSchedule": { + "type": "location", + "startDay": "FRI", + "endDay": "SAT", + "startTime": 0, + "endTime": 0, + "weekRepeatYn": "Y" + }, + "sabbathDayListMap": { + "FRI": "@RE_TERM_DAY_FRI_W", + "SAT": "@RE_TERM_DAY_SAT_W" + }, + "smartCareVersion": "V2", + "smartCare": { + "useSmartStorage": false, + "useSmartFreshStorage": true, + "useActiveIcePlus": false, + "useActiveSavingsV2": true + }, + "sideMenuInfo": { + "GRM_FOD01_Main": { + "title": "@RE_FOOD_MANAGEMENT_W", + "image": "image/ref_sidemenu_btn_foodmanager.png" + }, + "GRM_ENM01_Main": { + "title": "@RE_ENM_TITLE_W", + "image": "image/ref_sidemenu_btn_energymonitoring.png" + }, + "GCM_SDS01_SdsMain": { + "title": "@CP_NAME_SMART_DIAGNOSIS_W", + "image": "image/ref_sidemenu_btn_smart_diagnosis.png" + }, + "GRM_SET01_Main": { + "title": "@CP_SETTING_W", + "image": "image/ref_sidemenu_btn_setting.png" + }, + "GRM_ECO01_Main": { + "title": "@RE_ENM_TITLE_W", + "image": "wpm/GRM/image/ref_sidemenu_btn_energymonitoring.png" + }, + "GRM_SMC01_Main": { + "title": "@RE_SMARTCARE_RUN_V2_W", + "image": "wpm/GRM/image/ref_sidemenu_btn_smartcare.png" + }, + "GRM_SHO01_Main": { + "title": "@RE_GROCERY_LIST_W", + "image": "wpm/GRM/image/ref_sidemenu_btn_shopping.png" + } + }, + "visibleItems": [{ + "feature": "fridgeTemp", + "imageUrl": "", + "monTitle": "@RE_TERM_FRIDGE_W", + "controlTitle": "@RE_TERM_FRIDGE_W", + "controlDisabledOption": [{ + "optionValue": "@CP_OFF_EN_W", + "replaceOptionValue": "IGNORE" + } + ] + }, { + "feature": "expressFridge", + "imageUrl": "wpm/GRM/image/ref_home_ic_coldstorage.png", + "monTitle": "@RE_TERM_EXPRESS_FRIDGE_W", + "controlTitle": "@RE_TERM_EXPRESS_FRIDGE_W", + "templateType": "typeSwitch.html" + }, { + "feature": "ecoFriendly", + "imageUrl": "image/icon_fridge_eco.png", + "monTitle": "@RE_TERM_ECO_FRIENDLY_W", + "controlTitle": "@RE_TERM_ECO_FRIENDLY_W", + "templateType": "typeSwitch.html" + }, { + "feature": "smartCareV2", + "imageUrl": "wpm/GRM/image/ref_home_ic_smartcare.png", + "monTitle": "@RE_SMARTCARE_RUN_V2_W", + "controlTitle": "@RE_SMARTCARE_RUN_V2_W", + "templateType": "NONE" + } + ] + }, + "MonitoringValue": { + "monStatus": { + "_comment": "Monitoring Status _ Not Shown Item", + "dataType": "enum", + "default": "NORMAL", + "visibleItem": { + "monitoringIndex": [ + 0, + 1, + 2 + ], + "controlIndex": [ + 0, + 1, + 2 + ] + }, + "valueMapping": { + "FAIL": { + "index": 0, + "label": "", + "_comment": "Fail" + }, + "NOT_WORK": { + "index": 1, + "label": "", + "_comment": "Not working" + }, + "NORMAL": { + "index": 2, + "label": "", + "_comment": "Normal" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "fridgeTemp": { + "_comment": "Fridge Target Temperature", + "dataType": "range", + "default": 1, + "visibleItem": { + "monitoringIndex": [], + "controlIndex": [] + }, + "targetKey": { + "tempUnit": { + "CELSIUS": "fridgeTemp_C", + "FAHRENHEIT": "fridgeTemp_F" + } + }, + "valueMapping": { + "min": 0, + "max": 255, + "step": 1 + } + }, + "freezerTemp": { + "_comment": "Freezer Target Temperature", + "dataType": "range", + "default": 1, + "visibleItem": { + "monitoringIndex": [], + "controlIndex": [] + }, + "targetKey": { + "tempUnit": { + "CELSIUS": "freezerTemp_C", + "FAHRENHEIT": "freezerTemp_F" + } + }, + "valueMapping": { + "min": 0, + "max": 255, + "step": 1 + } + }, + "convertibleTemp": { + "_comment": "Convertible Target Temperature", + "dataType": "range", + "default": 1, + "visibleItem": { + "monitoringIndex": [], + "controlIndex": [] + }, + "targetKey": { + "tempUnit": { + "CELSIUS": "convertibleTemp_C", + "FAHRENHEIT": "convertibleTemp_F" + } + }, + "valueMapping": { + "min": 0, + "max": 255, + "step": 1 + } + }, + "expressMode": { + "_comment": "Express Fridge, ExpressFreeze, Rapid Freeze", + "dataType": "enum", + "default": "OFF", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [ + 0, + 1 + ] + }, + "valueMapping": { + "OFF": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "Express Mode OFF" + }, + "EXPRESS_ON": { + "index": 1, + "label": "@CP_ON_EN_W", + "_comment": "Express Fridge or Express Freeze ON" + }, + "RAPID_ON": { + "index": 2, + "label": "@RE_MAIN_SPEED_FREEZE_TERM_W", + "_comment": "Rapid Freeze ON" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "tempUnit": { + "_comment": "Temperature Unit", + "dataType": "enum", + "default": "FAHRENHEIT", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [] + }, + "valueMapping": { + "CELSIUS": { + "index": 0, + "label": "˚C", + "_comment": "Celsius Unit" + }, + "FAHRENHEIT": { + "index": 1, + "label": "˚F", + "_comment": "Fahrenheit Unit" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "freshAirFilter": { + "_comment": "Fresh Air Filter Status", + "dataType": "enum", + "default": "OFF", + "visibleItem": { + "monitoringIndex": [ + 0, + 1, + 2, + 3 + ], + "controlIndex": [] + }, + "valueMapping": { + "OFF": { + "index": 0, + "label": "@CP_TERM_OFF_KO_W", + "_comment": "Fresh Air Filter OFF" + }, + "AUTO": { + "index": 1, + "label": "@RE_STATE_FRESH_AIR_FILTER_MODE_AUTO_W", + "_comment": "Fresh Air Filter AUTO" + }, + "POWER": { + "index": 2, + "label": "@RE_STATE_FRESH_AIR_FILTER_MODE_POWER_W", + "_comment": "Fresh Air Filter POWER" + }, + "REPLACE": { + "index": 3, + "label": "@RE_STATE_REPLACE_FILTER_W", + "_comment": "Fresh Air Filter REPLACE" + }, + "SMART_STORAGE_POWER": { + "index": 4, + "label": "", + "_comment": "Fresh Air Filter Smart Storage POWER" + }, + "SMART_STORAGE_OFF": { + "index": 5, + "label": "", + "_comment": "Fresh Air Filter Smart Storage OFF" + }, + "SMART_STORAGE_ON": { + "index": 6, + "label": "", + "_comment": "Fresh Air Filter Smart Storage ON" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "waterFilter": { + "_comment": "Water Filter Status", + "dataType": "enum", + "default": "0_MONTH", + "visibleItem": { + "monitoringIndex": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ], + "controlIndex": [] + }, + "valueMapping": { + "0_MONTH": { + "index": 0, + "label": "@RE_TERM_OK_W", + "_comment": "Water Filter 0 Month Passed" + }, + "1_MONTH": { + "index": 1, + "label": "@RE_TERM_OK_W", + "_comment": "Water Filter 1 Month Passed" + }, + "2_MONTH": { + "index": 2, + "label": "@RE_TERM_OK_W", + "_comment": "Water Filter 2 Month Passed" + }, + "3_MONTH": { + "index": 3, + "label": "@RE_TERM_OK_W", + "_comment": "Water Filter 3 Month Passed" + }, + "4_MONTH": { + "index": 4, + "label": "@RE_TERM_OK_W", + "_comment": "Water Filter 4 Month Passed" + }, + "5_MONTH": { + "index": 5, + "label": "@RE_TERM_OK_W", + "_comment": "Water Filter 5 Month Passed" + }, + "6_MONTH": { + "index": 6, + "label": "@RE_STATE_REPLACE_FILTER_W", + "_comment": "Water Filter 6 Month Passed" + }, + "7_MONTH": { + "index": 7, + "label": "@RE_STATE_REPLACE_FILTER_W", + "_comment": "Water Filter 7 Month Passed" + }, + "8_MONTH": { + "index": 8, + "label": "@RE_STATE_REPLACE_FILTER_W", + "_comment": "Water Filter 8 Month Passed" + }, + "9_MONTH": { + "index": 9, + "label": "@RE_STATE_REPLACE_FILTER_W", + "_comment": "Water Filter 9 Month Passed" + }, + "10_MONTH": { + "index": 10, + "label": "@RE_STATE_REPLACE_FILTER_W", + "_comment": "Water Filter 10 Month Passed" + }, + "11_MONTH": { + "index": 11, + "label": "@RE_STATE_REPLACE_FILTER_W", + "_comment": "Water Filter 11 Month Passed" + }, + "12_MONTH": { + "index": 12, + "label": "@RE_STATE_REPLACE_FILTER_W", + "_comment": "Water Filter 12 Month Passed" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "displayLock": { + "_comment": "Display Lock Status(unlock, lock)", + "dataType": "enum", + "default": "UNLOCK", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [] + }, + "valueMapping": { + "UNLOCK": { + "index": 0, + "label": "", + "_comment": "Display Panel Unlocked" + }, + "LOCK": { + "index": 1, + "label": "", + "_comment": "Display Panel Locked" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "sabbathMode": { + "_comment": "Sabbath Mode State (ON, OFF)", + "dataType": "enum", + "default": "OFF", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [] + }, + "valueMapping": { + "OFF": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "Sabbath Mode OFF" + }, + "ON": { + "index": 1, + "label": "@CP_ON_EN_W", + "_comment": "Sabbath Mode ON" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "atLeastOneDoorOpen": { + "_comment": "Door Open State(Close or Open) global", + "dataType": "enum", + "default": "CLOSE", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [] + }, + "valueMapping": { + "CLOSE": { + "index": 0, + "label": "", + "_comment": "Door Closed" + }, + "OPEN": { + "index": 1, + "label": "", + "_comment": "Door Open" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "smartSavingMode": { + "_comment": "Smart Saving Setting Status", + "dataType": "enum", + "default": "OFF", + "visibleItem": { + "monitoringIndex": [ + 0, + 3, + 4 + ], + "controlIndex": [] + }, + "valueMapping": { + "OFF": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "Smart Saving OFF" + }, + "NIGHT_ON": { + "index": 1, + "label": "@RE_SMARTSAVING_MODE_NIGHT_W", + "_comment": "Smart Saving Night Mode ON" + }, + "CUSTOM_ON": { + "index": 2, + "label": "@RE_SMARTSAVING_MODE_CUSTOM_W", + "_comment": "Smart Saving Custom Mode ON" + }, + "SMARTGRID_DR_ON": { + "index": 3, + "label": "@RE_TERM_DEMAND_RESPONSE_FUNCTIONALITY_W", + "_comment": "Smart Grid Demand Response Mode ON" + }, + "SMARTGRID_DD_ON": { + "index": 4, + "label": "@RE_TERM_DELAY_DEFROST_CAPABILITY_W", + "_comment": "Smart Grid Delay Defrost Mode ON" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "smartSavingRun": { + "_comment": "Smart Saving Running Status", + "dataType": "enum", + "default": "STOP", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [] + }, + "valueMapping": { + "STOP": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "Smart Saving Stop (Smart Grid)" + }, + "RUN": { + "index": 1, + "label": "@CP_ON_EN_W", + "_comment": "Smart Saving Running (Smart Grid)" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "activeSaving": { + "_comment": "Active Saving Status", + "dataType": "enum", + "default": "OFF", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [] + }, + "valueMapping": { + "OFF": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "Active Saving OFF" + }, + "ON": { + "index": 1, + "label": "@CP_ON_EN_W", + "_comment": "Active Saving ON" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "ecoFriendly": { + "_comment": "Eco Friendly Status", + "dataType": "enum", + "default": "OFF", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [ + 0, + 1 + ] + }, + "valueMapping": { + "OFF": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "Eco Friendly OFF" + }, + "ON": { + "index": 1, + "label": "@CP_ON_EN_W", + "_comment": "Eco Friendly ON" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "fridgeTemp_C": { + "dataType": "enum", + "default": "1", + "_comment": "Temperature Unit :℉ or ℃ ", + "visibleItem": { + "monitoringIndex": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ], + "controlIndex": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7 + ] + }, + "valueMapping": { + "1": { + "index": 1, + "label": "7", + "_comment": "" + }, + "2": { + "index": 2, + "label": "6", + "_comment": "" + }, + "3": { + "index": 3, + "label": "5", + "_comment": "" + }, + "4": { + "index": 4, + "label": "4", + "_comment": "" + }, + "5": { + "index": 5, + "label": "3", + "_comment": "" + }, + "6": { + "index": 6, + "label": "2", + "_comment": "" + }, + "7": { + "index": 7, + "label": "1", + "_comment": "" + }, + "255": { + "index": 255, + "label": "IGNORE", + "_comment": "" + } + } + }, + "fridgeTemp_F": { + "dataType": "enum", + "default": "1", + "_comment": "Temperature Unit :℉ or ℃ ", + "visibleItem": { + "monitoringIndex": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14 + ], + "controlIndex": [] + }, + "valueMapping": { + "1": { + "index": 1, + "label": "46", + "_comment": "" + }, + "2": { + "index": 2, + "label": "45", + "_comment": "" + }, + "3": { + "index": 3, + "label": "44", + "_comment": "" + }, + "4": { + "index": 4, + "label": "43", + "_comment": "" + }, + "5": { + "index": 5, + "label": "42", + "_comment": "" + }, + "6": { + "index": 6, + "label": "41", + "_comment": "" + }, + "7": { + "index": 7, + "label": "40", + "_comment": "" + }, + "8": { + "index": 8, + "label": "39", + "_comment": "" + }, + "9": { + "index": 9, + "label": "38", + "_comment": "" + }, + "10": { + "index": 10, + "label": "37", + "_comment": "" + }, + "11": { + "index": 11, + "label": "36", + "_comment": "" + }, + "12": { + "index": 12, + "label": "35", + "_comment": "" + }, + "13": { + "index": 13, + "label": "34", + "_comment": "" + }, + "14": { + "index": 14, + "label": "33", + "_comment": "" + }, + "255": { + "index": 255, + "label": "IGNORE", + "_comment": "" + } + } + }, + "freezerTemp_C": { + "dataType": "enum", + "default": "1", + "_comment": "Temperature Unit :℉ or ℃ ", + "visibleItem": { + "monitoringIndex": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ], + "controlIndex": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9 + ] + }, + "valueMapping": { + "1": { + "index": 1, + "label": "-15", + "_comment": "" + }, + "2": { + "index": 2, + "label": "-16", + "_comment": "" + }, + "3": { + "index": 3, + "label": "-17", + "_comment": "" + }, + "4": { + "index": 4, + "label": "-18", + "_comment": "" + }, + "5": { + "index": 5, + "label": "-19", + "_comment": "" + }, + "6": { + "index": 6, + "label": "-20", + "_comment": "" + }, + "7": { + "index": 7, + "label": "-21", + "_comment": "" + }, + "8": { + "index": 8, + "label": "-22", + "_comment": "" + }, + "9": { + "index": 9, + "label": "-23", + "_comment": "" + }, + "255": { + "index": 255, + "label": "IGNORE", + "_comment": "" + } + } + }, + "freezerTemp_F": { + "dataType": "enum", + "default": "1", + "_comment": "Temperature Unit :℉ or ℃ ", + "visibleItem": { + "monitoringIndex": [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15 + ], + "controlIndex": [] + }, + "valueMapping": { + "0": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "" + }, + "1": { + "index": 1, + "label": "7", + "_comment": "" + }, + "2": { + "index": 2, + "label": "6", + "_comment": "" + }, + "3": { + "index": 3, + "label": "5", + "_comment": "" + }, + "4": { + "index": 4, + "label": "4", + "_comment": "" + }, + "5": { + "index": 5, + "label": "3", + "_comment": "" + }, + "6": { + "index": 6, + "label": "2", + "_comment": "" + }, + "7": { + "index": 7, + "label": "1", + "_comment": "" + }, + "8": { + "index": 8, + "label": "0", + "_comment": "" + }, + "9": { + "index": 9, + "label": "-1", + "_comment": "" + }, + "10": { + "index": 10, + "label": "-2", + "_comment": "" + }, + "11": { + "index": 11, + "label": "-3", + "_comment": "" + }, + "12": { + "index": 12, + "label": "-7", + "_comment": "" + }, + "13": { + "index": 13, + "label": "-12", + "_comment": "" + }, + "14": { + "index": 14, + "label": "-15", + "_comment": "" + }, + "15": { + "index": 15, + "label": "-17", + "_comment": "" + }, + "255": { + "index": 255, + "label": "IGNORE", + "_comment": "" + } + } + }, + "convertibleTemp_C": { + "dataType": "enum", + "default": "1", + "_comment": "Temperature Unit :℉ or ℃ ", + "visibleItem": { + "monitoringIndex": [ + 0, + 1, + 2, + 3, + 5, + 7, + 9, + 11, + 12, + 13 + ], + "controlIndex": [] + }, + "valueMapping": { + "0": { + "index": 0, + "label": "-13", + "_comment": "" + }, + "1": { + "index": 1, + "label": "-13", + "_comment": "" + }, + "2": { + "index": 2, + "label": "-14", + "_comment": "" + }, + "3": { + "index": 3, + "label": "-15", + "_comment": "" + }, + "5": { + "index": 5, + "label": "-16", + "_comment": "" + }, + "7": { + "index": 7, + "label": "-17", + "_comment": "" + }, + "9": { + "index": 9, + "label": "-18", + "_comment": "" + }, + "11": { + "index": 11, + "label": "-19", + "_comment": "" + }, + "12": { + "index": 12, + "label": "-20", + "_comment": "" + }, + "13": { + "index": 13, + "label": "-21", + "_comment": "" + }, + "255": { + "index": 255, + "label": "IGNORE", + "_comment": "" + } + } + }, + "convertibleTemp_F": { + "dataType": "enum", + "default": "1", + "_comment": "Temperature Unit :℉ or ℃ ", + "visibleItem": { + "monitoringIndex": [ + 0, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13 + ], + "controlIndex": [] + }, + "valueMapping": { + "0": { + "index": 0, + "label": "8", + "_comment": "" + }, + "1": { + "index": 1, + "label": "8", + "_comment": "" + }, + "2": { + "index": 2, + "label": "6", + "_comment": "" + }, + "3": { + "index": 3, + "label": "5", + "_comment": "" + }, + "4": { + "index": 4, + "label": "4", + "_comment": "" + }, + "5": { + "index": 5, + "label": "3", + "_comment": "" + }, + "6": { + "index": 6, + "label": "2", + "_comment": "" + }, + "7": { + "index": 7, + "label": "1", + "_comment": "" + }, + "8": { + "index": 8, + "label": "0", + "_comment": "" + }, + "9": { + "index": 9, + "label": "-1", + "_comment": "" + }, + "10": { + "index": 10, + "label": "-2", + "_comment": "" + }, + "11": { + "index": 11, + "label": "-3", + "_comment": "" + }, + "12": { + "index": 12, + "label": "-4", + "_comment": "" + }, + "13": { + "index": 13, + "label": "-6", + "_comment": "" + }, + "255": { + "index": 255, + "label": "IGNORE", + "_comment": "" + } + } + }, + "smartSavingModeCustomOpt": { + "dataType": "string" + }, + "smartCareV2": { + "_comment": "Smart CareV2 State (ON, OFF)", + "dataType": "enum", + "default": "OFF", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [ + 0, + 1 + ] + }, + "valueMapping": { + "OFF": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "Smart CareV2 OFF" + }, + "ON": { + "index": 1, + "label": "@CP_ON_EN_W", + "_comment": "Smart CareV2 ON" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + }, + "expressFridge": { + "_comment": "Express Fridge Status", + "dataType": "enum", + "default": "OFF", + "visibleItem": { + "monitoringIndex": [ + 0, + 1 + ], + "controlIndex": [ + 0, + 1 + ] + }, + "valueMapping": { + "OFF": { + "index": 0, + "label": "@CP_OFF_EN_W", + "_comment": "Express Fridge OFF" + }, + "ON": { + "index": 1, + "label": "@CP_ON_EN_W", + "_comment": "Express Fridge ON" + }, + "IGNORE": { + "index": 255, + "label": "", + "_comment": "Please ignore me" + } + } + } + }, + "ControlWifi": { + "basicCtrl": { + "command": "Set", + "data": { + "refState": { + "fridgeTemp": "{{fridgeTemp}}", + "fridgeDoorOpen": "{{fridgeDoorOpen}}", + "freezerTemp": "{{freezerTemp}}", + "freezerDoorOpen": "{{freezerDoorOpen}}", + "convertibleTemp": "{{convertibleTemp}}", + "convertibleDoorOpen": "{{convertibleDoorOpen}}", + "didDoorOpen": "{{didDoorOpen}}", + "smartSavingMode": "{{smartSavingMode}}", + "smartSavingRun": "{{smartSavingRun}}", + "activeSaving": "{{activeSaving}}", + "ecoFriendly": "{{ecoFriendly}}", + "expressMode": "{{expressMode}}", + "tempUnit": "{{tempUnit}}", + "freshAirFilter": "{{freshAirFilter}}", + "waterFilter": "{{waterFilter}}", + "displayLock": "{{displayLock}}", + "sabbathMode": "{{sabbathMode}}", + "atLeastOneDoorOpen": "{{atLeastOneDoorOpen}}", + "expressFridge": "{{expressFridge}}" + } + } + }, + "getActiveSavingScheduleCtrl": { + "command": "Get", + "data": {} + }, + "getSmartFreshStorageScheduleCtrl": { + "command": "Get", + "data": {} + }, + "getActiveIcePlusScheduleCtrl": { + "command": "Get", + "data": {} + } + }, + "Push": [{ + "category": "PUSH_REF_STATE", + "label": "@RE_SETTING_PUSH_PRODUCT_STATE_W", + "groupCode": "10101" + } + ], + "SmartMode": {} +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/thinq-washer-v2-cap.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/thinq-washer-v2-cap.json new file mode 100644 index 0000000000000..044f72c1c89a5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/thinq-washer-v2-cap.json @@ -0,0 +1,3235 @@ +{ + "Info": { + "productType": "WM", + "country": "WW", + "modelType": "FL", + "MP Project": "Vivace", + "ProjectName": "Vivace WO VC3 550 V700 1600 rpm ", + "modelName": "F_V8_Y___W.B_2QEUK", + "networkType": "WIFI", + "version": "0.8" + }, + "Module": { + "WPM": { + "GWM_CEN01_Main": "201", + "GWM_CRS01_Main": "201", + "GWM_CRS02_CourseList": "201", + "GWM_CRS03_CourseDetail": "201", + "GWM_WCH01_Main": "201", + "GWM_WCH01_UserGuide2": "201", + "GWM_ENM01_Main": "201", + "GCM_SDS01_SdsMain": "001", + "GWM_SET01_Main": "201", + "GWM_SET02_PushList": "201", + "GWM_FOT01_Main": "201" + }, + "Menu": [ + "GWM_CRS01_Main", + "GWM_WCH01_Main", + "GWM_ENM01_Main", + "GCM_SDS01_SdsMain", + "GWM_SET01_Main" + ] + }, + "Config": { + "downloadPanelLabel": "@WM_TERM_DOWNLOAD_CYCLE_EN_W", + "remoteStartLabel": "@WM_TITAN2_OPTION_REMOTE_START_W", + "maxDownloadCourseNum": 1, + "defaultCourse": "COTTON", + "downloadCourseAPId": "RINSESPIN", + "defaultSmartCourse": "RINSESPIN", + "tubCleanCourseId": "TUB_CLEAN", + "standbyEnable": true, + "fota": true, + "powerOffDownload": true, + "expectedStartTime": false, + "isAIDDAvailable": true, + "SmartCourseCategory": [ + { + "label": "@WM_COURSE_CATEGORY_HOME_FAMILY_W", + "courseIdList": [ + "BABYCARE", + "HYGIENE", + "SMALLLOAD", + "SKINCARE", + "RINSESPIN", + "KIDSWEAR", + "SCHOOLUNIFORM", + "RAINYSEASON", + "SINGLEGARMENT", + "NOISEMINIMIZE", + "MINIMIZEWRINKLES", + "LIGHTLYSOILEDITEMS", + "MINIMIZEDETERGENT", + "SLEEVEHEMSANDCOLLARS", + "JUICEANDFOODSTAINS", + "QUICKTUBCLEAN", + "DRAIN", + "SPIN" + ] + }, + { + "label": "@WM_COURSE_CATEGORY_SPORTS_LEISURE_W", + "courseIdList": [ + "SWIMMINGWEAR", + "GYMCLOTHES", + "SWEATSTAIN" + ] + }, + { + "label": "@WM_COURSE_CATEGORY_FABRIC_CARE_W", + "courseIdList": [ + "LINGERIE", + "COLDWASH", + "JEANS", + "BLANKET", + "COLORPROTECTION" + ] + } + ], + "smartCourseType": "smartCourseFL24inchBaseTitan", + "courseType": "courseFL24inchBaseTitan", + "downloadedCourseType": "downloadedCourseFL24inchBaseTitan" + }, + "MonitoringValue": { + "state": { + "dataType": "enum", + "label": null, + "valueMapping": { + "POWEROFF": { + "index": 0, + "label": "@WM_STATE_POWER_OFF_W" + }, + "INITIAL": { + "index": 1, + "label": "@WM_STATE_INITIAL_W" + }, + "PAUSE": { + "index": 2, + "label": "@WM_STATE_PAUSE_W" + }, + "RESERVED": { + "index": 3, + "label": "@WM_STATE_RESERVE_W" + }, + "DETECTING": { + "index": 4, + "label": "@WM_STATE_DETECTING_W" + }, + "RUNNING": { + "index": 6, + "label": "@WM_STATE_RUNNING_W" + }, + "RINSING": { + "index": 7, + "label": "@WM_STATE_RINSING_W" + }, + "SPINNING": { + "index": 8, + "label": "@WM_STATE_SPINNING_W" + }, + "DRYING": { + "index": 9, + "label": "@WM_STATE_DRYING_W" + }, + "END": { + "index": 10, + "label": "@WM_STATE_END_W" + }, + "COOLDOWN": { + "index": 11, + "label": "@WM_STATE_COOLDOWN_W" + }, + "RINSEHOLD": { + "index": 12, + "label": "@WM_STATE_RINSEHOLD_W" + }, + "WASH_REFRESHING": { + "index": 14, + "label": "@WM_STATE_WASH_REFRESHING_W" + }, + "STEAMSOFTENING": { + "index": 15, + "label": "@WM_STATE_STEAMSOFTENING_W" + }, + "DEMO": { + "index": 16, + "label": "@WM_STATE_DEMO_W" + }, + "ERROR": { + "index": 18, + "label": "@WM_STATE_ERROR_W" + } + } + }, + "preState": { + "dataType": "enum", + "label": null, + "valueMapping": { + "POWEROFF": { + "index": 0, + "label": "@WM_STATE_POWER_OFF_W" + }, + "INITIAL": { + "index": 1, + "label": "@WM_STATE_INITIAL_W" + }, + "PAUSE": { + "index": 2, + "label": "@WM_STATE_PAUSE_W" + }, + "RESERVED": { + "index": 3, + "label": "@WM_STATE_RESERVE_W" + }, + "DETECTING": { + "index": 4, + "label": "@WM_STATE_DETECTING_W" + }, + "RUNNING": { + "index": 6, + "label": "@WM_STATE_RUNNING_W" + }, + "RINSING": { + "index": 7, + "label": "@WM_STATE_RINSING_W" + }, + "SPINNING": { + "index": 8, + "label": "@WM_STATE_SPINNING_W" + }, + "DRYING": { + "index": 9, + "label": "@WM_STATE_DRYING_W" + }, + "END": { + "index": 10, + "label": "@WM_STATE_END_W" + }, + "COOLDOWN": { + "index": 11, + "label": "@WM_STATE_COOLDOWN_W" + }, + "RINSEHOLD": { + "index": 12, + "label": "@WM_STATE_RINSEHOLD_W" + }, + "WASH_REFRESHING": { + "index": 14, + "label": "@WM_STATE_WASH_REFRESHING_W" + }, + "STEAMSOFTENING": { + "index": 15, + "label": "@WM_STATE_STEAMSOFTENING_W" + }, + "DEMO": { + "index": 16, + "label": "@WM_STATE_DEMO_W" + }, + "ERROR": { + "index": 18, + "label": "@WM_STATE_ERROR_W" + } + } + }, + "remoteStart": { + "dataType": "enum", + "label": "@WM_OPTION_REMOTE_START_W", + "valueMapping": { + "REMOTE_START_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "REMOTE_START_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "initialBit": { + "dataType": "enum", + "label": null, + "valueMapping": { + "INITIAL_BIT_OFF": { + "index": 0, + "label": "INITIAL_BIT_OFF" + }, + "INITIAL_BIT_ON": { + "index": 1, + "label": "INITIAL_BIT_ON" + } + } + }, + "AIDDLed": { + "dataType": "enum", + "valueMapping": { + "AIDDLed_OFF": { + "index": 0, + "label": "AIDDLed_OFF" + }, + "AIDDLed_ON": { + "index": 1, + "label": "AIDDLed_ON" + } + } + }, + "childLock": { + "dataType": "enum", + "label": "@WM_OPTION_CHILDLOCK_W", + "valueMapping": { + "CHILDLOCK_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "CHILDLOCK_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "TCLCount": { + "dataType": "range", + "label": null, + "valueMapping": { + "min": 0, + "max": 60 + } + }, + "reserveTimeHour": { + "dataType": "range", + "label": "@WM_TITAN2_OPTION_DELAY_END_W", + "valueMapping": { + "min": 3, + "max": 19 + } + }, + "reserveTimeMinute": { + "dataType": "range", + "label": null, + "valueMapping": { + "min": 0, + "max": 59 + } + }, + "remainTimeHour": { + "dataType": "range", + "label": null, + "valueMapping": { + "min": 0, + "max": 30 + } + }, + "remainTimeMinute": { + "dataType": "range", + "label": null, + "valueMapping": { + "min": 0, + "max": 59 + } + }, + "initialTimeHour": { + "dataType": "range", + "label": null, + "valueMapping": { + "min": 0, + "max": 30 + } + }, + "initialTimeMinute": { + "dataType": "range", + "label": null, + "valueMapping": { + "min": 0, + "max": 59 + } + }, + "soilWash": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_WASH_W", + "valueMapping": { + "NO_SOILWASH": { + "index": 0, + "label": "@WM_TERM_NO_SELECT_W" + }, + "SOILWASH_TURBO_WASH": { + "index": 1, + "label": "@WM_TITAN2_OPTION_TURBO_WASH_W" + }, + "SOILWASH_TIMESAVE": { + "index": 2, + "label": "@WM_TITAN2_OPTION_WASH_TIMESAVE_W" + }, + "SOILWASH_NORMAL": { + "index": 3, + "label": "@WM_TITAN2_OPTION_WASH_NORMAL_W" + }, + "SOILWASH_INTENSIVE": { + "index": 4, + "label": "@WM_TITAN2_OPTION_WASH_INTENSIVE_W" + } + } + }, + "spin": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_SPIN_SPEED_W", + "valueMapping": { + "NOT_SELECTED": { + "index": 0, + "label": "@WM_TERM_NO_SELECT_W" + }, + "NO_SPIN": { + "index": 1, + "label": "@WM_TITAN2_OPTION_SPIN_NO_SPIN_W" + }, + "SPIN_400": { + "index": 2, + "label": "@WM_TITAN2_OPTION_SPIN_400_W" + }, + "SPIN_600": { + "index": 3, + "label": "@WM_TITAN2_OPTION_SPIN_600_W" + }, + "SPIN_700": { + "index": 4, + "label": "@WM_TITAN2_OPTION_SPIN_700_W" + }, + "SPIN_800": { + "index": 5, + "label": "@WM_TITAN2_OPTION_SPIN_800_W" + }, + "SPIN_900": { + "index": 6, + "label": "@WM_TITAN2_OPTION_SPIN_900_W" + }, + "SPIN_1000": { + "index": 7, + "label": "@WM_TITAN2_OPTION_SPIN_1000_W" + }, + "SPIN_1100": { + "index": 8, + "label": "@WM_TITAN2_OPTION_SPIN_1100_W" + }, + "SPIN_1200": { + "index": 9, + "label": "@WM_TITAN2_OPTION_SPIN_1200_W" + }, + "SPIN_1400": { + "index": 10, + "label": "@WM_TITAN2_OPTION_SPIN_1400_W" + }, + "SPIN_1600": { + "index": 11, + "label": "@WM_TITAN2_OPTION_SPIN_1600_W" + }, + "SPIN_Max": { + "index": 255, + "label": "@WM_TITAN2_OPTION_SPIN_MAX_W" + } + } + }, + "temp": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_TEMP_W", + "valueMapping": { + "NO_TEMP": { + "index": 0, + "label": "@WM_TERM_NO_SELECT_W" + }, + "TEMP_COLD": { + "index": 1, + "label": "@WM_TITAN2_OPTION_TEMP_COLD_W" + }, + "TEMP_20": { + "index": 2, + "label": "@WM_TITAN2_OPTION_TEMP_20_W" + }, + "TEMP_30": { + "index": 3, + "label": "@WM_TITAN2_OPTION_TEMP_30_W" + }, + "TEMP_40": { + "index": 4, + "label": "@WM_TITAN2_OPTION_TEMP_40_W" + }, + "TEMP_50": { + "index": 5, + "label": "@WM_TITAN2_OPTION_TEMP_50_W" + }, + "TEMP_60": { + "index": 6, + "label": "@WM_TITAN2_OPTION_TEMP_60_W" + }, + "TEMP_95": { + "index": 7, + "label": "@WM_TITAN2_OPTION_TEMP_95_W" + } + } + }, + "rinse": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_RINSE_W", + "valueMapping": { + "NO_RINSE": { + "index": 0, + "label": "@WM_TERM_NO_SELECT_W" + }, + "RINSE_NORMAL": { + "index": 1, + "label": "@WM_TITAN2_OPTION_RINSE_NORMAL_W" + }, + "RINSE_PLUS": { + "index": 2, + "label": "@WM_TITAN2_OPTION_RINSE_RINSE+_W" + }, + "RINSE_PLUSPLUS": { + "index": 3, + "label": "@WM_TITAN2_OPTION_RINSE_RINSE++_W" + }, + "RINSE_NORMAL_HOLD": { + "index": 4, + "label": "@WM_TITAN2_OPTION_RINSE_NORMALHOLD_W" + }, + "RINSE_PLUS_HOLD": { + "index": 5, + "label": "@WM_TITAN2_OPTION_RINSE_RINSE+HOLD_W" + } + } + }, + "turboWash": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_TURBO_WASH_W", + "valueMapping": { + "TURBOWASH_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "TURBOWASH_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "steam": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_STEAM_W", + "valueMapping": { + "STEAM_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "STEAM_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "preWash": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_PRE_WASH_W", + "valueMapping": { + "PREWASH_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "PREWASH_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "medicRinse": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_MEDIC_RINSE_W", + "valueMapping": { + "MEDICRINSE_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "MEDICRINSE_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "steamSoftener": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_STEAM_SOFTENER_W", + "valueMapping": { + "STEAMSOFTENER_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "STEAMSOFTENER_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "loadItemWasher": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_LOAD_ITEM_W", + "valueMapping": { + "LOADITEM_OFF": { + "index": 0, + "label": "0" + }, + "LOADITEM_1": { + "index": 1, + "label": "1" + }, + "LOADITEM_2": { + "index": 2, + "label": "2" + }, + "LOADITEM_3": { + "index": 3, + "label": "3" + } + } + }, + "standby": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_STANDBY_W", + "valueMapping": { + "STANDBY_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "STANDBY_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "creaseCare": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_CREASE_CARE_W", + "valueMapping": { + "CREASECARE_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "CREASECARE_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "proofing": { + "dataType": "enum", + "label": "@WM_TITAN2_OPTION_PROOFING_W", + "valueMapping": { + "PROOFING_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "PROOFING_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "courseFL24inchBaseTitan": { + "ref": "Course" + }, + "error": { + "dataType": "enum", + "valueMapping": { + "ERROR_NO": { + "_comment": "No Error", + "index": 0, + "label": "ERROR_NOERROR", + "title": "ERROR_NOERROR_TITLE", + "content": "ERROR_NOERROR_CONTENT" + }, + "ERROR_DE2": { + "_comment": "DE2 Error", + "index": 1, + "label": "@WM_WW_FL_ERROR_DE2_W", + "title": "@WM_WW_FL_ERROR_DE2_TITLE_W", + "content": "@WM_WW_FL_ERROR_DE2_CONTENT_S" + }, + "ERROR_DE1": { + "_comment": "DE1 Error", + "index": 2, + "label": "@WM_WW_FL_ERROR_DE1_W", + "title": "@WM_WW_FL_ERROR_DE1_TITLE_W", + "content": "@WM_WW_FL_ERROR_DE1_CONTENT_S" + }, + "ERROR_IE": { + "_comment": "IE Error", + "index": 3, + "label": "@WM_WW_FL_ERROR_IE_W", + "title": "@WM_WW_FL_ERROR_IE_TITLE_W", + "content": "@WM_WW_FL_ERROR_IE_CONTENT_S" + }, + "ERROR_OE": { + "_comment": "OE Error", + "index": 4, + "label": "@WM_WW_FL_ERROR_OE_W", + "title": "@WM_WW_FL_ERROR_OE_TITLE_W", + "content": "@WM_WW_FL_ERROR_OE_CONTENT_S" + }, + "ERROR_UE": { + "_comment": "UE Error", + "index": 5, + "label": "@WM_WW_FL_ERROR_UE_W", + "title": "@WM_WW_FL_ERROR_UE_TITLE_W", + "content": "@WM_WW_FL_ERROR_UE_CONTENT_S" + }, + "ERROR_FE": { + "_comment": "FE Error", + "index": 6, + "label": "@WM_WW_FL_ERROR_FE_W", + "title": "@WM_WW_FL_ERROR_FE_TITLE_W", + "content": "@WM_WW_FL_ERROR_FE_CONTENT_S" + }, + "ERROR_PE": { + "_comment": "PE Error", + "index": 7, + "label": "@WM_WW_FL_ERROR_PE_W", + "title": "@WM_WW_FL_ERROR_PE_TITLE_W", + "content": "@WM_WW_FL_ERROR_PE_CONTENT_S" + }, + "ERROR_TE": { + "_comment": "tE error", + "index": 8, + "label": "@WM_WW_FL_ERROR_TE_W", + "title": "@WM_WW_FL_ERROR_TE_TITLE_W", + "content": "@WM_WW_FL_ERROR_TE_CONTENT_S" + }, + "ERROR_LE": { + "_comment": "LE error", + "index": 9, + "label": "@WM_WW_FL_ERROR_LE_W", + "title": "@WM_WW_FL_ERROR_LE_TITLE_W", + "content": "@WM_WW_FL_ERROR_LE_CONTENT_S" + }, + "ERROR_DHE": { + "_comment": "dHE error", + "index": 11, + "label": "@WM_WW_FL_ERROR_DHE_W", + "title": "@WM_WW_FL_ERROR_DHE_TITLE_W", + "content": "@WM_WW_FL_ERROR_DHE_CONTENT_S" + }, + "ERROR_PF": { + "_comment": "PF error", + "index": 12, + "label": "@WM_WW_FL_ERROR_PF_W", + "title": "@WM_WW_FL_ERROR_PF_TITLE_W", + "content": "@WM_WW_FL_ERROR_PF_CONTENT_S" + }, + "ERROR_FF": { + "_comment": "FF error", + "index": 13, + "label": "@WM_WW_FL_ERROR_FF_W", + "title": "@WM_WW_FL_ERROR_FF_TITLE_W", + "content": "@WM_WW_FL_ERROR_FF_CONTENT_S" + }, + "ERROR_DCE": { + "_comment": "dCE Error", + "index": 14, + "label": "@WM_WW_FL_ERROR_DCE_W", + "title": "@WM_WW_FL_ERROR_DCE_TITLE_W", + "content": "@WM_WW_FL_ERROR_DCE_CONTENT_S" + }, + "ERROR_AE": { + "_comment": "AE Error (AquaLock)", + "index": 15, + "label": "@WM_WW_FL_ERROR_AE_W", + "title": "@WM_WW_FL_ERROR_AE_TITLE_W", + "content": "@WM_WW_FL_ERROR_AE_CONTENT_S" + }, + "ERROR_EE": { + "_comment": "EE error", + "index": 16, + "label": "@WM_WW_FL_ERROR_EE_W", + "title": "@WM_WW_FL_ERROR_EE_TITLE_W", + "content": "@WM_WW_FL_ERROR_EE_CONTENT_S" + }, + "ERROR_PS": { + "_comment": "PS Error", + "index": 17, + "label": "@WM_WW_FL_ERROR_PS_W", + "title": "@WM_WW_FL_ERROR_PS_TITLE_W", + "content": "@WM_WW_FL_ERROR_PS_CONTENT_S" + }, + "ERROR_DE4": { + "_comment": "dE4 Error", + "index": 18, + "label": "@WM_WW_FL_ERROR_DE4_W", + "title": "@WM_WW_FL_ERROR_DE4_TITLE_W", + "content": "@WM_WW_FL_ERROR_DE4_CONTENT_S" + }, + "ERROR_VS": { + "_comment": "vS Error", + "index": 19, + "label": "@WM_WW_FL_ERROR_VS_W", + "title": "@WM_WW_FL_ERROR_VS_TITLE_W", + "content": "@WM_WW_FL_ERROR_VS_CONTENT_S" + } + } + }, + "smartCourseFL24inchBaseTitan": { + "ref": "SmartCourse" + }, + "doorLock": { + "dataType": "enum", + "label": null, + "valueMapping": { + "DOOR_LOCK_OFF": { + "index": 0, + "label": "@CP_OFF_EN_W" + }, + "DOOR_LOCK_ON": { + "index": 1, + "label": "@CP_ON_EN_W" + } + } + }, + "downloadedCourseFL24inchBaseTitan": { + "ref": "SmartCourse" + } + }, + "ControlWifi": { + "WMStart": { + "command": "Set", + "data": { + "washerDryer": { + "course": "temp", + "soilWash": "NO_SOILWASH", + "spin": "NOT_SELECTED", + "temp": "NO_TEMP", + "rinse": "NO_RINSE", + "reserveTimeHour": 0, + "reserveTimeMinute": 0, + "loadItemWasher": "LOADITEM_OFF", + "turboWash": "TURBOWASH_OFF", + "creaseCare": "CREASECARE_OFF", + "steamSoftener": "STEAMSOFTENER_OFF", + "ecoHybrid": "ECOHYBRID_OFF", + "medicRinse": "MEDICRINSE_OFF", + "rinseSpin": "RINSE_SPIN_OFF", + "preWash": "PREWASH_OFF", + "steam": "STEAM_OFF", + "initialBit": "INITIAL_BIT_OFF", + "remoteStart": "REMOTE_START_OFF", + "doorLock": "DOOR_LOCK_OFF", + "childLock": "CHILDLOCK_OFF", + "SmartCourse": "temp" + } + } + }, + "WMDownload": { + "command": "Set", + "data": { + "washerDryer": { + "courseDownloadType": "COURSEDATA", + "courseDownloadDataLength": 21, + "course": "temp", + "soilWash": "NO_SOILWASH", + "spin": "NOT_SELECTED", + "temp": "NO_TEMP", + "rinse": "NO_RINSE", + "reserveTimeHour": 0, + "reserveTimeMinute": 0, + "loadItemWasher": "LOADITEM_OFF", + "turboWash": "TURBOWASH_OFF", + "creaseCare": "CREASECARE_OFF", + "steamSoftener": "STEAMSOFTENER_OFF", + "ecoHybrid": "ECOHYBRID_OFF", + "medicRinse": "MEDICRINSE_OFF", + "rinseSpin": "RINSE_SPIN_OFF", + "preWash": "PREWASH_OFF", + "steam": "STEAM_OFF", + "initialBit": "INITIAL_BIT_OFF", + "remoteStart": "REMOTE_START_OFF", + "doorLock": "DOOR_LOCK_OFF", + "childLock": "CHILDLOCK_OFF", + "SmartCourse": "temp" + } + } + }, + "WMOff": { + "command": "Set", + "data": { + "washerDryer": { + "controlDataType": "POWEROFF", + "controlDataValueLength": 1, + "controlDataValue": 0 + } + } + }, + "WMStop": { + "command": "Set", + "data": { + "washerDryer": { + "controlDataType": "PAUSE", + "controlDataValueLength": 1, + "controlDataValue": 0 + } + } + }, + "WMWakeup": { + "command": "Set", + "data": { + "washerDryer": { + "controlDataType": "WAKEUP", + "controlDataValueLength": 0 + } + } + } + }, + "Course": { + "COTTON": { + "_comment": "Cotton", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_COTTON_W", + "script": "", + "controlEnable": true, + "courseValue": "COTTON", + "imgIndex": 141, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_40", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40", + "TEMP_60", + "TEMP_95" + ] + }, + { + "value": "spin", + "default": "SPIN_Max", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800", + "SPIN_1000", + "SPIN_Max" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "EASYCARE": { + "_comment": "Easy Care", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_EASY_CARE_W", + "script": "", + "controlEnable": true, + "courseValue": "EASYCARE", + "imgIndex": 145, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_40", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40", + "TEMP_60" + ] + }, + { + "value": "spin", + "default": "SPIN_Max", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800", + "SPIN_1000", + "SPIN_Max" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "COTTONPLUS": { + "_comment": "Eco 40-60", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_ECO_40_60_W", + "script": "", + "controlEnable": true, + "courseValue": "COTTONPLUS", + "imgIndex": 148, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_40", + "selectable": [ + "TEMP_40", + "TEMP_60" + ] + }, + { + "value": "spin", + "default": "SPIN_Max", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800", + "SPIN_1000", + "SPIN_Max" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "DUVET": { + "_comment": "Duvet", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_DUVET_W", + "script": "", + "controlEnable": true, + "courseValue": "DUVET", + "imgIndex": 202, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_COLD", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40" + ] + }, + { + "value": "spin", + "default": "SPIN_1000", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800", + "SPIN_1000" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "MIXEDFABRIC": { + "_comment": "Mix", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_MIX_W", + "script": "", + "controlEnable": true, + "courseValue": "MIXEDFABRIC", + "imgIndex": 142, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_40", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40", + "TEMP_60" + ] + }, + { + "value": "spin", + "default": "SPIN_1000", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800", + "SPIN_1000", + "SPIN_Max" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SPORTSWEAR": { + "_comment": "Sports Wear", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_SPORTS_WEAR_W", + "script": "", + "controlEnable": true, + "courseValue": "SPORTSWEAR", + "imgIndex": 51, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_40", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40" + ] + }, + { + "value": "spin", + "default": "SPIN_800", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SILENTWASH": { + "_comment": "Silent Wash", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_SILENT_WASH_W", + "script": "", + "controlEnable": true, + "courseValue": "SILENTWASH", + "imgIndex": 26, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_40", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40", + "TEMP_60" + ] + }, + { + "value": "spin", + "default": "SPIN_800", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800", + "SPIN_1000" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SPEED14": { + "_comment": "Speed 14", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_SPEED_14_W", + "script": "", + "controlEnable": true, + "courseValue": "SPEED14", + "imgIndex": 143, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_20", + "selectable": [ + "TEMP_20", + "TEMP_30", + "TEMP_40" + ] + }, + { + "value": "spin", + "default": "SPIN_400", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800", + "SPIN_1000", + "SPIN_Max" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_ON" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "TUB_CLEAN": { + "_comment": "Tub Clean", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_TUB_CLEAN_W", + "script": "", + "controlEnable": true, + "courseValue": "TUB_CLEAN", + "imgIndex": 152, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL", + "showing": "@WM_TERM_NO_SELECT_W" + }, + { + "value": "temp", + "default": "TEMP_60", + "showing": "@WM_TERM_NO_SELECT_W" + }, + { + "value": "spin", + "default": "NO_SPIN", + "showing": "@WM_TERM_NO_SELECT_W" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "showing": "@WM_TERM_NO_SELECT_W" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + } + ] + }, + "WOOL": { + "_comment": "Wool", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_WOOL_W", + "script": "", + "controlEnable": true, + "courseValue": "WOOL", + "imgIndex": 99, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_30", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40" + ] + }, + { + "value": "spin", + "default": "SPIN_800", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "DELICATE": { + "_comment": "Delicate", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_DELICATE_W", + "script": "", + "controlEnable": true, + "courseValue": "DELICATE", + "imgIndex": 149, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_20", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40" + ] + }, + { + "value": "spin", + "default": "SPIN_800", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "ALLERGYSPASTEAM": { + "_comment": "Allergy SpaSteam", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_ALLERGY_SPASTEAM_W", + "script": "", + "controlEnable": true, + "courseValue": "ALLERGYSPASTEAM", + "imgIndex": 147, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_60" + }, + { + "value": "spin", + "default": "SPIN_Max", + "selectable": [ + "NO_SPIN", + "SPIN_400", + "SPIN_800", + "SPIN_1000", + "SPIN_Max" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_ON" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "TURBO39": { + "_comment": "Turbo Wash 39", + "courseType": "Course", + "name": "@WM_WW_FL_TITAN2_COURSE_TURBO_39_W", + "script": "", + "controlEnable": true, + "courseValue": "TURBO39", + "imgIndex": 212, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "temp", + "default": "TEMP_40", + "selectable": [ + "TEMP_COLD", + "TEMP_20", + "TEMP_30", + "TEMP_40", + "TEMP_60" + ] + }, + { + "value": "spin", + "default": "SPIN_1200", + "selectable": [ + "SPIN_400", + "SPIN_800", + "SPIN_1000", + "SPIN_1200", + "SPIN_Max" + ] + }, + { + "value": "rinse", + "default": "RINSE_NORMAL", + "selectable": [ + "RINSE_NORMAL", + "RINSE_PLUS" + ] + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_ON" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + } + }, + "SmartCourse": { + "BABYCARE": { + "_comment": "baby_care", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "BABYCARE", + "name": "@WM_WW_FL_SMARTCOURSE_BABY_CARE_W", + "script": "@WM_WW_FL_SMARTCOURSE_BABY_CARE_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 52, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_1000" + }, + { + "value": "temp", + "default": "TEMP_60" + }, + { + "value": "rinse", + "default": "RINSE_PLUS" + }, + { + "value": "preWash", + "default": "PREWASH_ON" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_ON", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "HYGIENE": { + "_comment": "Hygiene", + "Course": "ALLERGYCARE", + "courseType": "SmartCourse", + "courseValue": "HYGIENE", + "name": "@WM_WW_FL_SMARTCOURSE_HYGIENE_W", + "script": "@WM_WW_FL_SMARTCOURSE_HYGIENE_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 36, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "TEMP_60" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_ON" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SMALLLOAD": { + "_comment": "Small Load", + "Course": "SPEED14", + "courseType": "SmartCourse", + "courseValue": "SMALLLOAD", + "name": "@WM_WW_FL_SMARTCOURSE_SMALL_LOAD_W", + "script": "@WM_WW_FL_SMARTCOURSE_SMALL_LOAD_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 46, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_400" + }, + { + "value": "temp", + "default": "TEMP_20" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_ON" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "LINGERIE": { + "_comment": "Lingerie", + "Course": "DELICATE", + "courseType": "SmartCourse", + "courseValue": "LINGERIE", + "name": "@WM_WW_FL_SMARTCOURSE_LINGERIE_W", + "script": "@WM_WW_FL_SMARTCOURSE_LINGERIE_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 13, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_800" + }, + { + "value": "temp", + "default": "TEMP_20" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SKINCARE": { + "_comment": "skin_care", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "SKINCARE", + "name": "@WM_WW_FL_SMARTCOURSE_SKIN_CARE_W", + "script": "@WM_WW_FL_SMARTCOURSE_SKIN_CARE_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 16, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "TEMP_40" + }, + { + "value": "rinse", + "default": "RINSE_PLUS" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_ON", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "COLDWASH": { + "_comment": "Cold Wash", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "COLDWASH", + "name": "@WM_WW_FL_SMARTCOURSE_COLD_WASH_W", + "script": "@WM_WW_FL_SMARTCOURSE_COLD_WASH_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 22, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "TEMP_COLD" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "RINSESPIN": { + "_comment": "Rinse + Spin", + "Course": "RINSESPIN", + "courseType": "SmartCourse", + "courseValue": "RINSESPIN", + "name": "@WM_WW_FL_SMARTCOURSE_RINSE_SPIN_W", + "script": "@WM_WW_FL_SMARTCOURSE_RINSE_SPIN_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 60, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "NO_TEMP" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "KIDSWEAR": { + "_comment": "Kids Wear", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "KIDSWEAR", + "name": "@WM_WW_FL_SMARTCOURSE_KIDS_WEAR_W", + "script": "@WM_WW_FL_SMARTCOURSE_KIDS_WEAR_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 53, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_1200" + }, + { + "value": "temp", + "default": "TEMP_60" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_ON" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SCHOOLUNIFORM": { + "_comment": "School Uniform", + "Course": "EASYCARE", + "courseType": "SmartCourse", + "courseValue": "SCHOOLUNIFORM", + "name": "@WM_WW_FL_SMARTCOURSE_SCHOOL_UNIFORM_W", + "script": "@WM_WW_FL_SMARTCOURSE_SCHOOL_UNIFORM_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 130, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_1000" + }, + { + "value": "temp", + "default": "TEMP_40" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SWIMMINGWEAR": { + "_comment": "Swimming Wear", + "Course": "WOOL", + "courseType": "SmartCourse", + "courseValue": "SWIMMINGWEAR", + "name": "@WM_WW_FL_SMARTCOURSE_SWIMMING_WEAR_W", + "script": "@WM_WW_FL_SMARTCOURSE_SWIMMING_WEAR_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 54, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_400" + }, + { + "value": "temp", + "default": "TEMP_20" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "RAINYSEASON": { + "_comment": "Rainy Season", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "RAINYSEASON", + "name": "@WM_WW_FL_SMARTCOURSE_RAINY_SEASON_W", + "script": "@WM_WW_FL_SMARTCOURSE_RAINY_SEASON_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 55, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "TEMP_40" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "GYMCLOTHES": { + "_comment": "Gym Clothes", + "Course": "SPORTSWEAR", + "courseType": "SmartCourse", + "courseValue": "GYMCLOTHES", + "name": "@WM_WW_FL_SMARTCOURSE_GYM_CLOTHES_W", + "script": "@WM_WW_FL_SMARTCOURSE_GYM_CLOTHES_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 56, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_800" + }, + { + "value": "temp", + "default": "TEMP_COLD" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "JEANS": { + "_comment": "Jeans", + "Course": "DELICATE", + "courseType": "SmartCourse", + "courseValue": "JEANS", + "name": "@WM_WW_FL_SMARTCOURSE_JEANS_W", + "script": "@WM_WW_FL_SMARTCOURSE_JEANS_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 76, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "TEMP_20" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "BLANKET": { + "_comment": "Blanket", + "Course": "DUVET", + "courseType": "SmartCourse", + "courseValue": "BLANKET", + "name": "@WM_WW_FL_SMARTCOURSE_BLANKET_W", + "script": "@WM_WW_FL_SMARTCOURSE_BLANKET_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 57, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_400" + }, + { + "value": "temp", + "default": "TEMP_COLD" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SWEATSTAIN": { + "_comment": "Sweat Stain", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "SWEATSTAIN", + "name": "@WM_WW_FL_SMARTCOURSE_SWEAT_STAIN_W", + "script": "@WM_WW_FL_SMARTCOURSE_SWEAT_STAIN_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 58, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "TEMP_40" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SINGLEGARMENT": { + "_comment": "Single Garment", + "Course": "SPEED14", + "courseType": "SmartCourse", + "courseValue": "SINGLEGARMENT", + "name": "@WM_WW_FL_SMARTCOURSE_SINGLE_GARMENT_W", + "script": "@WM_WW_FL_SMARTCOURSE_SINGLE_GARMENT_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 59, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_400" + }, + { + "value": "temp", + "default": "TEMP_20" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_ON" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "COLORPROTECTION": { + "_comment": "Color Protection", + "Course": "DELICATE", + "courseType": "SmartCourse", + "courseValue": "COLORPROTECTION", + "name": "@WM_WW_FL_SMARTCOURSE_COLOR_PROTECTION_W", + "script": "@WM_WW_FL_SMARTCOURSE_COLOR_PROTECTION_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 47, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_800" + }, + { + "value": "temp", + "default": "TEMP_20" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "NOISEMINIMIZE": { + "_comment": "Noise Minimize", + "Course": "SILENTWASH", + "courseType": "SmartCourse", + "courseValue": "NOISEMINIMIZE", + "name": "@WM_WW_FL_SMARTCOURSE_NOISE_MINIMIZE_W", + "script": "@WM_WW_FL_SMARTCOURSE_NOISE_MINIMIZE_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 88, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_1000" + }, + { + "value": "temp", + "default": "TEMP_40" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "MINIMIZEWRINKLES": { + "_comment": "Minimize Wrinkles", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "MINIMIZEWRINKLES", + "name": "@WM_WW_FL_SMARTCOURSE_MINIMIZE_WRINKLES_W", + "script": "@WM_WW_FL_SMARTCOURSE_MINIMIZE_WRINKLES_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 80, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "TEMP_40" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "LIGHTLYSOILEDITEMS": { + "_comment": "Lightly Soiled Items", + "Course": "WOOL", + "courseType": "SmartCourse", + "courseValue": "LIGHTLYSOILEDITEMS", + "name": "@WM_WW_FL_SMARTCOURSE_LIGHTLY_SOILED_ITEMS_W", + "script": "@WM_WW_FL_SMARTCOURSE_LIGHTLY_SOILED_ITEMS_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 43, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_400" + }, + { + "value": "temp", + "default": "TEMP_20" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "MINIMIZEDETERGENT": { + "_comment": "Minimize Detergent Residue", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "MINIMIZEDETERGENT", + "name": "@WM_WW_FL_SMARTCOURSE_MINIMIZE_DETERGENT_RESIDUE_W", + "script": "@WM_WW_FL_SMARTCOURSE_MINIMIZE_DETERGENT_RESIDUE_1_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 30, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "TEMP_40" + }, + { + "value": "rinse", + "default": "RINSE_PLUS" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SLEEVEHEMSANDCOLLARS": { + "_comment": "Sleeve Hems and Collars", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "SLEEVEHEMSANDCOLLARS", + "name": "@WM_WW_FL_SMARTCOURSE_SLEEVE_HEMS_AND_COLLARS_W", + "script": "@WM_WW_FL_SMARTCOURSE_SLEEVE_HEMS_AND_COLLARS_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 37, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_1000" + }, + { + "value": "temp", + "default": "TEMP_60" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_ON" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "JUICEANDFOODSTAINS": { + "_comment": "Juice and Food Stains", + "Course": "COTTON", + "courseType": "SmartCourse", + "courseValue": "JUICEANDFOODSTAINS", + "name": "@WM_WW_FL_SMARTCOURSE_JUICE_AND_FOOD_STAINS_W", + "script": "@WM_WW_FL_SMARTCOURSE_JUICE_AND_FOOD_STAINS_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 108, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_1000" + }, + { + "value": "temp", + "default": "TEMP_40" + }, + { + "value": "rinse", + "default": "RINSE_NORMAL" + }, + { + "value": "preWash", + "default": "PREWASH_ON" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "QUICKTUBCLEAN": { + "_comment": "quick_tub_clean", + "Course": "QUICKTUBCLEAN", + "courseType": "SmartCourse", + "courseValue": "QUICKTUBCLEAN", + "name": "@WM_WW_FL_SMARTCOURSE_QUICK_TUB_CLEAN_W", + "script": "@WM_WW_FL_SMARTCOURSE_QUICK_TUB_CLEAN_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 38, + "function": [ + { + "value": "soilWash", + "default": "SOILWASH_NORMAL" + }, + { + "value": "spin", + "default": "SPIN_400" + }, + { + "value": "temp", + "default": "TEMP_COLD" + }, + { + "value": "rinse", + "default": "NO_RINSE" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + } + ] + }, + "DRAIN": { + "_comment": "Drain", + "Course": "SPINONLY", + "courseType": "SmartCourse", + "courseValue": "DRAIN", + "name": "@WM_WW_FL_SMARTCOURSE_DRAIN_W", + "script": "@WM_WW_FL_SMARTCOURSE_DRAIN_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 217, + "function": [ + { + "value": "soilWash", + "default": "NO_SOILWASH" + }, + { + "value": "spin", + "default": "NO_SPIN" + }, + { + "value": "temp", + "default": "NO_TEMP" + }, + { + "value": "rinse", + "default": "NO_RINSE" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + }, + "SPIN": { + "_comment": "Spin", + "Course": "SPINONLY", + "courseType": "SmartCourse", + "courseValue": "SPIN", + "name": "@WM_WW_FL_SMARTCOURSE_SPIN_W", + "script": "@WM_WW_FL_SMARTCOURSE_SPIN_SCRIPT_S", + "downloadEnable": true, + "controlEnable": true, + "imgIndex": 27, + "function": [ + { + "value": "soilWash", + "default": "NO_SOILWASH" + }, + { + "value": "spin", + "default": "SPIN_Max" + }, + { + "value": "temp", + "default": "NO_TEMP" + }, + { + "value": "rinse", + "default": "NO_RINSE" + }, + { + "value": "preWash", + "default": "PREWASH_OFF" + }, + { + "value": "turboWash", + "default": "TURBOWASH_OFF" + }, + { + "value": "steam", + "default": "STEAM_OFF" + }, + { + "value": "medicRinse", + "default": "MEDICRINSE_OFF", + "visibility": "gone" + }, + { + "value": "loadItemWasher", + "default": "LOADITEM_OFF", + "visibility": "gone" + }, + { + "value": "reserveTimeHour", + "default": 0 + } + ] + } + }, + "Push": [ + { + "category": "PUSH_WM_STATE", + "label": "@CP_ALARM_PRODUCT_STATE_W", + "groupCode": "20101", + "pushList": [ + { + "0000": "PUSH_WM_COMPLETE" + }, + { + "0001": "PUSH_WM_REMOTE_ANOTHER_ID" + }, + { + "0100": "PUSH_WM_ERROR" + }, + { + "0200": "PUSH_WM_REMOTE_START_OFF" + }, + { + "0201": "PUSH_WM_REMOTE_START_ON" + } + ] + } + ], + "EnergyMonitoring": { + "valueMapping": [ + "temp", + "spin", + "soilWash" + ], + "powertable": { + "1": 1, + "2": 2, + "3": 3, + "4": 4, + "5": 5, + "6": 6 + }, + "watertable": null + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/user-info-response-1.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/user-info-response-1.json new file mode 100644 index 0000000000000..f0284803c9ba8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/user-info-response-1.json @@ -0,0 +1,52 @@ +{ + "status":1, + "account":{ + "userID":"%s", + "userNo":"BR2005200239023", + "userIDType":"LGE", + "displayUserID":"faker", + "userIDList":[ + { + "lgeIDList":[ + { + "lgeIDType":"LGE", + "userID":"%s" + } + ] + } + ], + "dateOfBirth":"05-05-1978", + "country":"BR", + "countryName":"Brazil", + "blacklist":"N", + "age":"45", + "isSubscribe":"N", + "changePw":"N", + "toEmailId":"N", + "periodPW":"N", + "lgAccount":"Y", + "isService":"Y", + "userNickName":"faker", + "authUser":"N", + "serviceList":[ + { + "isService":"Y", + "svcName":"LG ThinQ", + "svcCode":"SVC202", + "joinDate":"29-05-2018" + }, + { + "isService":"Y", + "svcName":"LG Developer", + "svcCode":"SVC609", + "joinDate":"29-05-2018" + }, + { + "isService":"Y", + "svcName":"MC OAuth", + "svcCode":"SVC710", + "joinDate":"29-05-2018" + } + ] + } +} \ No newline at end of file diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json new file mode 100644 index 0000000000000..17cb8f393da3c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json @@ -0,0 +1,118 @@ +{ + "resultCode": "0000", + "result": { + "appType":"NUTS", + "modelCountryCode":"WW", + "countryCode":"DK", + "modelName":"F_R7_Y___W.A__QEUK", + "deviceType":201, + "deviceCode":"LA02", + "alias":"Frontbetjent vaskemaskine", + "deviceId":"592bd2a4-d3e7-16e9-a69f-44cb8b2e0c43", + "fwVer":"", + "imageFileName":"home_appliances_img_wmdrum.png", + "ssid":"Kepler", + "softapId":"", + "softapPass":"", + "macAddress":"", + "networkType":"02", + "timezoneCode":"Europe/Copenhagen", + "timezoneCodeAlias":"Europe/Copenhagen", + "utcOffset":1, + "utcOffsetDisplay":"+01:00", + "dstOffset":2, + "dstOffsetDisplay":"+02:00", + "curOffset":1, + "curOffsetDisplay":"+01:00", + "sdsGuide":"{\"deviceCode\":\"LA02\"}", + "newRegYn":"N", + "remoteControlType":"", + "userNo":"DK2202075642801", + "tftYn":"N", + "deviceState":"E", + "snapshot":{ + "washerDryer":{ + "initialBit":"INITIAL_BIT_OFF", + "standby":"STANDBY_OFF", + "courseFL24inchBaseTitan":"MIXEDFABRIC", + "initialTimeMinute":24.0, + "preState":"SPINNING", + "error":"ERROR_NO", + "dryLevel":"NOT_SELECTED", + "creaseCare":"CREASECARE_OFF", + "remainTimeHour":0.0, + "smartCourseFL24inchBaseTitan":"NOT_SELECTED", + "preWash":"PREWASH_OFF", + "steam":"STEAM_OFF", + "state":"SPINNING", + "rinse":"NO_RINSE", + "wrinkleCare":"WRINKLECARE_OFF", + "loadItemWasher":"LOADITEM_OFF", + "temp":"NO_TEMP", + "doorLock":"DOOR_LOCK_ON", + "reserveTimeMinute":0.0, + "AIDDLed":"AIDDLed_OFF", + "TCLCount":33.0, + "downloadedCourseFL24inchBaseTitan":"RINSESPIN", + "medicRinse":"MEDICRINSE_OFF", + "turboWash":"TURBOWASH_OFF", + "ecoHybrid":"ECOHYBRID_OFF", + "remainTimeMinute":11.0, + "reserveTimeHour":0.0, + "steamSoftener":"STEAMSOFTENER_OFF", + "childLock":"CHILDLOCK_OFF", + "remoteStart":"REMOTE_START_ON", + "spin":"SPIN_1400", + "soilWash":"NO_SOILWASH", + "rinseSpin":"RINSE_SPIN_OFF", + "initialTimeHour":1.0 + }, + "mid":8.4022883E7, + "online":true, + "static":{ + "deviceType":"201", + "countryCode":"DK" + }, + "meta":{ + "allDeviceInfoUpdate":false, + "messageId":"TSmRTV6yTUq2obot8_Q9Qg" + }, + "timestamp":1.644358361572E12 + }, + "online":true, + "platformType":"thinq2", + "area":125955, + "regDt":2.0220208013031E13, + "blackboxYn":"Y", + "modelProtocol":"courseFL24inchBaseTitan", + "receipeVersion":0, + "activeSaving":"OFF", + "smartCareV2":"OFF", + "order":0, + "nlpAlias":"none", + "drServiceYn":"N", + "fwInfoList":[ + { + "checksum":"016771B3", + "partNumber":"SAA41059310", + "order":2.0 + }, + { + "checksum":"000075A3", + "partNumber":"SAA41059211", + "order":1.0 + } + ], + "regDtUtc":"20220207233031", + "regIndex":0, + "groupableYn":"N", + "controllableYn":"N", + "combinedProductYn":"N", + "masterYn":"Y", + "controlGuideType":"TYPE4", + "initDevice":false, + "upgradableYn":"N", + "autoFwDownloadYn":"N", + "tclcount":0 + } +} \ No newline at end of file diff --git a/bundles/pom.xml b/bundles/pom.xml index 2e9c12d782a34..fced255d5bc46 100644 --- a/bundles/pom.xml +++ b/bundles/pom.xml @@ -223,6 +223,7 @@ org.openhab.binding.leapmotion org.openhab.binding.lghombot org.openhab.binding.lgtvserial + org.openhab.binding.lgthinq org.openhab.binding.lgwebos org.openhab.binding.lifx org.openhab.binding.linky From b003e848f7eb749cd2e0ea7a4abd44700dd3c26e Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Sun, 25 Jun 2023 10:36:53 -0300 Subject: [PATCH 002/130] [lgthinq][feat] extended information in AC, WM & DR (partial) Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 12 + .../internal/LGThinQBindingConstants.java | 29 +- .../internal/LGThinQHandlerFactory.java | 10 +- ...a => LGThinQStateDescriptionProvider.java} | 6 +- .../discovery/LGThinqDiscoveryService.java | 16 +- .../BaseThingWithExtraInfoHandler.java | 65 +++++ .../handler/LGThinQAbstractDeviceHandler.java | 276 ++++++++++++++++-- .../handler/LGThinQAirConditionerHandler.java | 163 ++++++++--- .../handler/LGThinQBridgeHandler.java | 18 +- .../handler/LGThinQFridgeHandler.java | 32 +- .../handler/LGThinQWasherDryerHandler.java | 104 +++---- .../internal/type/ThingModelTypeUtils.java | 11 +- .../lgservices/LGThinQACApiClientService.java | 5 + .../LGThinQACApiV1ClientServiceImpl.java | 80 +++-- .../LGThinQACApiV2ClientServiceImpl.java | 61 +++- .../LGThinQAbstractApiClientService.java | 42 +-- .../LGThinQAbstractApiV1ClientService.java | 35 +-- .../LGThinQAbstractApiV2ClientService.java | 41 +-- .../lgservices/LGThinQApiClientService.java | 6 +- .../LGThinQDRApiV2ClientServiceImpl.java | 14 +- .../LGThinQFridgeApiV1ClientServiceImpl.java | 8 - .../LGThinQFridgeApiV2ClientServiceImpl.java | 8 - .../LGThinQWMApiV1ClientServiceImpl.java | 23 +- .../LGThinQWMApiV2ClientServiceImpl.java | 13 +- .../model/AbstractCapabilityFactory.java | 3 + .../lgservices/model/CommandDefinition.java | 1 + .../model/DefaultSnapshotBuilder.java | 1 - .../lgthinq/lgservices/model/ResultCodes.java | 9 +- .../model/devices/ac/ACCapability.java | 18 ++ .../devices/ac/ACCapabilityFactoryV1.java | 20 ++ .../devices/ac/ACCapabilityFactoryV2.java | 42 ++- .../model/devices/ac/ExtendedDeviceInfo.java | 91 ++++++ .../fridge/FridgeCapabilityFactoryV1.java | 11 + .../fridge/FridgeCapabilityFactoryV2.java | 11 + .../AbstractWasherDryerCapabilityFactory.java | 8 +- .../washerdryer/WasherDryerCapability.java | 9 + .../WasherDryerCapabilityFactoryV1.java | 9 +- .../WasherDryerCapabilityFactoryV2.java | 7 +- .../washerdryer/WasherDryerSnapshot.java | 2 +- .../OH-INF/thing/air-conditioner.xml | 65 ++++- .../main/resources/OH-INF/thing/channels.xml | 55 ++++ .../main/resources/OH-INF/thing/fridge.xml | 21 +- .../resources/OH-INF/thing/washer-dryer.xml | 35 ++- 43 files changed, 1139 insertions(+), 357 deletions(-) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGThinQDeviceDynStateDescriptionProvider.java => LGThinQStateDescriptionProvider.java} (86%) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index 74f513dfea8c0..bfcd12425a49f 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -63,5 +63,17 @@ It would certainly motivate me to further improve this work or to create new oth [![Buy me a coffee!](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/nemerdaud) +For openHAB 4.0 just enter +mvn clean install -pl :org.openhab.binding.lgthinq +for openHAB 3.4.x enter + +mvn clean install -pl :org.openhab.binding.lgthinq -Dohc.version=3.4.0 -Doh.java.version=11 -Dkaraf.version=4.3.7 +Just be carefull, the clean command deletes the content of the target folder, so you’d better copy files before issuing the second command. +Only culprit, the openHAB 3 Binding version is still named + +org.openhab.binding.lgthinq-4.0.0-SNAPSHOT.jar +So you need to manually change it to + +org.openhab.binding.lgthinq-3.4.5-SNAPSHOT.jar diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 952949c28f2cf..13cb649d5d3cc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -20,8 +20,6 @@ import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.core.OpenHAB; import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.type.ChannelGroupTypeUID; -import org.openhab.core.thing.type.ChannelTypeUID; /** * The {@link LGThinQBindingConstants} class defines common constants, which are @@ -41,16 +39,8 @@ public class LGThinQBindingConstants { String.valueOf(DeviceTypes.AIR_CONDITIONER.deviceTypeId())); public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, "201"); public static final String WM_CHANNEL_REMOTE_START_GRP_ID = "remote-start-grp"; - public static final String WM_CHANNEL_DASHBOARD_GRP_ID = "dashboard"; - public static final ChannelTypeUID WM_RS_RINSE_CHANNEL_TYPE_UUID = new ChannelTypeUID(BINDING_ID, "washer-rinse"); - public static final ChannelTypeUID WM_RS_SPIN_CHANNEL_TYPE_UUID = new ChannelTypeUID(BINDING_ID, "washer-spin"); - public static final ChannelTypeUID WM_RS_TEMP_LEVEL_CHANNEL_TYPE_UUID = new ChannelTypeUID(BINDING_ID, - "washer-temp-level"); - public static final ChannelTypeUID WM_RS_START_STOP_CHANNEL_TYPE_UUID = new ChannelTypeUID(BINDING_ID, - "rs-washer-start-stop"); - - public static final ChannelGroupTypeUID WM_CHANNEL_GROUP_TYPE_REMOTE_START_UID = new ChannelGroupTypeUID(BINDING_ID, - WM_CHANNEL_REMOTE_START_GRP_ID); + public static final String CHANNEL_DASHBOARD_GRP_ID = "dashboard"; + public static final String CHANNEL_EXTENDED_INFO_GRP_ID = "extended-information"; public static final ThingTypeUID THING_TYPE_WASHING_TOWER = new ThingTypeUID(BINDING_ID, "" + DeviceTypes.WASHING_TOWER.deviceTypeId()); public static final ThingTypeUID THING_TYPE_DRYER = new ThingTypeUID(BINDING_ID, @@ -127,8 +117,7 @@ public class LGThinQBindingConstants { static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); public static final int SEARCH_TIME = 20; - // delay between each device's scan for state changes (in seconds) - public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 10; + public static final int DEFAULT_ENERGY_COLLECTOR_POLLING_UPDATE_DELAY = 60; // ====================== FRIDGE DEVICE CONSTANTS ============================= // CHANNEL IDS @@ -146,8 +135,6 @@ public class LGThinQBindingConstants { public static final String TEMP_UNIT_FAHRENHEIT = "FAHRENHEIT"; public static final String TEMP_UNIT_CELSIUS_SYMBOL = "°C"; public static final String TEMP_UNIT_FAHRENHEIT_SYMBOL = "°F"; - public static final String FRIDGE_TEMP_NODE_NAME_V2 = "fridgeTemp"; - public static final String FRIDGE_TEMP_NODE_NAME_V1 = "TempRefrigerator"; public static final String REFRIGERATOR_SNAPSHOT_NODE_V2 = "refState"; // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= @@ -155,6 +142,9 @@ public class LGThinQBindingConstants { public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; public static final String CHANNEL_POWER_ID = "power"; + public static final String CHANNEL_EXTENDED_INFO_COLLECTOR_ID = "extended_info_collector"; + public static final String CHANNEL_CURRENT_POWER_ID = "current_power"; + public static final String CHANNEL_REMAINING_FILTER_ID = "remaining_filter"; public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; public static final String CHANNEL_COOL_JET_ID = "cool_jet"; @@ -196,6 +186,11 @@ public class LGThinQBindingConstants { public static final String CAP_AC_AIR_CLEAN_COMMAND_ON = "@AC_MAIN_AIRCLEAN_ON_W"; public static final String CAP_AC_AIR_CLEAN_COMMAND_OFF = "@AC_MAIN_AIRCLEAN_OFF_W"; + // Extended Info Attribute Constants + public static final String EXTENDED_ATTR_INSTANT_POWER = "InOutInstantPower"; + public static final String EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE = "ChangePeriod"; + public static final String EXTENDED_ATTR_FILTER_USED_TIME = "UseTime"; + // ====================== WASHING MACHINE CONSTANTS ============================= public static final String WM_COURSE_NOT_SELECTED_VALUE = "NOT_SELECTED"; public static final String WM_POWER_OFF_VALUE = "POWEROFF"; @@ -214,6 +209,8 @@ public class LGThinQBindingConstants { public static final String WM_CHANNEL_SPIN_ID = "spin"; public static final String WM_CHANNEL_REMOTE_START_START_STOP = "rs-start-stop"; + + public static final String WM_CHANNEL_REMOTE_COURSE = "rs-course"; public static final String WM_CHANNEL_REMOTE_START_RINSE = "rs-rinse"; public static final String WM_CHANNEL_REMOTE_START_TEMP = "rs-temperature-level"; public static final String WM_CHANNEL_REMOTE_START_SPIN = "rs-spin"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index fd11e2393626c..92841da18bd90 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -47,7 +47,7 @@ public class LGThinQHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(LGThinQHandlerFactory.class); - private final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final LGThinQStateDescriptionProvider stateDescriptionProvider; @Nullable @Reference @@ -68,7 +68,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { protected @Nullable ThingHandler createHandler(Thing thing) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID) || THING_TYPE_HEAT_PUMP.equals(thingTypeUID)) { - return new LGThinQAirConditionerHandler(thing, stateDescriptionProvider); + return new LGThinQAirConditionerHandler(thing, stateDescriptionProvider, + Objects.requireNonNull(itemChannelLinkRegistry)); } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { return new LGThinQBridgeHandler((Bridge) thing); } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID) || THING_TYPE_WASHING_TOWER.equals(thingTypeUID)) { @@ -80,7 +81,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), Objects.requireNonNull(itemChannelLinkRegistry)); } else if (THING_TYPE_FRIDGE.equals(thingTypeUID)) { - return new LGThinQFridgeHandler(thing, stateDescriptionProvider); + return new LGThinQFridgeHandler(thing, stateDescriptionProvider, + Objects.requireNonNull(itemChannelLinkRegistry)); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; @@ -104,7 +106,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } @Activate - public LGThinQHandlerFactory(final @Reference LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + public LGThinQHandlerFactory(final @Reference LGThinQStateDescriptionProvider stateDescriptionProvider) { this.stateDescriptionProvider = stateDescriptionProvider; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java similarity index 86% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java index ed1237dc34c48..6cbf861759ca2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java @@ -27,10 +27,10 @@ * * @author Nemer Daud - Initial contribution */ -@Component(service = { DynamicStateDescriptionProvider.class, LGThinQDeviceDynStateDescriptionProvider.class }) -public class LGThinQDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { +@Component(service = { DynamicStateDescriptionProvider.class, LGThinQStateDescriptionProvider.class }) +public class LGThinQStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { @Activate - public LGThinQDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // + public LGThinQStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { this.eventPublisher = eventPublisher; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index e14fcc8e714bb..573c5bd27cebb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -66,21 +66,15 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d } @Override - public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException, IOException { - return 0; - } - - @Override - protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, - String controlKey, String command, String keyName, String value) throws Exception { + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, String keyName, String value) throws Exception { throw new UnsupportedOperationException("Not to use"); } @Override - protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, - String controlKey, String command, @Nullable String keyName, @Nullable String value, - @Nullable ObjectNode extraNode) throws Exception { + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) + throws Exception { throw new UnsupportedOperationException("Not to use"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java new file mode 100644 index 0000000000000..43f9f1ad4f1ee --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java @@ -0,0 +1,65 @@ +/** + * 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.lgthinq.internal.handler; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.BaseThingHandler; + +/** + * The {@link BaseThingWithExtraInfoHandler} contains method definitions to the Handle be able to work + * with extra info data. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class BaseThingWithExtraInfoHandler extends BaseThingHandler { + /** + * Creates a new instance of this class for the {@link Thing}. + * + * @param thing the thing that should be handled, not null + */ + public BaseThingWithExtraInfoHandler(Thing thing) { + super(thing); + } + + /** + * Handle must implement this method to update device's extra information collected to the respective channels. + * + * @param energyStateAttributes map containing the key and values collected + * @throws LGThinqException if some error occur + */ + protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { + throw new UnsupportedOperationException( + "Method must be implemented in the Handle that supports energy collector. It most likely a bug"); + } + + /** + * Must be implemented with the code to get energy state if the thing supports it. + * + * @return map containing energy state attributes + */ + protected Map collectExtraInfoState() throws LGThinqException { + throw new UnsupportedOperationException( + "Method must be implemented in the Handle that supports energy collector. It most likely a bug"); + } + + /** + * Reset (put in UNDEF) the channels related to extra information. Normally called when the collector stops. + */ + protected void resetExtraInfoChannels() { + }; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index fab2fc7564c1f..081ca76f6fc27 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -15,21 +15,24 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import java.lang.reflect.ParameterizedType; +import java.math.BigDecimal; import java.util.*; import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.*; import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.core.items.Item; +import org.openhab.core.library.types.OnOffType; import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.openhab.core.thing.type.*; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; @@ -43,18 +46,29 @@ */ @NonNullByDefault public abstract class LGThinQAbstractDeviceHandler - extends BaseThingHandler { + extends BaseThingWithExtraInfoHandler { private final Logger logger = LoggerFactory.getLogger(LGThinQAbstractDeviceHandler.class); protected final String lgPlatformType; + @Nullable + private S lastShot; + protected final ItemChannelLinkRegistry itemChannelLinkRegistry; private final Class snapshotClass; @Nullable protected C thinQCapability; private @Nullable Future commandExecutorQueueJob; private final ExecutorService executorService = Executors.newFixedThreadPool(1); private @Nullable ScheduledFuture thingStatePollingJob; + private @Nullable ScheduledFuture extraInfoCollectorPollingJob; private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); + /** Defined in the configurations of the thing. */ + private int pollingPeriodOnSeconds = 10; + private int pollingPeriodOffSeconds = 10; + private int currentPeriodSeconds = 10; + private int pollingExtraInfoPeriodSeconds = 60; + private boolean pollExtraInfoOnPowerOff = false; private Integer fetchMonitorRetries = 0; private boolean monitorV1Began = false; + private boolean isThingReconfigured = false; private String monitorWorkId = ""; protected final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(30); private String bridgeId = ""; @@ -64,20 +78,30 @@ public abstract class LGThinQAbstractDeviceHandler) ((ParameterizedType) getClass().getGenericSuperclass()) .getActualTypeArguments()[1]; + try { + this.lastShot = snapshotClass.getDeclaredConstructor().newInstance(); + } catch (Exception e) { + throw new IllegalArgumentException("Snapshot class can't be instantiated. It most likely a bug", e); + } } private void normalizeConfigurationsAndProperties() { @@ -98,6 +122,23 @@ public AsyncCommandParams(String channelUUID, Command command) { } } + /** + * Returns the simple channel UID name, i.e., without group. + * + * @param uid Full UID name + * @return simple channel UID name, i.e., without group. + */ + protected String getSimpleChannelUID(String uid) { + String simpleChannelUID; + if (uid.indexOf("#") > 0) { + // I have to remove the channelGroup from de channelUID + simpleChannelUID = uid.split("#")[1]; + } else { + simpleChannelUID = uid; + } + return simpleChannelUID; + } + /** * Return empty string if null argument is passed * @@ -144,6 +185,7 @@ protected void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail stat // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if // the bridge is out stopThingStatePolling(); + stopExtraInfoCollectorPolling(); } } @@ -217,12 +259,31 @@ public C getCapabilities() throws LGThinqApiException { return Objects.requireNonNull(thinQCapability, "Unexpected error. Return of capability shouldn't ever be null"); } + /** + * Get the first item value associated to the channel + * + * @param channelUID channel + * @return value of the first item related to this channel. + */ + @Nullable + protected String getItemLinkedValue(ChannelUID channelUID) { + Set items = itemChannelLinkRegistry.getLinkedItems(channelUID); + if (items.size() > 0) { + for (Item i : items) { + return i.getState().toString(); + } + } + return null; + } + protected abstract Logger getLogger(); protected void initializeThing(@Nullable ThingStatus bridgeStatus) { getLogger().debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); String thingId = getThing().getUID().getId(); Bridge bridge = getBridge(); + // setup configurations + loadConfigurations(); if (!thingId.isBlank()) { try { @@ -271,18 +332,85 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { } } // force start state pooling if the device is ONLINE + resetExtraInfoChannels(); startThingStatePolling(); } } + private void loadConfigurations() { + isThingReconfigured = true; + if (getThing().getConfiguration().containsKey("polling-period-poweron-seconds")) { + pollingPeriodOnSeconds = ((BigDecimal) getThing().getConfiguration().get("polling-period-poweron-seconds")) + .intValue(); + } + if (getThing().getConfiguration().containsKey("polling-period-poweroff-seconds")) { + pollingPeriodOffSeconds = ((BigDecimal) getThing().getConfiguration() + .get("polling-period-poweroff-seconds")).intValue(); + } + if (getThing().getConfiguration().containsKey("polling-extra-info-period-seconds")) { + pollingExtraInfoPeriodSeconds = ((BigDecimal) getThing().getConfiguration() + .get("polling-extra-info-period-seconds")).intValue(); + } + if (getThing().getConfiguration().containsKey("poll-extra-info-on-power-off")) { + pollExtraInfoOnPowerOff = (Boolean) getThing().getConfiguration().get("poll-extra-info-on-power-off"); + } + // if the periods are the same, I can define currentPeriod for polling right now. If not, I postpone to the nest + // snapshot update + if (pollingPeriodOffSeconds == pollingPeriodOnSeconds) { + currentPeriodSeconds = pollingPeriodOffSeconds; + } + } + @Override public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { getLogger().debug("bridgeStatusChanged {}", bridgeStatusInfo); - super.bridgeStatusChanged(bridgeStatusInfo); // restart scheduler initializeThing(bridgeStatusInfo.getStatus()); } + /** + * Determine if the Handle for this device supports Energy State Collector + * + * @return always false and must be overridden if the implemented handler supports energy collector + */ + protected boolean isExtraInfoCollectorSupported() { + return false; + } + + /** + * Returns if the energy collector is enabled. The handle that supports energy collection must + * provide a logic that defines if the collector is currently enabled. Normally, it uses a Switch Channel + * to provide a way to the user turn on/off the collector. + * + * @return true if the energyCollector must be enabled. + */ + protected boolean isExtraInfoCollectorEnabled() { + return false; + } + + private class UpdateExtraInfoCollector implements Runnable { + @Override + public void run() { + updateExtraInfoState(); + } + } + + private void updateExtraInfoState() { + if (!isExtraInfoCollectorSupported()) { + logger.error( + "The Energy Collector was started for a Handler that doesn't support it. It most likely a bug."); + return; + } + try { + Map extraInfoCollected = collectExtraInfoState(); + updateExtraInfoStateChannels(extraInfoCollected); + } catch (LGThinqException ex) { + getLogger().error( + "Error getting energy state and update the correlated channels. DeviceName:{}, DeviceId:{} ", + getDeviceAlias(), getDeviceId(), ex); + } + } + private class UpdateThingStateFromLG implements Runnable { @Override public void run() { @@ -295,20 +423,20 @@ protected void updateThingStateFromLG() { @Nullable S shot = getSnapshotDeviceAdapter(getDeviceId(), getCapabilities()); if (shot == null) { - // no data to update. Maybe, the monitor stopped, then it'a going to be restarted next try. + // no data to update. Maybe, the monitor stopped, then it's going to be restarted next try. return; } fetchMonitorRetries = 0; if (!shot.isOnline()) { if (getThing().getStatus() != ThingStatus.OFFLINE) { // only update channels if the device has just gone OFFLINE. - updateDeviceChannels(shot); + updateDeviceChannelsWrapper(shot); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.device-disconnected"); onDeviceDisconnected(); } } else { // do not update channels if the device is offline - updateDeviceChannels(shot); + updateDeviceChannelsWrapper(shot); if (getThing().getStatus() != ThingStatus.ONLINE) updateStatus(ThingStatus.ONLINE); } @@ -334,6 +462,46 @@ protected void updateThingStateFromLG() { } } + private void handlePowerChange(@Nullable DevicePowerState previous, DevicePowerState current) { + // isThingReconfigured is true when configurations has been updated or thing has just initialized + // this will force to analyse polling periods and starts + if (!isThingReconfigured && (pollingPeriodOffSeconds == pollingPeriodOnSeconds || previous == current)) { + // no changes needed + return; + } + // change from OFF to ON / OFF to ON + boolean isEnableToStartCollector = isExtraInfoCollectorEnabled() && isExtraInfoCollectorSupported(); + if (current == DevicePowerState.DV_POWER_ON) { + currentPeriodSeconds = pollingPeriodOnSeconds; + // if extendedInfo collector is enabled, then force do start to prevent previous stop + if (isEnableToStartCollector) { + startExtraInfoCollectorPolling(); + } + } else { + currentPeriodSeconds = pollingPeriodOffSeconds; + // if it's configured to stop extra-info collection on PowerOff, then stop the job + if (!pollExtraInfoOnPowerOff) { + stopExtraInfoCollectorPolling(); + } else if (isEnableToStartCollector) { + startExtraInfoCollectorPolling(); + } + } + // restart thing state polling for the new poolingPeriod configuration + stopThingStatePolling(); + startThingStatePolling(); + } + + private void updateDeviceChannelsWrapper(S snapshot) { + updateDeviceChannels(snapshot); + // handle power changes + handlePowerChange(lastShot == null ? null : lastShot.getPowerStatus(), snapshot.getPowerStatus()); + // after updated successfully, copy snapshot to last snapshot + lastShot = snapshot; + // and finish the cycle of thing reconfiguration (when thing starts or has configurations changed - if it's the + // case) + isThingReconfigured = false; + } + protected abstract void updateDeviceChannels(S snapshot); protected String translateFeatureToItemType(FeatureDataType dataType) { @@ -356,12 +524,36 @@ protected void stopThingStatePolling() { getLogger().debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); thingStatePollingJob.cancel(true); } + thingStatePollingJob = null; + } + + private void stopExtraInfoCollectorPolling() { + if (extraInfoCollectorPollingJob != null && !extraInfoCollectorPollingJob.isDone()) { + getLogger().debug("Stopping Energy Collector for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + extraInfoCollectorPollingJob.cancel(true); + } + resetExtraInfoChannels(); + extraInfoCollectorPollingJob = null; } protected void startThingStatePolling() { if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { - thingStatePollingJob = pollingScheduler.scheduleWithFixedDelay(new UpdateThingStateFromLG(), 10, - DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); + getLogger().debug("Starting LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePollingJob = pollingScheduler.scheduleWithFixedDelay(new UpdateThingStateFromLG(), 5, + currentPeriodSeconds, TimeUnit.SECONDS); + } + } + + /** + * Method responsible for start the Energy Collector Polling. Must be called buy the handles when it's desired. + * Normally, the thing has a Switch Channel that enable/disable the energy collector. By default, the collector is + * disabled. + */ + private void startExtraInfoCollectorPolling() { + if (extraInfoCollectorPollingJob == null || extraInfoCollectorPollingJob.isDone()) { + getLogger().debug("Starting Energy Collector for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + extraInfoCollectorPollingJob = pollingScheduler.scheduleWithFixedDelay(new UpdateExtraInfoCollector(), 10, + pollingExtraInfoPeriodSeconds, TimeUnit.SECONDS); } } @@ -389,6 +581,20 @@ protected String getBridgeId() { abstract protected DeviceTypes getDeviceType(); + private S handleV1OfflineException() { + try { + // As I don't know the current device status, then I reset to default values. + S shot = snapshotClass.getDeclaredConstructor().newInstance(); + shot.setPowerStatus(DevicePowerState.DV_POWER_OFF); + shot.setOnline(false); + return shot; + } catch (Exception ex) { + logger.error("Unexpected Error. The default constructor of this Snapshot wasn't found", ex); + throw new IllegalStateException("Unexpected Error. The default constructor of this Snapshot wasn't found", + ex); + } + } + @Nullable protected S getSnapshotDeviceAdapter(String deviceId, CapabilityDefinition capDef) throws LGThinqApiException, LGThinqApiExhaustionException { @@ -402,16 +608,11 @@ protected S getSnapshotDeviceAdapter(String deviceId, CapabilityDefinition capDe monitorV1Began = true; } } catch (LGThinqDeviceV1OfflineException e) { - stopDeviceV1Monitor(deviceId); try { - S shot = snapshotClass.getDeclaredConstructor().newInstance(); - shot.setOnline(false); - return shot; - } catch (Exception ex) { - getLogger().error("Unexpected error. Can't find default constructor for the Snapshot subclass", ex); - return null; + stopDeviceV1Monitor(deviceId); + } catch (Exception ignored) { } - + return handleV1OfflineException(); } catch (Exception e) { stopDeviceV1Monitor(deviceId); throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); @@ -441,7 +642,10 @@ protected S getSnapshotDeviceAdapter(String deviceId, CapabilityDefinition capDe // Force restart monitoring because of the errors returned (just in case) throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); } finally { - stopDeviceV1Monitor(deviceId); + try { + stopDeviceV1Monitor(deviceId); + } catch (Exception ignored) { + } } throw new LGThinqApiExhaustionException("Exhausted trying to get monitor data for the device:" + deviceId); } @@ -465,6 +669,26 @@ protected Runnable getQueuedCommandExecutor() { try { processCommand(params); + String channelUid = getSimpleChannelUID(params.channelUID); + if (CHANNEL_POWER_ID.equals(channelUid)) { + // if processed command come from POWER channel, then force updateDeviceChannels immediatly + // this is importante to analise if the poolings need to be changed in time. + updateThingStateFromLG(); + } else if (CHANNEL_EXTENDED_INFO_COLLECTOR_ID.equals(channelUid)) { + if (OnOffType.ON.equals(params.command)) { + logger.debug("Turning ON extended information collector"); + if (pollExtraInfoOnPowerOff + || DevicePowerState.DV_POWER_ON.equals(getLastShot().getPowerStatus())) { + startExtraInfoCollectorPolling(); + } + } else if (OnOffType.OFF.equals(params.command)) { + logger.debug("Turning OFF extended information collector"); + stopExtraInfoCollectorPolling(); + } else { + logger.error("Command {} for {} channel is unexpected. It's most likely a bug", params.command, + CHANNEL_EXTENDED_INFO_COLLECTOR_ID); + } + } } catch (LGThinqException e) { getLogger().error("Error executing Command {} to the channel {}. Thing goes offline until retry", params.command, params.channelUID, e); @@ -479,7 +703,15 @@ public void dispose() { if (thingStatePollingJob != null) { thingStatePollingJob.cancel(true); stopThingStatePolling(); + stopExtraInfoCollectorPolling(); stopCommandExecutorQueueJob(); + try { + if (LGAPIVerion.V1_0.equals(getCapabilities().getDeviceVersion())) { + stopDeviceV1Monitor(getDeviceId()); + } + } catch (Exception e) { + logger.warn("Can't stop active monitor. It's can be normally ignored. Cause:{}", e.getMessage()); + } thingStatePollingJob = null; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 85f2dbf264825..441a826d3589f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -17,12 +17,13 @@ import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; -import java.util.concurrent.ScheduledFuture; +import java.util.Map; +import org.apache.commons.lang3.math.NumberUtils; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.LGThinQACApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQACApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.LGThinQACApiV2ClientServiceImpl; @@ -33,17 +34,26 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ExtendedDeviceInfo; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.unit.Units; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.openhab.core.types.Command; import org.openhab.core.types.StateOption; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * The {@link LGThinQAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. @@ -53,30 +63,48 @@ @NonNullByDefault public class LGThinQAirConditionerHandler extends LGThinQAbstractDeviceHandler { + public final ChannelGroupUID channelGroupExtendedInfoUID; + public final ChannelGroupUID channelGroupDashboardUID; + private final ChannelUID powerChannelUID; private final ChannelUID opModeChannelUID; private final ChannelUID fanSpeedChannelUID; + private final ChannelUID targetTempChannelUID; + private final ChannelUID currTempChannelUID; private final ChannelUID jetModeChannelUID; private final ChannelUID airCleanChannelUID; private final ChannelUID autoDryChannelUID; private final ChannelUID energySavingChannelUID; + private final ChannelUID extendedInfoCollectorChannelUID; + private final ChannelUID currentPowerEnergyChannelUID; + private final ChannelUID remainingFilterChannelUID; + private final ObjectMapper mapper = new ObjectMapper(); private final Logger logger = LoggerFactory.getLogger(LGThinQAirConditionerHandler.class); @NonNullByDefault private final LGThinQACApiClientService lgThinqACApiClientService; - private @Nullable ScheduledFuture thingStatePollingJob; - public LGThinQAirConditionerHandler(Thing thing, - LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing, stateDescriptionProvider); + public LGThinQAirConditionerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, + ItemChannelLinkRegistry itemChannelLinkRegistry) { + super(thing, stateDescriptionProvider, itemChannelLinkRegistry); lgThinqACApiClientService = lgPlatformType.equals(PLATFORM_TYPE_V1) ? LGThinQACApiV1ClientServiceImpl.getInstance() : LGThinQACApiV2ClientServiceImpl.getInstance(); - opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); - fanSpeedChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); - jetModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_COOL_JET_ID); - airCleanChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_AIR_CLEAN_ID); - autoDryChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_AUTO_DRY_ID); - energySavingChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_ENERGY_SAVING_ID); + channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); + channelGroupExtendedInfoUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_EXTENDED_INFO_GRP_ID); + + opModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_MOD_OP_ID); + targetTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_TARGET_TEMP_ID); + currTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_CURRENT_TEMP_ID); + fanSpeedChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FAN_SPEED_ID); + jetModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_COOL_JET_ID); + airCleanChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AIR_CLEAN_ID); + autoDryChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AUTO_DRY_ID); + energySavingChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_ENERGY_SAVING_ID); + powerChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_POWER_ID); + extendedInfoCollectorChannelUID = new ChannelUID(channelGroupExtendedInfoUID, + CHANNEL_EXTENDED_INFO_COLLECTOR_ID); + currentPowerEnergyChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_CURRENT_POWER_ID); + remainingFilterChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_REMAINING_FILTER_ID); } @Override @@ -84,38 +112,53 @@ public void initialize() { logger.debug("Initializing Thinq thing."); Bridge bridge = getBridge(); initializeThing((bridge == null) ? null : bridge.getStatus()); + try { + ACCapability cap = getCapabilities(); + if (!isExtraInfoCollectorSupported()) { + ThingBuilder builder = editThing() + .withoutChannels(this.getThing().getChannelsOfGroup(channelGroupExtendedInfoUID.getId())); + updateThing(builder.build()); + } else if (!cap.isEnergyMonitorAvailable()) { + ThingBuilder builder = editThing().withoutChannel(currentPowerEnergyChannelUID); + updateThing(builder.build()); + } else if (!cap.isFilterMonitorAvailable()) { + ThingBuilder builder = editThing().withoutChannel(remainingFilterChannelUID); + updateThing(builder.build()); + } + } catch (LGThinqApiException e) { + logger.warn("Error getting capability of the device:{}", getDeviceId()); + } } @Override protected void updateDeviceChannels(ACCanonicalSnapshot shot) { - - updateState(CHANNEL_POWER_ID, + logger.debug("Calling updateDeviceChannel for device:{}", getDeviceAlias()); + updateState(powerChannelUID, DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF); - updateState(CHANNEL_MOD_OP_ID, new DecimalType(BigDecimal.valueOf(shot.getOperationMode()))); - updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(BigDecimal.valueOf(shot.getAirWindStrength()))); - updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(BigDecimal.valueOf(shot.getCurrentTemperature()))); - updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(BigDecimal.valueOf(shot.getTargetTemperature()))); + updateState(opModeChannelUID, new DecimalType(BigDecimal.valueOf(shot.getOperationMode()))); + updateState(fanSpeedChannelUID, new DecimalType(BigDecimal.valueOf(shot.getAirWindStrength()))); + updateState(currTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getCurrentTemperature()))); + updateState(targetTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getTargetTemperature()))); try { ACCapability acCap = getCapabilities(); if (getThing().getChannel(jetModeChannelUID) != null) { Double commandCoolJetOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); - updateState(CHANNEL_COOL_JET_ID, + updateState(jetModeChannelUID, commandCoolJetOn.equals(shot.getCoolJetMode()) ? OnOffType.ON : OnOffType.OFF); } if (getThing().getChannel(airCleanChannelUID) != null) { Double commandAirCleanOn = Double.valueOf(acCap.getAirCleanModeCommandOn()); - updateState(CHANNEL_AIR_CLEAN_ID, + updateState(airCleanChannelUID, commandAirCleanOn.equals(shot.getAirCleanMode()) ? OnOffType.ON : OnOffType.OFF); } if (getThing().getChannel(energySavingChannelUID) != null) { Double energySavingOn = Double.valueOf(acCap.getEnergySavingModeCommandOn()); - updateState(CHANNEL_ENERGY_SAVING_ID, + updateState(energySavingChannelUID, energySavingOn.equals(shot.getEnergySavingMode()) ? OnOffType.ON : OnOffType.OFF); } if (getThing().getChannel(autoDryChannelUID) != null) { Double autoDryOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); - updateState(CHANNEL_AUTO_DRY_ID, - autoDryOn.equals(shot.getAutoDryMode()) ? OnOffType.ON : OnOffType.OFF); + updateState(autoDryChannelUID, autoDryOn.equals(shot.getAutoDryMode()) ? OnOffType.ON : OnOffType.OFF); } } catch (LGThinqApiException e) { @@ -140,13 +183,13 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { if (getThing().getChannel(energySavingChannelUID) == null && acCap.isEnergySavingAvailable()) { createDynSwitchChannel(CHANNEL_ENERGY_SAVING_ID, energySavingChannelUID); } - if (getThing().getChannel(fanSpeedChannelUID) == null && !acCap.getFanSpeed().isEmpty()) { + if (!acCap.getFanSpeed().isEmpty()) { List options = new ArrayList<>(); acCap.getFanSpeed() .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); stateDescriptionProvider.setStateOptions(fanSpeedChannelUID, options); } - if (isLinked(opModeChannelUID)) { + if (!acCap.getOpMode().isEmpty()) { List options = new ArrayList<>(); acCap.getOpMode().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_OP_MODE.get(v))))); stateDescriptionProvider.setStateOptions(opModeChannelUID, options); @@ -163,13 +206,6 @@ protected Logger getLogger() { return logger; } - protected void stopThingStatePolling() { - if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { - logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); - thingStatePollingJob.cancel(true); - } - } - protected DeviceTypes getDeviceType() { if (THING_TYPE_HEAT_PUMP.equals(getThing().getThingTypeUID())) { return DeviceTypes.HEAT_PUMP; @@ -206,9 +242,14 @@ public void onDeviceDisconnected() { // TODO - HANDLE IT, Think if it's needed } + protected void resetExtraInfoChannels() { + updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + updateState(remainingFilterChannelUID, UnDefType.UNDEF); + } + protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { Command command = params.command; - switch (params.channelUID) { + switch (getSimpleChannelUID(params.channelUID)) { case CHANNEL_MOD_OP_ID: { if (params.command instanceof DecimalType) { lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), @@ -290,9 +331,63 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept ACTargetTmp.statusOf(targetTemp)); break; } + case CHANNEL_EXTENDED_INFO_COLLECTOR_ID: { + break; + } default: { logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); } } } + // =========== Energy Colletor Implementation ============= + + @Override + protected boolean isExtraInfoCollectorSupported() { + try { + return getCapabilities().isEnergyMonitorAvailable() || getCapabilities().isFilterMonitorAvailable(); + } catch (LGThinqApiException e) { + logger.warn("Can't get capabilities of the device: {}", getDeviceId()); + } + return false; + } + + @Override + protected boolean isExtraInfoCollectorEnabled() { + return OnOffType.ON.toString().equals(getItemLinkedValue(extendedInfoCollectorChannelUID)); + } + + @Override + protected Map collectExtraInfoState() throws LGThinqException { + ExtendedDeviceInfo info = lgThinqACApiClientService.getExtendedDeviceInfo(getBridgeId(), getDeviceId()); + return mapper.convertValue(info, new TypeReference<>() { + }); + } + + @Override + protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { + logger.debug("Calling updateExtraInfoStateChannels for device:{}", getDeviceAlias()); + String instantPowerConsumption = (String) energyStateAttributes.get(EXTENDED_ATTR_INSTANT_POWER); + String filterUsed = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_USED_TIME); + String filterTimelife = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE); + if (instantPowerConsumption == null) { + updateState(currentPowerEnergyChannelUID, UnDefType.NULL); + } else if (NumberUtils.isCreatable(instantPowerConsumption)) { + double ip = Double.parseDouble(instantPowerConsumption); + ip = ip / 1000; + updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.KILOWATT_HOUR)); + } else { + updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + } + + if (filterTimelife == null || filterUsed == null) { + updateState(remainingFilterChannelUID, UnDefType.NULL); + } else if (NumberUtils.isCreatable(filterTimelife) && NumberUtils.isCreatable(filterUsed)) { + double used = Double.parseDouble(filterUsed); + double max = Double.parseDouble(filterTimelife); + double perc = (1 - ((double) used / max)) * 100; + updateState(remainingFilterChannelUID, new QuantityType<>(perc, Units.PERCENT)); + } else { + updateState(remainingFilterChannelUID, UnDefType.UNDEF); + } + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index 94a0ad5e9b4d6..ca656cfc106a7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -82,7 +82,7 @@ public LGThinQBridgeHandler(Bridge bridge) { final ReentrantLock pollingLock = new ReentrantLock(); /** - * Abstract Runnable Polling Class to schedule sincronization status of the Bridge Thing Kinds ! + * Abstract Runnable Polling Class to schedule synchronization status of the Bridge Thing Kinds ! */ abstract class PollingRunnable implements Runnable { protected final String bridgeName; @@ -279,28 +279,16 @@ public void initialize() { logger.debug("Initializing LGThinq bridge handler."); lgthinqConfig = getConfigAs(LGThinQBridgeConfiguration.class); lgDevicePollingRunnable.lgthinqConfig = lgthinqConfig; - // generateWasherDryerThingTypes(); if (lgthinqConfig.username.isEmpty() || lgthinqConfig.password.isEmpty() || lgthinqConfig.language.isEmpty() || lgthinqConfig.country.isEmpty()) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/error.mandotory-fields-missing"); } else { - updateStatus(ThingStatus.UNKNOWN); + // updateStatus(ThingStatus.UNKNOWN); startLGThinqDevicePolling(); } } - // private void generateWasherDryerThingTypes() { - // // TODO - i18n labels and descriptions - // // Creating static channels - // List channels = new ArrayList<>(); - // channels.add(new ThinqChannel()) - // ThinqDevice device = new ThinqDevice(THING_TYPE_WASHING_MACHINE.getId(),"Washer Machine", "LG Thinq Washer - // Machine", - // - // ) - // } - @Override public void handleConfigurationUpdate(Map configurationParameters) { logger.debug("Bridge Configuration was updated. Cleaning the token registry file"); @@ -334,7 +322,7 @@ private void startLGThinqDevicePolling() { } // submit instantlly and schedule for the next polling interval. scheduler.submit(lgDevicePollingRunnable); - devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, poolingInterval, poolingInterval, + devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, 2, poolingInterval, TimeUnit.SECONDS); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index 8b6e4246ba51a..a0065b04f2835 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQFridgeApiClientService; @@ -37,8 +37,10 @@ import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.openhab.core.types.Command; import org.openhab.core.types.State; import org.openhab.core.types.StateOption; @@ -55,23 +57,30 @@ */ @NonNullByDefault public class LGThinQFridgeHandler extends LGThinQAbstractDeviceHandler { - + public final ChannelGroupUID channelGroupExtendedInfoUID; + public final ChannelGroupUID channelGroupDashboardUID; private final ChannelUID fridgeTempChannelUID; private final ChannelUID freezerTempChannelUID; + private final ChannelUID doorChannelUID; + private final ChannelUID tempUnitUID; private String tempUnit = TEMP_UNIT_CELSIUS; private final Logger logger = LoggerFactory.getLogger(LGThinQFridgeHandler.class); @NonNullByDefault private final LGThinQFridgeApiClientService lgThinqFridgeApiClientService; private @Nullable ScheduledFuture thingStatePollingJob; - public LGThinQFridgeHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing, stateDescriptionProvider); + public LGThinQFridgeHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, + ItemChannelLinkRegistry itemChannelLinkRegistry) { + super(thing, stateDescriptionProvider, itemChannelLinkRegistry); lgThinqFridgeApiClientService = lgPlatformType.equals(PLATFORM_TYPE_V1) ? LGThinQFridgeApiV1ClientServiceImpl.getInstance() : LGThinQFridgeApiV2ClientServiceImpl.getInstance(); - fridgeTempChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_FRIDGE_TEMP_ID); - freezerTempChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_FREEZER_TEMP_ID); - ChannelUID doorChannelUID = new ChannelUID(getThing().getUID(), FR_CHANNEL_DOOR_ID); + channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); + channelGroupExtendedInfoUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_EXTENDED_INFO_GRP_ID); + fridgeTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FRIDGE_TEMP_ID); + freezerTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FREEZER_TEMP_ID); + doorChannelUID = new ChannelUID(channelGroupDashboardUID, FR_CHANNEL_DOOR_ID); + tempUnitUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_REF_TEMP_UNIT); } @Override @@ -83,11 +92,11 @@ public void initialize() { @Override protected void updateDeviceChannels(FridgeCanonicalSnapshot shot) { - updateState(CHANNEL_FRIDGE_TEMP_ID, new QuantityType(shot.getFridgeStrTemp())); - updateState(CHANNEL_FREEZER_TEMP_ID, new QuantityType(shot.getFreezerStrTemp())); - updateState(FR_CHANNEL_DOOR_ID, parseDoorStatus(shot.getDoorStatus())); + updateState(fridgeTempChannelUID, new QuantityType(shot.getFridgeStrTemp())); + updateState(freezerTempChannelUID, new QuantityType(shot.getFreezerStrTemp())); + updateState(doorChannelUID, parseDoorStatus(shot.getDoorStatus())); - updateState(CHANNEL_REF_TEMP_UNIT, new StringType(shot.getTempUnit())); + updateState(tempUnitUID, new StringType(shot.getTempUnit())); if (!tempUnit.equals(shot.getTempUnit())) { tempUnit = shot.getTempUnit(); try { @@ -181,6 +190,5 @@ public void onDeviceDisconnected() { protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { Command command = params.command; - // TODO - Implement commands } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index ff3c360a05a5a..cc58a37d962ee 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -21,7 +21,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; @@ -32,7 +32,6 @@ import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.*; -import org.openhab.core.items.Item; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; @@ -54,7 +53,7 @@ public class LGThinQWasherDryerHandler extends LGThinQAbstractDeviceHandler { - private final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final LGThinQStateDescriptionProvider stateDescriptionProvider; private final ChannelUID courseChannelUID; private final ChannelUID remoteStartStopChannelUID; private final ChannelUID remainTimeChannelUID; @@ -69,26 +68,23 @@ public class LGThinQWasherDryerHandler private final ChannelUID doorLockChannelUID; private final ChannelUID standByModeChannelUID; private final ChannelUID remoteStartFlagChannelUID; + private final ChannelUID remoteStartCourseChannelUID; public final ChannelGroupUID channelGroupRemoteStartUID; public final ChannelGroupUID channelGroupDashboardUID; private final List remoteStartEnabledChannels = new CopyOnWriteArrayList<>(); - private final ItemChannelLinkRegistry itemChannelLinkRegistry; private final Map> cachedBitKeyDefinitions = new HashMap<>(); - @Nullable - private WasherDryerSnapshot lastShot; private final Logger logger = LoggerFactory.getLogger(LGThinQWasherDryerHandler.class); @NonNullByDefault private final LGThinQWMApiClientService lgThinqWMApiClientService; - public LGThinQWasherDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider, + public LGThinQWasherDryerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, ThinqChannelTypeProvider channelTypeProvider, ThinqChannelGroupTypeProvider channelGroupTypeProvider, ItemChannelLinkRegistry itemChannelLinkRegistry) { - super(thing, stateDescriptionProvider); - this.itemChannelLinkRegistry = itemChannelLinkRegistry; + super(thing, stateDescriptionProvider, itemChannelLinkRegistry); this.thinqChannelGroupProvider = channelGroupTypeProvider; this.thinqChannelProvider = channelTypeProvider; this.stateDescriptionProvider = stateDescriptionProvider; @@ -96,7 +92,7 @@ public LGThinQWasherDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionPr ? LGThinQWMApiV1ClientServiceImpl.getInstance() : LGThinQWMApiV2ClientServiceImpl.getInstance(); channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_GRP_ID); - channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_DASHBOARD_GRP_ID); + channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); courseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_COURSE_ID); dryLevelChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_DRY_LEVEL_ID); stateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STATE_ID); @@ -111,6 +107,7 @@ public LGThinQWasherDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionPr standByModeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STAND_BY_ID); remoteStartFlagChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMOTE_START_ID); remoteStartStopChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_START_START_STOP); + remoteStartCourseChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_COURSE); } @Override @@ -129,6 +126,12 @@ public void initialize() { initializeThing((bridge == null) ? null : bridge.getStatus()); } + private void loadOptionsCourse(WasherDryerCapability cap, ChannelUID courseChannel) { + List optionsCourses = new ArrayList<>(); + cap.getCourses().forEach((k, v) -> optionsCourses.add(new StateOption(k, emptyIfNull(v.getCourseName())))); + stateDescriptionProvider.setStateOptions(courseChannel, optionsCourses); + } + @Override public void updateChannelDynStateDescription() throws LGThinqApiException { WasherDryerCapability wmCap = getCapabilities(); @@ -138,9 +141,7 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_STATE, v)))); stateDescriptionProvider.setStateOptions(stateChannelUID, options); - List optionsCourses = new ArrayList<>(); - wmCap.getCourses().forEach((k, v) -> optionsCourses.add(new StateOption(k, emptyIfNull(v.getCourseName())))); - stateDescriptionProvider.setStateOptions(courseChannelUID, optionsCourses); + loadOptionsCourse(wmCap, courseChannelUID); List optionsTemp = new ArrayList<>(); wmCap.getTemperatureFeat().getValuesMapping() @@ -200,20 +201,9 @@ private ZonedDateTime getZonedDateTime(String minutesAndSeconds) { return ZonedDateTime.of(1970, 1, 1, 0, Integer.parseInt(min), Integer.parseInt(sec), 0, ZoneId.systemDefault()); } - @Nullable - private String getItemLinkedValue(ChannelUID channelUID) { - Set items = itemChannelLinkRegistry.getLinkedItems(channelUID); - if (items.size() > 0) { - for (Item i : items) { - return i.getState().toString(); - } - } - return null; - } - @Override protected void updateDeviceChannels(WasherDryerSnapshot shot) { - lastShot = shot; + WasherDryerSnapshot lastShot = getLastShot(); updateState("dashboard#" + CHANNEL_POWER_ID, (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); updateState(stateChannelUID, new StringType(shot.getState())); @@ -238,12 +228,17 @@ protected void updateDeviceChannels(WasherDryerSnapshot shot) { // === creating channel LaunchRemote dynChannels .add(createDynChannel(WM_CHANNEL_REMOTE_START_START_STOP, remoteStartStopChannelUID, "Switch")); + dynChannels.add(createDynChannel(WM_CHANNEL_REMOTE_COURSE, remoteStartCourseChannelUID, "String")); // Just enabled remote start. Then is Off updateState(remoteStartStopChannelUID, OnOffType.OFF); // === creating selectable channels for the Course (if any) try { WasherDryerCapability cap = getCapabilities(); - CourseDefinition courseDef = cap.getCourses().get(shot.getCourse()); + // TODO - V1 - App will always get the default course, and V2 ? + loadOptionsCourse(cap, remoteStartCourseChannelUID); + updateState(remoteStartCourseChannelUID, new StringType(cap.getDefaultCourseId())); + + CourseDefinition courseDef = cap.getCourses().get(cap.getDefaultCourseId()); if (WM_COURSE_NOT_SELECTED_VALUE.equals(shot.getSmartCourse()) && courseDef != null) { // only create selectable channels if the course is not a smart course. Smart courses have // already predefined @@ -290,9 +285,6 @@ protected void updateDeviceChannels(WasherDryerSnapshot shot) { remoteStartEnabledChannels.addAll(dynChannels); } - if (isLinked(remoteStartStopChannelUID)) { - updateState(WM_CHANNEL_REMOTE_START_START_STOP, new StringType("")); - } } else if (remoteStartEnabledChannels.size() > 0) { ThingBuilder builder = editThing().withoutChannels(remoteStartEnabledChannels); updateThing(builder.build()); @@ -313,8 +305,14 @@ protected DeviceTypes getDeviceType() { } private Map getRemoteStartData() throws LGThinqApiException { - if (lastShot == null) { - return Collections.EMPTY_MAP; + WasherDryerSnapshot lastShot = getLastShot(); + if (lastShot.getRawData().isEmpty()) { + return lastShot.getRawData(); + } + String selectedCourse = getItemLinkedValue(remoteStartCourseChannelUID); + if (selectedCourse == null) { + logger.error("Remote Start Channel must be linked to proceed with remote start."); + return Collections.emptyMap(); } WasherDryerCapability cap = getCapabilities(); Map rawData = lastShot.getRawData(); @@ -322,19 +320,27 @@ private Map getRemoteStartData() throws LGThinqApiException { CommandDefinition cmd = cap.getCommandsDefinition().get(cap.getCommandRemoteStart()); if (cmd == null) { logger.error("Command for Remote Start not found in the Washer descriptor. It's most likely a bug"); - return Collections.EMPTY_MAP; + return Collections.emptyMap(); } Map cmdData = cmd.getData(); + // 1st - copy snapshot data to command cmdData.forEach((k, v) -> { data.put(k, rawData.getOrDefault(k, v)); }); - String course = lastShot.getCourse(); + // 2nd - replace remote start data with selected course values + CourseDefinition selCourseDef = cap.getCourses().get(selectedCourse); + if (selCourseDef != null) { + selCourseDef.getFunctions().forEach(f -> { + data.put(f.getValue(), f.getDefaultValue()); + }); + } String smartCourse = lastShot.getSmartCourse(); - data.put(cap.getDefaultCourseFieldName(), course); + data.put(cap.getDefaultCourseFieldName(), selectedCourse); data.put(cap.getDefaultSmartCourseFeatName(), smartCourse); - CourseType courseType = cap.getCourses().get("NOT_SELECTED".equals(smartCourse) ? course : smartCourse) + CourseType courseType = cap.getCourses().get("NOT_SELECTED".equals(smartCourse) ? selectedCourse : smartCourse) .getCourseType(); data.put("courseType", courseType.getValue()); + // 3rd - replace custom selectable features with channel's ones. for (Channel c : remoteStartEnabledChannels) { String value = Objects.requireNonNullElse(getItemLinkedValue(c.getUID()), ""); String simpleChannelUID = getSimpleChannelUID(c.getUID().getId()); @@ -358,6 +364,7 @@ private Map getRemoteStartData() throws LGThinqApiException { @Override protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { + WasherDryerSnapshot lastShot = getLastShot(); Command command = params.command; String simpleChannelUID; simpleChannelUID = getSimpleChannelUID(params.channelUID); @@ -365,10 +372,10 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa case WM_CHANNEL_REMOTE_START_START_STOP: { if (command instanceof OnOffType) { if (OnOffType.ON.equals(command)) { - if (lastShot != null && !lastShot.isStandBy()) { + if (!lastShot.isStandBy()) { lgThinqWMApiClientService.remoteStart(getBridgeId(), getCapabilities(), getDeviceId(), getRemoteStartData()); - } else if (lastShot != null && lastShot.isStandBy()) { + } else { logger.warn( "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); } @@ -382,12 +389,6 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa } case WM_CHANNEL_STAND_BY_ID: { if (command instanceof OnOffType) { - if (lastShot == null || !lastShot.isStandBy()) { - logger.warn( - "Command {} was sent to StandBy channel, but the state of the WM is unknown or already waked up. Ignoring", - command); - break; - } lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); } else { logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); @@ -400,23 +401,6 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa } } - /** - * Returns the simple channel UID name, i.e., without group. - * - * @param uid Full UID name - * @return simple channel UID name, i.e., without group. - */ - private static String getSimpleChannelUID(String uid) { - String simpleChannelUID; - if (uid.indexOf("#") > 0) { - // I have to remove the channelGroup from de channelUID - simpleChannelUID = uid.split("#")[1]; - } else { - simpleChannelUID = uid; - } - return simpleChannelUID; - } - @Override public void onDeviceAdded(LGDevice device) { // TODO - handle it. Think if it's needed diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java index 04a38f1c32b29..1323349ed74f6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java @@ -22,14 +22,13 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.model.*; import org.openhab.core.config.core.*; import org.openhab.core.library.CoreItemFactory; import org.openhab.core.thing.*; import org.openhab.core.thing.type.*; import org.openhab.core.types.StateDescriptionFragmentBuilder; -import org.osgi.service.component.annotations.Component; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -39,7 +38,7 @@ * * @author Nemer Daud - Initial contribution */ -@Component +// @Component public class ThingModelTypeUtils { private static final Logger logger = LoggerFactory.getLogger(ThingModelTypeUtils.class); @@ -47,7 +46,7 @@ public class ThingModelTypeUtils { private ThinqChannelTypeProvider channelTypeProvider; private ThinqChannelGroupTypeProvider channelGroupTypeProvider; private ThinqConfigDescriptionProvider configDescriptionProvider; - private LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + private LGThinQStateDescriptionProvider stateDescriptionProvider; @Reference public void setThingTypeProvider(ThinqThingTypeProvider thingTypeProvider) { @@ -70,7 +69,7 @@ public void setConfigDescriptionProvider(ThinqConfigDescriptionProvider configDe } @Reference - public void setStateDescriptionProvider(LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + public void setStateDescriptionProvider(LGThinQStateDescriptionProvider stateDescriptionProvider) { this.stateDescriptionProvider = stateDescriptionProvider; } @@ -172,7 +171,7 @@ private ThingType createThingType(ThinqDevice device, List gro .build(); } - private void generateConfigDescription(ThinqDevice device, URI configDescriptionURI) { + public void generateConfigDescription(ThinqDevice device, URI configDescriptionURI) { List params = new ArrayList<>(); List groups = new ArrayList<>(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java index 496ce946cebf2..12ce1cc699821 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java @@ -12,11 +12,13 @@ */ package org.openhab.binding.lgthinq.lgservices; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ExtendedDeviceInfo; /** * The {@link LGThinQACApiClientService} @@ -39,4 +41,7 @@ void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp new void turnAutoDryMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; void turnEnergySavingMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + + ExtendedDeviceInfo getExtendedDeviceInfo(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java index fbd94ca5d2e2c..61ea6dd14bd70 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java @@ -12,7 +12,11 @@ */ package org.openhab.binding.lgthinq.lgservices; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V1_CONTROL_OP; +import static org.openhab.binding.lgthinq.internal.api.LGThinqCanonicalModelUtil.LG_ROOT_TAG_V1; + import java.io.IOException; +import java.util.Base64; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -24,9 +28,12 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ExtendedDeviceInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.JsonNode; + /** * The {@link LGThinQACApiV1ClientServiceImpl} * @@ -72,34 +79,51 @@ public ACCanonicalSnapshot getDeviceData(@NonNull String bridgeName, @NonNull St throw new UnsupportedOperationException("Method not supported in V1 API device."); } + private void readDataResultNodeToObject(String jsonResult, Object obj) throws IOException { + JsonNode node = objectMapper.readTree(jsonResult); + JsonNode data = node.path(LG_ROOT_TAG_V1).path("returnData"); + if (data.isTextual()) { + // analyses if its b64 or not + JsonNode format = node.path(LG_ROOT_TAG_V1).path("format"); + if ("B64".equals(format.textValue())) { + String dataStr = new String(Base64.getDecoder().decode(data.textValue())); + objectMapper.readerForUpdating(obj).readValue(dataStr); + } else { + objectMapper.readerForUpdating(obj).readValue(data.textValue()); + } + } else { + logger.warn("Data returned by LG API to get energy state is not present. Result:{}", node.toPrettyString()); + } + } + @Override - public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException, IOException { - // TODO - return 0; - } - - // // TODO - Analise this to get power consumption - // @Nullable - // private RestResult getConfigCommands(String bridgeName, String deviceId, String keyName) throws Exception { - // TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - // UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); - // Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - // token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - // - // String payload = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"Config\"," - // + " \"cmdOpt\": \"Get\"," + " \"value\": \"%s\"," + " \"deviceId\": \"%s\"," - // + " \"workId\": \"%s\"," + " \"data\": \"\"" + " }\n" + "}", keyName, deviceId, - // UUID.randomUUID().toString()); - // return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); - // } + public ExtendedDeviceInfo getExtendedDeviceInfo(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException { + ExtendedDeviceInfo info = new ExtendedDeviceInfo(); + try { + RestResult resp = sendCommand(bridgeName, deviceId, V1_CONTROL_OP, "Config", "Get", "", + "InOutInstantPower"); + handleGenericErrorResult(resp); + readDataResultNodeToObject(resp.getJsonResponse(), info); + + resp = sendCommand(bridgeName, deviceId, V1_CONTROL_OP, "Config", "Get", "", "Filter"); + handleGenericErrorResult(resp); + readDataResultNodeToObject(resp.getJsonResponse(), info); + + return info; + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending command to LG API", e); + } + } @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", "Operation", - "" + newPowerState.commandValue()); + RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", "Operation", + String.valueOf(newPowerState.commandValue())); handleGenericErrorResult(resp); } catch (Exception e) { throw new LGThinqApiException("Error adjusting device power", e); @@ -126,7 +150,7 @@ public void turnEnergySavingMode(String bridgeName, String deviceId, String mode protected void turnGenericMode(String bridgeName, String deviceId, String modeName, String modeOnOff) throws LGThinqApiException { try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", modeName, modeOnOff); + RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", modeName, modeOnOff); handleGenericErrorResult(resp); } catch (Exception e) { throw new LGThinqApiException("Error adjusting " + modeName + " mode", e); @@ -136,7 +160,7 @@ protected void turnGenericMode(String bridgeName, String deviceId, String modeNa @Override public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", "OpMode", "" + newOpMode); + RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", "OpMode", "" + newOpMode); handleGenericErrorResult(resp); } catch (Exception e) { throw new LGThinqApiException("Error adjusting operation mode", e); @@ -146,8 +170,8 @@ public void changeOperationMode(String bridgeName, String deviceId, int newOpMod @Override public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", "WindStrength", - "" + newFanSpeed); + RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", "WindStrength", + String.valueOf(newFanSpeed)); handleGenericErrorResult(resp); } catch (Exception e) { throw new LGThinqApiException("Error adjusting fan speed", e); @@ -158,8 +182,8 @@ public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGThinqApiException { try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "", "Control", "Set", "TempCfg", - "" + newTargetTemp.commandValue()); + RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", "TempCfg", + String.valueOf(newTargetTemp.commandValue())); handleGenericErrorResult(resp); } catch (Exception e) { throw new LGThinqApiException("Error adjusting target temperature", e); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java index e9816528b8d9d..12571b063eb9b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -27,9 +27,14 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ExtendedDeviceInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + /** * The {@link LGThinQACApiV2ClientServiceImpl} * @@ -168,7 +173,7 @@ public void initializeDevice(@NonNull String bridgeName, @NonNull String deviceI protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "control", "allEventEnable", "Set", + RestResult resp = sendCommand(bridgeName, deviceId, "control", "allEventEnable", "Set", "airState.mon.timeout", "70"); handleGenericErrorResult(resp); } catch (Exception e) { @@ -176,9 +181,59 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d } } + /** + * Expect receiving json of format: { + * ... + * result: { + * data: { + * ... + * } + * ... + * } + * } + * Data node will be deserialized into the object informed + * + * @param jsonResult json result + * @param obj object to be updated + * @throws IOException if there are errors deserialization the jsonResult + */ + private void readDataResultNodeToObject(String jsonResult, Object obj) throws IOException { + JsonNode node = objectMapper.readTree(jsonResult); + JsonNode data = node.path("result").path("data"); + if (data.isObject()) { + objectMapper.readerForUpdating(obj).readValue(data); + } else { + logger.warn("Data returned by LG API to get energy state is not present. Result:{}", node.toPrettyString()); + } + } + @Override - public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) + public ExtendedDeviceInfo getExtendedDeviceInfo(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { - throw new UnsupportedOperationException("Not supporte for this device"); + ExtendedDeviceInfo info = new ExtendedDeviceInfo(); + try { + ObjectNode dataList = JsonNodeFactory.instance.objectNode(); + dataList.put("dataGetList", (Integer) null); + dataList.put("dataSetList", (Integer) null); + + RestResult resp = sendCommand(bridgeName, deviceId, "control-sync", "energyStateCtrl", "Get", + "airState.energy.totalCurrent", "null", dataList); + handleGenericErrorResult(resp); + readDataResultNodeToObject(resp.getJsonResponse(), info); + + ObjectNode dataGetList = JsonNodeFactory.instance.objectNode(); + dataGetList.putArray("dataGetList").add("airState.filterMngStates.useTime") + .add("airState.filterMngStates.maxTime"); + resp = sendCommand(bridgeName, deviceId, "control-sync", "filterMngStateCtrl", "Get", null, null, + dataGetList); + handleGenericErrorResult(resp); + readDataResultNodeToObject(resp.getJsonResponse(), info); + + return info; + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending command to LG API", e); + } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index 8052afe10801c..7ab1aed0670e8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -59,7 +59,6 @@ public abstract class LGThinQAbstractApiClientService capabilityClass, Class snapshotClass) { this.tokenManager = TokenManager.getInstance(); - this.capabilityClass = capabilityClass; this.snapshotClass = snapshotClass; } @@ -257,6 +256,20 @@ public C getCapability(String deviceId, String uri, boolean forceRecreate) throw } } + private S handleV1OfflineException() { + try { + // As I don't know the current device status, then I reset to default values. + S shot = snapshotClass.getDeclaredConstructor().newInstance(); + shot.setPowerStatus(DevicePowerState.DV_POWER_OFF); + shot.setOnline(false); + return shot; + } catch (Exception ex) { + logger.error("Unexpected Error. The default constructor of this Snapshot wasn't found", ex); + throw new IllegalStateException("Unexpected Error. The default constructor of this Snapshot wasn't found", + ex); + } + } + public @Nullable S getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId, DeviceTypes deviceType, @NonNull C deviceCapability) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException, LGThinqUnmarshallException { @@ -268,23 +281,13 @@ public C getCapability(String deviceId, String uri, boolean forceRecreate) throw + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" + " ]\n" + " }\n" + "}", deviceId, workId); RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - Map envelop = null; + Map envelop; // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with // offline flag. try { envelop = handleGenericErrorResult(resp); } catch (LGThinqDeviceV1OfflineException e) { - try { - // As I don't know the current device status, then I reset to default values. - S shot = snapshotClass.getDeclaredConstructor().newInstance(); - shot.setPowerStatus(DevicePowerState.DV_POWER_OFF); - shot.setOnline(false); - return (S) shot; - } catch (Exception ex) { - logger.error("Unexpected Error. The default constructor of this Snapshot wasn't found", ex); - throw new IllegalStateException( - "Unexpected Error. The default constructor of this Snapshot wasn't found", ex); - } + return handleV1OfflineException(); } if (envelop.get("workList") != null && ((Map) envelop.get("workList")).get("returnData") != null) { @@ -390,9 +393,6 @@ public S getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, @No return null; } - public abstract double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException, IOException; - /** * Start monitor data form specific device. This is old one, works only on V1 API supported devices. * @@ -433,12 +433,12 @@ protected Map getCommonV2Headers(String language, String country return getCommonHeaders(language, country, accessToken, userNumber); } - protected abstract RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, - String controlKey, String command, String keyName, String value) throws Exception; + protected abstract RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, String keyName, String value) throws Exception; - protected abstract RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, - String controlKey, String command, @Nullable String keyName, @Nullable String value, - @Nullable ObjectNode extraNode) throws Exception; + protected abstract RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) + throws Exception; protected abstract Map handleGenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index 96d21dff2a14c..845ee2b67811c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -53,19 +53,14 @@ protected LGThinQAbstractApiV1ClientService(Class capabilityClass, Class s } @Override - protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, String controlKey, + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, String keyName, String value) throws Exception { - return sendControlCommands(bridgeName, deviceId, controlPath, controlKey, command, keyName, value, null); + return sendCommand(bridgeName, deviceId, controlPath, controlKey, command, keyName, value, null); } - protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, String controlKey, + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) throws Exception { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - ObjectNode payloadNode = JsonNodeFactory.instance.objectNode(); payloadNode.put("cmd", controlKey).put("cmdOpt", command); if (keyName == null || keyName.isEmpty()) { @@ -82,10 +77,10 @@ protected RestResult sendControlCommands(String bridgeName, String deviceId, Str if (extraNode != null) { payloadNode.setAll(extraNode); } - return sendControlCommands(bridgeName, deviceId, payloadNode); + return sendCommand(bridgeName, deviceId, payloadNode); } - protected RestResult sendControlCommands(String bridgeName, String deviceId, Object cmdPayload) throws Exception { + protected RestResult sendCommand(String bridgeName, String deviceId, Object cmdPayload) throws Exception { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -97,11 +92,14 @@ protected RestResult sendControlCommands(String bridgeName, String deviceId, Obj payloadNode = objectMapper.convertValue(cmdPayload, ObjectNode.class); } ObjectNode rootNode = JsonNodeFactory.instance.objectNode(); - payloadNode.put("deviceId", deviceId); - payloadNode.put("workId", UUID.randomUUID().toString()); - rootNode.set("lgedmRoot", payloadNode); - - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, rootNode.toPrettyString()); + ObjectNode bodyNode = JsonNodeFactory.instance.objectNode(); + bodyNode.put("deviceId", deviceId); + bodyNode.put("workId", UUID.randomUUID().toString()); + bodyNode.setAll(payloadNode); + rootNode.set("lgedmRoot", bodyNode); + String url = builder.build().toURL().toString(); + logger.debug("URL: {}, Post Payload:[{}]", url, rootNode.toPrettyString()); + RestResult resp = RestUtils.postCall(url, headers, rootNode.toPrettyString()); if (resp == null) { logger.error("Null result returned sending command to LG API V1"); throw new LGThinqApiException("Null result returned sending command to LG API V1"); @@ -125,7 +123,7 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); envelope = (Map) metaResult.get("lgedmRoot"); - String code = "" + envelope.get("returnCd"); + String code = String.valueOf(envelope.get("returnCd")); if (envelope.isEmpty()) { throw new LGThinqApiException(String.format( "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); @@ -139,9 +137,8 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp } logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, getErrorCodeMessage(code)); - throw new LGThinqApiException( - String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", - metaResult.get("returnCd"))); + throw new LGThinqApiException(String + .format("Status error executing endpoint. resultCode must be 0000, but was:%s", code)); } } catch (JsonProcessingException e) { throw new IllegalStateException("Unknown error occurred deserializing json stream", e); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java index 52bd48e115979..61343fb0fcff9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -14,6 +14,7 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; +import java.io.IOException; import java.util.Collections; import java.util.Map; @@ -51,26 +52,19 @@ protected LGThinQAbstractApiV2ClientService(Class capabilityClass, Class s } @Override - protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, String controlKey, + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, String keyName, String value) throws Exception { - return sendControlCommands(bridgeName, deviceId, controlPath, controlKey, command, keyName, value, null); + return sendCommand(bridgeName, deviceId, controlPath, controlKey, command, keyName, value, null); } - @Override - protected RestResult sendControlCommands(String bridgeName, String deviceId, String controlPath, String controlKey, - String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) - throws Exception { + protected RestResult postCall(String bridgeName, String deviceId, String controlPath, String payload) + throws LGThinqApiException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId, controlPath)); Map headers = getCommonV2Headers(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - ObjectNode payload = JsonNodeFactory.instance.objectNode(); - payload.put("ctrlKey", controlKey).put("command", command).put("dataKey", keyName).put("dataValue", value); - if (extraNode != null) { - payload.setAll(extraNode); - } - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, payload.toPrettyString()); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, payload); if (resp == null) { logger.error("Null result returned sending command to LG API V2"); throw new LGThinqApiException("Null result returned sending command to LG API V2"); @@ -78,16 +72,28 @@ protected RestResult sendControlCommands(String bridgeName, String deviceId, Str return resp; } + @Override + public RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) + throws Exception { + ObjectNode payload = JsonNodeFactory.instance.objectNode(); + payload.put("ctrlKey", controlKey).put("command", command).put("dataKey", keyName).put("dataValue", value); + if (extraNode != null) { + payload.setAll(extraNode); + } + return postCall(bridgeName, deviceId, controlPath, payload.toPrettyString()); + } + protected RestResult sendBasicControlCommands(String bridgeName, String deviceId, String command, String keyName, int value) throws Exception { - return sendControlCommands(bridgeName, deviceId, "control-sync", "basicCtrl", command, keyName, "" + value); + return sendCommand(bridgeName, deviceId, "control-sync", "basicCtrl", command, keyName, String.valueOf(value)); } @Override protected Map handleGenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { Map metaResult; if (resp == null) { - return Collections.EMPTY_MAP; + return Collections.emptyMap(); } if (resp.getStatusCode() != 200) { logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); @@ -95,21 +101,20 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { - metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); String code = (String) metaResult.get("resultCode"); - if (!ResultCodes.OK.containsResultCode("" + metaResult.get("resultCode"))) { + if (!ResultCodes.OK.containsResultCode(String.valueOf(metaResult.get("resultCode")))) { logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, getErrorCodeMessage(code)); throw new LGThinqApiException( String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", metaResult.get("resultCode"))); } + return metaResult; } catch (JsonProcessingException e) { throw new IllegalStateException("Unknown error occurred deserializing json stream", e); } - } - return Collections.EMPTY_MAP; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index d239768f24d3f..657abf9dd6458 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -49,13 +49,9 @@ public interface LGThinQApiClientService cmdPayload = prepareCommandV1(cmdStartDef, data); - RestResult result = sendControlCommands(bridgeName, deviceId, cmdPayload); + logger.debug("token Payload:[{}]", cmdPayload); + RestResult result = sendCommand(bridgeName, deviceId, cmdPayload); handleGenericErrorResult(result); } catch (LGThinqApiException e) { throw e; @@ -105,7 +97,7 @@ public void remoteStart(String bridgeName, WasherDryerCapability cap, String dev public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException { try { - RestResult result = sendControlCommands(bridgeName, deviceId, "", "Control", "Operation", "", "WakeUp"); + RestResult result = sendCommand(bridgeName, deviceId, "", "Control", "Operation", "", "WakeUp"); handleGenericErrorResult(result); } catch (LGThinqApiException e) { throw e; @@ -121,12 +113,15 @@ private Map prepareCommandV1(CommandDefinition cmdDef, Map e : snapData.entrySet()) { String value = String.valueOf(e.getValue()); if ("Start".equals(cmdDef.getCmdOptValue()) && e.getKey().equals("Option2")) { - value = String.valueOf(Integer.parseInt(value) | 1); + // For some reason, option2 fills only InitialBit with 1. + value = "1"; } dataStr = dataStr.replace("{{" + e.getKey() + "}}", value); } - Map cmd = objectMapper.readValue(cmdDef.getRawCommand(), new TypeReference<>() { + // Keep the order + LinkedHashMap cmd = objectMapper.readValue(cmdDef.getRawCommand(), new TypeReference<>() { }); + cmd.remove("encode"); // remove encode node in the raw command to be similar to LG App. logger.debug("Prepare command v1: {}", dataStr); if (cmdDef.isBinary()) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java index eeca4e477ea16..f06ce3d55fd89 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java @@ -14,7 +14,6 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_COMMAND_REMOTE_START_V2; -import java.io.IOException; import java.util.Map; import org.eclipse.jdt.annotation.NonNull; @@ -54,12 +53,6 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d // TODO - Analise what to do here } - @Override - public double getInstantPowerConsumption(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException, IOException { - return 0; - } - public static LGThinQWMApiClientService getInstance() { return instance; } @@ -96,8 +89,8 @@ public void remoteStart(String bridgeName, WasherDryerCapability cap, String dev } } - RestResult result = sendControlCommands(bridgeName, deviceId, "control-sync", WM_COMMAND_REMOTE_START_V2, - "Set", null, null, dataSetList); + RestResult result = sendCommand(bridgeName, deviceId, "control-sync", WM_COMMAND_REMOTE_START_V2, "Set", + null, null, dataSetList); handleGenericErrorResult(result); } catch (LGThinqApiException e) { throw e; @@ -113,7 +106,7 @@ public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LG dataSetList.putObject("dataSetList").putObject("washerDryer").put("controlDataType", "WAKEUP") .put("controlDataValueLength", wakeUp ? "1" : "0"); - RestResult result = sendControlCommands(bridgeName, deviceId, "control-sync", "WMWakeup", "Set", null, null, + RestResult result = sendCommand(bridgeName, deviceId, "control-sync", "WMWakeup", "Set", null, null, dataSetList); handleGenericErrorResult(result); } catch (LGThinqApiException e) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java index c94bc264aeb88..326db9b5387ff 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java @@ -122,4 +122,7 @@ protected void validateMandatoryNote(JsonNode node) throws LGThinqException { String.format("Error extracting mandatory %s node for this device cap file", node)); } } + + protected abstract Map getCommandsDefinition(JsonNode rootNode) + throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java index 53290c86e2f8c..4e505b19f522b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java @@ -30,6 +30,7 @@ public class CommandDefinition { /** * This is the command tag value that is used by the API to launch the command service */ + private String dataKey = ""; private String command = ""; private Map data = new HashMap<>(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java index 7710226bcf77d..cfb689c026093 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java @@ -80,7 +80,6 @@ public S createFromBinary(String binaryData, List prot for (String v : values) { aliasesMethod.putIfAbsent(v, property); } - } } for (MonitoringBinaryProtocol protField : prot) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java index 7886807c35df0..a13668aa57e61 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java @@ -24,6 +24,7 @@ public enum ResultCodes { + DEVICE_OFFLINE("Device Offline", "0106"), OK("Success", "0000", "0001"), DEVICE_NOT_RESPONSE("Device Not Response", "0111", "0103", "0104", "0106"), PORTAL_INTERWORKING_ERROR("Portal Internal Error", "0007"), @@ -31,10 +32,10 @@ public enum ResultCodes { "Login/Session Failed, Duplicated or Terms Not Agreed. Try to login and correct issues direct on LG Account Portal", "0004", "0102", "0110", "0114"), BASE64_CODING_ERROR("Base64 Decoding/Encoding error", "9002", "9001"), - NOT_SUPPORTED_CONTROL("Commnad/Control/Service is not supported", "0005", "0012", "8001"), + NOT_SUPPORTED_CONTROL("Command/Control/Service is not supported", "0005", "0012", "8001"), CONTROL_ERROR("Error in device control", "0105"), - LG_SERVER_ERROR("LG Server Error", "8101", "8102", "8103", "8104", "8105", "8106", "8107", "9003", "9004", "9005", - "9000", "8900", "0107"), + LG_SERVER_ERROR("LG Server Error/Invalid Request", "8101", "8102", "8103", "8104", "8105", "8106", "8107", "9003", + "9004", "9005", "9000", "8900", "0107"), PAYLOAD_ERROR("Malformed or Wrong Payload", "9999"), DUPLICATED_DATA("Duplicated Data/Alias", "0008", "0013"), ACCESS_DENIED("Access Denied. Verify your account/password in LG Account Portal.", "9006", "0011", "0113"), @@ -42,7 +43,7 @@ public enum ResultCodes { NETWORK_FAILED("Timeout/Network has failed.", "9020"), LIMIT_EXCEEDED_ERROR("Limit has been exceeded", "0112"), CUSTOMER_NUMBER_EXPIRED("Customer number has been expired", "0119"), - INVALID_CUSTOMER_DATA("Customer data is invalid", "0010"), + INVALID_CUSTOMER_DATA("Customer data is invalid or Data Doesn't exist.", "0010"), GENERAL_FAILURE("General Failure", "0100"), INVALID_CSR("Invalid CSR", "9010"), INVALID_PAYLOAD("Invalid Body/Payload", "0002"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java index 1a6b15dbd2c09..7fb46e8957d96 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java @@ -29,6 +29,8 @@ public class ACCapability extends AbstractCapability { private Map opMod = Collections.emptyMap(); private Map fanSpeed = Collections.emptyMap(); private boolean isJetModeAvailable; + private boolean isEnergyMonitorAvailable; + private boolean isFilterMonitorAvailable; private boolean isAutoDryModeAvailable; private boolean isEnergySavingAvailable; private boolean isAirCleanAvailable; @@ -154,4 +156,20 @@ public String getAirCleanModeCommandOff() { public void setAirCleanModeCommandOff(String airCleanModeCommandOff) { this.airCleanModeCommandOff = airCleanModeCommandOff; } + + public boolean isEnergyMonitorAvailable() { + return isEnergyMonitorAvailable; + } + + public void setEnergyMonitorAvailable(boolean energyMonitorAvailable) { + isEnergyMonitorAvailable = energyMonitorAvailable; + } + + public boolean isFilterMonitorAvailable() { + return isFilterMonitorAvailable; + } + + public void setFilterMonitorAvailable(boolean filterMonitorAvailable) { + isFilterMonitorAvailable = filterMonitorAvailable; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java index b0a143b8ad7bd..586215de88ac5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java @@ -12,11 +12,15 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.ac; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +41,11 @@ protected List getSupportedAPIVersions() { return List.of(LGAPIVerion.V1_0); } + @Override + protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + return Collections.emptyMap(); + } + @Override protected Map extractFeatureOptions(JsonNode optionsNode) { Map options = new HashMap<>(); @@ -46,6 +55,17 @@ protected Map extractFeatureOptions(JsonNode optionsNode) { return options; } + @Override + public ACCapability create(JsonNode rootNode) throws LGThinqException { + ACCapability cap = super.create(rootNode); + // set energy and filter availability (extended info) + cap.setEnergyMonitorAvailable( + !rootNode.path("ControlWifi").path("action").path("GetInOutInstantPower").isMissingNode()); + cap.setFilterMonitorAvailable( + !rootNode.path("ControlWifi").path("action").path("GetFilterUse").isMissingNode()); + return cap; + } + @Override protected String getDataTypeFeatureNodeName() { return "type"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java index 3aea1e60112fe..8d97915d9ac52 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java @@ -12,11 +12,12 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.ac; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +38,31 @@ protected List getSupportedAPIVersions() { return List.of(LGAPIVerion.V2_0); } + @Override + protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + Map result = new HashMap<>(); + JsonNode controlDeviceNode = rootNode.path("ControlDevice"); + if (controlDeviceNode.isArray()) { + controlDeviceNode.forEach(c -> { + String ctrlKey = c.path("ctrlKey").asText(); + // commands variations are described separated by pipe "|" + String[] commands = c.path("command").asText().split("\\|"); + String dataValues = c.path("dataValue").asText(); + int i = 0; + for (String cOpt : commands) { + CommandDefinition cd = new CommandDefinition(); + cd.setCommand(ctrlKey); + cd.setCmdOpt(cOpt); + cd.setCmdOptValue(dataValues.replaceAll("[{%}]", "")); + cd.setRawCommand(c.toPrettyString()); + result.put(ctrlKey, cd); + i++; + } + }); + } + return result; + } + @Override protected String getOpModeNodeName() { return "airState.opMode"; @@ -105,4 +131,14 @@ protected Map extractFeatureOptions(JsonNode optionsNode) { }); return options; } + + @Override + public ACCapability create(JsonNode rootNode) throws LGThinqException { + ACCapability cap = super.create(rootNode); + Map cmd = getCommandsDefinition(rootNode); + // set energy and filter availability (extended info) + cap.setEnergyMonitorAvailable(cmd.containsKey("energyStateCtrl")); + cap.setFilterMonitorAvailable(cmd.containsKey("filterMngStateCtrl")); + return cap; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java new file mode 100644 index 0000000000000..f37ce9a55c7ca --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java @@ -0,0 +1,91 @@ +/** + * 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.lgthinq.lgservices.model.devices.ac; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.Map; + +import org.apache.commons.lang3.math.NumberUtils; +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link ExtendedDeviceInfo} containing extended information obout the device. In + * AC cases, it holds instant power consumption, filter used in hours and max time to use. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class ExtendedDeviceInfo { + private String instantPower = ""; + + @JsonProperty(EXTENDED_ATTR_FILTER_USED_TIME) + @JsonAlias("airState.filterMngStates.useTime") + public String getFilterHoursUsed() { + return filterHoursUsed; + } + + public void setFilterHoursUsed(String filterHoursUsed) { + this.filterHoursUsed = filterHoursUsed; + } + + @JsonProperty(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE) + @JsonAlias("airState.filterMngStates.maxTime") + public String getFilterHoursMax() { + return filterHoursMax; + } + + public void setFilterHoursMax(String filterHoursMax) { + this.filterHoursMax = filterHoursMax; + } + + private String filterHoursUsed = ""; + private String filterHoursMax = ""; + + /** + * Returns the instant total power consumption + * + * @return the instant total power consumption + */ + @JsonProperty(EXTENDED_ATTR_INSTANT_POWER) + @JsonAlias("airState.energy.totalCurrent") + public String getRawInstantPower() { + return instantPower; + } + + public Double getInstantPower() { + return NumberUtils.isCreatable(instantPower) ? Double.parseDouble(instantPower) : 0.0; + } + + public void setRawInstantPower(String instantPower) { + this.instantPower = instantPower; + } + + public static void main(String[] args) { + ExtendedDeviceInfo info = new ExtendedDeviceInfo(); + info.setFilterHoursMax("1"); + info.setFilterHoursUsed("2"); + info.setRawInstantPower("344"); + ObjectMapper m = new ObjectMapper(); + Map values = m.convertValue(info, new TypeReference<>() { + }); + System.out.println(values); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index 54aac176bc4ac..4196e46a89f22 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -12,9 +12,14 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; +import java.util.Collections; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import com.fasterxml.jackson.databind.JsonNode; @@ -32,6 +37,12 @@ protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode fe return FeatureDefinition.NULL_DEFINITION; } + // TODO - Implement Commands parser + @Override + protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + return Collections.emptyMap(); + } + @Override public FridgeCapability getCapabilityInstance() { return new FridgeCanonicalCapability(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java index 195624d216827..776487ec8d1b5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -12,9 +12,14 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; +import java.util.Collections; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import com.fasterxml.jackson.databind.JsonNode; @@ -31,6 +36,12 @@ protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode fe return FeatureDefinition.NULL_DEFINITION; } + // TODO - Implement Commands parser + @Override + protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + return Collections.emptyMap(); + } + @Override public FridgeCapability getCapabilityInstance() { return new FridgeCanonicalCapability(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java index 4115c48526490..84e6ea8564da1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java @@ -19,10 +19,8 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; -import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; import org.slf4j.Logger; @@ -60,15 +58,14 @@ public abstract class AbstractWasherDryerCapabilityFactory extends AbstractCapab protected abstract MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode); - protected abstract Map getCommandsDefinition(JsonNode rootNode) - throws LGThinqApiException; - protected abstract String getCommandRemoteStartNodeName(); protected abstract String getCommandStopNodeName(); protected abstract String getCommandWakeUpNodeName(); + protected abstract String getDefaultCourseIdNodeName(); + @Override public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { WasherDryerCapability wdCap = super.create(rootNode); @@ -115,6 +112,7 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { getRinseFeatureNodeName(), new WasherDryerCapability.RinseFeatureFunction(), getSpinFeatureNodeName(), new WasherDryerCapability.SpinFeatureFunction())); wdCap.setMonitoringDataFormat(getMonitorDataFormat(rootNode)); + wdCap.setDefaultCourseId(rootNode.path("Config").path(getDefaultCourseIdNodeName()).asText()); return wdCap; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java index 5d1bfd32ca05d..997ad0aeb5385 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java @@ -37,6 +37,7 @@ public class WasherDryerCapability extends AbstractCapability getCommandsDefinition() { return commandsDefinition; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java index 0f97cc4f5cf61..c22cd4dbc68da 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -54,7 +54,7 @@ protected String getStateFeatureNodeName() { @Override protected String getProcessStateNodeName() { - return "ProcessState"; + return "PreState"; } @Override @@ -127,7 +127,7 @@ protected Map getCommandsDefinition(JsonNode rootNode Map data = new LinkedHashMap<>(); for (String f : strData.split(",")) { if (f.contains("{")) { - // its a featured field + // it's a featured field // create data entry with the key and blank value data.put(f.replaceAll("[{\\[}\\]]", ""), ""); } else { @@ -158,6 +158,11 @@ protected String getCommandWakeUpNodeName() { return "OperationWakeUp"; } + @Override + protected String getDefaultCourseIdNodeName() { + return "defaultCourseId"; + } + @Override protected String getDryLevelNodeName() { return "DryLevel"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java index a9f06640cf990..3f4d9945b9f83 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java @@ -150,7 +150,7 @@ protected String getStateFeatureNodeName() { @Override protected String getProcessStateNodeName() { - return "ProcessState"; + return "preState"; } @Override @@ -253,6 +253,11 @@ protected String getCommandWakeUpNodeName() { return "WMWakeup"; } + @Override + protected String getDefaultCourseIdNodeName() { + return "defaultCourse"; + } + @Override protected String getNotSelectedCourseKey() { return "NOT_SELECTED"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java index 3e7fb57c9813a..da6a14a7d67e4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java @@ -117,7 +117,7 @@ public DevicePowerState getPowerStatus() { @Override public void setPowerStatus(DevicePowerState value) { - throw new IllegalArgumentException("This method must not be accessed."); + this.powerState = value; } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml index c1e93657411ad..f1650ab164327 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml @@ -13,14 +13,73 @@ LG ThinQ Air Conditioner + + + + + + + + Settings required to optimize the polling behaviour. + false + + + + Seconds to wait to the next polling when device is off. Useful to save up + i/o and cpu when your + device is + not working. If you use only this binding to control the + device, you can put higher values here. + + 10 + + + + Seconds to wait to the next polling for device state (dashboard channels) + + 10 + + + + Seconds to wait to the next polling for Device's Extra Info (energy consumption, + remaining filter, etc) + + 60 + + + + If enables, extra info will be fetched even when the device is powered off. + It's not so common, since + extra info are normally changed only when the device is running. + + false + + + + + + + This is the Displayed Information. + + - - + - + + + Show more information about the device. + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index c029ef26b58ab..4b28325a0ce1c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -12,6 +12,45 @@ + + Number:Dimensionless + + Remaining filter without need to be replaced. + Battery + + + + + String + + Months passed since filter has been changed. + + + + + + + + + + + + + + + + + + + + + + Switch + + This switch enable collector for energy and filter consumption (if presents) + Switch + + Switch @@ -47,6 +86,15 @@ Temperature + + + Number:Energy + + Current Power Consumption + Energy + + + Number @@ -220,6 +268,13 @@ + + String + + Course + + + String diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml index 429e85047ac03..78f7d9654ea8b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml @@ -13,6 +13,15 @@ LG ThinQ Fridge + + + + + + + + + This is the Displayed Information. @@ -22,7 +31,13 @@ - - - + + + + Show more information about the device. + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml index 4c0a61efeca35..3ee7551082ac4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml @@ -12,25 +12,50 @@ LG ThinQ Washing Machine - - - + + + + + + Settings required to optimize the polling behaviour. + false + + + + Seconds to wait to the next polling when device is off. Useful to save up + i/o and cpu when your + device is + not working. If you use only this binding to control the + device, you can put higher values here. + + 10 + + + + Seconds to wait to the next polling for device state (dashboard channels) + + 10 + + - + Remote Start Actions and Options. + - + This is the Displayed Information. From aa1651ee526e7f43f46bb848b467f95ab2f7146f Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Sun, 25 Jun 2023 19:20:45 -0300 Subject: [PATCH 003/130] [lgthinq][fix] fixed compilation errors Signed-off-by: nemerdaud --- .../lgthinq/internal/handler/LGThinQWasherDryerHandler.java | 2 +- .../binding/lgthinq/lgservices/model/AbstractCapability.java | 3 +-- .../lgthinq/lgservices/model/AbstractCapabilityFactory.java | 1 - .../binding/lgthinq/lgservices/model/CapabilityDefinition.java | 1 - .../binding/lgthinq/lgservices/model/FeatureDefinition.java | 2 +- .../model/devices/ac/AbstractACCapabilityFactory.java | 2 +- .../model/devices/fridge/FridgeCapabilityFactoryV1.java | 2 +- .../model/devices/fridge/FridgeCapabilityFactoryV2.java | 2 +- .../model/devices/washerdryer/WasherDryerCapability.java | 2 +- .../devices/washerdryer/WasherDryerCapabilityFactoryV1.java | 2 +- .../devices/washerdryer/WasherDryerCapabilityFactoryV2.java | 2 +- 11 files changed, 9 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index cc58a37d962ee..b1a1b0a38168d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -26,7 +26,7 @@ import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.*; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java index 3b690fe6188e5..9bf4a3a89b15e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java @@ -12,14 +12,13 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.lgservices.FeatureDefinition.NULL_DEFINITION; +import static org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition.NULL_DEFINITION; import java.lang.reflect.ParameterizedType; import java.util.*; import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; import com.fasterxml.jackson.annotation.JsonIgnore; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java index 326db9b5387ff..f8a5f4c7f80c8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java @@ -19,7 +19,6 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java index 7dff468ad5517..53ef3f043f45b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java @@ -16,7 +16,6 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; /** * The {@link CapabilityDefinition} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java index d7043951d8084..b5ae2816c8e32 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices; +package org.openhab.binding.lgthinq.lgservices.model; import java.util.HashMap; import java.util.Map; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java index 7b8f5352b54da..836b61a39f27c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java @@ -21,7 +21,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index 4196e46a89f22..a9b927cf600d1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import com.fasterxml.jackson.databind.JsonNode; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java index 776487ec8d1b5..0d7351d5d3019 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -18,7 +18,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import com.fasterxml.jackson.databind.JsonNode; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java index 997ad0aeb5385..ff937e8bedb7f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java @@ -18,7 +18,7 @@ import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java index c22cd4dbc68da..363f301d5eb0f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -19,7 +19,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java index 3f4d9945b9f83..8c72b1c7d33ee 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java @@ -17,7 +17,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; From b14cdc05cc102817d468a71f3538d7eb3fbf6b68 Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Wed, 28 Jun 2023 21:13:51 -0300 Subject: [PATCH 004/130] [lgthinq][feat] improvements on exception handling Signed-off-by: nemerdaud --- .../LGThinQAbstractApiV1ClientService.java | 12 ++++++++---- .../LGThinQAbstractApiV2ClientService.java | 15 ++++++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index 845ee2b67811c..633634f71c009 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -101,7 +101,7 @@ protected RestResult sendCommand(String bridgeName, String deviceId, Object cmdP logger.debug("URL: {}, Post Payload:[{}]", url, rootNode.toPrettyString()); RestResult resp = RestUtils.postCall(url, headers, rootNode.toPrettyString()); if (resp == null) { - logger.error("Null result returned sending command to LG API V1"); + logger.warn("Null result returned sending command to LG API V1"); throw new LGThinqApiException("Null result returned sending command to LG API V1"); } return resp; @@ -115,9 +115,13 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp return envelope; } if (resp.getStatusCode() != 200) { - logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException( - String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + if (resp.getStatusCode() == 400) { + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + } else { + logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + throw new LGThinqApiException( + String.format("Error returned by LG Server API. HTTP Status: %s. The reason is: %s", resp.getStatusCode(), resp.getJsonResponse())); + } } else { try { metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java index 61343fb0fcff9..96eb8a40057f9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -51,7 +51,7 @@ protected LGThinQAbstractApiV2ClientService(Class capabilityClass, Class s super(capabilityClass, snapshotClass); } - @Override + @Override protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, String keyName, String value) throws Exception { return sendCommand(bridgeName, deviceId, controlPath, controlKey, command, keyName, value, null); @@ -66,7 +66,7 @@ protected RestResult postCall(String bridgeName, String deviceId, String control token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, payload); if (resp == null) { - logger.error("Null result returned sending command to LG API V2"); + logger.warn("Null result returned sending command to LG API V2: {}, {}, {}", deviceId, controlPath, payload); throw new LGThinqApiException("Null result returned sending command to LG API V2"); } return resp; @@ -96,9 +96,14 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp return Collections.emptyMap(); } if (resp.getStatusCode() != 200) { - logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException( - String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + if (resp.getStatusCode() == 400) { + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + return Collections.emptyMap(); + } else { + logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + throw new LGThinqApiException( + String.format("Error returned by LG Server API. HTTP Status: %s. The reason is: %s", resp.getStatusCode(), resp.getJsonResponse())); + } } else { try { metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { From 9a84333164091d0c6304eef6aa37222cccd4681f Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Wed, 28 Jun 2023 21:16:31 -0300 Subject: [PATCH 005/130] [lgthinq][fix] fixed initialization status and disposal Signed-off-by: nemerdaud --- .../handler/LGThinQAbstractDeviceHandler.java | 70 ++++++++++++++----- .../handler/LGThinQAirConditionerHandler.java | 4 +- .../handler/LGThinQBridgeHandler.java | 4 +- .../handler/LGThinQFridgeHandler.java | 8 --- .../handler/LGThinQWasherDryerHandler.java | 7 -- 5 files changed, 55 insertions(+), 38 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 081ca76f6fc27..5ec0627e088f0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -47,7 +47,8 @@ @NonNullByDefault public abstract class LGThinQAbstractDeviceHandler extends BaseThingWithExtraInfoHandler { - private final Logger logger = LoggerFactory.getLogger(LGThinQAbstractDeviceHandler.class); + private final Logger logger = LoggerFactory.getLogger(LGThinQAbstractDeviceHandler.class); + protected @Nullable LGThinQBridgeHandler account; protected final String lgPlatformType; @Nullable private S lastShot; @@ -278,10 +279,26 @@ protected String getItemLinkedValue(ChannelUID channelUID) { protected abstract Logger getLogger(); + @Override + public void initialize() { + getLogger().debug("Initializing Thinq thing."); + + Bridge bridge = getBridge(); + if (bridge != null) { + this.account = (LGThinQBridgeHandler) bridge.getHandler(); + this.bridgeId = bridge.getUID().getId(); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Bridge not set"); + return; + } + + initializeThing(bridge.getStatus()); + } + protected void initializeThing(@Nullable ThingStatus bridgeStatus) { getLogger().debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); String thingId = getThing().getUID().getId(); - Bridge bridge = getBridge(); + // setup configurations loadConfigurations(); @@ -293,22 +310,26 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values.", e); } - if (bridge != null) { - bridgeId = bridge.getUID().getId(); - LGThinQBridgeHandler handler = (LGThinQBridgeHandler) bridge.getHandler(); - // registry this thing to the bridge - if (handler == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } else { - handler.registryListenerThing(this); - if (bridgeStatus == ThingStatus.ONLINE) { + // registry this thing to the bridge + if (account == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Account not set"); + } else { + account.registryListenerThing(this); + switch (bridgeStatus) { + case ONLINE: updateStatus(ThingStatus.ONLINE); - } else { + break; + case INITIALIZING: + case UNINITIALIZED: + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + break; + case UNKNOWN: + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + break; + default: updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - } + break; } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); } } else { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, @@ -336,6 +357,12 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { startThingStatePolling(); } } + + public void refreshStatus() { + if (thing.getStatus() == ThingStatus.OFFLINE) { + initialize(); + } + } private void loadConfigurations() { isThingReconfigured = true; @@ -406,8 +433,8 @@ private void updateExtraInfoState() { updateExtraInfoStateChannels(extraInfoCollected); } catch (LGThinqException ex) { getLogger().error( - "Error getting energy state and update the correlated channels. DeviceName:{}, DeviceId:{} ", - getDeviceAlias(), getDeviceId(), ex); + "Error getting energy state and update the correlated channels. DeviceName: {}, DeviceId: {}. Error: {}", + getDeviceAlias(), getDeviceId(), ex.getMessage(), ex); } } @@ -452,8 +479,8 @@ protected void updateThingStateFromLG() { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } } catch (LGThinqException e) { - getLogger().error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", - getDeviceAlias(), getDeviceId(), e); + getLogger().error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry: {}", + getDeviceAlias(), getDeviceId(), e.getMessage(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (Throwable e) { getLogger().error( @@ -700,6 +727,11 @@ protected Runnable getQueuedCommandExecutor() { @Override public void dispose() { logger.debug("Disposing Thinq Thing {}", getDeviceId()); + + if (account != null) { + account.unRegistryListenerThing(this); + } + if (thingStatePollingJob != null) { thingStatePollingJob.cancel(true); stopThingStatePolling(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 441a826d3589f..d9abe85b4e41c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -109,9 +109,7 @@ public LGThinQAirConditionerHandler(Thing thing, LGThinQStateDescriptionProvider @Override public void initialize() { - logger.debug("Initializing Thinq thing."); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); + super.initialize(); try { ACCapability cap = getCapabilities(); if (!isExtraInfoCollectorSupported()) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index ca656cfc106a7..26ab1d6dc7cfd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -140,7 +140,7 @@ public void run() { try { doConnectedRun(); } catch (Exception e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.lgapi-getting-devices"); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.lgapi-getting-devices"); } } finally { @@ -227,6 +227,8 @@ protected void doConnectedRun() throws LGThinqException { discoveryService.removeLgDeviceDiscovery(device); } }); + + lGDeviceRegister.values().forEach(LGThinQAbstractDeviceHandler::refreshStatus); } }; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index a0065b04f2835..3ae97e6c702c6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -36,7 +36,6 @@ import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -83,13 +82,6 @@ public LGThinQFridgeHandler(Thing thing, LGThinQStateDescriptionProvider stateDe tempUnitUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_REF_TEMP_UNIT); } - @Override - public void initialize() { - logger.debug("Initializing Thinq thing."); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - @Override protected void updateDeviceChannels(FridgeCanonicalSnapshot shot) { updateState(fridgeTempChannelUID, new QuantityType(shot.getFridgeStrTemp())); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index b1a1b0a38168d..c00c86df35107 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -119,13 +119,6 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { remoteStartEnabledChannels.clear(); } - @Override - public void initialize() { - logger.debug("Initializing Thinq thing. Washer/Dryer Thing v3.4.3"); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - private void loadOptionsCourse(WasherDryerCapability cap, ChannelUID courseChannel) { List optionsCourses = new ArrayList<>(); cap.getCourses().forEach((k, v) -> optionsCourses.add(new StateOption(k, emptyIfNull(v.getCourseName())))); From a41f5f072158f3a9e81616e73c9d3434b4a81064 Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Thu, 29 Jun 2023 11:15:03 -0300 Subject: [PATCH 006/130] [lgthinq][feat] increased api timeouts to 10s Signed-off-by: nemerdaud --- .../openhab/binding/lgthinq/internal/api/RestUtils.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java index c44be29cb74c2..d1073ed2baec8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java @@ -53,10 +53,10 @@ public class RestUtils { private static final Logger logger = LoggerFactory.getLogger(RestUtils.class); private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; private static final RequestConfig requestConfig; + private static final int DEFAULT_TIMEOUT_MILLISECONDS = 10 * 1000; static { - int timeout = 5; - requestConfig = RequestConfig.custom().setConnectTimeout(timeout * 1000) - .setConnectionRequestTimeout(timeout * 1000).setSocketTimeout(timeout * 1000).build(); + requestConfig = RequestConfig.custom().setConnectTimeout(DEFAULT_TIMEOUT_MILLISECONDS) + .setConnectionRequestTimeout(DEFAULT_TIMEOUT_MILLISECONDS).setSocketTimeout(DEFAULT_TIMEOUT_MILLISECONDS).build(); } public static String getPreLoginEncPwd(String pwdToEnc) { @@ -162,7 +162,6 @@ private static RestResult postCall(String encodedUrl, Map header HttpPost request = new HttpPost(encodedUrl); headers.forEach(request::setHeader); request.setEntity(entity); - int hardTimeout = 6000; // milliseconds TimerTask task = new TimerTask() { @Override public void run() { @@ -170,7 +169,7 @@ public void run() { request.abort(); } }; - new Timer(true).schedule(task, hardTimeout); + new Timer(true).schedule(task, DEFAULT_TIMEOUT_MILLISECONDS); HttpResponse resp = client.execute(request); if (request.isAborted()) { logger.warn("POST to LG API was aborted due to timeout waiting for connection or data"); From 80826cc4cd5fb47202fd8a1ef2d95e64856313de Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Thu, 29 Jun 2023 22:53:46 -0300 Subject: [PATCH 007/130] [lgthinq][feat] improvements on exception handling Signed-off-by: nemerdaud --- .../lgservices/LGThinQAbstractApiClientService.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index 7ab1aed0670e8..4b859a7b0ba19 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -158,6 +158,10 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo String resultCode = "???"; if (resp.getStatusCode() != 200) { try { + if (resp.getStatusCode() == 400) { + logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + return Collections.emptyMap(); + } respMap = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); resultCode = respMap.get("resultCode"); @@ -200,6 +204,10 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG Map devicesResult; List devices; if (resp.getStatusCode() != 200) { + if (resp.getStatusCode() == 400) { + logger.warn("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + return Collections.emptyList(); + } logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); throw new LGThinqApiException(String .format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); From 19e0e74786ccfdddc03714bd0313ac97bf1007dc Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Mon, 3 Jul 2023 22:39:17 -0300 Subject: [PATCH 008/130] [lgthinq][fix] fixed issue that was preventing polling to start Signed-off-by: nemerdaud --- .../handler/LGThinQAbstractDeviceHandler.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 5ec0627e088f0..8d002fbe82a8d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -492,20 +492,24 @@ protected void updateThingStateFromLG() { private void handlePowerChange(@Nullable DevicePowerState previous, DevicePowerState current) { // isThingReconfigured is true when configurations has been updated or thing has just initialized // this will force to analyse polling periods and starts - if (!isThingReconfigured && (pollingPeriodOffSeconds == pollingPeriodOnSeconds || previous == current)) { + if (!isThingReconfigured && previous == current) { // no changes needed return; } + // change from OFF to ON / OFF to ON boolean isEnableToStartCollector = isExtraInfoCollectorEnabled() && isExtraInfoCollectorSupported(); + if (current == DevicePowerState.DV_POWER_ON) { currentPeriodSeconds = pollingPeriodOnSeconds; + // if extendedInfo collector is enabled, then force do start to prevent previous stop if (isEnableToStartCollector) { startExtraInfoCollectorPolling(); } } else { currentPeriodSeconds = pollingPeriodOffSeconds; + // if it's configured to stop extra-info collection on PowerOff, then stop the job if (!pollExtraInfoOnPowerOff) { stopExtraInfoCollectorPolling(); @@ -513,8 +517,12 @@ private void handlePowerChange(@Nullable DevicePowerState previous, DevicePowerS startExtraInfoCollectorPolling(); } } + // restart thing state polling for the new poolingPeriod configuration - stopThingStatePolling(); + if (pollingPeriodOffSeconds != pollingPeriodOnSeconds) { + stopThingStatePolling(); + } + startThingStatePolling(); } From 2e3eb9aa229e57e3dd0f8173258e174bb3e2e921 Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Mon, 3 Jul 2023 23:13:51 -0300 Subject: [PATCH 009/130] [lgthinq][fix] fixed dispose to stop all jobs Signed-off-by: nemerdaud --- .../handler/LGThinQAbstractDeviceHandler.java | 21 ++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 8d002fbe82a8d..6522007fa299c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -171,6 +171,7 @@ protected void stopCommandExecutorQueueJob() { if (commandExecutorQueueJob != null) { commandExecutorQueueJob.cancel(true); } + commandExecutorQueueJob = null; } protected void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { @@ -740,19 +741,15 @@ public void dispose() { account.unRegistryListenerThing(this); } - if (thingStatePollingJob != null) { - thingStatePollingJob.cancel(true); - stopThingStatePolling(); - stopExtraInfoCollectorPolling(); - stopCommandExecutorQueueJob(); - try { - if (LGAPIVerion.V1_0.equals(getCapabilities().getDeviceVersion())) { - stopDeviceV1Monitor(getDeviceId()); - } - } catch (Exception e) { - logger.warn("Can't stop active monitor. It's can be normally ignored. Cause:{}", e.getMessage()); + stopThingStatePolling(); + stopExtraInfoCollectorPolling(); + stopCommandExecutorQueueJob(); + try { + if (LGAPIVerion.V1_0.equals(getCapabilities().getDeviceVersion())) { + stopDeviceV1Monitor(getDeviceId()); } - thingStatePollingJob = null; + } catch (Exception e) { + logger.warn("Can't stop active monitor. It's can be normally ignored. Cause:{}", e.getMessage()); } } From f99a7a8ebfcb3e43bc2e570fdda2454ebec531ac Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Mon, 17 Jul 2023 22:54:14 -0300 Subject: [PATCH 010/130] [lgthinq][feat] replaced external apache httpclient with embedded jetty Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/pom.xml | 34 - .../internal/LGThinQHandlerFactory.java | 25 +- .../internal/api/OauthLgEmpAuthenticator.java | 28 +- .../lgthinq/internal/api/RestUtils.java | 117 ++-- .../lgthinq/internal/api/TokenManager.java | 13 +- .../discovery/LGThinqDiscoveryService.java | 50 +- .../handler/LGThinQAirConditionerHandler.java | 606 +++++++++--------- .../handler/LGThinQBridgeHandler.java | 22 +- .../handler/LGThinQFridgeHandler.java | 10 +- .../handler/LGThinQWasherDryerHandler.java | 7 +- .../LGThinQACApiV1ClientServiceImpl.java | 16 +- .../LGThinQACApiV2ClientServiceImpl.java | 20 +- .../LGThinQAbstractApiClientService.java | 27 +- .../LGThinQAbstractApiV1ClientService.java | 7 +- .../LGThinQAbstractApiV2ClientService.java | 7 +- .../LGThinQApiClientServiceFactory.java | 98 +++ .../LGThinQDRApiV2ClientServiceImpl.java | 15 +- .../LGThinQFridgeApiV1ClientServiceImpl.java | 15 +- .../LGThinQFridgeApiV2ClientServiceImpl.java | 15 +- .../LGThinQWMApiV1ClientServiceImpl.java | 14 +- .../LGThinQWMApiV2ClientServiceImpl.java | 15 +- .../lgthinq/handler/LGThinqBridgeTests.java | 16 +- 22 files changed, 570 insertions(+), 607 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml index 7ed34afa28691..2a920da8f314c 100644 --- a/bundles/org.openhab.binding.lgthinq/pom.xml +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -12,48 +12,14 @@ org.openhab.binding.lgthinq openHAB Add-ons :: Bundles :: LG Thinq Binding - - !net.sf.ehcache.*,!net.spy.* - - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - compile - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - compile - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - compile - - - org.apache.httpcomponents - httpclient-osgi - 4.5.13 - compile - - - org.apache.httpcomponents - httpcore-osgi - 4.4.10 - compile - com.github.tomakehurst wiremock-jre8 2.32.0 test - diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index 92841da18bd90..5d6997f723dec 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -22,6 +22,7 @@ import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.core.config.core.Configuration; +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; @@ -47,6 +48,9 @@ public class LGThinQHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(LGThinQHandlerFactory.class); + + private HttpClientFactory httpClientFactory; + private final LGThinQStateDescriptionProvider stateDescriptionProvider; @Nullable @@ -59,6 +63,13 @@ public class LGThinQHandlerFactory extends BaseThingHandlerFactory { @Reference protected ItemChannelLinkRegistry itemChannelLinkRegistry; + @Activate + public LGThinQHandlerFactory(final @Reference LGThinQStateDescriptionProvider stateDescriptionProvider, + @Reference final HttpClientFactory httpClientFactory) { + this.stateDescriptionProvider = stateDescriptionProvider; + this.httpClientFactory = httpClientFactory; + } + @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { return LGThinQBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); @@ -69,20 +80,20 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID) || THING_TYPE_HEAT_PUMP.equals(thingTypeUID)) { return new LGThinQAirConditionerHandler(thing, stateDescriptionProvider, - Objects.requireNonNull(itemChannelLinkRegistry)); + Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - return new LGThinQBridgeHandler((Bridge) thing); + return new LGThinQBridgeHandler((Bridge) thing, httpClientFactory); } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID) || THING_TYPE_WASHING_TOWER.equals(thingTypeUID)) { return new LGThinQWasherDryerHandler(thing, stateDescriptionProvider, Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), - Objects.requireNonNull(itemChannelLinkRegistry)); + Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } else if (THING_TYPE_DRYER.equals(thingTypeUID) || THING_TYPE_DRYER_TOWER.equals(thingTypeUID)) { return new LGThinQWasherDryerHandler(thing, stateDescriptionProvider, Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), - Objects.requireNonNull(itemChannelLinkRegistry)); + Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } else if (THING_TYPE_FRIDGE.equals(thingTypeUID)) { return new LGThinQFridgeHandler(thing, stateDescriptionProvider, - Objects.requireNonNull(itemChannelLinkRegistry)); + Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; @@ -105,8 +116,4 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return null; } - @Activate - public LGThinQHandlerFactory(final @Reference LGThinQStateDescriptionProvider stateDescriptionProvider) { - this.stateDescriptionProvider = stateDescriptionProvider; - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java index 0aa92c651c96f..9c48a31c60370 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java @@ -25,6 +25,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; @@ -41,21 +42,20 @@ */ @NonNullByDefault public class OauthLgEmpAuthenticator { - private static final Logger logger = LoggerFactory.getLogger(OauthLgEmpAuthenticator.class); - private static final OauthLgEmpAuthenticator instance; + + private static final Logger logger = LoggerFactory.getLogger(OauthLgEmpAuthenticator.class); private static final Map oauthSearchKeyQueryParams = new LinkedHashMap<>(); private static final ObjectMapper objectMapper = new ObjectMapper(); + static { - instance = new OauthLgEmpAuthenticator(); oauthSearchKeyQueryParams.put("key_name", "OAUTH_SECRETKEY"); oauthSearchKeyQueryParams.put("sever_type", "OP"); } - public static OauthLgEmpAuthenticator getInstance() { - return instance; - } + private HttpClient httpClient; - private OauthLgEmpAuthenticator() { + public OauthLgEmpAuthenticator(HttpClient httpClient) { + this.httpClient = httpClient; } static class PreLoginResult { @@ -158,7 +158,7 @@ public LGThinqGateway discoverGatewayConfiguration(String gwUrl, String language String alternativeEmpServer) throws IOException { Map header = getGatewayRestHeader(language, country); RestResult result; - result = RestUtils.getCall(gwUrl, header, null); + result = RestUtils.getCall(httpClient, gwUrl, header, null); if (result.getStatusCode() != 200) { throw new IllegalStateException( @@ -184,7 +184,7 @@ public PreLoginResult preLoginUser(LGThinqGateway gw, String username, String pa Map formData = Map.of("user_auth2", encPwd, "log_param", String.format( "login request / user_id : %s / " + "third_party : null / svc_list : SVC202,SVC710 / 3rd_service : ", username)); - RestResult resp = RestUtils.postCall(preLoginUrl, headers, formData); + RestResult resp = RestUtils.postCall(httpClient, preLoginUrl, headers, formData); if (resp == null) { logger.error("Error preLogin into account. Null data returned"); throw new IllegalStateException("Error login into account. Null data returned"); @@ -216,7 +216,7 @@ public LoginAccountResult loginUser(LGThinqGateway gw, PreLoginResult preLoginRe // OAuth String loginUrl = gw.getEmpBaseUri() + V2_SESSION_LOGIN_PATH + URLEncoder.encode(preLoginResult.getUsername(), StandardCharsets.UTF_8); - RestResult resp = RestUtils.postCall(loginUrl, headers, formData); + RestResult resp = RestUtils.postCall(httpClient, loginUrl, headers, formData); if (resp == null) { logger.error("Error login into account. Null data returned"); throw new IllegalStateException("Error loggin into acccount. Null data returned"); @@ -251,7 +251,7 @@ public TokenResult getToken(LGThinqGateway gw, LoginAccountResult accountResult) // 3 - get secret key from emp signature String empSearchKeyUrl = gw.getLoginBaseUri() + OAUTH_SEARCH_KEY_PATH; - RestResult resp = RestUtils.getCall(empSearchKeyUrl, null, oauthSearchKeyQueryParams); + RestResult resp = RestUtils.getCall(httpClient, empSearchKeyUrl, null, oauthSearchKeyQueryParams); if (resp.getStatusCode() != 200) { logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); throw new IllegalStateException(String.format("Error loggin into acccount:%s", resp.getJsonResponse())); @@ -289,7 +289,7 @@ public TokenResult getToken(LGThinqGateway gw, LoginAccountResult accountResult) "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.44"); logger.debug("===> Localized timestamp used: [{}]", timestamp); logger.debug("===> signature created: [{}]", new String(oauthSig)); - resp = RestUtils.postCall(gw.getTokenSessionEmpUrl(), oauthEmpHeaders, empData); + resp = RestUtils.postCall(httpClient, gw.getTokenSessionEmpUrl(), oauthEmpHeaders, empData); return handleTokenResult(resp); } @@ -302,7 +302,7 @@ public UserInfo getUserInfo(TokenResult token) throws IOException { String.format("Bearer %s", token.getAccessToken()), "X-Lge-Svccode", SVC_CODE, "X-Application-Key", APPLICATION_KEY, "lgemp-x-app-key", CLIENT_ID, "X-Device-Type", "M01", "X-Device-Platform", "ADR", "x-lge-oauth-date", timestamp, "x-lge-oauth-signature", new String(oauthSig)); - RestResult resp = RestUtils.getCall(oauthUrl, headers, null); + RestResult resp = RestUtils.getCall(httpClient, oauthUrl, headers, null); return handleAccountInfoResult(resp); } @@ -346,7 +346,7 @@ public TokenResult doRefreshToken(TokenResult currentToken) throws IOException, Map headers = Map.of("x-lge-appkey", CLIENT_ID, "x-lge-oauth-signature", new String(oauthSig), "x-lge-oauth-date", timestamp, "Accept", "application/json"); - RestResult resp = RestUtils.postCall(oauthUrl, headers, formData); + RestResult resp = RestUtils.postCall(httpClient, oauthUrl, headers, formData); return handleRefreshTokenResult(resp, currentToken); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java index d1073ed2baec8..c9a8b4ced9d24 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java @@ -20,26 +20,26 @@ import java.security.InvalidKeyException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; -import java.util.*; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.ws.rs.core.UriBuilder; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClientBuilder; -import org.apache.http.message.BasicNameValuePair; -import org.apache.http.util.EntityUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +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.util.FormContentProvider; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.util.Fields; +import org.openhab.core.i18n.CommunicationException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,14 +50,9 @@ */ @NonNullByDefault public class RestUtils { + private static final Logger logger = LoggerFactory.getLogger(RestUtils.class); private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - private static final RequestConfig requestConfig; - private static final int DEFAULT_TIMEOUT_MILLISECONDS = 10 * 1000; - static { - requestConfig = RequestConfig.custom().setConnectTimeout(DEFAULT_TIMEOUT_MILLISECONDS) - .setConnectionRequestTimeout(DEFAULT_TIMEOUT_MILLISECONDS).setSocketTimeout(DEFAULT_TIMEOUT_MILLISECONDS).build(); - } public static String getPreLoginEncPwd(String pwdToEnc) { MessageDigest digest; @@ -96,36 +91,31 @@ public static byte[] getTokenSignature(String authUrl, String secretKey, Map 0) ? reqUri.getPath() + "?" + reqUri.getRawQuery() : reqUri.getPath(); + String signUrl = empData.size() > 0 ? reqUri.getPath() + "?" + reqUri.getRawQuery() : reqUri.getPath(); String messageToSign = String.format("%s\n%s", signUrl, timestamp); return getOauth2Sig(messageToSign, secretKey); } - public static RestResult getCall(String encodedUrl, @Nullable Map headers, + public static RestResult getCall(HttpClient httpClient, String encodedUrl, @Nullable Map headers, @Nullable Map params) throws IOException { - UriBuilder builder = UriBuilder.fromUri(encodedUrl); - if (params != null) { - params.forEach(builder::queryParam); - } - URI encodedUri = builder.build(); - try (CloseableHttpClient client = HttpClientBuilder.create().build()) { - HttpGet request = new HttpGet(encodedUri); - if (headers != null) { - headers.forEach(request::setHeader); - } - HttpResponse resp = client.execute(request); - return new RestResult(EntityUtils.toString(resp.getEntity(), "UTF-8"), - resp.getStatusLine().getStatusCode()); + Request request = httpClient.newRequest(encodedUrl).method("GET"); + headers.forEach(request::header); + ContentResponse response; + try { + response = request.send(); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.error("Exception occurred during GET execution: {}", e.getMessage(), e); + throw new CommunicationException(e); } + return new RestResult(response.getContentAsString(), response.getStatus()); } @Nullable - public static RestResult postCall(String encodedUrl, Map headers, String jsonData) + public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, String jsonData) throws IOException { try { - StringEntity entity = new StringEntity(jsonData); - return postCall(encodedUrl, headers, entity); + return postCall(httpClient, encodedUrl, headers, new StringContentProvider(jsonData)); } catch (UnsupportedEncodingException e) { logger.error( "Unexpected error. Character encoding from json informed not supported by this platform. Payload:{}", @@ -136,19 +126,16 @@ public static RestResult postCall(String encodedUrl, Map headers } @Nullable - public static RestResult postCall(String encodedUrl, Map headers, Map formParams) + public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, Map formParams) throws IOException { - List pairs = new ArrayList<>(); - - formParams.forEach((k, v) -> pairs.add(new BasicNameValuePair(k, v))); - + Fields fields = new Fields(); + formParams.forEach(fields::put); try { - UrlEncodedFormEntity fe = new UrlEncodedFormEntity(pairs); - return postCall(encodedUrl, headers, fe); + return postCall(httpClient, encodedUrl, headers, new FormContentProvider(fields)); } catch (UnsupportedEncodingException e) { logger.error( "Unexpected error. Character encoding received from Form Parameters not supported by this platform. Form Parameters:{}", - pairs, e); + formParams, e); throw new IllegalStateException( "Unexpected error. Character encoding received from Form Parameters not supported by this platform.", e); @@ -156,27 +143,19 @@ public static RestResult postCall(String encodedUrl, Map headers } @Nullable - private static RestResult postCall(String encodedUrl, Map headers, HttpEntity entity) + private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, ContentProvider contentProvider) throws IOException { - try (CloseableHttpClient client = HttpClientBuilder.create().setDefaultRequestConfig(requestConfig).build()) { - HttpPost request = new HttpPost(encodedUrl); - headers.forEach(request::setHeader); - request.setEntity(entity); - TimerTask task = new TimerTask() { - @Override - public void run() { - if (request.expectContinue()) - request.abort(); - } - }; - new Timer(true).schedule(task, DEFAULT_TIMEOUT_MILLISECONDS); - HttpResponse resp = client.execute(request); - if (request.isAborted()) { - logger.warn("POST to LG API was aborted due to timeout waiting for connection or data"); - } - return new RestResult(EntityUtils.toString(resp.getEntity(), "UTF-8"), - resp.getStatusLine().getStatusCode()); - } catch (java.net.SocketTimeoutException e) { + + try { + Request request = httpClient.newRequest(encodedUrl) + .method("POST") + .content(contentProvider) + .timeout(10, TimeUnit.SECONDS); + headers.forEach(request::header); + ContentResponse response = request.content(contentProvider).timeout(10, TimeUnit.SECONDS) + .send(); + return new RestResult(response.getContentAsString(), response.getStatus()); + } catch (TimeoutException e) { if (logger.isDebugEnabled()) { logger.warn("Timeout reading post call result from LG API", e); } else { @@ -185,6 +164,14 @@ public void run() { // In SocketTimeout cases I'm considering that I have no response on time. Then, I return null data // forcing caller to retry. return null; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.error("InterruptedException occurred during POST execution: {}", e.getMessage(), e); + throw new CommunicationException(e); + } catch (ExecutionException e) { + logger.error("ExecutionException occurred during POST execution: {}", e.getMessage(), e); + throw new CommunicationException(e); } } + } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java index 7b0968b98a25e..8bc8ec175b42b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java @@ -25,6 +25,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.errors.*; import com.fasterxml.jackson.databind.ObjectMapper; @@ -40,17 +41,9 @@ public class TokenManager { private final OauthLgEmpAuthenticator oAuthAuthenticator; private final ObjectMapper objectMapper = new ObjectMapper(); private final Map tokenCached = new ConcurrentHashMap<>(); - private static final TokenManager instance; - static { - instance = new TokenManager(); - } - - private TokenManager() { - oAuthAuthenticator = OauthLgEmpAuthenticator.getInstance(); - } - public static TokenManager getInstance() { - return instance; + public TokenManager(HttpClient httpClient) { + oAuthAuthenticator = new OauthLgEmpAuthenticator(httpClient); } public boolean isTokenExpired(TokenResult token) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index 573c5bd27cebb..a1263f835acdf 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -15,21 +15,17 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; -import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.Objects; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.handler.LGThinQBridgeHandler; -import org.openhab.binding.lgthinq.lgservices.LGThinQAbstractApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory.LGThinQGeneralApiClientService; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.discovery.AbstractDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; @@ -41,8 +37,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.databind.node.ObjectNode; - /** * The {@link LGThinqDiscoveryService} * @@ -54,42 +48,10 @@ public class LGThinqDiscoveryService extends AbstractDiscoveryService implements private final Logger logger = LoggerFactory.getLogger(LGThinqDiscoveryService.class); private @Nullable LGThinQBridgeHandler bridgeHandler; private @Nullable ThingUID bridgeHandlerUID; - private final LGThinQApiClientService lgApiClientService; + private @Nullable LGThinQGeneralApiClientService lgApiClientService; public LGThinqDiscoveryService() { super(SUPPORTED_THING_TYPES, SEARCH_TIME); - lgApiClientService = new LGThinQAbstractApiClientService<>(AbstractCapability.class, - AbstractSnapshotDefinition.class) { - @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException { - } - - @Override - protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, - String command, String keyName, String value) throws Exception { - throw new UnsupportedOperationException("Not to use"); - } - - @Override - protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, - String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) - throws Exception { - throw new UnsupportedOperationException("Not to use"); - } - - @Override - protected Map handleGenericErrorResult(@Nullable RestResult resp) - throws LGThinqApiException { - throw new UnsupportedOperationException("Not to use"); - } - - @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { - throw new UnsupportedOperationException("Not to use"); - } - }; } @Override @@ -101,6 +63,7 @@ public void setThingHandler(@Nullable ThingHandler handler) { if (handler instanceof LGThinQBridgeHandler) { bridgeHandler = (LGThinQBridgeHandler) handler; bridgeHandlerUID = handler.getThing().getUID(); + lgApiClientService = LGThinQApiClientServiceFactory.newGeneralApiClientService(bridgeHandler.getHttpClientFactory()); } } @@ -143,9 +106,6 @@ public void addLgDeviceDiscovery(LGDevice device) { logger.debug("Discovered unsupported LG device of type '{}'({}) and model '{}' with id {}", device.getDeviceType(), device.getDeviceTypeId(), modelId, device.getDeviceId()); return; - } catch (IOException e) { - logger.error("Error getting device capabilities", e); - return; } Map properties = new HashMap<>(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index d9abe85b4e41c..7674f115b2b28 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -25,9 +25,8 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.LGThinQACApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQACApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinQACApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; @@ -35,11 +34,11 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ExtendedDeviceInfo; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.unit.Units; -import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -63,329 +62,326 @@ @NonNullByDefault public class LGThinQAirConditionerHandler extends LGThinQAbstractDeviceHandler { - public final ChannelGroupUID channelGroupExtendedInfoUID; - public final ChannelGroupUID channelGroupDashboardUID; - private final ChannelUID powerChannelUID; - private final ChannelUID opModeChannelUID; - private final ChannelUID fanSpeedChannelUID; - private final ChannelUID targetTempChannelUID; - private final ChannelUID currTempChannelUID; - private final ChannelUID jetModeChannelUID; - private final ChannelUID airCleanChannelUID; - private final ChannelUID autoDryChannelUID; - private final ChannelUID energySavingChannelUID; - private final ChannelUID extendedInfoCollectorChannelUID; - private final ChannelUID currentPowerEnergyChannelUID; - private final ChannelUID remainingFilterChannelUID; + public final ChannelGroupUID channelGroupExtendedInfoUID; + public final ChannelGroupUID channelGroupDashboardUID; + private final ChannelUID powerChannelUID; + private final ChannelUID opModeChannelUID; + private final ChannelUID fanSpeedChannelUID; + private final ChannelUID targetTempChannelUID; + private final ChannelUID currTempChannelUID; + private final ChannelUID jetModeChannelUID; + private final ChannelUID airCleanChannelUID; + private final ChannelUID autoDryChannelUID; + private final ChannelUID energySavingChannelUID; + private final ChannelUID extendedInfoCollectorChannelUID; + private final ChannelUID currentPowerEnergyChannelUID; + private final ChannelUID remainingFilterChannelUID; - private final ObjectMapper mapper = new ObjectMapper(); - private final Logger logger = LoggerFactory.getLogger(LGThinQAirConditionerHandler.class); - @NonNullByDefault - private final LGThinQACApiClientService lgThinqACApiClientService; + private final ObjectMapper mapper = new ObjectMapper(); + private final Logger logger = LoggerFactory.getLogger(LGThinQAirConditionerHandler.class); + @NonNullByDefault + private final LGThinQACApiClientService lgThinqACApiClientService; - public LGThinQAirConditionerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, - ItemChannelLinkRegistry itemChannelLinkRegistry) { - super(thing, stateDescriptionProvider, itemChannelLinkRegistry); - lgThinqACApiClientService = lgPlatformType.equals(PLATFORM_TYPE_V1) - ? LGThinQACApiV1ClientServiceImpl.getInstance() - : LGThinQACApiV2ClientServiceImpl.getInstance(); - channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); - channelGroupExtendedInfoUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_EXTENDED_INFO_GRP_ID); + public LGThinQAirConditionerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, + ItemChannelLinkRegistry itemChannelLinkRegistry, HttpClientFactory httpClientFactory) { + super(thing, stateDescriptionProvider, itemChannelLinkRegistry); + lgThinqACApiClientService = LGThinQApiClientServiceFactory.newACApiClientService(lgPlatformType, httpClientFactory); + channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); + channelGroupExtendedInfoUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_EXTENDED_INFO_GRP_ID); - opModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_MOD_OP_ID); - targetTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_TARGET_TEMP_ID); - currTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_CURRENT_TEMP_ID); - fanSpeedChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FAN_SPEED_ID); - jetModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_COOL_JET_ID); - airCleanChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AIR_CLEAN_ID); - autoDryChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AUTO_DRY_ID); - energySavingChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_ENERGY_SAVING_ID); - powerChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_POWER_ID); - extendedInfoCollectorChannelUID = new ChannelUID(channelGroupExtendedInfoUID, - CHANNEL_EXTENDED_INFO_COLLECTOR_ID); - currentPowerEnergyChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_CURRENT_POWER_ID); - remainingFilterChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_REMAINING_FILTER_ID); - } + opModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_MOD_OP_ID); + targetTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_TARGET_TEMP_ID); + currTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_CURRENT_TEMP_ID); + fanSpeedChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FAN_SPEED_ID); + jetModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_COOL_JET_ID); + airCleanChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AIR_CLEAN_ID); + autoDryChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AUTO_DRY_ID); + energySavingChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_ENERGY_SAVING_ID); + powerChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_POWER_ID); + extendedInfoCollectorChannelUID = new ChannelUID(channelGroupExtendedInfoUID, + CHANNEL_EXTENDED_INFO_COLLECTOR_ID); + currentPowerEnergyChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_CURRENT_POWER_ID); + remainingFilterChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_REMAINING_FILTER_ID); + } - @Override - public void initialize() { - super.initialize(); - try { - ACCapability cap = getCapabilities(); - if (!isExtraInfoCollectorSupported()) { - ThingBuilder builder = editThing() - .withoutChannels(this.getThing().getChannelsOfGroup(channelGroupExtendedInfoUID.getId())); - updateThing(builder.build()); - } else if (!cap.isEnergyMonitorAvailable()) { - ThingBuilder builder = editThing().withoutChannel(currentPowerEnergyChannelUID); - updateThing(builder.build()); - } else if (!cap.isFilterMonitorAvailable()) { - ThingBuilder builder = editThing().withoutChannel(remainingFilterChannelUID); - updateThing(builder.build()); - } - } catch (LGThinqApiException e) { - logger.warn("Error getting capability of the device:{}", getDeviceId()); - } - } + @Override + public void initialize() { + super.initialize(); + try { + ACCapability cap = getCapabilities(); + if (!isExtraInfoCollectorSupported()) { + ThingBuilder builder = editThing() + .withoutChannels(this.getThing().getChannelsOfGroup(channelGroupExtendedInfoUID.getId())); + updateThing(builder.build()); + } else if (!cap.isEnergyMonitorAvailable()) { + ThingBuilder builder = editThing().withoutChannel(currentPowerEnergyChannelUID); + updateThing(builder.build()); + } else if (!cap.isFilterMonitorAvailable()) { + ThingBuilder builder = editThing().withoutChannel(remainingFilterChannelUID); + updateThing(builder.build()); + } + } catch (LGThinqApiException e) { + logger.warn("Error getting capability of the device:{}", getDeviceId()); + } + } - @Override - protected void updateDeviceChannels(ACCanonicalSnapshot shot) { - logger.debug("Calling updateDeviceChannel for device:{}", getDeviceAlias()); - updateState(powerChannelUID, - DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF); - updateState(opModeChannelUID, new DecimalType(BigDecimal.valueOf(shot.getOperationMode()))); - updateState(fanSpeedChannelUID, new DecimalType(BigDecimal.valueOf(shot.getAirWindStrength()))); - updateState(currTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getCurrentTemperature()))); - updateState(targetTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getTargetTemperature()))); - try { - ACCapability acCap = getCapabilities(); - if (getThing().getChannel(jetModeChannelUID) != null) { - Double commandCoolJetOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); - updateState(jetModeChannelUID, - commandCoolJetOn.equals(shot.getCoolJetMode()) ? OnOffType.ON : OnOffType.OFF); - } - if (getThing().getChannel(airCleanChannelUID) != null) { - Double commandAirCleanOn = Double.valueOf(acCap.getAirCleanModeCommandOn()); - updateState(airCleanChannelUID, - commandAirCleanOn.equals(shot.getAirCleanMode()) ? OnOffType.ON : OnOffType.OFF); - } - if (getThing().getChannel(energySavingChannelUID) != null) { - Double energySavingOn = Double.valueOf(acCap.getEnergySavingModeCommandOn()); - updateState(energySavingChannelUID, - energySavingOn.equals(shot.getEnergySavingMode()) ? OnOffType.ON : OnOffType.OFF); - } - if (getThing().getChannel(autoDryChannelUID) != null) { - Double autoDryOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); - updateState(autoDryChannelUID, autoDryOn.equals(shot.getAutoDryMode()) ? OnOffType.ON : OnOffType.OFF); - } + @Override + protected void updateDeviceChannels(ACCanonicalSnapshot shot) { + logger.debug("Calling updateDeviceChannel for device:{}", getDeviceAlias()); + updateState(powerChannelUID, + DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF); + updateState(opModeChannelUID, new DecimalType(BigDecimal.valueOf(shot.getOperationMode()))); + updateState(fanSpeedChannelUID, new DecimalType(BigDecimal.valueOf(shot.getAirWindStrength()))); + updateState(currTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getCurrentTemperature()))); + updateState(targetTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getTargetTemperature()))); + try { + ACCapability acCap = getCapabilities(); + if (getThing().getChannel(jetModeChannelUID) != null) { + Double commandCoolJetOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); + updateState(jetModeChannelUID, + commandCoolJetOn.equals(shot.getCoolJetMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(airCleanChannelUID) != null) { + Double commandAirCleanOn = Double.valueOf(acCap.getAirCleanModeCommandOn()); + updateState(airCleanChannelUID, + commandAirCleanOn.equals(shot.getAirCleanMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(energySavingChannelUID) != null) { + Double energySavingOn = Double.valueOf(acCap.getEnergySavingModeCommandOn()); + updateState(energySavingChannelUID, + energySavingOn.equals(shot.getEnergySavingMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(autoDryChannelUID) != null) { + Double autoDryOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); + updateState(autoDryChannelUID, autoDryOn.equals(shot.getAutoDryMode()) ? OnOffType.ON : OnOffType.OFF); + } - } catch (LGThinqApiException e) { - logger.error("Unexpected Error gettinf ACCapability Capabilities", e); - } catch (NumberFormatException e) { - logger.warn("command value for capability is not numeric.", e); - } - } + } catch (LGThinqApiException e) { + logger.error("Unexpected Error gettinf ACCapability Capabilities", e); + } catch (NumberFormatException e) { + logger.warn("command value for capability is not numeric.", e); + } + } - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - ACCapability acCap = getCapabilities(); - if (getThing().getChannel(jetModeChannelUID) == null && acCap.isJetModeAvailable()) { - createDynSwitchChannel(CHANNEL_COOL_JET_ID, jetModeChannelUID); - } - if (getThing().getChannel(autoDryChannelUID) == null && acCap.isAutoDryModeAvailable()) { - createDynSwitchChannel(CHANNEL_AUTO_DRY_ID, autoDryChannelUID); - } - if (getThing().getChannel(airCleanChannelUID) == null && acCap.isAirCleanAvailable()) { - createDynSwitchChannel(CHANNEL_AIR_CLEAN_ID, airCleanChannelUID); - } - if (getThing().getChannel(energySavingChannelUID) == null && acCap.isEnergySavingAvailable()) { - createDynSwitchChannel(CHANNEL_ENERGY_SAVING_ID, energySavingChannelUID); - } - if (!acCap.getFanSpeed().isEmpty()) { - List options = new ArrayList<>(); - acCap.getFanSpeed() - .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); - stateDescriptionProvider.setStateOptions(fanSpeedChannelUID, options); - } - if (!acCap.getOpMode().isEmpty()) { - List options = new ArrayList<>(); - acCap.getOpMode().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_OP_MODE.get(v))))); - stateDescriptionProvider.setStateOptions(opModeChannelUID, options); - } - } + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + ACCapability acCap = getCapabilities(); + if (getThing().getChannel(jetModeChannelUID) == null && acCap.isJetModeAvailable()) { + createDynSwitchChannel(CHANNEL_COOL_JET_ID, jetModeChannelUID); + } + if (getThing().getChannel(autoDryChannelUID) == null && acCap.isAutoDryModeAvailable()) { + createDynSwitchChannel(CHANNEL_AUTO_DRY_ID, autoDryChannelUID); + } + if (getThing().getChannel(airCleanChannelUID) == null && acCap.isAirCleanAvailable()) { + createDynSwitchChannel(CHANNEL_AIR_CLEAN_ID, airCleanChannelUID); + } + if (getThing().getChannel(energySavingChannelUID) == null && acCap.isEnergySavingAvailable()) { + createDynSwitchChannel(CHANNEL_ENERGY_SAVING_ID, energySavingChannelUID); + } + if (!acCap.getFanSpeed().isEmpty()) { + List options = new ArrayList<>(); + acCap.getFanSpeed() + .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); + stateDescriptionProvider.setStateOptions(fanSpeedChannelUID, options); + } + if (!acCap.getOpMode().isEmpty()) { + List options = new ArrayList<>(); + acCap.getOpMode().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_OP_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(opModeChannelUID, options); + } + } - @Override - public LGThinQApiClientService getLgThinQAPIClientService() { - return lgThinqACApiClientService; - } + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqACApiClientService; + } - @Override - protected Logger getLogger() { - return logger; - } + @Override + protected Logger getLogger() { + return logger; + } - protected DeviceTypes getDeviceType() { - if (THING_TYPE_HEAT_PUMP.equals(getThing().getThingTypeUID())) { - return DeviceTypes.HEAT_PUMP; - } else if (THING_TYPE_AIR_CONDITIONER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.AIR_CONDITIONER; - } else { - throw new IllegalArgumentException( - "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for AirConditioner/HeatPump"); - } - } + protected DeviceTypes getDeviceType() { + if (THING_TYPE_HEAT_PUMP.equals(getThing().getThingTypeUID())) { + return DeviceTypes.HEAT_PUMP; + } else if (THING_TYPE_AIR_CONDITIONER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.AIR_CONDITIONER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for AirConditioner/HeatPump"); + } + } - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } - @Override - public void onDeviceDisconnected() { - // TODO - HANDLE IT, Think if it's needed - } + @Override + public void onDeviceDisconnected() { + // TODO - HANDLE IT, Think if it's needed + } - protected void resetExtraInfoChannels() { - updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); - updateState(remainingFilterChannelUID, UnDefType.UNDEF); - } + protected void resetExtraInfoChannels() { + updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + updateState(remainingFilterChannelUID, UnDefType.UNDEF); + } - protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { - Command command = params.command; - switch (getSimpleChannelUID(params.channelUID)) { - case CHANNEL_MOD_OP_ID: { - if (params.command instanceof DecimalType) { - lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), - ((DecimalType) command).intValue()); - } else { - logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); - } - break; - } - case CHANNEL_FAN_SPEED_ID: { - if (command instanceof DecimalType) { - lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), - ((DecimalType) command).intValue()); - } else { - logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); - } - break; - } - case CHANNEL_POWER_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); - } else { - logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); - } - break; - } - case CHANNEL_COOL_JET_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnCoolJetMode(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? getCapabilities().getCoolJetModeCommandOn() - : getCapabilities().getCoolJetModeCommandOff()); - } else { - logger.warn("Received command different of OnOffType in CoolJet Mode Channel. Ignoring"); - } - break; - } - case CHANNEL_AIR_CLEAN_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnAirCleanMode(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? getCapabilities().getAirCleanModeCommandOn() - : getCapabilities().getAirCleanModeCommandOff()); - } else { - logger.warn("Received command different of OnOffType in AirClean Mode Channel. Ignoring"); - } - break; - } - case CHANNEL_AUTO_DRY_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnAutoDryMode(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? getCapabilities().getAutoDryModeCommandOn() - : getCapabilities().getAutoDryModeCommandOff()); - } else { - logger.warn("Received command different of OnOffType in AutoDry Mode Channel. Ignoring"); - } - break; - } - case CHANNEL_ENERGY_SAVING_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnEnergySavingMode(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? getCapabilities().getEnergySavingModeCommandOn() - : getCapabilities().getEnergySavingModeCommandOff()); - } else { - logger.warn("Received command different of OnOffType in EvergySaving Mode Channel. Ignoring"); - } - break; - } - case CHANNEL_TARGET_TEMP_ID: { - double targetTemp; - if (command instanceof DecimalType) { - targetTemp = ((DecimalType) command).doubleValue(); - } else if (command instanceof QuantityType) { - targetTemp = ((QuantityType) command).doubleValue(); - } else { - logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); - break; - } - lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), - ACTargetTmp.statusOf(targetTemp)); - break; - } - case CHANNEL_EXTENDED_INFO_COLLECTOR_ID: { - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); - } - } - } - // =========== Energy Colletor Implementation ============= + protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + switch (getSimpleChannelUID(params.channelUID)) { + case CHANNEL_MOD_OP_ID: { + if (params.command instanceof DecimalType) { + lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); + } + break; + } + case CHANNEL_FAN_SPEED_ID: { + if (command instanceof DecimalType) { + lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); + } + break; + } + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + case CHANNEL_COOL_JET_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnCoolJetMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getCoolJetModeCommandOn() + : getCapabilities().getCoolJetModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in CoolJet Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_AIR_CLEAN_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnAirCleanMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getAirCleanModeCommandOn() + : getCapabilities().getAirCleanModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in AirClean Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_AUTO_DRY_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnAutoDryMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getAutoDryModeCommandOn() + : getCapabilities().getAutoDryModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in AutoDry Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_ENERGY_SAVING_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnEnergySavingMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getEnergySavingModeCommandOn() + : getCapabilities().getEnergySavingModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in EvergySaving Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_TARGET_TEMP_ID: { + double targetTemp; + if (command instanceof DecimalType) { + targetTemp = ((DecimalType) command).doubleValue(); + } else if (command instanceof QuantityType) { + targetTemp = ((QuantityType) command).doubleValue(); + } else { + logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); + break; + } + lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + ACTargetTmp.statusOf(targetTemp)); + break; + } + case CHANNEL_EXTENDED_INFO_COLLECTOR_ID: { + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } + // =========== Energy Colletor Implementation ============= - @Override - protected boolean isExtraInfoCollectorSupported() { - try { - return getCapabilities().isEnergyMonitorAvailable() || getCapabilities().isFilterMonitorAvailable(); - } catch (LGThinqApiException e) { - logger.warn("Can't get capabilities of the device: {}", getDeviceId()); - } - return false; - } + @Override + protected boolean isExtraInfoCollectorSupported() { + try { + return getCapabilities().isEnergyMonitorAvailable() || getCapabilities().isFilterMonitorAvailable(); + } catch (LGThinqApiException e) { + logger.warn("Can't get capabilities of the device: {}", getDeviceId()); + } + return false; + } - @Override - protected boolean isExtraInfoCollectorEnabled() { - return OnOffType.ON.toString().equals(getItemLinkedValue(extendedInfoCollectorChannelUID)); - } + @Override + protected boolean isExtraInfoCollectorEnabled() { + return OnOffType.ON.toString().equals(getItemLinkedValue(extendedInfoCollectorChannelUID)); + } - @Override - protected Map collectExtraInfoState() throws LGThinqException { - ExtendedDeviceInfo info = lgThinqACApiClientService.getExtendedDeviceInfo(getBridgeId(), getDeviceId()); - return mapper.convertValue(info, new TypeReference<>() { - }); - } + @Override + protected Map collectExtraInfoState() throws LGThinqException { + ExtendedDeviceInfo info = lgThinqACApiClientService.getExtendedDeviceInfo(getBridgeId(), getDeviceId()); + return mapper.convertValue(info, new TypeReference<>() { + }); + } - @Override - protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { - logger.debug("Calling updateExtraInfoStateChannels for device:{}", getDeviceAlias()); - String instantPowerConsumption = (String) energyStateAttributes.get(EXTENDED_ATTR_INSTANT_POWER); - String filterUsed = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_USED_TIME); - String filterTimelife = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE); - if (instantPowerConsumption == null) { - updateState(currentPowerEnergyChannelUID, UnDefType.NULL); - } else if (NumberUtils.isCreatable(instantPowerConsumption)) { - double ip = Double.parseDouble(instantPowerConsumption); - ip = ip / 1000; - updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.KILOWATT_HOUR)); - } else { - updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); - } + @Override + protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { + logger.debug("Calling updateExtraInfoStateChannels for device:{}", getDeviceAlias()); + String instantPowerConsumption = (String) energyStateAttributes.get(EXTENDED_ATTR_INSTANT_POWER); + String filterUsed = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_USED_TIME); + String filterTimelife = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE); + if (instantPowerConsumption == null) { + updateState(currentPowerEnergyChannelUID, UnDefType.NULL); + } else if (NumberUtils.isCreatable(instantPowerConsumption)) { + double ip = Double.parseDouble(instantPowerConsumption); + updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.WATT)); + } else { + updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + } - if (filterTimelife == null || filterUsed == null) { - updateState(remainingFilterChannelUID, UnDefType.NULL); - } else if (NumberUtils.isCreatable(filterTimelife) && NumberUtils.isCreatable(filterUsed)) { - double used = Double.parseDouble(filterUsed); - double max = Double.parseDouble(filterTimelife); + if (filterTimelife == null || filterUsed == null) { + updateState(remainingFilterChannelUID, UnDefType.NULL); + } else if (NumberUtils.isCreatable(filterTimelife) && NumberUtils.isCreatable(filterUsed)) { + double used = Double.parseDouble(filterUsed); + double max = Double.parseDouble(filterTimelife); double perc = (1 - ((double) used / max)) * 100; - updateState(remainingFilterChannelUID, new QuantityType<>(perc, Units.PERCENT)); - } else { - updateState(remainingFilterChannelUID, UnDefType.UNDEF); - } - } + updateState(remainingFilterChannelUID, new QuantityType<>(perc, Units.PERCENT)); + } else { + updateState(remainingFilterChannelUID, UnDefType.UNDEF); + } + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index 26ab1d6dc7cfd..8601cd09d3088 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -23,16 +23,18 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQBridgeConfiguration; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.LGThinQACApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory.LGThinQGeneralApiClientService; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.ThingStatus; @@ -68,16 +70,22 @@ public class LGThinQBridgeHandler extends ConfigStatusBridgeHandler implements L private LGThinQBridgeConfiguration lgthinqConfig; private TokenManager tokenManager; private LGThinqDiscoveryService discoveryService; - private LGThinQApiClientService lgApiClient; + private LGThinQGeneralApiClientService lgApiClient; private @Nullable Future initJob; private @Nullable ScheduledFuture devicePollingJob; + private @NonNull HttpClientFactory httpClientFactory; - public LGThinQBridgeHandler(Bridge bridge) { + public LGThinQBridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory) { super(bridge); - tokenManager = TokenManager.getInstance(); - lgApiClient = LGThinQACApiV1ClientServiceImpl.getInstance(); + this.httpClientFactory = httpClientFactory; + tokenManager = new TokenManager(httpClientFactory.getCommonHttpClient()); + lgApiClient = LGThinQApiClientServiceFactory.newGeneralApiClientService(httpClientFactory); lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); } + + public HttpClientFactory getHttpClientFactory() { + return httpClientFactory; + } final ReentrantLock pollingLock = new ReentrantLock(); @@ -168,7 +176,7 @@ public void registerDiscoveryListener(LGThinqDiscoveryService listener) { public Collection> getServices() { return Collections.singleton(LGThinqDiscoveryService.class); } - + @Override public void registryListenerThing(LGThinQAbstractDeviceHandler thing) { if (lGDeviceRegister.get(thing.getDeviceId()) == null) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index 3ae97e6c702c6..e63f4efb9124c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -26,13 +26,13 @@ import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQFridgeApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQFridgeApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinQFridgeApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; @@ -69,11 +69,9 @@ public class LGThinQFridgeHandler extends LGThinQAbstractDeviceHandler thingStatePollingJob; public LGThinQFridgeHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, - ItemChannelLinkRegistry itemChannelLinkRegistry) { + ItemChannelLinkRegistry itemChannelLinkRegistry, HttpClientFactory httpClientFactory) { super(thing, stateDescriptionProvider, itemChannelLinkRegistry); - lgThinqFridgeApiClientService = lgPlatformType.equals(PLATFORM_TYPE_V1) - ? LGThinQFridgeApiV1ClientServiceImpl.getInstance() - : LGThinQFridgeApiV2ClientServiceImpl.getInstance(); + lgThinqFridgeApiClientService = LGThinQApiClientServiceFactory.newFridgeApiClientService(lgPlatformType, httpClientFactory); channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); channelGroupExtendedInfoUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_EXTENDED_INFO_GRP_ID); fridgeTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FRIDGE_TEMP_ID); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index c00c86df35107..79ec3e02cb4c2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -32,6 +32,7 @@ import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.*; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; @@ -83,14 +84,12 @@ public class LGThinQWasherDryerHandler public LGThinQWasherDryerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, ThinqChannelTypeProvider channelTypeProvider, ThinqChannelGroupTypeProvider channelGroupTypeProvider, - ItemChannelLinkRegistry itemChannelLinkRegistry) { + ItemChannelLinkRegistry itemChannelLinkRegistry, HttpClientFactory httpClientFactory) { super(thing, stateDescriptionProvider, itemChannelLinkRegistry); this.thinqChannelGroupProvider = channelGroupTypeProvider; this.thinqChannelProvider = channelTypeProvider; this.stateDescriptionProvider = stateDescriptionProvider; - lgThinqWMApiClientService = lgPlatformType.equals(PLATFORM_TYPE_V1) - ? LGThinQWMApiV1ClientServiceImpl.getInstance() - : LGThinQWMApiV2ClientServiceImpl.getInstance(); + lgThinqWMApiClientService = LGThinQApiClientServiceFactory.newWMApiClientService(lgPlatformType, httpClientFactory); channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_GRP_ID); channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); courseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_COURSE_ID); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java index 61ea6dd14bd70..204db5a79df4b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java @@ -21,6 +21,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; @@ -42,16 +43,11 @@ @NonNullByDefault public class LGThinQACApiV1ClientServiceImpl extends LGThinQAbstractApiV1ClientService implements LGThinQACApiClientService { - private static final LGThinQACApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV1ClientServiceImpl.class); - static { - instance = new LGThinQACApiV1ClientServiceImpl(ACCapability.class, ACCanonicalSnapshot.class); - } + private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV1ClientServiceImpl.class); - protected LGThinQACApiV1ClientServiceImpl(Class capabilityClass, - Class snapshotClass) { - super(capabilityClass, snapshotClass); + protected LGThinQACApiV1ClientServiceImpl(HttpClient httpClient) { + super(ACCapability.class, ACCanonicalSnapshot.class, httpClient); } @Override @@ -59,10 +55,6 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d // Nothing to do on V1 ACCapability here } - public static LGThinQACApiClientService getInstance() { - return instance; - } - /** * Get snapshot data from the device. * It works only for API V2 device versions! diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java index 12571b063eb9b..cfad283a5c42e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -17,6 +17,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; @@ -43,23 +44,14 @@ @NonNullByDefault public class LGThinQACApiV2ClientServiceImpl extends LGThinQAbstractApiV2ClientService implements LGThinQACApiClientService { - private static final LGThinQACApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV2ClientServiceImpl.class); - static { - instance = new LGThinQACApiV2ClientServiceImpl(ACCapability.class, ACCanonicalSnapshot.class); - } - - protected LGThinQACApiV2ClientServiceImpl(Class capabilityClass, - Class snapshotClass) { - super(capabilityClass, snapshotClass); - } + private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV2ClientServiceImpl.class); - public static LGThinQACApiClientService getInstance() { - return instance; + protected LGThinQACApiV2ClientServiceImpl(HttpClient httpClient) { + super(ACCapability.class, ACCanonicalSnapshot.class, httpClient); } - @Override + @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { try { @@ -233,7 +225,7 @@ public ExtendedDeviceInfo getExtendedDeviceInfo(@NonNull String bridgeName, @Non } catch (LGThinqApiException e) { throw e; } catch (Exception e) { - throw new LGThinqApiException("Error sending command to LG API", e); + throw new LGThinqApiException("Error sending command to LG API: " + e.getMessage(), e); } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index 4b859a7b0ba19..de3223dcfbe05 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -27,6 +27,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.LGThinQBindingConstants; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; @@ -56,9 +57,11 @@ public abstract class LGThinQAbstractApiClientService capabilityClass; protected Class snapshotClass; + protected HttpClient httpClient; - protected LGThinQAbstractApiClientService(Class capabilityClass, Class snapshotClass) { - this.tokenManager = TokenManager.getInstance(); + protected LGThinQAbstractApiClientService(Class capabilityClass, Class snapshotClass, HttpClient httpClient) { + this.httpClient = httpClient; + this.tokenManager = new TokenManager(httpClient); this.capabilityClass = capabilityClass; this.snapshotClass = snapshotClass; } @@ -100,7 +103,7 @@ public List listAccountDevices(String bridgeName) throws LGThinqApiExc UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); + RestResult resp = RestUtils.getCall(httpClient, builder.build().toURL().toString(), headers, null); return handleListAccountDevicesResult(resp); } catch (Exception e) { throw new LGThinqApiException("Erros list account devices from LG Server API", e); @@ -139,7 +142,7 @@ public Map getDeviceSettings(String bridgeName, String deviceId) .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); + RestResult resp = RestUtils.getCall(httpClient, builder.build().toURL().toString(), headers, null); return handleDeviceSettingsResult(resp); } catch (Exception e) { throw new LGThinqApiException("Errors list account devices from LG Server API", e); @@ -157,6 +160,10 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo Map respMap = Collections.EMPTY_MAP; String resultCode = "???"; if (resp.getStatusCode() != 200) { + if (resp.getStatusCode() == 400) { + logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + return Collections.emptyMap(); + } try { if (resp.getStatusCode() == 400) { logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); @@ -167,7 +174,7 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo resultCode = respMap.get("resultCode"); if (resultCode != null) { logger.error( - "Error calling device settings from LG Server API. The code is:{} and The reason is:{}", + "Error calling device settings from LG Server API. The code is: {} and The reason is: {}", resultCode, ResultCodes.fromCode(resultCode)); throw new LGThinqApiException("Error calling device settings from LG Server API."); } @@ -208,9 +215,9 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG logger.warn("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); return Collections.emptyList(); } - logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); + logger.error("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); throw new LGThinqApiException(String - .format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); + .format("Error calling device list from LG Server API. The reason is: %s", resp.getJsonResponse())); } else { try { devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { @@ -288,7 +295,7 @@ private S handleV1OfflineException() { String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" + " ]\n" + " }\n" + "}", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + RestResult resp = RestUtils.postCall(httpClient, builder.build().toURL().toString(), headers, jsonData); Map envelop; // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with // offline flag. @@ -418,7 +425,7 @@ public String startMonitor(String bridgeName, String deviceId) String workerId = UUID.randomUUID().toString(); String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + RestResult resp = RestUtils.postCall(httpClient, builder.build().toURL().toString(), headers, jsonData); return Objects.requireNonNull((String) handleGenericErrorResult(resp).get("workId"), "Unexpected StartMonitor json result. Node 'workId' not present"); } @@ -432,7 +439,7 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + RestResult resp = RestUtils.postCall(httpClient, builder.build().toURL().toString(), headers, jsonData); handleGenericErrorResult(resp); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index 633634f71c009..97f8b19c088ab 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenResult; @@ -48,8 +49,8 @@ public abstract class LGThinQAbstractApiV1ClientService { private static final Logger logger = LoggerFactory.getLogger(LGThinQAbstractApiV1ClientService.class); - protected LGThinQAbstractApiV1ClientService(Class capabilityClass, Class snapshotClass) { - super(capabilityClass, snapshotClass); + protected LGThinQAbstractApiV1ClientService(Class capabilityClass, Class snapshotClass, HttpClient httpClient) { + super(capabilityClass, snapshotClass, httpClient); } @Override @@ -99,7 +100,7 @@ protected RestResult sendCommand(String bridgeName, String deviceId, Object cmdP rootNode.set("lgedmRoot", bodyNode); String url = builder.build().toURL().toString(); logger.debug("URL: {}, Post Payload:[{}]", url, rootNode.toPrettyString()); - RestResult resp = RestUtils.postCall(url, headers, rootNode.toPrettyString()); + RestResult resp = RestUtils.postCall(httpClient, url, headers, rootNode.toPrettyString()); if (resp == null) { logger.warn("Null result returned sending command to LG API V1"); throw new LGThinqApiException("Null result returned sending command to LG API V1"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java index 96eb8a40057f9..be0340a4f53c5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -22,6 +22,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenResult; @@ -47,8 +48,8 @@ public abstract class LGThinQAbstractApiV2ClientService { private static final Logger logger = LoggerFactory.getLogger(LGThinQAbstractApiV2ClientService.class); - protected LGThinQAbstractApiV2ClientService(Class capabilityClass, Class snapshotClass) { - super(capabilityClass, snapshotClass); + protected LGThinQAbstractApiV2ClientService(Class capabilityClass, Class snapshotClass, HttpClient httpClient) { + super(capabilityClass, snapshotClass, httpClient); } @Override @@ -64,7 +65,7 @@ protected RestResult postCall(String bridgeName, String deviceId, String control .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId, controlPath)); Map headers = getCommonV2Headers(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, payload); + RestResult resp = RestUtils.postCall(httpClient, builder.build().toURL().toString(), headers, payload); if (resp == null) { logger.warn("Null result returned sending command to LG API V2: {}, {}, {}", deviceId, controlPath, payload); throw new LGThinqApiException("Null result returned sending command to LG API V2"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java new file mode 100644 index 0000000000000..569b261b2f706 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java @@ -0,0 +1,98 @@ +/** + * 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.lgthinq.lgservices; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PLATFORM_TYPE_V1; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; +import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.core.io.net.http.HttpClientFactory; + +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Creates expecialized API clients. + * + * @author Julio Gesser + */ +public class LGThinQApiClientServiceFactory { + + public static LGThinQGeneralApiClientService newGeneralApiClientService(HttpClientFactory httpClientFactory) { + return new LGThinQGeneralApiClientService(httpClientFactory.getCommonHttpClient()); + } + + public static LGThinQACApiClientService newACApiClientService(String lgPlatformType, HttpClientFactory httpClientFactory) { + return lgPlatformType.equals(PLATFORM_TYPE_V1) + ? new LGThinQACApiV1ClientServiceImpl(httpClientFactory.getCommonHttpClient()) + : new LGThinQACApiV2ClientServiceImpl(httpClientFactory.getCommonHttpClient()); + } + + public static LGThinQFridgeApiClientService newFridgeApiClientService(String lgPlatformType, HttpClientFactory httpClientFactory) { + return lgPlatformType.equals(PLATFORM_TYPE_V1) + ? new LGThinQFridgeApiV1ClientServiceImpl(httpClientFactory.getCommonHttpClient()) + : new LGThinQFridgeApiV2ClientServiceImpl(httpClientFactory.getCommonHttpClient()); + } + + public static LGThinQWMApiClientService newWMApiClientService(String lgPlatformType, HttpClientFactory httpClientFactory) { + return lgPlatformType.equals(PLATFORM_TYPE_V1) + ? new LGThinQWMApiV1ClientServiceImpl(httpClientFactory.getCommonHttpClient()) + : new LGThinQWMApiV2ClientServiceImpl(httpClientFactory.getCommonHttpClient()); + } + + @NonNullByDefault + public static final class LGThinQGeneralApiClientService extends LGThinQAbstractApiClientService { + + private LGThinQGeneralApiClientService(HttpClient httpClient) { + super(GenericCapability.class, AbstractSnapshotDefinition.class, httpClient); + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { + throw new UnsupportedOperationException(); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + throw new UnsupportedOperationException(); + } + + @Override + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, String keyName, String value) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) throws Exception { + throw new UnsupportedOperationException(); + } + + @Override + protected Map handleGenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { + throw new UnsupportedOperationException(); + } + } + + @NonNullByDefault + private static final class GenericCapability extends AbstractCapability { + + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java index ddd7648babf8b..2536d80f9e88a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java @@ -14,6 +14,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; @@ -30,14 +31,8 @@ public class LGThinQDRApiV2ClientServiceImpl extends LGThinQAbstractApiV2ClientService implements LGThinQDRApiClientService { - private static final LGThinQDRApiV2ClientServiceImpl instance; - static { - instance = new LGThinQDRApiV2ClientServiceImpl(WasherDryerCapability.class, WasherDryerSnapshot.class); - } - - protected LGThinQDRApiV2ClientServiceImpl(Class capabilityClass, - Class snapshotClass) { - super(capabilityClass, snapshotClass); + protected LGThinQDRApiV2ClientServiceImpl(HttpClient httpClient) { + super(WasherDryerCapability.class, WasherDryerSnapshot.class, httpClient); } @Override @@ -45,10 +40,6 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d // TODO - Analise what to do here } - public static LGThinQDRApiV2ClientServiceImpl getInstance() { - return instance; - } - @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java index 45097fe692451..ab103230da590 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java @@ -15,6 +15,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; @@ -31,14 +32,8 @@ public class LGThinQFridgeApiV1ClientServiceImpl extends LGThinQAbstractApiV1ClientService implements LGThinQFridgeApiClientService { - private static final LGThinQFridgeApiClientService instance; - static { - instance = new LGThinQFridgeApiV1ClientServiceImpl(FridgeCapability.class, FridgeCanonicalSnapshot.class); - } - - protected LGThinQFridgeApiV1ClientServiceImpl(Class capabilityClass, - Class snapshotClass) { - super(capabilityClass, snapshotClass); + protected LGThinQFridgeApiV1ClientServiceImpl(HttpClient httpClient) { + super(FridgeCapability.class, FridgeCanonicalSnapshot.class, httpClient); } @Override @@ -46,10 +41,6 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d // Nothing to do for V1 thinq } - public static LGThinQFridgeApiClientService getInstance() { - return instance; - } - @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java index 5903964e2edfb..f7e9d39e22149 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java @@ -14,6 +14,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; @@ -29,14 +30,8 @@ public class LGThinQFridgeApiV2ClientServiceImpl extends LGThinQAbstractApiV2ClientService implements LGThinQFridgeApiClientService { - private static final LGThinQFridgeApiClientService instance; - static { - instance = new LGThinQFridgeApiV2ClientServiceImpl(FridgeCapability.class, FridgeCanonicalSnapshot.class); - } - - protected LGThinQFridgeApiV2ClientServiceImpl(Class capabilityClass, - Class snapshotClass) { - super(capabilityClass, snapshotClass); + protected LGThinQFridgeApiV2ClientServiceImpl(HttpClient httpClient) { + super(FridgeCapability.class, FridgeCanonicalSnapshot.class, httpClient); } @Override @@ -44,10 +39,6 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d // TODO - Analise what to do here } - public static LGThinQFridgeApiClientService getInstance() { - return instance; - } - @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java index eeae36d02ef21..1085d90424eee 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; @@ -41,14 +42,9 @@ public class LGThinQWMApiV1ClientServiceImpl extends LGThinQAbstractApiV1ClientService implements LGThinQWMApiClientService { private final Logger logger = LoggerFactory.getLogger(LGThinQWMApiV1ClientServiceImpl.class); - private static final LGThinQWMApiClientService instance; - static { - instance = new LGThinQWMApiV1ClientServiceImpl(WasherDryerCapability.class, WasherDryerSnapshot.class); - } - protected LGThinQWMApiV1ClientServiceImpl(Class capabilityClass, - Class snapshotClass) { - super(capabilityClass, snapshotClass); + protected LGThinQWMApiV1ClientServiceImpl(HttpClient httpClient) { + super(WasherDryerCapability.class, WasherDryerSnapshot.class, httpClient); } @Override @@ -56,10 +52,6 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d // Nothing to do for V1 thinq } - public static LGThinQWMApiClientService getInstance() { - return instance; - } - @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java index f06ce3d55fd89..14571b16204af 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java @@ -18,6 +18,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; @@ -38,14 +39,8 @@ public class LGThinQWMApiV2ClientServiceImpl extends LGThinQAbstractApiV2ClientService implements LGThinQWMApiClientService { - private static final LGThinQWMApiClientService instance; - static { - instance = new LGThinQWMApiV2ClientServiceImpl(WasherDryerCapability.class, WasherDryerSnapshot.class); - } - - protected LGThinQWMApiV2ClientServiceImpl(Class capabilityClass, - Class snapshotClass) { - super(capabilityClass, snapshotClass); + protected LGThinQWMApiV2ClientServiceImpl(HttpClient httpClient) { + super(WasherDryerCapability.class, WasherDryerSnapshot.class, httpClient); } @Override @@ -53,10 +48,6 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d // TODO - Analise what to do here } - public static LGThinQWMApiClientService getInstance() { - return instance; - } - @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java index 134474dc9bf69..d960d81060ae9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java @@ -27,6 +27,7 @@ import javax.ws.rs.core.UriBuilder; +import org.eclipse.jetty.client.HttpClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -38,6 +39,7 @@ import org.openhab.binding.lgthinq.internal.handler.LGThinQBridgeHandler; import org.openhab.binding.lgthinq.lgservices.*; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ThingUID; import org.slf4j.Logger; @@ -85,8 +87,8 @@ private String getCurrentTimestamp() { @Test public void testDiscoveryACThings() { setupAuthenticationMock(); - LGThinQApiClientService service1 = LGThinQACApiV1ClientServiceImpl.getInstance(); - LGThinQApiClientService service2 = LGThinQACApiV2ClientServiceImpl.getInstance(); + LGThinQApiClientService service1 = LGThinQApiClientServiceFactory.newACApiClientService(PLATFORM_TYPE_V1, mock(HttpClientFactory.class)); + LGThinQApiClientService service2 = LGThinQApiClientServiceFactory.newACApiClientService(PLATFORM_TYPE_V2, mock(HttpClientFactory.class)); try { List devices = service2.listAccountDevices("bridgeTest"); assertEquals(devices.size(), 2); @@ -128,12 +130,12 @@ private void setupAuthenticationMock() { String tempDir = System.getProperty("java.io.tmpdir"); LGThinQBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; LGThinQBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; - LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing); + LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing, mock(HttpClientFactory.class)); LGThinQBridgeHandler spyBridge = spy(b); doReturn(new LGThinQBridgeConfiguration(fakeUserName, fakePassword, fakeCountry, fakeLanguage, 60, "http://localhost:8880")).when(spyBridge).getConfigAs(any(Class.class)); spyBridge.initialize(); - TokenManager tokenManager = TokenManager.getInstance(); + TokenManager tokenManager = new TokenManager(mock(HttpClient.class)); try { if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { tokenManager.oauthFirstRegistration(fakeBridgeName, fakeLanguage, fakeCountry, fakeUserNameEncoded, @@ -187,10 +189,10 @@ public void testDiscoveryWMThings() { LGThinQBindingConstants.THINQ_USER_DATA_FOLDER = "" + tempDir; LGThinQBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; LGThinQBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; - LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing); + LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing, mock(HttpClientFactory.class)); - final LGThinQWMApiClientService service2 = LGThinQWMApiV2ClientServiceImpl.getInstance(); - TokenManager tokenManager = TokenManager.getInstance(); + final LGThinQWMApiClientService service2 = LGThinQApiClientServiceFactory.newWMApiClientService(PLATFORM_TYPE_V1, mock(HttpClientFactory.class)); + TokenManager tokenManager = new TokenManager(mock(HttpClient.class)); try { if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { tokenManager.oauthFirstRegistration(fakeBridgeName, fakeLanguage, fakeCountry, fakeUserName, From 3fcd71905a5b5f60321d8ffa4d633fa4d6c77fa0 Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Tue, 18 Jul 2023 10:41:43 -0300 Subject: [PATCH 011/130] [lgthinq][feat] replaced external apache httpclient with embedded jetty Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/pom.xml | 18 ++++++++++++++++++ .../src/main/feature/feature.xml | 3 +-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml index 2a920da8f314c..7c2667c0c65b1 100644 --- a/bundles/org.openhab.binding.lgthinq/pom.xml +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -14,6 +14,24 @@ openHAB Add-ons :: Bundles :: LG Thinq Binding + + com.fasterxml.jackson.core + jackson-core + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-annotations + ${jackson.version} + compile + + + com.fasterxml.jackson.core + jackson-databind + ${jackson.version} + compile + com.github.tomakehurst wiremock-jre8 diff --git a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml index c66d97eb54904..5f83cb2f657c9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml @@ -1,11 +1,10 @@ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features + openhab-runtime-base openhab.tp-jackson - mvn:org.apache.httpcomponents/httpclient-osgi/4.5.13 - mvn:org.apache.httpcomponents/httpcore-osgi/4.4.10 mvn:org.openhab.addons.bundles/org.openhab.binding.lgthinq/${project.version} From cd0e662ddf11487c4f43245b6d95109f67e2ced5 Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Tue, 18 Jul 2023 13:10:57 -0300 Subject: [PATCH 012/130] [lgthinq][feat] replaced external apache httpclient with embedded jetty Signed-off-by: nemerdaud --- .../binding/lgthinq/internal/api/RestUtils.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java index c9a8b4ced9d24..a0fe6c471444e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java @@ -100,7 +100,12 @@ public static RestResult getCall(HttpClient httpClient, String encodedUrl, @Null @Nullable Map params) throws IOException { Request request = httpClient.newRequest(encodedUrl).method("GET"); - headers.forEach(request::header); + if (params != null) { + params.forEach(request::param); + } + if (headers != null) { + headers.forEach(request::header); + } ContentResponse response; try { response = request.send(); @@ -151,7 +156,9 @@ private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map .method("POST") .content(contentProvider) .timeout(10, TimeUnit.SECONDS); - headers.forEach(request::header); + if (headers != null) { + headers.forEach(request::header); + } ContentResponse response = request.content(contentProvider).timeout(10, TimeUnit.SECONDS) .send(); return new RestResult(response.getContentAsString(), response.getStatus()); From 29a9dee68f883705513faa3405c4c1ce974593ec Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Tue, 18 Jul 2023 14:17:12 -0300 Subject: [PATCH 013/130] [lgthinq][fix] fixed discovery to remove registered devices Signed-off-by: nemerdaud --- .../discovery/LGThinqDiscoveryService.java | 4 + .../handler/LGThinQAirConditionerHandler.java | 596 +++++++++--------- .../handler/LGThinQBridgeHandler.java | 13 +- 3 files changed, 314 insertions(+), 299 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index a1263f835acdf..266cfd6135629 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -56,6 +56,8 @@ public LGThinqDiscoveryService() { @Override protected void startScan() { + logger.debug("Scan started"); + bridgeHandler.runDiscovery(); } @Override @@ -85,6 +87,7 @@ public void deactivate() { } public void removeLgDeviceDiscovery(LGDevice device) { + logger.debug("Thing removed from discovery: {}", device.getDeviceId()); try { ThingUID thingUID = getThingUID(device); thingRemoved(thingUID); @@ -94,6 +97,7 @@ public void removeLgDeviceDiscovery(LGDevice device) { } public void addLgDeviceDiscovery(LGDevice device) { + logger.debug("Thing added to discovery: {}", device.getDeviceId()); String modelId = device.getModelName(); ThingUID thingUID; ThingTypeUID thingTypeUID; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 7674f115b2b28..306f754e6709d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -62,326 +62,328 @@ @NonNullByDefault public class LGThinQAirConditionerHandler extends LGThinQAbstractDeviceHandler { - public final ChannelGroupUID channelGroupExtendedInfoUID; - public final ChannelGroupUID channelGroupDashboardUID; - private final ChannelUID powerChannelUID; - private final ChannelUID opModeChannelUID; - private final ChannelUID fanSpeedChannelUID; - private final ChannelUID targetTempChannelUID; - private final ChannelUID currTempChannelUID; - private final ChannelUID jetModeChannelUID; - private final ChannelUID airCleanChannelUID; - private final ChannelUID autoDryChannelUID; - private final ChannelUID energySavingChannelUID; - private final ChannelUID extendedInfoCollectorChannelUID; - private final ChannelUID currentPowerEnergyChannelUID; - private final ChannelUID remainingFilterChannelUID; + public final ChannelGroupUID channelGroupExtendedInfoUID; + public final ChannelGroupUID channelGroupDashboardUID; + private final ChannelUID powerChannelUID; + private final ChannelUID opModeChannelUID; + private final ChannelUID fanSpeedChannelUID; + private final ChannelUID targetTempChannelUID; + private final ChannelUID currTempChannelUID; + private final ChannelUID jetModeChannelUID; + private final ChannelUID airCleanChannelUID; + private final ChannelUID autoDryChannelUID; + private final ChannelUID energySavingChannelUID; + private final ChannelUID extendedInfoCollectorChannelUID; + private final ChannelUID currentPowerEnergyChannelUID; + private final ChannelUID remainingFilterChannelUID; - private final ObjectMapper mapper = new ObjectMapper(); - private final Logger logger = LoggerFactory.getLogger(LGThinQAirConditionerHandler.class); - @NonNullByDefault - private final LGThinQACApiClientService lgThinqACApiClientService; + private final ObjectMapper mapper = new ObjectMapper(); + private final Logger logger = LoggerFactory.getLogger(LGThinQAirConditionerHandler.class); + @NonNullByDefault + private final LGThinQACApiClientService lgThinqACApiClientService; - public LGThinQAirConditionerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, - ItemChannelLinkRegistry itemChannelLinkRegistry, HttpClientFactory httpClientFactory) { - super(thing, stateDescriptionProvider, itemChannelLinkRegistry); - lgThinqACApiClientService = LGThinQApiClientServiceFactory.newACApiClientService(lgPlatformType, httpClientFactory); - channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); - channelGroupExtendedInfoUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_EXTENDED_INFO_GRP_ID); + public LGThinQAirConditionerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, + ItemChannelLinkRegistry itemChannelLinkRegistry, HttpClientFactory httpClientFactory) { + super(thing, stateDescriptionProvider, itemChannelLinkRegistry); + lgThinqACApiClientService = LGThinQApiClientServiceFactory.newACApiClientService(lgPlatformType, httpClientFactory); + channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); + channelGroupExtendedInfoUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_EXTENDED_INFO_GRP_ID); - opModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_MOD_OP_ID); - targetTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_TARGET_TEMP_ID); - currTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_CURRENT_TEMP_ID); - fanSpeedChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FAN_SPEED_ID); - jetModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_COOL_JET_ID); - airCleanChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AIR_CLEAN_ID); - autoDryChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AUTO_DRY_ID); - energySavingChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_ENERGY_SAVING_ID); - powerChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_POWER_ID); - extendedInfoCollectorChannelUID = new ChannelUID(channelGroupExtendedInfoUID, - CHANNEL_EXTENDED_INFO_COLLECTOR_ID); - currentPowerEnergyChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_CURRENT_POWER_ID); - remainingFilterChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_REMAINING_FILTER_ID); - } + opModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_MOD_OP_ID); + targetTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_TARGET_TEMP_ID); + currTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_CURRENT_TEMP_ID); + fanSpeedChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FAN_SPEED_ID); + jetModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_COOL_JET_ID); + airCleanChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AIR_CLEAN_ID); + autoDryChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_AUTO_DRY_ID); + energySavingChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_ENERGY_SAVING_ID); + powerChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_POWER_ID); + extendedInfoCollectorChannelUID = new ChannelUID(channelGroupExtendedInfoUID, + CHANNEL_EXTENDED_INFO_COLLECTOR_ID); + currentPowerEnergyChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_CURRENT_POWER_ID); + remainingFilterChannelUID = new ChannelUID(channelGroupExtendedInfoUID, CHANNEL_REMAINING_FILTER_ID); + } - @Override - public void initialize() { - super.initialize(); - try { - ACCapability cap = getCapabilities(); - if (!isExtraInfoCollectorSupported()) { - ThingBuilder builder = editThing() - .withoutChannels(this.getThing().getChannelsOfGroup(channelGroupExtendedInfoUID.getId())); - updateThing(builder.build()); - } else if (!cap.isEnergyMonitorAvailable()) { - ThingBuilder builder = editThing().withoutChannel(currentPowerEnergyChannelUID); - updateThing(builder.build()); - } else if (!cap.isFilterMonitorAvailable()) { - ThingBuilder builder = editThing().withoutChannel(remainingFilterChannelUID); - updateThing(builder.build()); - } - } catch (LGThinqApiException e) { - logger.warn("Error getting capability of the device:{}", getDeviceId()); - } - } + @Override + public void initialize() { + super.initialize(); + try { + ACCapability cap = getCapabilities(); + if (!isExtraInfoCollectorSupported()) { + ThingBuilder builder = editThing() + .withoutChannels(this.getThing().getChannelsOfGroup(channelGroupExtendedInfoUID.getId())); + updateThing(builder.build()); + } else if (!cap.isEnergyMonitorAvailable()) { + ThingBuilder builder = editThing().withoutChannel(currentPowerEnergyChannelUID); + updateThing(builder.build()); + } else if (!cap.isFilterMonitorAvailable()) { + ThingBuilder builder = editThing().withoutChannel(remainingFilterChannelUID); + updateThing(builder.build()); + } + } catch (LGThinqApiException e) { + logger.warn("Error getting capability of the device: {}", getDeviceId()); + } + } - @Override - protected void updateDeviceChannels(ACCanonicalSnapshot shot) { - logger.debug("Calling updateDeviceChannel for device:{}", getDeviceAlias()); - updateState(powerChannelUID, - DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF); - updateState(opModeChannelUID, new DecimalType(BigDecimal.valueOf(shot.getOperationMode()))); - updateState(fanSpeedChannelUID, new DecimalType(BigDecimal.valueOf(shot.getAirWindStrength()))); - updateState(currTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getCurrentTemperature()))); - updateState(targetTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getTargetTemperature()))); - try { - ACCapability acCap = getCapabilities(); - if (getThing().getChannel(jetModeChannelUID) != null) { - Double commandCoolJetOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); - updateState(jetModeChannelUID, - commandCoolJetOn.equals(shot.getCoolJetMode()) ? OnOffType.ON : OnOffType.OFF); - } - if (getThing().getChannel(airCleanChannelUID) != null) { - Double commandAirCleanOn = Double.valueOf(acCap.getAirCleanModeCommandOn()); - updateState(airCleanChannelUID, - commandAirCleanOn.equals(shot.getAirCleanMode()) ? OnOffType.ON : OnOffType.OFF); - } - if (getThing().getChannel(energySavingChannelUID) != null) { - Double energySavingOn = Double.valueOf(acCap.getEnergySavingModeCommandOn()); - updateState(energySavingChannelUID, - energySavingOn.equals(shot.getEnergySavingMode()) ? OnOffType.ON : OnOffType.OFF); - } - if (getThing().getChannel(autoDryChannelUID) != null) { - Double autoDryOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); - updateState(autoDryChannelUID, autoDryOn.equals(shot.getAutoDryMode()) ? OnOffType.ON : OnOffType.OFF); - } + @Override + protected void updateDeviceChannels(ACCanonicalSnapshot shot) { + logger.debug("Calling updateDeviceChannel for device: {}", getDeviceId()); + updateState(powerChannelUID, + DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF); + updateState(opModeChannelUID, new DecimalType(BigDecimal.valueOf(shot.getOperationMode()))); + updateState(fanSpeedChannelUID, new DecimalType(BigDecimal.valueOf(shot.getAirWindStrength()))); + updateState(currTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getCurrentTemperature()))); + updateState(targetTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getTargetTemperature()))); + try { + ACCapability acCap = getCapabilities(); + if (getThing().getChannel(jetModeChannelUID) != null) { + Double commandCoolJetOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); + updateState(jetModeChannelUID, + commandCoolJetOn.equals(shot.getCoolJetMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(airCleanChannelUID) != null) { + Double commandAirCleanOn = Double.valueOf(acCap.getAirCleanModeCommandOn()); + updateState(airCleanChannelUID, + commandAirCleanOn.equals(shot.getAirCleanMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(energySavingChannelUID) != null) { + Double energySavingOn = Double.valueOf(acCap.getEnergySavingModeCommandOn()); + updateState(energySavingChannelUID, + energySavingOn.equals(shot.getEnergySavingMode()) ? OnOffType.ON : OnOffType.OFF); + } + if (getThing().getChannel(autoDryChannelUID) != null) { + Double autoDryOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); + updateState(autoDryChannelUID, autoDryOn.equals(shot.getAutoDryMode()) ? OnOffType.ON : OnOffType.OFF); + } - } catch (LGThinqApiException e) { - logger.error("Unexpected Error gettinf ACCapability Capabilities", e); - } catch (NumberFormatException e) { - logger.warn("command value for capability is not numeric.", e); - } - } + } catch (LGThinqApiException e) { + logger.error("Unexpected Error gettinf ACCapability Capabilities", e); + } catch (NumberFormatException e) { + logger.warn("command value for capability is not numeric.", e); + } + } - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - ACCapability acCap = getCapabilities(); - if (getThing().getChannel(jetModeChannelUID) == null && acCap.isJetModeAvailable()) { - createDynSwitchChannel(CHANNEL_COOL_JET_ID, jetModeChannelUID); - } - if (getThing().getChannel(autoDryChannelUID) == null && acCap.isAutoDryModeAvailable()) { - createDynSwitchChannel(CHANNEL_AUTO_DRY_ID, autoDryChannelUID); - } - if (getThing().getChannel(airCleanChannelUID) == null && acCap.isAirCleanAvailable()) { - createDynSwitchChannel(CHANNEL_AIR_CLEAN_ID, airCleanChannelUID); - } - if (getThing().getChannel(energySavingChannelUID) == null && acCap.isEnergySavingAvailable()) { - createDynSwitchChannel(CHANNEL_ENERGY_SAVING_ID, energySavingChannelUID); - } - if (!acCap.getFanSpeed().isEmpty()) { - List options = new ArrayList<>(); - acCap.getFanSpeed() - .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); - stateDescriptionProvider.setStateOptions(fanSpeedChannelUID, options); - } - if (!acCap.getOpMode().isEmpty()) { - List options = new ArrayList<>(); - acCap.getOpMode().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_OP_MODE.get(v))))); - stateDescriptionProvider.setStateOptions(opModeChannelUID, options); - } - } + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + ACCapability acCap = getCapabilities(); + if (getThing().getChannel(jetModeChannelUID) == null && acCap.isJetModeAvailable()) { + createDynSwitchChannel(CHANNEL_COOL_JET_ID, jetModeChannelUID); + } + if (getThing().getChannel(autoDryChannelUID) == null && acCap.isAutoDryModeAvailable()) { + createDynSwitchChannel(CHANNEL_AUTO_DRY_ID, autoDryChannelUID); + } + if (getThing().getChannel(airCleanChannelUID) == null && acCap.isAirCleanAvailable()) { + createDynSwitchChannel(CHANNEL_AIR_CLEAN_ID, airCleanChannelUID); + } + if (getThing().getChannel(energySavingChannelUID) == null && acCap.isEnergySavingAvailable()) { + createDynSwitchChannel(CHANNEL_ENERGY_SAVING_ID, energySavingChannelUID); + } + if (!acCap.getFanSpeed().isEmpty()) { + List options = new ArrayList<>(); + acCap.getFanSpeed() + .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); + stateDescriptionProvider.setStateOptions(fanSpeedChannelUID, options); + } + if (!acCap.getOpMode().isEmpty()) { + List options = new ArrayList<>(); + acCap.getOpMode().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_OP_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(opModeChannelUID, options); + } + } - @Override - public LGThinQApiClientService getLgThinQAPIClientService() { - return lgThinqACApiClientService; - } + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqACApiClientService; + } - @Override - protected Logger getLogger() { - return logger; - } + @Override + protected Logger getLogger() { + return logger; + } - protected DeviceTypes getDeviceType() { - if (THING_TYPE_HEAT_PUMP.equals(getThing().getThingTypeUID())) { - return DeviceTypes.HEAT_PUMP; - } else if (THING_TYPE_AIR_CONDITIONER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.AIR_CONDITIONER; - } else { - throw new IllegalArgumentException( - "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for AirConditioner/HeatPump"); - } - } + protected DeviceTypes getDeviceType() { + if (THING_TYPE_HEAT_PUMP.equals(getThing().getThingTypeUID())) { + return DeviceTypes.HEAT_PUMP; + } else if (THING_TYPE_AIR_CONDITIONER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.AIR_CONDITIONER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for AirConditioner/HeatPump"); + } + } - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } - @Override - public void onDeviceDisconnected() { - // TODO - HANDLE IT, Think if it's needed - } + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } - protected void resetExtraInfoChannels() { - updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); - updateState(remainingFilterChannelUID, UnDefType.UNDEF); - } + @Override + public void onDeviceDisconnected() { + // TODO - HANDLE IT, Think if it's needed + } - protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { - Command command = params.command; - switch (getSimpleChannelUID(params.channelUID)) { - case CHANNEL_MOD_OP_ID: { - if (params.command instanceof DecimalType) { - lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), - ((DecimalType) command).intValue()); - } else { - logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); - } - break; - } - case CHANNEL_FAN_SPEED_ID: { - if (command instanceof DecimalType) { - lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), - ((DecimalType) command).intValue()); - } else { - logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); - } - break; - } - case CHANNEL_POWER_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); - } else { - logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); - } - break; - } - case CHANNEL_COOL_JET_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnCoolJetMode(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? getCapabilities().getCoolJetModeCommandOn() - : getCapabilities().getCoolJetModeCommandOff()); - } else { - logger.warn("Received command different of OnOffType in CoolJet Mode Channel. Ignoring"); - } - break; - } - case CHANNEL_AIR_CLEAN_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnAirCleanMode(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? getCapabilities().getAirCleanModeCommandOn() - : getCapabilities().getAirCleanModeCommandOff()); - } else { - logger.warn("Received command different of OnOffType in AirClean Mode Channel. Ignoring"); - } - break; - } - case CHANNEL_AUTO_DRY_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnAutoDryMode(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? getCapabilities().getAutoDryModeCommandOn() - : getCapabilities().getAutoDryModeCommandOff()); - } else { - logger.warn("Received command different of OnOffType in AutoDry Mode Channel. Ignoring"); - } - break; - } - case CHANNEL_ENERGY_SAVING_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnEnergySavingMode(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? getCapabilities().getEnergySavingModeCommandOn() - : getCapabilities().getEnergySavingModeCommandOff()); - } else { - logger.warn("Received command different of OnOffType in EvergySaving Mode Channel. Ignoring"); - } - break; - } - case CHANNEL_TARGET_TEMP_ID: { - double targetTemp; - if (command instanceof DecimalType) { - targetTemp = ((DecimalType) command).doubleValue(); - } else if (command instanceof QuantityType) { - targetTemp = ((QuantityType) command).doubleValue(); - } else { - logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); - break; - } - lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), - ACTargetTmp.statusOf(targetTemp)); - break; - } - case CHANNEL_EXTENDED_INFO_COLLECTOR_ID: { - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); - } + protected void resetExtraInfoChannels() { + updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + if (!isExtraInfoCollectorEnabled()) { // if collector is enabled we can keep the current value + updateState(remainingFilterChannelUID, UnDefType.UNDEF); } } - // =========== Energy Colletor Implementation ============= - @Override - protected boolean isExtraInfoCollectorSupported() { - try { - return getCapabilities().isEnergyMonitorAvailable() || getCapabilities().isFilterMonitorAvailable(); - } catch (LGThinqApiException e) { + protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + switch (getSimpleChannelUID(params.channelUID)) { + case CHANNEL_MOD_OP_ID: { + if (params.command instanceof DecimalType) { + lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); + } + break; + } + case CHANNEL_FAN_SPEED_ID: { + if (command instanceof DecimalType) { + lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); + } + break; + } + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + case CHANNEL_COOL_JET_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnCoolJetMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getCoolJetModeCommandOn() + : getCapabilities().getCoolJetModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in CoolJet Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_AIR_CLEAN_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnAirCleanMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getAirCleanModeCommandOn() + : getCapabilities().getAirCleanModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in AirClean Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_AUTO_DRY_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnAutoDryMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getAutoDryModeCommandOn() + : getCapabilities().getAutoDryModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in AutoDry Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_ENERGY_SAVING_ID: { + if (command instanceof OnOffType) { + lgThinqACApiClientService.turnEnergySavingMode(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? getCapabilities().getEnergySavingModeCommandOn() + : getCapabilities().getEnergySavingModeCommandOff()); + } else { + logger.warn("Received command different of OnOffType in EvergySaving Mode Channel. Ignoring"); + } + break; + } + case CHANNEL_TARGET_TEMP_ID: { + double targetTemp; + if (command instanceof DecimalType) { + targetTemp = ((DecimalType) command).doubleValue(); + } else if (command instanceof QuantityType) { + targetTemp = ((QuantityType) command).doubleValue(); + } else { + logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); + break; + } + lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + ACTargetTmp.statusOf(targetTemp)); + break; + } + case CHANNEL_EXTENDED_INFO_COLLECTOR_ID: { + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } + // =========== Energy Colletor Implementation ============= + + @Override + protected boolean isExtraInfoCollectorSupported() { + try { + return getCapabilities().isEnergyMonitorAvailable() || getCapabilities().isFilterMonitorAvailable(); + } catch (LGThinqApiException e) { logger.warn("Can't get capabilities of the device: {}", getDeviceId()); - } - return false; - } + } + return false; + } - @Override - protected boolean isExtraInfoCollectorEnabled() { - return OnOffType.ON.toString().equals(getItemLinkedValue(extendedInfoCollectorChannelUID)); - } + @Override + protected boolean isExtraInfoCollectorEnabled() { + return OnOffType.ON.toString().equals(getItemLinkedValue(extendedInfoCollectorChannelUID)); + } - @Override - protected Map collectExtraInfoState() throws LGThinqException { - ExtendedDeviceInfo info = lgThinqACApiClientService.getExtendedDeviceInfo(getBridgeId(), getDeviceId()); - return mapper.convertValue(info, new TypeReference<>() { - }); - } + @Override + protected Map collectExtraInfoState() throws LGThinqException { + ExtendedDeviceInfo info = lgThinqACApiClientService.getExtendedDeviceInfo(getBridgeId(), getDeviceId()); + return mapper.convertValue(info, new TypeReference<>() { + }); + } - @Override - protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { - logger.debug("Calling updateExtraInfoStateChannels for device:{}", getDeviceAlias()); - String instantPowerConsumption = (String) energyStateAttributes.get(EXTENDED_ATTR_INSTANT_POWER); - String filterUsed = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_USED_TIME); - String filterTimelife = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE); - if (instantPowerConsumption == null) { - updateState(currentPowerEnergyChannelUID, UnDefType.NULL); - } else if (NumberUtils.isCreatable(instantPowerConsumption)) { - double ip = Double.parseDouble(instantPowerConsumption); - updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.WATT)); - } else { - updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); - } + @Override + protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { + logger.debug("Calling updateExtraInfoStateChannels for device: {}", getDeviceId()); + String instantPowerConsumption = (String) energyStateAttributes.get(EXTENDED_ATTR_INSTANT_POWER); + String filterUsed = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_USED_TIME); + String filterTimelife = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE); + if (instantPowerConsumption == null) { + updateState(currentPowerEnergyChannelUID, UnDefType.NULL); + } else if (NumberUtils.isCreatable(instantPowerConsumption)) { + double ip = Double.parseDouble(instantPowerConsumption); + updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.WATT)); + } else { + updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + } - if (filterTimelife == null || filterUsed == null) { - updateState(remainingFilterChannelUID, UnDefType.NULL); - } else if (NumberUtils.isCreatable(filterTimelife) && NumberUtils.isCreatable(filterUsed)) { - double used = Double.parseDouble(filterUsed); - double max = Double.parseDouble(filterTimelife); + if (filterTimelife == null || filterUsed == null) { + updateState(remainingFilterChannelUID, UnDefType.NULL); + } else if (NumberUtils.isCreatable(filterTimelife) && NumberUtils.isCreatable(filterUsed)) { + double used = Double.parseDouble(filterUsed); + double max = Double.parseDouble(filterTimelife); double perc = (1 - ((double) used / max)) * 100; - updateState(remainingFilterChannelUID, new QuantityType<>(perc, Units.PERCENT)); - } else { - updateState(remainingFilterChannelUID, UnDefType.UNDEF); - } - } + updateState(remainingFilterChannelUID, new QuantityType<>(perc, Units.PERCENT)); + } else { + updateState(remainingFilterChannelUID, UnDefType.UNDEF); + } + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index 8601cd09d3088..fd7a5523c08fa 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -213,18 +213,23 @@ protected void doConnectedRun() throws LGThinqException { // if not registered yet, and not discovered before, then add to discovery list. devices.forEach(device -> { String deviceId = device.getDeviceId(); + logger.debug("Device found: {}", deviceId); if (lGDeviceRegister.get(deviceId) == null && !lastDevicesDiscovered.containsKey(deviceId)) { logger.debug("Adding new LG Device to things registry with id:{}", deviceId); if (discoveryService != null) { discoveryService.addLgDeviceDiscovery(device); } + } else { + if (discoveryService != null && lGDeviceRegister.get(deviceId) != null) { + discoveryService.removeLgDeviceDiscovery(device); + } } lastDevicesDiscovered.put(deviceId, device); lastDevicesDiscoveredCopy.remove(deviceId); }); // the rest in lastDevicesDiscoveredCopy is not more registered in LG API. Remove from discovery lastDevicesDiscoveredCopy.forEach((deviceId, device) -> { - logger.trace("LG Device '{}' removed.", deviceId); + logger.debug("LG Device '{}' removed.", deviceId); lastDevicesDiscovered.remove(deviceId); LGThinQAbstractDeviceHandler deviceThing = lGDeviceRegister.get(deviceId); @@ -331,10 +336,14 @@ private void startLGThinqDevicePolling() { poolingInterval = configPollingInterval; } // submit instantlly and schedule for the next polling interval. - scheduler.submit(lgDevicePollingRunnable); + runDiscovery(); devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, 2, poolingInterval, TimeUnit.SECONDS); } + + public void runDiscovery() { + scheduler.submit(lgDevicePollingRunnable); + } @Override public void handleCommand(ChannelUID channelUID, Command command) { From 8700cc05f89aa32b885bc5d50f79dc9a1c19453b Mon Sep 17 00:00:00 2001 From: Julio Vilmar Gesser Date: Thu, 20 Jul 2023 18:11:32 -0300 Subject: [PATCH 014/130] [lgthinq][feat] replaced external apache httpclient with embedded jetty Signed-off-by: nemerdaud --- .../lgthinq/internal/api/RestUtils.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java index a0fe6c471444e..72f897c00550e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java @@ -106,14 +106,20 @@ public static RestResult getCall(HttpClient httpClient, String encodedUrl, @Null if (headers != null) { headers.forEach(request::header); } - ContentResponse response; + + if (logger.isTraceEnabled()) { + logger.trace("GET request: {}", request.getURI()); + } try { - response = request.send(); + ContentResponse response = request.send(); + + logger.trace("GET response: {}", response.getContentAsString()); + + return new RestResult(response.getContentAsString(), response.getStatus()); } catch (InterruptedException | TimeoutException | ExecutionException e) { logger.error("Exception occurred during GET execution: {}", e.getMessage(), e); throw new CommunicationException(e); } - return new RestResult(response.getContentAsString(), response.getStatus()); } @Nullable @@ -159,8 +165,16 @@ private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map if (headers != null) { headers.forEach(request::header); } - ContentResponse response = request.content(contentProvider).timeout(10, TimeUnit.SECONDS) + if (logger.isTraceEnabled()) { + logger.trace("POST request: {}", request.getURI()); + } + + ContentResponse response = request.content(contentProvider) + .timeout(10, TimeUnit.SECONDS) .send(); + + logger.trace("POST response: {}", response.getContentAsString()); + return new RestResult(response.getContentAsString(), response.getStatus()); } catch (TimeoutException e) { if (logger.isDebugEnabled()) { From a7e9163697471214c7ddf1475e2a25a71d6bb8be Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 18 Sep 2023 12:02:05 -0300 Subject: [PATCH 015/130] [lgthinq][fix] general binding fixes Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/pom.xml | 2 +- .../src/main/feature/feature.xml | 2 +- .../internal/LGThinQBindingConstants.java | 18 ++-- .../internal/LGThinQHandlerFactory.java | 19 ++-- .../internal/api/OauthLgEmpAuthenticator.java | 8 +- .../lgthinq/internal/api/RestUtils.java | 23 ++--- .../discovery/LGThinqDiscoveryService.java | 3 +- .../internal/errors/LGThinqApiException.java | 17 ++++ .../handler/LGThinQAbstractDeviceHandler.java | 44 ++++++--- .../handler/LGThinQAirConditionerHandler.java | 70 +++++++++++--- .../handler/LGThinQBridgeHandler.java | 13 +-- .../handler/LGThinQFridgeHandler.java | 13 +-- .../handler/LGThinQWasherDryerHandler.java | 5 +- .../LGThinQACApiV1ClientServiceImpl.java | 2 +- .../LGThinQACApiV2ClientServiceImpl.java | 4 +- .../LGThinQAbstractApiClientService.java | 12 ++- .../LGThinQAbstractApiV1ClientService.java | 12 ++- .../LGThinQAbstractApiV2ClientService.java | 17 ++-- .../LGThinQApiClientServiceFactory.java | 25 +++-- .../model/DefaultSnapshotBuilder.java | 4 + .../lgservices/model/FeatureDefinition.java | 1 - .../lgthinq/lgservices/model/ResultCodes.java | 16 ++-- .../model/devices/ac/ACCanonicalSnapshot.java | 95 +++++++++++++++++++ .../devices/ac/ACCapabilityFactoryV1.java | 5 + .../devices/ac/ACCapabilityFactoryV2.java | 5 + .../ac/AbstractACCapabilityFactory.java | 18 +++- .../fridge/FridgeCanonicalSnapshot.java | 3 +- .../fridge/FridgeCapabilityFactoryV1.java | 2 +- .../fridge/FridgeCapabilityFactoryV2.java | 2 +- .../washerdryer/WasherDryerCapability.java | 2 +- .../WasherDryerCapabilityFactoryV1.java | 2 +- .../WasherDryerCapabilityFactoryV2.java | 2 +- .../main/resources/OH-INF/thing/channels.xml | 42 +++++++- .../resources/OH-INF/thing/dish-washer.xml | 75 +++++++++++++++ .../src/main/resources/OH-INF/thing/dryer.xml | 61 ++++++++++++ .../main/resources/OH-INF/thing/heat-pump.xml | 65 ++++++++++++- .../resources/OH-INF/thing/washer-dryer.xml | 36 +++++++ .../lgthinq/handler/LGThinqBridgeTests.java | 9 +- 38 files changed, 621 insertions(+), 133 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml index 7c2667c0c65b1..2efd0b0a55675 100644 --- a/bundles/org.openhab.binding.lgthinq/pom.xml +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -14,7 +14,7 @@ openHAB Add-ons :: Bundles :: LG Thinq Binding - + com.fasterxml.jackson.core jackson-core ${jackson.version} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml index 5f83cb2f657c9..df53a90d37a9c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml @@ -1,7 +1,7 @@ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features - + openhab-runtime-base openhab.tp-jackson diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 13cb649d5d3cc..1cb34d0b1c8c8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -140,24 +140,28 @@ public class LGThinQBindingConstants { // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; + public static final String CHANNEL_AIR_WATER_SWITCH_ID = "hp_air_water_switch"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; public static final String CHANNEL_POWER_ID = "power"; public static final String CHANNEL_EXTENDED_INFO_COLLECTOR_ID = "extended_info_collector"; public static final String CHANNEL_CURRENT_POWER_ID = "current_power"; public static final String CHANNEL_REMAINING_FILTER_ID = "remaining_filter"; public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; + public static final String CHANNEL_MIN_TEMP_ID = "min_temperature"; + public static final String CHANNEL_MAX_TEMP_ID = "max_temperature"; public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; public static final String CHANNEL_COOL_JET_ID = "cool_jet"; public static final String CHANNEL_AIR_CLEAN_ID = "air_clean"; public static final String CHANNEL_AUTO_DRY_ID = "auto_dry"; public static final String CHANNEL_ENERGY_SAVING_ID = "energy_saving"; - public static final Map CAP_AC_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", - "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", - "@AC_MAIN_OPERATION_MODE_HEAT_W", "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", - "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", "@AC_MAIN_OPERATION_MODE_AI_W", "AI", - "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", - "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); + public static final String CAP_ACHP_OP_MODE_COOL_KEY = "@AC_MAIN_OPERATION_MODE_COOL_W"; + public static final String CAP_ACHP_OP_MODE_HEAT_KEY = "@AC_MAIN_OPERATION_MODE_HEAT_W"; + public static final Map CAP_AC_OP_MODE = Map.of(CAP_ACHP_OP_MODE_COOL_KEY, "Cool", + "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", CAP_ACHP_OP_MODE_HEAT_KEY, + "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", + "@AC_MAIN_OPERATION_MODE_AI_W", "AI", "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", + "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), @@ -174,6 +178,8 @@ public class LGThinQBindingConstants { Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); public static final Map CAP_AC_COOL_JET = Map.of("@COOL_JET", "Cool Jet"); + public static final Double CAP_HP_AIR_SWITCH = 0.0; + public static final Double CAP_HP_WATER_SWITCH = 1.0; // ======= RAC MODES public static final String CAP_AC_AUTODRY = "@AUTODRY"; public static final String CAP_AC_AUTODRY_NODE = "AutoDry"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index 5d6997f723dec..eda8c14e140be 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -48,9 +48,9 @@ public class LGThinQHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(LGThinQHandlerFactory.class); - - private HttpClientFactory httpClientFactory; - + + private HttpClientFactory httpClientFactory; + private final LGThinQStateDescriptionProvider stateDescriptionProvider; @Nullable @@ -63,12 +63,12 @@ public class LGThinQHandlerFactory extends BaseThingHandlerFactory { @Reference protected ItemChannelLinkRegistry itemChannelLinkRegistry; - @Activate - public LGThinQHandlerFactory(final @Reference LGThinQStateDescriptionProvider stateDescriptionProvider, - @Reference final HttpClientFactory httpClientFactory) { - this.stateDescriptionProvider = stateDescriptionProvider; - this.httpClientFactory = httpClientFactory; - } + @Activate + public LGThinQHandlerFactory(final @Reference LGThinQStateDescriptionProvider stateDescriptionProvider, + @Reference final HttpClientFactory httpClientFactory) { + this.stateDescriptionProvider = stateDescriptionProvider; + this.httpClientFactory = httpClientFactory; + } @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -115,5 +115,4 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } return null; } - } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java index 9c48a31c60370..6d7c694ecd9c5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java @@ -42,11 +42,11 @@ */ @NonNullByDefault public class OauthLgEmpAuthenticator { - - private static final Logger logger = LoggerFactory.getLogger(OauthLgEmpAuthenticator.class); + + private static final Logger logger = LoggerFactory.getLogger(OauthLgEmpAuthenticator.class); private static final Map oauthSearchKeyQueryParams = new LinkedHashMap<>(); private static final ObjectMapper objectMapper = new ObjectMapper(); - + static { oauthSearchKeyQueryParams.put("key_name", "OAUTH_SECRETKEY"); oauthSearchKeyQueryParams.put("sever_type", "OP"); @@ -55,7 +55,7 @@ public class OauthLgEmpAuthenticator { private HttpClient httpClient; public OauthLgEmpAuthenticator(HttpClient httpClient) { - this.httpClient = httpClient; + this.httpClient = httpClient; } static class PreLoginResult { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java index 72f897c00550e..e7af1f264ae11 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java @@ -123,8 +123,8 @@ public static RestResult getCall(HttpClient httpClient, String encodedUrl, @Null } @Nullable - public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, String jsonData) - throws IOException { + public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, + String jsonData) throws IOException { try { return postCall(httpClient, encodedUrl, headers, new StringContentProvider(jsonData)); } catch (UnsupportedEncodingException e) { @@ -137,8 +137,8 @@ public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map< } @Nullable - public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, Map formParams) - throws IOException { + public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, + Map formParams) throws IOException { Fields fields = new Fields(); formParams.forEach(fields::put); try { @@ -154,14 +154,12 @@ public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map< } @Nullable - private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, ContentProvider contentProvider) - throws IOException { + private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, + ContentProvider contentProvider) throws IOException { try { - Request request = httpClient.newRequest(encodedUrl) - .method("POST") - .content(contentProvider) - .timeout(10, TimeUnit.SECONDS); + Request request = httpClient.newRequest(encodedUrl).method("POST").content(contentProvider).timeout(10, + TimeUnit.SECONDS); if (headers != null) { headers.forEach(request::header); } @@ -169,9 +167,7 @@ private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map logger.trace("POST request: {}", request.getURI()); } - ContentResponse response = request.content(contentProvider) - .timeout(10, TimeUnit.SECONDS) - .send(); + ContentResponse response = request.content(contentProvider).timeout(10, TimeUnit.SECONDS).send(); logger.trace("POST response: {}", response.getContentAsString()); @@ -194,5 +190,4 @@ private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map throw new CommunicationException(e); } } - } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index 266cfd6135629..2b8a643d31607 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -65,7 +65,8 @@ public void setThingHandler(@Nullable ThingHandler handler) { if (handler instanceof LGThinQBridgeHandler) { bridgeHandler = (LGThinQBridgeHandler) handler; bridgeHandlerUID = handler.getThing().getUID(); - lgApiClientService = LGThinQApiClientServiceFactory.newGeneralApiClientService(bridgeHandler.getHttpClientFactory()); + lgApiClientService = LGThinQApiClientServiceFactory + .newGeneralApiClientService(bridgeHandler.getHttpClientFactory()); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java index cf3d44f71a61c..58bc52b4189db 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java @@ -13,6 +13,7 @@ package org.openhab.binding.lgthinq.internal.errors; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; /** * The {@link LGThinqApiException} @@ -21,11 +22,27 @@ */ @NonNullByDefault public class LGThinqApiException extends LGThinqException { + protected ResultCodes apiReasonCode = ResultCodes.UNKNOWN; + public LGThinqApiException(String message, Throwable cause) { super(message, cause); } + public LGThinqApiException(String message, Throwable cause, ResultCodes reasonCode) { + super(message, cause); + this.apiReasonCode = reasonCode; + } + + public ResultCodes getApiReasonCode() { + return apiReasonCode; + } + public LGThinqApiException(String message) { super(message); } + + public LGThinqApiException(String message, ResultCodes resultCode) { + super(message); + this.apiReasonCode = resultCode; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 6522007fa299c..0c67e9f542f10 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -14,6 +14,9 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import java.lang.management.ManagementFactory; +import java.lang.management.ThreadInfo; +import java.lang.management.ThreadMXBean; import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; import java.util.*; @@ -47,8 +50,8 @@ @NonNullByDefault public abstract class LGThinQAbstractDeviceHandler extends BaseThingWithExtraInfoHandler { - private final Logger logger = LoggerFactory.getLogger(LGThinQAbstractDeviceHandler.class); - protected @Nullable LGThinQBridgeHandler account; + private final Logger logger = LoggerFactory.getLogger(LGThinQAbstractDeviceHandler.class); + protected @Nullable LGThinQBridgeHandler account; protected final String lgPlatformType; @Nullable private S lastShot; @@ -211,7 +214,17 @@ public void handleCommand(ChannelUID channelUID, Command command) { commandBlockQueue.add(params); } catch (IllegalStateException ex) { getLogger().error( - "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); + "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command. Above the ThreadDump to analise the stuck"); + getLogger().error("Status of the commandQueue: consumer: {}, size: {}", + commandExecutorQueueJob == null || commandExecutorQueueJob.isDone() ? "OFF" : "ON", + commandBlockQueue.size()); + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] infos = bean.dumpAllThreads(true, true); + String message = ""; + for (ThreadInfo i : infos) { + message = String.format("%s\n%s", message, i.toString()); + } + logger.error("{}", message); updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, "Device Command Queue is Busy"); } @@ -295,7 +308,7 @@ public void initialize() { initializeThing(bridge.getStatus()); } - + protected void initializeThing(@Nullable ThingStatus bridgeStatus) { getLogger().debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); String thingId = getThing().getUID().getId(); @@ -315,7 +328,7 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { if (account == null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Account not set"); } else { - account.registryListenerThing(this); + account.registryListenerThing(this); switch (bridgeStatus) { case ONLINE: updateStatus(ThingStatus.ONLINE); @@ -358,7 +371,7 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { startThingStatePolling(); } } - + public void refreshStatus() { if (thing.getStatus() == ThingStatus.OFFLINE) { initialize(); @@ -497,20 +510,20 @@ private void handlePowerChange(@Nullable DevicePowerState previous, DevicePowerS // no changes needed return; } - + // change from OFF to ON / OFF to ON boolean isEnableToStartCollector = isExtraInfoCollectorEnabled() && isExtraInfoCollectorSupported(); - + if (current == DevicePowerState.DV_POWER_ON) { currentPeriodSeconds = pollingPeriodOnSeconds; - + // if extendedInfo collector is enabled, then force do start to prevent previous stop if (isEnableToStartCollector) { startExtraInfoCollectorPolling(); } } else { currentPeriodSeconds = pollingPeriodOffSeconds; - + // if it's configured to stop extra-info collection on PowerOff, then stop the job if (!pollExtraInfoOnPowerOff) { stopExtraInfoCollectorPolling(); @@ -518,12 +531,12 @@ private void handlePowerChange(@Nullable DevicePowerState previous, DevicePowerS startExtraInfoCollectorPolling(); } } - + // restart thing state polling for the new poolingPeriod configuration if (pollingPeriodOffSeconds != pollingPeriodOnSeconds) { stopThingStatePolling(); } - + startThingStatePolling(); } @@ -729,6 +742,9 @@ protected Runnable getQueuedCommandExecutor() { getLogger().error("Error executing Command {} to the channel {}. Thing goes offline until retry", params.command, params.channelUID, e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } catch (Exception e) { + getLogger().error("System error executing Command {} to the channel {}. Ignoring command", + params.command, params.channelUID, e); } } }; @@ -736,11 +752,11 @@ protected Runnable getQueuedCommandExecutor() { @Override public void dispose() { logger.debug("Disposing Thinq Thing {}", getDeviceId()); - + if (account != null) { account.unRegistryListenerThing(this); } - + stopThingStatePolling(); stopExtraInfoCollectorPolling(); stopCommandExecutorQueueJob(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 306f754e6709d..898c1fe433e98 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -44,9 +44,7 @@ import org.openhab.core.thing.Thing; import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.thing.link.ItemChannelLinkRegistry; -import org.openhab.core.types.Command; -import org.openhab.core.types.StateOption; -import org.openhab.core.types.UnDefType; +import org.openhab.core.types.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -66,9 +64,12 @@ public class LGThinQAirConditionerHandler extends LGThinQAbstractDeviceHandler maxTempConstraint || targetTemp < minTempConstraint) { + // values out of range + logger.error("Target Temperature: {} is out of range: {} - {}. Ignoring command", targetTemp, + minTempConstraint, maxTempConstraint); + break; + } lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), ACTargetTmp.statusOf(targetTemp)); break; @@ -343,7 +387,7 @@ protected boolean isExtraInfoCollectorSupported() { try { return getCapabilities().isEnergyMonitorAvailable() || getCapabilities().isFilterMonitorAvailable(); } catch (LGThinqApiException e) { - logger.warn("Can't get capabilities of the device: {}", getDeviceId()); + logger.warn("Can't get capabilities of the device: {}", getDeviceId()); } return false; } @@ -362,7 +406,7 @@ protected Map collectExtraInfoState() throws LGThinqException { @Override protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { - logger.debug("Calling updateExtraInfoStateChannels for device: {}", getDeviceId()); + logger.debug("Calling updateExtraInfoStateChannels for device: {}", getDeviceId()); String instantPowerConsumption = (String) energyStateAttributes.get(EXTENDED_ATTR_INSTANT_POWER); String filterUsed = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_USED_TIME); String filterTimelife = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index fd7a5523c08fa..bc861493a2581 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -82,7 +82,7 @@ public LGThinQBridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory) lgApiClient = LGThinQApiClientServiceFactory.newGeneralApiClientService(httpClientFactory); lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); } - + public HttpClientFactory getHttpClientFactory() { return httpClientFactory; } @@ -148,7 +148,8 @@ public void run() { try { doConnectedRun(); } catch (Exception e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.lgapi-getting-devices"); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/error.lgapi-getting-devices"); } } finally { @@ -176,7 +177,7 @@ public void registerDiscoveryListener(LGThinqDiscoveryService listener) { public Collection> getServices() { return Collections.singleton(LGThinqDiscoveryService.class); } - + @Override public void registryListenerThing(LGThinQAbstractDeviceHandler thing) { if (lGDeviceRegister.get(thing.getDeviceId()) == null) { @@ -240,7 +241,7 @@ protected void doConnectedRun() throws LGThinqException { discoveryService.removeLgDeviceDiscovery(device); } }); - + lGDeviceRegister.values().forEach(LGThinQAbstractDeviceHandler::refreshStatus); } }; @@ -340,9 +341,9 @@ private void startLGThinqDevicePolling() { devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, 2, poolingInterval, TimeUnit.SECONDS); } - + public void runDiscovery() { - scheduler.submit(lgDevicePollingRunnable); + scheduler.submit(lgDevicePollingRunnable); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index e63f4efb9124c..935a648312bfa 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -17,12 +17,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.concurrent.ScheduledFuture; import javax.measure.quantity.Temperature; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -66,12 +64,12 @@ public class LGThinQFridgeHandler extends LGThinQAbstractDeviceHandler thingStatePollingJob; public LGThinQFridgeHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, ItemChannelLinkRegistry itemChannelLinkRegistry, HttpClientFactory httpClientFactory) { super(thing, stateDescriptionProvider, itemChannelLinkRegistry); - lgThinqFridgeApiClientService = LGThinQApiClientServiceFactory.newFridgeApiClientService(lgPlatformType, httpClientFactory); + lgThinqFridgeApiClientService = LGThinQApiClientServiceFactory.newFridgeApiClientService(lgPlatformType, + httpClientFactory); channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); channelGroupExtendedInfoUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_EXTENDED_INFO_GRP_ID); fridgeTempChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_FRIDGE_TEMP_ID); @@ -142,13 +140,6 @@ protected Logger getLogger() { return logger; } - protected void stopThingStatePolling() { - if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { - logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); - thingStatePollingJob.cancel(true); - } - } - protected DeviceTypes getDeviceType() { return DeviceTypes.AIR_CONDITIONER; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index 79ec3e02cb4c2..d3ee90e7beb58 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -26,10 +26,10 @@ import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.*; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.*; import org.openhab.core.io.net.http.HttpClientFactory; @@ -89,7 +89,8 @@ public LGThinQWasherDryerHandler(Thing thing, LGThinQStateDescriptionProvider st this.thinqChannelGroupProvider = channelGroupTypeProvider; this.thinqChannelProvider = channelTypeProvider; this.stateDescriptionProvider = stateDescriptionProvider; - lgThinqWMApiClientService = LGThinQApiClientServiceFactory.newWMApiClientService(lgPlatformType, httpClientFactory); + lgThinqWMApiClientService = LGThinQApiClientServiceFactory.newWMApiClientService(lgPlatformType, + httpClientFactory); channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_GRP_ID); channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); courseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_COURSE_ID); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java index 204db5a79df4b..04c3affb53dd7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java @@ -44,7 +44,7 @@ public class LGThinQACApiV1ClientServiceImpl extends LGThinQAbstractApiV1ClientService implements LGThinQACApiClientService { - private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV1ClientServiceImpl.class); + private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV1ClientServiceImpl.class); protected LGThinQACApiV1ClientServiceImpl(HttpClient httpClient) { super(ACCapability.class, ACCanonicalSnapshot.class, httpClient); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java index cfad283a5c42e..ef6b304ef6de5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -45,13 +45,13 @@ public class LGThinQACApiV2ClientServiceImpl extends LGThinQAbstractApiV2ClientService implements LGThinQACApiClientService { - private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV2ClientServiceImpl.class); + private static final Logger logger = LoggerFactory.getLogger(LGThinQACApiV2ClientServiceImpl.class); protected LGThinQACApiV2ClientServiceImpl(HttpClient httpClient) { super(ACCapability.class, ACCanonicalSnapshot.class, httpClient); } - @Override + @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { try { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index de3223dcfbe05..cb20d6c2ae6e0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -161,12 +161,14 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo String resultCode = "???"; if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { - logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", + resp.getStatusCode(), resp.getJsonResponse()); return Collections.emptyMap(); } try { if (resp.getStatusCode() == 400) { - logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", + resp.getStatusCode(), resp.getJsonResponse()); return Collections.emptyMap(); } respMap = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { @@ -212,10 +214,12 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG List devices; if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { - logger.warn("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + logger.warn("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", + resp.getStatusCode(), resp.getJsonResponse()); return Collections.emptyList(); } - logger.error("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + logger.error("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", + resp.getStatusCode(), resp.getJsonResponse()); throw new LGThinqApiException(String .format("Error calling device list from LG Server API. The reason is: %s", resp.getJsonResponse())); } else { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index 97f8b19c088ab..10c1ade589a29 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -49,7 +49,8 @@ public abstract class LGThinQAbstractApiV1ClientService { private static final Logger logger = LoggerFactory.getLogger(LGThinQAbstractApiV1ClientService.class); - protected LGThinQAbstractApiV1ClientService(Class capabilityClass, Class snapshotClass, HttpClient httpClient) { + protected LGThinQAbstractApiV1ClientService(Class capabilityClass, Class snapshotClass, + HttpClient httpClient) { super(capabilityClass, snapshotClass, httpClient); } @@ -117,11 +118,14 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp } if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { - logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), + resp.getJsonResponse()); } else { - logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", + resp.getStatusCode(), resp.getJsonResponse()); throw new LGThinqApiException( - String.format("Error returned by LG Server API. HTTP Status: %s. The reason is: %s", resp.getStatusCode(), resp.getJsonResponse())); + String.format("Error returned by LG Server API. HTTP Status: %s. The reason is: %s", + resp.getStatusCode(), resp.getJsonResponse())); } } else { try { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java index be0340a4f53c5..fa9629d92f6fd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -48,11 +48,12 @@ public abstract class LGThinQAbstractApiV2ClientService { private static final Logger logger = LoggerFactory.getLogger(LGThinQAbstractApiV2ClientService.class); - protected LGThinQAbstractApiV2ClientService(Class capabilityClass, Class snapshotClass, HttpClient httpClient) { + protected LGThinQAbstractApiV2ClientService(Class capabilityClass, Class snapshotClass, + HttpClient httpClient) { super(capabilityClass, snapshotClass, httpClient); } - @Override + @Override protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, String keyName, String value) throws Exception { return sendCommand(bridgeName, deviceId, controlPath, controlKey, command, keyName, value, null); @@ -67,7 +68,8 @@ protected RestResult postCall(String bridgeName, String deviceId, String control token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); RestResult resp = RestUtils.postCall(httpClient, builder.build().toURL().toString(), headers, payload); if (resp == null) { - logger.warn("Null result returned sending command to LG API V2: {}, {}, {}", deviceId, controlPath, payload); + logger.warn("Null result returned sending command to LG API V2: {}, {}, {}", deviceId, controlPath, + payload); throw new LGThinqApiException("Null result returned sending command to LG API V2"); } return resp; @@ -98,12 +100,15 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp } if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { - logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), + resp.getJsonResponse()); return Collections.emptyMap(); } else { - logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); + logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", + resp.getStatusCode(), resp.getJsonResponse()); throw new LGThinqApiException( - String.format("Error returned by LG Server API. HTTP Status: %s. The reason is: %s", resp.getStatusCode(), resp.getJsonResponse())); + String.format("Error returned by LG Server API. HTTP Status: %s. The reason is: %s", + resp.getStatusCode(), resp.getJsonResponse())); } } else { try { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java index 569b261b2f706..c7beb32880305 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java @@ -40,48 +40,57 @@ public static LGThinQGeneralApiClientService newGeneralApiClientService(HttpClie return new LGThinQGeneralApiClientService(httpClientFactory.getCommonHttpClient()); } - public static LGThinQACApiClientService newACApiClientService(String lgPlatformType, HttpClientFactory httpClientFactory) { + public static LGThinQACApiClientService newACApiClientService(String lgPlatformType, + HttpClientFactory httpClientFactory) { return lgPlatformType.equals(PLATFORM_TYPE_V1) ? new LGThinQACApiV1ClientServiceImpl(httpClientFactory.getCommonHttpClient()) : new LGThinQACApiV2ClientServiceImpl(httpClientFactory.getCommonHttpClient()); } - public static LGThinQFridgeApiClientService newFridgeApiClientService(String lgPlatformType, HttpClientFactory httpClientFactory) { + public static LGThinQFridgeApiClientService newFridgeApiClientService(String lgPlatformType, + HttpClientFactory httpClientFactory) { return lgPlatformType.equals(PLATFORM_TYPE_V1) ? new LGThinQFridgeApiV1ClientServiceImpl(httpClientFactory.getCommonHttpClient()) : new LGThinQFridgeApiV2ClientServiceImpl(httpClientFactory.getCommonHttpClient()); } - public static LGThinQWMApiClientService newWMApiClientService(String lgPlatformType, HttpClientFactory httpClientFactory) { + public static LGThinQWMApiClientService newWMApiClientService(String lgPlatformType, + HttpClientFactory httpClientFactory) { return lgPlatformType.equals(PLATFORM_TYPE_V1) ? new LGThinQWMApiV1ClientServiceImpl(httpClientFactory.getCommonHttpClient()) : new LGThinQWMApiV2ClientServiceImpl(httpClientFactory.getCommonHttpClient()); } @NonNullByDefault - public static final class LGThinQGeneralApiClientService extends LGThinQAbstractApiClientService { + public static final class LGThinQGeneralApiClientService + extends LGThinQAbstractApiClientService { private LGThinQGeneralApiClientService(HttpClient httpClient) { super(GenericCapability.class, AbstractSnapshotDefinition.class, httpClient); } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { throw new UnsupportedOperationException(); } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) + throws LGThinqApiException { throw new UnsupportedOperationException(); } @Override - protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, String keyName, String value) throws Exception { + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, String keyName, String value) throws Exception { throw new UnsupportedOperationException(); } @Override - protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) throws Exception { + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) + throws Exception { throw new UnsupportedOperationException(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java index cfb689c026093..9b0241586aa02 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java @@ -83,6 +83,10 @@ public S createFromBinary(String binaryData, List prot } } for (MonitoringBinaryProtocol protField : prot) { + if (protField.startByte + protField.length > data.length) { + // end of data. If have more fields in the protocol, will be ignored + break; + } String fName = protField.fieldName; int value = 0; for (int i = protField.startByte; i < protField.startByte + protField.length; i++) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java index b5ae2816c8e32..a16f5de3c6d54 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java @@ -18,7 +18,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; /** * The {@link FeatureDefinition} defines the feature definitions extracted from the capability files in diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java index a13668aa57e61..b31995bd79851 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java @@ -28,9 +28,9 @@ public enum ResultCodes { OK("Success", "0000", "0001"), DEVICE_NOT_RESPONSE("Device Not Response", "0111", "0103", "0104", "0106"), PORTAL_INTERWORKING_ERROR("Portal Internal Error", "0007"), - LOGIN_FAILED( - "Login/Session Failed, Duplicated or Terms Not Agreed. Try to login and correct issues direct on LG Account Portal", - "0004", "0102", "0110", "0114"), + LOGIN_DUPLICATED("Login Duplicated", "0004"), + UPDATE_TERMS_NEEDED("Update Agreement Terms in LG App", "0110"), + LOGIN_FAILED("Login/Session Failed. Try to login and correct issues direct on LG Account Portal", "0102", "0114"), BASE64_CODING_ERROR("Base64 Decoding/Encoding error", "9002", "9001"), NOT_SUPPORTED_CONTROL("Command/Control/Service is not supported", "0005", "0012", "8001"), CONTROL_ERROR("Error in device control", "0105"), @@ -91,10 +91,12 @@ public static ResultCodes fromCode(String code) { return INVALID_PAYLOAD; case "0003": return INVALID_HEAD; - case "0004": - case "0102": - case "0110": // Email duplicated - case "0114": + case "0110": // Update Terms + return UPDATE_TERMS_NEEDED; + case "0004": // Duplicated Login + return LOGIN_DUPLICATED; + case "0102": // Not Logged in + case "0114": // Mismatch Login Session return LOGIN_FAILED; case "0100": return GENERAL_FAILURE; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java index aa9779dfaa126..87fd9bdb85267 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java @@ -31,6 +31,18 @@ @JsonIgnoreProperties(ignoreUnknown = true) public class ACCanonicalSnapshot extends AbstractSnapshotDefinition { + // ============ FOR HEAT PUMP ONLY =============== + private double hpWaterTempCoolMin; + private double hpWaterTempCoolMax; + private double hpWaterTempHeatMin; + private double hpWaterTempHeatMax; + private double hpAirTempCoolMin; + private double hpAirTempCoolMax; + private double hpAirTempHeatMin; + private double hpAirTempHeatMax; + private double hpAirWaterTempSwitch = -1; + // =============================================== + private int airWindStrength; private double targetTemperature; @@ -176,6 +188,89 @@ public void setOnline(boolean online) { this.online = online; } + // ==================== For HP only + @JsonProperty("airState.tempState.waterTempCoolMin") + public double getHpWaterTempCoolMin() { + return hpWaterTempCoolMin; + } + + public void setHpWaterTempCoolMin(double hpWaterTempCoolMin) { + this.hpWaterTempCoolMin = hpWaterTempCoolMin; + } + + @JsonProperty("airState.tempState.waterTempCoolMax") + public double getHpWaterTempCoolMax() { + return hpWaterTempCoolMax; + } + + public void setHpWaterTempCoolMax(double hpWaterTempCoolMax) { + this.hpWaterTempCoolMax = hpWaterTempCoolMax; + } + + @JsonProperty("airState.tempState.waterTempHeatMin") + public double getHpWaterTempHeatMin() { + return hpWaterTempHeatMin; + } + + public void setHpWaterTempHeatMin(double hpWaterTempHeatMin) { + this.hpWaterTempHeatMin = hpWaterTempHeatMin; + } + + @JsonProperty("airState.tempState.waterTempHeatMax") + public double getHpWaterTempHeatMax() { + return hpWaterTempHeatMax; + } + + public void setHpWaterTempHeatMax(double hpWaterTempHeatMax) { + this.hpWaterTempHeatMax = hpWaterTempHeatMax; + } + + @JsonProperty("airState.tempState.airTempCoolMin") + public double getHpAirTempCoolMin() { + return hpAirTempCoolMin; + } + + public void setHpAirTempCoolMin(double hpAirTempCoolMin) { + this.hpAirTempCoolMin = hpAirTempCoolMin; + } + + @JsonProperty("airState.tempState.airTempCoolMax") + public double getHpAirTempCoolMax() { + return hpAirTempCoolMax; + } + + public void setHpAirTempCoolMax(double hpAirTempCoolMax) { + this.hpAirTempCoolMax = hpAirTempCoolMax; + } + + @JsonProperty("airState.tempState.airTempHeatMin") + public double getHpAirTempHeatMin() { + return hpAirTempHeatMin; + } + + public void setHpAirTempHeatMin(double hpAirTempHeatMin) { + this.hpAirTempHeatMin = hpAirTempHeatMin; + } + + @JsonProperty("airState.tempState.airTempHeatMax") + public double getHpAirTempHeatMax() { + return hpAirTempHeatMax; + } + + public void setHpAirTempHeatMax(double hpAirTempHeatMax) { + this.hpAirTempHeatMax = hpAirTempHeatMax; + } + + @JsonProperty("airState.miscFuncState.awhpTempSwitch") + public double getHpAirWaterTempSwitch() { + return hpAirWaterTempSwitch; + } + + public void setHpAirWaterTempSwitch(double hpAirWaterTempSwitch) { + this.hpAirWaterTempSwitch = hpAirWaterTempSwitch; + } + // =================================== + @Override public String toString() { return "ACSnapShot{" + "airWindStrength=" + airWindStrength + ", targetTemperature=" + targetTemperature diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java index 586215de88ac5..4554f95c87512 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java @@ -125,4 +125,9 @@ protected String getOptionsMapNodeName() { protected String getValuesNodeName() { return "Value"; } + + @Override + protected String getHpAirWaterSwitchNodeName() { + throw new UnsupportedOperationException("Heat Pump Thinq V1 not implemented yet! Ignoring node"); + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java index 8d97915d9ac52..bd378a1fb7e69 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java @@ -132,6 +132,11 @@ protected Map extractFeatureOptions(JsonNode optionsNode) { return options; } + @Override + protected String getHpAirWaterSwitchNodeName() { + return "airState.miscFuncState.awhpTempSwitch"; + } + @Override public ACCapability create(JsonNode rootNode) throws LGThinqException { ACCapability cap = super.create(rootNode); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java index 836b61a39f27c..7ea03f38947a1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java @@ -21,10 +21,10 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -171,7 +171,7 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { valuesNode.path(getAutoDryStateNodeName()).path(getOptionsMapNodeName())); if (!dryStates.isEmpty()) { // sanity check acCap.setAutoDryModeAvailable(true); - dryStates.forEach((cmdValue, cmdKey) -> { + dryStates.forEach((cmdKey, cmdValue) -> { switch (cmdKey) { case CAP_AC_COMMAND_OFF: acCap.setAutoDryModeCommandOff(cmdValue); @@ -207,7 +207,18 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { } }); } + if (HEAT_PUMP.equals(acCap.getDeviceType())) { + JsonNode supHpAirSwitchNode = valuesNode.path(getHpAirWaterSwitchNodeName()).path(getOptionsMapNodeName()); + if (!supHpAirSwitchNode.isMissingNode()) { + supHpAirSwitchNode.fields().forEachRemaining(r -> { + String racOpValue = r.getValue().asText(); + }); + } + } + if (!supRACModeOps.isMissingNode()) { + + } JsonNode infoNode = rootNode.get("Info"); if (infoNode.isMissingNode()) { logger.warn("No info session defined in the cap data."); @@ -247,4 +258,7 @@ public ACCapability getCapabilityInstance() { } protected abstract String getValuesNodeName(); + + // ===== For HP only ==== + protected abstract String getHpAirWaterSwitchNodeName(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java index 4b8579f8651a3..b56570796e545 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java @@ -101,12 +101,11 @@ public void setFreezerTemp(Double freezerTemp) { @Override public DevicePowerState getPowerStatus() { - throw new IllegalStateException("Fridge has no Power state."); + return isOnline() ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF; } @Override public void setPowerStatus(DevicePowerState value) { - throw new IllegalStateException("Fridge has no Power state."); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index a9b927cf600d1..bb72454eb3e83 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -18,8 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import com.fasterxml.jackson.databind.JsonNode; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java index 0d7351d5d3019..b2b2268f9cdac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -18,8 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import com.fasterxml.jackson.databind.JsonNode; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java index ff937e8bedb7f..52cdf5e88068d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java @@ -18,9 +18,9 @@ import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; /** * The {@link WasherDryerCapability} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java index 363f301d5eb0f..6e5ffb410ca94 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -19,9 +19,9 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; import org.slf4j.Logger; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java index 8c72b1c7d33ee..c6f7dccdd5f33 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java @@ -17,9 +17,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; import org.slf4j.Logger; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 4b28325a0ce1c..c4dc800a50d8b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -20,6 +20,18 @@ + + Number + + Define the Temperature Selector based on Water/Air. + + + + + + + + String @@ -39,7 +51,7 @@ - + @@ -84,13 +96,37 @@ Target temperature. Temperature - + + + + + Number:Temperature + + Minimum temperature for this mode. + Temperature + + + + + Number:Temperature + + Maximum Temperature for this mode. + Temperature + Number:Energy - Current Power Consumption + Current Power Consumption (kWh) + Energy + + + + + Number:Power + + Current Power Consumption (W) Energy diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml new file mode 100644 index 0000000000000..6459e6d450321 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + LG ThinQ Dish Washer + + + + + + + + Settings required to optimize the polling behaviour. + false + + + + Seconds to wait to the next polling when device is off. Useful to save up + i/o and cpu when your + device is + not working. If you use only this binding to control the + device, you can put higher values here. + + 10 + + + + Seconds to wait to the next polling for device state (dashboard channels) + + 10 + + + + + + + This is the Displayed Information. + + + + + + + + + + + + + + + + + + + Show more information about the device. + + + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml index 6ef935f9beaed..035410a3f98b9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml @@ -12,12 +12,73 @@ LG ThinQ Dryer + + + + + + + + Settings required to optimize the polling behaviour. + false + + + + Seconds to wait to the next polling when device is off. Useful to save up + i/o and cpu when your + device is + not working. If you use only this binding to control the + device, you can put higher values here. + + 10 + + + + Seconds to wait to the next polling for device state (dashboard channels) + + 10 + + + + + + + + + LG ThinQ Dryer Tower + + + + Settings required to optimize the polling behaviour. + false + + + + Seconds to wait to the next polling when device is off. Useful to save up + i/o and cpu when your + device is + not working. If you use only this binding to control the + device, you can put higher values here. + + 10 + + + + Seconds to wait to the next polling for device state (dashboard channels) + + 10 + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml index c3a2026b334ef..5bcdc2e6875c1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -4,7 +4,7 @@ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0" xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + @@ -13,13 +13,74 @@ LG ThinQ Heat Pump + + + + + + + + Settings required to optimize the polling behaviour. + false + + + + Seconds to wait to the next polling when device is off. Useful to save up + i/o and cpu when your + device is + not working. If you use only this binding to control the + device, you can put higher values here. + + 10 + + + + Seconds to wait to the next polling for device state (dashboard channels) + + 10 + + + + Seconds to wait to the next polling for Device's Extra Info (energy consumption, + remaining filter, etc) + + 60 + + + + If enables, extra info will be fetched even when the device is powered off. + It's not so common, since + extra info are normally changed only when the device is running. + + false + + + + + + + This is the Displayed Information. + + + + - + + + Show more information about the device. + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml index 3ee7551082ac4..c14d280cd7ac8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml @@ -42,7 +42,43 @@ + + + + + + LG ThinQ Washing Tower + + + + + + + + Settings required to optimize the polling behaviour. + false + + + + Seconds to wait to the next polling when device is off. Useful to save up + i/o and cpu when your + device is + not working. If you use only this binding to control the + device, you can put higher values here. + + 10 + + + + Seconds to wait to the next polling for device state (dashboard channels) + + 10 + + + Remote Start Actions and Options. diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java index d960d81060ae9..bfce923b8f0ca 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java @@ -87,8 +87,10 @@ private String getCurrentTimestamp() { @Test public void testDiscoveryACThings() { setupAuthenticationMock(); - LGThinQApiClientService service1 = LGThinQApiClientServiceFactory.newACApiClientService(PLATFORM_TYPE_V1, mock(HttpClientFactory.class)); - LGThinQApiClientService service2 = LGThinQApiClientServiceFactory.newACApiClientService(PLATFORM_TYPE_V2, mock(HttpClientFactory.class)); + LGThinQApiClientService service1 = LGThinQApiClientServiceFactory.newACApiClientService(PLATFORM_TYPE_V1, + mock(HttpClientFactory.class)); + LGThinQApiClientService service2 = LGThinQApiClientServiceFactory.newACApiClientService(PLATFORM_TYPE_V2, + mock(HttpClientFactory.class)); try { List devices = service2.listAccountDevices("bridgeTest"); assertEquals(devices.size(), 2); @@ -191,7 +193,8 @@ public void testDiscoveryWMThings() { LGThinQBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing, mock(HttpClientFactory.class)); - final LGThinQWMApiClientService service2 = LGThinQApiClientServiceFactory.newWMApiClientService(PLATFORM_TYPE_V1, mock(HttpClientFactory.class)); + final LGThinQWMApiClientService service2 = LGThinQApiClientServiceFactory + .newWMApiClientService(PLATFORM_TYPE_V1, mock(HttpClientFactory.class)); TokenManager tokenManager = new TokenManager(mock(HttpClient.class)); try { if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { From 4ad0683ceb6528f64dd1efbc5ade4912ff36578e Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 17 Oct 2023 10:37:13 -0300 Subject: [PATCH 016/130] [lgthinq][fix] Fixed temperatures mapping in Refrigerators [feat] new channels and set temperatures command for Refrigerator. Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/pom.xml | 2 +- .../internal/LGThinQBindingConstants.java | 193 +++++--- .../lgthinq/internal/api/LGThinqGateway.java | 10 + .../lgthinq/internal/api/TokenManager.java | 6 + .../handler/LGThinQAbstractDeviceHandler.java | 16 +- .../handler/LGThinQDishWasherHandler.java | 427 ++++++++++++++++++ .../handler/LGThinQFridgeHandler.java | 233 +++++++++- .../LGThinQAbstractApiClientService.java | 6 +- .../LGThinQAbstractApiV1ClientService.java | 47 +- .../LGThinQFridgeApiClientService.java | 10 + .../LGThinQFridgeApiV1ClientServiceImpl.java | 51 +++ .../LGThinQFridgeApiV2ClientServiceImpl.java | 35 ++ .../LGThinQWMApiV1ClientServiceImpl.java | 23 +- .../model/AbstractCapabilityFactory.java | 57 ++- .../lgthinq/lgservices/model/ResultCodes.java | 19 +- .../AbstractDishWasherCapabilityFactory.java | 199 ++++++++ .../devices/dishwasher/CourseDefinition.java | 64 +++ .../devices/dishwasher/CourseFunction.java | 63 +++ .../model/devices/dishwasher/CourseType.java | 39 ++ .../dishwasher/DishWasherCapability.java | 234 ++++++++++ .../DishWasherCapabilityFactoryV1.java | 231 ++++++++++ .../DishWasherCapabilityFactoryV2.java | 271 +++++++++++ .../dishwasher/DishWasherSnapshot.java | 318 +++++++++++++ .../dishwasher/DishWasherSnapshotBuilder.java | 123 +++++ .../AbstractFridgeCapabilityFactory.java | 87 ++-- .../fridge/FridgeCanonicalCapability.java | 54 +++ .../fridge/FridgeCanonicalSnapshot.java | 34 ++ .../devices/fridge/FridgeCapability.java | 19 + .../fridge/FridgeCapabilityFactoryV1.java | 130 +++++- .../fridge/FridgeCapabilityFactoryV2.java | 104 ++++- .../WasherDryerCapabilityFactoryV1.java | 46 +- .../main/resources/OH-INF/thing/channels.xml | 57 ++- .../main/resources/OH-INF/thing/fridge.xml | 7 +- .../lgthinq/handler/LGThinqBridgeTests.java | 2 +- 34 files changed, 2979 insertions(+), 238 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseFunction.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseType.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml index 2efd0b0a55675..b5680812010ce 100644 --- a/bundles/org.openhab.binding.lgthinq/pom.xml +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -6,7 +6,7 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.0.0-SNAPSHOT + 4.1.0-SNAPSHOT org.openhab.binding.lgthinq diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 1cb34d0b1c8c8..b447272787399 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.lgthinq.internal; +import static java.util.Map.entry; + import java.io.File; import java.util.Map; import java.util.Set; @@ -128,14 +130,62 @@ public class LGThinQBindingConstants { // public static final String CHANNEL_COOL_JET_ID = "cool_jet"; public static final Double FRIDGE_TEMPERATURE_IGNORE_VALUE = 255.0; public static final Double FREEZER_TEMPERATURE_IGNORE_VALUE = 255.0; - public static final String CHANNEL_FRIDGE_TEMP_ID = "fridge-temperature"; - public static final String CHANNEL_FREEZER_TEMP_ID = "freezer-temperature"; - public static final String CHANNEL_REF_TEMP_UNIT = "temp-unit"; + public static final String FR_CHANNEL_FRIDGE_TEMP_ID = "fridge-temperature"; + public static final String FR_CHANNEL_FREEZER_TEMP_ID = "freezer-temperature"; + public static final String FR_CHANNEL_REF_TEMP_UNIT = "temp-unit"; public static final String TEMP_UNIT_CELSIUS = "CELSIUS"; public static final String TEMP_UNIT_FAHRENHEIT = "FAHRENHEIT"; public static final String TEMP_UNIT_CELSIUS_SYMBOL = "°C"; public static final String TEMP_UNIT_FAHRENHEIT_SYMBOL = "°F"; + + public static final String FR_CHANNEL_ICE_PLUS = "fr-ice-plus"; + public static final String FR_CHANNEL_EXPRESS_MODE = "fr-express-mode"; + public static final String FR_CHANNEL_SMART_SAVING_MODE_V2 = "fr-smart-saving-mode"; + public static final String FR_CHANNEL_SMART_SAVING_SWITCH_V1 = "fr-smart-saving-switch"; + public static final String FR_CHANNEL_ACTIVE_SAVING = "fr-active-saving"; + public static final String FR_CHANNEL_FRESH_AIR_FILTER = "fr_fresh_air_filter"; + public static final String FR_CHANNEL_WATER_FILTER = "fr_water_filter"; + public static final Set CELSIUS_UNIT_VALUES = Set.of("01", "1", "C", "CELSIUS", TEMP_UNIT_CELSIUS_SYMBOL); + public static final Set FAHRENHEIT_UNIT_VALUES = Set.of("02", "2", "F", "FAHRENHEIT", + TEMP_UNIT_FAHRENHEIT_SYMBOL); + public static final Set DOOR_OPEN_FR_VALUES = Set.of("1", "01", "OPEN"); + public static final Set DOOR_CLOSE_FR_VALUES = Set.of("0", "00", "CLOSE"); public static final String REFRIGERATOR_SNAPSHOT_NODE_V2 = "refState"; + public static final String FR_SET_CONTROL_COMMAND_NAME_V1 = "SetControl"; + public static final Map CAP_FR_SMART_SAVING_MODE = Map.of("@CP_TERM_USE_NOT_W", "Disabled", + "@RE_SMARTSAVING_MODE_NIGHT_W", "Night Mode", "@RE_SMARTSAVING_MODE_CUSTOM_W", "Custom Mode"); + public static final Map CAP_FR_ON_OFF = Map.of("@CP_OFF_EN_W", "Off", "@CP_ON_EN_W", "On"); + public static final Map CAP_FR_LABEL_ON_OFF = Map.of("OFF", "Off", "ON", "On", "IGNORE", + "Not Available"); + + public static final Map CAP_FR_LABEL_CLOSE_OPEN = Map.of("CLOSE", "Closed", "OPEN", "Open", + "IGNORE", "Not Available"); + + public static final Map CAP_FR_EXPRESS_MODES = Map.of("@CP_OFF_EN_W", "Express Mode Off", + "@CP_ON_EN_W", "Express Fridge/Freezer On", "@RE_MAIN_SPEED_FREEZE_TERM_W", "Rapid Freeze On"); + + public static final Map CAP_FR_FRESH_AIR_FILTER_MAP = Map.ofEntries(/* v1 */ entry("1", "Off"), + entry("2", "Auto Mode"), entry("3", "Power Mode"), entry("4", "Replace Filter"), + /* v2 */ entry("OFF", "Off"), entry("AUTO", "Auto Mode"), entry("POWER", "Power Mode"), + entry("REPLACE", "Replace Filter"), entry("SMART_STORAGE_POWER", "Smart Storage Power"), + entry("SMART_STORAGE_OFF", "Smart Storage Off"), entry("SMART_STORAGE_ON", "Smart Storage On"), + entry("IGNORE", "Not Available")); + + public static final Map CAP_FR_SMART_SAVING_V2_MODE = Map.of("OFF", "Off", "NIGHT_ON", "Night Mode", + "CUSTOM_ON", "Custom Mode", "SMARTGRID_DR_ON", "Demand Response", "SMARTGRID_DD_ON", "Delay Defrost", + "IGNORE", "Not Available"); + + public static final Map CAP_FR_WATER_FILTER = Map.ofEntries(entry("0_MONTH", "0 Month Used"), + entry("0", "0 Month Used"), entry("1_MONTH", "1 Month Used"), entry("1", "1 Month Used"), + entry("2_MONTH", "2 Month Used"), entry("2", "2 Month Used"), entry("3_MONTH", "3 Month Used"), + entry("3", "3 Month Used"), entry("4_MONTH", "4 Month Used"), entry("4", "4 Month Used"), + entry("5_MONTH", "5 Month Used"), entry("5", "5 Month Used"), entry("6_MONTH", "6 Month Used"), + entry("6", "6 Month Used"), entry("7_MONTH", "7 Month Used"), entry("8_MONTH", "8 Month Used"), + entry("9_MONTH", "9 Month Used"), entry("10_MONTH", "10 Month Used"), entry("11_MONTH", "11 Month Used"), + entry("12_MONTH", "12 Month Used"), entry("IGNORE", "Not Available")); + public static final String CAP_FR_WATER_FILTER_USED_POSTFIX = "Month(s) Used"; + public static final Map CAP_FR_TEMP_UNIT_V2_MAP = Map.of(TEMP_UNIT_CELSIUS, + TEMP_UNIT_CELSIUS_SYMBOL, TEMP_UNIT_FAHRENHEIT, TEMP_UNIT_FAHRENHEIT_SYMBOL); // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= // CHANNEL IDS @@ -164,18 +214,17 @@ public class LGThinQBindingConstants { "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( - Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), - Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), Map.entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), - Map.entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), Map.entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Auto"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); + entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), + entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), + entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), + entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), + entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Auto"), + entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), + entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), + entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), + entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), + entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), + entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); public static final Map CAP_AC_COOL_JET = Map.of("@COOL_JET", "Cool Jet"); public static final Double CAP_HP_AIR_SWITCH = 0.0; @@ -225,71 +274,67 @@ public class LGThinQBindingConstants { public static final String WM_CHANNEL_REMAIN_TIME_ID = "remain-time"; public static final String WM_CHANNEL_DELAY_TIME_ID = "delay-time"; - public static final Map CAP_WDM_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), - Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), - Map.entry("@WM_STATE_RESERVE_W", "Reserved"), Map.entry("@WM_STATE_DETECTING_W", "Detecting"), - Map.entry("@WM_STATE_RUNNING_W", "Running"), Map.entry("@WM_STATE_RINSING_W", "Rinsing"), - Map.entry("@WM_STATE_SPINNING_W", "Spinning"), Map.entry("@WM_STATE_COOLDOWN_W", "Cool Down"), - Map.entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), Map.entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), - Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_END_W", "End"), - Map.entry("@WM_STATE_DRYING_W", "Drying"), Map.entry("@WM_STATE_DEMO_W", "Demonstration"), - Map.entry("@WM_STATE_ADD_DRAIN_W", "Add Drain"), Map.entry("@WM_STATE_LOAD_DISPLAY_W", "Loading Display"), - Map.entry("@WM_STATE_FRESHCARE_W", "Refreshing"), Map.entry("@WM_STATE_ERROR_AUTO_OFF_W", "Error Auto Off"), - Map.entry("@WM_STATE_FROZEN_PREVENT_INITIAL_W", "Frozen Preventing"), - Map.entry("@FROZEN_PREVENT_PAUSE", "Frozen Preventing Paused"), - Map.entry("@FROZEN_PREVENT_RUNNING", "Frozen Preventing Running"), - Map.entry("@AUDIBLE_DIAGNOSIS", "Diagnosing"), Map.entry("@WM_STATE_ERROR_W", "Error")); + public static final Map CAP_WDM_STATE = Map.ofEntries(entry("@WM_STATE_POWER_OFF_W", "Off"), + entry("@WM_STATE_INITIAL_W", "Initial"), entry("@WM_STATE_PAUSE_W", "Pause"), + entry("@WM_STATE_RESERVE_W", "Reserved"), entry("@WM_STATE_DETECTING_W", "Detecting"), + entry("@WM_STATE_RUNNING_W", "Running"), entry("@WM_STATE_RINSING_W", "Rinsing"), + entry("@WM_STATE_SPINNING_W", "Spinning"), entry("@WM_STATE_COOLDOWN_W", "Cool Down"), + entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), + entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), entry("@WM_STATE_END_W", "End"), + entry("@WM_STATE_DRYING_W", "Drying"), entry("@WM_STATE_DEMO_W", "Demonstration"), + entry("@WM_STATE_ADD_DRAIN_W", "Add Drain"), entry("@WM_STATE_LOAD_DISPLAY_W", "Loading Display"), + entry("@WM_STATE_FRESHCARE_W", "Refreshing"), entry("@WM_STATE_ERROR_AUTO_OFF_W", "Error Auto Off"), + entry("@WM_STATE_FROZEN_PREVENT_INITIAL_W", "Frozen Preventing"), + entry("@FROZEN_PREVENT_PAUSE", "Frozen Preventing Paused"), + entry("@FROZEN_PREVENT_RUNNING", "Frozen Preventing Running"), entry("@AUDIBLE_DIAGNOSIS", "Diagnosing"), + entry("@WM_STATE_ERROR_W", "Error")); public static final Map CAP_WDM_PROCESS_STATE = Map.ofEntries( - Map.entry("@WM_STATE_DETECTING_W", "Detecting"), Map.entry("@WM_STATE_STEAM_W", "Steam"), - Map.entry("@WM_STATE_DRY_W", "Drying"), Map.entry("@WM_STATE_COOLING_W", "Cooling"), - Map.entry("@WM_STATE_ANTI_CREASE_W", "Anti Creasing"), Map.entry("@WM_STATE_END_W", "End"), - Map.entry("@WM_STATE_POWER_OFF_W", "Power Off"), Map.entry("@WM_STATE_INITIAL_W", "Initializing"), - Map.entry("@WM_STATE_PAUSE_W", "Paused"), Map.entry("@WM_STATE_RESERVE_W", "Reserved"), - Map.entry("@WM_STATE_RUNNING_W", "Running"), Map.entry("@WM_STATE_RINSING_W", "Rising"), - Map.entry("@WM_STATE_SPINNING_W", "@WM_STATE_DRYING_W"), Map.entry("WM_STATE_COOLDOWN_W", "Cool Down"), - Map.entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), Map.entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), - Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_ERROR_W", "Error")); + entry("@WM_STATE_DETECTING_W", "Detecting"), entry("@WM_STATE_STEAM_W", "Steam"), + entry("@WM_STATE_DRY_W", "Drying"), entry("@WM_STATE_COOLING_W", "Cooling"), + entry("@WM_STATE_ANTI_CREASE_W", "Anti Creasing"), entry("@WM_STATE_END_W", "End"), + entry("@WM_STATE_POWER_OFF_W", "Power Off"), entry("@WM_STATE_INITIAL_W", "Initializing"), + entry("@WM_STATE_PAUSE_W", "Paused"), entry("@WM_STATE_RESERVE_W", "Reserved"), + entry("@WM_STATE_RUNNING_W", "Running"), entry("@WM_STATE_RINSING_W", "Rising"), + entry("@WM_STATE_SPINNING_W", "@WM_STATE_DRYING_W"), entry("WM_STATE_COOLDOWN_W", "Cool Down"), + entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), + entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), entry("@WM_STATE_ERROR_W", "Error")); public static final Map CAP_DR_DRY_LEVEL = Map.ofEntries( - Map.entry("@WM_DRY24_DRY_LEVEL_IRON_W", "Iron"), Map.entry("@WM_DRY24_DRY_LEVEL_CUPBOARD_W", "Cupboard"), - Map.entry("@WM_DRY24_DRY_LEVEL_EXTRA_W", "Extra")); + entry("@WM_DRY24_DRY_LEVEL_IRON_W", "Iron"), entry("@WM_DRY24_DRY_LEVEL_CUPBOARD_W", "Cupboard"), + entry("@WM_DRY24_DRY_LEVEL_EXTRA_W", "Extra")); public static final Map CAP_WM_TEMPERATURE = Map.ofEntries( - Map.entry("@WM_TERM_NO_SELECT_W", "Not Selected"), Map.entry("@WM_TITAN2_OPTION_TEMP_20_W", "20"), - Map.entry("@WM_TITAN2_OPTION_TEMP_COLD_W", "Cold"), Map.entry("@WM_TITAN2_OPTION_TEMP_30_W", "30"), - Map.entry("@WM_TITAN2_OPTION_TEMP_40_W", "40"), Map.entry("@WM_TITAN2_OPTION_TEMP_50_W", "50"), - Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_TAP_COLD_W", "Tap Cold"), - Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_COLD_W", "Cold"), - Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_ECO_WARM_W", "Eco Warm"), - Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_WARM_W", "Warm"), - Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_HOT_W", "Hot"), - Map.entry("@WM_TITAN27_BIG_OPTION_TEMP_EXTRA_HOT_W", "Extra Hot")); - - public static final Map CAP_WM_SPIN = Map.ofEntries( - Map.entry("@WM_TERM_NO_SELECT_W", "Not Selected"), Map.entry("@WM_TITAN2_OPTION_SPIN_NO_SPIN_W", "No Spin"), - Map.entry("@WM_TITAN2_OPTION_SPIN_400_W", "400"), Map.entry("@WM_TITAN2_OPTION_SPIN_600_W", "600"), - Map.entry("@WM_TITAN2_OPTION_SPIN_700_W", "700"), Map.entry("@WM_TITAN2_OPTION_SPIN_800_W", "800"), - Map.entry("@WM_TITAN2_OPTION_SPIN_900_W", "900"), Map.entry("@WM_TITAN2_OPTION_SPIN_1000_W", "1000"), - Map.entry("@WM_TITAN2_OPTION_SPIN_1100_W", "1100"), Map.entry("@WM_TITAN2_OPTION_SPIN_1200_W", "1200"), - Map.entry("@WM_TITAN2_OPTION_SPIN_1400_W", "1400"), Map.entry("@WM_TITAN2_OPTION_SPIN_1600_W", "1600"), - Map.entry("@WM_TITAN2_OPTION_SPIN_MAX_W", "Max Spin"), - Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_NO_SPIN_W", "Drain Only"), - Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_LOW_W", "Low"), - Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_MEDIUM_W", "Medium"), - Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_HIGH_W", "High"), - Map.entry("@WM_TITAN27_BIG_OPTION_SPIN_EXTRA_HIGH_W", "Extra High")); - - public static final Map CAP_WM_RINSE = Map.ofEntries( - Map.entry("@WM_TERM_NO_SELECT_W", "Not Selected"), Map.entry("@WM_TITAN2_OPTION_RINSE_NORMAL_W", "Normal"), - Map.entry("@WM_TITAN2_OPTION_RINSE_RINSE+_W", "Plus"), - Map.entry("@WM_TITAN2_OPTION_RINSE_RINSE++_W", "Plus +"), - Map.entry("@WM_TITAN2_OPTION_RINSE_NORMALHOLD_W", "Normal Hold"), - Map.entry("@WM_TITAN2_OPTION_RINSE_RINSE+HOLD_W", "Plus Hold"), - Map.entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_0_W", "Normal"), - Map.entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_1_W", "Plus"), - Map.entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_2_W", "Plus +"), - Map.entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_3_W", "Plus ++")); + entry("@WM_TERM_NO_SELECT_W", "Not Selected"), entry("@WM_TITAN2_OPTION_TEMP_20_W", "20"), + entry("@WM_TITAN2_OPTION_TEMP_COLD_W", "Cold"), entry("@WM_TITAN2_OPTION_TEMP_30_W", "30"), + entry("@WM_TITAN2_OPTION_TEMP_40_W", "40"), entry("@WM_TITAN2_OPTION_TEMP_50_W", "50"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_TAP_COLD_W", "Tap Cold"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_COLD_W", "Cold"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_ECO_WARM_W", "Eco Warm"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_WARM_W", "Warm"), entry("@WM_TITAN27_BIG_OPTION_TEMP_HOT_W", "Hot"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_EXTRA_HOT_W", "Extra Hot")); + + public static final Map CAP_WM_SPIN = Map.ofEntries(entry("@WM_TERM_NO_SELECT_W", "Not Selected"), + entry("@WM_TITAN2_OPTION_SPIN_NO_SPIN_W", "No Spin"), entry("@WM_TITAN2_OPTION_SPIN_400_W", "400"), + entry("@WM_TITAN2_OPTION_SPIN_600_W", "600"), entry("@WM_TITAN2_OPTION_SPIN_700_W", "700"), + entry("@WM_TITAN2_OPTION_SPIN_800_W", "800"), entry("@WM_TITAN2_OPTION_SPIN_900_W", "900"), + entry("@WM_TITAN2_OPTION_SPIN_1000_W", "1000"), entry("@WM_TITAN2_OPTION_SPIN_1100_W", "1100"), + entry("@WM_TITAN2_OPTION_SPIN_1200_W", "1200"), entry("@WM_TITAN2_OPTION_SPIN_1400_W", "1400"), + entry("@WM_TITAN2_OPTION_SPIN_1600_W", "1600"), entry("@WM_TITAN2_OPTION_SPIN_MAX_W", "Max Spin"), + entry("@WM_TITAN27_BIG_OPTION_SPIN_NO_SPIN_W", "Drain Only"), + entry("@WM_TITAN27_BIG_OPTION_SPIN_LOW_W", "Low"), entry("@WM_TITAN27_BIG_OPTION_SPIN_MEDIUM_W", "Medium"), + entry("@WM_TITAN27_BIG_OPTION_SPIN_HIGH_W", "High"), + entry("@WM_TITAN27_BIG_OPTION_SPIN_EXTRA_HIGH_W", "Extra High")); + + public static final Map CAP_WM_RINSE = Map.ofEntries(entry("@WM_TERM_NO_SELECT_W", "Not Selected"), + entry("@WM_TITAN2_OPTION_RINSE_NORMAL_W", "Normal"), entry("@WM_TITAN2_OPTION_RINSE_RINSE+_W", "Plus"), + entry("@WM_TITAN2_OPTION_RINSE_RINSE++_W", "Plus +"), + entry("@WM_TITAN2_OPTION_RINSE_NORMALHOLD_W", "Normal Hold"), + entry("@WM_TITAN2_OPTION_RINSE_RINSE+HOLD_W", "Plus Hold"), + entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_0_W", "Normal"), + entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_1_W", "Plus"), + entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_2_W", "Plus +"), + entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_3_W", "Plus ++")); // This is the dictionary os course functions translations for V2 public static final Map> CAP_WM_DICT_V2 = Map.of("spin", CAP_WM_SPIN, "rinse", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java index 3d5125ec5ec71..15d8aae7c5851 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java @@ -135,4 +135,14 @@ public String getPassword() { public void setPassword(String password) { this.password = password; } + + @Override + public String toString() { + return "LGThinqGateway{" + "empBaseUri='" + empBaseUri + '\'' + ", loginBaseUri='" + loginBaseUri + '\'' + + ", apiRootV1='" + apiRootV1 + '\'' + ", apiRootV2='" + apiRootV2 + '\'' + ", authBase='" + authBase + + '\'' + ", language='" + language + '\'' + ", country='" + country + '\'' + ", username='" + + (!username.isEmpty() ? "******" : "") + '\'' + ", password='" + + (!password.isEmpty() ? "******" : "") + '\'' + ", alternativeEmpServer='" + + alternativeEmpServer + '\'' + ", accountVersion=" + accountVersion + '}'; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java index 8bc8ec175b42b..c255c3cec268c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java @@ -27,6 +27,8 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.errors.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.ObjectMapper; @@ -38,6 +40,7 @@ @NonNullByDefault public class TokenManager { private static final int EXPIRICY_TOLERANCE_SEC = 60; + private static final Logger logger = LoggerFactory.getLogger(TokenManager.class); private final OauthLgEmpAuthenticator oAuthAuthenticator; private final ObjectMapper objectMapper = new ObjectMapper(); private final Map tokenCached = new ConcurrentHashMap<>(); @@ -98,16 +101,19 @@ public void oauthFirstRegistration(String bridgeName, String language, String co try { preLogin = oAuthAuthenticator.preLoginUser(gw, username, password); } catch (Exception ex) { + logger.error("Error pre-login with gateway: {}", gw); throw new PreLoginException("Error doing pre-login of the user in the Emp LG Server", ex); } try { accountLogin = oAuthAuthenticator.loginUser(gw, preLogin); } catch (Exception ex) { + logger.error("Error logging with gateway: {}", gw); throw new AccountLoginException("Error doing user's account login on the Emp LG Server", ex); } try { token = oAuthAuthenticator.getToken(gw, accountLogin); } catch (Exception ex) { + logger.error("Error getting token with gateway: {}", gw); throw new TokenException("Error getting Token", ex); } try { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 0c67e9f542f10..5b52e7cb72690 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -769,13 +769,23 @@ public void dispose() { } } - protected Channel createDynChannel(String channelName, ChannelUID chanelUuid, String itemType) { + /** + * Create Dynamic channel. The channel type must be pre-defined in the thing definition (xml) and with + * the same name as the channel. + * + * @param channelNameAndTypeName channel name to be created and the same channel type name defined in the channels + * descriptor + * @param channelUuid Uid of the channel + * @param itemType item type (see openhab documentation) + * @return return the new channel created + */ + protected Channel createDynChannel(String channelNameAndTypeName, ChannelUID channelUuid, String itemType) { if (getCallback() == null) { throw new IllegalStateException("Unexpected behaviour. Callback not ready! Can't create dynamic channels"); } else { // dynamic create channel - ChannelBuilder builder = getCallback().createChannelBuilder(chanelUuid, - new ChannelTypeUID(BINDING_ID, channelName)); + ChannelBuilder builder = getCallback().createChannelBuilder(channelUuid, + new ChannelTypeUID(BINDING_ID, channelNameAndTypeName)); Channel channel = builder.withKind(ChannelKind.STATE).withAcceptedItemType(itemType).build(); updateThing(editThing().withChannel(channel).build()); return channel; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java new file mode 100644 index 0000000000000..6c53880474cce --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -0,0 +1,427 @@ +/** + * 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.*; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; +import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; +import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; +import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.*; +import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQDishWasherHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQDishWasherHandler extends LGThinQAbstractDeviceHandler { + + private final LGThinQStateDescriptionProvider stateDescriptionProvider; + private final ChannelUID courseChannelUID; + private final ChannelUID remoteStartStopChannelUID; + private final ChannelUID remainTimeChannelUID; + private final ChannelUID delayTimeChannelUID; + private final ChannelUID spinChannelUID; + private final ChannelUID rinseChannelUID; + private final ChannelUID stateChannelUID; + private final ChannelUID processStateChannelUID; + private final ChannelUID childLockChannelUID; + private final ChannelUID dryLevelChannelUID; + private final ChannelUID temperatureChannelUID; + private final ChannelUID doorLockChannelUID; + private final ChannelUID standByModeChannelUID; + private final ChannelUID remoteStartFlagChannelUID; + private final ChannelUID remoteStartCourseChannelUID; + + public final ChannelGroupUID channelGroupRemoteStartUID; + public final ChannelGroupUID channelGroupDashboardUID; + + private final List remoteStartEnabledChannels = new CopyOnWriteArrayList<>(); + + private final Map> cachedBitKeyDefinitions = new HashMap<>(); + + private final Logger logger = LoggerFactory.getLogger(LGThinQDishWasherHandler.class); + @NonNullByDefault + private final LGThinQWMApiClientService lgThinqWMApiClientService; + + public LGThinQDishWasherHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, + ThinqChannelTypeProvider channelTypeProvider, ThinqChannelGroupTypeProvider channelGroupTypeProvider, + ItemChannelLinkRegistry itemChannelLinkRegistry, HttpClientFactory httpClientFactory) { + super(thing, stateDescriptionProvider, itemChannelLinkRegistry); + this.thinqChannelGroupProvider = channelGroupTypeProvider; + this.thinqChannelProvider = channelTypeProvider; + this.stateDescriptionProvider = stateDescriptionProvider; + lgThinqWMApiClientService = LGThinQApiClientServiceFactory.newWMApiClientService(lgPlatformType, + httpClientFactory); + channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_GRP_ID); + channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); + courseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_COURSE_ID); + dryLevelChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_DRY_LEVEL_ID); + stateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STATE_ID); + processStateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_PROCESS_STATE_ID); + remainTimeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMAIN_TIME_ID); + delayTimeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_DELAY_TIME_ID); + temperatureChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_TEMP_LEVEL_ID); + doorLockChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_DOOR_LOCK_ID); + childLockChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_CHILD_LOCK_ID); + rinseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_RINSE_ID); + spinChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_SPIN_ID); + standByModeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STAND_BY_ID); + remoteStartFlagChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMOTE_START_ID); + remoteStartStopChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_START_START_STOP); + remoteStartCourseChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_COURSE); + } + + @Override + protected void initializeThing(@Nullable ThingStatus bridgeStatus) { + super.initializeThing(bridgeStatus); + ThingBuilder builder = editThing() + .withoutChannels(this.getThing().getChannelsOfGroup(channelGroupRemoteStartUID.getId())); + updateThing(builder.build()); + remoteStartEnabledChannels.clear(); + } + + private void loadOptionsCourse(WasherDryerCapability cap, ChannelUID courseChannel) { + List optionsCourses = new ArrayList<>(); + cap.getCourses().forEach((k, v) -> optionsCourses.add(new StateOption(k, emptyIfNull(v.getCourseName())))); + stateDescriptionProvider.setStateOptions(courseChannel, optionsCourses); + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + WasherDryerCapability wmCap = getCapabilities(); + + List options = new ArrayList<>(); + wmCap.getStateFeat().getValuesMapping() + .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_STATE, v)))); + stateDescriptionProvider.setStateOptions(stateChannelUID, options); + + loadOptionsCourse(wmCap, courseChannelUID); + + List optionsTemp = new ArrayList<>(); + wmCap.getTemperatureFeat().getValuesMapping() + .forEach((k, v) -> optionsTemp.add(new StateOption(k, keyIfValueNotFound(CAP_WM_TEMPERATURE, v)))); + stateDescriptionProvider.setStateOptions(temperatureChannelUID, optionsTemp); + + List optionsDoor = new ArrayList<>(); + optionsDoor.add(new StateOption("0", "Unlocked")); + optionsDoor.add(new StateOption("1", "Locked")); + stateDescriptionProvider.setStateOptions(doorLockChannelUID, optionsDoor); + + List optionsSpin = new ArrayList<>(); + wmCap.getSpinFeat().getValuesMapping() + .forEach((k, v) -> optionsSpin.add(new StateOption(k, keyIfValueNotFound(CAP_WM_SPIN, v)))); + stateDescriptionProvider.setStateOptions(spinChannelUID, optionsSpin); + + List optionsRinse = new ArrayList<>(); + wmCap.getRinseFeat().getValuesMapping() + .forEach((k, v) -> optionsRinse.add(new StateOption(k, keyIfValueNotFound(CAP_WM_RINSE, v)))); + stateDescriptionProvider.setStateOptions(rinseChannelUID, optionsRinse); + + List optionsPre = new ArrayList<>(); + wmCap.getProcessState().getValuesMapping() + .forEach((k, v) -> optionsPre.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_PROCESS_STATE, v)))); + stateDescriptionProvider.setStateOptions(processStateChannelUID, optionsPre); + + List optionsChildLock = new ArrayList<>(); + optionsChildLock.add(new StateOption("CHILDLOCK_OFF", "Unlocked")); + optionsChildLock.add(new StateOption("CHILDLOCK_ON", "Locked")); + stateDescriptionProvider.setStateOptions(childLockChannelUID, optionsChildLock); + + List optionsDryLevel = new ArrayList<>(); + wmCap.getDryLevel().getValuesMapping() + .forEach((k, v) -> optionsDryLevel.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); + stateDescriptionProvider.setStateOptions(dryLevelChannelUID, optionsDryLevel); + } + + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqWMApiClientService; + } + + @Override + protected Logger getLogger() { + return logger; + } + + private ZonedDateTime getZonedDateTime(String minutesAndSeconds) { + if (minutesAndSeconds.length() != 5) { + logger.error("Washer/Disher remain/delay time is not in standard MM:SS. Value received: {}. Reset to 00:00", + minutesAndSeconds); + minutesAndSeconds = "00:00"; + } + String min = minutesAndSeconds.substring(0, 2); + String sec = minutesAndSeconds.substring(3); + + return ZonedDateTime.of(1970, 1, 1, 0, Integer.parseInt(min), Integer.parseInt(sec), 0, ZoneId.systemDefault()); + } + + @Override + protected void updateDeviceChannels(WasherDryerSnapshot shot) { + WasherDryerSnapshot lastShot = getLastShot(); + updateState("dashboard#" + CHANNEL_POWER_ID, + (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); + updateState(stateChannelUID, new StringType(shot.getState())); + updateState(processStateChannelUID, new StringType(shot.getProcessState())); + updateState(dryLevelChannelUID, new StringType(shot.getDryLevel())); + updateState(childLockChannelUID, new StringType(shot.getChildLock())); + updateState(courseChannelUID, new StringType(shot.getCourse())); + updateState(temperatureChannelUID, new StringType(shot.getTemperatureLevel())); + updateState(doorLockChannelUID, new StringType(shot.getDoorLock())); + updateState(remainTimeChannelUID, new StringType(shot.getRemainingTime())); + updateState(delayTimeChannelUID, new StringType(shot.getReserveTime())); + updateState(standByModeChannelUID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); + updateState(remoteStartFlagChannelUID, shot.isRemoteStartEnabled() ? OnOffType.ON : OnOffType.OFF); + updateState(spinChannelUID, new StringType(shot.getSpin())); + updateState(rinseChannelUID, new StringType(shot.getRinse())); + Channel rsStartStopChannel = getThing().getChannel(remoteStartStopChannelUID); + final List dynChannels = new ArrayList<>(); + // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. + if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { + ThingHandlerCallback callback = getCallback(); + if (rsStartStopChannel == null && callback != null) { + // === creating channel LaunchRemote + dynChannels + .add(createDynChannel(WM_CHANNEL_REMOTE_START_START_STOP, remoteStartStopChannelUID, "Switch")); + dynChannels.add(createDynChannel(WM_CHANNEL_REMOTE_COURSE, remoteStartCourseChannelUID, "String")); + // Just enabled remote start. Then is Off + updateState(remoteStartStopChannelUID, OnOffType.OFF); + // === creating selectable channels for the Course (if any) + try { + WasherDryerCapability cap = getCapabilities(); + // TODO - V1 - App will always get the default course, and V2 ? + loadOptionsCourse(cap, remoteStartCourseChannelUID); + updateState(remoteStartCourseChannelUID, new StringType(cap.getDefaultCourseId())); + + CourseDefinition courseDef = cap.getCourses().get(cap.getDefaultCourseId()); + if (WM_COURSE_NOT_SELECTED_VALUE.equals(shot.getSmartCourse()) && courseDef != null) { + // only create selectable channels if the course is not a smart course. Smart courses have + // already predefined + // the functions values + for (CourseFunction f : courseDef.getFunctions()) { + if (!f.isSelectable()) { + // only for selectable features + continue; + } + // handle well know dynamic fields + FeatureDefinition fd = cap.getFeatureDefinition(f.getValue()); + ChannelUID targetChannel = null; + ChannelUID refChannel = null; + if (!FeatureDefinition.NULL_DEFINITION.equals(fd)) { + targetChannel = new ChannelUID(channelGroupRemoteStartUID, fd.getChannelId()); + refChannel = new ChannelUID(channelGroupDashboardUID, fd.getRefChannelId()); + dynChannels.add(createDynChannel(fd.getChannelId(), targetChannel, + translateFeatureToItemType(fd.getDataType()))); + if (CAP_WM_DICT_V2.containsKey(f.getValue())) { + // if the function has translation dictionary (I hope so), then the values in + // the selectable channel will be translated to something more readable + List options = new ArrayList<>(); + for (String v : f.getSelectableValues()) { + Map values = CAP_WM_DICT_V2.get(f.getValue()); + if (values != null) { + // Canonical Value is the KEY (@...) that represents a constant in the + // definition + // that can be translated to a human description + String canonicalValue = Objects + .requireNonNullElse(fd.getValuesMapping().get(v), v); + options.add(new StateOption(v, keyIfValueNotFound(values, canonicalValue))); + stateDescriptionProvider.setStateOptions(targetChannel, options); + } + } + } + // update state with the default referenced channel + updateState(targetChannel, new StringType(getItemLinkedValue(refChannel))); + } + } + } + } catch (LGThinqApiException e) { + throw new RuntimeException(e); + } + remoteStartEnabledChannels.addAll(dynChannels); + + } + } else if (remoteStartEnabledChannels.size() > 0) { + ThingBuilder builder = editThing().withoutChannels(remoteStartEnabledChannels); + updateThing(builder.build()); + remoteStartEnabledChannels.clear(); + } + } + + @Override + protected DeviceTypes getDeviceType() { + if (THING_TYPE_WASHING_MACHINE.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHERDRYER_MACHINE; + } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHING_TOWER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); + } + } + + private Map getRemoteStartData() throws LGThinqApiException { + WasherDryerSnapshot lastShot = getLastShot(); + if (lastShot.getRawData().isEmpty()) { + return lastShot.getRawData(); + } + String selectedCourse = getItemLinkedValue(remoteStartCourseChannelUID); + if (selectedCourse == null) { + logger.error("Remote Start Channel must be linked to proceed with remote start."); + return Collections.emptyMap(); + } + WasherDryerCapability cap = getCapabilities(); + Map rawData = lastShot.getRawData(); + Map data = new HashMap<>(); + CommandDefinition cmd = cap.getCommandsDefinition().get(cap.getCommandRemoteStart()); + if (cmd == null) { + logger.error("Command for Remote Start not found in the Washer descriptor. It's most likely a bug"); + return Collections.emptyMap(); + } + Map cmdData = cmd.getData(); + // 1st - copy snapshot data to command + cmdData.forEach((k, v) -> { + data.put(k, rawData.getOrDefault(k, v)); + }); + // 2nd - replace remote start data with selected course values + CourseDefinition selCourseDef = cap.getCourses().get(selectedCourse); + if (selCourseDef != null) { + selCourseDef.getFunctions().forEach(f -> { + data.put(f.getValue(), f.getDefaultValue()); + }); + } + String smartCourse = lastShot.getSmartCourse(); + data.put(cap.getDefaultCourseFieldName(), selectedCourse); + data.put(cap.getDefaultSmartCourseFeatName(), smartCourse); + CourseType courseType = cap.getCourses().get("NOT_SELECTED".equals(smartCourse) ? selectedCourse : smartCourse) + .getCourseType(); + data.put("courseType", courseType.getValue()); + // 3rd - replace custom selectable features with channel's ones. + for (Channel c : remoteStartEnabledChannels) { + String value = Objects.requireNonNullElse(getItemLinkedValue(c.getUID()), ""); + String simpleChannelUID = getSimpleChannelUID(c.getUID().getId()); + switch (simpleChannelUID) { + case WM_CHANNEL_REMOTE_START_RINSE: + data.put(cap.getRinseFeat().getName(), value); + break; + case WM_CHANNEL_REMOTE_START_TEMP: + data.put(cap.getTemperatureFeat().getName(), value); + break; + case WM_CHANNEL_REMOTE_START_SPIN: + data.put(cap.getSpinFeat().getName(), value); + break; + default: + logger.warn("channel [{}] not mapped for this binding. It most likely a bug.", simpleChannelUID); + } + } + + return data; + } + + @Override + protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { + WasherDryerSnapshot lastShot = getLastShot(); + Command command = params.command; + String simpleChannelUID; + simpleChannelUID = getSimpleChannelUID(params.channelUID); + switch (simpleChannelUID) { + case WM_CHANNEL_REMOTE_START_START_STOP: { + if (command instanceof OnOffType) { + if (OnOffType.ON.equals(command)) { + if (!lastShot.isStandBy()) { + lgThinqWMApiClientService.remoteStart(getBridgeId(), getCapabilities(), getDeviceId(), + getRemoteStartData()); + } else { + logger.warn( + "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); + } + } else { + logger.warn("Command Remote Start OFF not implemented yet"); + } + } else { + logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); + } + break; + } + case WM_CHANNEL_STAND_BY_ID: { + if (command instanceof OnOffType) { + lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); + } else { + logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + /** + * Put the channels in default state if the device is disconnected or gone. + */ + @Override + public void onDeviceDisconnected() { + updateState(CHANNEL_POWER_ID, OnOffType.OFF); + updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); + updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); + updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index 935a648312bfa..2425168ba8847 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -15,12 +15,15 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; +import javax.measure.Unit; import javax.measure.quantity.Temperature; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -31,9 +34,12 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; import org.openhab.core.io.net.http.HttpClientFactory; +import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OpenClosedType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.library.types.StringType; +import org.openhab.core.library.unit.ImperialUnits; +import org.openhab.core.library.unit.SIUnits; import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -59,6 +65,12 @@ public class LGThinQFridgeHandler extends LGThinQAbstractDeviceHandler getTemperatureUnit(FridgeCanonicalSnapshot shot) { + if (!(CELSIUS_UNIT_VALUES.contains(shot.getTempUnit()) + || FAHRENHEIT_UNIT_VALUES.contains(shot.getTempUnit()))) { + logger.warn( + "Temperature Unit not recognized (must be Celsius or Fahrenheit). Ignoring and considering Celsius as default"); + return SIUnits.CELSIUS; + } + return CELSIUS_UNIT_VALUES.contains(shot.getTempUnit()) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; } @Override protected void updateDeviceChannels(FridgeCanonicalSnapshot shot) { - updateState(fridgeTempChannelUID, new QuantityType(shot.getFridgeStrTemp())); - updateState(freezerTempChannelUID, new QuantityType(shot.getFreezerStrTemp())); - updateState(doorChannelUID, parseDoorStatus(shot.getDoorStatus())); + Unit unTemp = getTemperatureUnit(shot); + if (isLinked(fridgeTempChannelUID)) { + updateState(fridgeTempChannelUID, + new QuantityType<>(decodeTempValue(fridgeTempChannelUID, shot.getFridgeTemp().intValue()), unTemp)); + } + if (isLinked(freezerTempChannelUID)) { + updateState(freezerTempChannelUID, new QuantityType<>( + decodeTempValue(freezerTempChannelUID, shot.getFreezerTemp().intValue()), unTemp)); + } + if (isLinked(doorChannelUID)) { + updateState(doorChannelUID, parseDoorStatus(shot.getDoorStatus())); + } + if (isLinked(expressModeChannelUID)) { + updateState(expressModeChannelUID, new StringType(shot.getExpressMode())); + } + if (isLinked(freshAirFilterChannelUID)) { + updateState(freshAirFilterChannelUID, new StringType(shot.getFreshAirFilterState())); + } + if (isLinked(waterFilterChannelUID)) { + updateState(waterFilterChannelUID, new StringType(shot.getWaterFilterUsedMonth())); + } updateState(tempUnitUID, new StringType(shot.getTempUnit())); if (!tempUnit.equals(shot.getTempUnit())) { @@ -97,37 +145,82 @@ protected void updateDeviceChannels(FridgeCanonicalSnapshot shot) { } private State parseDoorStatus(String doorStatus) { - if ("CLOSE".equals(doorStatus)) { + if (DOOR_CLOSE_FR_VALUES.contains(doorStatus)) { return OpenClosedType.CLOSED; - } else if ("OPEN".equals(doorStatus)) { + } else if (DOOR_OPEN_FR_VALUES.contains(doorStatus)) { return OpenClosedType.OPEN; } else { return UnDefType.UNDEF; } } - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - FridgeCapability refCap = getCapabilities(); + protected Integer decodeTempValue(ChannelUID ch, Integer value) { + FridgeCapability refCap = null; + try { + refCap = getCapabilities(); + } catch (LGThinqApiException e) { + logger.error("Error getting capability of the device. It's mostly like a bug", e); + return 0; + } // temperature channels are little different. First we need to get the tempUnit in the first snapshot, - - if (isLinked(fridgeTempChannelUID)) { - updateTemperatureChannel(fridgeTempChannelUID, - TEMP_UNIT_CELSIUS.equals(tempUnit) ? refCap.getFridgeTempCMap() : refCap.getFridgeTempFMap()); + Map convertionMap; + if (fridgeTempChannelUID.equals(ch)) { + convertionMap = TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFridgeTempFMap() + : refCap.getFridgeTempCMap(); + } else if (freezerTempChannelUID.equals(ch)) { + convertionMap = TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFreezerTempFMap() + : refCap.getFreezerTempCMap(); + } else { + throw new IllegalStateException("Conversion Map Channel temperature not mapped. It's most likely a bug"); } - if (isLinked(freezerTempChannelUID)) { - updateTemperatureChannel(freezerTempChannelUID, - TEMP_UNIT_CELSIUS.equals(tempUnit) ? refCap.getFreezerTempCMap() : refCap.getFreezerTempFMap()); + String strValue = convertionMap.get(value.toString()); + if (strValue == null) { + logger.error("Temperature value informed can't be converted based on the cap file. It mostly like a bug"); + return 0; + } + try { + return Integer.valueOf(strValue); + } catch (Exception ex) { + logger.error("Temperature value converted can't be cast to Integer. It mostly like a bug", ex); + return 0; } } - private void updateTemperatureChannel(ChannelUID tempChannelUID, Map mapOptions) { - List options = new ArrayList<>(); - mapOptions.forEach((value, label) -> options.add(new StateOption(value, label))); - stateDescriptionProvider.setStatePattern(tempChannelUID, - "%.0f " + (TEMP_UNIT_CELSIUS.equals(tempUnit) ? TEMP_UNIT_CELSIUS_SYMBOL - : (TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? TEMP_UNIT_FAHRENHEIT_SYMBOL : "%unit%"))); - stateDescriptionProvider.setStateOptions(tempChannelUID, options); + protected Integer encodeTempValue(ChannelUID ch, Integer value) { + FridgeCapability refCap = null; + try { + refCap = getCapabilities(); + } catch (LGThinqApiException e) { + logger.error("Error getting capability of the device. It's mostly like a bug", e); + return 0; + } + // temperature channels are little different. First we need to get the tempUnit in the first snapshot, + final Map convertionMap, invertedMap; + if (fridgeTempChannelUID.equals(ch)) { + convertionMap = TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFridgeTempFMap() + : refCap.getFridgeTempCMap(); + } else if (freezerTempChannelUID.equals(ch)) { + convertionMap = TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFreezerTempFMap() + : refCap.getFreezerTempCMap(); + } else { + throw new IllegalStateException("Conversion Map Channel temperature not mapped. It's most likely a bug"); + } + invertedMap = new HashMap<>(); + convertionMap.forEach((k, v) -> { + invertedMap.put(v, k); + }); + + String strValue = invertedMap.get(value.toString()); + if (strValue == null) { + logger.error("Temperature value informed can't be converted based on the cap file. It mostly like a bug"); + return 0; + } + try { + return Integer.valueOf(strValue); + } catch (Exception ex) { + logger.error("Temperature value converted can't be cast to Integer. It mostly like a bug", ex); + return 0; + } } @Override @@ -169,7 +262,97 @@ public void onDeviceDisconnected() { // TODO - HANDLE IT, Think if it's needed } + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + FridgeCapability cap = getCapabilities(); + if (!cap.getIcePlusMap().isEmpty() && getThing().getChannel(icePlusChannelUID) == null) { + createDynChannel(FR_CHANNEL_ICE_PLUS, icePlusChannelUID, "Switch"); + } + if (!cap.getExpressModeMap().isEmpty() && getThing().getChannel(expressModeChannelUID) == null) { + createDynChannel(FR_CHANNEL_EXPRESS_MODE, expressModeChannelUID, "String"); + } + Unit unTemp = getTemperatureUnit(getLastShot()); + if (SIUnits.CELSIUS.equals(unTemp)) { + loadChannelTempStateOption(cap.getFridgeTempCMap(), fridgeTempChannelUID, unTemp); + loadChannelTempStateOption(cap.getFreezerTempCMap(), freezerTempChannelUID, unTemp); + } else { + loadChannelTempStateOption(cap.getFridgeTempFMap(), fridgeTempChannelUID, unTemp); + loadChannelTempStateOption(cap.getFreezerTempFMap(), freezerTempChannelUID, unTemp); + } + loadChannelStateOption(cap.getActiveSavingMap(), activeSavingChannelUID); + + loadChannelStateOption(cap.getExpressModeMap(), expressModeChannelUID, CAP_FR_EXPRESS_MODES); + + loadChannelStateOption(cap.getActiveSavingMap(), activeSavingChannelUID); + + loadChannelStateOption(cap.getSmartSavingMap(), smartSavingModeChannelUID); + + loadChannelStateOption(cap.getTempUnitMap(), tempUnitUID); + + loadChannelStateOption(CAP_FR_FRESH_AIR_FILTER_MAP, freshAirFilterChannelUID); + + loadChannelStateOption(CAP_FR_WATER_FILTER, waterFilterChannelUID); + } + + private void loadChannelStateOption(Map cap, ChannelUID channelUID) { + loadChannelStateOption(cap, channelUID, null); + } + + private void loadChannelTempStateOption(Map cap, ChannelUID channelUID, Unit unTemp) { + final List faOptions = new ArrayList<>(); + cap.forEach((k, v) -> { + try { + Integer vInt = Integer.valueOf(v); + QuantityType t = new QuantityType<>(Integer.valueOf(v), unTemp); + faOptions.add(new StateOption(t.toString(), t.toString())); + } catch (NumberFormatException ex) { + logger.debug("Error converting invalid temperature number: {}. This can be safely ignored", v); + } + }); + stateDescriptionProvider.setStateOptions(channelUID, faOptions); + } + + private void loadChannelStateOption(Map cap, ChannelUID channelUID, + @Nullable Map decodeMap) { + final List faOptions = new ArrayList<>(); + cap.forEach((k, v) -> faOptions.add(new StateOption(k, decodeMap == null ? v : decodeMap.get(v)))); + stateDescriptionProvider.setStateOptions(channelUID, faOptions); + } + + @Override protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { + FridgeCanonicalSnapshot lastShot = getLastShot(); + Map cmdSnap = lastShot.getRawData(); Command command = params.command; + String simpleChannelUID; + simpleChannelUID = getSimpleChannelUID(params.channelUID); + switch (simpleChannelUID) { + case FR_CHANNEL_FREEZER_TEMP_ID: + case FR_CHANNEL_FRIDGE_TEMP_ID: { + int targetTemp; + if (command instanceof DecimalType) { + targetTemp = ((DecimalType) command).intValue(); + } else if (command instanceof QuantityType) { + targetTemp = ((QuantityType) command).intValue(); + } else { + logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); + break; + } + + if (FR_CHANNEL_FRIDGE_TEMP_ID.equals(simpleChannelUID)) { + targetTemp = encodeTempValue(fridgeTempChannelUID, targetTemp); + lgThinqFridgeApiClientService.setFridgeTemperature(getBridgeId(), getDeviceId(), getCapabilities(), + targetTemp, lastShot.getTempUnit(), cmdSnap); + } else { + targetTemp = encodeTempValue(freezerTempChannelUID, targetTemp); + lgThinqFridgeApiClientService.setFreezerTemperature(getBridgeId(), getDeviceId(), getCapabilities(), + targetTemp, lastShot.getTempUnit(), cmdSnap); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index cb20d6c2ae6e0..9e92cdec5aefb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -106,7 +106,7 @@ public List listAccountDevices(String bridgeName) throws LGThinqApiExc RestResult resp = RestUtils.getCall(httpClient, builder.build().toURL().toString(), headers, null); return handleListAccountDevicesResult(resp); } catch (Exception e) { - throw new LGThinqApiException("Erros list account devices from LG Server API", e); + throw new LGThinqApiException("Error listing account devices from LG Server API", e); } } @@ -215,11 +215,11 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { logger.warn("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), resp.getJsonResponse()); + resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); return Collections.emptyList(); } logger.error("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), resp.getJsonResponse()); + resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); throw new LGThinqApiException(String .format("Error calling device list from LG Server API. The reason is: %s", resp.getJsonResponse())); } else { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index 10c1ade589a29..7d6053fa99387 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -14,12 +14,11 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V1_CONTROL_OP; -import java.util.Collections; -import java.util.Map; -import java.util.UUID; +import java.util.*; import javax.ws.rs.core.UriBuilder; +import org.apache.commons.lang3.ArrayUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; @@ -30,6 +29,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -155,4 +155,45 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp } return envelope; } + + /** + * Principal method to prepare the command to be sent to V1 Devices mainly when the command is generic, + * i.e, you can send a command structure to redefine any changeable feature of the device + * + * @param cmdDef command definition with template of the payload and data (binary or not) + * @param snapData snapshot data with features to be set in the device + * @return return the command structure. + * @throws JsonProcessingException + */ + protected Map prepareCommandV1(CommandDefinition cmdDef, Map snapData) + throws JsonProcessingException { + // expected map ordered here + String dataStr = cmdDef.getDataTemplate(); + // Keep the order + for (Map.Entry e : snapData.entrySet()) { + String value = String.valueOf(e.getValue()); + dataStr = dataStr.replace("{{" + e.getKey() + "}}", value); + } + + return completeCommandDataNodeV1(cmdDef, dataStr); + } + + protected LinkedHashMap completeCommandDataNodeV1(CommandDefinition cmdDef, String dataStr) + throws JsonProcessingException { + LinkedHashMap data = objectMapper.readValue(cmdDef.getRawCommand(), new TypeReference<>() { + }); + logger.debug("Prepare command v1: {}", dataStr); + if (cmdDef.isBinary()) { + data.put("format", "B64"); + List list = objectMapper.readValue(dataStr, new TypeReference<>() { + }); + // convert the list of integer to a bytearray + byte[] bytes = ArrayUtils.toPrimitive(list.stream().map(Integer::byteValue).toArray(Byte[]::new)); + String str_data_encoded = new String(Base64.getEncoder().encode(bytes)); + data.put("data", str_data_encoded); + } else { + data.put("data", dataStr); + } + return data; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java index cf1c6d561e889..a14ea0863f397 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java @@ -12,7 +12,11 @@ */ package org.openhab.binding.lgthinq.lgservices; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; @@ -24,5 +28,11 @@ @NonNullByDefault public interface LGThinQFridgeApiClientService extends LGThinQApiClientService { + void setFridgeTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, + Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) + throws LGThinqApiException; + void setFreezerTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, + Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) + throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java index ab103230da590..7597a0948f83b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java @@ -12,15 +12,23 @@ */ package org.openhab.binding.lgthinq.lgservices; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.FR_SET_CONTROL_COMMAND_NAME_V1; + +import java.util.Map; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * The {@link LGThinQFridgeApiV1ClientServiceImpl} @@ -31,6 +39,7 @@ public class LGThinQFridgeApiV1ClientServiceImpl extends LGThinQAbstractApiV1ClientService implements LGThinQFridgeApiClientService { + private static final Logger logger = LoggerFactory.getLogger(LGThinQFridgeApiV1ClientServiceImpl.class); protected LGThinQFridgeApiV1ClientServiceImpl(HttpClient httpClient) { super(FridgeCapability.class, FridgeCanonicalSnapshot.class, httpClient); @@ -53,4 +62,46 @@ public FridgeCanonicalSnapshot getDeviceData(@NonNull String bridgeName, @NonNul @NonNull CapabilityDefinition capDef) throws LGThinqApiException { throw new UnsupportedOperationException("Method not supported in V1 API device."); } + + @Override + public void setFridgeTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, + Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) + throws LGThinqApiException { + assert snapCmdData != null; + snapCmdData.put("TempRefrigerator", targetTemperatureIndex); + setTemperature(bridgeId, deviceId, fridgeCapability, snapCmdData); + } + + @Override + public void setFreezerTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, + Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) + throws LGThinqApiException { + assert snapCmdData != null; + snapCmdData.put("TempFreezer", targetTemperatureIndex); + setTemperature(bridgeId, deviceId, fridgeCapability, snapCmdData); + } + + private void setTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, + @Nullable Map snapCmdData) throws LGThinqApiException { + try { + CommandDefinition cmdSetControlDef = fridgeCapability.getCommandsDefinition() + .get(FR_SET_CONTROL_COMMAND_NAME_V1); + if (cmdSetControlDef == null) { + logger.warn("No command definition found for set control command. Ignoring command"); + return; + } + if (snapCmdData == null) { + logger.error("Snapshot to complete command was not send. It's mostly like a bug"); + return; + } + Map cmdPayload = prepareCommandV1(cmdSetControlDef, snapCmdData); + logger.debug("setControl Payload:[{}]", cmdPayload); + RestResult result = sendCommand(bridgeId, deviceId, cmdPayload); + handleGenericErrorResult(result); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error sending remote start", e); + } + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java index f7e9d39e22149..d4d74215dc627 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java @@ -12,14 +12,21 @@ */ package org.openhab.binding.lgthinq.lgservices; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; + /** * The {@link LGThinQFridgeApiV2ClientServiceImpl} * @@ -44,4 +51,32 @@ public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState throws LGThinqApiException { throw new UnsupportedOperationException("Not implemented yet."); } + + @Override + public void setFridgeTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, + Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) + throws LGThinqApiException { + setTemperature("fridgeTemp", bridgeId, deviceId, targetTemperatureIndex, tempUnit); + } + + @Override + public void setFreezerTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, + Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) + throws LGThinqApiException { + setTemperature("freezerTemp", bridgeId, deviceId, targetTemperatureIndex, tempUnit); + } + + private void setTemperature(String tempFeature, String bridgeId, String deviceId, Integer targetTemperature, + String tempUnit) throws LGThinqApiException { + ObjectNode dataSetList = JsonNodeFactory.instance.objectNode(); + ObjectNode nodeData = dataSetList.putObject("dataSetList").putObject("refState"); + nodeData.put(tempFeature, targetTemperature).put("tempUnit", tempUnit); + try { + RestResult result = sendCommand(bridgeId, deviceId, "control-sync", "basicCtrl", "Set", null, null, + dataSetList); + handleGenericErrorResult(result); + } catch (Exception e) { + throw new LGThinqApiException("Error sending command", e); + } + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java index 1085d90424eee..2dc4e447abb8e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java @@ -14,7 +14,6 @@ import java.util.*; -import org.apache.commons.lang3.ArrayUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -30,7 +29,6 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; /** * The {@link LGThinQWMApiV1ClientServiceImpl} @@ -98,7 +96,8 @@ public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LG } } - private Map prepareCommandV1(CommandDefinition cmdDef, Map snapData) + @Override + protected Map prepareCommandV1(CommandDefinition cmdDef, Map snapData) throws JsonProcessingException { // expected map ordered here String dataStr = cmdDef.getDataTemplate(); @@ -111,22 +110,8 @@ private Map prepareCommandV1(CommandDefinition cmdDef, Map cmd = objectMapper.readValue(cmdDef.getRawCommand(), new TypeReference<>() { - }); - cmd.remove("encode"); // remove encode node in the raw command to be similar to LG App. - - logger.debug("Prepare command v1: {}", dataStr); - if (cmdDef.isBinary()) { - cmd.put("format", "B64"); - List list = objectMapper.readValue(dataStr, new TypeReference<>() { - }); - // convert the list of integer to a bytearray - byte[] bytes = ArrayUtils.toPrimitive(list.stream().map(Integer::byteValue).toArray(Byte[]::new)); - String str_data_encoded = new String(Base64.getEncoder().encode(bytes)); - cmd.put("data", str_data_encoded); - } else { - cmd.put("data", dataStr); - } + LinkedHashMap cmd = completeCommandDataNodeV1(cmdDef, dataStr); + cmd.remove("encode"); return cmd; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java index f8a5f4c7f80c8..744ccce6abb42 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java @@ -12,8 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import java.util.List; -import java.util.Map; +import java.util.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -124,4 +123,58 @@ protected void validateMandatoryNote(JsonNode node) throws LGThinqException { protected abstract Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException; + + /** + * General method to parse commands for average of V1 Thinq Devices. + * + * @param rootNode ControlWifi root node + * @return return map with commands definition + */ + protected Map getCommandsDefinitionV1(JsonNode rootNode) { + boolean isBinaryCommands = MonitoringResultFormat.BINARY_FORMAT.getFormat() + .equals(rootNode.path("ControlWifi").path("type").textValue()); + JsonNode commandNode = rootNode.path("ControlWifi").path("action"); + if (commandNode.isMissingNode()) { + logger.warn("No commands found in the devices's definition. This is most likely a bug."); + return Collections.EMPTY_MAP; + } + Map commands = new HashMap<>(); + for (Iterator> it = commandNode.fields(); it.hasNext();) { + Map.Entry e = it.next(); + String commandName = e.getKey(); + CommandDefinition cd = new CommandDefinition(); + JsonNode thisCommandNode = e.getValue(); + JsonNode cmdField = thisCommandNode.path("cmd"); + if (cmdField.isMissingNode()) { + // command not supported + continue; + } + cd.setCommand(cmdField.textValue()); + cd.setCmdOpt(thisCommandNode.path("cmdOpt").textValue()); + cd.setCmdOptValue(thisCommandNode.path("value").textValue()); + cd.setBinary(isBinaryCommands); + String strData = Objects.requireNonNullElse(thisCommandNode.path("data").textValue(), ""); + cd.setDataTemplate(strData); + cd.setRawCommand(thisCommandNode.toPrettyString()); + int reservedIndex = 0; + // keep the order + if (!strData.isEmpty()) { + Map data = new LinkedHashMap<>(); + for (String f : strData.split(",")) { + if (f.contains("{")) { + // it's a featured field + // create data entry with the key and blank value + data.put(f.replaceAll("[{\\[}\\]]", ""), ""); + } else { + // its a fixed reserved value + data.put("Reserved" + reservedIndex, f.replaceAll("[{\\[}\\]]", "")); + reservedIndex++; + } + } + cd.setData(data); + } + commands.put(commandName, cd); + } + return commands; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java index b31995bd79851..f7a08d4ec5870 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java @@ -16,6 +16,11 @@ import java.util.List; import java.util.Map; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * The {@link ResultCodes} * @@ -23,7 +28,6 @@ */ public enum ResultCodes { - DEVICE_OFFLINE("Device Offline", "0106"), OK("Success", "0000", "0001"), DEVICE_NOT_RESPONSE("Device Not Response", "0111", "0103", "0104", "0106"), @@ -64,6 +68,7 @@ public enum ResultCodes { private final String description; private final List codes; + private static final ObjectMapper objectMapper = new ObjectMapper(); public boolean containsResultCode(String code) { return codes.contains(code); @@ -73,6 +78,18 @@ public String getDescription() { return description; } + public static String getReasonResponse(String jsonResponse) { + + try { + JsonNode devicesResult = objectMapper.readValue(jsonResponse, new TypeReference<>() { + }); + String resultCode = devicesResult.path("resultCode").asText(); + return String.format("%s - %s", resultCode, fromCode(resultCode).description); + } catch (JsonProcessingException e) { + return ""; + } + } + public List getCodes() { return codes; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java new file mode 100644 index 0000000000000..3a58b4d59f34d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java @@ -0,0 +1,199 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; + +/** + * The {@link AbstractDishWasherCapabilityFactory} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class AbstractDishWasherCapabilityFactory extends AbstractCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(AbstractDishWasherCapabilityFactory.class); + + protected abstract String getStateFeatureNodeName(); + + protected abstract String getProcessStateNodeName(); + + protected abstract String getPreStateFeatureNodeName(); + + // --- Selectable features ----- + protected abstract String getRinseFeatureNodeName(); + + protected abstract String getTemperatureFeatureNodeName(); + + protected abstract String getSpinFeatureNodeName(); + + // ------------------------------ + protected abstract String getSoilWashFeatureNodeName(); + + protected abstract String getDoorLockFeatureNodeName(); + + protected abstract MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode); + + protected abstract String getCommandRemoteStartNodeName(); + + protected abstract String getCommandStopNodeName(); + + protected abstract String getCommandWakeUpNodeName(); + + protected abstract String getDefaultCourseIdNodeName(); + + @Override + public DishWasherCapability create(JsonNode rootNode) throws LGThinqException { + DishWasherCapability wdCap = super.create(rootNode); + JsonNode coursesNode = rootNode.path(getCourseNodeName(rootNode)); + JsonNode smartCoursesNode = rootNode.path(getSmartCourseNodeName(rootNode)); + if (coursesNode.isMissingNode()) { + throw new LGThinqException("Course node not present in Capability Json Descriptor"); + } + + Map allCourses = new HashMap<>(getCourseDefinitions(coursesNode)); + allCourses.putAll(getSmartCourseDefinitions(smartCoursesNode)); + // TODO - Put Downloaded Course + wdCap.setCourses(allCourses); + + JsonNode monitorValueNode = rootNode.path(getMonitorValueNodeName()); + if (monitorValueNode.isMissingNode()) { + throw new LGThinqException("MonitoringValue node not found in the V2 WashingDryer cap definition."); + } + // mapping possible states + wdCap.setState(newFeatureDefinition(getStateFeatureNodeName(), monitorValueNode)); + wdCap.setProcessState(newFeatureDefinition(getProcessStateNodeName(), monitorValueNode)); + // --- Selectable features ----- + wdCap.setRinseFeat(newFeatureDefinition(getRinseFeatureNodeName(), monitorValueNode, + WM_CHANNEL_REMOTE_START_RINSE, WM_CHANNEL_RINSE_ID)); + wdCap.setTemperatureFeat(newFeatureDefinition(getTemperatureFeatureNodeName(), monitorValueNode, + WM_CHANNEL_REMOTE_START_TEMP, WM_CHANNEL_TEMP_LEVEL_ID)); + wdCap.setSpinFeat(newFeatureDefinition(getSpinFeatureNodeName(), monitorValueNode, WM_CHANNEL_REMOTE_START_SPIN, + WM_CHANNEL_SPIN_ID)); + // ---------------------------- + wdCap.setDryLevel(newFeatureDefinition(getDryLevelNodeName(), monitorValueNode)); + wdCap.setSoilWash(newFeatureDefinition(getSoilWashFeatureNodeName(), monitorValueNode)); + wdCap.setCommandsDefinition(getCommandsDefinition(rootNode)); + if (monitorValueNode.get(getDoorLockFeatureNodeName()) != null) { + wdCap.setHasDoorLook(true); + } + wdCap.setDefaultCourseFieldName(getConfigCourseType(rootNode)); + wdCap.setDefaultSmartCourseFeatName(getConfigSmartCourseType(rootNode)); + wdCap.setCommandStop(getCommandStopNodeName()); + wdCap.setCommandRemoteStart(getCommandRemoteStartNodeName()); + wdCap.setCommandWakeUp(getCommandWakeUpNodeName()); + // custom feature values map. + wdCap.setFeatureDefinitionMap( + Map.of(getTemperatureFeatureNodeName(), new DishWasherCapability.TemperatureFeatureFunction(), + getRinseFeatureNodeName(), new DishWasherCapability.RinseFeatureFunction(), + getSpinFeatureNodeName(), new DishWasherCapability.SpinFeatureFunction())); + wdCap.setMonitoringDataFormat(getMonitorDataFormat(rootNode)); + wdCap.setDefaultCourseId(rootNode.path("Config").path(getDefaultCourseIdNodeName()).asText()); + return wdCap; + } + + protected Map getGenericCourseDefinitions(JsonNode courseNode, CourseType type) { + Map coursesDef = new HashMap<>(); + courseNode.fields().forEachRemaining(e -> { + CourseDefinition cd = new CourseDefinition(); + JsonNode thisCourseNode = e.getValue(); + cd.setCourseName(thisCourseNode.path("_comment").textValue()); + if (CourseType.SMART_COURSE.equals(type)) { + cd.setBaseCourseName(thisCourseNode.path("Course").textValue()); + } + cd.setCourseType(type); + if (thisCourseNode.path("function").isArray()) { + // just to be safe here + ArrayNode functions = (ArrayNode) thisCourseNode.path("function"); + List functionList = cd.getFunctions(); + for (JsonNode fNode : functions) { + // map all course functions here + CourseFunction f = new CourseFunction(); + f.setValue(fNode.path("value").textValue()); + f.setDefaultValue(fNode.path("default").textValue()); + JsonNode selectableNode = fNode.path("selectable"); + // only Courses (not SmartCourses or DownloadedCourses) can have selectable functions + f.setSelectable( + !selectableNode.isMissingNode() && selectableNode.isArray() && (type == CourseType.COURSE)); + if (f.isSelectable()) { + List selectableValues = f.getSelectableValues(); + // map values acceptable for this function + for (JsonNode v : (ArrayNode) selectableNode) { + if (v.isValueNode()) { + selectableValues.add(v.textValue()); + } + } + f.setSelectableValues(selectableValues); + } + functionList.add(f); + } + cd.setFunctions(functionList); + } + coursesDef.put(e.getKey(), cd); + }); + CourseDefinition cdNotSelected = new CourseDefinition(); + cdNotSelected.setCourseType(type); + cdNotSelected.setCourseName("Not Selected"); + coursesDef.put(getNotSelectedCourseKey(), cdNotSelected); + return coursesDef; + } + + protected Map getCourseDefinitions(JsonNode courseNode) { + return getGenericCourseDefinitions(courseNode, CourseType.COURSE); + } + + protected Map getSmartCourseDefinitions(JsonNode smartCourseNode) { + return getGenericCourseDefinitions(smartCourseNode, CourseType.SMART_COURSE); + } + + protected abstract String getDryLevelNodeName(); + + protected abstract String getNotSelectedCourseKey(); + + @Override + public final List getSupportedDeviceTypes() { + return List.of(DeviceTypes.WASHERDRYER_MACHINE, DeviceTypes.DRYER); + } + + protected abstract String getCourseNodeName(JsonNode rootNode); + + protected abstract String getSmartCourseNodeName(JsonNode rootNode); + + protected abstract String getDefaultCourse(JsonNode rootNode); + + protected abstract String getRemoteFeatName(); + + protected abstract String getStandByFeatName(); + + protected abstract String getConfigCourseType(JsonNode rootNode); + + protected abstract String getConfigSmartCourseType(JsonNode rootNote); + + protected abstract String getConfigDownloadCourseType(JsonNode rootNode); + + protected abstract String getMonitorValueNodeName(); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseDefinition.java new file mode 100644 index 0000000000000..05b3dc5ea2a9b --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseDefinition.java @@ -0,0 +1,64 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CourseDefinition} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CourseDefinition { + private String courseName = ""; + // Name of the course this is based on. It's only used for SmartCourses + private String baseCourseName = ""; + private CourseType courseType = CourseType.UNDEF; + private List functions = new ArrayList<>(); + + public String getCourseName() { + return courseName; + } + + public String getBaseCourseName() { + return baseCourseName; + } + + public void setBaseCourseName(String baseCourseName) { + this.baseCourseName = baseCourseName; + } + + public void setCourseName(String courseName) { + this.courseName = courseName; + } + + public CourseType getCourseType() { + return courseType; + } + + public void setCourseType(CourseType courseType) { + this.courseType = courseType; + } + + public List getFunctions() { + return functions; + } + + public void setFunctions(List functions) { + this.functions = functions; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseFunction.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseFunction.java new file mode 100644 index 0000000000000..1b9486467cd83 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseFunction.java @@ -0,0 +1,63 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CourseFunction} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CourseFunction { + private String value = ""; + private String defaultValue = ""; + private boolean isSelectable; + private List selectableValues = new ArrayList<>(); + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public boolean isSelectable() { + return isSelectable; + } + + public void setSelectable(boolean selectable) { + isSelectable = selectable; + } + + public List getSelectableValues() { + return selectableValues; + } + + public void setSelectableValues(List selectableValues) { + this.selectableValues = selectableValues; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseType.java new file mode 100644 index 0000000000000..87b58d90103b7 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseType.java @@ -0,0 +1,39 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CourseType} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum CourseType { + // TODO - review DownloadCourse value, in remote start debugging + COURSE("Course"), + SMART_COURSE("SmartCourse"), + DOWNLOADED_COURSE("DownloadedCourse"), + UNDEF("Undefined"); + + private final String value; + + CourseType(String s) { + value = s; + } + + public String getValue() { + return value; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java new file mode 100644 index 0000000000000..8f0bb137463a0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java @@ -0,0 +1,234 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; + +/** + * The {@link DishWasherCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DishWasherCapability extends AbstractCapability { + private String defaultCourseFieldName = ""; + private String doorLockFeatName = ""; + private String childLockFeatName = ""; + private String defaultSmartCourseFieldName = ""; + private String commandRemoteStart = ""; + private String remoteStartFeatName = ""; + private String commandWakeUp = ""; + private String commandStop = ""; + private String defaultCourseId = ""; + private FeatureDefinition state = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition soilWash = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition spin = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition temperature = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition rinse = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition error = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition dryLevel = FeatureDefinition.NULL_DEFINITION; + private FeatureDefinition processState = FeatureDefinition.NULL_DEFINITION; + private boolean hasDoorLook; + private boolean hasTurboWash; + private Map commandsDefinition = new HashMap<>(); + private Map courses = new LinkedHashMap<>(); + + static class RinseFeatureFunction implements Function { + @Override + public FeatureDefinition apply(DishWasherCapability c) { + return c.getRinseFeat(); + } + } + + static class TemperatureFeatureFunction implements Function { + @Override + public FeatureDefinition apply(DishWasherCapability c) { + return c.getTemperatureFeat(); + } + } + + static class SpinFeatureFunction implements Function { + @Override + public FeatureDefinition apply(DishWasherCapability c) { + return c.getSpinFeat(); + } + } + + public String getDefaultCourseId() { + return defaultCourseId; + } + + public void setDefaultCourseId(String defaultCourseId) { + this.defaultCourseId = defaultCourseId; + } + + public Map getCommandsDefinition() { + return commandsDefinition; + } + + public FeatureDefinition getDryLevel() { + return dryLevel; + } + + public String getCommandStop() { + return commandStop; + } + + public void setCommandStop(String commandStop) { + this.commandStop = commandStop; + } + + public String getCommandRemoteStart() { + return commandRemoteStart; + } + + public void setCommandRemoteStart(String commandRemoteStart) { + this.commandRemoteStart = commandRemoteStart; + } + + public String getCommandWakeUp() { + return commandWakeUp; + } + + public void setCommandWakeUp(String commandWakeUp) { + this.commandWakeUp = commandWakeUp; + } + + public void setDryLevel(FeatureDefinition dryLevel) { + this.dryLevel = dryLevel; + } + + public FeatureDefinition getProcessState() { + return processState; + } + + public void setProcessState(FeatureDefinition processState) { + this.processState = processState; + } + + public void setCommandsDefinition(Map commandsDefinition) { + this.commandsDefinition = commandsDefinition; + } + + public Map getCourses() { + return courses; + } + + public void setCourses(Map courses) { + this.courses = courses; + } + + public FeatureDefinition getStateFeat() { + return state; + } + + public boolean hasDoorLook() { + return this.hasDoorLook; + } + + public void setHasDoorLook(boolean hasDoorLook) { + this.hasDoorLook = hasDoorLook; + } + + public void setState(FeatureDefinition state) { + this.state = state; + } + + public FeatureDefinition getSoilWash() { + return soilWash; + } + + public void setSoilWash(FeatureDefinition soilWash) { + this.soilWash = soilWash; + } + + public FeatureDefinition getSpinFeat() { + return spin; + } + + public void setSpinFeat(FeatureDefinition spin) { + this.spin = spin; + } + + public FeatureDefinition getTemperatureFeat() { + return temperature; + } + + public void setTemperatureFeat(FeatureDefinition temperature) { + this.temperature = temperature; + } + + public FeatureDefinition getRinseFeat() { + return rinse; + } + + public void setRinseFeat(FeatureDefinition rinse) { + this.rinse = rinse; + } + + public FeatureDefinition getError() { + return error; + } + + public void setError(FeatureDefinition error) { + this.error = error; + } + + public String getDefaultCourseFieldName() { + return defaultCourseFieldName; + } + + public void setDefaultCourseFieldName(String defaultCourseFieldName) { + this.defaultCourseFieldName = defaultCourseFieldName; + } + + public String getDefaultSmartCourseFeatName() { + return defaultSmartCourseFieldName; + } + + public void setDefaultSmartCourseFeatName(String defaultSmartCourseFieldName) { + this.defaultSmartCourseFieldName = defaultSmartCourseFieldName; + } + + public String getRemoteStartFeatName() { + return remoteStartFeatName; + } + + public void setRemoteStartFeatName(String remoteStartFeatName) { + this.remoteStartFeatName = remoteStartFeatName; + } + + public String getChildLockFeatName() { + return childLockFeatName; + } + + public void setChildLockFeatName(String childLockFeatName) { + this.childLockFeatName = childLockFeatName; + } + + public String getDoorLockFeatName() { + return doorLockFeatName; + } + + public void setDoorLockFeatName(String doorLockFeatName) { + this.doorLockFeatName = doorLockFeatName; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java new file mode 100644 index 0000000000000..65dca5d277f72 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java @@ -0,0 +1,231 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import java.util.*; +import java.util.concurrent.atomic.AtomicReference; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link DishWasherCapabilityFactoryV1} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DishWasherCapabilityFactoryV1 extends AbstractDishWasherCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(DishWasherCapabilityFactoryV1.class); + + @Override + public DishWasherCapability create(JsonNode rootNode) throws LGThinqException { + DishWasherCapability cap = super.create(rootNode); + cap.setRemoteStartFeatName("RemoteStart"); + cap.setChildLockFeatName("ChildLock"); + cap.setDoorLockFeatName("DoorLock"); + return cap; + } + + @Override + protected String getStateFeatureNodeName() { + return "State"; + } + + @Override + protected String getProcessStateNodeName() { + return "PreState"; + } + + @Override + protected String getPreStateFeatureNodeName() { + return "PreState"; + } + + @Override + protected String getRinseFeatureNodeName() { + return "RinseOption"; + } + + @Override + protected String getTemperatureFeatureNodeName() { + return "WaterTemp"; + } + + @Override + protected String getSpinFeatureNodeName() { + return "SpinSpeed"; + } + + @Override + protected String getSoilWashFeatureNodeName() { + return "Wash"; + } + + @Override + protected String getDoorLockFeatureNodeName() { + // there is no dook lock node in V1. + return "DUMMY_DOOR_LOCK"; + } + + @Override + protected MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode) { + String type = rootNode.path("Monitoring").path("type").textValue(); + return MonitoringResultFormat.getFormatOf(Objects.requireNonNullElse(type, "")); + } + + @Override + protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + return getCommandsDefinitionV1(rootNode); + } + + @Override + protected String getCommandRemoteStartNodeName() { + return "OperationStart"; + } + + @Override + protected String getCommandStopNodeName() { + return "OperationStop"; + } + + @Override + protected String getCommandWakeUpNodeName() { + return "OperationWakeUp"; + } + + @Override + protected String getDefaultCourseIdNodeName() { + return "defaultCourseId"; + } + + @Override + protected String getDryLevelNodeName() { + return "DryLevel"; + } + + @Override + protected String getNotSelectedCourseKey() { + return "0"; + } + + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V1_0); + } + + @Override + protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, + @Nullable String targetChannelId, @Nullable String refChannelId) { + JsonNode featureNode = featuresNode.path(featureName); + if (featureNode.isMissingNode()) { + return FeatureDefinition.NULL_DEFINITION; + } + FeatureDefinition fd = new FeatureDefinition(); + fd.setName(featureName); + fd.setLabel(featureName); + fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); + fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); + // All features from V1 are ENUMs + fd.setDataType(FeatureDataType.ENUM); + JsonNode valuesMappingNode = featureNode.path("option"); + if (!valuesMappingNode.isMissingNode()) { + + Map valuesMapping = new HashMap<>(); + valuesMappingNode.fields().forEachRemaining(e -> { + // collect values as: + // + // "option":{ + // "0":"@WM_STATE_POWER_OFF_W", + // to "0" -> "@WM_STATE_POWER_OFF_W" + valuesMapping.put(e.getKey(), e.getValue().asText()); + }); + fd.setValuesMapping(valuesMapping); + } + + return fd; + } + + @Override + public DishWasherCapability getCapabilityInstance() { + return new DishWasherCapability(); + } + + @Override + /* + * Return the default Course ID. + * OBS:In the V1, the default course points to the ID of the course list that is the default. + */ + protected String getDefaultCourse(JsonNode rootNode) { + return rootNode.path("Config").path("defaultCourseId").textValue(); + } + + @Override + protected String getRemoteFeatName() { + return "RemoteStart"; + } + + @Override + protected String getStandByFeatName() { + return "Standby"; + } + + @Override + protected String getConfigCourseType(JsonNode rootNode) { + if (rootNode.path(getMonitorValueNodeName()).path("APCourse").isMissingNode()) { + return "Course"; + } else { + return "APCourse"; + } + } + + @Override + protected String getCourseNodeName(JsonNode rootNode) { + JsonNode refOptions = rootNode.path(getMonitorValueNodeName()).path(getConfigCourseType(rootNode)) + .path("option"); + if (refOptions.isArray()) { + AtomicReference courseNodeName = new AtomicReference<>(""); + for (JsonNode node : refOptions) { + return node.asText(); + } + } + return ""; + } + + @Override + protected String getSmartCourseNodeName(JsonNode rootNode) { + return "SmartCourse"; + } + + @Override + protected String getConfigSmartCourseType(JsonNode rootNote) { + return "SmartCourse"; + } + + @Override + protected String getConfigDownloadCourseType(JsonNode rootNode) { + // just to ignore because there is no DownloadCourseType in V1 + return "XXXXXXXXXXX"; + } + + @Override + protected String getMonitorValueNodeName() { + return "Value"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java new file mode 100644 index 0000000000000..ba541e65075c7 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java @@ -0,0 +1,271 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import java.util.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ValueNode; + +/** + * The {@link DishWasherCapabilityFactoryV2} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DishWasherCapabilityFactoryV2 extends AbstractDishWasherCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(DishWasherCapabilityFactoryV2.class); + + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V2_0); + } + + @Override + public DishWasherCapability create(JsonNode rootNode) throws LGThinqException { + DishWasherCapability cap = super.create(rootNode); + cap.setRemoteStartFeatName("remoteStart"); + cap.setChildLockFeatName("standby"); + cap.setDoorLockFeatName("loadItemWasher"); + return cap; + } + + @Override + protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, + @Nullable String targetChannelId, @Nullable String refChannelId) { + JsonNode featureNode = featuresNode.path(featureName); + if (featureNode.isMissingNode()) { + return FeatureDefinition.NULL_DEFINITION; + } + FeatureDefinition fd = new FeatureDefinition(); + fd.setName(featureName); + fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); + fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); + + JsonNode labelNode = featureNode.path("label"); + if (!labelNode.isMissingNode() && !labelNode.isNull()) { + fd.setLabel(labelNode.asText()); + } else { + fd.setLabel(featureName); + } + // all features from V2 are enums + fd.setDataType(FeatureDataType.ENUM); + JsonNode valuesMappingNode = featureNode.path("valueMapping"); + if (!valuesMappingNode.isMissingNode()) { + + Map valuesMapping = new HashMap<>(); + valuesMappingNode.fields().forEachRemaining(e -> { + // collect values as: + // + // "POWEROFF": { + // "index": 0, + // "label": "@WM_STATE_POWER_OFF_W" + // }, + // to "POWEROFF" -> "@WM_STATE_POWER_OFF_W" + valuesMapping.put(e.getKey(), e.getValue().path("label").asText()); + }); + fd.setValuesMapping(valuesMapping); + } + + return fd; + } + + @Override + public DishWasherCapability getCapabilityInstance() { + return new DishWasherCapability(); + } + + @Override + protected String getCourseNodeName(JsonNode rootNode) { + String courseType = getConfigCourseType(rootNode); + return rootNode.path(getMonitorValueNodeName()).path(courseType).path("ref").textValue(); + } + + @Override + protected String getSmartCourseNodeName(JsonNode rootNode) { + return "SmartCourse"; + } + + private String getConfigNodeName() { + return "Config"; + } + + @Override + /* + * Return the default Course Name + * OBS:In the V2, the default course points to the default course name + */ + protected String getDefaultCourse(JsonNode rootNode) { + return rootNode.path(getConfigNodeName()).path("defaultCourse").textValue(); + } + + @Override + protected String getRemoteFeatName() { + return "remoteStart"; + } + + @Override + protected String getStandByFeatName() { + return "standby"; + } + + @Override + protected String getConfigCourseType(JsonNode rootNode) { + return rootNode.path(getConfigNodeName()).path("courseType").textValue(); + } + + protected String getConfigSmartCourseType(JsonNode rootNode) { + return rootNode.path(getConfigNodeName()).path("smartCourseType").textValue(); + } + + protected String getConfigDownloadCourseType(JsonNode rootNode) { + return rootNode.path(getConfigNodeName()).path("downloadedCourseType").textValue(); + } + + @Override + protected String getStateFeatureNodeName() { + return "state"; + } + + @Override + protected String getProcessStateNodeName() { + return "preState"; + } + + @Override + protected String getPreStateFeatureNodeName() { + return "preState"; + } + + @Override + protected String getRinseFeatureNodeName() { + return "rinse"; + } + + @Override + protected String getTemperatureFeatureNodeName() { + return "temp"; + } + + @Override + protected String getSpinFeatureNodeName() { + return "spin"; + } + + @Override + protected String getSoilWashFeatureNodeName() { + return "soilWash"; + } + + @Override + protected String getDoorLockFeatureNodeName() { + return "doorLock"; + } + + @Override + protected MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode) { + // All v2 are Json format + return MonitoringResultFormat.JSON_FORMAT; + } + + @Override + protected Map getCommandsDefinition(JsonNode rootNode) { + JsonNode commandNode = rootNode.path("ControlWifi"); + List escapeDataValues = Arrays.asList("course", "SmartCourse", "doorLock", "childLock"); + if (commandNode.isMissingNode()) { + logger.warn("No commands found in the DryerWasher definition. This is most likely a bug."); + return Collections.EMPTY_MAP; + } + Map commands = new HashMap<>(); + for (Iterator> it = commandNode.fields(); it.hasNext();) { + Map.Entry e = it.next(); + String commandName = e.getKey(); + if (commandName.equals("vtCtrl")) { + // ignore command + continue; + } + CommandDefinition cd = new CommandDefinition(); + JsonNode thisCommandNode = e.getValue(); + cd.setCommand(thisCommandNode.path("command").textValue()); + JsonNode dataValues = thisCommandNode.path("data").path("washerDryer"); + if (!dataValues.isMissingNode()) { + Map data = new HashMap<>(); + dataValues.fields().forEachRemaining(f -> { + // only load features outside escape. + if (!escapeDataValues.contains(f.getKey())) { + if (f.getValue().isValueNode()) { + ValueNode vn = (ValueNode) f.getValue(); + if (f.getValue().isTextual()) { + data.put(f.getKey(), vn.asText()); + } else if (f.getValue().isNumber()) { + data.put(f.getKey(), vn.asInt()); + } + } + } + }); + // add extra data features + data.put(getConfigCourseType(rootNode), ""); + data.put(getConfigSmartCourseType(rootNode), ""); + data.put("courseType", ""); + cd.setData(data); + cd.setRawCommand(thisCommandNode.toPrettyString()); + } else { + logger.warn("Data node not found in the WasherDryer definition. It's most likely a bug"); + } + commands.put(commandName, cd); + } + return commands; + } + + @Override + protected String getCommandRemoteStartNodeName() { + return "WMStart"; + } + + @Override + protected String getCommandStopNodeName() { + return "WMStop"; + } + + @Override + protected String getCommandWakeUpNodeName() { + return "WMWakeup"; + } + + @Override + protected String getDefaultCourseIdNodeName() { + return "defaultCourse"; + } + + @Override + protected String getNotSelectedCourseKey() { + return "NOT_SELECTED"; + } + + @Override + protected String getMonitorValueNodeName() { + return "MonitoringValue"; + } + + @Override + protected String getDryLevelNodeName() { + return "dryLevel"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java new file mode 100644 index 0000000000000..12c3845f864ca --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java @@ -0,0 +1,318 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link DishWasherSnapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class DishWasherSnapshot extends AbstractSnapshotDefinition { + private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; + private String state = ""; + private String processState = ""; + private boolean online; + private String course = ""; + private String smartCourse = ""; + private String downloadedCourse = ""; + private String temperatureLevel = ""; + private String doorLock = ""; + private String option1 = ""; + private String option2 = ""; + private String childLock = ""; + private Double remainingHour = 0.00; + private Double remainingMinute = 0.00; + private Double reserveHour = 0.00; + private Double reserveMinute = 0.00; + + private String remoteStart = ""; + private boolean remoteStartEnabled = false; + private String standByStatus = ""; + + private String dryLevel = ""; + private boolean standBy = false; + private String error = ""; + private String rinse = ""; + private String spin = ""; + + private String loadItem = ""; + + public String getLoadItem() { + return loadItem; + } + + @JsonAlias({ "LoadItem" }) + @JsonProperty("loadItemWasher") + public void setLoadItem(String loadItem) { + this.loadItem = loadItem; + } + + @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) + @JsonProperty("courseFL24inchBaseTitan") + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } + + @JsonProperty("dryLevel") + @JsonAlias({ "DryLevel" }) + public String getDryLevel() { + return dryLevel; + } + + public void setDryLevel(String dryLevel) { + this.dryLevel = dryLevel; + } + + @JsonProperty("processState") + @JsonAlias({ "ProcessState", "preState", "PreState" }) + public String getProcessState() { + return processState; + } + + public void setProcessState(String processState) { + this.processState = processState; + } + + @JsonProperty("error") + @JsonAlias({ "Error" }) + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + @Override + public DevicePowerState getPowerStatus() { + return powerState; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + this.powerState = value; + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } + + @JsonProperty("state") + @JsonAlias({ "state", "State" }) + public String getState() { + return state; + } + + @JsonProperty("smartCourseFL24inchBaseTitan") + @JsonAlias({ "smartCourseFL24inchBaseTitan", "SmartCourse" }) + public String getSmartCourse() { + return smartCourse; + } + + @JsonProperty("downloadedCourseFL24inchBaseTitan") + @JsonAlias({ "downloadedCourseFLUpper25inchBaseUS" }) + public String getDownloadedCourse() { + return downloadedCourse; + } + + public void setDownloadedCourse(String downloadedCourse) { + this.downloadedCourse = downloadedCourse; + } + + @JsonIgnore + public String getRemainingTime() { + return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); + } + + @JsonIgnore + public String getReserveTime() { + return String.format("%02.0f:%02.0f", getReserveHour(), getReserveMinute()); + } + + @JsonProperty("remainTimeHour") + @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + public Double getRemainingHour() { + return remainingHour; + } + + public void setRemainingHour(Double remainingHour) { + this.remainingHour = remainingHour; + } + + @JsonProperty("remainTimeMinute") + @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + public Double getRemainingMinute() { + return remainingMinute; + } + + public void setRemainingMinute(Double remainingMinute) { + this.remainingMinute = remainingMinute; + } + + @JsonProperty("reserveTimeHour") + @JsonAlias({ "reserveTimeHour", "Reserve_Time_H" }) + public Double getReserveHour() { + return reserveHour; + } + + public void setReserveHour(Double reserveHour) { + this.reserveHour = reserveHour; + } + + @JsonProperty("reserveTimeMinute") + @JsonAlias({ "reserveTimeMinute", "Reserve_Time_M" }) + public Double getReserveMinute() { + return reserveMinute; + } + + public void setReserveMinute(Double reserveMinute) { + this.reserveMinute = reserveMinute; + } + + public void setSmartCourse(String smartCourse) { + this.smartCourse = smartCourse; + } + + @JsonProperty("temp") + @JsonAlias({ "WaterTemp" }) + public String getTemperatureLevel() { + return temperatureLevel; + } + + public void setTemperatureLevel(String temperatureLevel) { + this.temperatureLevel = temperatureLevel; + } + + @JsonProperty("doorLock") + @JsonAlias({ "DoorLock", "DoorClose" }) + public String getDoorLock() { + return doorLock; + } + + public void setDoorLock(String doorLock) { + this.doorLock = doorLock; + } + + @JsonProperty("ChildLock") + @JsonAlias({ "childLock" }) + public String getChildLock() { + return childLock; + } + + public void setChildLock(String childLock) { + this.childLock = childLock; + } + + public void setState(String state) { + this.state = state; + if (state.equals(WM_POWER_OFF_VALUE)) { + powerState = DevicePowerState.DV_POWER_OFF; + } else { + powerState = DevicePowerState.DV_POWER_ON; + } + } + + public boolean isRemoteStartEnabled() { + return remoteStartEnabled; + } + + @JsonProperty("remoteStart") + @JsonAlias({ "RemoteStart" }) + public String getRemoteStart() { + return remoteStart; + } + + public void setRemoteStart(String remoteStart) { + this.remoteStart = remoteStart.contains("ON") || remoteStart.equals("1") ? "ON" + : (remoteStart.contains("OFF") || remoteStart.equals("0") ? "OFF" : remoteStart); + remoteStartEnabled = this.remoteStart.equals("ON"); + } + + @JsonProperty("standby") + @JsonAlias({ "Standby" }) + public String getStandByStatus() { + return standByStatus; + } + + public void setStandByStatus(String standByStatus) { + this.standByStatus = standByStatus.contains("ON") || standByStatus.equals("1") ? "ON" + : (standByStatus.contains("OFF") || standByStatus.equals("0") ? "OFF" : standByStatus); + ; + standBy = this.standByStatus.contains("ON"); + } + + public boolean isStandBy() { + return standBy; + } + + @JsonProperty("rinse") + @JsonAlias({ "RinseOption" }) + public String getRinse() { + return rinse; + } + + public void setRinse(String rinse) { + this.rinse = rinse; + } + + @JsonProperty("spin") + @JsonAlias({ "SpinSpeed" }) + public String getSpin() { + return spin; + } + + public void setSpin(String spin) { + this.spin = spin; + } + + @JsonProperty("Option1") + public String getOption1() { + return option1; + } + + public void setOption1(String option1) { + this.option1 = option1; + } + + @JsonProperty("Option2") + public String getOption2() { + return option2; + } + + public void setOption2(String option2) { + this.option2 = option2; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java new file mode 100644 index 0000000000000..aebe81910676f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java @@ -0,0 +1,123 @@ +/** + * 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.lgthinq.lgservices.model.devices.dishwasher; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; + +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.*; + +/** + * The {@link WasherDryerSnapshotBuilder} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DishWasherSnapshotBuilder extends DefaultSnapshotBuilder { + public DishWasherSnapshotBuilder() { + super(DishWasherSnapshot.class); + } + + @Override + public DishWasherSnapshot createFromBinary(String binaryData, List prot, + CapabilityDefinition capDef) throws LGThinqUnmarshallException, LGThinqApiException { + DishWasherSnapshot snap = super.createFromBinary(binaryData, prot, capDef); + snap.setRemoteStart( + bitValue(((DishWasherCapability) capDef).getRemoteStartFeatName(), snap.getRawData(), capDef)); + snap.setDoorLock(bitValue(((DishWasherCapability) capDef).getDoorLockFeatName(), snap.getRawData(), capDef)); + snap.setChildLock(bitValue(((DishWasherCapability) capDef).getChildLockFeatName(), snap.getRawData(), capDef)); + return snap; + } + + @Override + protected DishWasherSnapshot getSnapshot(Map snapMap, CapabilityDefinition capDef) { + DishWasherSnapshot snap; + DeviceTypes type = capDef.getDeviceType(); + LGAPIVerion version = capDef.getDeviceVersion(); + switch (type) { + case WASHING_TOWER: + case WASHERDRYER_MACHINE: + switch (version) { + case V1_0: { + snap = objectMapper.convertValue(snapMap, snapClass); + snap.setRawData(snapMap); + } + case V2_0: { + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), + "washerDryer node must be present in the snapshot"); + snap = objectMapper.convertValue(washerDryerMap, snapClass); + setAltCourseNodeName(capDef, snap, washerDryerMap); + snap.setRawData(washerDryerMap); + return snap; + } + } + case DRYER_TOWER: + case DRYER: + switch (version) { + case V1_0: { + throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + } + case V2_0: { + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), + "washerDryer node must be present in the snapshot"); + snap = objectMapper.convertValue(washerDryerMap, snapClass); + setAltCourseNodeName(capDef, snap, washerDryerMap); + snap.setRawData(snapMap); + return snap; + } + } + } + throw new IllegalStateException( + "Snapshot for device type " + type + " not supported for this builder. It most likely a bug"); + } + + private static void setAltCourseNodeName(CapabilityDefinition capDef, DishWasherSnapshot snap, + Map washerDryerMap) { + if (snap.getCourse().isEmpty() && capDef instanceof DishWasherCapability) { + String altCourseNodeName = ((DishWasherCapability) capDef).getDefaultCourseFieldName(); + String altSmartCourseNodeName = ((DishWasherCapability) capDef).getDefaultSmartCourseFeatName(); + snap.setCourse(Objects.requireNonNullElse((String) washerDryerMap.get(altCourseNodeName), "")); + snap.setSmartCourse(Objects.requireNonNullElse((String) washerDryerMap.get(altSmartCourseNodeName), "")); + } + } + + @Override + protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { + switch (type) { + case DRYER_TOWER: + case DRYER: + return LGAPIVerion.V2_0; + case WASHING_TOWER: + case WASHERDRYER_MACHINE: + if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { + return LGAPIVerion.V2_0; + } else if (snapMap.containsKey("State")) { + return LGAPIVerion.V1_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine WASHERDRYER_MACHINE API version."); + } + default: + throw new IllegalStateException("Discovery version for device type " + type + + " not supported for this builder. It most likely a bug"); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java index 1a514d5db9939..d3a1dba8e8b9d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java @@ -22,7 +22,6 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,36 +36,17 @@ public abstract class AbstractFridgeCapabilityFactory extends AbstractCapabilityFactory { private static final Logger logger = LoggerFactory.getLogger(AbstractFridgeCapabilityFactory.class); - private void loadTempNode(JsonNode tempNode, Map capMap, String unit) { - tempNode.forEach(v -> { - // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' - capMap.put(v.path("index").asText() + " " + unit, v.path("label").textValue() + " " + unit); - }); - } + protected abstract void loadTempNode(JsonNode tempNode, Map capMap, String unit); @Override public FridgeCapability create(JsonNode rootNode) throws LGThinqException { FridgeCapability frCap = super.create(rootNode); - JsonNode node = mapper.valueToTree(rootNode); if (node.isNull()) { logger.error("Can't parse json capability for Fridge. The payload has been ignored"); logger.debug("payload {}", rootNode); throw new LGThinqException("Can't parse json capability for Fridge. The payload has been ignored"); } - /** - * iterate over valueMappings like: - * "valueMapping": { - * "1": {"index" : 1, "label" : "7", "_comment" : ""}, - * "2": {"index" : 2, "label" : "6", "_comment" : ""}, - * "3": {"index" : 3, "label" : "5", "_comment" : ""}, - * "4": {"index" : 4, "label" : "4", "_comment" : ""}, - * "5": {"index" : 5, "label" : "3", "_comment" : ""}, - * "6": {"index" : 6, "label" : "2", "_comment" : ""}, - * "7": {"index" : 7, "label" : "1", "_comment" : ""}, - * "255" : {"index" : 255, "label" : "IGNORE", "_comment" : ""} - * } - */ JsonNode fridgeTempCNode = node.path(getMonitorValueNodeName()).path(getFridgeTempCNodeName()) .path(getOptionsNodeName()); @@ -76,23 +56,62 @@ public FridgeCapability create(JsonNode rootNode) throws LGThinqException { .path(getOptionsNodeName()); JsonNode freezerTempFNode = node.path(getMonitorValueNodeName()).path(getFreezerTempFNodeName()) .path(getOptionsNodeName()); + JsonNode tempUnitNode = node.path(getMonitorValueNodeName()).path(getTempUnitNodeName()) + .path(getOptionsNodeName()); + JsonNode icePlusNode = node.path(getMonitorValueNodeName()).path(getIcePlusNodeName()) + .path(getOptionsNodeName()); + JsonNode freshAirFilterNode = node.path(getMonitorValueNodeName()).path(getFreshAirFilterNodeName()) + .path(getOptionsNodeName()); + JsonNode waterFilterNode = node.path(getMonitorValueNodeName()).path(getWaterFilterNodeName()) + .path(getOptionsNodeName()); + JsonNode expressModeNode = node.path(getMonitorValueNodeName()).path(getExpressModeNodeName()) + .path(getOptionsNodeName()); + JsonNode smartSavingModeNode = node.path(getMonitorValueNodeName()).path(getSmartSavingModeNodeName()) + .path(getOptionsNodeName()); + JsonNode activeSavingNode = node.path(getMonitorValueNodeName()).path(getActiveSavingNodeName()) + .path(getOptionsNodeName()); + JsonNode atLeastOneDoorOpenNode = node.path(getMonitorValueNodeName()).path(getAtLeastOneDoorOpenNodeName()) + .path(getOptionsNodeName()); + loadTempNode(fridgeTempCNode, frCap.getFridgeTempCMap(), TEMP_UNIT_CELSIUS_SYMBOL); loadTempNode(fridgeTempFNode, frCap.getFridgeTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); loadTempNode(freezerTempCNode, frCap.getFreezerTempCMap(), TEMP_UNIT_CELSIUS_SYMBOL); loadTempNode(freezerTempFNode, frCap.getFreezerTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + loadTempUnitNode(tempUnitNode, frCap.getTempUnitMap()); + loadIcePlus(icePlusNode, frCap.getIcePlusMap()); + loadFreshAirFilter(freshAirFilterNode, frCap.getFreshAirFilterMap()); + loadWaterFilter(waterFilterNode, frCap.getWaterFilterMap()); + loadExpressMode(expressModeNode, frCap.getExpressModeMap()); + loadSmartSavingMode(smartSavingModeNode, frCap.getSmartSavingMap()); + loadActiveSaving(activeSavingNode, frCap.getActiveSavingMap()); + loadAtLeastOneDoorOpen(atLeastOneDoorOpenNode, frCap.getAtLeastOneDoorOpenMap()); + + frCap.getCommandsDefinition().putAll(getCommandsDefinition(node)); return frCap; } + protected abstract void loadTempUnitNode(JsonNode tempUnitNode, Map tempUnitMap); + + protected abstract void loadIcePlus(JsonNode icePlusNode, Map icePlusMap); + + protected abstract void loadFreshAirFilter(JsonNode freshAirFilterNode, Map freshAirFilterMap); + + protected abstract void loadWaterFilter(JsonNode waterFilterNode, Map waterFilterMap); + + protected abstract void loadExpressMode(JsonNode expressModeNode, Map expressModeMap); + + protected abstract void loadSmartSavingMode(JsonNode smartSavingModeNode, Map smartSavingModeMap); + + protected abstract void loadActiveSaving(JsonNode activeSavingNode, Map activeSavingMap); + + protected abstract void loadAtLeastOneDoorOpen(JsonNode atLeastOneDoorOpenNode, + Map atLeastOneDoorOpenMap); + @Override protected List getSupportedDeviceTypes() { return List.of(DeviceTypes.REFRIGERATOR); } - @Override - protected List getSupportedAPIVersions() { - return List.of(LGAPIVerion.V1_0, LGAPIVerion.V2_0); - } - protected abstract String getMonitorValueNodeName(); protected abstract String getFridgeTempCNodeName(); @@ -103,5 +122,21 @@ protected List getSupportedAPIVersions() { protected abstract String getFreezerTempFNodeName(); + protected abstract String getTempUnitNodeName(); + + protected abstract String getIcePlusNodeName(); + + protected abstract String getFreshAirFilterNodeName(); + + protected abstract String getWaterFilterNodeName(); + + protected abstract String getExpressModeNodeName(); + + protected abstract String getSmartSavingModeNodeName(); + + protected abstract String getActiveSavingNodeName(); + + protected abstract String getAtLeastOneDoorOpenNodeName(); + protected abstract String getOptionsNodeName(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java index 8f3fe9dd831b0..d12fe17aa70b8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java @@ -17,6 +17,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,6 +39,15 @@ public class FridgeCanonicalCapability extends AbstractCapability fridgeTempFMap = new LinkedHashMap(); private final Map freezerTempCMap = new LinkedHashMap(); private final Map freezerTempFMap = new LinkedHashMap(); + private final Map tempUnitMap = new LinkedHashMap(); + private final Map icePlusMap = new LinkedHashMap();; + private final Map freshAirFilterMap = new LinkedHashMap();; + private final Map waterFilterMap = new LinkedHashMap();; + private final Map expressModeMap = new LinkedHashMap();; + private final Map smartSavingMap = new LinkedHashMap();; + private final Map activeSavingMap = new LinkedHashMap();; + private final Map atLeastOneDoorOpenMap = new LinkedHashMap<>(); + private final Map commandsDefinition = new LinkedHashMap<>(); public Map getFridgeTempCMap() { return fridgeTempCMap; @@ -54,4 +64,48 @@ public Map getFreezerTempCMap() { public Map getFreezerTempFMap() { return freezerTempFMap; } + + @Override + public Map getTempUnitMap() { + return tempUnitMap; + } + + @Override + public Map getIcePlusMap() { + return icePlusMap; + } + + @Override + public Map getFreshAirFilterMap() { + return freshAirFilterMap; + } + + @Override + public Map getWaterFilterMap() { + return waterFilterMap; + } + + @Override + public Map getExpressModeMap() { + return expressModeMap; + } + + @Override + public Map getSmartSavingMap() { + return smartSavingMap; + } + + @Override + public Map getActiveSavingMap() { + return activeSavingMap; + } + + @Override + public Map getAtLeastOneDoorOpenMap() { + return atLeastOneDoorOpenMap; + } + + public Map getCommandsDefinition() { + return commandsDefinition; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java index b56570796e545..78bdb6ec708fa 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java @@ -41,8 +41,22 @@ public class FridgeCanonicalSnapshot extends AbstractFridgeSnapshot { private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default private String doorStatus = ""; + private String waterFilterUsedMonth = ""; + private String freshAirFilterState = ""; + + private String expressMode = ""; + + @JsonProperty("expressMode") + public String getExpressMode() { + return expressMode; + } + + public void setExpressMode(String expressMode) { + this.expressMode = expressMode; + } @JsonProperty("atLeastOneDoorOpen") + @JsonAlias("DoorOpenState") public String getDoorStatus() { return doorStatus; } @@ -108,6 +122,26 @@ public DevicePowerState getPowerStatus() { public void setPowerStatus(DevicePowerState value) { } + @JsonAlias({ "WaterFilterUsedMonth" }) + @JsonProperty("waterFilter") + public String getWaterFilterUsedMonth() { + return waterFilterUsedMonth; + } + + @JsonAlias({ "FreshAirFilter" }) + @JsonProperty("freshAirFilter") + public String getFreshAirFilterState() { + return freshAirFilterState; + } + + public void setWaterFilterUsedMonth(String waterFilterUsedMonth) { + this.waterFilterUsedMonth = waterFilterUsedMonth; + } + + public void setFreshAirFilterState(String freshAirFilterState) { + this.freshAirFilterState = freshAirFilterState; + } + @Override public boolean isOnline() { return online; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java index 5237deb13cc24..7d427285cbcdd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java @@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; /** * The {@link FridgeCapability} @@ -32,4 +33,22 @@ public interface FridgeCapability extends CapabilityDefinition { public Map getFreezerTempCMap(); public Map getFreezerTempFMap(); + + public Map getTempUnitMap(); + + Map getIcePlusMap(); + + Map getFreshAirFilterMap(); + + Map getWaterFilterMap(); + + Map getExpressModeMap(); + + Map getSmartSavingMap(); + + Map getActiveSavingMap(); + + Map getAtLeastOneDoorOpenMap(); + + Map getCommandsDefinition(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index bb72454eb3e83..7f6839d790f7b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -12,14 +12,18 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; -import java.util.Collections; -import java.util.Map; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; @@ -30,6 +34,8 @@ */ @NonNullByDefault public class FridgeCapabilityFactoryV1 extends AbstractFridgeCapabilityFactory { + private static final Logger logger = LoggerFactory.getLogger(FridgeCapabilityFactoryV1.class); + @Override protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, @Nullable String targetChannelId, @Nullable String refChannelId) { @@ -37,10 +43,76 @@ protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode fe return FeatureDefinition.NULL_DEFINITION; } - // TODO - Implement Commands parser @Override protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { - return Collections.emptyMap(); + return getCommandsDefinitionV1(rootNode); + } + + private void loadGenericFeatNode(JsonNode featNode, Map capMap, + final Map constantsMap) { + featNode.fields().forEachRemaining(f -> { + // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' + String translatedValue = constantsMap.get(f.getValue().asText()); + translatedValue = translatedValue == null ? f.getValue().asText() : translatedValue; + capMap.put(f.getKey(), translatedValue); + }); + } + + protected void loadTempNode(JsonNode tempNode, Map capMap, String unit) { + loadGenericFeatNode(tempNode, capMap, Collections.emptyMap()); + } + + @Override + protected void loadTempUnitNode(JsonNode tempUnitNode, Map tempUnitMap) { + loadGenericFeatNode(tempUnitNode, tempUnitMap, Collections.emptyMap()); + } + + @Override + protected void loadIcePlus(JsonNode icePlusNode, Map icePlusMap) { + loadGenericFeatNode(icePlusNode, icePlusMap, CAP_FR_ON_OFF); + } + + @Override + protected void loadFreshAirFilter(JsonNode freshAirFilterNode, Map freshAirFilterMap) { + loadGenericFeatNode(freshAirFilterNode, freshAirFilterMap, CAP_FR_FRESH_AIR_FILTER_MAP); + } + + @Override + protected void loadWaterFilter(JsonNode waterFilterNode, Map waterFilterMap) { + int minValue = waterFilterNode.path("min").asInt(0); + int maxValue = waterFilterNode.path("max").asInt(6); + for (int i = minValue; i <= maxValue; i++) { + waterFilterMap.put(String.valueOf(i), i + CAP_FR_WATER_FILTER_USED_POSTFIX); + } + } + + @Override + protected void loadExpressMode(JsonNode expressModeNode, Map expressModeMap) { + // not supported + } + + @Override + protected void loadSmartSavingMode(JsonNode smartSavingModeNode, Map smartSavingModeMap) { + loadGenericFeatNode(smartSavingModeNode, smartSavingModeMap, CAP_FR_SMART_SAVING_MODE); + } + + @Override + protected void loadActiveSaving(JsonNode activeSavingNode, Map activeSavingMap) { + int minValue = activeSavingNode.path("min").asInt(0); + int maxValue = activeSavingNode.path("max").asInt(3); + for (int i = minValue; i <= maxValue; i++) { + activeSavingMap.put(String.valueOf(i), String.valueOf(i)); + } + } + + @Override + protected void loadAtLeastOneDoorOpen(JsonNode atLeastOneDoorOpenNode, Map atLeastOneDoorOpenMap) { + loadGenericFeatNode(atLeastOneDoorOpenNode, atLeastOneDoorOpenMap, Collections.emptyMap()); + } + + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V1_0); } @Override @@ -50,31 +122,69 @@ public FridgeCapability getCapabilityInstance() { @Override protected String getMonitorValueNodeName() { - return "MonitoringValue"; + return "Value"; } @Override protected String getFridgeTempCNodeName() { - return "fridgeTemp_C"; + return "TempRefrigerator_C"; } @Override protected String getFridgeTempFNodeName() { - return "fridgeTemp_F"; + return "TempRefrigerator_F"; } @Override protected String getFreezerTempCNodeName() { - return "freezerTemp_C"; + return "TempFreezer_C"; } @Override protected String getFreezerTempFNodeName() { - return "freezerTemp_F"; + return "TempFreezer_F"; } @Override protected String getOptionsNodeName() { - return "valueMapping"; + return "option"; + } + + protected String getTempUnitNodeName() { + return "TempUnit"; + } + + protected String getIcePlusNodeName() { + return "IcePlus"; + } + + @Override + protected String getFreshAirFilterNodeName() { + return "FreshAirFilter"; + } + + @Override + protected String getWaterFilterNodeName() { + return "WaterFilterUsedMonth"; + } + + @Override + protected String getExpressModeNodeName() { + return "UNSUPPORTED"; + } + + @Override + protected String getSmartSavingModeNodeName() { + return "SmartSavingMode"; + } + + @Override + protected String getActiveSavingNodeName() { + return "SmartSavingModeStatus"; + } + + @Override + protected String getAtLeastOneDoorOpenNodeName() { + return "DoorOpenState"; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java index b2b2268f9cdac..efab278ce0f7e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -12,7 +12,10 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + import java.util.Collections; +import java.util.List; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -20,6 +23,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; import com.fasterxml.jackson.databind.JsonNode; @@ -36,17 +40,77 @@ protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode fe return FeatureDefinition.NULL_DEFINITION; } - // TODO - Implement Commands parser @Override protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + // doesn't meter command definition for V2 return Collections.emptyMap(); } + @Override + protected List getSupportedAPIVersions() { + return List.of(LGAPIVerion.V2_0); + } + @Override public FridgeCapability getCapabilityInstance() { return new FridgeCanonicalCapability(); } + private void loadGenericFeatNode(JsonNode featNode, Map capMap, + final Map constantsMap) { + featNode.fields().forEachRemaining(f -> { + // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' + String translatedValue = constantsMap.get(f.getValue().path("label").asText()); + translatedValue = translatedValue == null ? f.getValue().path("label").asText() : translatedValue; + capMap.put(f.getKey(), translatedValue); + }); + } + + @Override + protected void loadTempNode(JsonNode tempNode, Map capMap, String unit) { + loadGenericFeatNode(tempNode, capMap, Collections.emptyMap()); + } + + @Override + protected void loadTempUnitNode(JsonNode tempUnitNode, Map tempUnitMap) { + tempUnitMap.putAll(CAP_FR_TEMP_UNIT_V2_MAP); + } + + @Override + protected void loadIcePlus(JsonNode icePlusNode, Map icePlusMap) { + // not supported + } + + @Override + protected void loadFreshAirFilter(JsonNode freshAirFilterNode, Map freshAirFilterMap) { + loadGenericFeatNode(freshAirFilterNode, freshAirFilterMap, CAP_FR_FRESH_AIR_FILTER_MAP); + } + + @Override + protected void loadWaterFilter(JsonNode waterFilterNode, Map waterFilterMap) { + loadGenericFeatNode(waterFilterNode, waterFilterMap, CAP_FR_WATER_FILTER); + } + + @Override + protected void loadExpressMode(JsonNode expressModeNode, Map expressModeMap) { + loadGenericFeatNode(expressModeNode, expressModeMap, CAP_FR_WATER_FILTER); + } + + @Override + protected void loadSmartSavingMode(JsonNode smartSavingModeNode, Map smartSavingModeMap) { + loadGenericFeatNode(smartSavingModeNode, smartSavingModeMap, CAP_FR_SMART_SAVING_V2_MODE); + } + + @Override + protected void loadActiveSaving(JsonNode activeSavingNode, Map activeSavingMap) { + loadGenericFeatNode(activeSavingNode, activeSavingMap, CAP_FR_LABEL_ON_OFF); + } + + @Override + protected void loadAtLeastOneDoorOpen(JsonNode atLeastOneDoorOpenNode, Map atLeastOneDoorOpenMap) { + loadGenericFeatNode(atLeastOneDoorOpenNode, atLeastOneDoorOpenMap, CAP_FR_LABEL_CLOSE_OPEN); + } + @Override protected String getMonitorValueNodeName() { return "MonitoringValue"; @@ -72,6 +136,44 @@ protected String getFreezerTempFNodeName() { return "freezerTemp_F"; } + protected String getTempUnitNodeName() { + return "tempUnit"; + } + + protected String getIcePlusNodeName() { + return "UNSUPPORTED"; + } + + @Override + protected String getFreshAirFilterNodeName() { + return "freshAirFilter"; + } + + @Override + protected String getWaterFilterNodeName() { + return "waterFilter"; + } + + @Override + protected String getExpressModeNodeName() { + return "expressMode"; + } + + @Override + protected String getSmartSavingModeNodeName() { + return "smartSavingMode"; + } + + @Override + protected String getActiveSavingNodeName() { + return "activeSaving"; + } + + @Override + protected String getAtLeastOneDoorOpenNodeName() { + return "atLeastOneDoorOpen"; + } + @Override protected String getOptionsNodeName() { return "valueMapping"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java index 6e5ffb410ca94..cd86a9cab5a76 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -96,51 +96,7 @@ protected MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode) { @Override protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { - boolean isBinaryCommands = MonitoringResultFormat.BINARY_FORMAT.getFormat() - .equals(rootNode.path("ControlWifi").path("type").textValue()); - JsonNode commandNode = rootNode.path("ControlWifi").path("action"); - if (commandNode.isMissingNode()) { - logger.warn("No commands found in the DryerWasher definition. This is most likely a bug."); - return Collections.EMPTY_MAP; - } - Map commands = new HashMap<>(); - for (Iterator> it = commandNode.fields(); it.hasNext();) { - Map.Entry e = it.next(); - String commandName = e.getKey(); - CommandDefinition cd = new CommandDefinition(); - JsonNode thisCommandNode = e.getValue(); - JsonNode cmdField = thisCommandNode.path("cmd"); - if (cmdField.isMissingNode()) { - // command not supported - continue; - } - cd.setCommand(cmdField.textValue()); - cd.setCmdOpt(thisCommandNode.path("cmdOpt").textValue()); - cd.setCmdOptValue(thisCommandNode.path("value").textValue()); - cd.setBinary(isBinaryCommands); - String strData = Objects.requireNonNullElse(thisCommandNode.path("data").textValue(), ""); - cd.setDataTemplate(strData); - cd.setRawCommand(thisCommandNode.toPrettyString()); - int reservedIndex = 0; - // keep the order - if (!strData.isEmpty()) { - Map data = new LinkedHashMap<>(); - for (String f : strData.split(",")) { - if (f.contains("{")) { - // it's a featured field - // create data entry with the key and blank value - data.put(f.replaceAll("[{\\[}\\]]", ""), ""); - } else { - // its a fixed reserved value - data.put("Reserved" + reservedIndex, f.replaceAll("[{\\[}\\]]", "")); - reservedIndex++; - } - } - cd.setData(data); - } - commands.put(commandName, cd); - } - return commands; + return getCommandsDefinitionV1(rootNode); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index c4dc800a50d8b..d2b53b3248ff2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -32,28 +32,18 @@ - + String Months passed since filter has been changed. - - - - - - - - - - - - - - - - - - + + + + + String + + Fresh Air Filter State. + @@ -389,4 +379,33 @@ + + String + + Fridge Express Mode + + + + Switch + + Ice Plus Feature + + + + Switch + + Smart Saving + + + + String + + Smart Saving Mode + + + + Switch + + Active Saving + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml index 78f7d9654ea8b..0a9d40e4623b9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml @@ -27,17 +27,14 @@ - - - Show more information about the device. - - + + diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java index bfce923b8f0ca..f2b125da24018 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java @@ -203,7 +203,7 @@ public void testDiscoveryWMThings() { } List devices = service2.listAccountDevices("bridgeTest"); assertEquals(devices.size(), 1); - // service2.getDeviceData(fakeBridgeName, "fakeDeviceId", new WasherDryerCapability()); + // service2.getDeviceData(fakeBridgeName, "fakeDeviceId", new DishWasherCapability()); } catch (Exception e) { logger.error("Error testing facade", e); } From 861b62e025a1e0a1cdbda6b7c417d1159c97816f Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Thu, 16 May 2024 11:18:40 -0300 Subject: [PATCH 017/130] [lgthinq][fix] Bug fixes and DW support Signed-off-by: nemerdaud --- .../internal/LGThinQBindingConstants.java | 33 +- .../internal/LGThinQHandlerFactory.java | 6 + .../discovery/LGThinqDiscoveryService.java | 2 + .../handler/LGThinQAirConditionerHandler.java | 1 + .../handler/LGThinQDishWasherHandler.java | 288 ++---------------- .../handler/LGThinQFridgeHandler.java | 83 ++++- .../handler/LGThinQWasherDryerHandler.java | 5 + .../lgservices/LGThinQACApiClientService.java | 2 + .../LGThinQACApiV1ClientServiceImpl.java | 5 + .../LGThinQACApiV2ClientServiceImpl.java | 5 + .../LGThinQAbstractApiClientService.java | 9 +- .../LGThinQApiClientServiceFactory.java | 7 + .../LGThinQDishWasherApiClientService.java | 34 +++ ...ThinQDishWasherApiV1ClientServiceImpl.java | 72 +++++ ...ThinQDishWasherApiV2ClientServiceImpl.java | 60 ++++ .../LGThinQFridgeApiClientService.java | 9 + .../LGThinQFridgeApiV1ClientServiceImpl.java | 32 +- .../LGThinQFridgeApiV2ClientServiceImpl.java | 37 +++ .../model/AbstractCapabilityFactory.java | 2 +- .../lgservices/model/CapabilityFactory.java | 4 +- .../model/DefaultSnapshotBuilder.java | 2 +- .../lgthinq/lgservices/model/DeviceTypes.java | 5 + .../lgthinq/lgservices/model/ModelUtils.java | 2 + .../model/SnapshotBuilderFactory.java | 4 + .../washers}/CourseDefinition.java | 2 +- .../washers}/CourseFunction.java | 2 +- .../washers}/CourseType.java | 2 +- .../model/devices/commons/washers/Utils.java | 75 +++++ .../AbstractDishWasherCapabilityFactory.java | 167 +++------- .../model/devices/dishwasher/CourseType.java | 39 --- .../dishwasher/DishWasherCapability.java | 180 +---------- .../DishWasherCapabilityFactoryV1.java | 125 +------- .../DishWasherCapabilityFactoryV2.java | 201 +++--------- .../dishwasher/DishWasherSnapshot.java | 174 +---------- .../dishwasher/DishWasherSnapshotBuilder.java | 103 ++----- .../AbstractFridgeCapabilityFactory.java | 51 +++- .../fridge/FridgeCanonicalCapability.java | 28 +- .../fridge/FridgeCanonicalSnapshot.java | 20 ++ .../devices/fridge/FridgeCapability.java | 20 +- .../fridge/FridgeCapabilityFactoryV1.java | 20 +- .../fridge/FridgeCapabilityFactoryV2.java | 33 +- .../AbstractWasherDryerCapabilityFactory.java | 58 +--- .../devices/washerdryer/CourseDefinition.java | 64 ---- .../devices/washerdryer/CourseFunction.java | 63 ---- .../washerdryer/WasherDryerCapability.java | 1 + .../WasherDryerCapabilityFactoryV1.java | 18 +- .../WasherDryerCapabilityFactoryV2.java | 6 + .../WasherDryerSnapshotBuilder.java | 5 +- .../main/resources/OH-INF/thing/channels.xml | 16 +- .../resources/OH-INF/thing/dish-washer.xml | 17 -- 50 files changed, 824 insertions(+), 1375 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/{dishwasher => commons/washers}/CourseDefinition.java (95%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/{dishwasher => commons/washers}/CourseFunction.java (95%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/{washerdryer => commons/washers}/CourseType.java (92%) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseType.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index b447272787399..f47b7a57d6a50 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -39,7 +39,8 @@ public class LGThinQBindingConstants { public static final String PROPERTY_VENDOR_NAME = "LG Thinq"; public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, String.valueOf(DeviceTypes.AIR_CONDITIONER.deviceTypeId())); - public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, "201"); + public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, + String.valueOf(DeviceTypes.WASHERDRYER_MACHINE.deviceTypeId())); public static final String WM_CHANNEL_REMOTE_START_GRP_ID = "remote-start-grp"; public static final String CHANNEL_DASHBOARD_GRP_ID = "dashboard"; public static final String CHANNEL_EXTENDED_INFO_GRP_ID = "extended-information"; @@ -56,9 +57,12 @@ public class LGThinQBindingConstants { public static final ThingTypeUID THING_TYPE_FRIDGE = new ThingTypeUID(BINDING_ID, "" + DeviceTypes.REFRIGERATOR.deviceTypeId()); + + public static final ThingTypeUID THING_TYPE_DISHWASHER = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.DISH_WASHER.deviceTypeId()); public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, THING_TYPE_WASHING_MACHINE, THING_TYPE_WASHING_TOWER, THING_TYPE_DRYER_TOWER, THING_TYPE_DRYER, - THING_TYPE_FRIDGE, THING_TYPE_BRIDGE, THING_TYPE_HEAT_PUMP); + THING_TYPE_FRIDGE, THING_TYPE_BRIDGE, THING_TYPE_HEAT_PUMP, THING_TYPE_DISHWASHER); public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_AIR_CONDITIONER, THING_TYPE_WASHING_MACHINE, THING_TYPE_WASHING_TOWER, THING_TYPE_DRYER, THING_TYPE_DRYER_TOWER, THING_TYPE_HEAT_PUMP); @@ -139,7 +143,9 @@ public class LGThinQBindingConstants { public static final String TEMP_UNIT_FAHRENHEIT_SYMBOL = "°F"; public static final String FR_CHANNEL_ICE_PLUS = "fr-ice-plus"; - public static final String FR_CHANNEL_EXPRESS_MODE = "fr-express-mode"; + public static final String FR_CHANNEL_EXPRESS_FREEZE_MODE = "fr-express-mode"; + public static final String FR_CHANNEL_EXPRESS_COOL_MODE = "fr-express-cool-mode"; + public static final String FR_CHANNEL_VACATION_MODE = "fr-eco-friendly-mode"; public static final String FR_CHANNEL_SMART_SAVING_MODE_V2 = "fr-smart-saving-mode"; public static final String FR_CHANNEL_SMART_SAVING_SWITCH_V1 = "fr-smart-saving-switch"; public static final String FR_CHANNEL_ACTIVE_SAVING = "fr-active-saving"; @@ -161,9 +167,8 @@ public class LGThinQBindingConstants { public static final Map CAP_FR_LABEL_CLOSE_OPEN = Map.of("CLOSE", "Closed", "OPEN", "Open", "IGNORE", "Not Available"); - public static final Map CAP_FR_EXPRESS_MODES = Map.of("@CP_OFF_EN_W", "Express Mode Off", - "@CP_ON_EN_W", "Express Fridge/Freezer On", "@RE_MAIN_SPEED_FREEZE_TERM_W", "Rapid Freeze On"); - + public static final Map CAP_FR_EXPRESS_FREEZE_MODES = Map.of("@CP_OFF_EN_W", "Express Mode Off", + "@CP_ON_EN_W", "Express Freeze On", "@RE_MAIN_SPEED_FREEZE_TERM_W", "Rapid Freeze On"); public static final Map CAP_FR_FRESH_AIR_FILTER_MAP = Map.ofEntries(/* v1 */ entry("1", "Off"), entry("2", "Auto Mode"), entry("3", "Power Mode"), entry("4", "Replace Filter"), /* v2 */ entry("OFF", "Off"), entry("AUTO", "Auto Mode"), entry("POWER", "Power Mode"), @@ -342,4 +347,20 @@ public class LGThinQBindingConstants { public static final String WM_COMMAND_REMOTE_START_V2 = "WMStart"; // ============================================================================== + + // ====================== WASHING MACHINE CONSTANTS ============================= + public static final String DW_SNAPSHOT_WASHER_DRYER_NODE_V2 = "dishwasher"; + public static final Map CAP_DW_DOOR_STATE = Map.of("@CP_OFF_EN_W", "Close", "@CP_ON_EN_W", + "Opened"); + public static final Map CAP_DW_PROCESS_STATE = Map.ofEntries(entry("@DW_STATE_INITIAL_W", "None"), + entry("@DW_STATE_RESERVE_W", "Reserved"), entry("@DW_STATE_RUNNING_W", "Running"), + entry("@DW_STATE_RINSING_W", "Rising"), entry("@DW_STATE_DRYING_W", "Drying"), + entry("@DW_STATE_COMPLETE_W", "Complete"), entry("@DW_STATE_NIGHTDRY_W", "Night Dry"), + entry("@DW_STATE_CANCEL_W", "Cancelled")); + + public static final Map CAP_DW_STATE = Map.ofEntries(entry("@DW_STATE_POWER_OFF_W", "Off"), + entry("@WM_STATE_INITIAL_W", "Initial"), entry("@DW_STATE_RUNNING_W", "Running"), + entry("@DW_STATE_PAUSE_W", "Paused"), entry("@DW_STATE_STANDBY_W", "Stand By"), + entry("@DW_STATE_COMPLETE_W", "Complete"), entry("@DW_STATE_POWER_FAIL_W", "Power Fail")); + // ============================================================================== } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index eda8c14e140be..6de9cc59a1648 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -94,6 +94,10 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } else if (THING_TYPE_FRIDGE.equals(thingTypeUID)) { return new LGThinQFridgeHandler(thing, stateDescriptionProvider, Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); + } else if (THING_TYPE_DISHWASHER.equals(thingTypeUID)) { + return new LGThinQDishWasherHandler(thing, stateDescriptionProvider, + Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), + Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; @@ -112,6 +116,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); } else if (THING_TYPE_FRIDGE.equals(thingTypeUID)) { return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } else if (THING_TYPE_DISHWASHER.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); } return null; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index 2b8a643d31607..e11ee27280b54 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -151,6 +151,8 @@ private ThingTypeUID getThingTypeUID(LGDevice device) throws LGThinqException { return THING_TYPE_DRYER; case REFRIGERATOR: return THING_TYPE_FRIDGE; + case DISH_WASHER: + return THING_TYPE_DISHWASHER; default: throw new LGThinqException(String.format("device type [%s] not supported", device.getDeviceType())); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 898c1fe433e98..d786086332fd9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -316,6 +316,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept lgThinqACApiClientService.turnCoolJetMode(getBridgeId(), getDeviceId(), command == OnOffType.ON ? getCapabilities().getCoolJetModeCommandOn() : getCapabilities().getCoolJetModeCommandOff()); + lgThinqACApiClientService.turnBellOnOff(getBridgeId(), getDeviceId(), "1"); } else { logger.warn("Received command different of OnOffType in CoolJet Mode Channel. Ignoring"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java index 6c53880474cce..e7bd1bad64193 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -14,30 +14,24 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; -import java.time.ZoneId; -import java.time.ZonedDateTime; import java.util.*; -import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; -import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQDishWasherApiClientService; import org.openhab.binding.lgthinq.lgservices.model.*; -import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.*; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerCallback; -import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.thing.link.ItemChannelLinkRegistry; -import org.openhab.core.types.Command; import org.openhab.core.types.StateOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -49,35 +43,20 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinQDishWasherHandler extends LGThinQAbstractDeviceHandler { +public class LGThinQDishWasherHandler extends LGThinQAbstractDeviceHandler { private final LGThinQStateDescriptionProvider stateDescriptionProvider; private final ChannelUID courseChannelUID; - private final ChannelUID remoteStartStopChannelUID; private final ChannelUID remainTimeChannelUID; - private final ChannelUID delayTimeChannelUID; - private final ChannelUID spinChannelUID; - private final ChannelUID rinseChannelUID; private final ChannelUID stateChannelUID; private final ChannelUID processStateChannelUID; - private final ChannelUID childLockChannelUID; - private final ChannelUID dryLevelChannelUID; - private final ChannelUID temperatureChannelUID; private final ChannelUID doorLockChannelUID; - private final ChannelUID standByModeChannelUID; - private final ChannelUID remoteStartFlagChannelUID; - private final ChannelUID remoteStartCourseChannelUID; - public final ChannelGroupUID channelGroupRemoteStartUID; public final ChannelGroupUID channelGroupDashboardUID; - private final List remoteStartEnabledChannels = new CopyOnWriteArrayList<>(); - - private final Map> cachedBitKeyDefinitions = new HashMap<>(); - private final Logger logger = LoggerFactory.getLogger(LGThinQDishWasherHandler.class); @NonNullByDefault - private final LGThinQWMApiClientService lgThinqWMApiClientService; + private final LGThinQDishWasherApiClientService lgThinqDishWasherApiClientService; public LGThinQDishWasherHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, ThinqChannelTypeProvider channelTypeProvider, ThinqChannelGroupTypeProvider channelGroupTypeProvider, @@ -86,37 +65,17 @@ public LGThinQDishWasherHandler(Thing thing, LGThinQStateDescriptionProvider sta this.thinqChannelGroupProvider = channelGroupTypeProvider; this.thinqChannelProvider = channelTypeProvider; this.stateDescriptionProvider = stateDescriptionProvider; - lgThinqWMApiClientService = LGThinQApiClientServiceFactory.newWMApiClientService(lgPlatformType, + lgThinqDishWasherApiClientService = LGThinQApiClientServiceFactory.newDishWasherApiClientService(lgPlatformType, httpClientFactory); - channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_GRP_ID); channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); courseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_COURSE_ID); - dryLevelChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_DRY_LEVEL_ID); stateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STATE_ID); processStateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_PROCESS_STATE_ID); remainTimeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMAIN_TIME_ID); - delayTimeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_DELAY_TIME_ID); - temperatureChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_TEMP_LEVEL_ID); doorLockChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_DOOR_LOCK_ID); - childLockChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_CHILD_LOCK_ID); - rinseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_RINSE_ID); - spinChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_SPIN_ID); - standByModeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STAND_BY_ID); - remoteStartFlagChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMOTE_START_ID); - remoteStartStopChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_START_START_STOP); - remoteStartCourseChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_COURSE); } - @Override - protected void initializeThing(@Nullable ThingStatus bridgeStatus) { - super.initializeThing(bridgeStatus); - ThingBuilder builder = editThing() - .withoutChannels(this.getThing().getChannelsOfGroup(channelGroupRemoteStartUID.getId())); - updateThing(builder.build()); - remoteStartEnabledChannels.clear(); - } - - private void loadOptionsCourse(WasherDryerCapability cap, ChannelUID courseChannel) { + private void loadOptionsCourse(DishWasherCapability cap, ChannelUID courseChannel) { List optionsCourses = new ArrayList<>(); cap.getCourses().forEach((k, v) -> optionsCourses.add(new StateOption(k, emptyIfNull(v.getCourseName())))); stateDescriptionProvider.setStateOptions(courseChannel, optionsCourses); @@ -124,54 +83,29 @@ private void loadOptionsCourse(WasherDryerCapability cap, ChannelUID courseChann @Override public void updateChannelDynStateDescription() throws LGThinqApiException { - WasherDryerCapability wmCap = getCapabilities(); + DishWasherCapability dwCap = getCapabilities(); List options = new ArrayList<>(); - wmCap.getStateFeat().getValuesMapping() - .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_STATE, v)))); + dwCap.getStateFeat().getValuesMapping() + .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DW_STATE, v)))); stateDescriptionProvider.setStateOptions(stateChannelUID, options); - loadOptionsCourse(wmCap, courseChannelUID); - - List optionsTemp = new ArrayList<>(); - wmCap.getTemperatureFeat().getValuesMapping() - .forEach((k, v) -> optionsTemp.add(new StateOption(k, keyIfValueNotFound(CAP_WM_TEMPERATURE, v)))); - stateDescriptionProvider.setStateOptions(temperatureChannelUID, optionsTemp); + loadOptionsCourse(dwCap, courseChannelUID); List optionsDoor = new ArrayList<>(); - optionsDoor.add(new StateOption("0", "Unlocked")); - optionsDoor.add(new StateOption("1", "Locked")); + dwCap.getDoorStateFeat().getValuesMapping() + .forEach((k, v) -> optionsDoor.add(new StateOption(k, keyIfValueNotFound(CAP_DW_DOOR_STATE, v)))); stateDescriptionProvider.setStateOptions(doorLockChannelUID, optionsDoor); - List optionsSpin = new ArrayList<>(); - wmCap.getSpinFeat().getValuesMapping() - .forEach((k, v) -> optionsSpin.add(new StateOption(k, keyIfValueNotFound(CAP_WM_SPIN, v)))); - stateDescriptionProvider.setStateOptions(spinChannelUID, optionsSpin); - - List optionsRinse = new ArrayList<>(); - wmCap.getRinseFeat().getValuesMapping() - .forEach((k, v) -> optionsRinse.add(new StateOption(k, keyIfValueNotFound(CAP_WM_RINSE, v)))); - stateDescriptionProvider.setStateOptions(rinseChannelUID, optionsRinse); - List optionsPre = new ArrayList<>(); - wmCap.getProcessState().getValuesMapping() - .forEach((k, v) -> optionsPre.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_PROCESS_STATE, v)))); + dwCap.getProcessState().getValuesMapping() + .forEach((k, v) -> optionsPre.add(new StateOption(k, keyIfValueNotFound(CAP_DW_PROCESS_STATE, v)))); stateDescriptionProvider.setStateOptions(processStateChannelUID, optionsPre); - - List optionsChildLock = new ArrayList<>(); - optionsChildLock.add(new StateOption("CHILDLOCK_OFF", "Unlocked")); - optionsChildLock.add(new StateOption("CHILDLOCK_ON", "Locked")); - stateDescriptionProvider.setStateOptions(childLockChannelUID, optionsChildLock); - - List optionsDryLevel = new ArrayList<>(); - wmCap.getDryLevel().getValuesMapping() - .forEach((k, v) -> optionsDryLevel.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); - stateDescriptionProvider.setStateOptions(dryLevelChannelUID, optionsDryLevel); } @Override - public LGThinQApiClientService getLgThinQAPIClientService() { - return lgThinqWMApiClientService; + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqDishWasherApiClientService; } @Override @@ -179,107 +113,18 @@ protected Logger getLogger() { return logger; } - private ZonedDateTime getZonedDateTime(String minutesAndSeconds) { - if (minutesAndSeconds.length() != 5) { - logger.error("Washer/Disher remain/delay time is not in standard MM:SS. Value received: {}. Reset to 00:00", - minutesAndSeconds); - minutesAndSeconds = "00:00"; - } - String min = minutesAndSeconds.substring(0, 2); - String sec = minutesAndSeconds.substring(3); - - return ZonedDateTime.of(1970, 1, 1, 0, Integer.parseInt(min), Integer.parseInt(sec), 0, ZoneId.systemDefault()); - } - @Override - protected void updateDeviceChannels(WasherDryerSnapshot shot) { - WasherDryerSnapshot lastShot = getLastShot(); + protected void updateDeviceChannels(DishWasherSnapshot shot) { + DishWasherSnapshot lastShot = getLastShot(); updateState("dashboard#" + CHANNEL_POWER_ID, (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); updateState(stateChannelUID, new StringType(shot.getState())); updateState(processStateChannelUID, new StringType(shot.getProcessState())); - updateState(dryLevelChannelUID, new StringType(shot.getDryLevel())); - updateState(childLockChannelUID, new StringType(shot.getChildLock())); updateState(courseChannelUID, new StringType(shot.getCourse())); - updateState(temperatureChannelUID, new StringType(shot.getTemperatureLevel())); updateState(doorLockChannelUID, new StringType(shot.getDoorLock())); updateState(remainTimeChannelUID, new StringType(shot.getRemainingTime())); - updateState(delayTimeChannelUID, new StringType(shot.getReserveTime())); - updateState(standByModeChannelUID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); - updateState(remoteStartFlagChannelUID, shot.isRemoteStartEnabled() ? OnOffType.ON : OnOffType.OFF); - updateState(spinChannelUID, new StringType(shot.getSpin())); - updateState(rinseChannelUID, new StringType(shot.getRinse())); - Channel rsStartStopChannel = getThing().getChannel(remoteStartStopChannelUID); final List dynChannels = new ArrayList<>(); // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. - if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { - ThingHandlerCallback callback = getCallback(); - if (rsStartStopChannel == null && callback != null) { - // === creating channel LaunchRemote - dynChannels - .add(createDynChannel(WM_CHANNEL_REMOTE_START_START_STOP, remoteStartStopChannelUID, "Switch")); - dynChannels.add(createDynChannel(WM_CHANNEL_REMOTE_COURSE, remoteStartCourseChannelUID, "String")); - // Just enabled remote start. Then is Off - updateState(remoteStartStopChannelUID, OnOffType.OFF); - // === creating selectable channels for the Course (if any) - try { - WasherDryerCapability cap = getCapabilities(); - // TODO - V1 - App will always get the default course, and V2 ? - loadOptionsCourse(cap, remoteStartCourseChannelUID); - updateState(remoteStartCourseChannelUID, new StringType(cap.getDefaultCourseId())); - - CourseDefinition courseDef = cap.getCourses().get(cap.getDefaultCourseId()); - if (WM_COURSE_NOT_SELECTED_VALUE.equals(shot.getSmartCourse()) && courseDef != null) { - // only create selectable channels if the course is not a smart course. Smart courses have - // already predefined - // the functions values - for (CourseFunction f : courseDef.getFunctions()) { - if (!f.isSelectable()) { - // only for selectable features - continue; - } - // handle well know dynamic fields - FeatureDefinition fd = cap.getFeatureDefinition(f.getValue()); - ChannelUID targetChannel = null; - ChannelUID refChannel = null; - if (!FeatureDefinition.NULL_DEFINITION.equals(fd)) { - targetChannel = new ChannelUID(channelGroupRemoteStartUID, fd.getChannelId()); - refChannel = new ChannelUID(channelGroupDashboardUID, fd.getRefChannelId()); - dynChannels.add(createDynChannel(fd.getChannelId(), targetChannel, - translateFeatureToItemType(fd.getDataType()))); - if (CAP_WM_DICT_V2.containsKey(f.getValue())) { - // if the function has translation dictionary (I hope so), then the values in - // the selectable channel will be translated to something more readable - List options = new ArrayList<>(); - for (String v : f.getSelectableValues()) { - Map values = CAP_WM_DICT_V2.get(f.getValue()); - if (values != null) { - // Canonical Value is the KEY (@...) that represents a constant in the - // definition - // that can be translated to a human description - String canonicalValue = Objects - .requireNonNullElse(fd.getValuesMapping().get(v), v); - options.add(new StateOption(v, keyIfValueNotFound(values, canonicalValue))); - stateDescriptionProvider.setStateOptions(targetChannel, options); - } - } - } - // update state with the default referenced channel - updateState(targetChannel, new StringType(getItemLinkedValue(refChannel))); - } - } - } - } catch (LGThinqApiException e) { - throw new RuntimeException(e); - } - remoteStartEnabledChannels.addAll(dynChannels); - - } - } else if (remoteStartEnabledChannels.size() > 0) { - ThingBuilder builder = editThing().withoutChannels(remoteStartEnabledChannels); - updateThing(builder.build()); - remoteStartEnabledChannels.clear(); - } } @Override @@ -294,101 +139,9 @@ protected DeviceTypes getDeviceType() { } } - private Map getRemoteStartData() throws LGThinqApiException { - WasherDryerSnapshot lastShot = getLastShot(); - if (lastShot.getRawData().isEmpty()) { - return lastShot.getRawData(); - } - String selectedCourse = getItemLinkedValue(remoteStartCourseChannelUID); - if (selectedCourse == null) { - logger.error("Remote Start Channel must be linked to proceed with remote start."); - return Collections.emptyMap(); - } - WasherDryerCapability cap = getCapabilities(); - Map rawData = lastShot.getRawData(); - Map data = new HashMap<>(); - CommandDefinition cmd = cap.getCommandsDefinition().get(cap.getCommandRemoteStart()); - if (cmd == null) { - logger.error("Command for Remote Start not found in the Washer descriptor. It's most likely a bug"); - return Collections.emptyMap(); - } - Map cmdData = cmd.getData(); - // 1st - copy snapshot data to command - cmdData.forEach((k, v) -> { - data.put(k, rawData.getOrDefault(k, v)); - }); - // 2nd - replace remote start data with selected course values - CourseDefinition selCourseDef = cap.getCourses().get(selectedCourse); - if (selCourseDef != null) { - selCourseDef.getFunctions().forEach(f -> { - data.put(f.getValue(), f.getDefaultValue()); - }); - } - String smartCourse = lastShot.getSmartCourse(); - data.put(cap.getDefaultCourseFieldName(), selectedCourse); - data.put(cap.getDefaultSmartCourseFeatName(), smartCourse); - CourseType courseType = cap.getCourses().get("NOT_SELECTED".equals(smartCourse) ? selectedCourse : smartCourse) - .getCourseType(); - data.put("courseType", courseType.getValue()); - // 3rd - replace custom selectable features with channel's ones. - for (Channel c : remoteStartEnabledChannels) { - String value = Objects.requireNonNullElse(getItemLinkedValue(c.getUID()), ""); - String simpleChannelUID = getSimpleChannelUID(c.getUID().getId()); - switch (simpleChannelUID) { - case WM_CHANNEL_REMOTE_START_RINSE: - data.put(cap.getRinseFeat().getName(), value); - break; - case WM_CHANNEL_REMOTE_START_TEMP: - data.put(cap.getTemperatureFeat().getName(), value); - break; - case WM_CHANNEL_REMOTE_START_SPIN: - data.put(cap.getSpinFeat().getName(), value); - break; - default: - logger.warn("channel [{}] not mapped for this binding. It most likely a bug.", simpleChannelUID); - } - } - - return data; - } - @Override protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { - WasherDryerSnapshot lastShot = getLastShot(); - Command command = params.command; - String simpleChannelUID; - simpleChannelUID = getSimpleChannelUID(params.channelUID); - switch (simpleChannelUID) { - case WM_CHANNEL_REMOTE_START_START_STOP: { - if (command instanceof OnOffType) { - if (OnOffType.ON.equals(command)) { - if (!lastShot.isStandBy()) { - lgThinqWMApiClientService.remoteStart(getBridgeId(), getCapabilities(), getDeviceId(), - getRemoteStartData()); - } else { - logger.warn( - "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); - } - } else { - logger.warn("Command Remote Start OFF not implemented yet"); - } - } else { - logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); - } - break; - } - case WM_CHANNEL_STAND_BY_ID: { - if (command instanceof OnOffType) { - lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); - } else { - logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); - } - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); - } - } + logger.error("Command {} to the channel {} not supported. Ignored.", params.command, params.channelUID); } @Override @@ -420,7 +173,6 @@ public void onDeviceDisconnected() { updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index 2425168ba8847..3bf71c21c7624 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -34,10 +34,7 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; import org.openhab.core.io.net.http.HttpClientFactory; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.OpenClosedType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.library.types.StringType; +import org.openhab.core.library.types.*; import org.openhab.core.library.unit.ImperialUnits; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.thing.ChannelGroupUID; @@ -68,7 +65,9 @@ public class LGThinQFridgeHandler extends LGThinQAbstractDeviceHandler unTemp = getTemperatureUnit(getLastShot()); if (SIUnits.CELSIUS.equals(unTemp)) { @@ -281,7 +297,7 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { } loadChannelStateOption(cap.getActiveSavingMap(), activeSavingChannelUID); - loadChannelStateOption(cap.getExpressModeMap(), expressModeChannelUID, CAP_FR_EXPRESS_MODES); + loadChannelStateOption(cap.getExpressFreezeModeMap(), expressFreezeModeChannelUID); loadChannelStateOption(cap.getActiveSavingMap(), activeSavingChannelUID); @@ -350,6 +366,47 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept } break; } + case FR_CHANNEL_ICE_PLUS: { + if (command instanceof OnOffType) { + lgThinqFridgeApiClientService.setIcePlus(getBridgeId(), getDeviceId(), getCapabilities(), + OnOffType.ON.equals(command), cmdSnap); + } else { + logger.warn("Received command different of OnOff in IcePlus Channel. It's mostly like a bug"); + } + break; + } + case FR_CHANNEL_EXPRESS_FREEZE_MODE: { + String targetExpressMode; + if (command instanceof StringType) { + targetExpressMode = ((StringType) command).toString(); + } else { + logger.warn("Received command different of String in ExpressMode Channel. It's mostly like a bug"); + break; + } + + lgThinqFridgeApiClientService.setExpressMode(getBridgeId(), getDeviceId(), targetExpressMode); + break; + } + case FR_CHANNEL_EXPRESS_COOL_MODE: { + if (command instanceof OnOffType) { + lgThinqFridgeApiClientService.setExpressCoolMode(getBridgeId(), getDeviceId(), + OnOffType.ON.equals(command)); + } else { + logger.warn( + "Received command different of OnOffType in ExpressCoolMode Channel. It's mostly like a bug"); + } + break; + } + case FR_CHANNEL_VACATION_MODE: { + if (command instanceof OnOffType) { + lgThinqFridgeApiClientService.setEcoFriendlyMode(getBridgeId(), getDeviceId(), + OnOffType.ON.equals(command)); + } else { + logger.warn( + "Received command different of OnOffType in VacationMode Channel. It's most likely a bug"); + } + break; + } default: { logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index d3ee90e7beb58..3bf2893d8fe41 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -31,6 +31,9 @@ import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseFunction; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseType; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.*; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.OnOffType; @@ -291,6 +294,8 @@ protected DeviceTypes getDeviceType() { return DeviceTypes.WASHERDRYER_MACHINE; } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { return DeviceTypes.WASHING_TOWER; + } else if (THING_TYPE_DRYER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHING_TOWER; } else { throw new IllegalArgumentException( "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java index 12ce1cc699821..9d7c70d190735 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java @@ -42,6 +42,8 @@ void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp new void turnEnergySavingMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + void turnBellOnOff(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + ExtendedDeviceInfo getExtendedDeviceInfo(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java index 04c3affb53dd7..14c77cae72a2d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java @@ -139,6 +139,11 @@ public void turnEnergySavingMode(String bridgeName, String deviceId, String mode turnGenericMode(bridgeName, deviceId, "PowerSave", modeOnOff); } + @Override + public void turnBellOnOff(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "SpkVolume", modeOnOff); + } + protected void turnGenericMode(String bridgeName, String deviceId, String modeName, String modeOnOff) throws LGThinqApiException { try { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java index ef6b304ef6de5..d2237582bca3b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -91,6 +91,11 @@ protected void turnGenericMode(String bridgeName, String deviceId, String modeNa } } + @Override + public void turnBellOnOff(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { + turnGenericMode(bridgeName, deviceId, "airState.bellSound.appControl", modeOnOff); + } + @Override public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { try { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index 9e92cdec5aefb..657b4af977ff0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -162,13 +162,13 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), resp.getJsonResponse()); + resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); return Collections.emptyMap(); } try { if (resp.getStatusCode() == 400) { logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), resp.getJsonResponse()); + resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); return Collections.emptyMap(); } respMap = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { @@ -430,6 +430,11 @@ public String startMonitor(String bridgeName, String deviceId) String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); RestResult resp = RestUtils.postCall(httpClient, builder.build().toURL().toString(), headers, jsonData); + Map respMap = handleGenericErrorResult(resp); + if (respMap.isEmpty()) { + logger.debug( + "Unexpected StartMonitor json null result. Possible causes: 1) you are monitoring the device in LG App at same time, 2) temporary problems in the server. Try again later"); + } return Objects.requireNonNull((String) handleGenericErrorResult(resp).get("workId"), "Unexpected StartMonitor json result. Node 'workId' not present"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java index c7beb32880305..16606fc8c1ae1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java @@ -61,6 +61,13 @@ public static LGThinQWMApiClientService newWMApiClientService(String lgPlatformT : new LGThinQWMApiV2ClientServiceImpl(httpClientFactory.getCommonHttpClient()); } + public static LGThinQDishWasherApiClientService newDishWasherApiClientService(String lgPlatformType, + HttpClientFactory httpClientFactory) { + return lgPlatformType.equals(PLATFORM_TYPE_V1) + ? new LGThinQDishWasherApiV1ClientServiceImpl(httpClientFactory.getCommonHttpClient()) + : new LGThinQDishWasherApiV2ClientServiceImpl(httpClientFactory.getCommonHttpClient()); + } + @NonNullByDefault public static final class LGThinQGeneralApiClientService extends LGThinQAbstractApiClientService { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java new file mode 100644 index 0000000000000..73e869ee125ac --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java @@ -0,0 +1,34 @@ +/** + * 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.lgthinq.lgservices; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; + +/** + * The {@link LGThinQDishWasherApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGThinQDishWasherApiClientService + extends LGThinQApiClientService { + void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) + throws LGThinqApiException; + + void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java new file mode 100644 index 0000000000000..7d3b599fd58e3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java @@ -0,0 +1,72 @@ +/** + * 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.lgthinq.lgservices; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQDishWasherApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQDishWasherApiV1ClientServiceImpl + extends LGThinQAbstractApiV1ClientService + implements LGThinQDishWasherApiClientService { + private final Logger logger = LoggerFactory.getLogger(LGThinQDishWasherApiV1ClientServiceImpl.class); + + protected LGThinQDishWasherApiV1ClientServiceImpl(HttpClient httpClient) { + super(DishWasherCapability.class, DishWasherSnapshot.class, httpClient); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { + // Nothing to do for V1 thinq + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not Supported for this device"); + } + + @Override + @Nullable + public DishWasherSnapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull CapabilityDefinition capDef) throws LGThinqApiException { + throw new UnsupportedOperationException("Method not supported in V1 API device."); + } + + @Override + public void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..45931b6ff8716 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java @@ -0,0 +1,60 @@ +/** + * 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.lgthinq.lgservices; + +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; + +/** + * The {@link LGThinQDishWasherApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQDishWasherApiV2ClientServiceImpl + extends LGThinQAbstractApiV2ClientService + implements LGThinQDishWasherApiClientService { + + protected LGThinQDishWasherApiV2ClientServiceImpl(HttpClient httpClient) { + super(DishWasherCapability.class, DishWasherSnapshot.class, httpClient); + } + + @Override + protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { + // TODO - Analise what to do here + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Unsupported for this device"); + } + + @Override + public void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet"); + } + + @Override + public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet"); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java index a14ea0863f397..f8dbfdd17ea6f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java @@ -35,4 +35,13 @@ void setFridgeTemperature(String bridgeId, String deviceId, FridgeCapability fri void setFreezerTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) throws LGThinqApiException; + + void setExpressMode(String bridgeId, String deviceId, String expressModeIndex) throws LGThinqApiException; + + void setExpressCoolMode(String bridgeId, String deviceId, boolean trueOnFalseOff) throws LGThinqApiException; + + void setEcoFriendlyMode(String bridgeId, String deviceId, boolean trueOnFalseOff) throws LGThinqApiException; + + void setIcePlus(String bridgeId, String deviceId, FridgeCapability fridgeCapability, boolean trueOnFalseOff, + Map snapCmdData) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java index 7597a0948f83b..475e25ea85669 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java @@ -69,7 +69,7 @@ public void setFridgeTemperature(String bridgeId, String deviceId, FridgeCapabil throws LGThinqApiException { assert snapCmdData != null; snapCmdData.put("TempRefrigerator", targetTemperatureIndex); - setTemperature(bridgeId, deviceId, fridgeCapability, snapCmdData); + setControlCommand(bridgeId, deviceId, fridgeCapability, snapCmdData); } @Override @@ -78,10 +78,36 @@ public void setFreezerTemperature(String bridgeId, String deviceId, FridgeCapabi throws LGThinqApiException { assert snapCmdData != null; snapCmdData.put("TempFreezer", targetTemperatureIndex); - setTemperature(bridgeId, deviceId, fridgeCapability, snapCmdData); + setControlCommand(bridgeId, deviceId, fridgeCapability, snapCmdData); } - private void setTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, + @Override + public void setExpressMode(String bridgeId, String deviceId, String expressModeIndex) throws LGThinqApiException { + throw new UnsupportedOperationException("V1 Fridge doesn't support ExpressMode feature. It mostly like a bug"); + } + + @Override + public void setExpressCoolMode(String bridgeId, String deviceId, boolean trueOnFalseOff) + throws LGThinqApiException { + throw new UnsupportedOperationException( + "V1 Fridge doesn't support ExpressCoolMode feature. It mostly like a bug"); + } + + @Override + public void setEcoFriendlyMode(String bridgeId, String deviceId, boolean trueOnFalseOff) + throws LGThinqApiException { + throw new UnsupportedOperationException( + "V1 Fridge doesn't support ExpressCoolMode feature. It mostly like a bug"); + } + + @Override + public void setIcePlus(String bridgeId, String deviceId, FridgeCapability fridgeCapability, boolean trueOnFalseOff, + Map snapCmdData) throws LGThinqApiException { + snapCmdData.put("IcePlus", trueOnFalseOff ? 1 : 0); + setControlCommand(bridgeId, deviceId, fridgeCapability, snapCmdData); + } + + private void setControlCommand(String bridgeId, String deviceId, FridgeCapability fridgeCapability, @Nullable Map snapCmdData) throws LGThinqApiException { try { CommandDefinition cmdSetControlDef = fridgeCapability.getCommandsDefinition() diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java index d4d74215dc627..92ab47033a6db 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java @@ -66,6 +66,43 @@ public void setFreezerTemperature(String bridgeId, String deviceId, FridgeCapabi setTemperature("freezerTemp", bridgeId, deviceId, targetTemperatureIndex, tempUnit); } + @Override + public void setExpressMode(String bridgeId, String deviceId, String expressMode) throws LGThinqApiException { + sendSimpleDataSetListCommand(bridgeId, deviceId, "expressMode", expressMode); + } + + private void sendSimpleDataSetListCommand(String bridgeId, String deviceId, String feature, String value) + throws LGThinqApiException { + ObjectNode dataSetList = JsonNodeFactory.instance.objectNode(); + ObjectNode nodeData = dataSetList.putObject("dataSetList").putObject("refState"); + nodeData.put(feature, value); + try { + RestResult result = sendCommand(bridgeId, deviceId, "control-sync", "basicCtrl", "Set", null, null, + dataSetList); + handleGenericErrorResult(result); + } catch (Exception e) { + throw new LGThinqApiException("Error sending command", e); + } + } + + @Override + public void setExpressCoolMode(String bridgeId, String deviceId, boolean trueOnFalseOff) + throws LGThinqApiException { + sendSimpleDataSetListCommand(bridgeId, deviceId, "expressFridge", trueOnFalseOff ? "ON" : "OFF"); + } + + @Override + public void setEcoFriendlyMode(String bridgeId, String deviceId, boolean trueOnFalseOff) + throws LGThinqApiException { + sendSimpleDataSetListCommand(bridgeId, deviceId, "ecoFriendly", trueOnFalseOff ? "ON" : "OFF"); + } + + @Override + public void setIcePlus(String bridgeId, String deviceId, FridgeCapability fridgeCapability, boolean trueOnFalseOff, + Map snapCmdData) throws LGThinqApiException { + throw new UnsupportedOperationException("V2 Fridge doesn't support IcePlus feature. It mostly like a bug"); + } + private void setTemperature(String tempFeature, String bridgeId, String deviceId, Integer targetTemperature, String tempUnit) throws LGThinqApiException { ObjectNode dataSetList = JsonNodeFactory.instance.objectNode(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java index 744ccce6abb42..f71de3eaa2be6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java @@ -94,7 +94,7 @@ protected String getMonitoringNodeName() { protected abstract List getSupportedAPIVersions(); /** - * Return the feature definition, i.e, the defition of the device attributes that can be mapped to Channels. + * Return the feature definition, i.e, the definition of the device attributes that can be mapped to Channels. * The targetChannelId is needed if you intend to get the destination channelId for that feature, typically for * dynamic channels. * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java index 2a2e9898e5329..f605beb37ae5d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java @@ -21,6 +21,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapabilityFactoryV1; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapabilityFactoryV2; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapabilityFactoryV2; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapabilityFactoryV1; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapabilityFactoryV2; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapabilityFactoryV1; @@ -43,7 +44,8 @@ public class CapabilityFactory { private CapabilityFactory() { List> factories = Arrays.asList(new ACCapabilityFactoryV1(), new ACCapabilityFactoryV2(), new FridgeCapabilityFactoryV1(), new FridgeCapabilityFactoryV2(), - new WasherDryerCapabilityFactoryV1(), new WasherDryerCapabilityFactoryV2()); + new WasherDryerCapabilityFactoryV1(), new WasherDryerCapabilityFactoryV2(), + new WasherDryerCapabilityFactoryV1(), new DishWasherCapabilityFactoryV2()); factories.forEach(f -> { f.getSupportedDeviceTypes().forEach(d -> { Map> versionMap = capabilityDeviceFactories.get(d); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java index 9b0241586aa02..9a6e51c65f553 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java @@ -231,7 +231,7 @@ private Map getBitKey(String key, final Map rootMap) { throw new IllegalStateException( "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } + case DISH_WASHER: + return LGAPIVerion.V2_0; default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java index ecdcfa7aede3d..6db7f5e3a8efc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java @@ -18,6 +18,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACSnapshotBuilder; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshotBuilder; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeSnapshotBuilder; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; @@ -53,6 +55,8 @@ public SnapshotBuilder getBuilder(Class getGenericCourseDefinitions(JsonNode courseNode, CourseType type, + String notSelectedCourseKey) { + Map coursesDef = new HashMap<>(); + courseNode.fields().forEachRemaining(e -> { + CourseDefinition cd = new CourseDefinition(); + JsonNode thisCourseNode = e.getValue(); + cd.setCourseName(thisCourseNode.path("_comment").textValue()); + if (CourseType.SMART_COURSE.equals(type)) { + cd.setBaseCourseName(thisCourseNode.path("Course").textValue()); + } + cd.setCourseType(type); + if (thisCourseNode.path("function").isArray()) { + // just to be safe here + ArrayNode functions = (ArrayNode) thisCourseNode.path("function"); + List functionList = cd.getFunctions(); + for (JsonNode fNode : functions) { + // map all course functions here + CourseFunction f = new CourseFunction(); + f.setValue(fNode.path("value").textValue()); + f.setDefaultValue(fNode.path("default").textValue()); + JsonNode selectableNode = fNode.path("selectable"); + // only Courses (not SmartCourses or DownloadedCourses) can have selectable functions + f.setSelectable( + !selectableNode.isMissingNode() && selectableNode.isArray() && (type == CourseType.COURSE)); + if (f.isSelectable()) { + List selectableValues = f.getSelectableValues(); + // map values acceptable for this function + for (JsonNode v : (ArrayNode) selectableNode) { + if (v.isValueNode()) { + selectableValues.add(v.textValue()); + } + } + f.setSelectableValues(selectableValues); + } + functionList.add(f); + } + cd.setFunctions(functionList); + } + coursesDef.put(e.getKey(), cd); + }); + CourseDefinition cdNotSelected = new CourseDefinition(); + cdNotSelected.setCourseType(type); + cdNotSelected.setCourseName("Not Selected"); + coursesDef.put(notSelectedCourseKey, cdNotSelected); + return coursesDef; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java index 3a58b4d59f34d..c3f7612601331 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java @@ -12,22 +12,23 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.function.BiConsumer; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseType; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; /** * The {@link AbstractDishWasherCapabilityFactory} @@ -42,158 +43,78 @@ public abstract class AbstractDishWasherCapabilityFactory extends AbstractCapabi protected abstract String getProcessStateNodeName(); - protected abstract String getPreStateFeatureNodeName(); - - // --- Selectable features ----- - protected abstract String getRinseFeatureNodeName(); - - protected abstract String getTemperatureFeatureNodeName(); - - protected abstract String getSpinFeatureNodeName(); - - // ------------------------------ - protected abstract String getSoilWashFeatureNodeName(); - protected abstract String getDoorLockFeatureNodeName(); - protected abstract MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode); + protected abstract String getConvertingRulesNodeName(); - protected abstract String getCommandRemoteStartNodeName(); + protected abstract String getControlConvertingRulesNodeName(); - protected abstract String getCommandStopNodeName(); - - protected abstract String getCommandWakeUpNodeName(); - - protected abstract String getDefaultCourseIdNodeName(); + protected abstract MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode); @Override public DishWasherCapability create(JsonNode rootNode) throws LGThinqException { - DishWasherCapability wdCap = super.create(rootNode); - JsonNode coursesNode = rootNode.path(getCourseNodeName(rootNode)); - JsonNode smartCoursesNode = rootNode.path(getSmartCourseNodeName(rootNode)); + DishWasherCapability dwCap = super.create(rootNode); + JsonNode coursesNode = rootNode.path(getCourseNodeName()); + JsonNode smartCoursesNode = rootNode.path(getSmartCourseNodeName()); if (coursesNode.isMissingNode()) { throw new LGThinqException("Course node not present in Capability Json Descriptor"); } - Map allCourses = new HashMap<>(getCourseDefinitions(coursesNode)); - allCourses.putAll(getSmartCourseDefinitions(smartCoursesNode)); - // TODO - Put Downloaded Course - wdCap.setCourses(allCourses); + Map courses = new HashMap<>(getCourseDefinitions(coursesNode)); + Map smartCourses = new HashMap<>(getSmartCourseDefinitions(smartCoursesNode)); + Map convertedAllCourses = new HashMap<>(); + // change the Key to the reverse MapCourses coming from LG API + BiConsumer, JsonNode> convertCoursesRules = (courseMap, node) -> { + node.fields().forEachRemaining(e -> { + CourseDefinition df = courseMap.get(e.getKey()); + if (df != null) { + convertedAllCourses.put(e.getValue().asText(), df); + } + }); + }; + JsonNode controlConvertingRules = rootNode.path(getConvertingRulesNodeName()).path(getCourseNodeName()) + .path(getControlConvertingRulesNodeName()); + if (!controlConvertingRules.isMissingNode()) { + convertCoursesRules.accept(courses, controlConvertingRules); + } + controlConvertingRules = rootNode.path(getConvertingRulesNodeName()).path(getSmartCourseNodeName()) + .path(getControlConvertingRulesNodeName()); + if (!controlConvertingRules.isMissingNode()) { + convertCoursesRules.accept(smartCourses, controlConvertingRules); + } + + dwCap.setCourses(convertedAllCourses); JsonNode monitorValueNode = rootNode.path(getMonitorValueNodeName()); if (monitorValueNode.isMissingNode()) { throw new LGThinqException("MonitoringValue node not found in the V2 WashingDryer cap definition."); } // mapping possible states - wdCap.setState(newFeatureDefinition(getStateFeatureNodeName(), monitorValueNode)); - wdCap.setProcessState(newFeatureDefinition(getProcessStateNodeName(), monitorValueNode)); - // --- Selectable features ----- - wdCap.setRinseFeat(newFeatureDefinition(getRinseFeatureNodeName(), monitorValueNode, - WM_CHANNEL_REMOTE_START_RINSE, WM_CHANNEL_RINSE_ID)); - wdCap.setTemperatureFeat(newFeatureDefinition(getTemperatureFeatureNodeName(), monitorValueNode, - WM_CHANNEL_REMOTE_START_TEMP, WM_CHANNEL_TEMP_LEVEL_ID)); - wdCap.setSpinFeat(newFeatureDefinition(getSpinFeatureNodeName(), monitorValueNode, WM_CHANNEL_REMOTE_START_SPIN, - WM_CHANNEL_SPIN_ID)); - // ---------------------------- - wdCap.setDryLevel(newFeatureDefinition(getDryLevelNodeName(), monitorValueNode)); - wdCap.setSoilWash(newFeatureDefinition(getSoilWashFeatureNodeName(), monitorValueNode)); - wdCap.setCommandsDefinition(getCommandsDefinition(rootNode)); - if (monitorValueNode.get(getDoorLockFeatureNodeName()) != null) { - wdCap.setHasDoorLook(true); - } - wdCap.setDefaultCourseFieldName(getConfigCourseType(rootNode)); - wdCap.setDefaultSmartCourseFeatName(getConfigSmartCourseType(rootNode)); - wdCap.setCommandStop(getCommandStopNodeName()); - wdCap.setCommandRemoteStart(getCommandRemoteStartNodeName()); - wdCap.setCommandWakeUp(getCommandWakeUpNodeName()); - // custom feature values map. - wdCap.setFeatureDefinitionMap( - Map.of(getTemperatureFeatureNodeName(), new DishWasherCapability.TemperatureFeatureFunction(), - getRinseFeatureNodeName(), new DishWasherCapability.RinseFeatureFunction(), - getSpinFeatureNodeName(), new DishWasherCapability.SpinFeatureFunction())); - wdCap.setMonitoringDataFormat(getMonitorDataFormat(rootNode)); - wdCap.setDefaultCourseId(rootNode.path("Config").path(getDefaultCourseIdNodeName()).asText()); - return wdCap; - } - - protected Map getGenericCourseDefinitions(JsonNode courseNode, CourseType type) { - Map coursesDef = new HashMap<>(); - courseNode.fields().forEachRemaining(e -> { - CourseDefinition cd = new CourseDefinition(); - JsonNode thisCourseNode = e.getValue(); - cd.setCourseName(thisCourseNode.path("_comment").textValue()); - if (CourseType.SMART_COURSE.equals(type)) { - cd.setBaseCourseName(thisCourseNode.path("Course").textValue()); - } - cd.setCourseType(type); - if (thisCourseNode.path("function").isArray()) { - // just to be safe here - ArrayNode functions = (ArrayNode) thisCourseNode.path("function"); - List functionList = cd.getFunctions(); - for (JsonNode fNode : functions) { - // map all course functions here - CourseFunction f = new CourseFunction(); - f.setValue(fNode.path("value").textValue()); - f.setDefaultValue(fNode.path("default").textValue()); - JsonNode selectableNode = fNode.path("selectable"); - // only Courses (not SmartCourses or DownloadedCourses) can have selectable functions - f.setSelectable( - !selectableNode.isMissingNode() && selectableNode.isArray() && (type == CourseType.COURSE)); - if (f.isSelectable()) { - List selectableValues = f.getSelectableValues(); - // map values acceptable for this function - for (JsonNode v : (ArrayNode) selectableNode) { - if (v.isValueNode()) { - selectableValues.add(v.textValue()); - } - } - f.setSelectableValues(selectableValues); - } - functionList.add(f); - } - cd.setFunctions(functionList); - } - coursesDef.put(e.getKey(), cd); - }); - CourseDefinition cdNotSelected = new CourseDefinition(); - cdNotSelected.setCourseType(type); - cdNotSelected.setCourseName("Not Selected"); - coursesDef.put(getNotSelectedCourseKey(), cdNotSelected); - return coursesDef; + dwCap.setState(newFeatureDefinition(getStateFeatureNodeName(), monitorValueNode)); + dwCap.setProcessState(newFeatureDefinition(getProcessStateNodeName(), monitorValueNode)); + dwCap.setDoorStateFeat(newFeatureDefinition(getDoorLockFeatureNodeName(), monitorValueNode)); + dwCap.setMonitoringDataFormat(getMonitorDataFormat(rootNode)); + return dwCap; } protected Map getCourseDefinitions(JsonNode courseNode) { - return getGenericCourseDefinitions(courseNode, CourseType.COURSE); + return Utils.getGenericCourseDefinitions(courseNode, CourseType.COURSE, getNotSelectedCourseKey()); } protected Map getSmartCourseDefinitions(JsonNode smartCourseNode) { - return getGenericCourseDefinitions(smartCourseNode, CourseType.SMART_COURSE); + return Utils.getGenericCourseDefinitions(smartCourseNode, CourseType.SMART_COURSE, getNotSelectedCourseKey()); } - protected abstract String getDryLevelNodeName(); - protected abstract String getNotSelectedCourseKey(); @Override public final List getSupportedDeviceTypes() { - return List.of(DeviceTypes.WASHERDRYER_MACHINE, DeviceTypes.DRYER); + return List.of(DeviceTypes.DISH_WASHER); } - protected abstract String getCourseNodeName(JsonNode rootNode); - - protected abstract String getSmartCourseNodeName(JsonNode rootNode); - - protected abstract String getDefaultCourse(JsonNode rootNode); - - protected abstract String getRemoteFeatName(); - - protected abstract String getStandByFeatName(); - - protected abstract String getConfigCourseType(JsonNode rootNode); - - protected abstract String getConfigSmartCourseType(JsonNode rootNote); + protected abstract String getCourseNodeName(); - protected abstract String getConfigDownloadCourseType(JsonNode rootNode); + protected abstract String getSmartCourseNodeName(); protected abstract String getMonitorValueNodeName(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseType.java deleted file mode 100644 index 87b58d90103b7..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/CourseType.java +++ /dev/null @@ -1,39 +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.lgthinq.lgservices.model.devices.dishwasher; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link CourseType} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum CourseType { - // TODO - review DownloadCourse value, in remote start debugging - COURSE("Course"), - SMART_COURSE("SmartCourse"), - DOWNLOADED_COURSE("DownloadedCourse"), - UNDEF("Undefined"); - - private final String value; - - CourseType(String s) { - value = s; - } - - public String getValue() { - return value; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java index 8f0bb137463a0..31bde31b309e2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java @@ -12,15 +12,13 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; -import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; -import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; /** * The {@link DishWasherCapability} @@ -29,93 +27,11 @@ */ @NonNullByDefault public class DishWasherCapability extends AbstractCapability { - private String defaultCourseFieldName = ""; - private String doorLockFeatName = ""; - private String childLockFeatName = ""; - private String defaultSmartCourseFieldName = ""; - private String commandRemoteStart = ""; - private String remoteStartFeatName = ""; - private String commandWakeUp = ""; - private String commandStop = ""; - private String defaultCourseId = ""; + private FeatureDefinition doorState = FeatureDefinition.NULL_DEFINITION; private FeatureDefinition state = FeatureDefinition.NULL_DEFINITION; - private FeatureDefinition soilWash = FeatureDefinition.NULL_DEFINITION; - private FeatureDefinition spin = FeatureDefinition.NULL_DEFINITION; - private FeatureDefinition temperature = FeatureDefinition.NULL_DEFINITION; - private FeatureDefinition rinse = FeatureDefinition.NULL_DEFINITION; - private FeatureDefinition error = FeatureDefinition.NULL_DEFINITION; - private FeatureDefinition dryLevel = FeatureDefinition.NULL_DEFINITION; private FeatureDefinition processState = FeatureDefinition.NULL_DEFINITION; - private boolean hasDoorLook; - private boolean hasTurboWash; - private Map commandsDefinition = new HashMap<>(); private Map courses = new LinkedHashMap<>(); - static class RinseFeatureFunction implements Function { - @Override - public FeatureDefinition apply(DishWasherCapability c) { - return c.getRinseFeat(); - } - } - - static class TemperatureFeatureFunction implements Function { - @Override - public FeatureDefinition apply(DishWasherCapability c) { - return c.getTemperatureFeat(); - } - } - - static class SpinFeatureFunction implements Function { - @Override - public FeatureDefinition apply(DishWasherCapability c) { - return c.getSpinFeat(); - } - } - - public String getDefaultCourseId() { - return defaultCourseId; - } - - public void setDefaultCourseId(String defaultCourseId) { - this.defaultCourseId = defaultCourseId; - } - - public Map getCommandsDefinition() { - return commandsDefinition; - } - - public FeatureDefinition getDryLevel() { - return dryLevel; - } - - public String getCommandStop() { - return commandStop; - } - - public void setCommandStop(String commandStop) { - this.commandStop = commandStop; - } - - public String getCommandRemoteStart() { - return commandRemoteStart; - } - - public void setCommandRemoteStart(String commandRemoteStart) { - this.commandRemoteStart = commandRemoteStart; - } - - public String getCommandWakeUp() { - return commandWakeUp; - } - - public void setCommandWakeUp(String commandWakeUp) { - this.commandWakeUp = commandWakeUp; - } - - public void setDryLevel(FeatureDefinition dryLevel) { - this.dryLevel = dryLevel; - } - public FeatureDefinition getProcessState() { return processState; } @@ -124,10 +40,6 @@ public void setProcessState(FeatureDefinition processState) { this.processState = processState; } - public void setCommandsDefinition(Map commandsDefinition) { - this.commandsDefinition = commandsDefinition; - } - public Map getCourses() { return courses; } @@ -140,95 +52,15 @@ public FeatureDefinition getStateFeat() { return state; } - public boolean hasDoorLook() { - return this.hasDoorLook; + public FeatureDefinition getDoorStateFeat() { + return doorState; } - public void setHasDoorLook(boolean hasDoorLook) { - this.hasDoorLook = hasDoorLook; + public void setDoorStateFeat(FeatureDefinition doorState) { + this.doorState = doorState; } public void setState(FeatureDefinition state) { this.state = state; } - - public FeatureDefinition getSoilWash() { - return soilWash; - } - - public void setSoilWash(FeatureDefinition soilWash) { - this.soilWash = soilWash; - } - - public FeatureDefinition getSpinFeat() { - return spin; - } - - public void setSpinFeat(FeatureDefinition spin) { - this.spin = spin; - } - - public FeatureDefinition getTemperatureFeat() { - return temperature; - } - - public void setTemperatureFeat(FeatureDefinition temperature) { - this.temperature = temperature; - } - - public FeatureDefinition getRinseFeat() { - return rinse; - } - - public void setRinseFeat(FeatureDefinition rinse) { - this.rinse = rinse; - } - - public FeatureDefinition getError() { - return error; - } - - public void setError(FeatureDefinition error) { - this.error = error; - } - - public String getDefaultCourseFieldName() { - return defaultCourseFieldName; - } - - public void setDefaultCourseFieldName(String defaultCourseFieldName) { - this.defaultCourseFieldName = defaultCourseFieldName; - } - - public String getDefaultSmartCourseFeatName() { - return defaultSmartCourseFieldName; - } - - public void setDefaultSmartCourseFeatName(String defaultSmartCourseFieldName) { - this.defaultSmartCourseFieldName = defaultSmartCourseFieldName; - } - - public String getRemoteStartFeatName() { - return remoteStartFeatName; - } - - public void setRemoteStartFeatName(String remoteStartFeatName) { - this.remoteStartFeatName = remoteStartFeatName; - } - - public String getChildLockFeatName() { - return childLockFeatName; - } - - public void setChildLockFeatName(String childLockFeatName) { - this.childLockFeatName = childLockFeatName; - } - - public String getDoorLockFeatName() { - return doorLockFeatName; - } - - public void setDoorLockFeatName(String doorLockFeatName) { - this.doorLockFeatName = doorLockFeatName; - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java index 65dca5d277f72..0c73017ed5672 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java @@ -13,12 +13,10 @@ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; import java.util.*; -import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -26,23 +24,14 @@ import com.fasterxml.jackson.databind.JsonNode; /** - * The {@link DishWasherCapabilityFactoryV1} + * The {@link DishWasherCapabilityFactoryV1} - Not implemented * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class DishWasherCapabilityFactoryV1 extends AbstractDishWasherCapabilityFactory { +public abstract class DishWasherCapabilityFactoryV1 extends AbstractDishWasherCapabilityFactory { private static final Logger logger = LoggerFactory.getLogger(DishWasherCapabilityFactoryV1.class); - @Override - public DishWasherCapability create(JsonNode rootNode) throws LGThinqException { - DishWasherCapability cap = super.create(rootNode); - cap.setRemoteStartFeatName("RemoteStart"); - cap.setChildLockFeatName("ChildLock"); - cap.setDoorLockFeatName("DoorLock"); - return cap; - } - @Override protected String getStateFeatureNodeName() { return "State"; @@ -53,35 +42,9 @@ protected String getProcessStateNodeName() { return "PreState"; } - @Override - protected String getPreStateFeatureNodeName() { - return "PreState"; - } - - @Override - protected String getRinseFeatureNodeName() { - return "RinseOption"; - } - - @Override - protected String getTemperatureFeatureNodeName() { - return "WaterTemp"; - } - - @Override - protected String getSpinFeatureNodeName() { - return "SpinSpeed"; - } - - @Override - protected String getSoilWashFeatureNodeName() { - return "Wash"; - } - @Override protected String getDoorLockFeatureNodeName() { - // there is no dook lock node in V1. - return "DUMMY_DOOR_LOCK"; + return "Door"; } @Override @@ -95,31 +58,6 @@ protected Map getCommandsDefinition(JsonNode rootNode return getCommandsDefinitionV1(rootNode); } - @Override - protected String getCommandRemoteStartNodeName() { - return "OperationStart"; - } - - @Override - protected String getCommandStopNodeName() { - return "OperationStop"; - } - - @Override - protected String getCommandWakeUpNodeName() { - return "OperationWakeUp"; - } - - @Override - protected String getDefaultCourseIdNodeName() { - return "defaultCourseId"; - } - - @Override - protected String getDryLevelNodeName() { - return "DryLevel"; - } - @Override protected String getNotSelectedCourseKey() { return "0"; @@ -167,63 +105,6 @@ public DishWasherCapability getCapabilityInstance() { return new DishWasherCapability(); } - @Override - /* - * Return the default Course ID. - * OBS:In the V1, the default course points to the ID of the course list that is the default. - */ - protected String getDefaultCourse(JsonNode rootNode) { - return rootNode.path("Config").path("defaultCourseId").textValue(); - } - - @Override - protected String getRemoteFeatName() { - return "RemoteStart"; - } - - @Override - protected String getStandByFeatName() { - return "Standby"; - } - - @Override - protected String getConfigCourseType(JsonNode rootNode) { - if (rootNode.path(getMonitorValueNodeName()).path("APCourse").isMissingNode()) { - return "Course"; - } else { - return "APCourse"; - } - } - - @Override - protected String getCourseNodeName(JsonNode rootNode) { - JsonNode refOptions = rootNode.path(getMonitorValueNodeName()).path(getConfigCourseType(rootNode)) - .path("option"); - if (refOptions.isArray()) { - AtomicReference courseNodeName = new AtomicReference<>(""); - for (JsonNode node : refOptions) { - return node.asText(); - } - } - return ""; - } - - @Override - protected String getSmartCourseNodeName(JsonNode rootNode) { - return "SmartCourse"; - } - - @Override - protected String getConfigSmartCourseType(JsonNode rootNote) { - return "SmartCourse"; - } - - @Override - protected String getConfigDownloadCourseType(JsonNode rootNode) { - // just to ignore because there is no DownloadCourseType in V1 - return "XXXXXXXXXXX"; - } - @Override protected String getMonitorValueNodeName() { return "Value"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java index ba541e65075c7..066eea2eb096b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java @@ -16,13 +16,11 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ValueNode; /** * The {@link DishWasherCapabilityFactoryV2} @@ -38,13 +36,18 @@ protected List getSupportedAPIVersions() { return List.of(LGAPIVerion.V2_0); } + protected String getDoorLockFeatureNodeName() { + return "Door"; + } + + @Override + protected String getConvertingRulesNodeName() { + return "ConvertingRule"; + } + @Override - public DishWasherCapability create(JsonNode rootNode) throws LGThinqException { - DishWasherCapability cap = super.create(rootNode); - cap.setRemoteStartFeatName("remoteStart"); - cap.setChildLockFeatName("standby"); - cap.setDoorLockFeatName("loadItemWasher"); - return cap; + protected String getControlConvertingRulesNodeName() { + return "ControlConvertingRule"; } @Override @@ -58,30 +61,34 @@ protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode fe fd.setName(featureName); fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); + fd.setLabel(featureName); - JsonNode labelNode = featureNode.path("label"); - if (!labelNode.isMissingNode() && !labelNode.isNull()) { - fd.setLabel(labelNode.asText()); - } else { - fd.setLabel(featureName); - } // all features from V2 are enums fd.setDataType(FeatureDataType.ENUM); - JsonNode valuesMappingNode = featureNode.path("valueMapping"); - if (!valuesMappingNode.isMissingNode()) { + // surprisingly the DW V2 has the same json struct as V1 of other devices. + JsonNode optionsNode = featureNode.path("option"); + if (!optionsNode.isMissingNode()) { - Map valuesMapping = new HashMap<>(); - valuesMappingNode.fields().forEachRemaining(e -> { + Map options = new HashMap<>(); + optionsNode.fields().forEachRemaining(e -> { // collect values as: // - // "POWEROFF": { - // "index": 0, - // "label": "@WM_STATE_POWER_OFF_W" + // "State": { + // "type": "Enum", + // "default": "POWEROFF", + // "option": { + // "POWEROFF": "@DW_STATE_POWER_OFF_W", + // "INITIAL": "@DW_STATE_INITIAL_W", + // "RUNNING": "@DW_STATE_RUNNING_W", + // "PAUSE": "@DW_STATE_PAUSE_W", + // "STANDBY": "@DW_STATE_POWER_OFF_W", + // "END": "@DW_STATE_COMPLETE_W", + // "POWERFAIL": "@DW_STATE_POWER_FAIL_W" + // } // }, - // to "POWEROFF" -> "@WM_STATE_POWER_OFF_W" - valuesMapping.put(e.getKey(), e.getValue().path("label").asText()); + options.put(e.getKey(), e.getValue().asText()); }); - fd.setValuesMapping(valuesMapping); + fd.setValuesMapping(options); } return fd; @@ -93,13 +100,12 @@ public DishWasherCapability getCapabilityInstance() { } @Override - protected String getCourseNodeName(JsonNode rootNode) { - String courseType = getConfigCourseType(rootNode); - return rootNode.path(getMonitorValueNodeName()).path(courseType).path("ref").textValue(); + protected String getCourseNodeName() { + return "Course"; } @Override - protected String getSmartCourseNodeName(JsonNode rootNode) { + protected String getSmartCourseNodeName() { return "SmartCourse"; } @@ -107,76 +113,14 @@ private String getConfigNodeName() { return "Config"; } - @Override - /* - * Return the default Course Name - * OBS:In the V2, the default course points to the default course name - */ - protected String getDefaultCourse(JsonNode rootNode) { - return rootNode.path(getConfigNodeName()).path("defaultCourse").textValue(); - } - - @Override - protected String getRemoteFeatName() { - return "remoteStart"; - } - - @Override - protected String getStandByFeatName() { - return "standby"; - } - - @Override - protected String getConfigCourseType(JsonNode rootNode) { - return rootNode.path(getConfigNodeName()).path("courseType").textValue(); - } - - protected String getConfigSmartCourseType(JsonNode rootNode) { - return rootNode.path(getConfigNodeName()).path("smartCourseType").textValue(); - } - - protected String getConfigDownloadCourseType(JsonNode rootNode) { - return rootNode.path(getConfigNodeName()).path("downloadedCourseType").textValue(); - } - @Override protected String getStateFeatureNodeName() { - return "state"; + return "State"; } @Override protected String getProcessStateNodeName() { - return "preState"; - } - - @Override - protected String getPreStateFeatureNodeName() { - return "preState"; - } - - @Override - protected String getRinseFeatureNodeName() { - return "rinse"; - } - - @Override - protected String getTemperatureFeatureNodeName() { - return "temp"; - } - - @Override - protected String getSpinFeatureNodeName() { - return "spin"; - } - - @Override - protected String getSoilWashFeatureNodeName() { - return "soilWash"; - } - - @Override - protected String getDoorLockFeatureNodeName() { - return "doorLock"; + return "Process"; } @Override @@ -187,71 +131,7 @@ protected MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode) { @Override protected Map getCommandsDefinition(JsonNode rootNode) { - JsonNode commandNode = rootNode.path("ControlWifi"); - List escapeDataValues = Arrays.asList("course", "SmartCourse", "doorLock", "childLock"); - if (commandNode.isMissingNode()) { - logger.warn("No commands found in the DryerWasher definition. This is most likely a bug."); - return Collections.EMPTY_MAP; - } - Map commands = new HashMap<>(); - for (Iterator> it = commandNode.fields(); it.hasNext();) { - Map.Entry e = it.next(); - String commandName = e.getKey(); - if (commandName.equals("vtCtrl")) { - // ignore command - continue; - } - CommandDefinition cd = new CommandDefinition(); - JsonNode thisCommandNode = e.getValue(); - cd.setCommand(thisCommandNode.path("command").textValue()); - JsonNode dataValues = thisCommandNode.path("data").path("washerDryer"); - if (!dataValues.isMissingNode()) { - Map data = new HashMap<>(); - dataValues.fields().forEachRemaining(f -> { - // only load features outside escape. - if (!escapeDataValues.contains(f.getKey())) { - if (f.getValue().isValueNode()) { - ValueNode vn = (ValueNode) f.getValue(); - if (f.getValue().isTextual()) { - data.put(f.getKey(), vn.asText()); - } else if (f.getValue().isNumber()) { - data.put(f.getKey(), vn.asInt()); - } - } - } - }); - // add extra data features - data.put(getConfigCourseType(rootNode), ""); - data.put(getConfigSmartCourseType(rootNode), ""); - data.put("courseType", ""); - cd.setData(data); - cd.setRawCommand(thisCommandNode.toPrettyString()); - } else { - logger.warn("Data node not found in the WasherDryer definition. It's most likely a bug"); - } - commands.put(commandName, cd); - } - return commands; - } - - @Override - protected String getCommandRemoteStartNodeName() { - return "WMStart"; - } - - @Override - protected String getCommandStopNodeName() { - return "WMStop"; - } - - @Override - protected String getCommandWakeUpNodeName() { - return "WMWakeup"; - } - - @Override - protected String getDefaultCourseIdNodeName() { - return "defaultCourse"; + return Collections.emptyMap(); } @Override @@ -261,11 +141,6 @@ protected String getNotSelectedCourseKey() { @Override protected String getMonitorValueNodeName() { - return "MonitoringValue"; - } - - @Override - protected String getDryLevelNodeName() { - return "dryLevel"; + return "Value"; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java index 12c3845f864ca..9d662a7be0237 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java @@ -37,41 +37,14 @@ public class DishWasherSnapshot extends AbstractSnapshotDefinition { private boolean online; private String course = ""; private String smartCourse = ""; - private String downloadedCourse = ""; - private String temperatureLevel = ""; private String doorLock = ""; - private String option1 = ""; - private String option2 = ""; - private String childLock = ""; private Double remainingHour = 0.00; private Double remainingMinute = 0.00; private Double reserveHour = 0.00; private Double reserveMinute = 0.00; - private String remoteStart = ""; - private boolean remoteStartEnabled = false; - private String standByStatus = ""; - - private String dryLevel = ""; - private boolean standBy = false; - private String error = ""; - private String rinse = ""; - private String spin = ""; - - private String loadItem = ""; - - public String getLoadItem() { - return loadItem; - } - - @JsonAlias({ "LoadItem" }) - @JsonProperty("loadItemWasher") - public void setLoadItem(String loadItem) { - this.loadItem = loadItem; - } - - @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) - @JsonProperty("courseFL24inchBaseTitan") + @JsonAlias({ "Course" }) + @JsonProperty("course") public String getCourse() { return course; } @@ -80,18 +53,8 @@ public void setCourse(String course) { this.course = course; } - @JsonProperty("dryLevel") - @JsonAlias({ "DryLevel" }) - public String getDryLevel() { - return dryLevel; - } - - public void setDryLevel(String dryLevel) { - this.dryLevel = dryLevel; - } - - @JsonProperty("processState") - @JsonAlias({ "ProcessState", "preState", "PreState" }) + @JsonProperty("process") + @JsonAlias({ "Process" }) public String getProcessState() { return processState; } @@ -100,16 +63,6 @@ public void setProcessState(String processState) { this.processState = processState; } - @JsonProperty("error") - @JsonAlias({ "Error" }) - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - @Override public DevicePowerState getPowerStatus() { return powerState; @@ -131,27 +84,17 @@ public void setOnline(boolean online) { } @JsonProperty("state") - @JsonAlias({ "state", "State" }) + @JsonAlias({ "State" }) public String getState() { return state; } - @JsonProperty("smartCourseFL24inchBaseTitan") - @JsonAlias({ "smartCourseFL24inchBaseTitan", "SmartCourse" }) + @JsonProperty("smartCourse") + @JsonAlias({ "SmartCourse" }) public String getSmartCourse() { return smartCourse; } - @JsonProperty("downloadedCourseFL24inchBaseTitan") - @JsonAlias({ "downloadedCourseFLUpper25inchBaseUS" }) - public String getDownloadedCourse() { - return downloadedCourse; - } - - public void setDownloadedCourse(String downloadedCourse) { - this.downloadedCourse = downloadedCourse; - } - @JsonIgnore public String getRemainingTime() { return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); @@ -163,7 +106,7 @@ public String getReserveTime() { } @JsonProperty("remainTimeHour") - @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + @JsonAlias({ "Remain_Time_H" }) public Double getRemainingHour() { return remainingHour; } @@ -173,7 +116,7 @@ public void setRemainingHour(Double remainingHour) { } @JsonProperty("remainTimeMinute") - @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + @JsonAlias({ "Remain_Time_M" }) public Double getRemainingMinute() { return remainingMinute; } @@ -183,7 +126,7 @@ public void setRemainingMinute(Double remainingMinute) { } @JsonProperty("reserveTimeHour") - @JsonAlias({ "reserveTimeHour", "Reserve_Time_H" }) + @JsonAlias({ "Reserve_Time_H" }) public Double getReserveHour() { return reserveHour; } @@ -193,7 +136,7 @@ public void setReserveHour(Double reserveHour) { } @JsonProperty("reserveTimeMinute") - @JsonAlias({ "reserveTimeMinute", "Reserve_Time_M" }) + @JsonAlias({ "Reserve_Time_M" }) public Double getReserveMinute() { return reserveMinute; } @@ -206,18 +149,8 @@ public void setSmartCourse(String smartCourse) { this.smartCourse = smartCourse; } - @JsonProperty("temp") - @JsonAlias({ "WaterTemp" }) - public String getTemperatureLevel() { - return temperatureLevel; - } - - public void setTemperatureLevel(String temperatureLevel) { - this.temperatureLevel = temperatureLevel; - } - - @JsonProperty("doorLock") - @JsonAlias({ "DoorLock", "DoorClose" }) + @JsonProperty("door") + @JsonAlias({ "Door" }) public String getDoorLock() { return doorLock; } @@ -226,16 +159,6 @@ public void setDoorLock(String doorLock) { this.doorLock = doorLock; } - @JsonProperty("ChildLock") - @JsonAlias({ "childLock" }) - public String getChildLock() { - return childLock; - } - - public void setChildLock(String childLock) { - this.childLock = childLock; - } - public void setState(String state) { this.state = state; if (state.equals(WM_POWER_OFF_VALUE)) { @@ -244,75 +167,4 @@ public void setState(String state) { powerState = DevicePowerState.DV_POWER_ON; } } - - public boolean isRemoteStartEnabled() { - return remoteStartEnabled; - } - - @JsonProperty("remoteStart") - @JsonAlias({ "RemoteStart" }) - public String getRemoteStart() { - return remoteStart; - } - - public void setRemoteStart(String remoteStart) { - this.remoteStart = remoteStart.contains("ON") || remoteStart.equals("1") ? "ON" - : (remoteStart.contains("OFF") || remoteStart.equals("0") ? "OFF" : remoteStart); - remoteStartEnabled = this.remoteStart.equals("ON"); - } - - @JsonProperty("standby") - @JsonAlias({ "Standby" }) - public String getStandByStatus() { - return standByStatus; - } - - public void setStandByStatus(String standByStatus) { - this.standByStatus = standByStatus.contains("ON") || standByStatus.equals("1") ? "ON" - : (standByStatus.contains("OFF") || standByStatus.equals("0") ? "OFF" : standByStatus); - ; - standBy = this.standByStatus.contains("ON"); - } - - public boolean isStandBy() { - return standBy; - } - - @JsonProperty("rinse") - @JsonAlias({ "RinseOption" }) - public String getRinse() { - return rinse; - } - - public void setRinse(String rinse) { - this.rinse = rinse; - } - - @JsonProperty("spin") - @JsonAlias({ "SpinSpeed" }) - public String getSpin() { - return spin; - } - - public void setSpin(String spin) { - this.spin = spin; - } - - @JsonProperty("Option1") - public String getOption1() { - return option1; - } - - public void setOption1(String option1) { - this.option1 = option1; - } - - @JsonProperty("Option2") - public String getOption2() { - return option2; - } - - public void setOption2(String option2) { - this.option2 = option2; - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java index aebe81910676f..f771617c56b45 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java @@ -12,19 +12,16 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.DW_SNAPSHOT_WASHER_DRYER_NODE_V2; -import java.util.List; import java.util.Map; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; import org.openhab.binding.lgthinq.lgservices.model.*; /** - * The {@link WasherDryerSnapshotBuilder} + * The {@link DishWasherSnapshotBuilder} * * @author Nemer Daud - Initial contribution */ @@ -34,90 +31,42 @@ public DishWasherSnapshotBuilder() { super(DishWasherSnapshot.class); } - @Override - public DishWasherSnapshot createFromBinary(String binaryData, List prot, - CapabilityDefinition capDef) throws LGThinqUnmarshallException, LGThinqApiException { - DishWasherSnapshot snap = super.createFromBinary(binaryData, prot, capDef); - snap.setRemoteStart( - bitValue(((DishWasherCapability) capDef).getRemoteStartFeatName(), snap.getRawData(), capDef)); - snap.setDoorLock(bitValue(((DishWasherCapability) capDef).getDoorLockFeatName(), snap.getRawData(), capDef)); - snap.setChildLock(bitValue(((DishWasherCapability) capDef).getChildLockFeatName(), snap.getRawData(), capDef)); - return snap; - } - @Override protected DishWasherSnapshot getSnapshot(Map snapMap, CapabilityDefinition capDef) { DishWasherSnapshot snap; DeviceTypes type = capDef.getDeviceType(); LGAPIVerion version = capDef.getDeviceVersion(); - switch (type) { - case WASHING_TOWER: - case WASHERDRYER_MACHINE: - switch (version) { - case V1_0: { - snap = objectMapper.convertValue(snapMap, snapClass); - snap.setRawData(snapMap); - } - case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = objectMapper.convertValue(washerDryerMap, snapClass); - setAltCourseNodeName(capDef, snap, washerDryerMap); - snap.setRawData(washerDryerMap); - return snap; - } - } - case DRYER_TOWER: - case DRYER: - switch (version) { - case V1_0: { - throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); - } - case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = objectMapper.convertValue(washerDryerMap, snapClass); - setAltCourseNodeName(capDef, snap, washerDryerMap); - snap.setRawData(snapMap); - return snap; - } - } + if (!type.equals(DeviceTypes.DISH_WASHER)) { + throw new IllegalArgumentException( + String.format("Device Type %s not supported by this builder. It's most likely a bug.", type)); } - throw new IllegalStateException( - "Snapshot for device type " + type + " not supported for this builder. It most likely a bug"); - } - - private static void setAltCourseNodeName(CapabilityDefinition capDef, DishWasherSnapshot snap, - Map washerDryerMap) { - if (snap.getCourse().isEmpty() && capDef instanceof DishWasherCapability) { - String altCourseNodeName = ((DishWasherCapability) capDef).getDefaultCourseFieldName(); - String altSmartCourseNodeName = ((DishWasherCapability) capDef).getDefaultSmartCourseFeatName(); - snap.setCourse(Objects.requireNonNullElse((String) washerDryerMap.get(altCourseNodeName), "")); - snap.setSmartCourse(Objects.requireNonNullElse((String) washerDryerMap.get(altSmartCourseNodeName), "")); + switch (version) { + case V1_0: + throw new IllegalArgumentException("Version 1.0 for DishWasher is not supported yet."); + case V2_0: + Map dishWasher = Objects.requireNonNull( + (Map) snapMap.get(DW_SNAPSHOT_WASHER_DRYER_NODE_V2), + "dishwasher node must be present in the snapshot"); + snap = objectMapper.convertValue(dishWasher, snapClass); + snap.setRawData(dishWasher); + return snap; + default: + throw new IllegalArgumentException( + String.format("Version %s for DishWasher is not supported.", version)); } } @Override protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { - switch (type) { - case DRYER_TOWER: - case DRYER: + if (type == DeviceTypes.DISH_WASHER) { + if (snapMap.containsKey(DW_SNAPSHOT_WASHER_DRYER_NODE_V2)) { return LGAPIVerion.V2_0; - case WASHING_TOWER: - case WASHERDRYER_MACHINE: - if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { - return LGAPIVerion.V2_0; - } else if (snapMap.containsKey("State")) { - return LGAPIVerion.V1_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine WASHERDRYER_MACHINE API version."); - } - default: - throw new IllegalStateException("Discovery version for device type " + type - + " not supported for this builder. It most likely a bug"); + } else { + throw new IllegalStateException( + "DishWasher supports only Thinq V1. Can't find key node attributes to determine DishWasher V2 API."); + } } + throw new IllegalStateException( + "Discovery version for device type " + type + " not supported for this builder. It most likely a bug"); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java index d3a1dba8e8b9d..8025f35c23566 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java @@ -15,6 +15,7 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_CELSIUS_SYMBOL; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_FAHRENHEIT_SYMBOL; +import java.util.HashMap; import java.util.List; import java.util.Map; @@ -38,6 +39,15 @@ public abstract class AbstractFridgeCapabilityFactory extends AbstractCapability protected abstract void loadTempNode(JsonNode tempNode, Map capMap, String unit); + private Map convertCelsius2Fahrenheit(Map celcius) { + Map fMap = new HashMap<>(); + celcius.forEach((k, v) -> { + int c = Integer.parseInt(v); + fMap.put(k, String.valueOf((c * 9 / 5) + 32)); + }); + return fMap; + } + @Override public FridgeCapability create(JsonNode rootNode) throws LGThinqException { FridgeCapability frCap = super.create(rootNode); @@ -50,10 +60,19 @@ public FridgeCapability create(JsonNode rootNode) throws LGThinqException { JsonNode fridgeTempCNode = node.path(getMonitorValueNodeName()).path(getFridgeTempCNodeName()) .path(getOptionsNodeName()); + // version 1.4 of refrigerators thinq1 doesn't contain temp. segregated in C and F. + if (fridgeTempCNode.isMissingNode()) { + fridgeTempCNode = node.path(getMonitorValueNodeName()).path(getFridgeTempNodeName()) + .path(getOptionsNodeName()); + } JsonNode fridgeTempFNode = node.path(getMonitorValueNodeName()).path(getFridgeTempFNodeName()) .path(getOptionsNodeName()); JsonNode freezerTempCNode = node.path(getMonitorValueNodeName()).path(getFreezerTempCNodeName()) .path(getOptionsNodeName()); + if (freezerTempCNode.isMissingNode()) { + freezerTempCNode = node.path(getMonitorValueNodeName()).path(getFreezerTempNodeName()) + .path(getOptionsNodeName()); + } JsonNode freezerTempFNode = node.path(getMonitorValueNodeName()).path(getFreezerTempFNodeName()) .path(getOptionsNodeName()); JsonNode tempUnitNode = node.path(getMonitorValueNodeName()).path(getTempUnitNodeName()) @@ -72,16 +91,29 @@ public FridgeCapability create(JsonNode rootNode) throws LGThinqException { .path(getOptionsNodeName()); JsonNode atLeastOneDoorOpenNode = node.path(getMonitorValueNodeName()).path(getAtLeastOneDoorOpenNodeName()) .path(getOptionsNodeName()); - + if (!node.path(getMonitorValueNodeName()).path(getExpressCoolNodeName()).isMissingNode()) { + frCap.setExpressCoolModePresent(true); + } + if (!node.path(getMonitorValueNodeName()).path(getEcoFriendlyNodeName()).isMissingNode()) { + frCap.setEcoFriendlyModePresent(true); + } loadTempNode(fridgeTempCNode, frCap.getFridgeTempCMap(), TEMP_UNIT_CELSIUS_SYMBOL); - loadTempNode(fridgeTempFNode, frCap.getFridgeTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + if (fridgeTempFNode.isMissingNode()) { + frCap.getFridgeTempFMap().putAll(convertCelsius2Fahrenheit(frCap.getFridgeTempCMap())); + } else { + loadTempNode(fridgeTempFNode, frCap.getFridgeTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + } loadTempNode(freezerTempCNode, frCap.getFreezerTempCMap(), TEMP_UNIT_CELSIUS_SYMBOL); - loadTempNode(freezerTempFNode, frCap.getFreezerTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + if (freezerTempFNode.isMissingNode()) { + frCap.getFreezerTempFMap().putAll(convertCelsius2Fahrenheit(frCap.getFreezerTempCMap())); + } else { + loadTempNode(freezerTempFNode, frCap.getFreezerTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + } loadTempUnitNode(tempUnitNode, frCap.getTempUnitMap()); loadIcePlus(icePlusNode, frCap.getIcePlusMap()); loadFreshAirFilter(freshAirFilterNode, frCap.getFreshAirFilterMap()); loadWaterFilter(waterFilterNode, frCap.getWaterFilterMap()); - loadExpressMode(expressModeNode, frCap.getExpressModeMap()); + loadExpressFreezeMode(expressModeNode, frCap.getExpressFreezeModeMap()); loadSmartSavingMode(smartSavingModeNode, frCap.getSmartSavingMap()); loadActiveSaving(activeSavingNode, frCap.getActiveSavingMap()); loadAtLeastOneDoorOpen(atLeastOneDoorOpenNode, frCap.getAtLeastOneDoorOpenMap()); @@ -98,7 +130,8 @@ public FridgeCapability create(JsonNode rootNode) throws LGThinqException { protected abstract void loadWaterFilter(JsonNode waterFilterNode, Map waterFilterMap); - protected abstract void loadExpressMode(JsonNode expressModeNode, Map expressModeMap); + protected abstract void loadExpressFreezeMode(JsonNode expressFreezeModeNode, + Map expressFreezeModeMap); protected abstract void loadSmartSavingMode(JsonNode smartSavingModeNode, Map smartSavingModeMap); @@ -116,10 +149,14 @@ protected List getSupportedDeviceTypes() { protected abstract String getFridgeTempCNodeName(); + protected abstract String getFridgeTempNodeName(); + protected abstract String getFridgeTempFNodeName(); protected abstract String getFreezerTempCNodeName(); + protected abstract String getFreezerTempNodeName(); + protected abstract String getFreezerTempFNodeName(); protected abstract String getTempUnitNodeName(); @@ -138,5 +175,9 @@ protected List getSupportedDeviceTypes() { protected abstract String getAtLeastOneDoorOpenNodeName(); + protected abstract String getExpressCoolNodeName(); + protected abstract String getOptionsNodeName(); + + protected abstract String getEcoFriendlyNodeName(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java index d12fe17aa70b8..560cec892d4f7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java @@ -43,11 +43,28 @@ public class FridgeCanonicalCapability extends AbstractCapability icePlusMap = new LinkedHashMap();; private final Map freshAirFilterMap = new LinkedHashMap();; private final Map waterFilterMap = new LinkedHashMap();; - private final Map expressModeMap = new LinkedHashMap();; + private final Map expressFreezeModeMap = new LinkedHashMap();; private final Map smartSavingMap = new LinkedHashMap();; private final Map activeSavingMap = new LinkedHashMap();; private final Map atLeastOneDoorOpenMap = new LinkedHashMap<>(); private final Map commandsDefinition = new LinkedHashMap<>(); + private final Map expressCoolModeMap = new LinkedHashMap<>();; + private boolean isExpressCoolModePresent = false; + private boolean isEcoFriendlyModePresent = false; + + public void setExpressCoolModePresent(boolean expressCoolModePresent) { + isExpressCoolModePresent = expressCoolModePresent; + } + + @Override + public boolean isEcoFriendlyModePresent() { + return isEcoFriendlyModePresent; + } + + @Override + public void setEcoFriendlyModePresent(boolean isEcoFriendlyModePresent) { + this.isEcoFriendlyModePresent = isEcoFriendlyModePresent; + } public Map getFridgeTempCMap() { return fridgeTempCMap; @@ -86,8 +103,8 @@ public Map getWaterFilterMap() { } @Override - public Map getExpressModeMap() { - return expressModeMap; + public Map getExpressFreezeModeMap() { + return expressFreezeModeMap; } @Override @@ -108,4 +125,9 @@ public Map getAtLeastOneDoorOpenMap() { public Map getCommandsDefinition() { return commandsDefinition; } + + @Override + public boolean isExpressCoolModePresent() { + return isExpressCoolModePresent; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java index 78bdb6ec708fa..a95bb26242388 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java @@ -45,6 +45,17 @@ public class FridgeCanonicalSnapshot extends AbstractFridgeSnapshot { private String freshAirFilterState = ""; private String expressMode = ""; + private String expressCoolMode = ""; + private String ecoFriendlyMode = ""; + + @JsonProperty("ecoFriendly") + public String getEcoFriendlyMode() { + return ecoFriendlyMode; + } + + public void setEcoFriendlyMode(String ecoFriendlyMode) { + this.ecoFriendlyMode = ecoFriendlyMode; + } @JsonProperty("expressMode") public String getExpressMode() { @@ -151,4 +162,13 @@ public boolean isOnline() { public void setOnline(boolean online) { this.online = online; } + + @JsonProperty("expressFridge") + public String getExpressCoolMode() { + return expressCoolMode; + } + + public void setExpressCoolMode(String expressCoolMode) { + this.expressCoolMode = expressCoolMode; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java index 7d427285cbcdd..727e223160ebc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java @@ -26,15 +26,15 @@ @NonNullByDefault public interface FridgeCapability extends CapabilityDefinition { - public Map getFridgeTempCMap(); + Map getFridgeTempCMap(); - public Map getFridgeTempFMap(); + Map getFridgeTempFMap(); - public Map getFreezerTempCMap(); + Map getFreezerTempCMap(); - public Map getFreezerTempFMap(); + Map getFreezerTempFMap(); - public Map getTempUnitMap(); + Map getTempUnitMap(); Map getIcePlusMap(); @@ -42,7 +42,7 @@ public interface FridgeCapability extends CapabilityDefinition { Map getWaterFilterMap(); - Map getExpressModeMap(); + Map getExpressFreezeModeMap(); Map getSmartSavingMap(); @@ -51,4 +51,12 @@ public interface FridgeCapability extends CapabilityDefinition { Map getAtLeastOneDoorOpenMap(); Map getCommandsDefinition(); + + boolean isExpressCoolModePresent(); + + void setExpressCoolModePresent(boolean isPresent); + + boolean isEcoFriendlyModePresent(); + + void setEcoFriendlyModePresent(boolean isPresent); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index 7f6839d790f7b..519e883c97cf6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -87,7 +87,7 @@ protected void loadWaterFilter(JsonNode waterFilterNode, Map wat } @Override - protected void loadExpressMode(JsonNode expressModeNode, Map expressModeMap) { + protected void loadExpressFreezeMode(JsonNode expressFreezeModeNode, Map expressFreezeModeMap) { // not supported } @@ -130,6 +130,10 @@ protected String getFridgeTempCNodeName() { return "TempRefrigerator_C"; } + protected String getFridgeTempNodeName() { + return "TempRefrigerator"; + } + @Override protected String getFridgeTempFNodeName() { return "TempRefrigerator_F"; @@ -140,6 +144,10 @@ protected String getFreezerTempCNodeName() { return "TempFreezer_C"; } + protected String getFreezerTempNodeName() { + return "TempFreezer"; + } + @Override protected String getFreezerTempFNodeName() { return "TempFreezer_F"; @@ -150,6 +158,11 @@ protected String getOptionsNodeName() { return "option"; } + @Override + protected String getEcoFriendlyNodeName() { + return "UNSUPPORTED"; + } + protected String getTempUnitNodeName() { return "TempUnit"; } @@ -187,4 +200,9 @@ protected String getActiveSavingNodeName() { protected String getAtLeastOneDoorOpenNodeName() { return "DoorOpenState"; } + + @Override + protected String getExpressCoolNodeName() { + return "UNSUPPORTED"; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java index efab278ce0f7e..46d50aedec729 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -60,9 +60,11 @@ private void loadGenericFeatNode(JsonNode featNode, Map capMap, final Map constantsMap) { featNode.fields().forEachRemaining(f -> { // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' - String translatedValue = constantsMap.get(f.getValue().path("label").asText()); - translatedValue = translatedValue == null ? f.getValue().path("label").asText() : translatedValue; - capMap.put(f.getKey(), translatedValue); + if (!"IGNORE".equals(f.getKey())) { + String translatedValue = constantsMap.get(f.getValue().path("label").asText()); + translatedValue = translatedValue == null ? f.getValue().path("label").asText() : translatedValue; + capMap.put(f.getKey(), translatedValue); + } }); } @@ -92,8 +94,8 @@ protected void loadWaterFilter(JsonNode waterFilterNode, Map wat } @Override - protected void loadExpressMode(JsonNode expressModeNode, Map expressModeMap) { - loadGenericFeatNode(expressModeNode, expressModeMap, CAP_FR_WATER_FILTER); + protected void loadExpressFreezeMode(JsonNode expressFreezeModeNode, Map expressFreezeModeMap) { + loadGenericFeatNode(expressFreezeModeNode, expressFreezeModeMap, CAP_FR_EXPRESS_FREEZE_MODES); } @Override @@ -121,6 +123,11 @@ protected String getFridgeTempCNodeName() { return "fridgeTemp_C"; } + protected String getFridgeTempNodeName() { + throw new UnsupportedOperationException( + "Refrigerator Thinq2 doesn't support FridgeTemp node. It most likely a bug"); + } + @Override protected String getFridgeTempFNodeName() { return "fridgeTemp_F"; @@ -131,6 +138,12 @@ protected String getFreezerTempCNodeName() { return "freezerTemp_C"; } + @Override + protected String getFreezerTempNodeName() { + throw new UnsupportedOperationException( + "Refrigerator Thinq2 doesn't support FreezerTemp node. It most likely a bug"); + } + @Override protected String getFreezerTempFNodeName() { return "freezerTemp_F"; @@ -174,8 +187,18 @@ protected String getAtLeastOneDoorOpenNodeName() { return "atLeastOneDoorOpen"; } + @Override + protected String getExpressCoolNodeName() { + return "expressFridge"; + } + @Override protected String getOptionsNodeName() { return "valueMapping"; } + + @Override + protected String getEcoFriendlyNodeName() { + return "ecoFriendly"; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java index 84e6ea8564da1..f0b6db1a2ce23 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java @@ -23,11 +23,13 @@ import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseType; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ArrayNode; /** * The {@link AbstractWasherDryerCapabilityFactory} @@ -98,7 +100,9 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { wdCap.setDryLevel(newFeatureDefinition(getDryLevelNodeName(), monitorValueNode)); wdCap.setSoilWash(newFeatureDefinition(getSoilWashFeatureNodeName(), monitorValueNode)); wdCap.setCommandsDefinition(getCommandsDefinition(rootNode)); - if (monitorValueNode.get(getDoorLockFeatureNodeName()) != null) { + // DoorLock feat can be in alone (v2) or inside Options node (v1) + if (monitorValueNode.get(getDoorLockFeatureNodeName()) != null + || hasFeatInOptions(getDoorLockFeatureNodeName(), monitorValueNode)) { wdCap.setHasDoorLook(true); } wdCap.setDefaultCourseFieldName(getConfigCourseType(rootNode)); @@ -116,58 +120,14 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { return wdCap; } - protected Map getGenericCourseDefinitions(JsonNode courseNode, CourseType type) { - Map coursesDef = new HashMap<>(); - courseNode.fields().forEachRemaining(e -> { - CourseDefinition cd = new CourseDefinition(); - JsonNode thisCourseNode = e.getValue(); - cd.setCourseName(thisCourseNode.path("_comment").textValue()); - if (CourseType.SMART_COURSE.equals(type)) { - cd.setBaseCourseName(thisCourseNode.path("Course").textValue()); - } - cd.setCourseType(type); - if (thisCourseNode.path("function").isArray()) { - // just to be safe here - ArrayNode functions = (ArrayNode) thisCourseNode.path("function"); - List functionList = cd.getFunctions(); - for (JsonNode fNode : functions) { - // map all course functions here - CourseFunction f = new CourseFunction(); - f.setValue(fNode.path("value").textValue()); - f.setDefaultValue(fNode.path("default").textValue()); - JsonNode selectableNode = fNode.path("selectable"); - // only Courses (not SmartCourses or DownloadedCourses) can have selectable functions - f.setSelectable( - !selectableNode.isMissingNode() && selectableNode.isArray() && (type == CourseType.COURSE)); - if (f.isSelectable()) { - List selectableValues = f.getSelectableValues(); - // map values acceptable for this function - for (JsonNode v : (ArrayNode) selectableNode) { - if (v.isValueNode()) { - selectableValues.add(v.textValue()); - } - } - f.setSelectableValues(selectableValues); - } - functionList.add(f); - } - cd.setFunctions(functionList); - } - coursesDef.put(e.getKey(), cd); - }); - CourseDefinition cdNotSelected = new CourseDefinition(); - cdNotSelected.setCourseType(type); - cdNotSelected.setCourseName("Not Selected"); - coursesDef.put(getNotSelectedCourseKey(), cdNotSelected); - return coursesDef; - } + protected abstract boolean hasFeatInOptions(String featName, JsonNode monitoringValueNode); protected Map getCourseDefinitions(JsonNode courseNode) { - return getGenericCourseDefinitions(courseNode, CourseType.COURSE); + return Utils.getGenericCourseDefinitions(courseNode, CourseType.COURSE, getNotSelectedCourseKey()); } protected Map getSmartCourseDefinitions(JsonNode smartCourseNode) { - return getGenericCourseDefinitions(smartCourseNode, CourseType.SMART_COURSE); + return Utils.getGenericCourseDefinitions(smartCourseNode, CourseType.SMART_COURSE, getNotSelectedCourseKey()); } protected abstract String getDryLevelNodeName(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java deleted file mode 100644 index f12fc7dc0e21e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java +++ /dev/null @@ -1,64 +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.lgthinq.lgservices.model.devices.washerdryer; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link CourseDefinition} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class CourseDefinition { - private String courseName = ""; - // Name of the course this is based on. It's only used for SmartCourses - private String baseCourseName = ""; - private CourseType courseType = CourseType.UNDEF; - private List functions = new ArrayList<>(); - - public String getCourseName() { - return courseName; - } - - public String getBaseCourseName() { - return baseCourseName; - } - - public void setBaseCourseName(String baseCourseName) { - this.baseCourseName = baseCourseName; - } - - public void setCourseName(String courseName) { - this.courseName = courseName; - } - - public CourseType getCourseType() { - return courseType; - } - - public void setCourseType(CourseType courseType) { - this.courseType = courseType; - } - - public List getFunctions() { - return functions; - } - - public void setFunctions(List functions) { - this.functions = functions; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java deleted file mode 100644 index 3e83c4c155ba0..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java +++ /dev/null @@ -1,63 +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.lgthinq.lgservices.model.devices.washerdryer; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link CourseFunction} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class CourseFunction { - private String value = ""; - private String defaultValue = ""; - private boolean isSelectable; - private List selectableValues = new ArrayList<>(); - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getDefaultValue() { - return defaultValue; - } - - public void setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - } - - public boolean isSelectable() { - return isSelectable; - } - - public void setSelectable(boolean selectable) { - isSelectable = selectable; - } - - public List getSelectableValues() { - return selectableValues; - } - - public void setSelectableValues(List selectableValues) { - this.selectableValues = selectableValues; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java index 52cdf5e88068d..8859d45672f1e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java @@ -21,6 +21,7 @@ import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; /** * The {@link WasherDryerCapability} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java index cd86a9cab5a76..d7080e2e5dabb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -47,6 +47,21 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { return cap; } + @Override + protected boolean hasFeatInOptions(String featName, JsonNode monitoringValueNode) { + for (String optionNode : new String[] { "Option1", "Option2" }) { + JsonNode arrNode = monitoringValueNode.path(optionNode).path("option"); + if (arrNode.isArray()) { + for (JsonNode v : arrNode) { + if (v.asText().equals(featName)) { + return true; + } + } + } + } + return false; + } + @Override protected String getStateFeatureNodeName() { return "State"; @@ -84,8 +99,7 @@ protected String getSoilWashFeatureNodeName() { @Override protected String getDoorLockFeatureNodeName() { - // there is no dook lock node in V1. - return "DUMMY_DOOR_LOCK"; + return "DoorLock"; } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java index c6f7dccdd5f33..6b153979dd20b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java @@ -51,6 +51,12 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { return cap; } + @Override + protected boolean hasFeatInOptions(String featName, JsonNode monitoringValueNode) { + // there's no option node in V2 + return false; + } + @Override protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, @Nullable String targetChannelId, @Nullable String refChannelId) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java index d07a1e02d56e0..680b1b9eefd20 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java @@ -40,7 +40,10 @@ public WasherDryerSnapshot createFromBinary(String binaryData, List String - - Fridge Express Mode + + Express Freeze Mode + + + + Switch + + Express Cool + + + + Switch + + Vacation Mode diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml index 6459e6d450321..44a5448033a19 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml @@ -14,7 +14,6 @@ LG ThinQ Dish Washer - @@ -51,24 +50,8 @@ - - - - - - - - - - - - Show more information about the device. - - - - From a75463c2874290d1a984cd3167ddb4f4dbf126fa Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Thu, 16 May 2024 11:19:30 -0300 Subject: [PATCH 018/130] [lgthinq][fix] Bug fixes and DW support Signed-off-by: nemerdaud --- .../lgthinq/internal/handler/LGThinQAirConditionerHandler.java | 1 - 1 file changed, 1 deletion(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index d786086332fd9..898c1fe433e98 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -316,7 +316,6 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept lgThinqACApiClientService.turnCoolJetMode(getBridgeId(), getDeviceId(), command == OnOffType.ON ? getCapabilities().getCoolJetModeCommandOn() : getCapabilities().getCoolJetModeCommandOff()); - lgThinqACApiClientService.turnBellOnOff(getBridgeId(), getDeviceId(), "1"); } else { logger.warn("Received command different of OnOffType in CoolJet Mode Channel. Ignoring"); } From 6c4fdc5ea1d18c55cf82e8de829ff22ff2037099 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Thu, 16 May 2024 15:10:16 -0300 Subject: [PATCH 019/130] [lgthinq][fix] Fixed Discovering and Bridge initialization after upgrade from 4.1.0 -> 4.2.0 Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/pom.xml | 6 +- .../internal/LGThinQBindingConstants.java | 2 +- .../internal/LGThinQBridgeConfiguration.java | 2 +- .../internal/LGThinQHandlerFactory.java | 2 +- .../LGThinQStateDescriptionProvider.java | 2 +- .../api/LGThinqCanonicalModelUtil.java | 2 +- .../lgthinq/internal/api/LGThinqGateway.java | 2 +- .../internal/api/OauthLgEmpAuthenticator.java | 2 +- .../lgthinq/internal/api/RestResult.java | 2 +- .../lgthinq/internal/api/RestUtils.java | 2 +- .../lgthinq/internal/api/TokenManager.java | 2 +- .../lgthinq/internal/api/TokenResult.java | 2 +- .../lgthinq/internal/api/UserInfo.java | 2 +- .../internal/api/model/GatewayResult.java | 2 +- .../internal/api/model/HeaderResult.java | 2 +- .../discovery/LGThinqDiscoveryService.java | 96 ++++++++----------- .../errors/AccountLoginException.java | 2 +- .../internal/errors/LGThinqApiException.java | 2 +- .../errors/LGThinqApiExhaustionException.java | 2 +- ...GThinqDeviceV1MonitorExpiredException.java | 2 +- .../LGThinqDeviceV1OfflineException.java | 2 +- .../internal/errors/LGThinqException.java | 2 +- .../errors/LGThinqGatewayException.java | 2 +- .../errors/LGThinqUnmarshallException.java | 2 +- .../internal/errors/PreLoginException.java | 2 +- .../errors/RefreshTokenException.java | 2 +- .../internal/errors/TokenException.java | 2 +- .../internal/errors/UserInfoException.java | 2 +- .../BaseThingWithExtraInfoHandler.java | 2 +- .../handler/LGThinQAbstractDeviceHandler.java | 4 +- .../handler/LGThinQAirConditionerHandler.java | 2 +- .../internal/handler/LGThinQBridge.java | 6 +- .../handler/LGThinQBridgeHandler.java | 49 +++++----- .../handler/LGThinQDishWasherHandler.java | 2 +- .../handler/LGThinQFridgeHandler.java | 2 +- .../handler/LGThinQWasherDryerHandler.java | 2 +- .../lgthinq/internal/model/DataType.java | 2 +- .../internal/model/DeviceParameter.java | 2 +- .../internal/model/DeviceParameterGroup.java | 2 +- .../lgthinq/internal/model/ThinqChannel.java | 2 +- .../internal/model/ThinqChannelGroup.java | 2 +- .../lgthinq/internal/model/ThinqDevice.java | 2 +- .../internal/type/ThingModelTypeUtils.java | 2 +- .../type/ThinqChannelGroupTypeProvider.java | 2 +- .../type/ThinqChannelTypeProvider.java | 2 +- .../type/ThinqConfigDescriptionProvider.java | 2 +- .../internal/type/ThinqThingTypeProvider.java | 2 +- .../internal/type/ThinqTypesProviderImpl.java | 2 +- .../lgthinq/internal/type/UidUtils.java | 2 +- .../lgservices/LGThinQACApiClientService.java | 2 +- .../LGThinQACApiV1ClientServiceImpl.java | 2 +- .../LGThinQACApiV2ClientServiceImpl.java | 2 +- .../LGThinQAbstractApiClientService.java | 2 +- .../LGThinQAbstractApiV1ClientService.java | 2 +- .../LGThinQAbstractApiV2ClientService.java | 2 +- .../lgservices/LGThinQApiClientService.java | 2 +- .../LGThinQApiClientServiceFactory.java | 2 +- .../lgservices/LGThinQDRApiClientService.java | 2 +- .../LGThinQDRApiV2ClientServiceImpl.java | 2 +- .../LGThinQDishWasherApiClientService.java | 2 +- ...ThinQDishWasherApiV1ClientServiceImpl.java | 2 +- ...ThinQDishWasherApiV2ClientServiceImpl.java | 2 +- .../LGThinQFridgeApiClientService.java | 2 +- .../LGThinQFridgeApiV1ClientServiceImpl.java | 2 +- .../LGThinQFridgeApiV2ClientServiceImpl.java | 2 +- .../lgservices/LGThinQWMApiClientService.java | 2 +- .../LGThinQWMApiV1ClientServiceImpl.java | 2 +- .../LGThinQWMApiV2ClientServiceImpl.java | 2 +- .../lgservices/model/AbstractCapability.java | 2 +- .../model/AbstractCapabilityFactory.java | 2 +- .../model/AbstractSnapshotDefinition.java | 2 +- .../model/CapabilityDefinition.java | 2 +- .../lgservices/model/CapabilityFactory.java | 2 +- .../lgservices/model/CommandDefinition.java | 2 +- .../model/DefaultSnapshotBuilder.java | 2 +- .../lgservices/model/DevicePowerState.java | 2 +- .../model/DeviceRegistryService.java | 2 +- .../lgthinq/lgservices/model/DeviceTypes.java | 2 +- .../lgservices/model/FeatureDataType.java | 2 +- .../lgservices/model/FeatureDefinition.java | 2 +- .../lgthinq/lgservices/model/LGAPIVerion.java | 2 +- .../lgthinq/lgservices/model/LGDevice.java | 2 +- .../lgthinq/lgservices/model/ModelUtils.java | 2 +- .../model/MonitoringBinaryProtocol.java | 2 +- .../model/MonitoringResultFormat.java | 2 +- .../lgthinq/lgservices/model/ResultCodes.java | 2 +- .../lgservices/model/SnapshotBuilder.java | 2 +- .../model/SnapshotBuilderFactory.java | 2 +- .../lgservices/model/SnapshotDefinition.java | 2 +- .../model/devices/ac/ACCanonicalSnapshot.java | 2 +- .../model/devices/ac/ACCapability.java | 2 +- .../devices/ac/ACCapabilityFactoryV1.java | 2 +- .../devices/ac/ACCapabilityFactoryV2.java | 2 +- .../model/devices/ac/ACFanSpeed.java | 2 +- .../lgservices/model/devices/ac/ACOpMode.java | 2 +- .../model/devices/ac/ACSnapshotBuilder.java | 2 +- .../model/devices/ac/ACTargetTmp.java | 2 +- .../ac/AbstractACCapabilityFactory.java | 2 +- .../model/devices/ac/ExtendedDeviceInfo.java | 2 +- .../commons/washers/CourseDefinition.java | 2 +- .../commons/washers/CourseFunction.java | 2 +- .../devices/commons/washers/CourseType.java | 2 +- .../model/devices/commons/washers/Utils.java | 2 +- .../AbstractDishWasherCapabilityFactory.java | 2 +- .../dishwasher/DishWasherCapability.java | 2 +- .../DishWasherCapabilityFactoryV1.java | 2 +- .../DishWasherCapabilityFactoryV2.java | 2 +- .../dishwasher/DishWasherSnapshot.java | 2 +- .../dishwasher/DishWasherSnapshotBuilder.java | 2 +- .../AbstractFridgeCapabilityFactory.java | 2 +- .../fridge/AbstractFridgeSnapshot.java | 2 +- .../fridge/FridgeCanonicalCapability.java | 2 +- .../fridge/FridgeCanonicalSnapshot.java | 2 +- .../devices/fridge/FridgeCapability.java | 2 +- .../fridge/FridgeCapabilityFactoryV1.java | 2 +- .../fridge/FridgeCapabilityFactoryV2.java | 2 +- .../devices/fridge/FridgeSnapshotBuilder.java | 2 +- .../AbstractWasherDryerCapabilityFactory.java | 2 +- .../washerdryer/WasherDryerCapability.java | 2 +- .../WasherDryerCapabilityFactoryV1.java | 2 +- .../WasherDryerCapabilityFactoryV2.java | 2 +- .../washerdryer/WasherDryerSnapshot.java | 2 +- .../WasherDryerSnapshotBuilder.java | 2 +- .../binding/lgthinq/handler/JsonUtils.java | 2 +- .../lgthinq/handler/LGThinqBridgeTests.java | 2 +- .../LGThinQWasherDryerHandlerTest.java | 2 +- .../model/CapabilityFactoryTest.java | 2 +- 127 files changed, 199 insertions(+), 206 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml index b5680812010ce..71c06a05b1e3d 100644 --- a/bundles/org.openhab.binding.lgthinq/pom.xml +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -1,12 +1,12 @@ - + 4.0.0 org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.1.0-SNAPSHOT + 4.2.0-SNAPSHOT org.openhab.binding.lgthinq diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index f47b7a57d6a50..7e204e69bfb41 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java index 86538ae3b1fab..578d97c1c35f8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index 6de9cc59a1648..bdfa0fffa4176 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java index 6cbf861759ca2..4c4f587261b05 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java index d6a9701229c8b..50bf772c4be2b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java index 15d8aae7c5851..8e0c4b0bfe6dd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java index 6d7c694ecd9c5..2f023d9fdfd6c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java index 41aa94301d63e..95cd1323e46e8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java index e7af1f264ae11..eee210db9c5d9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java index c255c3cec268c..7346f2fb70326 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java index 13f232af90140..9f678b54f2b28 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java index 788fcf973c8db..e783df386503f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java index d4c4b6265eaa3..77baec9e9393e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java index e028e693c1bd8..6ad1f0fec3022 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index e11ee27280b54..aea47b6a02fb8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -15,9 +15,8 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.time.Instant; +import java.util.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -26,14 +25,14 @@ import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory.LGThinQGeneralApiClientService; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.core.config.discovery.AbstractDiscoveryService; +import org.openhab.core.config.discovery.*; +import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; -import org.openhab.core.config.discovery.DiscoveryService; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerService; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.ServiceScope; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,49 +41,39 @@ * * @author Nemer Daud - Initial contribution */ +@Component(scope = ServiceScope.PROTOTYPE, service = LGThinqDiscoveryService.class) @NonNullByDefault -public class LGThinqDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService { +public class LGThinqDiscoveryService extends AbstractThingHandlerDiscoveryService { private final Logger logger = LoggerFactory.getLogger(LGThinqDiscoveryService.class); - private @Nullable LGThinQBridgeHandler bridgeHandler; private @Nullable ThingUID bridgeHandlerUID; private @Nullable LGThinQGeneralApiClientService lgApiClientService; public LGThinqDiscoveryService() { - super(SUPPORTED_THING_TYPES, SEARCH_TIME); + super(LGThinQBridgeHandler.class, SUPPORTED_THING_TYPES, SEARCH_TIME); } @Override - protected void startScan() { - logger.debug("Scan started"); - bridgeHandler.runDiscovery(); - } - - @Override - public void setThingHandler(@Nullable ThingHandler handler) { - if (handler instanceof LGThinQBridgeHandler) { - bridgeHandler = (LGThinQBridgeHandler) handler; - bridgeHandlerUID = handler.getThing().getUID(); - lgApiClientService = LGThinQApiClientServiceFactory - .newGeneralApiClientService(bridgeHandler.getHttpClientFactory()); - } - } - - @Override - public @Nullable ThingHandler getThingHandler() { - return bridgeHandler; + public void initialize() { + bridgeHandlerUID = thingHandler.getThing().getUID(); + // thingHandler is the LGThinQBridgeHandler + thingHandler.registerDiscoveryListener(this); + lgApiClientService = LGThinQApiClientServiceFactory + .newGeneralApiClientService(thingHandler.getHttpClientFactory()); + super.initialize(); } @Override - public void activate() { - if (bridgeHandler != null) { - bridgeHandler.registerDiscoveryListener(this); - } + protected void startScan() { + logger.debug("Scan started"); + // thingHandler is the LGThinQBridgeHandler + thingHandler.runDiscovery(); } @Override - public void deactivate() { - ThingHandlerService.super.deactivate(); + protected synchronized void stopScan() { + super.stopScan(); + removeOlderResults(getTimestampOfLastScan(), thingHandler.getThing().getUID()); } public void removeLgDeviceDiscovery(LGDevice device) { @@ -136,25 +125,24 @@ private ThingUID getThingUID(LGDevice device) throws LGThinqException { private ThingTypeUID getThingTypeUID(LGDevice device) throws LGThinqException { // Short switch, but is here because it is going to be increase after new LG Devices were added - switch (device.getDeviceType()) { - case AIR_CONDITIONER: - return THING_TYPE_AIR_CONDITIONER; - case HEAT_PUMP: - return THING_TYPE_HEAT_PUMP; - case WASHERDRYER_MACHINE: - return THING_TYPE_WASHING_MACHINE; - case WASHING_TOWER: - return THING_TYPE_WASHING_TOWER; - case DRYER_TOWER: - return THING_TYPE_DRYER_TOWER; - case DRYER: - return THING_TYPE_DRYER; - case REFRIGERATOR: - return THING_TYPE_FRIDGE; - case DISH_WASHER: - return THING_TYPE_DISHWASHER; - default: + return switch (device.getDeviceType()) { + case AIR_CONDITIONER -> THING_TYPE_AIR_CONDITIONER; + case HEAT_PUMP -> THING_TYPE_HEAT_PUMP; + case WASHERDRYER_MACHINE -> THING_TYPE_WASHING_MACHINE; + case WASHING_TOWER -> THING_TYPE_WASHING_TOWER; + case DRYER_TOWER -> THING_TYPE_DRYER_TOWER; + case DRYER -> THING_TYPE_DRYER; + case REFRIGERATOR -> THING_TYPE_FRIDGE; + case DISH_WASHER -> THING_TYPE_DISHWASHER; + default -> throw new LGThinqException(String.format("device type [%s] not supported", device.getDeviceType())); - } + }; + } + + @Override + public void dispose() { + super.dispose(); + removeOlderResults(Instant.now().toEpochMilli(), bridgeHandlerUID); + thingHandler.unregisterDiscoveryListener(); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java index 5199b3e4a1c84..7910c0d6351b2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java index 58bc52b4189db..b24673cc9d03d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java index bda7a0ae53bda..d358c22c3d3a2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java index 73a257442ac75..e0ad5b656b541 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java index 2becaf759e4bc..1bdbae5a9cc73 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java index 9592dc76331e4..531f602f2ae34 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java index 605863197f174..e2f403d906c79 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java index 3c72f60297373..e6346ad1ff636 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java index 9ebe61308c346..f28d666548cbc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java index 60fcb3304e701..03d018234ea18 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java index 42d3ffcbd7dc1..a844e87b095e6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java index f092ceefbdf2f..a8bf5fea9d2ec 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java index 43f9f1ad4f1ee..7977edfe64926 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 5b52e7cb72690..35e3335559978 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -252,7 +252,7 @@ public String getDeviceId() { public abstract void updateChannelDynStateDescription() throws LGThinqApiException; - public abstract LGThinQApiClientService getLgThinQAPIClientService(); + public abstract LGThinQApiClientService<@NonNull C, @NonNull S> getLgThinQAPIClientService(); protected void createDynSwitchChannel(String channelName, ChannelUID chanelUuid) { if (getCallback() == null) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 898c1fe433e98..2e189839122ad 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java index 2ffa5c7fcbb48..610fa60068b61 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -12,6 +12,7 @@ */ package org.openhab.binding.lgthinq.internal.handler; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; /** @@ -19,12 +20,11 @@ * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public interface LGThinQBridge { void registerDiscoveryListener(LGThinqDiscoveryService listener); void registryListenerThing(LGThinQAbstractDeviceHandler thing); void unRegistryListenerThing(LGThinQAbstractDeviceHandler thing); - - LGThinQAbstractDeviceHandler getThingByDeviceId(String deviceId); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index bc861493a2581..6fdce58d189f9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. @@ -24,6 +24,7 @@ import java.util.concurrent.locks.ReentrantLock; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQBridgeConfiguration; import org.openhab.binding.lgthinq.internal.api.TokenManager; @@ -50,6 +51,7 @@ * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public class LGThinQBridgeHandler extends ConfigStatusBridgeHandler implements LGThinQBridge { private Map lGDeviceRegister = new ConcurrentHashMap<>(); @@ -67,13 +69,13 @@ public class LGThinQBridgeHandler extends ConfigStatusBridgeHandler implements L } } private final Logger logger = LoggerFactory.getLogger(LGThinQBridgeHandler.class); - private LGThinQBridgeConfiguration lgthinqConfig; + private @Nullable LGThinQBridgeConfiguration lgthinqConfig; private TokenManager tokenManager; - private LGThinqDiscoveryService discoveryService; - private LGThinQGeneralApiClientService lgApiClient; + private @Nullable LGThinqDiscoveryService discoveryService; + private @Nullable LGThinQGeneralApiClientService lgApiClient; private @Nullable Future initJob; private @Nullable ScheduledFuture devicePollingJob; - private @NonNull HttpClientFactory httpClientFactory; + private final @NonNull HttpClientFactory httpClientFactory; public LGThinQBridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory) { super(bridge); @@ -94,7 +96,7 @@ public HttpClientFactory getHttpClientFactory() { */ abstract class PollingRunnable implements Runnable { protected final String bridgeName; - protected LGThinQBridgeConfiguration lgthinqConfig; + protected @Nullable LGThinQBridgeConfiguration lgthinqConfig; PollingRunnable(String bridgeName) { this.bridgeName = bridgeName; @@ -167,17 +169,6 @@ public void registerDiscoveryListener(LGThinqDiscoveryService listener) { } } - /** - * Registry the OSGi services used by this Bridge. - * Eventually, the Discovery Service will be activated with this bridge as argument. - * - * @return Services to be registered to OSGi. - */ - @Override - public Collection> getServices() { - return Collections.singleton(LGThinqDiscoveryService.class); - } - @Override public void registryListenerThing(LGThinQAbstractDeviceHandler thing) { if (lGDeviceRegister.get(thing.getDeviceId()) == null) { @@ -195,11 +186,6 @@ public void unRegistryListenerThing(LGThinQAbstractDeviceHandler thing) { lGDeviceRegister.remove(thing.getDeviceId()); } - @Override - public LGThinQAbstractDeviceHandler getThingByDeviceId(String deviceId) { - return lGDeviceRegister.get(deviceId); - } - private LGDevicePollingRunnable lgDevicePollingRunnable; class LGDevicePollingRunnable extends PollingRunnable { @@ -349,4 +335,23 @@ public void runDiscovery() { @Override public void handleCommand(ChannelUID channelUID, Command command) { } + + public boolean unregisterDiscoveryListener() { + if (discoveryService != null) { + discoveryService = null; + return true; + } + + return false; + } + + /** + * Registry the OSGi services used by this Bridge. + * Eventually, the Discovery Service will be activated with this bridge as argument. + * + * @return Services to be registered to OSGi. + */ + public Collection> getServices() { + return Set.of(LGThinqDiscoveryService.class); + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java index e7bd1bad64193..b2ff0a58d6c53 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index 3bf71c21c7624..047c5f75c96fd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index 3bf2893d8fe41..ed8a5092900e2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java index 0212268a464e3..b841aff1583b3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java index b7acaac796966..c6e6161bfc365 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java index 0addfd9b9cb4e..882c76d00fd63 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java index 14cc669161ab2..8546c73857e0c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java index 253816cfdcfe1..fb3691348f02c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java index 1a02ddec4fc76..c1dc92f86b7b3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java index 1323349ed74f6..e1de12cd9036d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java index 5af844701adb8..1f7aff1c33a54 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java index 2c770f35b75b0..9ebfa9466963d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java index 9820b790ccf4e..2eeaf865d1bc6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java index 5c6ed67d3fb80..c36cd7690e6b8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java index 3e1fe08898da5..200d71f1cdb3f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java index b550bb8f54e00..1314ed99bd53f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java index 9d7c70d190735..d8fed543ceb63 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java index 14c77cae72a2d..535f6d00f580e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java index d2237582bca3b..f2c30a97ffc0b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index 657b4af977ff0..c238e397c8d93 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index 7d6053fa99387..2da62d5ade836 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java index fa9629d92f6fd..65ed4bb4e6a95 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index 657abf9dd6458..b23f4902a5456 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java index 16606fc8c1ae1..1d3f64f58db8b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientServiceFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java index 3398a492d4d0e..e1a2dd156a559 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java index 2536d80f9e88a..44a7bbb648060 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java index 73e869ee125ac..74648814b8878 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java index 7d3b599fd58e3..819aa384b8e99 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java index 45931b6ff8716..bce79ebf2af33 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java index f8dbfdd17ea6f..416345debfbd3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java index 475e25ea85669..8eed310e35873 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java index 92ab47033a6db..0255eeddda0a0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java index e539d20a4cb16..29d197c4662ee 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java index 2dc4e447abb8e..3f199d93708b4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java index 14571b16204af..1e09cc4c66a0d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java index 9bf4a3a89b15e..a344efbbd7ba6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java index f71de3eaa2be6..939cfed374276 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java index 45f660d5c7438..bc736bfa265ce 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java index 53ef3f043f45b..7c53ec768bc59 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java index f605beb37ae5d..ed78a93bc5883 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java index 4e505b19f522b..a9f7b5f8c58e8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java index 9a6e51c65f553..9e4251e802d04 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java index 385eca34fcd21..a7780da8ede28 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java index fe295323a49f0..67fc36261b145 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java index 2162f4840b7a7..a1b690dda01e5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java index 4d1d4ef0d26ec..f70bd51650f89 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java index a16f5de3c6d54..67f9f5255c4a7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDefinition.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java index d7f51103a4e77..579f4f797e71b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java index a3aacfd72695b..a0605abb5ff9f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java index e3058ee623fc3..6518a0d1c47ea 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringBinaryProtocol.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringBinaryProtocol.java index 0c5c8903a660c..2a270c91f1a91 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringBinaryProtocol.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringBinaryProtocol.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java index 04e9c004ce8ee..eac43372837f1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java index f7a08d4ec5870..919ebdf2c2d0c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java index 0218eb42692ad..fc47f08266c30 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java index 6db7f5e3a8efc..cae199e527141 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotDefinition.java index 247706a6f16f8..9ed74e876a909 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotDefinition.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java index 87fd9bdb85267..9a305d42adf70 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java index 7fb46e8957d96..e935f375011b7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java index 4554f95c87512..b936930d7fbd8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java index bd378a1fb7e69..88b4d55abc09e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java index 74faa4efcace7..baac299973d12 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java index 28a5ff759e712..1cd632837253c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java index 9661ccda96ab1..f9c65f54b4b10 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACTargetTmp.java index ef752dc851ad3..26f0c9acdfb31 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACTargetTmp.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACTargetTmp.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java index 7ea03f38947a1..4eeedce5d2498 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java index f37ce9a55c7ca..73db5c0237662 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseDefinition.java index 23855480f68f0..fd7df82897a2a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseDefinition.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseFunction.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseFunction.java index 36d1997e50856..02c9ce40276fb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseFunction.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseFunction.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseType.java index a1f5ea071d513..b84a14c428028 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseType.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseType.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java index 2f7edfdb0e32e..c0a9b4a8a6d72 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java index c3f7612601331..2b86fdf3c9762 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java index 31bde31b309e2..9157615ea42b4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapability.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java index 0c73017ed5672..ce10fa50f8468 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java index 066eea2eb096b..234789f1de672 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java index 9d662a7be0237..0a56e876106a1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java index f771617c56b45..0d625b4f5fba1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java index 8025f35c23566..2c83c27af5990 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeSnapshot.java index 2528deb6c13d6..7ae7c18ce582a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeSnapshot.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java index 560cec892d4f7..639643aa31ca7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java index a95bb26242388..be31b4e70e6a0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java index 727e223160ebc..4ebb3fc0dbe5a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapability.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index 519e883c97cf6..f0eb6613941b7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java index 46d50aedec729..8010dec93caa5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java index 6620e22b5fa5a..222a01240eacf 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java index f0b6db1a2ce23..11ca90afa91fb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java index 8859d45672f1e..490da35f6bada 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java index d7080e2e5dabb..94f2af41c8e7f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java index 6b153979dd20b..43c4fe8b38c91 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java index da6a14a7d67e4..dcdba425e44fb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java index 680b1b9eefd20..3f133ddacb0d9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index 93c7b07f87978..b1d243ee49162 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java index f2b125da24018..3937aa480e0b4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java index 03b6207c5f66f..db9d334471937 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java index 0f2d668aef2a3..1e9297adf51c7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2010-2023 Contributors to the openHAB project + * Copyright (c) 2010-2024 Contributors to the openHAB project * * See the NOTICE file(s) distributed with this work for additional * information. From 16c5635c61241454a4fe61fc4fc1ea3aff42f068 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Fri, 28 Jan 2022 19:34:59 -0300 Subject: [PATCH 020/130] [lgthinq] Initial Contribution. Binding to integrate OpenHab to LG Thinq API to control Thinq compatible devices through OpenHab. Signed-off-by: nemerdaud --- .../internal/LGAirConditionerHandler.java | 499 ++++++++++++++++++ .../LGDeviceDynStateDescriptionProvider.java | 39 ++ .../lgthinq/internal/LGDeviceThing.java | 47 ++ .../internal/LGThinqBindingConstants.java | 121 +++++ .../internal/LGThinqConfiguration.java | 86 +++ .../internal/LGThinqHandlerFactory.java | 85 +++ .../binding/lgthinq/internal/api/Gateway.java | 122 +++++ .../internal/errors/LGApiException.java | 31 ++ .../LGDeviceV1MonitorExpiredException.java | 32 ++ .../errors/LGDeviceV1OfflineException.java | 33 ++ .../internal/errors/LGGatewayException.java | 27 + .../lgthinq/internal/handler/LGBridge.java | 31 ++ .../internal/handler/LGBridgeHandler.java | 315 +++++++++++ .../lgthinq/lgapi/LGApiClientService.java | 68 +++ .../lgthinq/lgapi/LGApiClientServiceImpl.java | 173 ++++++ .../lgapi/LGApiV1ClientServiceImpl.java | 330 ++++++++++++ .../lgapi/LGApiV2ClientServiceImpl.java | 274 ++++++++++ .../lgthinq/lgapi/model/ACCapability.java | 66 +++ .../lgthinq/lgapi/model/ACFanSpeed.java | 91 ++++ .../binding/lgthinq/lgapi/model/ACOpMode.java | 89 ++++ .../lgthinq/lgapi/model/ACSnapShot.java | 109 ++++ .../lgthinq/lgapi/model/ACSnapShotV1.java | 58 ++ .../lgthinq/lgapi/model/ACSnapShotV2.java | 58 ++ .../lgthinq/lgapi/model/ACTargetTmp.java | 93 ++++ .../lgthinq/lgapi/model/DevicePowerState.java | 71 +++ .../lgthinq/lgapi/model/DeviceTypes.java | 42 ++ .../binding/lgthinq/lgapi/model/LGDevice.java | 107 ++++ .../lgthinq/handler/LGBridgeTests.java | 313 +++++++++++ 28 files changed, 3410 insertions(+) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java new file mode 100644 index 0000000000000..7ceeb3c631450 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java @@ -0,0 +1,499 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + +import java.util.*; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; +import org.openhab.binding.lgthinq.lgapi.LGApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.LGApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGAirConditionerHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGAirConditionerHandler extends BaseThingHandler implements LGDeviceThing { + public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner + + public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); + private final LGDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final ChannelUID opModeChannelUID; + private final ChannelUID opModeFanSpeedUID; + @Nullable + private ACCapability acCapability; + private final String lgPlatfomType; + private final Logger logger = LoggerFactory.getLogger(LGAirConditionerHandler.class); + @NonNullByDefault + private final LGApiClientService lgApiClientService; + private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; + // Bridges status that this thing must top scanning for state change + private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, + ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + ThingStatusDetail.CONFIGURATION_ERROR); + private static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); + private @Nullable ScheduledFuture thingStatePoolingJob; + private @Nullable Future commandExecutorQueueJob; + // *** Long running isolated threadpools. + private final ScheduledExecutorService poolingScheduler = Executors.newScheduledThreadPool(1); + private final ExecutorService executorService = Executors.newFixedThreadPool(1); + + private boolean monitorV1Began = false; + private String monitorWorkId = ""; + private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); + @NonNullByDefault + private String bridgeId = ""; + + public LGAirConditionerHandler(Thing thing, LGDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing); + this.stateDescriptionProvider = stateDescriptionProvider; + lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); + lgApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGApiV1ClientServiceImpl.getInstance() + : LGApiV2ClientServiceImpl.getInstance(); + opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); + opModeFanSpeedUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); + } + + static class AsyncCommandParams { + final String channelUID; + final Command command; + + public AsyncCommandParams(String channelUUID, Command command) { + this.channelUID = channelUUID; + this.command = command; + } + } + + @Override + public Collection> getServices() { + return super.getServices(); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + logger.debug("bridgeStatusChanged {}", bridgeStatusInfo); + initializeThing(bridgeStatusInfo.getStatus()); + } + + private void initializeThing(@Nullable ThingStatus bridgeStatus) { + logger.debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); + String deviceId = getThing().getUID().getId(); + if (!SUPPORTED_LG_PLATFORMS.contains(lgPlatfomType)) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "LG Platform [" + lgPlatfomType + "] not supported for this thing"); + return; + } + Bridge bridge = getBridge(); + if (!deviceId.isBlank()) { + try { + updateChannelDynStateDescription(); + } catch (LGApiException e) { + logger.error( + "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); + } + if (bridge != null) { + LGBridgeHandler handler = (LGBridgeHandler) bridge.getHandler(); + // registry this thing to the bridge + if (handler == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } else { + handler.registryListenerThing(this); + if (bridgeStatus == ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error-no-device-id"); + } + // finally, start command queue, regardless os the thing state, as we can still try to send commands without + // property ONLINE (the successful result from command request can put the thing in ONLINE status). + startCommandExecutorQueueJob(); + } + + private void startCommandExecutorQueueJob() { + if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { + commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); + } + } + + private ExecutorService getExecutorService() { + return executorService; + } + + private void stopCommandExecutorQueueJob() { + if (commandExecutorQueueJob != null) { + commandExecutorQueueJob.cancel(true); + } + } + + protected void startThingStatePooling() { + if (thingStatePoolingJob == null || thingStatePoolingJob.isDone()) { + thingStatePoolingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, + DEFAULT_STATE_POOLING_UPDATE_DELAY, TimeUnit.SECONDS); + } + } + + private void updateThingStateFromLG() { + try { + ACSnapShot shot = getSnapshotDeviceAdapter(getDeviceId()); + if (shot == null) { + // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. + return; + } + if (!shot.isOnline()) { + if (getThing().getStatus() != ThingStatus.OFFLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); + updateState(CHANNEL_POWER_ID, + OnOffType.from(shot.getAcPowerStatus() == DevicePowerState.DV_POWER_OFF)); + } + return; + } + if (shot.getOperationMode() != null) { + updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); + } + if (shot.getAcPowerStatus() != null) { + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getAcPowerStatus() == DevicePowerState.DV_POWER_ON)); + // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as + // soon as LG WebOs do) + } + if (shot.getAcFanSpeed() != null) { + updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(shot.getAirWindStrength())); + } + if (shot.getCurrentTemperature() != null) { + updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(shot.getCurrentTemperature())); + } + if (shot.getTargetTemperature() != null) { + updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); + } + updateStatus(ThingStatus.ONLINE); + } catch (LGThinqException e) { + logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", + getDeviceAlias(), getDeviceId(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + + private ScheduledExecutorService getLocalScheduler() { + return poolingScheduler; + } + + private String getBridgeId() { + if (bridgeId.isBlank() && getBridge() == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + logger.error("Configuration error um Thinq Thing - No Bridge defined for the thing."); + return "UNKNOWN"; + } else if (bridgeId.isBlank() && getBridge() != null) { + bridgeId = getBridge().getUID().getId(); + } + return bridgeId; + } + + private void forceStopDeviceV1Monitor(String deviceId) { + try { + monitorV1Began = false; + lgApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + } catch (Exception e) { + logger.error("Error stopping LG Device monitor", e); + } + } + + @NonNull + private String emptyIfNull(@Nullable String value) { + return value == null ? "" : "" + value; + } + + @Override + public void updateChannelDynStateDescription() throws LGApiException { + ACCapability acCap = getAcCapabilities(); + if (isLinked(opModeChannelUID)) { + List options = new ArrayList<>(); + acCap.getSupportedOpMode().forEach((v) -> options + .add(new StateOption(emptyIfNull(acCap.getOpMod().get(v)), emptyIfNull(CAP_OP_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(opModeChannelUID, options); + } + if (isLinked(opModeFanSpeedUID)) { + List options = new ArrayList<>(); + acCap.getSupportedFanSpeed().forEach((v) -> options + .add(new StateOption(emptyIfNull(acCap.getFanSpeed().get(v)), emptyIfNull(CAP_FAN_SPEED.get(v))))); + stateDescriptionProvider.setStateOptions(opModeFanSpeedUID, options); + } + } + + @Override + public ACCapability getAcCapabilities() throws LGApiException { + if (acCapability == null) { + acCapability = lgApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); + } + return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); + } + + @Nullable + private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiException { + // analise de platform version + if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { + return lgApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); + } else { + try { + if (!monitorV1Began) { + monitorWorkId = lgApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorV1Began = true; + } + } catch (LGDeviceV1OfflineException e) { + forceStopDeviceV1Monitor(deviceId); + ACSnapShot shot = new ACSnapShotV1(); + shot.setOnline(false); + return shot; + } catch (Exception e) { + forceStopDeviceV1Monitor(deviceId); + throw new LGApiException("Error starting device monitor in LG API for the device:" + deviceId, e); + } + int retries = 10; + ACSnapShot shot; + while (retries > 0) { + // try to get monitoring data result 3 times. + try { + shot = lgApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + if (shot != null) { + return shot; + } + Thread.sleep(500); + retries--; + } catch (LGDeviceV1MonitorExpiredException e) { + forceStopDeviceV1Monitor(deviceId); + logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); + return null; + } catch (Exception e) { + // If it can't get monitor handler, then stop monitor and restart the process again in new + // interaction + // Force restart monitoring because of the errors returned (just in case) + forceStopDeviceV1Monitor(deviceId); + throw new LGApiException("Error getting monitor data for the device:" + deviceId, e); + } + } + forceStopDeviceV1Monitor(deviceId); + throw new LGApiException("Exhausted trying to get monitor data for the device:" + deviceId); + } + } + + protected void stopThingStatePooling() { + if (thingStatePoolingJob != null && !thingStatePoolingJob.isDone()) { + logger.debug("Stopping LG thinq pooling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePoolingJob.cancel(true); + } + } + + private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { + if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { + // start the thing pooling + startThingStatePooling(); + } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE + && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { + // comunication error is not a specific Bridge error, then we must analise it to give + // this thinq the change to recovery from communication errors + if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR + || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { + // in case of status offline, I only stop the pooling if is not an COMMUNICATION_ERROR or if + // the bridge is out + stopThingStatePooling(); + } + + } + lastThingStatus = newStatus; + } + + @Override + protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { + handleStatusChanged(newStatus, statusDetail); + super.updateStatus(newStatus, statusDetail, description); + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it + } + + @Override + public String getDeviceId() { + return getThing().getUID().getId(); + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceModelName() { + return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public boolean onDeviceStateChanged() { + // TODO - HANDLE IT + return false; + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT + } + + @Override + public void onDeviceGone() { + // TODO - HANDLE IT + } + + @Override + public void dispose() { + if (thingStatePoolingJob != null) { + thingStatePoolingJob.cancel(true); + stopThingStatePooling(); + stopCommandExecutorQueueJob(); + thingStatePoolingJob = null; + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + updateThingStateFromLG(); + } else { + AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); + try { + // Ensure commands are send in a pipe per device. + commandBlockQueue.add(params); + } catch (IllegalStateException ex) { + logger.error( + "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, + "Device Command Queue is Busy"); + } + + } + } + + private final Runnable queuedCommandExecutor = new Runnable() { + @Override + public void run() { + while (true) { + AsyncCommandParams params; + try { + params = commandBlockQueue.take(); + } catch (InterruptedException e) { + logger.debug("Interrupting async command queue executor."); + return; + } + Command command = params.command; + + try { + switch (params.channelUID) { + case CHANNEL_MOD_OP_ID: { + if (params.command instanceof DecimalType) { + lgApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); + } + break; + } + case CHANNEL_FAN_SPEED_ID: { + if (command instanceof DecimalType) { + lgApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); + } + break; + } + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + case CHANNEL_TARGET_TEMP_ID: { + double targetTemp; + if (command instanceof DecimalType) { + targetTemp = ((DecimalType) command).doubleValue(); + } else if (command instanceof QuantityType) { + targetTemp = ((QuantityType) command).doubleValue(); + } else { + logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); + break; + } + lgApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + ACTargetTmp.statusOf(targetTemp)); + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, + params.channelUID); + } + } + } catch (LGThinqException e) { + logger.error("Error executing Command {} to the channel {}. Thing goes offline until retry", + command, params.channelUID, e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + } + }; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java new file mode 100644 index 0000000000000..a74b5c73c9ce8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import org.openhab.core.events.EventPublisher; +import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.thing.type.DynamicStateDescriptionProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link LGDeviceThing} is a main interface contract for all LG Thinq things + * + * @author Nemer Daud - Initial contribution + */ +@Component(service = { DynamicStateDescriptionProvider.class, LGDeviceDynStateDescriptionProvider.class }) +public class LGDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { + @Activate + public LGDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // + final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // + final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { + this.eventPublisher = eventPublisher; + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java new file mode 100644 index 0000000000000..54cd67361e071 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.lgapi.model.ACCapability; +import org.openhab.binding.lgthinq.lgapi.model.LGDevice; + +/** + * The {@link LGDeviceThing} is a main interface contract for all LG Thinq things + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGDeviceThing { + + void onDeviceAdded(@NonNullByDefault LGDevice device); + + String getDeviceId(); + + String getDeviceAlias(); + + String getDeviceModelName(); + + String getDeviceUriJsonConfig(); + + boolean onDeviceStateChanged(); + + void onDeviceRemoved(); + + void onDeviceGone(); + + void updateChannelDynStateDescription() throws LGApiException; + + ACCapability getAcCapabilities() throws LGApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java new file mode 100644 index 0000000000000..5c2767108f97c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import java.io.File; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.OpenHAB; + +/** + * The {@link LGThinqBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqBindingConstants { + + public static final String BINDING_ID = "lgthinq"; + + public static final String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; + public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; + public static String BASE_CAP_CONFIG_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-cap.json"; + public static final String V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; + public static final String V2_USER_INFO = "/users/profile"; + public static final String V2_API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; + public static final String V2_CLIENT_ID = "65260af7e8e6547b51fdccf930097c51eb9885a508d3fddfa9ee6cdec22ae1bd"; + public static final String V2_SVC_PHASE = "OP"; + public static final String V2_APP_LEVEL = "PRD"; + public static final String V2_APP_OS = "LINUX"; + public static final String V2_APP_TYPE = "NUTS"; + public static final String V2_APP_VER = "3.0.1700"; + public static final String V2_SESSION_LOGIN_PATH = "/emp/v2.0/account/session/"; + public static final String V2_LS_PATH = "/service/application/dashboard"; + public static final String V2_DEVICE_CONFIG_PATH = "service/devices/"; + public static final String V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/control-sync"; + public static final String V1_START_MON_PATH = "rti/rtiMon"; + public static final String V1_POOL_MON_PATH = "rti/rtiResult"; + public static final String V1_CONTROL_OP = "rti/rtiControl"; + public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; + public static final String GATEWAY_SERVICE_PATH = "/v1/service/application/gateway-uri"; + public static String GATEWAY_URL = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH; + public static final String PRE_LOGIN_PATH = "/preLogin"; + public static final String SECURITY_KEY = "nuts_securitykey"; + public static final String APP_KEY = "wideq"; + public static final String DATA_ROOT = "result"; + public static final String POST_DATA_ROOT = "lgedmRoot"; + public static final String RETURN_CODE_ROOT = "resultCode"; + public static final String RETURN_MESSAGE_ROOT = "returnMsg"; + public static final String SVC_CODE = "SVC202"; + public static final String OAUTH_SECRET_KEY = "c053c2a6ddeb7ad97cb0eed0dcb31cf8"; + public static final String OAUTH_CLIENT_KEY = "LGAO722A02"; + public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss +0000"; + public static final String DEFAULT_COUNTRY = "US"; + public static final String DEFAULT_LANGUAGE = "en-US"; + public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; + public static String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com/emp/oauth2/token/empsession"; + // v2 + public static final String API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; + + // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, + // and the build id of the thinq app it can also just be a random + // string, we use the same client id used for oauth + public static final String CLIENT_ID = "LGAO221A02"; + public static final String MESSAGE_ID = "wideq"; + public static final String SVC_PHASE = "OP"; + public static final String APP_LEVEL = "PRD"; + public static final String APP_OS = "ANDROID"; + public static final String APP_TYPE = "NUTS"; + public static final String APP_VER = "3.5.1200"; + + public static final String DEVICE_ID = "device_id"; + public static final String MODEL_NAME = "model_name"; + public static final String DEVICE_ALIAS = "device_alias"; + public static final String MODEL_URL_INFO = "model_url_indo"; + public static final String PLATFORM_TYPE = "platform_type"; + public static final String PLATFORM_TYPE_V1 = "thinq1"; + public static final String PLATFORM_TYPE_V2 = "thinq2"; + + public static final int SEARCH_TIME = 20; + // delay between each devices's scan for state changes (in seconds) + public static final int DEFAULT_STATE_POOLING_UPDATE_DELAY = 30; + // CHANNEL IDS + public static final String CHANNEL_MOD_OP_ID = "op_mode"; + public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; + public static final String CHANNEL_POWER_ID = "power"; + public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; + public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; + + public static final Map CAP_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", + "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", + "@AC_MAIN_OPERATION_MODE_HEAT_W", "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", + "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", "@AC_MAIN_OPERATION_MODE_AI_W", "AI", + "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", + "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); + + public static final Map CAP_FAN_SPEED = Map.ofEntries( + Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), + Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), Map.entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), + Map.entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), Map.entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Nature"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java new file mode 100644 index 0000000000000..c3a618b7dae5e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqConfiguration { + /** + * Sample configuration parameters. Replace with your own. + */ + public String username = ""; + public String password = ""; + public String country = ""; + public String language = ""; + public Integer poolingIntervalSec = 0; + + public LGThinqConfiguration() { + } + + public LGThinqConfiguration(String username, String password, String country, String language, + Integer poolingIntervalSec) { + this.username = username; + this.password = password; + this.country = country; + this.language = language; + this.poolingIntervalSec = poolingIntervalSec; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getCountry() { + return country; + } + + public String getLanguage() { + return language; + } + + public Integer getPoolingIntervalSec() { + return poolingIntervalSec; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setCountry(String country) { + this.country = country; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setPoolingIntervalSec(Integer poolingIntervalSec) { + this.poolingIntervalSec = poolingIntervalSec; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java new file mode 100644 index 0000000000000..803d80a477971 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGAirConditionerHandler.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler.THING_TYPE_BRIDGE; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinqHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.lgthinq") +public class LGThinqHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, + THING_TYPE_BRIDGE); + private final Logger logger = LoggerFactory.getLogger(LGThinqHandlerFactory.class); + private final LGDeviceDynStateDescriptionProvider stateDescriptionProvider; + + @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_AIR_CONDITIONER.equals(thingTypeUID)) { + return new LGAirConditionerHandler(thing, stateDescriptionProvider); + } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return new LGBridgeHandler((Bridge) thing); + } + logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); + return null; + } + + @Override + public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, + @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { + if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, null); + } else if (LGAirConditionerHandler.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } + return null; + } + + @Activate + public LGThinqHandlerFactory(final @Reference LGDeviceDynStateDescriptionProvider stateDescriptionProvider) { + this.stateDescriptionProvider = stateDescriptionProvider; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java new file mode 100644 index 0000000000000..64c5535923928 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.api; + +import java.io.Serializable; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link Gateway} hold informations about the LG Gateway + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class Gateway implements Serializable { + private String empBaseUri = ""; + private String loginBaseUri = ""; + private String apiRootV1 = ""; + private String apiRootV2 = ""; + private String authBase = ""; + private String language = ""; + private String country = ""; + private String username = ""; + private String password = ""; + + public Gateway() { + } + + public Gateway(Map params, String language, String country) { + this.apiRootV2 = Objects.requireNonNullElse(params.get("thinq2Uri"), ""); + this.apiRootV1 = Objects.requireNonNullElse(params.get("thinq1Uri"), ""); + this.loginBaseUri = Objects.requireNonNullElse(params.get("empSpxUri"), ""); + this.authBase = Objects.requireNonNullElse(params.get("empUri"), ""); + this.empBaseUri = Objects.requireNonNullElse(params.get("empTermsUri"), ""); + this.language = language; + this.country = country; + } + + public String getEmpBaseUri() { + return empBaseUri; + } + + public String getApiRootV2() { + return apiRootV2; + } + + public String getAuthBase() { + return authBase; + } + + public String getLanguage() { + return language; + } + + public String getCountry() { + return country; + } + + public String getLoginBaseUri() { + return loginBaseUri; + } + + public String getApiRootV1() { + return apiRootV1; + } + + public void setEmpBaseUri(String empBaseUri) { + this.empBaseUri = empBaseUri; + } + + public void setLoginBaseUri(String loginBaseUri) { + this.loginBaseUri = loginBaseUri; + } + + public void setApiRootV1(String apiRootV1) { + this.apiRootV1 = apiRootV1; + } + + public void setApiRootV2(String apiRootV2) { + this.apiRootV2 = apiRootV2; + } + + public void setAuthBase(String authBase) { + this.authBase = authBase; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java new file mode 100644 index 0000000000000..95ca53e421209 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGApiException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGApiException extends LGThinqException { + public LGApiException(String message, Throwable cause) { + super(message, cause); + } + + public LGApiException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java new file mode 100644 index 0000000000000..3272c29338952 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGDeviceV1MonitorExpiredException} - Normally caught by V1 API in monitoring device. + * After long-running moniotor, it indicates the need to refresh the monitor. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGDeviceV1MonitorExpiredException extends LGThinqException { + public LGDeviceV1MonitorExpiredException(String message, Throwable cause) { + super(message, cause); + } + + public LGDeviceV1MonitorExpiredException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java new file mode 100644 index 0000000000000..dee34280a8c21 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGDeviceV1OfflineException} - Normally caught by V1 API in monitoring device. + * When the device is OFFLINE (away from internet), the API doesn't return data information and this + * exception is thrown to indicate that this device is offline for monitoring + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGDeviceV1OfflineException extends LGThinqException { + public LGDeviceV1OfflineException(String message, Throwable cause) { + super(message, cause); + } + + public LGDeviceV1OfflineException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java new file mode 100644 index 0000000000000..20942017c4a1a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGGatewayException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGGatewayException extends LGThinqException { + public LGGatewayException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java new file mode 100644 index 0000000000000..d9f8f36ab93f3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.handler; + +import org.openhab.binding.lgthinq.internal.LGDeviceThing; +import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; + +/** + * The {@link LGBridge} + * + * @author Nemer Daud - Initial contribution + */ +public interface LGBridge { + void registerDiscoveryListener(LGThinqDiscoveryService listener); + + void registryListenerThing(LGDeviceThing thing); + + void unRegistryListenerThing(LGDeviceThing thing); + + LGDeviceThing getThingByDeviceId(String deviceId); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java new file mode 100644 index 0000000000000..d6a40ab443285 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java @@ -0,0 +1,315 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGDeviceThing; +import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; +import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgapi.LGApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGBridgeHandler} + * + * @author Nemer Daud - Initial contribution + */ +public class LGBridgeHandler extends ConfigStatusBridgeHandler implements LGBridge { + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(LGThinqBindingConstants.BINDING_ID, "bridge"); + + private Map lGDeviceRegister = new ConcurrentHashMap<>(); + private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); + + static { + var logger = LoggerFactory.getLogger(LGBridgeHandler.class); + try { + File directory = new File(THINQ_USER_DATA_FOLDER); + if (!directory.exists()) { + directory.mkdir(); + } + } catch (Exception e) { + logger.warn("Unable to setup thinq userdata directory: {}", e.getMessage()); + } + } + private final Logger logger = LoggerFactory.getLogger(LGBridgeHandler.class); + private LGThinqConfiguration lgthinqConfig; + private TokenManager tokenManager; + private LGThinqDiscoveryService discoveryService; + private LGApiClientService lgApiClient; + private @Nullable Future initJob; + private @Nullable ScheduledFuture devicePollingJob; + + public LGBridgeHandler(Bridge bridge) { + super(bridge); + tokenManager = TokenManager.getInstance(); + lgApiClient = LGApiV1ClientServiceImpl.getInstance(); + lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); + } + + final ReentrantLock pollingLock = new ReentrantLock(); + + /** + * Abstract Runnable Pooling Class to schedule sincronization status of the Bridge Thing Kinds ! + */ + abstract class PollingRunnable implements Runnable { + protected final String bridgeName; + protected LGThinqConfiguration lgthinqConfig; + + PollingRunnable(String bridgeName) { + this.bridgeName = bridgeName; + } + + @Override + public void run() { + try { + pollingLock.lock(); + // check if configuration file already exists + if (tokenManager.isOauthTokenRegistered(bridgeName)) { + logger.debug( + "Token authentication process has been already done. Skip first authentication process."); + try { + tokenManager.getValidRegisteredToken(bridgeName); + } catch (IOException e) { + logger.error("Error reading LGThinq TokenFile", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, + "@text/error.toke-file-corrupted"); + return; + } catch (RefreshTokenException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, + "@text/error.toke-refresh"); + return; + } + } else { + try { + tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), + lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword()); + if (tokenManager.getValidRegisteredToken(bridgeName) != null) { + logger.debug("Successful getting token from LG API"); + } + } catch (IOException e) { + logger.debug( + "I/O error accessing json token configuration file. Updating Bridge Status to OFFLINE.", + e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.toke-file-access-error"); + return; + } catch (LGThinqException e) { + logger.debug("Error accessing LG API. Updating Bridge Status to OFFLINE.", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/error.lgapi-communication-error"); + return; + } + } + if (thing.getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } + + try { + doConnectedRun(); + } catch (Exception e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.lgapi-getting-devices"); + } + + } finally { + pollingLock.unlock(); + } + } + + protected abstract void doConnectedRun() throws IOException, LGThinqException; + } + + @Override + public void registerDiscoveryListener(LGThinqDiscoveryService listener) { + if (discoveryService == null) { + discoveryService = listener; + } + } + + /** + * Registry the OSGi services used by this Bridge. + * Eventually, the Discovery Service will be activated with this bridge as argument. + * + * @return Services to be registered to OSGi. + */ + @Override + public Collection> getServices() { + return Collections.singleton(LGThinqDiscoveryService.class); + } + + @Override + public void registryListenerThing(LGDeviceThing thing) { + if (lGDeviceRegister.get(thing.getDeviceId()) == null) { + lGDeviceRegister.put(thing.getDeviceId(), thing); + // remove device from discovery list, if exists. + LGDevice device = lastDevicesDiscovered.get(thing.getDeviceId()); + if (device != null) { + discoveryService.removeLgDeviceDiscovery(device); + } + } + } + + @Override + public void unRegistryListenerThing(LGDeviceThing thing) { + lGDeviceRegister.remove(thing.getDeviceId()); + } + + @Override + public LGDeviceThing getThingByDeviceId(String deviceId) { + return lGDeviceRegister.get(deviceId); + } + + private LGDevicePollingRunnable lgDevicePollingRunnable; + + class LGDevicePollingRunnable extends PollingRunnable { + public LGDevicePollingRunnable(String bridgeName) { + super(bridgeName); + } + + @Override + protected void doConnectedRun() throws LGThinqException { + Map lastDevicesDiscoveredCopy = new HashMap<>(lastDevicesDiscovered); + for (final LGDevice device : lgApiClient.listAccountDevices(bridgeName)) { + String deviceId = device.getDeviceId(); + + if (lGDeviceRegister.get(deviceId) == null) { + logger.debug("Adding new LG Device to things registry with id:{}", deviceId); + if (discoveryService != null) { + discoveryService.addLgDeviceDiscovery(bridgeName, device); + } + } + lastDevicesDiscovered.put(deviceId, device); + lastDevicesDiscoveredCopy.remove(deviceId); + } + // the rest in lastDevicesDiscoveredCopy is not more registered in LG API. Remove from discovery + lastDevicesDiscoveredCopy.forEach((deviceId, device) -> { + logger.trace("LG Device '{}' removed.", deviceId); + lastDevicesDiscovered.remove(deviceId); + + LGDeviceThing deviceThing = lGDeviceRegister.get(deviceId); + if (deviceThing != null) { + deviceThing.onDeviceRemoved(); + } + if (discoveryService != null && deviceThing != null) { + discoveryService.removeLgDeviceDiscovery(device); + } + }); + } + }; + + @Override + public Collection getConfigStatus() { + List resultList = new ArrayList<>(); + if (lgthinqConfig.username.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("USERNAME").withMessageKeySuffix("missing field") + .withArguments("username").build()); + } + if (lgthinqConfig.password.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("PASSWORD").withMessageKeySuffix("missing field") + .withArguments("password").build()); + } + if (lgthinqConfig.language.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("LANGUAGE").withMessageKeySuffix("missing field") + .withArguments("language").build()); + } + if (lgthinqConfig.country.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("COUNTRY").withMessageKeySuffix("missing field") + .withArguments("country").build()); + + } + return resultList; + } + + @Override + public void handleRemoval() { + if (devicePollingJob != null) + devicePollingJob.cancel(true); + tokenManager.cleanupTokenRegistry(getBridge().getUID().getId()); + super.handleRemoval(); + } + + @Override + public void dispose() { + if (devicePollingJob != null) { + devicePollingJob.cancel(true); + devicePollingJob = null; + } + } + + @Override + public T getConfigAs(Class configurationClass) { + return super.getConfigAs(configurationClass); + } + + @Override + public void initialize() { + logger.debug("Initializing LGThinq bridge handler."); + lgthinqConfig = getConfigAs(LGThinqConfiguration.class); + lgDevicePollingRunnable.lgthinqConfig = lgthinqConfig; + + if (lgthinqConfig.username.isEmpty() || lgthinqConfig.password.isEmpty() || lgthinqConfig.language.isEmpty() + || lgthinqConfig.country.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.mandotory-fields-missing"); + } else { + updateStatus(ThingStatus.UNKNOWN); + startLGDevicePolling(); + } + } + + private void startLGDevicePolling() { + // stop current scheduler, if any + if (devicePollingJob != null && !devicePollingJob.isDone()) { + devicePollingJob.cancel(true); + } + long pollingInterval; + int configPollingInterval = lgthinqConfig.getPoolingIntervalSec(); + // It's not recommended to pool for resources in LG API short intervals to do not enter in BlackList + if (configPollingInterval < 300) { + pollingInterval = TimeUnit.SECONDS.toSeconds(300); + logger.info("Wrong configuration value for polling interval. Using default value: {}s", pollingInterval); + } else { + pollingInterval = configPollingInterval; + } + // submit instantlly and schedule for the next pooling interval. + scheduler.submit(lgDevicePollingRunnable); + devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, pollingInterval, pollingInterval, + TimeUnit.SECONDS); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java new file mode 100644 index 0000000000000..38fa1d503026f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgapi.model.*; + +/** + * The {@link LGApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGApiClientService { + + List listAccountDevices(String bridgeName) throws LGApiException; + + Map getDeviceSettings(String bridgeName, String deviceId) throws LGApiException; + + /** + * Retrieve actual data from device (its sensors and points states). + * + * @param deviceId device number + * @return return snapshot state of the device + * @throws LGApiException if some error interacting with LG API Server occur. + */ + @Nullable + ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException; + + void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGApiException; + + void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException; + + void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException; + + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGApiException; + + String startMonitor(String bridgeName, String deviceId) + throws LGApiException, LGDeviceV1OfflineException, IOException; + + ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException; + + void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; + + @Nullable + ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) + throws LGApiException, LGDeviceV1MonitorExpiredException, IOException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java new file mode 100644 index 0000000000000..c048f707bd6f5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.lgapi.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link LGApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class LGApiClientServiceImpl implements LGApiClientService { + private static final Logger logger = LoggerFactory.getLogger(LGApiClientServiceImpl.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + + protected abstract TokenManager getTokenManager(); + + static Map getCommonHeaders(String language, String country, String accessToken, + String userNumber) { + Map headers = new HashMap<>(); + headers.put("Accept", "application/json"); + headers.put("Content-type", "application/json;charset=UTF-8"); + headers.put("x-api-key", V2_API_KEY); + headers.put("x-client-id", V2_CLIENT_ID); + headers.put("x-country-code", country); + headers.put("x-language-code", language); + headers.put("x-message-id", UUID.randomUUID().toString()); + headers.put("x-service-code", SVC_CODE); + headers.put("x-service-phase", V2_SVC_PHASE); + headers.put("x-thinq-app-level", V2_APP_LEVEL); + headers.put("x-thinq-app-os", V2_APP_OS); + headers.put("x-thinq-app-type", V2_APP_TYPE); + headers.put("x-thinq-app-ver", V2_APP_VER); + headers.put("x-thinq-security-key", SECURITY_KEY); + if (!accessToken.isBlank()) + headers.put("x-emp-token", accessToken); + if (!userNumber.isBlank()) + headers.put("x-user-no", userNumber); + return headers; + } + + /** + * Even using V2 URL, this endpoint support grab informations about account devices from V1 and V2. + * + * @return list os LG Devices. + * @throws LGApiException if some communication error occur. + */ + @Override + public List listAccountDevices(String bridgeName) throws LGApiException { + try { + TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); + return handleListAccountDevicesResult(resp); + } catch (Exception e) { + throw new LGApiException("Erros list account devices from LG Server API", e); + } + } + + /** + * Get device settings and snapshot for a specific device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGApiException if some communication error occur. + */ + @Override + public Map getDeviceSettings(String bridgeName, String deviceId) throws LGApiException { + try { + TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) + .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); + return handleDeviceSettingsResult(resp); + } catch (Exception e) { + throw new LGApiException("Erros list account devices from LG Server API", e); + } + } + + private Map handleDeviceSettingsResult(RestResult resp) throws LGApiException { + return genericHandleDeviceSettingsResult(resp, logger, objectMapper); + } + + @SuppressWarnings("unchecked") + static Map genericHandleDeviceSettingsResult(RestResult resp, Logger logger, + ObjectMapper objectMapper) throws LGApiException { + Map deviceSettings; + if (resp.getStatusCode() != 200) { + logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGApiException(String.format("Error calling device settings from LG Server API. The reason is:%s", + resp.getJsonResponse())); + } else { + try { + deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + if (!"0000".equals(deviceSettings.get("resultCode"))) { + throw new LGApiException( + String.format("Status error getting device list. resultCode must be 0000, but was:%s", + deviceSettings.get("resultCode"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + + } + return Objects.requireNonNull((Map) deviceSettings.get("result"), + "Unexpected json result asking for Device Settings. Node 'result' no present"); + } + + @SuppressWarnings("unchecked") + private List handleListAccountDevicesResult(RestResult resp) throws LGApiException { + Map devicesResult; + List devices; + if (resp.getStatusCode() != 200) { + logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGApiException(String.format("Error calling device list from LG Server API. The reason is:%s", + resp.getJsonResponse())); + } else { + try { + devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + if (!"0000".equals(devicesResult.get("resultCode"))) { + throw new LGApiException( + String.format("Status error getting device list. resultCode must be 0000, but was:%s", + devicesResult.get("resultCode"))); + } + List> items = (List>) ((Map) devicesResult + .get("result")).get("item"); + devices = objectMapper.convertValue(items, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream.", e); + } + + } + + return devices; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java new file mode 100644 index 0000000000000..628dddcb7d34e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java @@ -0,0 +1,330 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgapi.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link LGApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGApiV1ClientServiceImpl extends LGApiClientServiceImpl { + private static final LGApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGApiV1ClientServiceImpl.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + private final TokenManager tokenManager; + + static { + instance = new LGApiV1ClientServiceImpl(); + } + + private LGApiV1ClientServiceImpl() { + tokenManager = TokenManager.getInstance(); + } + + public static LGApiClientService getInstance() { + return instance; + } + + @Override + protected TokenManager getTokenManager() { + return tokenManager; + } + + /** + * Get snapshot data from the device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGApiException if some communication error occur. + */ + @Override + @Nullable + public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException { + throw new UnsupportedOperationException("Method not supported in V1 API device."); + } + + public RestResult sendControlCommands(String bridgeName, String deviceId, String keyName, int value) + throws Exception { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + + String payload = String.format( + "{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"Control\"," + " \"cmdOpt\": \"Set\"," + + " \"value\": {\"%s\": \"%d\"}," + " \"deviceId\": \"%s\"," + + " \"workId\": \"%s\"," + " \"data\": \"\"" + " }\n" + "}", + keyName, value, deviceId, UUID.randomUUID().toString()); + return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", newPowerState.commandValue()); + handleV1GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting device power", e); + } + } + + @Override + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "OpMode", newOpMode); + + handleV1GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "WindStrength", newFanSpeed); + + handleV1GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting fan speed", e); + } + } + + @Override + public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "TempCfg", newTargetTemp.commandValue()); + + handleV1GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting target temperature", e); + } + } + + /** + * Start monitor data form specific device. This is old one, works only on V1 API supported devices. + * + * @param deviceId Device ID + * @return Work1 to be uses to grab data during monitoring. + * @throws LGApiException If some communication error occur. + */ + @Override + public String startMonitor(String bridgeName, String deviceId) + throws LGApiException, LGDeviceV1OfflineException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String workerId = UUID.randomUUID().toString(); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + return Objects.requireNonNull((String) handleV1GenericErrorResult(resp).get("workId"), + "Unexpected StartMonitor json result. Node 'workId' not present"); + } + + @NonNull + private Map handleV1GenericErrorResult(@Nullable RestResult resp) + throws LGApiException, LGDeviceV1OfflineException { + Map metaResult; + Map envelope = Collections.emptyMap(); + if (resp == null) { + return envelope; + } + if (resp.getStatusCode() != 200) { + logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGApiException( + String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + envelope = (Map) metaResult.get("lgedmRoot"); + if (envelope == null) { + throw new LGApiException(String.format( + "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); + } else if (!"0000".equals(envelope.get("returnCd"))) { + if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { + // Disconnected Device + throw new LGDeviceV1OfflineException("Device is offline. No data available"); + } + throw new LGApiException( + String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", + metaResult.get("returnCd"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + } + return envelope; + } + + @Override + public void stopMonitor(String bridgeName, String deviceId, String workId) + throws LGApiException, RefreshTokenException, IOException, LGDeviceV1OfflineException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + handleV1GenericErrorResult(resp); + } + + @Override + @Nullable + public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) + throws LGApiException, LGDeviceV1MonitorExpiredException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_POOL_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" + + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" + + " ]\n" + " }\n" + "}", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + Map envelop = null; + // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with + // offline flag. + try { + envelop = handleV1GenericErrorResult(resp); + } catch (LGDeviceV1OfflineException e) { + ACSnapShot shot = new ACSnapShotV2(); + shot.setOnline(false); + return shot; + } + if (envelop.get("workList") != null + && ((Map) envelop.get("workList")).get("returnData") != null) { + Map workList = ((Map) envelop.get("workList")); + if (!"0000".equals(workList.get("returnCode"))) { + LGDeviceV1MonitorExpiredException e = new LGDeviceV1MonitorExpiredException( + String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); + logger.warn("{}", e.getMessage()); + throw e; + } + + String jsonMonDataB64 = (String) workList.get("returnData"); + String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); + ACSnapShot shot = objectMapper.readValue(jsonMon, ACSnapShotV1.class); + shot.setOnline("E".equals(workList.get("deviceState"))); + return shot; + } else { + // no data available yet + return null; + } + } + + private File getCapFileForDevice(String deviceId) { + return new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); + } + + /** + * Get capability em registry/cache on file for next consult + * + * @param deviceId ID of the device + * @param uri URI of the config capanility + * @return return simplified capability + * @throws LGApiException If some error occurr + */ + @Override + @NonNull + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException { + try { + File regFile = getCapFileForDevice(deviceId); + ACCapability acCap = new ACCapability(); + Map mapper; + if (regFile.isFile() && !forceRecreate) { + // reg exists. Retrieve from it + mapper = objectMapper.readValue(regFile, new TypeReference>() { + }); + } else { + RestResult res = RestUtils.getCall(uri, null, null); + mapper = objectMapper.readValue(res.getJsonResponse(), new TypeReference>() { + }); + // try save file + objectMapper.writeValue(getCapFileForDevice(deviceId), mapper); + } + Map cap = (Map) mapper.get("Value"); + if (cap == null) { + throw new LGApiException("Error extracting capabilities supported by the device"); + } + + Map opModes = (Map) cap.get("OpMode"); + if (opModes == null) { + throw new LGApiException("Error extracting opModes supported by the device"); + } else { + Map modes = new HashMap(); + ((Map) opModes.get("option")).forEach((k, v) -> { + modes.put(v, k); + }); + acCap.setOpMod(modes); + } + Map fanSpeed = (Map) cap.get("WindStrength"); + if (fanSpeed == null) { + throw new LGApiException("Error extracting fanSpeed supported by the device"); + } else { + Map fanModes = new HashMap(); + ((Map) fanSpeed.get("option")).forEach((k, v) -> { + fanModes.put(v, k); + }); + acCap.setFanSpeed(fanModes); + + } + // Set supported modes for the device + + Map> supOpModes = (Map>) cap.get("SupportOpMode"); + acCap.setSupportedOpMode(new ArrayList<>(supOpModes.get("option").values())); + acCap.getSupportedOpMode().remove("@NON"); + Map> supFanSpeeds = (Map>) cap + .get("SupportWindStrength"); + acCap.setSupportedFanSpeed(new ArrayList<>(supFanSpeeds.get("option").values())); + acCap.getSupportedFanSpeed().remove("@NON"); + + return acCap; + } catch (IOException e) { + throw new LGApiException("Error reading IO interface", e); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..4075036fb8b65 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java @@ -0,0 +1,274 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgapi.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link LGApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGApiV2ClientServiceImpl extends LGApiClientServiceImpl { + private static final LGApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGApiV2ClientServiceImpl.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + private final TokenManager tokenManager; + + static { + instance = new LGApiV2ClientServiceImpl(); + } + + private LGApiV2ClientServiceImpl() { + tokenManager = TokenManager.getInstance(); + } + + @Override + protected TokenManager getTokenManager() { + return tokenManager; + } + + public static LGApiClientService getInstance() { + return instance; + } + + private Map getCommonV2Headers(String language, String country, String accessToken, + String userNumber) { + return getCommonHeaders(language, country, accessToken, userNumber); + } + + /** + * Get snapshot data from the device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGApiException if some communication error occur. + */ + @Override + @Nullable + public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException { + Map deviceSettings = getDeviceSettings(bridgeName, deviceId); + if (deviceSettings.get("snapshot") != null) { + Map snapMap = (Map) deviceSettings.get("snapshot"); + + ACSnapShot shot = objectMapper.convertValue(snapMap, ACSnapShotV2.class); + shot.setOnline((Boolean) snapMap.get("online")); + return shot; + } + return null; + } + + public RestResult sendControlCommands(String bridgeName, String deviceId, String command, String keyName, int value) + throws Exception { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) + .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId)); + Map headers = getCommonV2Headers(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String payload = String.format("{\n" + "\"ctrlKey\": \"basicCtrl\",\n" + "\"command\": \"%s\",\n" + + "\"dataKey\": \"%s\",\n" + "\"dataValue\": %d}", command, keyName, value); + return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", "airState.operation", + newPowerState.commandValue()); + handleV2GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting device power", e); + } + } + + @Override + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.opMode", newOpMode); + handleV2GenericErrorResult(resp); + } catch (LGApiException e) { + throw e; + } catch (Exception e) { + throw new LGApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.windStrength", newFanSpeed); + handleV2GenericErrorResult(resp); + } catch (LGApiException e) { + throw e; + } catch (Exception e) { + throw new LGApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.tempState.target", + newTargetTemp.commandValue()); + handleV2GenericErrorResult(resp); + } catch (LGApiException e) { + throw e; + } catch (Exception e) { + throw new LGApiException("Error adjusting operation mode", e); + } + } + + /** + * Start monitor data form specific device. This is old one, works only on V1 API supported devices. + * + * @param deviceId Device ID + * @return Work1 to be uses to grab data during monitoring. + * @throws LGApiException If some communication error occur. + */ + @Override + public String startMonitor(String bridgeName, String deviceId) + throws LGApiException, LGDeviceV1OfflineException, IOException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } + + @Override + @NonNull + @SuppressWarnings("ignoring Map type check") + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException { + try { + File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); + ACCapability acCap = new ACCapability(); + Map mapper; + if (regFile.isFile() || forceRecreate) { + try (InputStream in = new URL(uri).openStream()) { + Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + mapper = objectMapper.readValue(regFile, new TypeReference<>() { + }); + Map cap = (Map) mapper.get("Value"); + if (cap == null) { + throw new LGApiException("Error extracting capabilities supported by the device"); + } + + Map opModes = (Map) cap.get("airState.opMode"); + if (opModes == null) { + throw new LGApiException("Error extracting opModes supported by the device"); + } else { + Map modes = new HashMap(); + ((Map) opModes.get("value_mapping")).forEach((k, v) -> { + modes.put(v, k); + }); + acCap.setOpMod(modes); + } + Map fanSpeed = (Map) cap.get("airState.windStrength"); + if (fanSpeed == null) { + throw new LGApiException("Error extracting fanSpeed supported by the device"); + } else { + Map fanModes = new HashMap(); + ((Map) fanSpeed.get("value_mapping")).forEach((k, v) -> { + fanModes.put(v, k); + }); + acCap.setFanSpeed(fanModes); + + } + // Set supported modes for the device + Map> supOpModes = (Map>) cap + .get("support.airState.opMode"); + acCap.setSupportedOpMode(new ArrayList<>(supOpModes.get("value_mapping").values())); + acCap.getSupportedOpMode().remove("@NON"); + Map> supFanSpeeds = (Map>) cap + .get("support.airState.windStrength"); + acCap.setSupportedFanSpeed(new ArrayList<>(supFanSpeeds.get("value_mapping").values())); + acCap.getSupportedFanSpeed().remove("@NON"); + return acCap; + } catch (IOException e) { + throw new LGApiException("Error reading IO interface", e); + } + } + + private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGApiException { + Map metaResult; + if (resp == null) { + return; + } + if (resp.getStatusCode() != 200) { + logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGApiException( + String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { + }); + if (!"0000".equals(metaResult.get("resultCode"))) { + throw new LGApiException( + String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", + metaResult.get("resultCode"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + + } + } + + private Map handleDeviceSettingsResult(RestResult resp) throws LGApiException { + return genericHandleDeviceSettingsResult(resp, logger, objectMapper); + } + + @Override + public void stopMonitor(String bridgeName, String deviceId, String workId) + throws LGApiException, RefreshTokenException, IOException, LGDeviceV1OfflineException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } + + @Override + @Nullable + public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) + throws LGApiException, LGDeviceV1MonitorExpiredException, IOException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java new file mode 100644 index 0000000000000..8f047b9148c4c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACCapability { + + private Map opMod = Collections.emptyMap(); + private Map fanSpeed = Collections.emptyMap(); + + private List supportedOpMode = Collections.emptyList(); + private List supportedFanSpeed = Collections.emptyList(); + + public Map getOpMod() { + return opMod; + } + + public void setOpMod(Map opMod) { + this.opMod = opMod; + } + + public Map getFanSpeed() { + return fanSpeed; + } + + public void setFanSpeed(Map fanSpeed) { + this.fanSpeed = fanSpeed; + } + + public List getSupportedOpMode() { + return supportedOpMode; + } + + public void setSupportedOpMode(List supportedOpMode) { + this.supportedOpMode = supportedOpMode; + } + + public List getSupportedFanSpeed() { + return supportedFanSpeed; + } + + public void setSupportedFanSpeed(List supportedFanSpeed) { + this.supportedFanSpeed = supportedFanSpeed; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java new file mode 100644 index 0000000000000..1f072c631e5ea --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACSnapShot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACFanSpeed { + F1(2.0), + F2(3.0), + F3(4.0), + F4(5.0), + F5(6.0), + F_AUTO(8.0), + F_UNK(-1); + + private final double funStrength; + + ACFanSpeed(double v) { + this.funStrength = v; + } + + public static ACFanSpeed statusOf(double value) { + switch ((int) value) { + case 2: + return F1; + case 3: + return F2; + case 4: + return F3; + case 5: + return F4; + case 6: + return F5; + case 8: + return F_AUTO; + default: + return F_UNK; + } + } + + /** + * "0": "@AC_MAIN_WIND_STRENGTH_SLOW_W", + * "1": "@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", + * "2": "@AC_MAIN_WIND_STRENGTH_LOW_W", + * "3": "@AC_MAIN_WIND_STRENGTH_LOW_MID_W", + * "4": "@AC_MAIN_WIND_STRENGTH_MID_W", + * "5": "@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", + * "6": "@AC_MAIN_WIND_STRENGTH_HIGH_W", + * "7": "@AC_MAIN_WIND_STRENGTH_POWER_W", + * "8": "@AC_MAIN_WIND_STRENGTH_NATURE_W", + */ + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case F1: + return 2; + case F2: + return 3; + case F3: + return 4; + case F4: + return 5; + case F5: + return 6; + case F_AUTO: + return 8; + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java new file mode 100644 index 0000000000000..baf3c98f0210e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACSnapShot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACOpMode { + COOL(0), + DRY(1), + FAN(2), + AI(3), + HEAT(4), + AIRCLEAN(5), + ENSAV(8), + OP_UNK(-1); + + private final int opMode; + + ACOpMode(int v) { + this.opMode = v; + } + + public static ACOpMode statusOf(int value) { + switch ((int) value) { + case 0: + return COOL; + case 1: + return DRY; + case 2: + return FAN; + case 3: + return AI; + case 4: + return HEAT; + case 5: + return AIRCLEAN; + case 8: + return ENSAV; + default: + return OP_UNK; + } + } + + public int getValue() { + return this.opMode; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case COOL: + return 0;// "@AC_MAIN_OPERATION_MODE_COOL_W" + case DRY: + return 1; // "@AC_MAIN_OPERATION_MODE_DRY_W" + case FAN: + return 2; // "@AC_MAIN_OPERATION_MODE_FAN_W" + case AI: + return 3; // "@AC_MAIN_OPERATION_MODE_AI_W" + case HEAT: + return 4; // "@AC_MAIN_OPERATION_MODE_HEAT_W" + case AIRCLEAN: + return 5; // "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W" + case ENSAV: + return 8; // "AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W" + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java new file mode 100644 index 0000000000000..df8926d89b97a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * The {@link ACSnapShot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class ACSnapShot { + + private int airWindStrength; + + private double targetTemperature; + + private double currentTemperature; + + private int operationMode; + @Nullable + private Integer operation; + @JsonIgnore + private boolean online; + + @JsonIgnore + public DevicePowerState getAcPowerStatus() { + return operation == null ? DevicePowerState.DV_POWER_UNK : DevicePowerState.statusOf(operation); + } + + @JsonIgnore + public ACFanSpeed getAcFanSpeed() { + return ACFanSpeed.statusOf(airWindStrength); + } + + public Integer getAirWindStrength() { + return airWindStrength; + } + + public void setAirWindStrength(Integer airWindStrength) { + this.airWindStrength = airWindStrength; + } + + public Double getTargetTemperature() { + return targetTemperature; + } + + public void setTargetTemperature(Double targetTemperature) { + this.targetTemperature = targetTemperature; + } + + public Double getCurrentTemperature() { + return currentTemperature; + } + + public void setCurrentTemperature(Double currentTemperature) { + this.currentTemperature = currentTemperature; + } + + public Integer getOperationMode() { + return operationMode; + } + + public void setOperationMode(Integer operationMode) { + this.operationMode = operationMode; + } + + @Nullable + public Integer getOperation() { + return operation; + } + + public void setOperation(Integer operation) { + this.operation = operation; + } + + @JsonIgnore + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } + + @Override + public String toString() { + return "ACSnapShot{" + "airWindStrength=" + airWindStrength + ", targetTemperature=" + targetTemperature + + ", currentTemperature=" + currentTemperature + ", operationMode=" + operationMode + ", operation=" + + operation + ", acPowerStatus=" + getAcPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() + + ", acOpMode=" + ", online=" + isOnline() + " }"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java new file mode 100644 index 0000000000000..34002d3a58d42 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link ACSnapShotV1} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACSnapShotV1 extends ACSnapShot { + + @Override + @JsonProperty("WindStrength") + public Integer getAirWindStrength() { + return super.getAirWindStrength(); + } + + @Override + @JsonProperty("TempCfg") + public Double getTargetTemperature() { + return super.getTargetTemperature(); + } + + @Override + @JsonProperty("TempCur") + public Double getCurrentTemperature() { + return super.getCurrentTemperature(); + } + + @Override + @JsonProperty("OpMode") + public Integer getOperationMode() { + return super.getOperationMode(); + } + + @Override + @JsonProperty("Operation") + @Nullable + public Integer getOperation() { + return super.getOperation(); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java new file mode 100644 index 0000000000000..a373367eccbf5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link ACSnapShotV2} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACSnapShotV2 extends ACSnapShot { + + @Override + @JsonProperty("airState.windStrength") + public Integer getAirWindStrength() { + return super.getAirWindStrength(); + } + + @Override + @JsonProperty("airState.tempState.target") + public Double getTargetTemperature() { + return super.getTargetTemperature(); + } + + @Override + @JsonProperty("airState.tempState.current") + public Double getCurrentTemperature() { + return super.getCurrentTemperature(); + } + + @Override + @JsonProperty("airState.opMode") + public Integer getOperationMode() { + return super.getOperationMode(); + } + + @Override + @JsonProperty("airState.operation") + @Nullable + public Integer getOperation() { + return super.getOperation(); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java new file mode 100644 index 0000000000000..b43fbe5b1c4a3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACTargetTmp} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACTargetTmp { + _17(17.0), + _18(18.0), + _19(19.0), + _20(20.0), + _21(21.0), + _22(22.0), + _23(23.0), + _24(24.0), + _25(25.0), + _26(26.0), + _27(27.0), + _28(28.0), + _29(29.0), + _30(30.0), + UNK(-1); + + private final double targetTmp; + + ACTargetTmp(double v) { + this.targetTmp = v; + } + + public static ACTargetTmp statusOf(double value) { + switch ((int) value) { + case 17: + return _17; + case 18: + return _18; + case 19: + return _19; + case 20: + return _20; + case 21: + return _21; + case 22: + return _22; + case 23: + return _23; + case 24: + return _24; + case 25: + return _25; + case 26: + return _26; + case 27: + return _27; + case 28: + return _28; + case 29: + return _29; + case 30: + return _30; + default: + return UNK; + } + } + + public double getValue() { + return this.targetTmp; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + return (int) this.targetTmp; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java new file mode 100644 index 0000000000000..e597165334d71 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link DevicePowerState} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum DevicePowerState { + DV_POWER_ON(1), + DV_POWER_OFF(0), + DV_POWER_UNK(-1); + + private final int powerState; + + public double getValue() { + return powerState; + } + + DevicePowerState(int i) { + powerState = i; + } + + public static DevicePowerState statusOf(double value) { + switch ((int) value) { + case 0: + return DV_POWER_OFF; + case 1: + case 256: + case 257: + return DV_POWER_ON; + + default: + return DV_POWER_UNK; + } + } + + public static double valueOf(DevicePowerState dps) { + return dps.powerState; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case DV_POWER_ON: + return 257;// "@AC_MAIN_OPERATION_ALL_ON_W" + case DV_POWER_OFF: + return 0; // "@AC_MAIN_OPERATION_OFF_W" + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java new file mode 100644 index 0000000000000..a034870766775 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +/** + * The {@link DeviceTypes} + * + * @author Nemer Daud - Initial contribution + */ +public enum DeviceTypes { + AIR_CONDITIONER(401), + UNKNOWN(-1); + + private final int deviceTypeId; + + public int deviceTypeId() { + return deviceTypeId; + } + + public static DeviceTypes fromDeviceTypeId(int deviceTypeId) { + switch (deviceTypeId) { + case 401: + return AIR_CONDITIONER; + default: + return UNKNOWN; + } + } + + DeviceTypes(int i) { + this.deviceTypeId = i; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java new file mode 100644 index 0000000000000..3413aaeaf0700 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link LGDevice} + * + * @author Nemer Daud - Initial contribution + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@NonNullByDefault +public class LGDevice { + private String modelName = ""; + @JsonProperty("deviceType") + private int deviceTypeId; + private String deviceCode = ""; + private String alias = ""; + private String deviceId = ""; + private String platformType = ""; + private String modelJsonUri = ""; + private boolean online; + + public String getModelName() { + return modelName; + } + + @JsonIgnore + public DeviceTypes getDeviceType() { + return DeviceTypes.fromDeviceTypeId(deviceTypeId); + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public int getDeviceTypeId() { + return deviceTypeId; + } + + public void setDeviceTypeId(int deviceTypeId) { + this.deviceTypeId = deviceTypeId; + } + + public String getDeviceCode() { + return deviceCode; + } + + public void setDeviceCode(String deviceCode) { + this.deviceCode = deviceCode; + } + + public String getModelJsonUri() { + return modelJsonUri; + } + + public void setModelJsonUri(String modelJsonUri) { + this.modelJsonUri = modelJsonUri; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getPlatformType() { + return platformType; + } + + public void setPlatformType(String platformType) { + this.platformType = platformType; + } + + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java new file mode 100644 index 0000000000000..ebc16454853a2 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java @@ -0,0 +1,313 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.handler; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; +import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; +import org.openhab.binding.lgthinq.lgapi.LGApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.LGApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +/** + * The {@link LGBridgeTests} + * + * @author Nemer Daud - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@WireMockTest(httpPort = 8880) +class LGBridgeTests { + private static final Logger logger = LoggerFactory.getLogger(LGBridgeTests.class); + private String fakeBridgeName = "fakeBridgeId"; + private String fakeLanguage = "pt-BR"; + private String fakeCountry = "BR"; + private String fakeUserName = "someone@some.url"; + private String fakePassword = "somepassword"; + private String gtwResponse = "{\n" + " \"resultCode\":\"0000\",\n" + " \"result\":{\n" + + " \"countryCode\":\"BR\",\n" + " \"languageCode\":\"pt-BR\",\n" + + " \"thinq1Uri\":\"http://localhost:8880/api\",\n" + + " \"thinq2Uri\":\"http://localhost:8880/v1\",\n" + " \"empUri\":\"http://localhost:8880\",\n" + + " \"empSpxUri\":\"http://localhost:8880/spx\",\n" + " \"rtiUri\":\"localhost:8880\",\n" + + " \"mediaUri\":\"localhost:8880\",\n" + " \"appLatestVer\":\"4.0.14230\",\n" + + " \"appUpdateYn\":\"N\",\n" + " \"appLink\":\"market://details?id=com.lgeha.nuts\",\n" + + " \"uuidLoginYn\":\"N\",\n" + " \"lineLoginYn\":\"N\",\n" + " \"lineChannelId\":\"\",\n" + + " \"cicTel\":\"4004-5400\",\n" + " \"cicUri\":\"\",\n" + " \"isSupportVideoYn\":\"N\",\n" + + " \"countryLangDescription\":\"Brasil/Português\",\n" + + " \"empTermsUri\":\"http://localhost:8880\",\n" + + " \"googleAssistantUri\":\"https://assistant.google.com/services/invoke/uid/000000d26892b8a3\",\n" + + " \"smartWorldUri\":\"\",\n" + " \"racUri\":\"br.rac.lgeapi.com\",\n" + + " \"cssUri\":\"https://aic-common.lgthinq.com\",\n" + + " \"cssWebUri\":\"http://s3-an2-op-t20-css-web-resource.s3-website.ap-northeast-2.amazonaws.com\",\n" + + " \"iotssUri\":\"https://aic-iotservice.lgthinq.com\",\n" + " \"chatBotUri\":\"\",\n" + + " \"autoOrderSetUri\":\"\",\n" + " \"autoOrderManageUri\":\"\",\n" + + " \"aiShoppingUri\":\"\",\n" + " \"onestopCall\":\"\",\n" + + " \"onestopEngineerUri\":\"\",\n" + " \"hdssUri\":\"\",\n" + " \"amazonDrsYn\":\"N\",\n" + + " \"features\":{\n" + " \"supportTvIoTServerYn\":\"Y\",\n" + + " \"disableWeatherCard\":\"Y\",\n" + " \"thinqCss\":\"Y\",\n" + + " \"bleConfirmYn\":\"Y\",\n" + " \"tvRcmdContentYn\":\"Y\",\n" + + " \"supportProductManualYn\":\"N\",\n" + " \"clientDbYn\":\"Y\",\n" + + " \"androidAutoYn\":\"Y\",\n" + " \"searchYn\":\"Y\",\n" + + " \"thinqFaq\":\"Y\",\n" + " \"thinqNotice\":\"Y\",\n" + + " \"groupControlYn\":\"Y\",\n" + " \"inAppReviewYn\":\"Y\",\n" + + " \"cicSupport\":\"Y\",\n" + " \"qrRegisterYn\":\"Y\",\n" + + " \"supportBleYn\":\"Y\"\n" + " },\n" + " \"serviceCards\":[\n" + " \n" + + " ],\n" + " \"uris\":{\n" + + " \"takeATourUri\":\"https://s3-us2-op-t20-css-contents.s3.us-west-2.amazonaws.com/workexperience-new/ios/no-version/index.html\",\n" + + " \"gscsUri\":\"https://gscs-america.lge.com\",\n" + + " \"amazonDartUri\":\"https://shs.lgthinq.com\"\n" + " }\n" + " }\n" + "}"; + private String preLoginResponse = "{\n" + " \"encrypted_pw\":\"SOME_DUMMY_ENC_PWD\",\n" + + " \"signature\":\"SOME_DUMMY_SIGNATURE\",\n" + " \"tStamp\":\"1643236928\"\n" + "}"; + private String userIdType = "LGE"; + private String loginSessionId = "emp;11111111;222222222"; + private String loginSessionResponse = "{\n" + " \"account\":{\n" + " \"loginSessionID\":\"" + loginSessionId + + "\",\n" + " \"userID\":\"" + fakeUserName + "\",\n" + " \"userIDType\":\"" + userIdType + + "\",\n" + " \"dateOfBirth\":\"05-05-1978\",\n" + " \"country\":\"BR\",\n" + + " \"countryName\":\"Brazil\",\n" + " \"blacklist\":\"N\",\n" + " \"age\":\"43\",\n" + + " \"isSubscribe\":\"N\",\n" + " \"changePw\":\"N\",\n" + " \"toEmailId\":\"N\",\n" + + " \"periodPW\":\"N\",\n" + " \"lgAccount\":\"Y\",\n" + " \"isService\":\"Y\",\n" + + " \"userNickName\":\"faker\",\n" + " \"termsList\":[\n" + " \n" + " ],\n" + + " \"userIDList\":[\n" + " {\n" + " \"lgeIDList\":[\n" + " {\n" + + " \"lgeIDType\":\"LGE\",\n" + " \"userID\":\"" + fakeUserName + "\"\n" + + " }\n" + " ]\n" + " }\n" + " ],\n" + " \"serviceList\":[\n" + + " {\n" + " \"svcCode\":\"SVC202\",\n" + " \"svcName\":\"LG ThinQ\",\n" + + " \"isService\":\"Y\",\n" + " \"joinDate\":\"30-04-2020\"\n" + " },\n" + + " {\n" + " \"svcCode\":\"SVC710\",\n" + " \"svcName\":\"EMP OAuth\",\n" + + " \"isService\":\"Y\",\n" + " \"joinDate\":\"30-04-2020\"\n" + " }\n" + + " ],\n" + " \"displayUserID\":\"faker\",\n" + " \"notiList\":{\n" + + " \"totCount\":\"0\",\n" + " \"list\":[\n" + " \n" + " ]\n" + + " },\n" + " \"authUser\":\"N\",\n" + " \"dummyIdFlag\":\"N\"\n" + " }\n" + "}"; + private String userInfoReturned = "{\n" + " \"status\":1,\n" + " \"account\":{\n" + " \"userID\":\"" + + fakeUserName + "\",\n" + " \"userNo\":\"BR2005200239023\",\n" + " \"userIDType\":\"LGE\",\n" + + " \"displayUserID\":\"faker\",\n" + " \"userIDList\":[\n" + " {\n" + + " \"lgeIDList\":[\n" + " {\n" + " \"lgeIDType\":\"LGE\",\n" + + " \"userID\":\"" + fakeUserName + "\"\n" + " }\n" + " ]\n" + + " }\n" + " ],\n" + " \"dateOfBirth\":\"05-05-1978\",\n" + " \"country\":\"BR\",\n" + + " \"countryName\":\"Brazil\",\n" + " \"blacklist\":\"N\",\n" + " \"age\":\"45\",\n" + + " \"isSubscribe\":\"N\",\n" + " \"changePw\":\"N\",\n" + " \"toEmailId\":\"N\",\n" + + " \"periodPW\":\"N\",\n" + " \"lgAccount\":\"Y\",\n" + " \"isService\":\"Y\",\n" + + " \"userNickName\":\"faker\",\n" + " \"authUser\":\"N\",\n" + " \"serviceList\":[\n" + + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"LG ThinQ\",\n" + + " \"svcCode\":\"SVC202\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " },\n" + + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"LG Developer\",\n" + + " \"svcCode\":\"SVC609\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " },\n" + + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"MC OAuth\",\n" + + " \"svcCode\":\"SVC710\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " }\n" + + " ]\n" + " }\n" + "}"; + private String dashboardListReturned = "{\n" + " \"resultCode\":\"0000\",\n" + " \"result\":{\n" + + " \"langPackCommonVer\":\"125.6\",\n" + + " \"langPackCommonUri\":\"https://objectcontent.lgthinq.com/f1cae877-1d1e-4c12-8010-acbcdcce2df1?hdnts=exp=1706183232~hmac=257aa8146a089de87496cb13aa0b43761a19e7db225558dfb8996919746b465b\",\n" + + " \"item\":[\n" + " {\n" + " \"modelName\":\"RAC_056905_WW\",\n" + + " \"subModelName\":\"\",\n" + " \"deviceType\":401,\n" + + " \"deviceCode\":\"AI01\",\n" + " \"alias\":\"Bedroom\",\n" + + " \"deviceId\":\"abra-cadabra-0001-5771\",\n" + " \"fwVer\":\"2.5.8_RTOS_3K\",\n" + + " \"imageFileName\":\"ac_home_wall_airconditioner_img.png\",\n" + + " \"imageUrl\":\"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6\",\n" + + " \"smallImageUrl\":\"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5\",\n" + + " \"ssid\":\"dummy-ssid\",\n" + " \"macAddress\":\"74:40:be:92:ac:08\",\n" + + " \"networkType\":\"02\",\n" + " \"timezoneCode\":\"America/Sao_Paulo\",\n" + + " \"timezoneCodeAlias\":\"Brazil/Sao Paulo\",\n" + " \"utcOffset\":-3,\n" + + " \"utcOffsetDisplay\":\"-03:00\",\n" + " \"dstOffset\":-2,\n" + + " \"dstOffsetDisplay\":\"-02:00\",\n" + " \"curOffset\":-2,\n" + + " \"curOffsetDisplay\":\"-02:00\",\n" + + " \"sdsGuide\":\"{\\\"deviceCode\\\":\\\"AI01\\\"}\",\n" + " \"newRegYn\":\"N\",\n" + + " \"remoteControlType\":\"\",\n" + " \"modelJsonVer\":7.13,\n" + + " \"modelJsonUri\":\"https://aic.lgthinq.com:46030/api/webContents/modelJSON?modelName=modelJSON_401&countryCode=KR&contentsId=abra-cadabra-0001-5771&authKey=thinq\",\n" + + " \"appModuleVer\":12.49,\n" + + " \"appModuleUri\":\"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155\",\n" + + " \"appRestartYn\":\"Y\",\n" + " \"appModuleSize\":6082481,\n" + + " \"langPackProductTypeVer\":59.9,\n" + + " \"langPackProductTypeUri\":\"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea\",\n" + + " \"langPackModelVer\":\"\",\n" + " \"langPackModelUri\":\"\",\n" + + " \"deviceState\":\"E\",\n" + " \"online\":false,\n" + + " \"platformType\":\"thinq1\",\n" + " \"regDt\":2.0200909053555E13,\n" + + " \"modelProtocol\":\"STANDARD\",\n" + " \"order\":0,\n" + + " \"drServiceYn\":\"N\",\n" + " \"fwInfoList\":[\n" + " {\n" + + " \"partNumber\":\"SAA38690433\",\n" + " \"checksum\":\"00000000\",\n" + + " \"verOrder\":0\n" + " }\n" + " ],\n" + + " \"guideTypeYn\":\"Y\",\n" + " \"guideType\":\"RAC_TYPE1\",\n" + + " \"regDtUtc\":\"20200909073555\",\n" + " \"regIndex\":0,\n" + + " \"groupableYn\":\"Y\",\n" + " \"controllableYn\":\"Y\",\n" + + " \"combinedProductYn\":\"N\",\n" + " \"masterYn\":\"Y\",\n" + + " \"pccModelYn\":\"N\",\n" + " \"sdsPid\":{\n" + " \"sds4\":\"\",\n" + + " \"sds3\":\"\",\n" + " \"sds2\":\"\",\n" + " \"sds1\":\"\"\n" + + " },\n" + " \"autoOrderYn\":\"N\",\n" + + " \"modelNm\":\"RAC_056905_WW\",\n" + " \"initDevice\":false,\n" + + " \"existsEntryPopup\":\"N\",\n" + " \"tclcount\":0\n" + " },\n" + + " {\n" + " \"appType\":\"NUTS\",\n" + " \"modelCountryCode\":\"WW\",\n" + + " \"countryCode\":\"BR\",\n" + " \"modelName\":\"RAC_056905_WW\",\n" + + " \"deviceType\":401,\n" + " \"deviceCode\":\"AI01\",\n" + + " \"alias\":\"Office\",\n" + " \"deviceId\":\"abra-cadabra-0001-5772\",\n" + + " \"fwVer\":\"\",\n" + + " \"imageFileName\":\"ac_home_wall_airconditioner_img.png\",\n" + + " \"imageUrl\":\"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6\",\n" + + " \"smallImageUrl\":\"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5\",\n" + + " \"ssid\":\"smart-gameficacao\",\n" + " \"softapId\":\"\",\n" + + " \"softapPass\":\"\",\n" + " \"macAddress\":\"\",\n" + + " \"networkType\":\"02\",\n" + " \"timezoneCode\":\"America/Sao_Paulo\",\n" + + " \"timezoneCodeAlias\":\"Brazil/Sao Paulo\",\n" + " \"utcOffset\":-3,\n" + + " \"utcOffsetDisplay\":\"-03:00\",\n" + " \"dstOffset\":-2,\n" + + " \"dstOffsetDisplay\":\"-02:00\",\n" + " \"curOffset\":-2,\n" + + " \"curOffsetDisplay\":\"-02:00\",\n" + + " \"sdsGuide\":\"{\\\"deviceCode\\\":\\\"AI01\\\"}\",\n" + " \"newRegYn\":\"N\",\n" + + " \"remoteControlType\":\"\",\n" + " \"userNo\":\"BR2004259832795\",\n" + + " \"tftYn\":\"N\",\n" + " \"modelJsonVer\":12.11,\n" + + " \"modelJsonUri\":\"https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f\",\n" + + " \"appModuleVer\":12.49,\n" + + " \"appModuleUri\":\"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155\",\n" + + " \"appRestartYn\":\"Y\",\n" + " \"appModuleSize\":6082481,\n" + + " \"langPackProductTypeVer\":59.9,\n" + + " \"langPackProductTypeUri\":\"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea\",\n" + + " \"deviceState\":\"E\",\n" + " \"snapshot\":{\n" + + " \"airState.windStrength\":8.0,\n" + " \"airState.wMode.lowHeating\":0.0,\n" + + " \"airState.diagCode\":0.0,\n" + + " \"airState.lightingState.displayControl\":1.0,\n" + + " \"airState.wDir.hStep\":0.0,\n" + " \"mid\":8.4615358E7,\n" + + " \"airState.energy.onCurrent\":476.0,\n" + + " \"airState.wMode.airClean\":0.0,\n" + + " \"airState.quality.sensorMon\":0.0,\n" + + " \"airState.energy.accumulatedTime\":0.0,\n" + + " \"airState.miscFuncState.antiBugs\":0.0,\n" + + " \"airState.tempState.target\":18.0,\n" + " \"airState.operation\":1.0,\n" + + " \"airState.wMode.jet\":0.0,\n" + " \"airState.wDir.vStep\":2.0,\n" + + " \"timestamp\":1.643248573766E12,\n" + " \"airState.powerSave.basic\":0.0,\n" + + " \"airState.quality.PM10\":0.0,\n" + " \"static\":{\n" + + " \"deviceType\":\"401\",\n" + " \"countryCode\":\"BR\"\n" + + " },\n" + " \"airState.quality.overall\":0.0,\n" + + " \"airState.tempState.current\":25.0,\n" + + " \"airState.miscFuncState.extraOp\":0.0,\n" + + " \"airState.energy.accumulated\":0.0,\n" + + " \"airState.reservation.sleepTime\":0.0,\n" + + " \"airState.miscFuncState.autoDry\":0.0,\n" + + " \"airState.reservation.targetTimeToStart\":0.0,\n" + " \"meta\":{\n" + + " \"allDeviceInfoUpdate\":false,\n" + + " \"messageId\":\"fVz2AE-2SC-rf3GnerGdeQ\"\n" + " },\n" + + " \"airState.quality.PM1\":0.0,\n" + " \"airState.wMode.smartCare\":0.0,\n" + + " \"airState.quality.PM2\":0.0,\n" + " \"online\":true,\n" + + " \"airState.opMode\":0.0,\n" + + " \"airState.reservation.targetTimeToStop\":0.0,\n" + + " \"airState.filterMngStates.maxTime\":0.0,\n" + + " \"airState.filterMngStates.useTime\":0.0\n" + " },\n" + + " \"online\":true,\n" + " \"platformType\":\"thinq2\",\n" + + " \"area\":45883,\n" + " \"regDt\":2.0220111184827E13,\n" + + " \"blackboxYn\":\"Y\",\n" + " \"modelProtocol\":\"STANDARD\",\n" + + " \"order\":0,\n" + " \"drServiceYn\":\"N\",\n" + " \"fwInfoList\":[\n" + + " {\n" + " \"checksum\":\"00004105\",\n" + + " \"order\":1.0,\n" + " \"partNumber\":\"SAA40128563\"\n" + + " }\n" + " ],\n" + " \"modemInfo\":{\n" + + " \"appVersion\":\"clip_hna_v1.9.116\",\n" + + " \"modelName\":\"RAC_056905_WW\",\n" + " \"modemType\":\"QCOM_QCA4010\",\n" + + " \"ruleEngine\":\"y\"\n" + " },\n" + " \"guideTypeYn\":\"Y\",\n" + + " \"guideType\":\"RAC_TYPE1\",\n" + " \"regDtUtc\":\"20220111204827\",\n" + + " \"regIndex\":0,\n" + " \"groupableYn\":\"Y\",\n" + + " \"controllableYn\":\"Y\",\n" + " \"combinedProductYn\":\"N\",\n" + + " \"masterYn\":\"Y\",\n" + " \"pccModelYn\":\"N\",\n" + " \"sdsPid\":{\n" + + " \"sds4\":\"\",\n" + " \"sds3\":\"\",\n" + " \"sds2\":\"\",\n" + + " \"sds1\":\"\"\n" + " },\n" + " \"autoOrderYn\":\"N\",\n" + + " \"initDevice\":false,\n" + " \"existsEntryPopup\":\"N\",\n" + + " \"tclcount\":0\n" + " }\n" + " ],\n" + " \"group\":[\n" + " \n" + + " ]\n" + " }\n" + "}"; + private String secretKey = "gregre9812012910291029120912091209"; + private String oauthTokenSearchKeyReturned = "{\"returnData\":\"" + secretKey + "\"}"; + private String refreshToken = "12897238974bb327862378ef290128390273aa7389723894734de"; + private String accessToken = "11a1222c39f16a5c8b3fa45bb4c9be2e00a29a69dced2fa7fe731f1728346ee669f1a96d1f0b4925e5aa330b6dbab882772"; + private String sessionTokenReturned = "{\"status\":1,\"access_token\":\"" + accessToken + + "\",\"expires_in\":\"3600\",\"refresh_token\":\"" + refreshToken + + "\",\"oauth2_backend_url\":\"http://localhost:8880/\"}"; + + private String getCurrentTimestamp() { + SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.format(new Date()); + } + + @Test + public void testDiscoveryThings() { + stubFor(get(GATEWAY_SERVICE_PATH).willReturn(ok(gtwResponse))); + String preLoginPwd = RestUtils.getPreLoginEncPwd(fakePassword); + stubFor(post("/spx" + PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) + .willReturn(ok(preLoginResponse))); + URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + OAUTH_SEARCH_KEY_PATH) + .queryParam("key_name", "OAUTH_SECRETKEY").queryParam("sever_type", "OP").build(); + stubFor(get(String.format("%s?%s", uri.getPath(), uri.getQuery())).willReturn(ok(oauthTokenSearchKeyReturned))); + stubFor(post(V2_SESSION_LOGIN_PATH + fakeUserName).withRequestBody(containing("user_auth2=SOME_DUMMY_ENC_PWD")) + .withHeader("X-Signature", equalTo("SOME_DUMMY_SIGNATURE")) + .withHeader("X-Timestamp", equalTo("1643236928")).willReturn(ok(loginSessionResponse))); + stubFor(get(V2_USER_INFO).willReturn(ok(userInfoReturned))); + stubFor(get("/v1" + V2_LS_PATH).willReturn(ok(dashboardListReturned))); + String currTimestamp = getCurrentTimestamp(); + Map empData = new LinkedHashMap<>(); + empData.put("account_type", userIdType); + empData.put("country_code", fakeCountry); + empData.put("username", fakeUserName); + + stubFor(post("/emp/oauth2/token/empsession").withRequestBody(containing("account_type=" + userIdType)) + .withRequestBody(containing("country_code=" + fakeCountry)) + .withRequestBody(containing("username=" + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8))) + .withHeader("lgemp-x-session-key", equalTo(loginSessionId)).willReturn(ok(sessionTokenReturned))); + // faking some constants + LGThinqBindingConstants.GATEWAY_URL = "http://localhost:8880" + GATEWAY_SERVICE_PATH; + LGThinqBindingConstants.V2_EMP_SESS_URL = "http://localhost:8880/emp/oauth2/token/empsession"; + Bridge fakeThing = mock(Bridge.class); + ThingUID fakeThingUid = mock(ThingUID.class); + when(fakeThingUid.getId()).thenReturn(fakeBridgeName); + when(fakeThing.getUID()).thenReturn(fakeThingUid); + String tempDir = System.getProperty("java.io.tmpdir"); + LGThinqBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; + LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; + LGBridgeHandler b = new LGBridgeHandler(fakeThing); + LGBridgeHandler spyBridge = spy(b); + doReturn(new LGThinqConfiguration(fakeUserName, fakePassword, fakeCountry, fakeLanguage, 60)).when(spyBridge) + .getConfigAs(any(Class.class)); + spyBridge.initialize(); + LGApiClientService service1 = LGApiV1ClientServiceImpl.getInstance(); + LGApiClientService service2 = LGApiV2ClientServiceImpl.getInstance(); + TokenManager tokenManager = TokenManager.getInstance(); + try { + if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { + tokenManager.oauthFirstRegistration(fakeBridgeName, fakeLanguage, fakeCountry, fakeUserName, + fakePassword); + } + List devices = service2.listAccountDevices("bridgeTest"); + assertEquals(devices.size(), 2); + } catch (Exception e) { + logger.error("Error testing facade", e); + } + } +} From ff2e20ce9508342b473d5ea8421cd39a6071d490 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Fri, 28 Jan 2022 20:21:05 -0300 Subject: [PATCH 021/130] [lgthinq] pushing missing changes Signed-off-by: nemerdaud --- CODEOWNERS | 1 + bom/openhab-addons/pom.xml | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CODEOWNERS b/CODEOWNERS index 0e7d479c9a36f..aefc22a7defe7 100755 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -188,6 +188,7 @@ /bundles/org.openhab.binding.lcn/ @fwolter /bundles/org.openhab.binding.leapmotion/ @kaikreuzer /bundles/org.openhab.binding.lghombot/ @FluBBaOfWard +/bundles/org.openhab.binding.lgthinq/ @nemerdaud /bundles/org.openhab.binding.lgtvserial/ @fa2k /bundles/org.openhab.binding.lgthinq/ @nemerdaud /bundles/org.openhab.binding.lgwebos/ @sprehn diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 77312c0f4c5f1..157c159f0a67d 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -936,6 +936,11 @@ org.openhab.binding.lghombot ${project.version} + + org.openhab.addons.bundles + org.openhab.binding.lgthinq + ${project.version} + org.openhab.addons.bundles org.openhab.binding.lgtvserial From 0f0c7aac5b796b1c572c0fc89a88fd82ca3a55e4 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 12:24:48 -0300 Subject: [PATCH 022/130] [lgthinq] i18n enhancement, adjusting class names & references, adjusted some tests, more code cleaning. Signed-off-by: nemerdaud --- ...java => LGThinqAirConditionerHandler.java} | 49 ++- .../internal/LGThinqBindingConstants.java | 8 + ...inqDeviceDynStateDescriptionProvider.java} | 8 +- ...viceThing.java => LGThinqDeviceThing.java} | 4 +- .../internal/LGThinqHandlerFactory.java | 16 +- .../binding/lgthinq/internal/api/Gateway.java | 122 ------- .../{LGBridge.java => LGThinqBridge.java} | 12 +- ...Handler.java => LGThinqBridgeHandler.java} | 30 +- ...vice.java => LGThinqApiClientService.java} | 4 +- ....java => LGThinqApiClientServiceImpl.java} | 6 +- ...ava => LGThinqApiV1ClientServiceImpl.java} | 14 +- ...ava => LGThinqApiV2ClientServiceImpl.java} | 14 +- .../lgthinq/handler/LGBridgeTests.java | 313 ------------------ 13 files changed, 85 insertions(+), 515 deletions(-) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGAirConditionerHandler.java => LGThinqAirConditionerHandler.java} (89%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGDeviceDynStateDescriptionProvider.java => LGThinqDeviceDynStateDescriptionProvider.java} (81%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGDeviceThing.java => LGThinqDeviceThing.java} (90%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/{LGBridge.java => LGThinqBridge.java} (68%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/{LGBridgeHandler.java => LGThinqBridgeHandler.java} (92%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/{LGApiClientService.java => LGThinqApiClientService.java} (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/{LGApiClientServiceImpl.java => LGThinqApiClientServiceImpl.java} (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/{LGApiV1ClientServiceImpl.java => LGThinqApiV1ClientServiceImpl.java} (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/{LGApiV2ClientServiceImpl.java => LGThinqApiV2ClientServiceImpl.java} (96%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java similarity index 89% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 7ceeb3c631450..ea0c1b7859654 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -25,10 +25,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; -import org.openhab.binding.lgthinq.lgapi.LGApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.LGApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgapi.model.*; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -43,32 +43,28 @@ import org.slf4j.LoggerFactory; /** - * The {@link LGAirConditionerHandler} is responsible for handling commands, which are + * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGAirConditionerHandler extends BaseThingHandler implements LGDeviceThing { - public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner +public class LGThinqAirConditionerHandler extends BaseThingHandler implements LGThinqDeviceThing { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); - private final LGDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; private final ChannelUID opModeChannelUID; private final ChannelUID opModeFanSpeedUID; @Nullable private ACCapability acCapability; private final String lgPlatfomType; - private final Logger logger = LoggerFactory.getLogger(LGAirConditionerHandler.class); + private final Logger logger = LoggerFactory.getLogger(LGThinqAirConditionerHandler.class); @NonNullByDefault - private final LGApiClientService lgApiClientService; + private final LGThinqApiClientService lgThinqApiClientService; private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; // Bridges status that this thing must top scanning for state change private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, ThingStatusDetail.CONFIGURATION_ERROR); - private static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); private @Nullable ScheduledFuture thingStatePoolingJob; private @Nullable Future commandExecutorQueueJob; // *** Long running isolated threadpools. @@ -81,12 +77,13 @@ public class LGAirConditionerHandler extends BaseThingHandler implements LGDevic @NonNullByDefault private String bridgeId = ""; - public LGAirConditionerHandler(Thing thing, LGDeviceDynStateDescriptionProvider stateDescriptionProvider) { + public LGThinqAirConditionerHandler(Thing thing, + LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { super(thing); this.stateDescriptionProvider = stateDescriptionProvider; lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGApiV1ClientServiceImpl.getInstance() - : LGApiV2ClientServiceImpl.getInstance(); + lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() + : LGThinqApiV2ClientServiceImpl.getInstance(); opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); opModeFanSpeedUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); } @@ -136,7 +133,7 @@ private void initializeThing(@Nullable ThingStatus bridgeStatus) { "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); } if (bridge != null) { - LGBridgeHandler handler = (LGBridgeHandler) bridge.getHandler(); + LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); // registry this thing to the bridge if (handler == null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); @@ -241,7 +238,7 @@ private String getBridgeId() { private void forceStopDeviceV1Monitor(String deviceId) { try { monitorV1Began = false; - lgApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + lgThinqApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); } catch (Exception e) { logger.error("Error stopping LG Device monitor", e); } @@ -272,7 +269,7 @@ public void updateChannelDynStateDescription() throws LGApiException { @Override public ACCapability getAcCapabilities() throws LGApiException { if (acCapability == null) { - acCapability = lgApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); + acCapability = lgThinqApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); } return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } @@ -281,11 +278,11 @@ public ACCapability getAcCapabilities() throws LGApiException { private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return lgApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); + return lgThinqApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { - monitorWorkId = lgApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); monitorV1Began = true; } } catch (LGDeviceV1OfflineException e) { @@ -302,7 +299,7 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiExcepti while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = lgApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + shot = lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); if (shot != null) { return shot; } @@ -444,7 +441,7 @@ public void run() { switch (params.channelUID) { case CHANNEL_MOD_OP_ID: { if (params.command instanceof DecimalType) { - lgApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + lgThinqApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), ((DecimalType) command).intValue()); } else { logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); @@ -453,7 +450,7 @@ public void run() { } case CHANNEL_FAN_SPEED_ID: { if (command instanceof DecimalType) { - lgApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + lgThinqApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), ((DecimalType) command).intValue()); } else { logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); @@ -462,7 +459,7 @@ public void run() { } case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { - lgApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), command == ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); } else { logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); @@ -479,7 +476,7 @@ public void run() { logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); break; } - lgApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + lgThinqApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), ACTargetTmp.statusOf(targetTemp)); break; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 5c2767108f97c..4165da3a117c8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -13,10 +13,14 @@ package org.openhab.binding.lgthinq.internal; import java.io.File; +import java.util.Collections; import java.util.Map; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; import org.openhab.core.OpenHAB; +import org.openhab.core.thing.ThingTypeUID; /** * The {@link LGThinqBindingConstants} class defines common constants, which are @@ -28,6 +32,9 @@ public class LGThinqBindingConstants { public static final String BINDING_ID = "lgthinq"; + public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner + public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); public static final String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; @@ -87,6 +94,7 @@ public class LGThinqBindingConstants { public static final String PLATFORM_TYPE = "platform_type"; public static final String PLATFORM_TYPE_V1 = "thinq1"; public static final String PLATFORM_TYPE_V2 = "thinq2"; + static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); public static final int SEARCH_TIME = 20; // delay between each devices's scan for state changes (in seconds) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java similarity index 81% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java index a74b5c73c9ce8..2b50c6e63ca83 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java @@ -22,14 +22,14 @@ import org.osgi.service.component.annotations.Reference; /** - * The {@link LGDeviceThing} is a main interface contract for all LG Thinq things + * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things * * @author Nemer Daud - Initial contribution */ -@Component(service = { DynamicStateDescriptionProvider.class, LGDeviceDynStateDescriptionProvider.class }) -public class LGDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { +@Component(service = { DynamicStateDescriptionProvider.class, LGThinqDeviceDynStateDescriptionProvider.class }) +public class LGThinqDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { @Activate - public LGDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // + public LGThinqDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { this.eventPublisher = eventPublisher; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java similarity index 90% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index 54cd67361e071..64c77140d443a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -18,12 +18,12 @@ import org.openhab.binding.lgthinq.lgapi.model.LGDevice; /** - * The {@link LGDeviceThing} is a main interface contract for all LG Thinq things + * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGDeviceThing { +public interface LGThinqDeviceThing { void onDeviceAdded(@NonNullByDefault LGDevice device); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 803d80a477971..3b9b6ad3249d4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -12,14 +12,14 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGAirConditionerHandler.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler.THING_TYPE_BRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; +import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -47,7 +47,7 @@ public class LGThinqHandlerFactory extends BaseThingHandlerFactory { private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, THING_TYPE_BRIDGE); private final Logger logger = LoggerFactory.getLogger(LGThinqHandlerFactory.class); - private final LGDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -59,9 +59,9 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { - return new LGAirConditionerHandler(thing, stateDescriptionProvider); + return new LGThinqAirConditionerHandler(thing, stateDescriptionProvider); } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - return new LGBridgeHandler((Bridge) thing); + return new LGThinqBridgeHandler((Bridge) thing); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; @@ -72,14 +72,14 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { return super.createThing(thingTypeUID, configuration, thingUID, null); - } else if (LGAirConditionerHandler.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { + } else if (LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); } return null; } @Activate - public LGThinqHandlerFactory(final @Reference LGDeviceDynStateDescriptionProvider stateDescriptionProvider) { + public LGThinqHandlerFactory(final @Reference LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { this.stateDescriptionProvider = stateDescriptionProvider; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java deleted file mode 100644 index 64c5535923928..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.api; - -import java.io.Serializable; -import java.util.Map; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link Gateway} hold informations about the LG Gateway - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class Gateway implements Serializable { - private String empBaseUri = ""; - private String loginBaseUri = ""; - private String apiRootV1 = ""; - private String apiRootV2 = ""; - private String authBase = ""; - private String language = ""; - private String country = ""; - private String username = ""; - private String password = ""; - - public Gateway() { - } - - public Gateway(Map params, String language, String country) { - this.apiRootV2 = Objects.requireNonNullElse(params.get("thinq2Uri"), ""); - this.apiRootV1 = Objects.requireNonNullElse(params.get("thinq1Uri"), ""); - this.loginBaseUri = Objects.requireNonNullElse(params.get("empSpxUri"), ""); - this.authBase = Objects.requireNonNullElse(params.get("empUri"), ""); - this.empBaseUri = Objects.requireNonNullElse(params.get("empTermsUri"), ""); - this.language = language; - this.country = country; - } - - public String getEmpBaseUri() { - return empBaseUri; - } - - public String getApiRootV2() { - return apiRootV2; - } - - public String getAuthBase() { - return authBase; - } - - public String getLanguage() { - return language; - } - - public String getCountry() { - return country; - } - - public String getLoginBaseUri() { - return loginBaseUri; - } - - public String getApiRootV1() { - return apiRootV1; - } - - public void setEmpBaseUri(String empBaseUri) { - this.empBaseUri = empBaseUri; - } - - public void setLoginBaseUri(String loginBaseUri) { - this.loginBaseUri = loginBaseUri; - } - - public void setApiRootV1(String apiRootV1) { - this.apiRootV1 = apiRootV1; - } - - public void setApiRootV2(String apiRootV2) { - this.apiRootV2 = apiRootV2; - } - - public void setAuthBase(String authBase) { - this.authBase = authBase; - } - - public void setLanguage(String language) { - this.language = language; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java similarity index 68% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java index d9f8f36ab93f3..259307eb1819c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java @@ -12,20 +12,20 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import org.openhab.binding.lgthinq.internal.LGDeviceThing; +import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; /** - * The {@link LGBridge} + * The {@link LGThinqBridge} * * @author Nemer Daud - Initial contribution */ -public interface LGBridge { +public interface LGThinqBridge { void registerDiscoveryListener(LGThinqDiscoveryService listener); - void registryListenerThing(LGDeviceThing thing); + void registryListenerThing(LGThinqDeviceThing thing); - void unRegistryListenerThing(LGDeviceThing thing); + void unRegistryListenerThing(LGThinqDeviceThing thing); - LGDeviceThing getThingByDeviceId(String deviceId); + LGThinqDeviceThing getThingByDeviceId(String deviceId); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java similarity index 92% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index d6a40ab443285..de1ee7691abbe 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -24,15 +24,15 @@ import java.util.concurrent.locks.ReentrantLock; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGDeviceThing; import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; +import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgapi.LGApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgapi.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; import org.openhab.core.thing.*; @@ -43,18 +43,18 @@ import org.slf4j.LoggerFactory; /** - * The {@link LGBridgeHandler} + * The {@link LGThinqBridgeHandler} * * @author Nemer Daud - Initial contribution */ -public class LGBridgeHandler extends ConfigStatusBridgeHandler implements LGBridge { +public class LGThinqBridgeHandler extends ConfigStatusBridgeHandler implements LGThinqBridge { public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(LGThinqBindingConstants.BINDING_ID, "bridge"); - private Map lGDeviceRegister = new ConcurrentHashMap<>(); + private Map lGDeviceRegister = new ConcurrentHashMap<>(); private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); static { - var logger = LoggerFactory.getLogger(LGBridgeHandler.class); + var logger = LoggerFactory.getLogger(LGThinqBridgeHandler.class); try { File directory = new File(THINQ_USER_DATA_FOLDER); if (!directory.exists()) { @@ -64,18 +64,18 @@ public class LGBridgeHandler extends ConfigStatusBridgeHandler implements LGBrid logger.warn("Unable to setup thinq userdata directory: {}", e.getMessage()); } } - private final Logger logger = LoggerFactory.getLogger(LGBridgeHandler.class); + private final Logger logger = LoggerFactory.getLogger(LGThinqBridgeHandler.class); private LGThinqConfiguration lgthinqConfig; private TokenManager tokenManager; private LGThinqDiscoveryService discoveryService; - private LGApiClientService lgApiClient; + private LGThinqApiClientService lgApiClient; private @Nullable Future initJob; private @Nullable ScheduledFuture devicePollingJob; - public LGBridgeHandler(Bridge bridge) { + public LGThinqBridgeHandler(Bridge bridge) { super(bridge); tokenManager = TokenManager.getInstance(); - lgApiClient = LGApiV1ClientServiceImpl.getInstance(); + lgApiClient = LGThinqApiV1ClientServiceImpl.getInstance(); lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); } @@ -170,7 +170,7 @@ public Collection> getServices() { } @Override - public void registryListenerThing(LGDeviceThing thing) { + public void registryListenerThing(LGThinqDeviceThing thing) { if (lGDeviceRegister.get(thing.getDeviceId()) == null) { lGDeviceRegister.put(thing.getDeviceId(), thing); // remove device from discovery list, if exists. @@ -182,12 +182,12 @@ public void registryListenerThing(LGDeviceThing thing) { } @Override - public void unRegistryListenerThing(LGDeviceThing thing) { + public void unRegistryListenerThing(LGThinqDeviceThing thing) { lGDeviceRegister.remove(thing.getDeviceId()); } @Override - public LGDeviceThing getThingByDeviceId(String deviceId) { + public LGThinqDeviceThing getThingByDeviceId(String deviceId) { return lGDeviceRegister.get(deviceId); } @@ -218,7 +218,7 @@ protected void doConnectedRun() throws LGThinqException { logger.trace("LG Device '{}' removed.", deviceId); lastDevicesDiscovered.remove(deviceId); - LGDeviceThing deviceThing = lGDeviceRegister.get(deviceId); + LGThinqDeviceThing deviceThing = lGDeviceRegister.get(deviceId); if (deviceThing != null) { deviceThing.onDeviceRemoved(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index 38fa1d503026f..6ecbf3e002343 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -26,12 +26,12 @@ import org.openhab.binding.lgthinq.lgapi.model.*; /** - * The {@link LGApiClientService} + * The {@link LGThinqApiClientService} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGApiClientService { +public interface LGThinqApiClientService { List listAccountDevices(String bridgeName) throws LGApiException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index c048f707bd6f5..edc2b07c5c5b0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -33,13 +33,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGApiV1ClientServiceImpl} + * The {@link LGThinqApiV1ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public abstract class LGApiClientServiceImpl implements LGApiClientService { - private static final Logger logger = LoggerFactory.getLogger(LGApiClientServiceImpl.class); +public abstract class LGThinqApiClientServiceImpl implements LGThinqApiClientService { + private static final Logger logger = LoggerFactory.getLogger(LGThinqApiClientServiceImpl.class); private final ObjectMapper objectMapper = new ObjectMapper(); protected abstract TokenManager getTokenManager(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index 628dddcb7d34e..51de488bb84f6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -40,26 +40,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGApiV1ClientServiceImpl} + * The {@link LGThinqApiV1ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGApiV1ClientServiceImpl extends LGApiClientServiceImpl { - private static final LGApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGApiV1ClientServiceImpl.class); +public class LGThinqApiV1ClientServiceImpl extends LGThinqApiClientServiceImpl { + private static final LGThinqApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinqApiV1ClientServiceImpl.class); private final ObjectMapper objectMapper = new ObjectMapper(); private final TokenManager tokenManager; static { - instance = new LGApiV1ClientServiceImpl(); + instance = new LGThinqApiV1ClientServiceImpl(); } - private LGApiV1ClientServiceImpl() { + private LGThinqApiV1ClientServiceImpl() { tokenManager = TokenManager.getInstance(); } - public static LGApiClientService getInstance() { + public static LGThinqApiClientService getInstance() { return instance; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index 4075036fb8b65..9e4a2dae575f4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -44,22 +44,22 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGApiV2ClientServiceImpl} + * The {@link LGThinqApiV2ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGApiV2ClientServiceImpl extends LGApiClientServiceImpl { - private static final LGApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGApiV2ClientServiceImpl.class); +public class LGThinqApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl { + private static final LGThinqApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinqApiV2ClientServiceImpl.class); private final ObjectMapper objectMapper = new ObjectMapper(); private final TokenManager tokenManager; static { - instance = new LGApiV2ClientServiceImpl(); + instance = new LGThinqApiV2ClientServiceImpl(); } - private LGApiV2ClientServiceImpl() { + private LGThinqApiV2ClientServiceImpl() { tokenManager = TokenManager.getInstance(); } @@ -68,7 +68,7 @@ protected TokenManager getTokenManager() { return tokenManager; } - public static LGApiClientService getInstance() { + public static LGThinqApiClientService getInstance() { return instance; } diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java deleted file mode 100644 index ebc16454853a2..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.handler; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.any; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; -import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; -import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; -import org.openhab.binding.lgthinq.lgapi.LGApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.LGApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.model.LGDevice; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ThingUID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.github.tomakehurst.wiremock.junit5.WireMockTest; - -/** - * The {@link LGBridgeTests} - * - * @author Nemer Daud - Initial contribution - */ -@ExtendWith(MockitoExtension.class) -@WireMockTest(httpPort = 8880) -class LGBridgeTests { - private static final Logger logger = LoggerFactory.getLogger(LGBridgeTests.class); - private String fakeBridgeName = "fakeBridgeId"; - private String fakeLanguage = "pt-BR"; - private String fakeCountry = "BR"; - private String fakeUserName = "someone@some.url"; - private String fakePassword = "somepassword"; - private String gtwResponse = "{\n" + " \"resultCode\":\"0000\",\n" + " \"result\":{\n" - + " \"countryCode\":\"BR\",\n" + " \"languageCode\":\"pt-BR\",\n" - + " \"thinq1Uri\":\"http://localhost:8880/api\",\n" - + " \"thinq2Uri\":\"http://localhost:8880/v1\",\n" + " \"empUri\":\"http://localhost:8880\",\n" - + " \"empSpxUri\":\"http://localhost:8880/spx\",\n" + " \"rtiUri\":\"localhost:8880\",\n" - + " \"mediaUri\":\"localhost:8880\",\n" + " \"appLatestVer\":\"4.0.14230\",\n" - + " \"appUpdateYn\":\"N\",\n" + " \"appLink\":\"market://details?id=com.lgeha.nuts\",\n" - + " \"uuidLoginYn\":\"N\",\n" + " \"lineLoginYn\":\"N\",\n" + " \"lineChannelId\":\"\",\n" - + " \"cicTel\":\"4004-5400\",\n" + " \"cicUri\":\"\",\n" + " \"isSupportVideoYn\":\"N\",\n" - + " \"countryLangDescription\":\"Brasil/Português\",\n" - + " \"empTermsUri\":\"http://localhost:8880\",\n" - + " \"googleAssistantUri\":\"https://assistant.google.com/services/invoke/uid/000000d26892b8a3\",\n" - + " \"smartWorldUri\":\"\",\n" + " \"racUri\":\"br.rac.lgeapi.com\",\n" - + " \"cssUri\":\"https://aic-common.lgthinq.com\",\n" - + " \"cssWebUri\":\"http://s3-an2-op-t20-css-web-resource.s3-website.ap-northeast-2.amazonaws.com\",\n" - + " \"iotssUri\":\"https://aic-iotservice.lgthinq.com\",\n" + " \"chatBotUri\":\"\",\n" - + " \"autoOrderSetUri\":\"\",\n" + " \"autoOrderManageUri\":\"\",\n" - + " \"aiShoppingUri\":\"\",\n" + " \"onestopCall\":\"\",\n" - + " \"onestopEngineerUri\":\"\",\n" + " \"hdssUri\":\"\",\n" + " \"amazonDrsYn\":\"N\",\n" - + " \"features\":{\n" + " \"supportTvIoTServerYn\":\"Y\",\n" - + " \"disableWeatherCard\":\"Y\",\n" + " \"thinqCss\":\"Y\",\n" - + " \"bleConfirmYn\":\"Y\",\n" + " \"tvRcmdContentYn\":\"Y\",\n" - + " \"supportProductManualYn\":\"N\",\n" + " \"clientDbYn\":\"Y\",\n" - + " \"androidAutoYn\":\"Y\",\n" + " \"searchYn\":\"Y\",\n" - + " \"thinqFaq\":\"Y\",\n" + " \"thinqNotice\":\"Y\",\n" - + " \"groupControlYn\":\"Y\",\n" + " \"inAppReviewYn\":\"Y\",\n" - + " \"cicSupport\":\"Y\",\n" + " \"qrRegisterYn\":\"Y\",\n" - + " \"supportBleYn\":\"Y\"\n" + " },\n" + " \"serviceCards\":[\n" + " \n" - + " ],\n" + " \"uris\":{\n" - + " \"takeATourUri\":\"https://s3-us2-op-t20-css-contents.s3.us-west-2.amazonaws.com/workexperience-new/ios/no-version/index.html\",\n" - + " \"gscsUri\":\"https://gscs-america.lge.com\",\n" - + " \"amazonDartUri\":\"https://shs.lgthinq.com\"\n" + " }\n" + " }\n" + "}"; - private String preLoginResponse = "{\n" + " \"encrypted_pw\":\"SOME_DUMMY_ENC_PWD\",\n" - + " \"signature\":\"SOME_DUMMY_SIGNATURE\",\n" + " \"tStamp\":\"1643236928\"\n" + "}"; - private String userIdType = "LGE"; - private String loginSessionId = "emp;11111111;222222222"; - private String loginSessionResponse = "{\n" + " \"account\":{\n" + " \"loginSessionID\":\"" + loginSessionId - + "\",\n" + " \"userID\":\"" + fakeUserName + "\",\n" + " \"userIDType\":\"" + userIdType - + "\",\n" + " \"dateOfBirth\":\"05-05-1978\",\n" + " \"country\":\"BR\",\n" - + " \"countryName\":\"Brazil\",\n" + " \"blacklist\":\"N\",\n" + " \"age\":\"43\",\n" - + " \"isSubscribe\":\"N\",\n" + " \"changePw\":\"N\",\n" + " \"toEmailId\":\"N\",\n" - + " \"periodPW\":\"N\",\n" + " \"lgAccount\":\"Y\",\n" + " \"isService\":\"Y\",\n" - + " \"userNickName\":\"faker\",\n" + " \"termsList\":[\n" + " \n" + " ],\n" - + " \"userIDList\":[\n" + " {\n" + " \"lgeIDList\":[\n" + " {\n" - + " \"lgeIDType\":\"LGE\",\n" + " \"userID\":\"" + fakeUserName + "\"\n" - + " }\n" + " ]\n" + " }\n" + " ],\n" + " \"serviceList\":[\n" - + " {\n" + " \"svcCode\":\"SVC202\",\n" + " \"svcName\":\"LG ThinQ\",\n" - + " \"isService\":\"Y\",\n" + " \"joinDate\":\"30-04-2020\"\n" + " },\n" - + " {\n" + " \"svcCode\":\"SVC710\",\n" + " \"svcName\":\"EMP OAuth\",\n" - + " \"isService\":\"Y\",\n" + " \"joinDate\":\"30-04-2020\"\n" + " }\n" - + " ],\n" + " \"displayUserID\":\"faker\",\n" + " \"notiList\":{\n" - + " \"totCount\":\"0\",\n" + " \"list\":[\n" + " \n" + " ]\n" - + " },\n" + " \"authUser\":\"N\",\n" + " \"dummyIdFlag\":\"N\"\n" + " }\n" + "}"; - private String userInfoReturned = "{\n" + " \"status\":1,\n" + " \"account\":{\n" + " \"userID\":\"" - + fakeUserName + "\",\n" + " \"userNo\":\"BR2005200239023\",\n" + " \"userIDType\":\"LGE\",\n" - + " \"displayUserID\":\"faker\",\n" + " \"userIDList\":[\n" + " {\n" - + " \"lgeIDList\":[\n" + " {\n" + " \"lgeIDType\":\"LGE\",\n" - + " \"userID\":\"" + fakeUserName + "\"\n" + " }\n" + " ]\n" - + " }\n" + " ],\n" + " \"dateOfBirth\":\"05-05-1978\",\n" + " \"country\":\"BR\",\n" - + " \"countryName\":\"Brazil\",\n" + " \"blacklist\":\"N\",\n" + " \"age\":\"45\",\n" - + " \"isSubscribe\":\"N\",\n" + " \"changePw\":\"N\",\n" + " \"toEmailId\":\"N\",\n" - + " \"periodPW\":\"N\",\n" + " \"lgAccount\":\"Y\",\n" + " \"isService\":\"Y\",\n" - + " \"userNickName\":\"faker\",\n" + " \"authUser\":\"N\",\n" + " \"serviceList\":[\n" - + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"LG ThinQ\",\n" - + " \"svcCode\":\"SVC202\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " },\n" - + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"LG Developer\",\n" - + " \"svcCode\":\"SVC609\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " },\n" - + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"MC OAuth\",\n" - + " \"svcCode\":\"SVC710\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " }\n" - + " ]\n" + " }\n" + "}"; - private String dashboardListReturned = "{\n" + " \"resultCode\":\"0000\",\n" + " \"result\":{\n" - + " \"langPackCommonVer\":\"125.6\",\n" - + " \"langPackCommonUri\":\"https://objectcontent.lgthinq.com/f1cae877-1d1e-4c12-8010-acbcdcce2df1?hdnts=exp=1706183232~hmac=257aa8146a089de87496cb13aa0b43761a19e7db225558dfb8996919746b465b\",\n" - + " \"item\":[\n" + " {\n" + " \"modelName\":\"RAC_056905_WW\",\n" - + " \"subModelName\":\"\",\n" + " \"deviceType\":401,\n" - + " \"deviceCode\":\"AI01\",\n" + " \"alias\":\"Bedroom\",\n" - + " \"deviceId\":\"abra-cadabra-0001-5771\",\n" + " \"fwVer\":\"2.5.8_RTOS_3K\",\n" - + " \"imageFileName\":\"ac_home_wall_airconditioner_img.png\",\n" - + " \"imageUrl\":\"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6\",\n" - + " \"smallImageUrl\":\"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5\",\n" - + " \"ssid\":\"dummy-ssid\",\n" + " \"macAddress\":\"74:40:be:92:ac:08\",\n" - + " \"networkType\":\"02\",\n" + " \"timezoneCode\":\"America/Sao_Paulo\",\n" - + " \"timezoneCodeAlias\":\"Brazil/Sao Paulo\",\n" + " \"utcOffset\":-3,\n" - + " \"utcOffsetDisplay\":\"-03:00\",\n" + " \"dstOffset\":-2,\n" - + " \"dstOffsetDisplay\":\"-02:00\",\n" + " \"curOffset\":-2,\n" - + " \"curOffsetDisplay\":\"-02:00\",\n" - + " \"sdsGuide\":\"{\\\"deviceCode\\\":\\\"AI01\\\"}\",\n" + " \"newRegYn\":\"N\",\n" - + " \"remoteControlType\":\"\",\n" + " \"modelJsonVer\":7.13,\n" - + " \"modelJsonUri\":\"https://aic.lgthinq.com:46030/api/webContents/modelJSON?modelName=modelJSON_401&countryCode=KR&contentsId=abra-cadabra-0001-5771&authKey=thinq\",\n" - + " \"appModuleVer\":12.49,\n" - + " \"appModuleUri\":\"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155\",\n" - + " \"appRestartYn\":\"Y\",\n" + " \"appModuleSize\":6082481,\n" - + " \"langPackProductTypeVer\":59.9,\n" - + " \"langPackProductTypeUri\":\"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea\",\n" - + " \"langPackModelVer\":\"\",\n" + " \"langPackModelUri\":\"\",\n" - + " \"deviceState\":\"E\",\n" + " \"online\":false,\n" - + " \"platformType\":\"thinq1\",\n" + " \"regDt\":2.0200909053555E13,\n" - + " \"modelProtocol\":\"STANDARD\",\n" + " \"order\":0,\n" - + " \"drServiceYn\":\"N\",\n" + " \"fwInfoList\":[\n" + " {\n" - + " \"partNumber\":\"SAA38690433\",\n" + " \"checksum\":\"00000000\",\n" - + " \"verOrder\":0\n" + " }\n" + " ],\n" - + " \"guideTypeYn\":\"Y\",\n" + " \"guideType\":\"RAC_TYPE1\",\n" - + " \"regDtUtc\":\"20200909073555\",\n" + " \"regIndex\":0,\n" - + " \"groupableYn\":\"Y\",\n" + " \"controllableYn\":\"Y\",\n" - + " \"combinedProductYn\":\"N\",\n" + " \"masterYn\":\"Y\",\n" - + " \"pccModelYn\":\"N\",\n" + " \"sdsPid\":{\n" + " \"sds4\":\"\",\n" - + " \"sds3\":\"\",\n" + " \"sds2\":\"\",\n" + " \"sds1\":\"\"\n" - + " },\n" + " \"autoOrderYn\":\"N\",\n" - + " \"modelNm\":\"RAC_056905_WW\",\n" + " \"initDevice\":false,\n" - + " \"existsEntryPopup\":\"N\",\n" + " \"tclcount\":0\n" + " },\n" - + " {\n" + " \"appType\":\"NUTS\",\n" + " \"modelCountryCode\":\"WW\",\n" - + " \"countryCode\":\"BR\",\n" + " \"modelName\":\"RAC_056905_WW\",\n" - + " \"deviceType\":401,\n" + " \"deviceCode\":\"AI01\",\n" - + " \"alias\":\"Office\",\n" + " \"deviceId\":\"abra-cadabra-0001-5772\",\n" - + " \"fwVer\":\"\",\n" - + " \"imageFileName\":\"ac_home_wall_airconditioner_img.png\",\n" - + " \"imageUrl\":\"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6\",\n" - + " \"smallImageUrl\":\"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5\",\n" - + " \"ssid\":\"smart-gameficacao\",\n" + " \"softapId\":\"\",\n" - + " \"softapPass\":\"\",\n" + " \"macAddress\":\"\",\n" - + " \"networkType\":\"02\",\n" + " \"timezoneCode\":\"America/Sao_Paulo\",\n" - + " \"timezoneCodeAlias\":\"Brazil/Sao Paulo\",\n" + " \"utcOffset\":-3,\n" - + " \"utcOffsetDisplay\":\"-03:00\",\n" + " \"dstOffset\":-2,\n" - + " \"dstOffsetDisplay\":\"-02:00\",\n" + " \"curOffset\":-2,\n" - + " \"curOffsetDisplay\":\"-02:00\",\n" - + " \"sdsGuide\":\"{\\\"deviceCode\\\":\\\"AI01\\\"}\",\n" + " \"newRegYn\":\"N\",\n" - + " \"remoteControlType\":\"\",\n" + " \"userNo\":\"BR2004259832795\",\n" - + " \"tftYn\":\"N\",\n" + " \"modelJsonVer\":12.11,\n" - + " \"modelJsonUri\":\"https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f\",\n" - + " \"appModuleVer\":12.49,\n" - + " \"appModuleUri\":\"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155\",\n" - + " \"appRestartYn\":\"Y\",\n" + " \"appModuleSize\":6082481,\n" - + " \"langPackProductTypeVer\":59.9,\n" - + " \"langPackProductTypeUri\":\"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea\",\n" - + " \"deviceState\":\"E\",\n" + " \"snapshot\":{\n" - + " \"airState.windStrength\":8.0,\n" + " \"airState.wMode.lowHeating\":0.0,\n" - + " \"airState.diagCode\":0.0,\n" - + " \"airState.lightingState.displayControl\":1.0,\n" - + " \"airState.wDir.hStep\":0.0,\n" + " \"mid\":8.4615358E7,\n" - + " \"airState.energy.onCurrent\":476.0,\n" - + " \"airState.wMode.airClean\":0.0,\n" - + " \"airState.quality.sensorMon\":0.0,\n" - + " \"airState.energy.accumulatedTime\":0.0,\n" - + " \"airState.miscFuncState.antiBugs\":0.0,\n" - + " \"airState.tempState.target\":18.0,\n" + " \"airState.operation\":1.0,\n" - + " \"airState.wMode.jet\":0.0,\n" + " \"airState.wDir.vStep\":2.0,\n" - + " \"timestamp\":1.643248573766E12,\n" + " \"airState.powerSave.basic\":0.0,\n" - + " \"airState.quality.PM10\":0.0,\n" + " \"static\":{\n" - + " \"deviceType\":\"401\",\n" + " \"countryCode\":\"BR\"\n" - + " },\n" + " \"airState.quality.overall\":0.0,\n" - + " \"airState.tempState.current\":25.0,\n" - + " \"airState.miscFuncState.extraOp\":0.0,\n" - + " \"airState.energy.accumulated\":0.0,\n" - + " \"airState.reservation.sleepTime\":0.0,\n" - + " \"airState.miscFuncState.autoDry\":0.0,\n" - + " \"airState.reservation.targetTimeToStart\":0.0,\n" + " \"meta\":{\n" - + " \"allDeviceInfoUpdate\":false,\n" - + " \"messageId\":\"fVz2AE-2SC-rf3GnerGdeQ\"\n" + " },\n" - + " \"airState.quality.PM1\":0.0,\n" + " \"airState.wMode.smartCare\":0.0,\n" - + " \"airState.quality.PM2\":0.0,\n" + " \"online\":true,\n" - + " \"airState.opMode\":0.0,\n" - + " \"airState.reservation.targetTimeToStop\":0.0,\n" - + " \"airState.filterMngStates.maxTime\":0.0,\n" - + " \"airState.filterMngStates.useTime\":0.0\n" + " },\n" - + " \"online\":true,\n" + " \"platformType\":\"thinq2\",\n" - + " \"area\":45883,\n" + " \"regDt\":2.0220111184827E13,\n" - + " \"blackboxYn\":\"Y\",\n" + " \"modelProtocol\":\"STANDARD\",\n" - + " \"order\":0,\n" + " \"drServiceYn\":\"N\",\n" + " \"fwInfoList\":[\n" - + " {\n" + " \"checksum\":\"00004105\",\n" - + " \"order\":1.0,\n" + " \"partNumber\":\"SAA40128563\"\n" - + " }\n" + " ],\n" + " \"modemInfo\":{\n" - + " \"appVersion\":\"clip_hna_v1.9.116\",\n" - + " \"modelName\":\"RAC_056905_WW\",\n" + " \"modemType\":\"QCOM_QCA4010\",\n" - + " \"ruleEngine\":\"y\"\n" + " },\n" + " \"guideTypeYn\":\"Y\",\n" - + " \"guideType\":\"RAC_TYPE1\",\n" + " \"regDtUtc\":\"20220111204827\",\n" - + " \"regIndex\":0,\n" + " \"groupableYn\":\"Y\",\n" - + " \"controllableYn\":\"Y\",\n" + " \"combinedProductYn\":\"N\",\n" - + " \"masterYn\":\"Y\",\n" + " \"pccModelYn\":\"N\",\n" + " \"sdsPid\":{\n" - + " \"sds4\":\"\",\n" + " \"sds3\":\"\",\n" + " \"sds2\":\"\",\n" - + " \"sds1\":\"\"\n" + " },\n" + " \"autoOrderYn\":\"N\",\n" - + " \"initDevice\":false,\n" + " \"existsEntryPopup\":\"N\",\n" - + " \"tclcount\":0\n" + " }\n" + " ],\n" + " \"group\":[\n" + " \n" - + " ]\n" + " }\n" + "}"; - private String secretKey = "gregre9812012910291029120912091209"; - private String oauthTokenSearchKeyReturned = "{\"returnData\":\"" + secretKey + "\"}"; - private String refreshToken = "12897238974bb327862378ef290128390273aa7389723894734de"; - private String accessToken = "11a1222c39f16a5c8b3fa45bb4c9be2e00a29a69dced2fa7fe731f1728346ee669f1a96d1f0b4925e5aa330b6dbab882772"; - private String sessionTokenReturned = "{\"status\":1,\"access_token\":\"" + accessToken - + "\",\"expires_in\":\"3600\",\"refresh_token\":\"" + refreshToken - + "\",\"oauth2_backend_url\":\"http://localhost:8880/\"}"; - - private String getCurrentTimestamp() { - SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - return sdf.format(new Date()); - } - - @Test - public void testDiscoveryThings() { - stubFor(get(GATEWAY_SERVICE_PATH).willReturn(ok(gtwResponse))); - String preLoginPwd = RestUtils.getPreLoginEncPwd(fakePassword); - stubFor(post("/spx" + PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) - .willReturn(ok(preLoginResponse))); - URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + OAUTH_SEARCH_KEY_PATH) - .queryParam("key_name", "OAUTH_SECRETKEY").queryParam("sever_type", "OP").build(); - stubFor(get(String.format("%s?%s", uri.getPath(), uri.getQuery())).willReturn(ok(oauthTokenSearchKeyReturned))); - stubFor(post(V2_SESSION_LOGIN_PATH + fakeUserName).withRequestBody(containing("user_auth2=SOME_DUMMY_ENC_PWD")) - .withHeader("X-Signature", equalTo("SOME_DUMMY_SIGNATURE")) - .withHeader("X-Timestamp", equalTo("1643236928")).willReturn(ok(loginSessionResponse))); - stubFor(get(V2_USER_INFO).willReturn(ok(userInfoReturned))); - stubFor(get("/v1" + V2_LS_PATH).willReturn(ok(dashboardListReturned))); - String currTimestamp = getCurrentTimestamp(); - Map empData = new LinkedHashMap<>(); - empData.put("account_type", userIdType); - empData.put("country_code", fakeCountry); - empData.put("username", fakeUserName); - - stubFor(post("/emp/oauth2/token/empsession").withRequestBody(containing("account_type=" + userIdType)) - .withRequestBody(containing("country_code=" + fakeCountry)) - .withRequestBody(containing("username=" + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8))) - .withHeader("lgemp-x-session-key", equalTo(loginSessionId)).willReturn(ok(sessionTokenReturned))); - // faking some constants - LGThinqBindingConstants.GATEWAY_URL = "http://localhost:8880" + GATEWAY_SERVICE_PATH; - LGThinqBindingConstants.V2_EMP_SESS_URL = "http://localhost:8880/emp/oauth2/token/empsession"; - Bridge fakeThing = mock(Bridge.class); - ThingUID fakeThingUid = mock(ThingUID.class); - when(fakeThingUid.getId()).thenReturn(fakeBridgeName); - when(fakeThing.getUID()).thenReturn(fakeThingUid); - String tempDir = System.getProperty("java.io.tmpdir"); - LGThinqBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; - LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; - LGBridgeHandler b = new LGBridgeHandler(fakeThing); - LGBridgeHandler spyBridge = spy(b); - doReturn(new LGThinqConfiguration(fakeUserName, fakePassword, fakeCountry, fakeLanguage, 60)).when(spyBridge) - .getConfigAs(any(Class.class)); - spyBridge.initialize(); - LGApiClientService service1 = LGApiV1ClientServiceImpl.getInstance(); - LGApiClientService service2 = LGApiV2ClientServiceImpl.getInstance(); - TokenManager tokenManager = TokenManager.getInstance(); - try { - if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { - tokenManager.oauthFirstRegistration(fakeBridgeName, fakeLanguage, fakeCountry, fakeUserName, - fakePassword); - } - List devices = service2.listAccountDevices("bridgeTest"); - assertEquals(devices.size(), 2); - } catch (Exception e) { - logger.error("Error testing facade", e); - } - } -} From 943097e63fa14af69e576558121f5107c815b3fe Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 12:54:10 -0300 Subject: [PATCH 023/130] [lgthinq] adjusting class names & references, removed portuguese TODOs. Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 82 +++++++++---------- .../internal/LGThinqBindingConstants.java | 14 ++-- .../internal/LGThinqConfiguration.java | 16 ++-- .../lgthinq/internal/LGThinqDeviceThing.java | 6 +- .../internal/errors/LGApiException.java | 31 ------- .../LGDeviceV1MonitorExpiredException.java | 32 -------- .../errors/LGDeviceV1OfflineException.java | 33 -------- .../internal/errors/LGGatewayException.java | 27 ------ .../handler/LGThinqBridgeHandler.java | 34 ++++---- .../lgapi/LGThinqApiClientService.java | 28 +++---- .../lgapi/LGThinqApiClientServiceImpl.java | 28 +++---- .../lgapi/LGThinqApiV1ClientServiceImpl.java | 82 +++++++++---------- .../lgapi/LGThinqApiV2ClientServiceImpl.java | 58 ++++++------- 13 files changed, 172 insertions(+), 299 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index ea0c1b7859654..1ea6d4f58242d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -12,18 +12,12 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; - -import java.util.*; -import java.util.concurrent.*; - import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; @@ -42,6 +36,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.*; +import java.util.concurrent.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + /** * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. @@ -65,10 +65,10 @@ public class LGThinqAirConditionerHandler extends BaseThingHandler implements LG private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, ThingStatusDetail.CONFIGURATION_ERROR); - private @Nullable ScheduledFuture thingStatePoolingJob; + private @Nullable ScheduledFuture thingStatePollingJob; private @Nullable Future commandExecutorQueueJob; // *** Long running isolated threadpools. - private final ScheduledExecutorService poolingScheduler = Executors.newScheduledThreadPool(1); + private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); private final ExecutorService executorService = Executors.newFixedThreadPool(1); private boolean monitorV1Began = false; @@ -128,7 +128,7 @@ private void initializeThing(@Nullable ThingStatus bridgeStatus) { if (!deviceId.isBlank()) { try { updateChannelDynStateDescription(); - } catch (LGApiException e) { + } catch (LGThinqApiException e) { logger.error( "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); } @@ -173,10 +173,10 @@ private void stopCommandExecutorQueueJob() { } } - protected void startThingStatePooling() { - if (thingStatePoolingJob == null || thingStatePoolingJob.isDone()) { - thingStatePoolingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, - DEFAULT_STATE_POOLING_UPDATE_DELAY, TimeUnit.SECONDS); + protected void startThingStatePolling() { + if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { + thingStatePollingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, + DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); } } @@ -221,7 +221,7 @@ private void updateThingStateFromLG() { } private ScheduledExecutorService getLocalScheduler() { - return poolingScheduler; + return pollingScheduler; } private String getBridgeId() { @@ -250,7 +250,7 @@ private String emptyIfNull(@Nullable String value) { } @Override - public void updateChannelDynStateDescription() throws LGApiException { + public void updateChannelDynStateDescription() throws LGThinqApiException { ACCapability acCap = getAcCapabilities(); if (isLinked(opModeChannelUID)) { List options = new ArrayList<>(); @@ -267,7 +267,7 @@ public void updateChannelDynStateDescription() throws LGApiException { } @Override - public ACCapability getAcCapabilities() throws LGApiException { + public ACCapability getAcCapabilities() throws LGThinqApiException { if (acCapability == null) { acCapability = lgThinqApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); } @@ -275,7 +275,7 @@ public ACCapability getAcCapabilities() throws LGApiException { } @Nullable - private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiException { + private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { return lgThinqApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); @@ -285,14 +285,14 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiExcepti monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); monitorV1Began = true; } - } catch (LGDeviceV1OfflineException e) { + } catch (LGThinqDeviceV1OfflineException e) { forceStopDeviceV1Monitor(deviceId); ACSnapShot shot = new ACSnapShotV1(); shot.setOnline(false); return shot; } catch (Exception e) { forceStopDeviceV1Monitor(deviceId); - throw new LGApiException("Error starting device monitor in LG API for the device:" + deviceId, e); + throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); } int retries = 10; ACSnapShot shot; @@ -305,7 +305,7 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiExcepti } Thread.sleep(500); retries--; - } catch (LGDeviceV1MonitorExpiredException e) { + } catch (LGThinqDeviceV1MonitorExpiredException e) { forceStopDeviceV1Monitor(deviceId); logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); return null; @@ -314,34 +314,34 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiExcepti // interaction // Force restart monitoring because of the errors returned (just in case) forceStopDeviceV1Monitor(deviceId); - throw new LGApiException("Error getting monitor data for the device:" + deviceId, e); + throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); } } forceStopDeviceV1Monitor(deviceId); - throw new LGApiException("Exhausted trying to get monitor data for the device:" + deviceId); + throw new LGThinqApiException("Exhausted trying to get monitor data for the device:" + deviceId); } } - protected void stopThingStatePooling() { - if (thingStatePoolingJob != null && !thingStatePoolingJob.isDone()) { - logger.debug("Stopping LG thinq pooling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); - thingStatePoolingJob.cancel(true); + protected void stopThingStatePolling() { + if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { + logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePollingJob.cancel(true); } } private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { - // start the thing pooling - startThingStatePooling(); + // start the thing polling + startThingStatePolling(); } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { // comunication error is not a specific Bridge error, then we must analise it to give // this thinq the change to recovery from communication errors if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { - // in case of status offline, I only stop the pooling if is not an COMMUNICATION_ERROR or if + // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if // the bridge is out - stopThingStatePooling(); + stopThingStatePolling(); } } @@ -356,7 +356,7 @@ protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetai @Override public void onDeviceAdded(LGDevice device) { - // TODO - handle it + // TODO - handle it. Think if it's needed } @Override @@ -381,27 +381,27 @@ public String getDeviceUriJsonConfig() { @Override public boolean onDeviceStateChanged() { - // TODO - HANDLE IT + // TODO - HANDLE IT, Think if it's needed return false; } @Override public void onDeviceRemoved() { - // TODO - HANDLE IT + // TODO - HANDLE IT, Think if it's needed } @Override public void onDeviceGone() { - // TODO - HANDLE IT + // TODO - HANDLE IT, Think if it's needed } @Override public void dispose() { - if (thingStatePoolingJob != null) { - thingStatePoolingJob.cancel(true); - stopThingStatePooling(); + if (thingStatePollingJob != null) { + thingStatePollingJob.cancel(true); + stopThingStatePolling(); stopCommandExecutorQueueJob(); - thingStatePoolingJob = null; + thingStatePollingJob = null; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 4165da3a117c8..56cedc0759465 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.lgthinq.internal; -import java.io.File; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; import org.openhab.core.OpenHAB; import org.openhab.core.thing.ThingTypeUID; +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + /** * The {@link LGThinqBindingConstants} class defines common constants, which are * used across the whole binding. @@ -53,7 +53,7 @@ public class LGThinqBindingConstants { public static final String V2_DEVICE_CONFIG_PATH = "service/devices/"; public static final String V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/control-sync"; public static final String V1_START_MON_PATH = "rti/rtiMon"; - public static final String V1_POOL_MON_PATH = "rti/rtiResult"; + public static final String V1_MON_DATA_PATH = "rti/rtiResult"; public static final String V1_CONTROL_OP = "rti/rtiControl"; public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; public static final String GATEWAY_SERVICE_PATH = "/v1/service/application/gateway-uri"; @@ -98,7 +98,7 @@ public class LGThinqBindingConstants { public static final int SEARCH_TIME = 20; // delay between each devices's scan for state changes (in seconds) - public static final int DEFAULT_STATE_POOLING_UPDATE_DELAY = 30; + public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 30; // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java index c3a618b7dae5e..5bb094898006a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java @@ -12,8 +12,6 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - import org.eclipse.jdt.annotation.NonNullByDefault; /** @@ -30,18 +28,18 @@ public class LGThinqConfiguration { public String password = ""; public String country = ""; public String language = ""; - public Integer poolingIntervalSec = 0; + public Integer pollingIntervalSec = 0; public LGThinqConfiguration() { } public LGThinqConfiguration(String username, String password, String country, String language, - Integer poolingIntervalSec) { + Integer pollingIntervalSec) { this.username = username; this.password = password; this.country = country; this.language = language; - this.poolingIntervalSec = poolingIntervalSec; + this.pollingIntervalSec = pollingIntervalSec; } public String getUsername() { @@ -60,8 +58,8 @@ public String getLanguage() { return language; } - public Integer getPoolingIntervalSec() { - return poolingIntervalSec; + public Integer getPollingIntervalSec() { + return pollingIntervalSec; } public void setUsername(String username) { @@ -80,7 +78,7 @@ public void setLanguage(String language) { this.language = language; } - public void setPoolingIntervalSec(Integer poolingIntervalSec) { - this.poolingIntervalSec = poolingIntervalSec; + public void setPollingIntervalSec(Integer pollingIntervalSec) { + this.pollingIntervalSec = pollingIntervalSec; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index 64c77140d443a..ddfc7a165d4f7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -13,7 +13,7 @@ package org.openhab.binding.lgthinq.internal; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgapi.model.ACCapability; import org.openhab.binding.lgthinq.lgapi.model.LGDevice; @@ -41,7 +41,7 @@ public interface LGThinqDeviceThing { void onDeviceGone(); - void updateChannelDynStateDescription() throws LGApiException; + void updateChannelDynStateDescription() throws LGThinqApiException; - ACCapability getAcCapabilities() throws LGApiException; + ACCapability getAcCapabilities() throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java deleted file mode 100644 index 95ca53e421209..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGApiException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGApiException extends LGThinqException { - public LGApiException(String message, Throwable cause) { - super(message, cause); - } - - public LGApiException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java deleted file mode 100644 index 3272c29338952..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGDeviceV1MonitorExpiredException} - Normally caught by V1 API in monitoring device. - * After long-running moniotor, it indicates the need to refresh the monitor. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGDeviceV1MonitorExpiredException extends LGThinqException { - public LGDeviceV1MonitorExpiredException(String message, Throwable cause) { - super(message, cause); - } - - public LGDeviceV1MonitorExpiredException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java deleted file mode 100644 index dee34280a8c21..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGDeviceV1OfflineException} - Normally caught by V1 API in monitoring device. - * When the device is OFFLINE (away from internet), the API doesn't return data information and this - * exception is thrown to indicate that this device is offline for monitoring - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGDeviceV1OfflineException extends LGThinqException { - public LGDeviceV1OfflineException(String message, Throwable cause) { - super(message, cause); - } - - public LGDeviceV1OfflineException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java deleted file mode 100644 index 20942017c4a1a..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGGatewayException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGGatewayException extends LGThinqException { - public LGGatewayException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index de1ee7691abbe..24c7c52adce07 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -12,17 +12,6 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; @@ -42,6 +31,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; + /** * The {@link LGThinqBridgeHandler} * @@ -82,7 +82,7 @@ public LGThinqBridgeHandler(Bridge bridge) { final ReentrantLock pollingLock = new ReentrantLock(); /** - * Abstract Runnable Pooling Class to schedule sincronization status of the Bridge Thing Kinds ! + * Abstract Runnable Polling Class to schedule sincronization status of the Bridge Thing Kinds ! */ abstract class PollingRunnable implements Runnable { protected final String bridgeName; @@ -285,25 +285,25 @@ public void initialize() { "@text/error.mandotory-fields-missing"); } else { updateStatus(ThingStatus.UNKNOWN); - startLGDevicePolling(); + startLGThinqDevicePolling(); } } - private void startLGDevicePolling() { + private void startLGThinqDevicePolling() { // stop current scheduler, if any if (devicePollingJob != null && !devicePollingJob.isDone()) { devicePollingJob.cancel(true); } long pollingInterval; - int configPollingInterval = lgthinqConfig.getPoolingIntervalSec(); - // It's not recommended to pool for resources in LG API short intervals to do not enter in BlackList + int configPollingInterval = lgthinqConfig.getPollingIntervalSec(); + // It's not recommended to polling for resources in LG API short intervals to do not enter in BlackList if (configPollingInterval < 300) { pollingInterval = TimeUnit.SECONDS.toSeconds(300); logger.info("Wrong configuration value for polling interval. Using default value: {}s", pollingInterval); } else { pollingInterval = configPollingInterval; } - // submit instantlly and schedule for the next pooling interval. + // submit instantlly and schedule for the next polling interval. scheduler.submit(lgDevicePollingRunnable); devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, pollingInterval, pollingInterval, TimeUnit.SECONDS); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index 6ecbf3e002343..8e5f80a8553bb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -19,9 +19,9 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgapi.model.*; @@ -33,36 +33,36 @@ @NonNullByDefault public interface LGThinqApiClientService { - List listAccountDevices(String bridgeName) throws LGApiException; + List listAccountDevices(String bridgeName) throws LGThinqApiException; - Map getDeviceSettings(String bridgeName, String deviceId) throws LGApiException; + Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException; /** * Retrieve actual data from device (its sensors and points states). * * @param deviceId device number * @return return snapshot state of the device - * @throws LGApiException if some error interacting with LG API Server occur. + * @throws LGThinqApiException if some error interacting with LG API Server occur. */ @Nullable - ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException; + ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; - void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGApiException; + void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; - void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException; + void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; - void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException; + void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; - void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGApiException; + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGThinqApiException; String startMonitor(String bridgeName, String deviceId) - throws LGApiException, LGDeviceV1OfflineException, IOException; + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; - ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException; + ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; @Nullable ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) - throws LGApiException, LGDeviceV1MonitorExpiredException, IOException; + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index edc2b07c5c5b0..bb5d0c4f97856 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -23,7 +23,7 @@ import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgapi.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,10 +72,10 @@ static Map getCommonHeaders(String language, String country, Str * Even using V2 URL, this endpoint support grab informations about account devices from V1 and V2. * * @return list os LG Devices. - * @throws LGApiException if some communication error occur. + * @throws LGThinqApiException if some communication error occur. */ @Override - public List listAccountDevices(String bridgeName) throws LGApiException { + public List listAccountDevices(String bridgeName) throws LGThinqApiException { try { TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); @@ -84,7 +84,7 @@ public List listAccountDevices(String bridgeName) throws LGApiExceptio RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); return handleListAccountDevicesResult(resp); } catch (Exception e) { - throw new LGApiException("Erros list account devices from LG Server API", e); + throw new LGThinqApiException("Erros list account devices from LG Server API", e); } } @@ -94,10 +94,10 @@ public List listAccountDevices(String bridgeName) throws LGApiExceptio * * @param deviceId device ID for de desired V2 LG Thinq. * @return return map containing metamodel of settings and snapshot - * @throws LGApiException if some communication error occur. + * @throws LGThinqApiException if some communication error occur. */ @Override - public Map getDeviceSettings(String bridgeName, String deviceId) throws LGApiException { + public Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException { try { TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) @@ -107,28 +107,28 @@ public Map getDeviceSettings(String bridgeName, String deviceId) RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); return handleDeviceSettingsResult(resp); } catch (Exception e) { - throw new LGApiException("Erros list account devices from LG Server API", e); + throw new LGThinqApiException("Erros list account devices from LG Server API", e); } } - private Map handleDeviceSettingsResult(RestResult resp) throws LGApiException { + private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { return genericHandleDeviceSettingsResult(resp, logger, objectMapper); } @SuppressWarnings("unchecked") static Map genericHandleDeviceSettingsResult(RestResult resp, Logger logger, - ObjectMapper objectMapper) throws LGApiException { + ObjectMapper objectMapper) throws LGThinqApiException { Map deviceSettings; if (resp.getStatusCode() != 200) { logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGApiException(String.format("Error calling device settings from LG Server API. The reason is:%s", + throw new LGThinqApiException(String.format("Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (!"0000".equals(deviceSettings.get("resultCode"))) { - throw new LGApiException( + throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", deviceSettings.get("resultCode"))); } @@ -142,19 +142,19 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo } @SuppressWarnings("unchecked") - private List handleListAccountDevicesResult(RestResult resp) throws LGApiException { + private List handleListAccountDevicesResult(RestResult resp) throws LGThinqApiException { Map devicesResult; List devices; if (resp.getStatusCode() != 200) { logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGApiException(String.format("Error calling device list from LG Server API. The reason is:%s", + throw new LGThinqApiException(String.format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (!"0000".equals(devicesResult.get("resultCode"))) { - throw new LGApiException( + throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", devicesResult.get("resultCode"))); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index 51de488bb84f6..ff8e1f3ce3479 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -12,14 +12,9 @@ */ package org.openhab.binding.lgthinq.lgapi; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -27,17 +22,20 @@ import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgapi.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.IOException; +import java.util.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; /** * The {@link LGThinqApiV1ClientServiceImpl} @@ -74,11 +72,11 @@ protected TokenManager getTokenManager() { * * @param deviceId device ID for de desired V2 LG Thinq. * @return return map containing metamodel of settings and snapshot - * @throws LGApiException if some communication error occur. + * @throws LGThinqApiException if some communication error occur. */ @Override @Nullable - public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException { + public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { throw new UnsupportedOperationException("Method not supported in V1 API device."); } @@ -99,46 +97,46 @@ public RestResult sendControlCommands(String bridgeName, String deviceId, String @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGApiException { + throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", newPowerState.commandValue()); handleV1GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting device power", e); + throw new LGThinqApiException("Error adjusting device power", e); } } @Override - public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException { + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "OpMode", newOpMode); handleV1GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting operation mode", e); + throw new LGThinqApiException("Error adjusting operation mode", e); } } @Override - public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException { + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "WindStrength", newFanSpeed); handleV1GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting fan speed", e); + throw new LGThinqApiException("Error adjusting fan speed", e); } } @Override public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGApiException { + throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "TempCfg", newTargetTemp.commandValue()); handleV1GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting target temperature", e); + throw new LGThinqApiException("Error adjusting target temperature", e); } } @@ -147,11 +145,11 @@ public void changeTargetTemperature(String bridgeName, String deviceId, ACTarget * * @param deviceId Device ID * @return Work1 to be uses to grab data during monitoring. - * @throws LGApiException If some communication error occur. + * @throws LGThinqApiException If some communication error occur. */ @Override public String startMonitor(String bridgeName, String deviceId) - throws LGApiException, LGDeviceV1OfflineException, IOException { + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -166,7 +164,7 @@ public String startMonitor(String bridgeName, String deviceId) @NonNull private Map handleV1GenericErrorResult(@Nullable RestResult resp) - throws LGApiException, LGDeviceV1OfflineException { + throws LGThinqApiException, LGThinqDeviceV1OfflineException { Map metaResult; Map envelope = Collections.emptyMap(); if (resp == null) { @@ -174,7 +172,7 @@ private Map handleV1GenericErrorResult(@Nullable RestResult resp } if (resp.getStatusCode() != 200) { logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGApiException( + throw new LGThinqApiException( String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { @@ -182,14 +180,14 @@ private Map handleV1GenericErrorResult(@Nullable RestResult resp }); envelope = (Map) metaResult.get("lgedmRoot"); if (envelope == null) { - throw new LGApiException(String.format( + throw new LGThinqApiException(String.format( "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); } else if (!"0000".equals(envelope.get("returnCd"))) { if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { // Disconnected Device - throw new LGDeviceV1OfflineException("Device is offline. No data available"); + throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); } - throw new LGApiException( + throw new LGThinqApiException( String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", metaResult.get("returnCd"))); } @@ -202,7 +200,7 @@ private Map handleV1GenericErrorResult(@Nullable RestResult resp @Override public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGApiException, RefreshTokenException, IOException, LGDeviceV1OfflineException { + throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -216,9 +214,9 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) @Override @Nullable public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) - throws LGApiException, LGDeviceV1MonitorExpiredException, IOException { + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_POOL_MON_PATH); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" @@ -230,7 +228,7 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev // offline flag. try { envelop = handleV1GenericErrorResult(resp); - } catch (LGDeviceV1OfflineException e) { + } catch (LGThinqDeviceV1OfflineException e) { ACSnapShot shot = new ACSnapShotV2(); shot.setOnline(false); return shot; @@ -239,7 +237,7 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev && ((Map) envelop.get("workList")).get("returnData") != null) { Map workList = ((Map) envelop.get("workList")); if (!"0000".equals(workList.get("returnCode"))) { - LGDeviceV1MonitorExpiredException e = new LGDeviceV1MonitorExpiredException( + LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); logger.warn("{}", e.getMessage()); throw e; @@ -266,11 +264,11 @@ private File getCapFileForDevice(String deviceId) { * @param deviceId ID of the device * @param uri URI of the config capanility * @return return simplified capability - * @throws LGApiException If some error occurr + * @throws LGThinqApiException If some error occurr */ @Override @NonNull - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException { + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { try { File regFile = getCapFileForDevice(deviceId); ACCapability acCap = new ACCapability(); @@ -288,12 +286,12 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for } Map cap = (Map) mapper.get("Value"); if (cap == null) { - throw new LGApiException("Error extracting capabilities supported by the device"); + throw new LGThinqApiException("Error extracting capabilities supported by the device"); } Map opModes = (Map) cap.get("OpMode"); if (opModes == null) { - throw new LGApiException("Error extracting opModes supported by the device"); + throw new LGThinqApiException("Error extracting opModes supported by the device"); } else { Map modes = new HashMap(); ((Map) opModes.get("option")).forEach((k, v) -> { @@ -303,7 +301,7 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for } Map fanSpeed = (Map) cap.get("WindStrength"); if (fanSpeed == null) { - throw new LGApiException("Error extracting fanSpeed supported by the device"); + throw new LGThinqApiException("Error extracting fanSpeed supported by the device"); } else { Map fanModes = new HashMap(); ((Map) fanSpeed.get("option")).forEach((k, v) -> { @@ -324,7 +322,7 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for return acCap; } catch (IOException e) { - throw new LGApiException("Error reading IO interface", e); + throw new LGThinqApiException("Error reading IO interface", e); } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index 9e4a2dae575f4..69e093b486303 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -31,9 +31,9 @@ import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgapi.model.*; import org.slf4j.Logger; @@ -83,11 +83,11 @@ private Map getCommonV2Headers(String language, String country, * * @param deviceId device ID for de desired V2 LG Thinq. * @return return map containing metamodel of settings and snapshot - * @throws LGApiException if some communication error occur. + * @throws LGThinqApiException if some communication error occur. */ @Override @Nullable - public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException { + public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { Map deviceSettings = getDeviceSettings(bridgeName, deviceId); if (deviceSettings.get("snapshot") != null) { Map snapMap = (Map) deviceSettings.get("snapshot"); @@ -113,51 +113,51 @@ public RestResult sendControlCommands(String bridgeName, String deviceId, String @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGApiException { + throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", "airState.operation", newPowerState.commandValue()); handleV2GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting device power", e); + throw new LGThinqApiException("Error adjusting device power", e); } } @Override - public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException { + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.opMode", newOpMode); handleV2GenericErrorResult(resp); - } catch (LGApiException e) { + } catch (LGThinqApiException e) { throw e; } catch (Exception e) { - throw new LGApiException("Error adjusting operation mode", e); + throw new LGThinqApiException("Error adjusting operation mode", e); } } @Override - public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException { + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.windStrength", newFanSpeed); handleV2GenericErrorResult(resp); - } catch (LGApiException e) { + } catch (LGThinqApiException e) { throw e; } catch (Exception e) { - throw new LGApiException("Error adjusting operation mode", e); + throw new LGThinqApiException("Error adjusting operation mode", e); } } @Override public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGApiException { + throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.tempState.target", newTargetTemp.commandValue()); handleV2GenericErrorResult(resp); - } catch (LGApiException e) { + } catch (LGThinqApiException e) { throw e; } catch (Exception e) { - throw new LGApiException("Error adjusting operation mode", e); + throw new LGThinqApiException("Error adjusting operation mode", e); } } @@ -166,18 +166,18 @@ public void changeTargetTemperature(String bridgeName, String deviceId, ACTarget * * @param deviceId Device ID * @return Work1 to be uses to grab data during monitoring. - * @throws LGApiException If some communication error occur. + * @throws LGThinqApiException If some communication error occur. */ @Override public String startMonitor(String bridgeName, String deviceId) - throws LGApiException, LGDeviceV1OfflineException, IOException { + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { throw new UnsupportedOperationException("Not supported in V2 API."); } @Override @NonNull @SuppressWarnings("ignoring Map type check") - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException { + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { try { File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); ACCapability acCap = new ACCapability(); @@ -191,12 +191,12 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for }); Map cap = (Map) mapper.get("Value"); if (cap == null) { - throw new LGApiException("Error extracting capabilities supported by the device"); + throw new LGThinqApiException("Error extracting capabilities supported by the device"); } Map opModes = (Map) cap.get("airState.opMode"); if (opModes == null) { - throw new LGApiException("Error extracting opModes supported by the device"); + throw new LGThinqApiException("Error extracting opModes supported by the device"); } else { Map modes = new HashMap(); ((Map) opModes.get("value_mapping")).forEach((k, v) -> { @@ -206,7 +206,7 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for } Map fanSpeed = (Map) cap.get("airState.windStrength"); if (fanSpeed == null) { - throw new LGApiException("Error extracting fanSpeed supported by the device"); + throw new LGThinqApiException("Error extracting fanSpeed supported by the device"); } else { Map fanModes = new HashMap(); ((Map) fanSpeed.get("value_mapping")).forEach((k, v) -> { @@ -226,25 +226,25 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for acCap.getSupportedFanSpeed().remove("@NON"); return acCap; } catch (IOException e) { - throw new LGApiException("Error reading IO interface", e); + throw new LGThinqApiException("Error reading IO interface", e); } } - private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGApiException { + private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { Map metaResult; if (resp == null) { return; } if (resp.getStatusCode() != 200) { logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGApiException( + throw new LGThinqApiException( String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { }); if (!"0000".equals(metaResult.get("resultCode"))) { - throw new LGApiException( + throw new LGThinqApiException( String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", metaResult.get("resultCode"))); } @@ -255,20 +255,20 @@ private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGApiE } } - private Map handleDeviceSettingsResult(RestResult resp) throws LGApiException { + private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { return genericHandleDeviceSettingsResult(resp, logger, objectMapper); } @Override public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGApiException, RefreshTokenException, IOException, LGDeviceV1OfflineException { + throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { throw new UnsupportedOperationException("Not supported in V2 API."); } @Override @Nullable public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) - throws LGApiException, LGDeviceV1MonitorExpiredException, IOException { + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { throw new UnsupportedOperationException("Not supported in V2 API."); } } From e42c06db724521f969a2229edcf32bba671535d0 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 12:59:01 -0300 Subject: [PATCH 024/130] [lgthinq] Fixing code formatting. Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 12 +++++----- .../internal/LGThinqBindingConstants.java | 10 ++++---- .../lgapi/LGThinqApiClientService.java | 3 ++- .../lgapi/LGThinqApiClientServiceImpl.java | 8 +++---- .../lgapi/LGThinqApiV1ClientServiceImpl.java | 23 +++++++++++-------- .../lgapi/LGThinqApiV2ClientServiceImpl.java | 3 ++- 6 files changed, 32 insertions(+), 27 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 1ea6d4f58242d..04822cf0acb61 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -12,6 +12,12 @@ */ package org.openhab.binding.lgthinq.internal; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + +import java.util.*; +import java.util.concurrent.*; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -36,12 +42,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.concurrent.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; - /** * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 56cedc0759465..6b9bd4b85fa89 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.lgthinq.internal; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; -import org.openhab.core.OpenHAB; -import org.openhab.core.thing.ThingTypeUID; - import java.io.File; import java.util.Collections; import java.util.Map; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; +import org.openhab.core.OpenHAB; +import org.openhab.core.thing.ThingTypeUID; + /** * The {@link LGThinqBindingConstants} class defines common constants, which are * used across the whole binding. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index 8e5f80a8553bb..4bf11b52fa1c7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -53,7 +53,8 @@ public interface LGThinqApiClientService { void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; - void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGThinqApiException; + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGThinqApiException; String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index bb5d0c4f97856..ad584301199aa 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -121,8 +121,8 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo Map deviceSettings; if (resp.getStatusCode() != 200) { logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException(String.format("Error calling device settings from LG Server API. The reason is:%s", - resp.getJsonResponse())); + throw new LGThinqApiException(String.format( + "Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { @@ -147,8 +147,8 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG List devices; if (resp.getStatusCode() != 200) { logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException(String.format("Error calling device list from LG Server API. The reason is:%s", - resp.getJsonResponse())); + throw new LGThinqApiException(String + .format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index ff8e1f3ce3479..8c3fdb5cabbcd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -12,9 +12,14 @@ */ package org.openhab.binding.lgthinq.lgapi; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -30,12 +35,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.UriBuilder; -import java.io.File; -import java.io.IOException; -import java.util.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link LGThinqApiV1ClientServiceImpl} @@ -268,7 +270,8 @@ private File getCapFileForDevice(String deviceId) { */ @Override @NonNull - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) + throws LGThinqApiException { try { File regFile = getCapFileForDevice(deviceId); ACCapability acCap = new ACCapability(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index 69e093b486303..f9d080773cc06 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -177,7 +177,8 @@ public String startMonitor(String bridgeName, String deviceId) @Override @NonNull @SuppressWarnings("ignoring Map type check") - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) + throws LGThinqApiException { try { File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); ACCapability acCap = new ACCapability(); From 733b6d525e26b65774af24a5a81d04e8d8248b21 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 13:32:10 -0300 Subject: [PATCH 025/130] [lgthinq] Optimizing imports Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 12 +++---- .../internal/LGThinqBindingConstants.java | 10 +++--- .../internal/LGThinqHandlerFactory.java | 10 +++--- .../lgthinq/internal/api/TokenResult.java | 4 +-- .../lgthinq/internal/api/UserInfo.java | 4 +-- .../lgapi/LGThinqApiClientService.java | 8 ++--- .../lgapi/LGThinqApiClientServiceImpl.java | 18 +++++------ .../lgapi/LGThinqApiV1ClientServiceImpl.java | 20 ++++++------ .../lgapi/LGThinqApiV2ClientServiceImpl.java | 31 ++++++++++--------- .../lgthinq/lgapi/model/ACCapability.java | 4 +-- .../lgthinq/lgapi/model/ACSnapShot.java | 5 ++- .../lgthinq/lgapi/model/ACSnapShotV1.java | 3 +- .../lgthinq/lgapi/model/ACSnapShotV2.java | 3 +- .../binding/lgthinq/lgapi/model/LGDevice.java | 3 +- 14 files changed, 64 insertions(+), 71 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 04822cf0acb61..1ea6d4f58242d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -12,12 +12,6 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; - -import java.util.*; -import java.util.concurrent.*; - import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -42,6 +36,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.*; +import java.util.concurrent.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + /** * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 6b9bd4b85fa89..56cedc0759465 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.lgthinq.internal; -import java.io.File; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; import org.openhab.core.OpenHAB; import org.openhab.core.thing.ThingTypeUID; +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + /** * The {@link LGThinqBindingConstants} class defines common constants, which are * used across the whole binding. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 3b9b6ad3249d4..4d3364841c968 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -12,11 +12,6 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; - -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; @@ -34,6 +29,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Set; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; + /** * The {@link LGThinqHandlerFactory} is responsible for creating things and thing * handlers. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java index 9f678b54f2b28..004850e567886 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java @@ -12,11 +12,11 @@ */ package org.openhab.binding.lgthinq.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + import java.io.Serializable; import java.util.Date; -import org.eclipse.jdt.annotation.NonNullByDefault; - /** * The {@link TokenResult} Hold information about token and related entities * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java index e783df386503f..96869677fef93 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java @@ -12,10 +12,10 @@ */ package org.openhab.binding.lgthinq.internal.api; -import java.io.Serializable; - import org.eclipse.jdt.annotation.NonNullByDefault; +import java.io.Serializable; + /** * The {@link UserInfo} User Info (registered in LG Account) * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index 4bf11b52fa1c7..c2e2b2bb37669 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -12,10 +12,6 @@ */ package org.openhab.binding.lgthinq.lgapi; -import java.io.IOException; -import java.util.List; -import java.util.Map; - import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -25,6 +21,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgapi.model.*; +import java.io.IOException; +import java.util.List; +import java.util.Map; + /** * The {@link LGThinqApiClientService} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index ad584301199aa..cb3751a53d002 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -12,25 +12,23 @@ */ package org.openhab.binding.lgthinq.lgapi; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgapi.model.LGDevice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.core.UriBuilder; +import java.util.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; /** * The {@link LGThinqApiV1ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index 8c3fdb5cabbcd..53e5e8f4afad4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -12,14 +12,9 @@ */ package org.openhab.binding.lgthinq.lgapi; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -35,9 +30,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.IOException; +import java.util.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; /** * The {@link LGThinqApiV1ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index f9d080773cc06..baa4b2d5b9638 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -12,18 +12,9 @@ */ package org.openhab.binding.lgthinq.lgapi; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -39,9 +30,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; /** * The {@link LGThinqApiV2ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java index 8f047b9148c4c..ca18ab6a15de3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java @@ -12,12 +12,12 @@ */ package org.openhab.binding.lgthinq.lgapi.model; +import org.eclipse.jdt.annotation.NonNullByDefault; + import java.util.Collections; import java.util.List; import java.util.Map; -import org.eclipse.jdt.annotation.NonNullByDefault; - /** * The {@link ACCapability} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java index df8926d89b97a..3719415e454f8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * The {@link ACSnapShot} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java index 34002d3a58d42..4a9a5d5312a59 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi.model; +import com.fasterxml.jackson.annotation.JsonProperty; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * The {@link ACSnapShotV1} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java index a373367eccbf5..231e63dcad724 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi.model; +import com.fasterxml.jackson.annotation.JsonProperty; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * The {@link ACSnapShotV2} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java index 3413aaeaf0700..fcc42a5a770e7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import org.eclipse.jdt.annotation.NonNullByDefault; - import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.eclipse.jdt.annotation.NonNullByDefault; /** * The {@link LGDevice} From 93b4a67edd7c26adc85f239f6a7da030dfc6ea78 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 13:36:52 -0300 Subject: [PATCH 026/130] [lgthinq] reformating coding by spotless Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 12 +++---- .../internal/LGThinqBindingConstants.java | 10 +++--- .../internal/LGThinqHandlerFactory.java | 10 +++--- .../lgthinq/internal/api/TokenResult.java | 4 +-- .../lgthinq/internal/api/UserInfo.java | 4 +-- .../handler/LGThinqBridgeHandler.java | 22 ++++++------ .../lgapi/LGThinqApiClientService.java | 8 ++--- .../lgapi/LGThinqApiClientServiceImpl.java | 16 +++++---- .../lgapi/LGThinqApiV1ClientServiceImpl.java | 20 ++++++----- .../lgapi/LGThinqApiV2ClientServiceImpl.java | 34 ++++++++++--------- .../lgthinq/lgapi/model/ACCapability.java | 4 +-- .../lgthinq/lgapi/model/ACSnapShot.java | 5 +-- .../lgthinq/lgapi/model/ACSnapShotV1.java | 3 +- .../lgthinq/lgapi/model/ACSnapShotV2.java | 3 +- .../binding/lgthinq/lgapi/model/LGDevice.java | 3 +- 15 files changed, 84 insertions(+), 74 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 1ea6d4f58242d..04822cf0acb61 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -12,6 +12,12 @@ */ package org.openhab.binding.lgthinq.internal; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + +import java.util.*; +import java.util.concurrent.*; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -36,12 +42,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.concurrent.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; - /** * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 56cedc0759465..6b9bd4b85fa89 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.lgthinq.internal; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; -import org.openhab.core.OpenHAB; -import org.openhab.core.thing.ThingTypeUID; - import java.io.File; import java.util.Collections; import java.util.Map; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; +import org.openhab.core.OpenHAB; +import org.openhab.core.thing.ThingTypeUID; + /** * The {@link LGThinqBindingConstants} class defines common constants, which are * used across the whole binding. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 4d3364841c968..3b9b6ad3249d4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -12,6 +12,11 @@ */ package org.openhab.binding.lgthinq.internal; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; + +import java.util.Set; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; @@ -29,11 +34,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Set; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; - /** * The {@link LGThinqHandlerFactory} is responsible for creating things and thing * handlers. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java index 004850e567886..9f678b54f2b28 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java @@ -12,11 +12,11 @@ */ package org.openhab.binding.lgthinq.internal.api; -import org.eclipse.jdt.annotation.NonNullByDefault; - import java.io.Serializable; import java.util.Date; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link TokenResult} Hold information about token and related entities * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java index 96869677fef93..e783df386503f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java @@ -12,10 +12,10 @@ */ package org.openhab.binding.lgthinq.internal.api; -import org.eclipse.jdt.annotation.NonNullByDefault; - import java.io.Serializable; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link UserInfo} User Info (registered in LG Account) * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 24c7c52adce07..a76ff76453933 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -12,6 +12,17 @@ */ package org.openhab.binding.lgthinq.internal.handler; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; @@ -31,17 +42,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; - /** * The {@link LGThinqBridgeHandler} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index c2e2b2bb37669..4bf11b52fa1c7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -12,6 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi; +import java.io.IOException; +import java.util.List; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -21,10 +25,6 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgapi.model.*; -import java.io.IOException; -import java.util.List; -import java.util.Map; - /** * The {@link LGThinqApiClientService} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index cb3751a53d002..699fba682e1f6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -12,9 +12,12 @@ */ package org.openhab.binding.lgthinq.lgapi; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; @@ -25,10 +28,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.UriBuilder; -import java.util.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link LGThinqApiV1ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index 53e5e8f4afad4..8c3fdb5cabbcd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -12,9 +12,14 @@ */ package org.openhab.binding.lgthinq.lgapi; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -30,12 +35,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.UriBuilder; -import java.io.File; -import java.io.IOException; -import java.util.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link LGThinqApiV1ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index baa4b2d5b9638..b6d878e0c3452 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -12,9 +12,21 @@ */ package org.openhab.binding.lgthinq.lgapi; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.core.UriBuilder; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -30,19 +42,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.UriBuilder; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link LGThinqApiV2ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java index ca18ab6a15de3..8f047b9148c4c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java @@ -12,12 +12,12 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import org.eclipse.jdt.annotation.NonNullByDefault; - import java.util.Collections; import java.util.List; import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link ACCapability} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java index 3719415e454f8..df8926d89b97a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java @@ -12,11 +12,12 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * The {@link ACSnapShot} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java index 4a9a5d5312a59..34002d3a58d42 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java @@ -12,10 +12,11 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import com.fasterxml.jackson.annotation.JsonProperty; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * The {@link ACSnapShotV1} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java index 231e63dcad724..a373367eccbf5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java @@ -12,10 +12,11 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import com.fasterxml.jackson.annotation.JsonProperty; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * The {@link ACSnapShotV2} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java index fcc42a5a770e7..3413aaeaf0700 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java @@ -12,10 +12,11 @@ */ package org.openhab.binding.lgthinq.lgapi.model; +import org.eclipse.jdt.annotation.NonNullByDefault; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import org.eclipse.jdt.annotation.NonNullByDefault; /** * The {@link LGDevice} From ed4e035a7be6e86ffa10c2442592ed5408013213 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sun, 30 Jan 2022 11:57:11 -0300 Subject: [PATCH 027/130] [lgthinq] Organizing imports by spotless, moving constants to LGThinqBindingConstants. Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../lgthinq/internal/LGThinqAirConditionerHandler.java | 4 ++-- .../binding/lgthinq/internal/LGThinqBindingConstants.java | 4 ++++ .../binding/lgthinq/internal/LGThinqHandlerFactory.java | 6 +----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 04822cf0acb61..a9382b661c88c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -13,7 +13,6 @@ package org.openhab.binding.lgthinq.internal; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; import java.util.*; import java.util.concurrent.*; @@ -460,7 +459,8 @@ public void run() { case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON + : DevicePowerState.DV_POWER_OFF); } else { logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 6b9bd4b85fa89..177b3f90c9f1a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.lgthinq.internal; +import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; + import java.io.File; import java.util.Collections; import java.util.Map; @@ -34,6 +36,8 @@ public class LGThinqBindingConstants { public static final String BINDING_ID = "lgthinq"; public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, + THING_TYPE_BRIDGE); public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); public static final String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 3b9b6ad3249d4..06a98cd4fd38b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -15,8 +15,6 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; @@ -44,14 +42,12 @@ @Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.lgthinq") public class LGThinqHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, - THING_TYPE_BRIDGE); private final Logger logger = LoggerFactory.getLogger(LGThinqHandlerFactory.class); private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + return LGThinqBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); } @Override From fe033d568abd4f6f80f9142a7fb9d0e4d5561f45 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sun, 30 Jan 2022 15:02:42 -0300 Subject: [PATCH 028/130] [lgthinq] Moving constants to LGThinqBindingConstants. Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../binding/lgthinq/internal/LGThinqBindingConstants.java | 3 +-- .../binding/lgthinq/internal/LGThinqHandlerFactory.java | 2 +- .../binding/lgthinq/internal/handler/LGThinqBridgeHandler.java | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 177b3f90c9f1a..54d717236767c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,8 +12,6 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; - import java.io.File; import java.util.Collections; import java.util.Map; @@ -34,6 +32,7 @@ public class LGThinqBindingConstants { public static final String BINDING_ID = "lgthinq"; + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 06a98cd4fd38b..c89554b5e2c90 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -13,7 +13,7 @@ package org.openhab.binding.lgthinq.internal; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_BRIDGE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index a76ff76453933..81d3b36fd9277 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -24,7 +24,6 @@ import java.util.concurrent.locks.ReentrantLock; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; import org.openhab.binding.lgthinq.internal.api.TokenManager; @@ -48,7 +47,6 @@ * @author Nemer Daud - Initial contribution */ public class LGThinqBridgeHandler extends ConfigStatusBridgeHandler implements LGThinqBridge { - public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(LGThinqBindingConstants.BINDING_ID, "bridge"); private Map lGDeviceRegister = new ConcurrentHashMap<>(); private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); From 4917330655125545d0d9cbe6762d6f9014fae532 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Thu, 3 Feb 2022 12:19:08 -0300 Subject: [PATCH 029/130] [lgthinq] Repackaging, Enhanced data binding and some logs for troubleshooting Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 8 +- .../internal/LGThinqBindingConstants.java | 13 ++- .../lgthinq/internal/LGThinqDeviceThing.java | 4 +- .../handler/LGThinqBridgeHandler.java | 16 +-- .../lgthinq/lgapi/model/DevicePowerState.java | 71 ------------ .../lgthinq/lgapi/model/DeviceTypes.java | 42 ------- .../binding/lgthinq/lgapi/model/LGDevice.java | 107 ------------------ .../LGThinqApiClientService.java | 4 +- .../LGThinqApiClientServiceImpl.java | 4 +- .../LGThinqApiV1ClientServiceImpl.java | 4 +- .../LGThinqApiV2ClientServiceImpl.java | 4 +- .../model/ACCapability.java | 2 +- .../model/ACFanSpeed.java | 2 +- .../{lgapi => lgservices}/model/ACOpMode.java | 2 +- .../model/ACSnapShot.java | 2 +- .../model/ACSnapShotV1.java | 2 +- .../model/ACSnapShotV2.java | 2 +- .../model/ACTargetTmp.java | 2 +- 18 files changed, 38 insertions(+), 253 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/LGThinqApiClientService.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/LGThinqApiClientServiceImpl.java (98%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/LGThinqApiV1ClientServiceImpl.java (99%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/LGThinqApiV2ClientServiceImpl.java (99%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACCapability.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACFanSpeed.java (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACOpMode.java (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACSnapShot.java (98%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACSnapShotV1.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACSnapShotV2.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACTargetTmp.java (97%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index a9382b661c88c..a9d6282b084cc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -25,10 +25,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 54d717236767c..f9f6d136bbcbd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -18,7 +18,7 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.core.OpenHAB; import org.openhab.core.thing.ThingTypeUID; @@ -59,8 +59,9 @@ public class LGThinqBindingConstants { public static final String V1_MON_DATA_PATH = "rti/rtiResult"; public static final String V1_CONTROL_OP = "rti/rtiControl"; public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; - public static final String GATEWAY_SERVICE_PATH = "/v1/service/application/gateway-uri"; - public static String GATEWAY_URL = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH; + public static final String GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; + public static final String GATEWAY_SERVICE_PATH_V1 = "/api/common/gatewayUriList"; + public static String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; public static final String PRE_LOGIN_PATH = "/preLogin"; public static final String SECURITY_KEY = "nuts_securitykey"; public static final String APP_KEY = "wideq"; @@ -76,8 +77,10 @@ public class LGThinqBindingConstants { public static final String DEFAULT_LANGUAGE = "en-US"; public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; public static String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com/emp/oauth2/token/empsession"; - // v2 - public static final String API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; + public static final String API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; + + public static final String API_KEY_V1 = "wideq"; + public static final String API_SECURITY_KEY_V1 = "nuts_securitykey"; // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, // and the build id of the thinq app it can also just be a random diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index ddfc7a165d4f7..8d879d1457ae5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -14,8 +14,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgapi.model.ACCapability; -import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.ACCapability; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; /** * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 81d3b36fd9277..bab834f8212fd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -30,11 +30,14 @@ import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; -import org.openhab.core.thing.*; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; @@ -114,9 +117,8 @@ public void run() { try { tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword()); - if (tokenManager.getValidRegisteredToken(bridgeName) != null) { - logger.debug("Successful getting token from LG API"); - } + tokenManager.getValidRegisteredToken(bridgeName); + logger.debug("Successful getting token from LG API"); } catch (IOException e) { logger.debug( "I/O error accessing json token configuration file. Updating Bridge Status to OFFLINE.", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java deleted file mode 100644 index e597165334d71..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link DevicePowerState} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum DevicePowerState { - DV_POWER_ON(1), - DV_POWER_OFF(0), - DV_POWER_UNK(-1); - - private final int powerState; - - public double getValue() { - return powerState; - } - - DevicePowerState(int i) { - powerState = i; - } - - public static DevicePowerState statusOf(double value) { - switch ((int) value) { - case 0: - return DV_POWER_OFF; - case 1: - case 256: - case 257: - return DV_POWER_ON; - - default: - return DV_POWER_UNK; - } - } - - public static double valueOf(DevicePowerState dps) { - return dps.powerState; - } - - /** - * Value of command (not state, but command to change the state of device) - * - * @return value of the command to reach the state - */ - public int commandValue() { - switch (this) { - case DV_POWER_ON: - return 257;// "@AC_MAIN_OPERATION_ALL_ON_W" - case DV_POWER_OFF: - return 0; // "@AC_MAIN_OPERATION_OFF_W" - default: - throw new IllegalArgumentException("Enum not accepted for command:" + this); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java deleted file mode 100644 index a034870766775..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; - -/** - * The {@link DeviceTypes} - * - * @author Nemer Daud - Initial contribution - */ -public enum DeviceTypes { - AIR_CONDITIONER(401), - UNKNOWN(-1); - - private final int deviceTypeId; - - public int deviceTypeId() { - return deviceTypeId; - } - - public static DeviceTypes fromDeviceTypeId(int deviceTypeId) { - switch (deviceTypeId) { - case 401: - return AIR_CONDITIONER; - default: - return UNKNOWN; - } - } - - DeviceTypes(int i) { - this.deviceTypeId = i; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java deleted file mode 100644 index 3413aaeaf0700..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link LGDevice} - * - * @author Nemer Daud - Initial contribution - */ -@JsonIgnoreProperties(ignoreUnknown = true) -@NonNullByDefault -public class LGDevice { - private String modelName = ""; - @JsonProperty("deviceType") - private int deviceTypeId; - private String deviceCode = ""; - private String alias = ""; - private String deviceId = ""; - private String platformType = ""; - private String modelJsonUri = ""; - private boolean online; - - public String getModelName() { - return modelName; - } - - @JsonIgnore - public DeviceTypes getDeviceType() { - return DeviceTypes.fromDeviceTypeId(deviceTypeId); - } - - public void setModelName(String modelName) { - this.modelName = modelName; - } - - public int getDeviceTypeId() { - return deviceTypeId; - } - - public void setDeviceTypeId(int deviceTypeId) { - this.deviceTypeId = deviceTypeId; - } - - public String getDeviceCode() { - return deviceCode; - } - - public void setDeviceCode(String deviceCode) { - this.deviceCode = deviceCode; - } - - public String getModelJsonUri() { - return modelJsonUri; - } - - public void setModelJsonUri(String modelJsonUri) { - this.modelJsonUri = modelJsonUri; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - } - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getPlatformType() { - return platformType; - } - - public void setPlatformType(String platformType) { - this.platformType = platformType; - } - - public boolean isOnline() { - return online; - } - - public void setOnline(boolean online) { - this.online = online; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java index 4bf11b52fa1c7..5ac07b5d8ac21 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi; +package org.openhab.binding.lgthinq.lgservices; import java.io.IOException; import java.util.List; @@ -23,7 +23,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgservices.model.*; /** * The {@link LGThinqApiClientService} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java similarity index 98% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index 699fba682e1f6..c3ef550b937a6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi; +package org.openhab.binding.lgthinq.lgservices; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; @@ -24,7 +24,7 @@ import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java similarity index 99% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java index 8c3fdb5cabbcd..9984269e429f2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi; +package org.openhab.binding.lgthinq.lgservices; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; @@ -31,7 +31,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java similarity index 99% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java index b6d878e0c3452..e259308efccf3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi; +package org.openhab.binding.lgthinq.lgservices; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; @@ -38,7 +38,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java index 8f047b9148c4c..aae3164658013 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import java.util.Collections; import java.util.List; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java index 1f072c631e5ea..3381241e9c39a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java index baf3c98f0210e..b5d24a6691c02 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java similarity index 98% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java index df8926d89b97a..754a80aa75b30 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java index 34002d3a58d42..2a4918838f78c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java index a373367eccbf5..78ab6cf2ea72b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java index b43fbe5b1c4a3..72a24289c15ee 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; From 6d7f09515b235d0a5094478111b33d31917cf126 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Thu, 3 Feb 2022 14:20:49 -0300 Subject: [PATCH 030/130] [lgthinq][Feat] save capabilities of not supported Thinq devices. Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 2 +- .../handler/LGThinqBridgeHandler.java | 2 +- .../lgservices/LGThinqApiClientService.java | 6 ++++- .../LGThinqApiClientServiceImpl.java | 21 +++++++++++++++ .../LGThinqApiV1ClientServiceImpl.java | 20 ++++---------- .../LGThinqApiV2ClientServiceImpl.java | 21 +++------------ .../main/resources/OH-INF/thing/washer.xml | 26 +++++++++++++++++++ 7 files changed, 63 insertions(+), 35 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index a9d6282b084cc..d7b2881e73579 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -268,7 +268,7 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { @Override public ACCapability getAcCapabilities() throws LGThinqApiException { if (acCapability == null) { - acCapability = lgThinqApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); + acCapability = lgThinqApiClientService.getACCapability(getDeviceId(), getDeviceUriJsonConfig(), false); } return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index bab834f8212fd..2fc12e6d24612 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -207,7 +207,7 @@ protected void doConnectedRun() throws LGThinqException { if (lGDeviceRegister.get(deviceId) == null) { logger.debug("Adding new LG Device to things registry with id:{}", deviceId); if (discoveryService != null) { - discoveryService.addLgDeviceDiscovery(bridgeName, device); + discoveryService.addLgDeviceDiscovery(device); } } lastDevicesDiscovered.put(deviceId, device); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java index 5ac07b5d8ac21..115794b5f6305 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices; +import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; @@ -59,7 +60,10 @@ void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp new String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; - ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; + ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; + + File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) + throws LGThinqApiException, IOException; void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index c3ef550b937a6..a36fd7ba0a0e7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -14,6 +14,12 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.*; import javax.ws.rs.core.UriBuilder; @@ -88,6 +94,21 @@ public List listAccountDevices(String bridgeName) throws LGThinqApiExc } } + @Override + public File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); + try { + if (regFile.isFile() || forceRecreate) { + try (InputStream in = new URL(uri).openStream()) { + Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + } catch (IOException e) { + throw new LGThinqApiException("Error reading IO interface", e); + } + return regFile; + } + /** * Get device settings and snapshot for a specific device. * It works only for API V2 device versions! diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java index 9984269e429f2..030901c213cf1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java @@ -270,23 +270,13 @@ private File getCapFileForDevice(String deviceId) { */ @Override @NonNull - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) - throws LGThinqApiException { + public ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { try { - File regFile = getCapFileForDevice(deviceId); + File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); + Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { + }); ACCapability acCap = new ACCapability(); - Map mapper; - if (regFile.isFile() && !forceRecreate) { - // reg exists. Retrieve from it - mapper = objectMapper.readValue(regFile, new TypeReference>() { - }); - } else { - RestResult res = RestUtils.getCall(uri, null, null); - mapper = objectMapper.readValue(res.getJsonResponse(), new TypeReference>() { - }); - // try save file - objectMapper.writeValue(getCapFileForDevice(deviceId), mapper); - } + Map cap = (Map) mapper.get("Value"); if (cap == null) { throw new LGThinqApiException("Error extracting capabilities supported by the device"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java index e259308efccf3..af9406f539863 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java @@ -12,15 +12,10 @@ */ package org.openhab.binding.lgthinq.lgservices; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -180,24 +175,16 @@ public String startMonitor(String bridgeName, String deviceId) @Override @NonNull @SuppressWarnings("ignoring Map type check") - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) - throws LGThinqApiException { + public ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { try { - File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); - ACCapability acCap = new ACCapability(); - Map mapper; - if (regFile.isFile() || forceRecreate) { - try (InputStream in = new URL(uri).openStream()) { - Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } - mapper = objectMapper.readValue(regFile, new TypeReference<>() { + File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); + Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { }); Map cap = (Map) mapper.get("Value"); if (cap == null) { throw new LGThinqApiException("Error extracting capabilities supported by the device"); } - + ACCapability acCap = new ACCapability(); Map opModes = (Map) cap.get("airState.opMode"); if (opModes == null) { throw new LGThinqApiException("Error extracting opModes supported by the device"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml new file mode 100644 index 0000000000000..1ec2d87e1e528 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + LG Thinq Washing Machine + + + + + + + + + + + + From 6bbcdbc1aa16ba59d93f85b4d327134dc4103044 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 00:31:16 -0300 Subject: [PATCH 031/130] [lgthinq][Feat] Introducing of Washin Machine support. Only monitoring for now. No Channel added Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 94 ++++++------------- .../internal/LGThinqBindingConstants.java | 14 +-- .../lgthinq/internal/LGThinqDeviceThing.java | 82 +++++++++++++--- .../internal/LGThinqHandlerFactory.java | 7 +- .../handler/LGThinqBridgeHandler.java | 5 +- .../lgservices/LGThinqApiClientService.java | 7 +- .../LGThinqApiClientServiceImpl.java | 22 +++++ .../LGThinqApiV1ClientServiceImpl.java | 82 +++------------- .../LGThinqApiV2ClientServiceImpl.java | 79 +++++----------- .../model/{ => ac}/ACCapability.java | 5 +- .../lgservices/model/{ => ac}/ACFanSpeed.java | 4 +- .../lgservices/model/{ => ac}/ACOpMode.java | 4 +- .../{ACSnapShot.java => ac/ACSnapshot.java} | 19 ++-- .../ACSnapshotV1.java} | 6 +- .../ACSnapshotV2.java} | 6 +- .../model/{ => ac}/ACTargetTmp.java | 2 +- .../main/resources/OH-INF/thing/washer.xml | 4 - 17 files changed, 198 insertions(+), 244 deletions(-) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ => ac}/ACCapability.java (90%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ => ac}/ACFanSpeed.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ => ac}/ACOpMode.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ACSnapShot.java => ac/ACSnapshot.java} (81%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ACSnapShotV1.java => ac/ACSnapshotV1.java} (90%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ACSnapShotV2.java => ac/ACSnapshotV2.java} (91%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ => ac}/ACTargetTmp.java (97%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index d7b2881e73579..6dd0f08dc19cb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -24,16 +24,18 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; @@ -48,7 +50,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqAirConditionerHandler extends BaseThingHandler implements LGThinqDeviceThing { +public class LGThinqAirConditionerHandler extends LGThinqDeviceThing { private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; private final ChannelUID opModeChannelUID; @@ -110,53 +112,7 @@ public void initialize() { } @Override - public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { - logger.debug("bridgeStatusChanged {}", bridgeStatusInfo); - initializeThing(bridgeStatusInfo.getStatus()); - } - - private void initializeThing(@Nullable ThingStatus bridgeStatus) { - logger.debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); - String deviceId = getThing().getUID().getId(); - if (!SUPPORTED_LG_PLATFORMS.contains(lgPlatfomType)) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "LG Platform [" + lgPlatfomType + "] not supported for this thing"); - return; - } - Bridge bridge = getBridge(); - if (!deviceId.isBlank()) { - try { - updateChannelDynStateDescription(); - } catch (LGThinqApiException e) { - logger.error( - "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); - } - if (bridge != null) { - LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); - // registry this thing to the bridge - if (handler == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } else { - handler.registryListenerThing(this); - if (bridgeStatus == ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - } - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/offline.conf-error-no-device-id"); - } - // finally, start command queue, regardless os the thing state, as we can still try to send commands without - // property ONLINE (the successful result from command request can put the thing in ONLINE status). - startCommandExecutorQueueJob(); - } - - private void startCommandExecutorQueueJob() { + protected void startCommandExecutorQueueJob() { if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); } @@ -181,7 +137,7 @@ protected void startThingStatePolling() { private void updateThingStateFromLG() { try { - ACSnapShot shot = getSnapshotDeviceAdapter(getDeviceId()); + ACSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); if (shot == null) { // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. return; @@ -190,15 +146,15 @@ private void updateThingStateFromLG() { if (getThing().getStatus() != ThingStatus.OFFLINE) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); updateState(CHANNEL_POWER_ID, - OnOffType.from(shot.getAcPowerStatus() == DevicePowerState.DV_POWER_OFF)); + OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_OFF)); } return; } if (shot.getOperationMode() != null) { updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); } - if (shot.getAcPowerStatus() != null) { - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getAcPowerStatus() == DevicePowerState.DV_POWER_ON)); + if (shot.getPowerStatus() != null) { + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as // soon as LG WebOs do) } @@ -212,7 +168,7 @@ private void updateThingStateFromLG() { updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); } updateStatus(ThingStatus.ONLINE); - } catch (LGThinqException e) { + } catch (Exception e) { logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", getDeviceAlias(), getDeviceId(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); @@ -250,34 +206,40 @@ private String emptyIfNull(@Nullable String value) { @Override public void updateChannelDynStateDescription() throws LGThinqApiException { - ACCapability acCap = getAcCapabilities(); + ACCapability acCap = getCapabilities(); if (isLinked(opModeChannelUID)) { List options = new ArrayList<>(); acCap.getSupportedOpMode().forEach((v) -> options - .add(new StateOption(emptyIfNull(acCap.getOpMod().get(v)), emptyIfNull(CAP_OP_MODE.get(v))))); + .add(new StateOption(emptyIfNull(acCap.getOpMod().get(v)), emptyIfNull(CAP_AC_OP_MODE.get(v))))); stateDescriptionProvider.setStateOptions(opModeChannelUID, options); } if (isLinked(opModeFanSpeedUID)) { List options = new ArrayList<>(); - acCap.getSupportedFanSpeed().forEach((v) -> options - .add(new StateOption(emptyIfNull(acCap.getFanSpeed().get(v)), emptyIfNull(CAP_FAN_SPEED.get(v))))); + acCap.getSupportedFanSpeed().forEach((v) -> options.add( + new StateOption(emptyIfNull(acCap.getFanSpeed().get(v)), emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); stateDescriptionProvider.setStateOptions(opModeFanSpeedUID, options); } } @Override - public ACCapability getAcCapabilities() throws LGThinqApiException { + public ACCapability getCapabilities() throws LGThinqApiException { if (acCapability == null) { - acCapability = lgThinqApiClientService.getACCapability(getDeviceId(), getDeviceUriJsonConfig(), false); + acCapability = (ACCapability) lgThinqApiClientService.getCapability(getDeviceId(), getDeviceUriJsonConfig(), + false); } return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } + @Override + protected Logger getLogger() { + return logger; + } + @Nullable - private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { + private ACSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return lgThinqApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); + return (ACSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { @@ -286,7 +248,7 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx } } catch (LGThinqDeviceV1OfflineException e) { forceStopDeviceV1Monitor(deviceId); - ACSnapShot shot = new ACSnapShotV1(); + ACSnapshot shot = new ACSnapshotV1(); shot.setOnline(false); return shot; } catch (Exception e) { @@ -294,11 +256,11 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); } int retries = 10; - ACSnapShot shot; + ACSnapshot shot; while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + shot = (ACSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); if (shot != null) { return shot; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index f9f6d136bbcbd..f88735c8c8d8b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -13,7 +13,6 @@ package org.openhab.binding.lgthinq.internal; import java.io.File; -import java.util.Collections; import java.util.Map; import java.util.Set; @@ -35,11 +34,14 @@ public class LGThinqBindingConstants { public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner + public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.WASHING_MACHINE.deviceTypeId()); // deviceType from AirConditioner public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, - THING_TYPE_BRIDGE); - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); + THING_TYPE_WASHING_MACHINE, THING_TYPE_BRIDGE); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_AIR_CONDITIONER, + THING_TYPE_WASHING_MACHINE); - public static final String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; + public static String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; public static String BASE_CAP_CONFIG_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-cap.json"; public static final String V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; @@ -112,14 +114,14 @@ public class LGThinqBindingConstants { public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; - public static final Map CAP_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", + public static final Map CAP_AC_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", "@AC_MAIN_OPERATION_MODE_HEAT_W", "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", "@AC_MAIN_OPERATION_MODE_AI_W", "AI", "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); - public static final Map CAP_FAN_SPEED = Map.ofEntries( + public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index 8d879d1457ae5..a049d5cce642e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -13,9 +13,14 @@ package org.openhab.binding.lgthinq.internal; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.ACCapability; +import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; +import org.openhab.binding.lgthinq.lgservices.model.Capability; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.slf4j.Logger; /** * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things @@ -23,25 +28,78 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGThinqDeviceThing { +public abstract class LGThinqDeviceThing extends BaseThingHandler { - void onDeviceAdded(@NonNullByDefault LGDevice device); + public LGThinqDeviceThing(Thing thing) { + super(thing); + } - String getDeviceId(); + public abstract void onDeviceAdded(@NonNullByDefault LGDevice device); - String getDeviceAlias(); + public abstract String getDeviceId(); - String getDeviceModelName(); + public abstract String getDeviceAlias(); - String getDeviceUriJsonConfig(); + public abstract String getDeviceModelName(); - boolean onDeviceStateChanged(); + public abstract String getDeviceUriJsonConfig(); - void onDeviceRemoved(); + public abstract boolean onDeviceStateChanged(); - void onDeviceGone(); + public abstract void onDeviceRemoved(); - void updateChannelDynStateDescription() throws LGThinqApiException; + public abstract void onDeviceGone(); - ACCapability getAcCapabilities() throws LGThinqApiException; + public abstract void updateChannelDynStateDescription() throws LGThinqApiException; + + public abstract T getCapabilities() throws LGThinqApiException; + + protected abstract Logger getLogger(); + + protected abstract void startCommandExecutorQueueJob(); + + protected void initializeThing(@Nullable ThingStatus bridgeStatus) { + getLogger().debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); + String deviceId = getThing().getUID().getId(); + + Bridge bridge = getBridge(); + if (!deviceId.isBlank()) { + try { + updateChannelDynStateDescription(); + } catch (LGThinqApiException e) { + getLogger().error( + "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); + } + if (bridge != null) { + LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); + // registry this thing to the bridge + if (handler == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } else { + handler.registryListenerThing(this); + if (bridgeStatus == ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error-no-device-id"); + } + // finally, start command queue, regardless of the thing state, as we can still try to send commands without + // property ONLINE (the successful result from command request can put the thing in ONLINE status). + startCommandExecutorQueueJob(); + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + getLogger().debug("bridgeStatusChanged {}", bridgeStatusInfo); + super.bridgeStatusChanged(bridgeStatusInfo); + // restart scheduler + initializeThing(bridgeStatusInfo.getStatus()); + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index c89554b5e2c90..77add4311bdb6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -12,8 +12,7 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_BRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -58,6 +57,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return new LGThinqAirConditionerHandler(thing, stateDescriptionProvider); } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { return new LGThinqBridgeHandler((Bridge) thing); + } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { + return new LGThinqWasherHandler((Bridge) thing, stateDescriptionProvider); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; @@ -70,6 +71,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return super.createThing(thingTypeUID, configuration, thingUID, null); } else if (LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } else if (LGThinqBindingConstants.THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); } return null; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 2fc12e6d24612..8505ef6399939 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -34,10 +34,7 @@ import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.*; import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java index 115794b5f6305..7cd41f254fd3a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java @@ -25,6 +25,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; /** * The {@link LGThinqApiClientService} @@ -46,7 +47,7 @@ public interface LGThinqApiClientService { * @throws LGThinqApiException if some error interacting with LG API Server occur. */ @Nullable - ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; + Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; @@ -60,7 +61,7 @@ void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp new String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; - ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; + Capability getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException, IOException; @@ -68,6 +69,6 @@ File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; @Nullable - ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) + Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index a36fd7ba0a0e7..be5ce38cecfff 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -30,7 +30,10 @@ import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.Capability; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -191,4 +194,23 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG return devices; } + + /** + * Get capability em registry/cache on file for next consult + * + * @param deviceId ID of the device + * @param uri URI of the config capability + * @return return simplified capability + * @throws LGThinqApiException If some error occurr + */ + public Capability getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + try { + File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); + Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { + }); + return CapabilityFactory.getInstance().create(mapper, ACCapability.class); + } catch (IOException e) { + throw new LGThinqApiException("Error reading IO interface", e); + } + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java index 030901c213cf1..e98662a26e729 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java @@ -14,7 +14,6 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import java.io.File; import java.io.IOException; import java.util.*; @@ -31,7 +30,12 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,7 +82,7 @@ protected TokenManager getTokenManager() { */ @Override @Nullable - public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { throw new UnsupportedOperationException("Method not supported in V1 API device."); } @@ -214,9 +218,8 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) } @Override - @Nullable - public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull String workId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -231,7 +234,7 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev try { envelop = handleV1GenericErrorResult(resp); } catch (LGThinqDeviceV1OfflineException e) { - ACSnapShot shot = new ACSnapShotV2(); + ACSnapshot shot = new ACSnapshotV2(); shot.setOnline(false); return shot; } @@ -247,7 +250,7 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev String jsonMonDataB64 = (String) workList.get("returnData"); String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); - ACSnapShot shot = objectMapper.readValue(jsonMon, ACSnapShotV1.class); + ACSnapshot shot = objectMapper.readValue(jsonMon, ACSnapshotV1.class); shot.setOnline("E".equals(workList.get("deviceState"))); return shot; } else { @@ -255,67 +258,4 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev return null; } } - - private File getCapFileForDevice(String deviceId) { - return new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); - } - - /** - * Get capability em registry/cache on file for next consult - * - * @param deviceId ID of the device - * @param uri URI of the config capanility - * @return return simplified capability - * @throws LGThinqApiException If some error occurr - */ - @Override - @NonNull - public ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { - try { - File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); - Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { - }); - ACCapability acCap = new ACCapability(); - - Map cap = (Map) mapper.get("Value"); - if (cap == null) { - throw new LGThinqApiException("Error extracting capabilities supported by the device"); - } - - Map opModes = (Map) cap.get("OpMode"); - if (opModes == null) { - throw new LGThinqApiException("Error extracting opModes supported by the device"); - } else { - Map modes = new HashMap(); - ((Map) opModes.get("option")).forEach((k, v) -> { - modes.put(v, k); - }); - acCap.setOpMod(modes); - } - Map fanSpeed = (Map) cap.get("WindStrength"); - if (fanSpeed == null) { - throw new LGThinqApiException("Error extracting fanSpeed supported by the device"); - } else { - Map fanModes = new HashMap(); - ((Map) fanSpeed.get("option")).forEach((k, v) -> { - fanModes.put(v, k); - }); - acCap.setFanSpeed(fanModes); - - } - // Set supported modes for the device - - Map> supOpModes = (Map>) cap.get("SupportOpMode"); - acCap.setSupportedOpMode(new ArrayList<>(supOpModes.get("option").values())); - acCap.getSupportedOpMode().remove("@NON"); - Map> supFanSpeeds = (Map>) cap - .get("SupportWindStrength"); - acCap.setSupportedFanSpeed(new ArrayList<>(supFanSpeeds.get("option").values())); - acCap.getSupportedFanSpeed().remove("@NON"); - - return acCap; - } catch (IOException e) { - throw new LGThinqApiException("Error reading IO interface", e); - } - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java index af9406f539863..71f428bb3ca1d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java @@ -16,8 +16,6 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; import javax.ws.rs.core.UriBuilder; @@ -25,6 +23,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; @@ -33,7 +32,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotFactory; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,12 +87,25 @@ private Map getCommonV2Headers(String language, String country, */ @Override @Nullable - public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { Map deviceSettings = getDeviceSettings(bridgeName, deviceId); if (deviceSettings.get("snapshot") != null) { Map snapMap = (Map) deviceSettings.get("snapshot"); + if (logger.isDebugEnabled()) { + try { + objectMapper.writeValue(new File(String.format( + LGThinqBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", + deviceId)), deviceSettings); + } catch (IOException e) { + logger.error("Error saving data trace", e); + } + } + if (snapMap == null) { + // No snapshot value provided + return null; + } - ACSnapShot shot = objectMapper.convertValue(snapMap, ACSnapShotV2.class); + Snapshot shot = SnapshotFactory.getInstance().create(deviceSettings); shot.setOnline((Boolean) snapMap.get("online")); return shot; } @@ -172,55 +187,6 @@ public String startMonitor(String bridgeName, String deviceId) throw new UnsupportedOperationException("Not supported in V2 API."); } - @Override - @NonNull - @SuppressWarnings("ignoring Map type check") - public ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { - try { - File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); - Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { - }); - Map cap = (Map) mapper.get("Value"); - if (cap == null) { - throw new LGThinqApiException("Error extracting capabilities supported by the device"); - } - ACCapability acCap = new ACCapability(); - Map opModes = (Map) cap.get("airState.opMode"); - if (opModes == null) { - throw new LGThinqApiException("Error extracting opModes supported by the device"); - } else { - Map modes = new HashMap(); - ((Map) opModes.get("value_mapping")).forEach((k, v) -> { - modes.put(v, k); - }); - acCap.setOpMod(modes); - } - Map fanSpeed = (Map) cap.get("airState.windStrength"); - if (fanSpeed == null) { - throw new LGThinqApiException("Error extracting fanSpeed supported by the device"); - } else { - Map fanModes = new HashMap(); - ((Map) fanSpeed.get("value_mapping")).forEach((k, v) -> { - fanModes.put(v, k); - }); - acCap.setFanSpeed(fanModes); - - } - // Set supported modes for the device - Map> supOpModes = (Map>) cap - .get("support.airState.opMode"); - acCap.setSupportedOpMode(new ArrayList<>(supOpModes.get("value_mapping").values())); - acCap.getSupportedOpMode().remove("@NON"); - Map> supFanSpeeds = (Map>) cap - .get("support.airState.windStrength"); - acCap.setSupportedFanSpeed(new ArrayList<>(supFanSpeeds.get("value_mapping").values())); - acCap.getSupportedFanSpeed().remove("@NON"); - return acCap; - } catch (IOException e) { - throw new LGThinqApiException("Error reading IO interface", e); - } - } - private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { Map metaResult; if (resp == null) { @@ -257,9 +223,8 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) } @Override - @Nullable - public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull String workId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { throw new UnsupportedOperationException("Not supported in V2 API."); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java similarity index 90% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index aae3164658013..0e7d9f59d1049 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -10,13 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.Capability; /** * The {@link ACCapability} @@ -24,7 +25,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class ACCapability { +public class ACCapability extends Capability { private Map opMod = Collections.emptyMap(); private Map fanSpeed = Collections.emptyMap(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java index 3381241e9c39a..3c16809f811e3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link ACSnapShot} + * The {@link ACSnapshot} * * @author Nemer Daud - Initial contribution */ diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java index b5d24a6691c02..38a8dac94a4ca 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link ACSnapShot} + * The {@link ACSnapshot} * * @author Nemer Daud - Initial contribution */ diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java similarity index 81% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java index 754a80aa75b30..5a049417303ab 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java @@ -10,22 +10,24 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** - * The {@link ACSnapShot} + * The {@link ACSnapshot} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault @JsonIgnoreProperties(ignoreUnknown = true) -public abstract class ACSnapShot { +public abstract class ACSnapshot implements Snapshot { private int airWindStrength; @@ -40,10 +42,15 @@ public abstract class ACSnapShot { private boolean online; @JsonIgnore - public DevicePowerState getAcPowerStatus() { + public DevicePowerState getPowerStatus() { return operation == null ? DevicePowerState.DV_POWER_UNK : DevicePowerState.statusOf(operation); } + @JsonIgnore + public void setPowerStatus(DevicePowerState value) { + operation = (int) value.getValue(); + } + @JsonIgnore public ACFanSpeed getAcFanSpeed() { return ACFanSpeed.statusOf(airWindStrength); @@ -103,7 +110,7 @@ public void setOnline(boolean online) { public String toString() { return "ACSnapShot{" + "airWindStrength=" + airWindStrength + ", targetTemperature=" + targetTemperature + ", currentTemperature=" + currentTemperature + ", operationMode=" + operationMode + ", operation=" - + operation + ", acPowerStatus=" + getAcPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() - + ", acOpMode=" + ", online=" + isOnline() + " }"; + + operation + ", acPowerStatus=" + getPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() + ", acOpMode=" + + ", online=" + isOnline() + " }"; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java similarity index 90% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java index 2a4918838f78c..8cec78795c997 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -18,12 +18,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * The {@link ACSnapShotV1} + * The {@link ACSnapshotV1} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class ACSnapShotV1 extends ACSnapShot { +public class ACSnapshotV1 extends ACSnapshot { @Override @JsonProperty("WindStrength") diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java similarity index 91% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java index 78ab6cf2ea72b..3d5da370f5ebc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -18,12 +18,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * The {@link ACSnapShotV2} + * The {@link ACSnapshotV2} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class ACSnapShotV2 extends ACSnapShot { +public class ACSnapshotV2 extends ACSnapshot { @Override @JsonProperty("airState.windStrength") diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java index 72a24289c15ee..4b2fbc27bd6e5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 1ec2d87e1e528..a6ed2656f36ac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -14,10 +14,6 @@ LG Thinq Washing Machine - - - - From f787c5ae97b76bd31ad5c19647695bff71f81729 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 00:35:55 -0300 Subject: [PATCH 032/130] [lgthinq][Feat] Introducing of Washin Machine support. Only monitoring for now. No Channel added Signed-off-by: nemerdaud --- .../internal/LGThinqWasherHandler.java | 393 ++++++++++++++++++ .../lgthinq/lgservices/model/Capability.java | 22 + .../lgthinq/lgservices/model/Snapshot.java | 32 ++ .../lgservices/model/SnapshotFactory.java | 99 +++++ .../lgservices/model/washer/WMCapability.java | 122 ++++++ .../lgservices/model/washer/WMSnapshot.java | 55 +++ 6 files changed, 723 insertions(+) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java new file mode 100644 index 0000000000000..4c39ae758902d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -0,0 +1,393 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.washer.WMCapability; +import org.openhab.binding.lgthinq.lgservices.model.washer.WMSnapshot; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinqWasherHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqWasherHandler extends LGThinqDeviceThing { + + private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; + @Nullable + private WMCapability wmCapability; + private final String lgPlatfomType; + private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); + @NonNullByDefault + private final LGThinqApiClientService lgThinqApiClientService; + private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; + // Bridges status that this thing must top scanning for state change + private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, + ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + ThingStatusDetail.CONFIGURATION_ERROR); + private @Nullable ScheduledFuture thingStatePollingJob; + private @Nullable Future commandExecutorQueueJob; + // *** Long running isolated threadpools. + private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); + private final ExecutorService executorService = Executors.newFixedThreadPool(1); + + private boolean monitorV1Began = false; + private String monitorWorkId = ""; + private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); + @NonNullByDefault + private String bridgeId = ""; + + public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing); + this.stateDescriptionProvider = stateDescriptionProvider; + lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); + lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() + : LGThinqApiV2ClientServiceImpl.getInstance(); + } + + static class AsyncCommandParams { + final String channelUID; + final Command command; + + public AsyncCommandParams(String channelUUID, Command command) { + this.channelUID = channelUUID; + this.command = command; + } + } + + @Override + public Collection> getServices() { + return super.getServices(); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + protected void startCommandExecutorQueueJob() { + if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { + commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); + } + } + + private ExecutorService getExecutorService() { + return executorService; + } + + private void stopCommandExecutorQueueJob() { + if (commandExecutorQueueJob != null) { + commandExecutorQueueJob.cancel(true); + } + } + + protected void startThingStatePolling() { + if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { + thingStatePollingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, + DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); + } + } + + private void updateThingStateFromLG() { + try { + WMSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); + if (shot == null) { + // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. + return; + } + if (!shot.isOnline()) { + if (getThing().getStatus() != ThingStatus.OFFLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); + updateState(CHANNEL_POWER_ID, + OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_OFF)); + } + return; + } + + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + + updateStatus(ThingStatus.ONLINE); + } catch (LGThinqException e) { + logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", + getDeviceAlias(), getDeviceId(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + + private ScheduledExecutorService getLocalScheduler() { + return pollingScheduler; + } + + private String getBridgeId() { + if (bridgeId.isBlank() && getBridge() == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + logger.error("Configuration error um Thinq Thing - No Bridge defined for the thing."); + return "UNKNOWN"; + } else if (bridgeId.isBlank() && getBridge() != null) { + bridgeId = getBridge().getUID().getId(); + } + return bridgeId; + } + + private void forceStopDeviceV1Monitor(String deviceId) { + try { + monitorV1Began = false; + lgThinqApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + } catch (Exception e) { + logger.error("Error stopping LG Device monitor", e); + } + } + + @NonNull + private String emptyIfNull(@Nullable String value) { + return value == null ? "" : "" + value; + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + // not dynamic state channel in this device + } + + @Override + public WMCapability getCapabilities() throws LGThinqApiException { + if (wmCapability == null) { + wmCapability = (WMCapability) lgThinqApiClientService.getCapability(getDeviceId(), getDeviceUriJsonConfig(), + false); + } + return Objects.requireNonNull(wmCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); + } + + @Override + protected Logger getLogger() { + return logger; + } + + @Nullable + private WMSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { + // analise de platform version + if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { + return (WMSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); + } else { + try { + if (!monitorV1Began) { + monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorV1Began = true; + } + } catch (LGThinqDeviceV1OfflineException e) { + forceStopDeviceV1Monitor(deviceId); + WMSnapshot shot = new WMSnapshot(); + shot.setOnline(false); + return shot; + } catch (Exception e) { + forceStopDeviceV1Monitor(deviceId); + throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); + } + int retries = 10; + WMSnapshot shot; + while (retries > 0) { + // try to get monitoring data result 3 times. + try { + shot = (WMSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + if (shot != null) { + return shot; + } + Thread.sleep(500); + retries--; + } catch (LGThinqDeviceV1MonitorExpiredException e) { + forceStopDeviceV1Monitor(deviceId); + logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); + return null; + } catch (Exception e) { + // If it can't get monitor handler, then stop monitor and restart the process again in new + // interaction + // Force restart monitoring because of the errors returned (just in case) + forceStopDeviceV1Monitor(deviceId); + throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); + } + } + forceStopDeviceV1Monitor(deviceId); + throw new LGThinqApiException("Exhausted trying to get monitor data for the device:" + deviceId); + } + } + + protected void stopThingStatePolling() { + if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { + logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePollingJob.cancel(true); + } + } + + private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { + if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { + // start the thing polling + startThingStatePolling(); + } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE + && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { + // comunication error is not a specific Bridge error, then we must analise it to give + // this thinq the change to recovery from communication errors + if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR + || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { + // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if + // the bridge is out + stopThingStatePolling(); + } + + } + lastThingStatus = newStatus; + } + + @Override + protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { + handleStatusChanged(newStatus, statusDetail); + super.updateStatus(newStatus, statusDetail, description); + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceId() { + return getThing().getUID().getId(); + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceModelName() { + return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public boolean onDeviceStateChanged() { + // TODO - HANDLE IT, Think if it's needed + return false; + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void onDeviceGone() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void dispose() { + if (thingStatePollingJob != null) { + thingStatePollingJob.cancel(true); + stopThingStatePolling(); + stopCommandExecutorQueueJob(); + thingStatePollingJob = null; + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + updateThingStateFromLG(); + } else { + AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); + try { + // Ensure commands are send in a pipe per device. + commandBlockQueue.add(params); + } catch (IllegalStateException ex) { + logger.error( + "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, + "Device Command Queue is Busy"); + } + + } + } + + private final Runnable queuedCommandExecutor = new Runnable() { + @Override + public void run() { + while (true) { + AsyncCommandParams params; + try { + params = commandBlockQueue.take(); + } catch (InterruptedException e) { + logger.debug("Interrupting async command queue executor."); + return; + } + Command command = params.command; + + try { + switch (params.channelUID) { + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON + : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, + params.channelUID); + } + } + } catch (LGThinqException e) { + logger.error("Error executing Command {} to the channel {}. Thing goes offline until retry", + command, params.channelUID, e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + } + }; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java new file mode 100644 index 0000000000000..2fe161c37684b --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; + +/** + * The {@link Capability} + * + * @author Nemer Daud - Initial contribution + */ +public abstract class Capability { + +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java new file mode 100644 index 0000000000000..da1212c16f879 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link Snapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface Snapshot { + + public DevicePowerState getPowerStatus(); + + public void setPowerStatus(DevicePowerState value); + + public boolean isOnline(); + + public void setOnline(boolean online); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java new file mode 100644 index 0000000000000..5898ba88318e7 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -0,0 +1,99 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; + +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; +import org.openhab.binding.lgthinq.lgservices.model.washer.WMSnapshot; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link SnapshotFactory} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class SnapshotFactory { + private static final SnapshotFactory instance; + private static final ObjectMapper objectMapper = new ObjectMapper(); + static { + instance = new SnapshotFactory(); + } + + public static final SnapshotFactory getInstance() { + return instance; + } + + public Snapshot create(Map deviceSettings) throws LGThinqApiException { + DeviceTypes type = getDeviceType(deviceSettings); + Map snapMap = (Map) deviceSettings.get("snapshot"); + if (snapMap == null) { + throw new LGThinqApiException("snapshot node not present in device monitoring result."); + } + LGAPIVerion version = discoveryAPIVersion(snapMap, type); + switch (type) { + case AIR_CONDITIONER: + switch (version) { + case V1_0: { + return objectMapper.convertValue(deviceSettings, ACSnapshotV2.class); + } + case V2_0: { + return objectMapper.convertValue(deviceSettings, ACSnapshotV1.class); + } + } + case WASHING_MACHINE: + switch (version) { + case V1_0: { + throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + } + case V2_0: { + return objectMapper.convertValue(deviceSettings, WMSnapshot.class); + } + } + + default: + throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); + } + } + + private DeviceTypes getDeviceType(Map rootMap) { + Integer deviceTypeId = (Integer) rootMap.get("deviceType"); + Objects.requireNonNull(deviceTypeId, "Unexpected error. deviceType field not present in snapshot schema"); + return DeviceTypes.fromDeviceTypeId(deviceTypeId); + } + + private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { + switch (type) { + case AIR_CONDITIONER: + if (snapMap.containsKey("airState.opMode")) { + return LGAPIVerion.V2_0; + } else if (snapMap.containsKey("OpMode")) { + return LGAPIVerion.V1_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine AC API version."); + } + + case WASHING_MACHINE: + return LGAPIVerion.V2_0; + default: + throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java new file mode 100644 index 0000000000000..42d97011d10fd --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.Capability; + +/** + * The {@link WMCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMCapability extends Capability { + public enum MonitoringCap { + STATE("state"), + SOIL_WASH("soilWash"), + SPIN("spin"), + TEMPERATURE("temp"), + RINSE("rinse"); + + final String value; + + MonitoringCap(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + private static class MonitoringValue { + private Map state = new LinkedHashMap(); + private Map soilWash = new LinkedHashMap(); + private Map spin = new LinkedHashMap(); + private Map temperature = new LinkedHashMap(); + private Map rinse = new LinkedHashMap(); + private boolean hasDoorLook; + private boolean hasTurboWash; + } + + private MonitoringValue monitoringValue = new MonitoringValue(); + private Map courses = new LinkedHashMap(); + + public Map getCourses() { + return courses; + } + + public void addCourse(String courseLabel, String courseName) { + courses.put(courseLabel, courseName); + } + + public Map getState() { + return monitoringValue.state; + } + + public Map getSoilWash() { + return monitoringValue.soilWash; + } + + public Map getSpin() { + return monitoringValue.spin; + } + + public Map getTemperature() { + return monitoringValue.temperature; + } + + public Map getRinse() { + return monitoringValue.rinse; + } + + public boolean hasDoorLook() { + return monitoringValue.hasDoorLook; + } + + public void setHasDoorLook(boolean hasDoorLook) { + monitoringValue.hasDoorLook = hasDoorLook; + } + + public boolean hasTurboWash() { + return monitoringValue.hasTurboWash; + } + + public void setHasTurboWash(boolean hasTurboWash) { + monitoringValue.hasTurboWash = hasTurboWash; + } + + public void addMonitoringValue(MonitoringCap monCap, String key, String value) { + switch (monCap) { + case STATE: + monitoringValue.state.put(key, value); + break; + case SOIL_WASH: + monitoringValue.soilWash.put(key, value); + break; + case SPIN: + monitoringValue.spin.put(key, value); + break; + case TEMPERATURE: + monitoringValue.temperature.put(key, value); + break; + case RINSE: + monitoringValue.rinse.put(key, value); + break; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java new file mode 100644 index 0000000000000..02e57b1cfb332 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; + +/** + * The {@link WMSnapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMSnapshot implements Snapshot { + private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; + private String course = ""; + + @Override + public DevicePowerState getPowerStatus() { + return powerState; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + this.powerState = value; + } + + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } + + @Override + public boolean isOnline() { + return false; + } + + @Override + public void setOnline(boolean online) { + } +} From 0146e9be83f4ab1467e313f95162a247d3d2ce58 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 13:20:07 -0300 Subject: [PATCH 033/130] [lgthinq][Fix] Remove configuration token file if the bridge configuration is changes; fix classcast exception in Washer thing creation Signed-off-by: nemerdaud --- .../internal/LGThinqBindingConstants.java | 7 ++++--- .../lgthinq/internal/LGThinqConfiguration.java | 12 +++++++++++- .../lgthinq/internal/LGThinqHandlerFactory.java | 2 +- .../internal/handler/LGThinqBridgeHandler.java | 17 ++++++++++++++++- 4 files changed, 32 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index f88735c8c8d8b..319e29ce5c959 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -63,7 +63,7 @@ public class LGThinqBindingConstants { public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; public static final String GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; public static final String GATEWAY_SERVICE_PATH_V1 = "/api/common/gatewayUriList"; - public static String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; + public static final String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; public static final String PRE_LOGIN_PATH = "/preLogin"; public static final String SECURITY_KEY = "nuts_securitykey"; public static final String APP_KEY = "wideq"; @@ -78,7 +78,8 @@ public class LGThinqBindingConstants { public static final String DEFAULT_COUNTRY = "US"; public static final String DEFAULT_LANGUAGE = "en-US"; public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; - public static String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com/emp/oauth2/token/empsession"; + public static final String V2_EMP_SESS_PATH = "/emp/oauth2/token/empsession"; + public static final String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com" + V2_EMP_SESS_PATH; public static final String API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; public static final String API_KEY_V1 = "wideq"; @@ -98,7 +99,7 @@ public class LGThinqBindingConstants { public static final String DEVICE_ID = "device_id"; public static final String MODEL_NAME = "model_name"; public static final String DEVICE_ALIAS = "device_alias"; - public static final String MODEL_URL_INFO = "model_url_indo"; + public static final String MODEL_URL_INFO = "model_url_info"; public static final String PLATFORM_TYPE = "platform_type"; public static final String PLATFORM_TYPE_V1 = "thinq1"; public static final String PLATFORM_TYPE_V2 = "thinq2"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java index 5bb094898006a..8bd5ef613199a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java @@ -29,17 +29,19 @@ public class LGThinqConfiguration { public String country = ""; public String language = ""; public Integer pollingIntervalSec = 0; + public String alternativeServer = ""; public LGThinqConfiguration() { } public LGThinqConfiguration(String username, String password, String country, String language, - Integer pollingIntervalSec) { + Integer pollingIntervalSec, String alternativeServer) { this.username = username; this.password = password; this.country = country; this.language = language; this.pollingIntervalSec = pollingIntervalSec; + this.alternativeServer = alternativeServer; } public String getUsername() { @@ -81,4 +83,12 @@ public void setLanguage(String language) { public void setPollingIntervalSec(Integer pollingIntervalSec) { this.pollingIntervalSec = pollingIntervalSec; } + + public String getAlternativeServer() { + return alternativeServer; + } + + public void setAlternativeServer(String alternativeServer) { + this.alternativeServer = alternativeServer; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 77add4311bdb6..68b203cf3eb5c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -58,7 +58,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { return new LGThinqBridgeHandler((Bridge) thing); } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { - return new LGThinqWasherHandler((Bridge) thing, stateDescriptionProvider); + return new LGThinqWasherHandler(thing, stateDescriptionProvider); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 8505ef6399939..19bcf9a8797ce 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.lgthinq.internal.handler; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_CONNECTION_DATA_FILE; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; import java.io.File; @@ -113,7 +114,8 @@ public void run() { } else { try { tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), - lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword()); + lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword(), + lgthinqConfig.getAlternativeServer()); tokenManager.getValidRegisteredToken(bridgeName); logger.debug("Successful getting token from LG API"); } catch (IOException e) { @@ -286,6 +288,19 @@ public void initialize() { } } + @Override + public void handleConfigurationUpdate(Map configurationParameters) { + logger.debug("Bridge Configuration was updated. Cleaning the token registry file"); + File f = new File(String.format(THINQ_CONNECTION_DATA_FILE, getThing().getUID().getId())); + if (f.isFile()) { + // file exists. Delete it + if (!f.delete()) { + logger.error("Error deleting file:{}", f.getAbsolutePath()); + } + } + super.handleConfigurationUpdate(configurationParameters); + } + private void startLGThinqDevicePolling() { // stop current scheduler, if any if (devicePollingJob != null && !devicePollingJob.isDone()) { From fdbb9f72d6f5d1c535a52a26b2099bda7f24215e Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 16:24:37 -0300 Subject: [PATCH 034/130] [lgthinq][Feat] Add Polish coyntry support and WM Snapshot DataModel Signed-off-by: nemerdaud --- .../lgservices/model/washer/ControlWifi.java | 82 +++++++++++++++++++ .../lgthinq/lgservices/model/washer/Data.java | 39 +++++++++ .../lgservices/model/washer/WMDownload.java | 43 ++++++++++ .../lgservices/model/washer/WMOff.java | 43 ++++++++++ .../lgservices/model/washer/WMSnapshot.java | 15 ++++ .../lgservices/model/washer/WMStart.java | 43 ++++++++++ .../lgservices/model/washer/WMStop.java | 43 ++++++++++ .../lgservices/model/washer/WMWakeup.java | 45 ++++++++++ .../lgservices/model/washer/WasherDryer.java | 47 +++++++++++ 9 files changed, 400 insertions(+) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java new file mode 100644 index 0000000000000..ebecc18eb3842 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import static org.openhab.binding.lgthinq.lgservices.model.washer.WMDownload.EMPTY_WM_DOWNLOAD; +import static org.openhab.binding.lgthinq.lgservices.model.washer.WMStart.EMPTY_WM_START; +import static org.openhab.binding.lgthinq.lgservices.model.washer.WMStop.EMPTY_WM_STOP; +import static org.openhab.binding.lgthinq.lgservices.model.washer.WMWakeup.EMPTY_WM_WAKEUP; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link ControlWifi} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ControlWifi { + static final ControlWifi EMPTY_CONTROL_WIFI = new ControlWifi(); + @JsonProperty("WMStart") + private WMStart wmStart = EMPTY_WM_START; + @JsonProperty("WMDownload") + private WMDownload wmDownload = EMPTY_WM_DOWNLOAD; + @JsonProperty("WMOff") + private WMOff wmOff = WMOff.EMPTY_WM_OFF; + @JsonProperty("WMStop") + private WMStop wmStop = EMPTY_WM_STOP; + @JsonProperty("WMWakeup") + private WMWakeup wmWakeup = EMPTY_WM_WAKEUP; + + public void setWmStart(WMStart wmStart) { + this.wmStart = wmStart; + } + + public WMStart getWmStart() { + return wmStart; + } + + public void setWmDownload(WMDownload wmDownload) { + this.wmDownload = wmDownload; + } + + public WMDownload getWmDownload() { + return wmDownload; + } + + public void setWmOff(WMOff wmOff) { + this.wmOff = wmOff; + } + + public WMOff getWmOff() { + return wmOff; + } + + public void setWmStop(WMStop wmStop) { + this.wmStop = wmStop; + } + + public WMStop getWmStop() { + return wmStop; + } + + public void setWmWakeup(WMWakeup wmWakeup) { + this.wmWakeup = wmWakeup; + } + + public WMWakeup getWmWakeup() { + return wmWakeup; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java new file mode 100644 index 0000000000000..4ef5c57e9ae90 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import static org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryer.EMPTY_WASHER_DRYER; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link Data} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class Data { + public static final Data EMPTY_DATA = new Data(); + @JsonProperty("washerDryer") + private WasherDryer washerdryer = EMPTY_WASHER_DRYER; + + public void setWasherDryer(WasherDryer washerdryer) { + this.washerdryer = washerdryer; + } + + public WasherDryer getWasherDryer() { + return washerdryer; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java new file mode 100644 index 0000000000000..a2ee326b500d0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMDownload} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMDownload { + static final WMDownload EMPTY_WM_DOWNLOAD = new WMDownload(); + private String command = ""; + private Data data = Data.EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java new file mode 100644 index 0000000000000..49c9e3ea8f1e7 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMOff} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMOff { + static final WMOff EMPTY_WM_OFF = new WMOff(); + private String command = ""; + private Data data = Data.EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java index 02e57b1cfb332..b8a2f1223649d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java @@ -12,10 +12,14 @@ */ package org.openhab.binding.lgthinq.lgservices.model.washer; +import static org.openhab.binding.lgthinq.lgservices.model.washer.ControlWifi.EMPTY_CONTROL_WIFI; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * The {@link WMSnapshot} * @@ -26,6 +30,17 @@ public class WMSnapshot implements Snapshot { private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; private String course = ""; + @JsonProperty("ControlWifi") + private ControlWifi controlWifi = EMPTY_CONTROL_WIFI; + + public void setControlWifi(ControlWifi controlWifi) { + this.controlWifi = controlWifi; + } + + public ControlWifi getControlWifi() { + return controlWifi; + } + @Override public DevicePowerState getPowerStatus() { return powerState; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java new file mode 100644 index 0000000000000..7470f40790204 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMStart} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMStart { + static final WMStart EMPTY_WM_START = new WMStart(); + private String command = ""; + private Data data = Data.EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java new file mode 100644 index 0000000000000..2712a745a6cf1 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMStop} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMStop { + static final WMStop EMPTY_WM_STOP = new WMStop(); + private String command = ""; + private Data data = Data.EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java new file mode 100644 index 0000000000000..e685a981fda8a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import static org.openhab.binding.lgthinq.lgservices.model.washer.Data.EMPTY_DATA; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMWakeup} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMWakeup { + static final WMWakeup EMPTY_WM_WAKEUP = new WMWakeup(); + private String command = ""; + private Data data = EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java new file mode 100644 index 0000000000000..ce14c4cb29243 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link WasherDryer} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WasherDryer { + static final WasherDryer EMPTY_WASHER_DRYER = new WasherDryer(); + @JsonProperty("controlDataType") + private String controlDataType = ""; + @JsonProperty("controlDataValueLength") + private int controlDataValueLength; + + public void setControlDataType(String controlDataType) { + this.controlDataType = controlDataType; + } + + public String getControlDataType() { + return controlDataType; + } + + public void setControlDataValueLength(int controlDataValueLength) { + this.controlDataValueLength = controlDataValueLength; + } + + public int getControlDataValueLength() { + return controlDataValueLength; + } +} From f50f62232185c75418279a0c2addd721a593fe43 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 23:08:56 -0300 Subject: [PATCH 035/130] [lgthinq][Feat] Mapped Power and State channels (read only) Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 28 +++---- .../internal/LGThinqBindingConstants.java | 17 +++- .../lgthinq/internal/LGThinqDeviceThing.java | 5 +- .../internal/LGThinqWasherHandler.java | 32 +++++--- .../LGThinqApiClientServiceImpl.java | 3 +- .../lgservices/model/SnapshotFactory.java | 13 ++- .../lgservices/model/washer/ControlWifi.java | 82 ------------------- .../lgthinq/lgservices/model/washer/Data.java | 39 --------- .../lgservices/model/washer/WMDownload.java | 43 ---------- .../lgservices/model/washer/WMOff.java | 43 ---------- .../lgservices/model/washer/WMStart.java | 43 ---------- .../lgservices/model/washer/WMStop.java | 43 ---------- .../lgservices/model/washer/WMWakeup.java | 45 ---------- .../lgservices/model/washer/WasherDryer.java | 47 ----------- ...Snapshot.java => WasherDryerSnapshot.java} | 56 +++++++------ .../main/resources/OH-INF/thing/washer.xml | 1 + 16 files changed, 91 insertions(+), 449 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/{WMSnapshot.java => WasherDryerSnapshot.java} (51%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 6dd0f08dc19cb..43d9a8bc0b245 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -27,7 +27,8 @@ import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; @@ -150,23 +151,14 @@ private void updateThingStateFromLG() { } return; } - if (shot.getOperationMode() != null) { - updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); - } - if (shot.getPowerStatus() != null) { - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); - // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as - // soon as LG WebOs do) - } - if (shot.getAcFanSpeed() != null) { - updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(shot.getAirWindStrength())); - } - if (shot.getCurrentTemperature() != null) { - updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(shot.getCurrentTemperature())); - } - if (shot.getTargetTemperature() != null) { - updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); - } + + updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as + // soon as LG WebOs do) + updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(shot.getAirWindStrength())); + updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(shot.getCurrentTemperature())); + updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); updateStatus(ThingStatus.ONLINE); } catch (Exception e) { logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 319e29ce5c959..d14ed9fa93d6c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -111,7 +111,7 @@ public class LGThinqBindingConstants { // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; - public static final String CHANNEL_POWER_ID = "power"; + public static final String CHANNEL_POWER_ID = "startThingStatePolling"; public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; @@ -135,4 +135,19 @@ public class LGThinqBindingConstants { Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); + + // ====================== WASHING MACHINE CONSTANTS ============================= + public static final String WM_POWER_OFF_VALUE = "POWEROFF"; + public static final String WM_SNAPSHOT_WASHER_DRYER_NODE = "washerDryer"; + public static final String WM_CHANNEL_STATE_ID = "state"; + public static final Map CAP_WP_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), + Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), + Map.entry("@WM_STATE_RESERVE_W", "Reverse"), Map.entry("@WM_STATE_DETECTING_W", "Detecting"), + Map.entry("@WM_STATE_RUNNING_W", "Running"), Map.entry("@WM_STATE_RINSING_W", "Rinsing"), + Map.entry("@WM_STATE_SPINNING_W", "Spinning"), Map.entry("@WM_STATE_COOLDOWN_W", "Cool Down"), + Map.entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), Map.entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), + Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_END_W", "End"), + Map.entry("@WM_STATE_DRYING_W", "Drying"), Map.entry("@WM_STATE_DEMO_W", "Demonstration"), + Map.entry("@WM_STATE_ERROR_W", "Error")); + // ============================================================================== } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index a049d5cce642e..9b9dde78bc7df 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -52,7 +52,7 @@ public LGThinqDeviceThing(Thing thing) { public abstract void updateChannelDynStateDescription() throws LGThinqApiException; - public abstract T getCapabilities() throws LGThinqApiException; + public abstract Capability getCapabilities() throws LGThinqApiException; protected abstract Logger getLogger(); @@ -68,7 +68,8 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { updateChannelDynStateDescription(); } catch (LGThinqApiException e) { getLogger().error( - "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); + "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values.", + e); } if (bridge != null) { LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java index 4c39ae758902d..3c0e44d48c8f9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -14,9 +14,7 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import java.util.Collection; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNull; @@ -32,12 +30,14 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.washer.WMCapability; -import org.openhab.binding.lgthinq.lgservices.model.washer.WMSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; +import org.openhab.core.types.StateOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,6 +53,7 @@ public class LGThinqWasherHandler extends LGThinqDeviceThing { private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; @Nullable private WMCapability wmCapability; + private final ChannelUID stateChannelUUID; private final String lgPlatfomType; private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); @NonNullByDefault @@ -80,6 +81,7 @@ public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvide lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() : LGThinqApiV2ClientServiceImpl.getInstance(); + stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); } static class AsyncCommandParams { @@ -130,7 +132,7 @@ protected void startThingStatePolling() { private void updateThingStateFromLG() { try { - WMSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); + WasherDryerSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); if (shot == null) { // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. return; @@ -145,6 +147,7 @@ private void updateThingStateFromLG() { } updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); updateStatus(ThingStatus.ONLINE); } catch (LGThinqException e) { @@ -185,7 +188,13 @@ private String emptyIfNull(@Nullable String value) { @Override public void updateChannelDynStateDescription() throws LGThinqApiException { - // not dynamic state channel in this device + WMCapability wmCap = getCapabilities(); + if (isLinked(stateChannelUUID)) { + List options = new ArrayList<>(); + // invert key/value + wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); + stateDescriptionProvider.setStateOptions(stateChannelUUID, options); + } } @Override @@ -203,10 +212,10 @@ protected Logger getLogger() { } @Nullable - private WMSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { + private WasherDryerSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (WMSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); + return (WasherDryerSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { @@ -215,7 +224,7 @@ private WMSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx } } catch (LGThinqDeviceV1OfflineException e) { forceStopDeviceV1Monitor(deviceId); - WMSnapshot shot = new WMSnapshot(); + WasherDryerSnapshot shot = new WasherDryerSnapshot(); shot.setOnline(false); return shot; } catch (Exception e) { @@ -223,11 +232,12 @@ private WMSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); } int retries = 10; - WMSnapshot shot; + WasherDryerSnapshot shot; while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = (WMSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + shot = (WasherDryerSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, + monitorWorkId); if (shot != null) { return shot; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index be5ce38cecfff..d87983e2c686e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -33,7 +33,6 @@ import org.openhab.binding.lgthinq.lgservices.model.Capability; import org.openhab.binding.lgthinq.lgservices.model.CapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -208,7 +207,7 @@ public Capability getCapability(String deviceId, String uri, boolean forceRecrea File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { }); - return CapabilityFactory.getInstance().create(mapper, ACCapability.class); + return CapabilityFactory.getInstance().create(mapper); } catch (IOException e) { throw new LGThinqApiException("Error reading IO interface", e); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 5898ba88318e7..8e7205063f465 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.lgthinq.lgservices.model; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; + import java.util.Map; import java.util.Objects; @@ -19,7 +21,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; -import org.openhab.binding.lgthinq.lgservices.model.washer.WMSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; import com.fasterxml.jackson.databind.ObjectMapper; @@ -51,10 +53,10 @@ public Snapshot create(Map deviceSettings) throws LGThinqApiExce case AIR_CONDITIONER: switch (version) { case V1_0: { - return objectMapper.convertValue(deviceSettings, ACSnapshotV2.class); + return objectMapper.convertValue(snapMap, ACSnapshotV2.class); } case V2_0: { - return objectMapper.convertValue(deviceSettings, ACSnapshotV1.class); + return objectMapper.convertValue(snapMap, ACSnapshotV1.class); } } case WASHING_MACHINE: @@ -63,7 +65,10 @@ public Snapshot create(Map deviceSettings) throws LGThinqApiExce throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); } case V2_0: { - return objectMapper.convertValue(deviceSettings, WMSnapshot.class); + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), + "washerDryer node must be present in the snapshot"); + return objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java deleted file mode 100644 index ebecc18eb3842..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import static org.openhab.binding.lgthinq.lgservices.model.washer.WMDownload.EMPTY_WM_DOWNLOAD; -import static org.openhab.binding.lgthinq.lgservices.model.washer.WMStart.EMPTY_WM_START; -import static org.openhab.binding.lgthinq.lgservices.model.washer.WMStop.EMPTY_WM_STOP; -import static org.openhab.binding.lgthinq.lgservices.model.washer.WMWakeup.EMPTY_WM_WAKEUP; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link ControlWifi} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ControlWifi { - static final ControlWifi EMPTY_CONTROL_WIFI = new ControlWifi(); - @JsonProperty("WMStart") - private WMStart wmStart = EMPTY_WM_START; - @JsonProperty("WMDownload") - private WMDownload wmDownload = EMPTY_WM_DOWNLOAD; - @JsonProperty("WMOff") - private WMOff wmOff = WMOff.EMPTY_WM_OFF; - @JsonProperty("WMStop") - private WMStop wmStop = EMPTY_WM_STOP; - @JsonProperty("WMWakeup") - private WMWakeup wmWakeup = EMPTY_WM_WAKEUP; - - public void setWmStart(WMStart wmStart) { - this.wmStart = wmStart; - } - - public WMStart getWmStart() { - return wmStart; - } - - public void setWmDownload(WMDownload wmDownload) { - this.wmDownload = wmDownload; - } - - public WMDownload getWmDownload() { - return wmDownload; - } - - public void setWmOff(WMOff wmOff) { - this.wmOff = wmOff; - } - - public WMOff getWmOff() { - return wmOff; - } - - public void setWmStop(WMStop wmStop) { - this.wmStop = wmStop; - } - - public WMStop getWmStop() { - return wmStop; - } - - public void setWmWakeup(WMWakeup wmWakeup) { - this.wmWakeup = wmWakeup; - } - - public WMWakeup getWmWakeup() { - return wmWakeup; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java deleted file mode 100644 index 4ef5c57e9ae90..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import static org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryer.EMPTY_WASHER_DRYER; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link Data} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class Data { - public static final Data EMPTY_DATA = new Data(); - @JsonProperty("washerDryer") - private WasherDryer washerdryer = EMPTY_WASHER_DRYER; - - public void setWasherDryer(WasherDryer washerdryer) { - this.washerdryer = washerdryer; - } - - public WasherDryer getWasherDryer() { - return washerdryer; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java deleted file mode 100644 index a2ee326b500d0..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMDownload} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMDownload { - static final WMDownload EMPTY_WM_DOWNLOAD = new WMDownload(); - private String command = ""; - private Data data = Data.EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java deleted file mode 100644 index 49c9e3ea8f1e7..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMOff} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMOff { - static final WMOff EMPTY_WM_OFF = new WMOff(); - private String command = ""; - private Data data = Data.EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java deleted file mode 100644 index 7470f40790204..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMStart} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMStart { - static final WMStart EMPTY_WM_START = new WMStart(); - private String command = ""; - private Data data = Data.EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java deleted file mode 100644 index 2712a745a6cf1..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMStop} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMStop { - static final WMStop EMPTY_WM_STOP = new WMStop(); - private String command = ""; - private Data data = Data.EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java deleted file mode 100644 index e685a981fda8a..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import static org.openhab.binding.lgthinq.lgservices.model.washer.Data.EMPTY_DATA; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMWakeup} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMWakeup { - static final WMWakeup EMPTY_WM_WAKEUP = new WMWakeup(); - private String command = ""; - private Data data = EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java deleted file mode 100644 index ce14c4cb29243..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link WasherDryer} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WasherDryer { - static final WasherDryer EMPTY_WASHER_DRYER = new WasherDryer(); - @JsonProperty("controlDataType") - private String controlDataType = ""; - @JsonProperty("controlDataValueLength") - private int controlDataValueLength; - - public void setControlDataType(String controlDataType) { - this.controlDataType = controlDataType; - } - - public String getControlDataType() { - return controlDataType; - } - - public void setControlDataValueLength(int controlDataValueLength) { - this.controlDataValueLength = controlDataValueLength; - } - - public int getControlDataValueLength() { - return controlDataValueLength; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java similarity index 51% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java index b8a2f1223649d..762db7d85a11b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java @@ -12,34 +12,30 @@ */ package org.openhab.binding.lgthinq.lgservices.model.washer; -import static org.openhab.binding.lgthinq.lgservices.model.washer.ControlWifi.EMPTY_CONTROL_WIFI; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_POWER_OFF_VALUE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** - * The {@link WMSnapshot} - * + * The {@link WasherDryerSnapshot} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->washerDryer, but this POJO expects + * to map field below washerDryer + * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class WMSnapshot implements Snapshot { +@JsonIgnoreProperties(ignoreUnknown = true) +public class WasherDryerSnapshot implements Snapshot { private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; - private String course = ""; - - @JsonProperty("ControlWifi") - private ControlWifi controlWifi = EMPTY_CONTROL_WIFI; - - public void setControlWifi(ControlWifi controlWifi) { - this.controlWifi = controlWifi; - } - - public ControlWifi getControlWifi() { - return controlWifi; - } + private String state = ""; + private boolean online; @Override public DevicePowerState getPowerStatus() { @@ -48,23 +44,31 @@ public DevicePowerState getPowerStatus() { @Override public void setPowerStatus(DevicePowerState value) { - this.powerState = value; - } - - public String getCourse() { - return course; - } - - public void setCourse(String course) { - this.course = course; + throw new IllegalArgumentException("This method must not be accessed."); } @Override public boolean isOnline() { - return false; + return online; } @Override public void setOnline(boolean online) { + this.online = online; + } + + @JsonProperty("state") + @JsonGetter + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + if (state.equals(WM_POWER_OFF_VALUE)) { + powerState = DevicePowerState.DV_POWER_OFF; + } else { + powerState = DevicePowerState.DV_POWER_ON; + } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index a6ed2656f36ac..1cbd8f9bf783e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -15,6 +15,7 @@ + From 9cd1e844608a71414582f43af706b42c6ed8268a Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 08:52:35 -0300 Subject: [PATCH 036/130] [lgthinq][Feat] Added Course and SmartCourse channels. Error message handler Signed-off-by: nemerdaud --- .../internal/LGThinqBindingConstants.java | 35 +++++++++++++++ .../internal/LGThinqWasherHandler.java | 16 +++++++ .../LGThinqApiClientServiceImpl.java | 12 +++++ .../LGThinqApiV1ClientServiceImpl.java | 2 + .../LGThinqApiV2ClientServiceImpl.java | 1 + .../lgservices/model/washer/WMCapability.java | 10 +++++ .../model/washer/WasherDryerSnapshot.java | 26 ++++++++++- .../main/resources/OH-INF/thing/channels.xml | 44 +++++++++++++++++++ .../main/resources/OH-INF/thing/washer.xml | 2 + 9 files changed, 146 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index d14ed9fa93d6c..4073085beb132 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -108,6 +108,37 @@ public class LGThinqBindingConstants { public static final int SEARCH_TIME = 20; // delay between each devices's scan for state changes (in seconds) public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 30; + + public static final Map ERROR_CODE_RESPONSE = Map.ofEntries(Map.entry("0000", "OK"), + Map.entry("0001", "PARTIAL_OK"), Map.entry("0103", "OPERATION_IN_PROGRESS_DEVICE"), + Map.entry("0007", "PORTAL_INTERWORKING_ERROR"), Map.entry("0104", "PROCESSING_REFRIGERATOR"), + Map.entry("0111", "RESPONSE_DELAY_DEVICE"), Map.entry("8107", "SERVICE_SERVER_ERROR"), + Map.entry("8102", "SSP_ERROR"), Map.entry("9020", "TIME_OUT"), Map.entry("8104", "WRONG_XML_OR_URI"), + Map.entry("9000", "AWS_IOT_ERROR"), Map.entry("8105", "AWS_S3_ERROR"), Map.entry("8106", "AWS_SQS_ERROR"), + Map.entry("9002", "BASE64_DECODING_ERROR"), Map.entry("9001", "BASE64_ENCODING_ERROR"), + Map.entry("8103", "CLIP_ERROR"), Map.entry("0105", "CONTROL_ERROR_REFRIGERATOR"), + Map.entry("9003", "CREATE_SESSION_FAIL"), Map.entry("9004", "DB_PROCESSING_FAIL"), + Map.entry("8101", "DM_ERROR"), Map.entry("0013", "DUPLICATED_ALIAS"), Map.entry("0008", "DUPLICATED_DATA"), + Map.entry("0004", "DUPLICATED_LOGIN"), Map.entry("0102", "EMP_AUTHENTICATION_FAILED"), + Map.entry("8900", "ETC_COMMUNICATION_ERROR"), Map.entry("9999", "ETC_ERROR"), + Map.entry("0112", "EXCEEDING_LIMIT"), Map.entry("0119", "EXPIRED_CUSTOMER_NUMBER"), + Map.entry("9005", "EXPIRES_SESSION_BY_WITHDRAWAL"), Map.entry("0100", "FAIL"), + Map.entry("8001", "INACTIVE_API"), Map.entry("0107", "INSUFFICIENT_STORAGE_SPACE"), + Map.entry("9010", "INVAILD_CSR"), Map.entry("0002", "INVALID_BODY"), + Map.entry("0118", "INVALID_CUSTOMER_NUMBER"), Map.entry("0003", "INVALID_HEADER"), + Map.entry("0301", "INVALID_PUSH_TOKEN"), Map.entry("0116", "INVALID_REQUEST_DATA_FOR_DIAGNOSIS"), + Map.entry("0014", "MISMATCH_DEVICE_GROUP"), Map.entry("0114", "MISMATCH_LOGIN_SESSION"), + Map.entry("0006", "MISMATCH_NONCE"), Map.entry("0115", "MISMATCH_REGISTRED_DEVICE"), + Map.entry("0110", "NOT_AGREED_TERMS"), Map.entry("0106", "NOT_CONNECTED_DEVICE"), + Map.entry("0120", "NOT_CONTRACT_CUSTOMER_NUMBER"), Map.entry("0010", "NOT_EXIST_DATA"), + Map.entry("0009", "NOT_EXIST_DEVICE"), Map.entry("0117", "NOT_EXIST_MODEL_JSON"), + Map.entry("0121", "NOT_REGISTERED_SMART_CARE"), Map.entry("0012", "NOT_SUPPORTED_COMMAND"), + Map.entry("8000", "NOT_SUPPORTED_COUNTRY"), Map.entry("0005", "NOT_SUPPORTED_SERVICE"), + Map.entry("0109", "NO_INFORMATION_DR"), Map.entry("0108", "NO_INFORMATION_SLEEP_MODE"), + Map.entry("0011", "NO_PERMISSION"), Map.entry("0113", "NO_PERMISION_MODIFY_RECIPE"), + Map.entry("0101", "NO_REGISTERED_DEVICE"), Map.entry("9006", "NO_USER_INFORMATION")); + + // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; @@ -140,6 +171,9 @@ public class LGThinqBindingConstants { public static final String WM_POWER_OFF_VALUE = "POWEROFF"; public static final String WM_SNAPSHOT_WASHER_DRYER_NODE = "washerDryer"; public static final String WM_CHANNEL_STATE_ID = "state"; + public static final String WM_CHANNEL_COURSE_ID = "course"; + public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; + public static final Map CAP_WP_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), Map.entry("@WM_STATE_RESERVE_W", "Reverse"), Map.entry("@WM_STATE_DETECTING_W", "Detecting"), @@ -149,5 +183,6 @@ public class LGThinqBindingConstants { Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_END_W", "End"), Map.entry("@WM_STATE_DRYING_W", "Drying"), Map.entry("@WM_STATE_DEMO_W", "Demonstration"), Map.entry("@WM_STATE_ERROR_W", "Error")); + // ============================================================================== } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java index 3c0e44d48c8f9..d3f848e7f15b7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -54,6 +54,8 @@ public class LGThinqWasherHandler extends LGThinqDeviceThing { @Nullable private WMCapability wmCapability; private final ChannelUID stateChannelUUID; + private final ChannelUID courseChannelUUID; + private final ChannelUID smartCourseChannelUUID; private final String lgPlatfomType; private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); @NonNullByDefault @@ -82,6 +84,8 @@ public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvide lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() : LGThinqApiV2ClientServiceImpl.getInstance(); stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); + courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); + smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); } static class AsyncCommandParams { @@ -148,6 +152,8 @@ private void updateThingStateFromLG() { updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); + updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); + updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); updateStatus(ThingStatus.ONLINE); } catch (LGThinqException e) { @@ -195,6 +201,16 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); stateDescriptionProvider.setStateOptions(stateChannelUUID, options); } + if (isLinked(courseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(courseChannelUUID, options); + } + if (isLinked(smartCourseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); + } } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index d87983e2c686e..fd27b44aab2ce 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -25,6 +25,7 @@ import javax.ws.rs.core.UriBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; @@ -151,6 +152,7 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (!"0000".equals(deviceSettings.get("resultCode"))) { + logErrorResultCodeMessage((String) deviceSettings.get("resultCode")); throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", deviceSettings.get("resultCode"))); @@ -177,6 +179,7 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (!"0000".equals(devicesResult.get("resultCode"))) { + logErrorResultCodeMessage((String) devicesResult.get("resultCode")); throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", devicesResult.get("resultCode"))); @@ -194,6 +197,15 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG return devices; } + protected static void logErrorResultCodeMessage(@Nullable String resultCode) { + if (resultCode == null) { + return; + } + String errMessage = ERROR_CODE_RESPONSE.get(resultCode.trim()); + logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", resultCode, + errMessage == null ? "UNKNOW ERROR MESSAGE" : errMessage); + } + /** * Get capability em registry/cache on file for next consult * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java index e98662a26e729..8fbb559252428 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java @@ -189,6 +189,7 @@ private Map handleV1GenericErrorResult(@Nullable RestResult resp throw new LGThinqApiException(String.format( "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); } else if (!"0000".equals(envelope.get("returnCd"))) { + logErrorResultCodeMessage((String) envelope.get("returnCd")); if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { // Disconnected Device throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); @@ -242,6 +243,7 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) && ((Map) envelop.get("workList")).get("returnData") != null) { Map workList = ((Map) envelop.get("workList")); if (!"0000".equals(workList.get("returnCode"))) { + logErrorResultCodeMessage((String) workList.get("resultCode")); LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); logger.warn("{}", e.getMessage()); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java index 71f428bb3ca1d..6014eb51f3903 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java @@ -201,6 +201,7 @@ private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThin metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { }); if (!"0000".equals(metaResult.get("resultCode"))) { + logErrorResultCodeMessage((String) metaResult.get("resultCode")); throw new LGThinqApiException( String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", metaResult.get("resultCode"))); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java index 42d97011d10fd..4294f98cc1b26 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java @@ -56,14 +56,24 @@ private static class MonitoringValue { private MonitoringValue monitoringValue = new MonitoringValue(); private Map courses = new LinkedHashMap(); + private Map smartCourses = new LinkedHashMap(); + public Map getCourses() { return courses; } + public Map getSmartCourses() { + return smartCourses; + } + public void addCourse(String courseLabel, String courseName) { courses.put(courseLabel, courseName); } + public void addSmartCourse(String courseLabel, String courseName) { + smartCourses.put(courseLabel, courseName); + } + public Map getState() { return monitoringValue.state; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java index 762db7d85a11b..00b36df380e9f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java @@ -18,7 +18,7 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -36,6 +36,18 @@ public class WasherDryerSnapshot implements Snapshot { private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; private String state = ""; private boolean online; + private String course = ""; + private String smartCourse = ""; + + @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) + @JsonProperty("courseFL24inchBaseTitan") + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } @Override public DevicePowerState getPowerStatus() { @@ -58,11 +70,21 @@ public void setOnline(boolean online) { } @JsonProperty("state") - @JsonGetter + @JsonAlias({ "state", "State" }) public String getState() { return state; } + @JsonProperty("smartCourseFL24inchBaseTitan") + @JsonAlias({ "smartCourseFL24inchBaseTitan", "SmartCourse" }) + public String getSmartCourse() { + return smartCourse; + } + + public void setSmartCourse(String smartCourse) { + this.smartCourse = smartCourse; + } + public void setState(String state) { this.state = state; if (state.equals(WM_POWER_OFF_VALUE)) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 3e864e50b8b35..c021a4a535500 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -420,4 +420,48 @@ Active Saving + + + String + + Washer Course + + + + + + + + + + + + + + + + + + + + String + + Washer Smart Course + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 1cbd8f9bf783e..44818829f1fad 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -16,6 +16,8 @@ + + From b5ffed8f787df2d0eb315685590cccffd7d3bed4 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 08:54:24 -0300 Subject: [PATCH 037/130] [lgthinq][Feat] Setting channels to readonly Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../src/main/resources/OH-INF/thing/channels.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index c021a4a535500..fe41577a6fd60 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -425,7 +425,7 @@ String Washer Course - + @@ -447,7 +447,7 @@ String Washer Smart Course - + From a139f03fc48c841656d6d7195f4b7517b97905c7 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 14:35:11 -0300 Subject: [PATCH 038/130] [lgthinq][Feat] Adding support for Temperature Level and Dook Lock channels Signed-off-by: nemerdaud --- .../internal/LGThinqBindingConstants.java | 4 +++- .../internal/LGThinqWasherHandler.java | 3 ++- .../model/washer/WasherDryerSnapshot.java | 20 +++++++++++++++++++ .../main/resources/OH-INF/thing/washer.xml | 2 ++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 4073085beb132..284d0b26edaa5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -107,7 +107,7 @@ public class LGThinqBindingConstants { public static final int SEARCH_TIME = 20; // delay between each devices's scan for state changes (in seconds) - public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 30; + public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 10; public static final Map ERROR_CODE_RESPONSE = Map.ofEntries(Map.entry("0000", "OK"), Map.entry("0001", "PARTIAL_OK"), Map.entry("0103", "OPERATION_IN_PROGRESS_DEVICE"), @@ -173,6 +173,8 @@ public class LGThinqBindingConstants { public static final String WM_CHANNEL_STATE_ID = "state"; public static final String WM_CHANNEL_COURSE_ID = "course"; public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; + public static final String WM_CHANNEL_TEMP_LEVEL_ID = "temperature-level"; + public static final String WM_CHANNEL_DOOR_LOCK_ID = "door-lock"; public static final Map CAP_WP_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java index d3f848e7f15b7..0269c63f015eb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -154,7 +154,8 @@ private void updateThingStateFromLG() { updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); - + updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); + updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); updateStatus(ThingStatus.ONLINE); } catch (LGThinqException e) { logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java index 00b36df380e9f..53943e435cb9f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java @@ -38,6 +38,8 @@ public class WasherDryerSnapshot implements Snapshot { private boolean online; private String course = ""; private String smartCourse = ""; + private String temperatureLevel = ""; + private String doorLock = ""; @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) @JsonProperty("courseFL24inchBaseTitan") @@ -85,6 +87,24 @@ public void setSmartCourse(String smartCourse) { this.smartCourse = smartCourse; } + @JsonProperty("temp") + public String getTemperatureLevel() { + return temperatureLevel; + } + + public void setTemperatureLevel(String temperatureLevel) { + this.temperatureLevel = temperatureLevel; + } + + @JsonProperty("doorLock") + public String getDoorLock() { + return doorLock; + } + + public void setDoorLock(String doorLock) { + this.doorLock = doorLock; + } + public void setState(String state) { this.state = state; if (state.equals(WM_POWER_OFF_VALUE)) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 44818829f1fad..11abb169992ed 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -18,6 +18,8 @@ + + From 7427d66a4680572b7ff892ec103a1919b4f7b953 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 19:17:27 -0300 Subject: [PATCH 039/130] [lgthinq][Fix] Fixing bug in mapping Power channel; Fix AC service monitoring after Snapshot refactory Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 38 ++-- .../internal/LGThinqBindingConstants.java | 2 +- .../internal/LGThinqWasherHandler.java | 27 ++- .../handler/LGThinqBridgeHandler.java | 4 +- .../lgservices/LGThinqACApiClientService.java | 32 ++++ ...a => LGThinqACApiV1ClientServiceImpl.java} | 114 ++---------- ...a => LGThinqACApiV2ClientServiceImpl.java} | 75 ++------ .../lgservices/LGThinqApiClientService.java | 12 +- .../LGThinqApiClientServiceImpl.java | 173 +++++++++++++++++- .../lgservices/LGThinqWMApiClientService.java | 21 +++ .../LGThinqWMApiV2ClientServiceImpl.java | 39 ++++ .../lgservices/model/SnapshotFactory.java | 38 ++-- .../lgservices/model/ac/ACSnapshot.java | 14 +- .../lgservices/model/ac/ACSnapshotV1.java | 58 ------ .../lgservices/model/ac/ACSnapshotV2.java | 58 ------ 15 files changed, 359 insertions(+), 346 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/{LGThinqApiV1ClientServiceImpl.java => LGThinqACApiV1ClientServiceImpl.java} (55%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/{LGThinqApiV2ClientServiceImpl.java => LGThinqACApiV2ClientServiceImpl.java} (71%) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 43d9a8bc0b245..840c555188275 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -24,14 +24,14 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqACApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -61,7 +61,7 @@ public class LGThinqAirConditionerHandler extends LGThinqDeviceThing { private final String lgPlatfomType; private final Logger logger = LoggerFactory.getLogger(LGThinqAirConditionerHandler.class); @NonNullByDefault - private final LGThinqApiClientService lgThinqApiClientService; + private final LGThinqACApiClientService lgThinqACApiClientService; private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; // Bridges status that this thing must top scanning for state change private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, @@ -84,8 +84,9 @@ public LGThinqAirConditionerHandler(Thing thing, super(thing); this.stateDescriptionProvider = stateDescriptionProvider; lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() - : LGThinqApiV2ClientServiceImpl.getInstance(); + lgThinqACApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) + ? LGThinqACApiV1ClientServiceImpl.getInstance() + : LGThinqACApiV2ClientServiceImpl.getInstance(); opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); opModeFanSpeedUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); } @@ -185,7 +186,7 @@ private String getBridgeId() { private void forceStopDeviceV1Monitor(String deviceId) { try { monitorV1Began = false; - lgThinqApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + lgThinqACApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); } catch (Exception e) { logger.error("Error stopping LG Device monitor", e); } @@ -216,8 +217,8 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { @Override public ACCapability getCapabilities() throws LGThinqApiException { if (acCapability == null) { - acCapability = (ACCapability) lgThinqApiClientService.getCapability(getDeviceId(), getDeviceUriJsonConfig(), - false); + acCapability = (ACCapability) lgThinqACApiClientService.getCapability(getDeviceId(), + getDeviceUriJsonConfig(), false); } return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } @@ -231,16 +232,16 @@ protected Logger getLogger() { private ACSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (ACSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); + return (ACSnapshot) lgThinqACApiClientService.getDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { - monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorWorkId = lgThinqACApiClientService.startMonitor(getBridgeId(), getDeviceId()); monitorV1Began = true; } } catch (LGThinqDeviceV1OfflineException e) { forceStopDeviceV1Monitor(deviceId); - ACSnapshot shot = new ACSnapshotV1(); + ACSnapshot shot = new ACSnapshot(); shot.setOnline(false); return shot; } catch (Exception e) { @@ -252,7 +253,8 @@ private ACSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = (ACSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + shot = (ACSnapshot) lgThinqACApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId, + DeviceTypes.AIR_CONDITIONER); if (shot != null) { return shot; } @@ -394,7 +396,7 @@ public void run() { switch (params.channelUID) { case CHANNEL_MOD_OP_ID: { if (params.command instanceof DecimalType) { - lgThinqApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), ((DecimalType) command).intValue()); } else { logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); @@ -403,7 +405,7 @@ public void run() { } case CHANNEL_FAN_SPEED_ID: { if (command instanceof DecimalType) { - lgThinqApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), ((DecimalType) command).intValue()); } else { logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); @@ -412,7 +414,7 @@ public void run() { } case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { - lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); } else { @@ -430,7 +432,7 @@ public void run() { logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); break; } - lgThinqApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), ACTargetTmp.statusOf(targetTemp)); break; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 284d0b26edaa5..31ea1454d986a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -142,7 +142,7 @@ public class LGThinqBindingConstants { // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; - public static final String CHANNEL_POWER_ID = "startThingStatePolling"; + public static final String CHANNEL_POWER_ID = "power"; public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java index 0269c63f015eb..94927b10d52e2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -24,10 +24,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqWMApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqWMApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.washer.WMCapability; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; @@ -59,7 +59,7 @@ public class LGThinqWasherHandler extends LGThinqDeviceThing { private final String lgPlatfomType; private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); @NonNullByDefault - private final LGThinqApiClientService lgThinqApiClientService; + private final LGThinqWMApiClientService lgThinqWMApiClientService; private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; // Bridges status that this thing must top scanning for state change private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, @@ -81,8 +81,7 @@ public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvide super(thing); this.stateDescriptionProvider = stateDescriptionProvider; lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() - : LGThinqApiV2ClientServiceImpl.getInstance(); + lgThinqWMApiClientService = LGThinqWMApiV2ClientServiceImpl.getInstance(); stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); @@ -182,7 +181,7 @@ private String getBridgeId() { private void forceStopDeviceV1Monitor(String deviceId) { try { monitorV1Began = false; - lgThinqApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + lgThinqWMApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); } catch (Exception e) { logger.error("Error stopping LG Device monitor", e); } @@ -217,8 +216,8 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { @Override public WMCapability getCapabilities() throws LGThinqApiException { if (wmCapability == null) { - wmCapability = (WMCapability) lgThinqApiClientService.getCapability(getDeviceId(), getDeviceUriJsonConfig(), - false); + wmCapability = (WMCapability) lgThinqWMApiClientService.getCapability(getDeviceId(), + getDeviceUriJsonConfig(), false); } return Objects.requireNonNull(wmCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } @@ -232,11 +231,11 @@ protected Logger getLogger() { private WasherDryerSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (WasherDryerSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); + return (WasherDryerSnapshot) lgThinqWMApiClientService.getDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { - monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorWorkId = lgThinqWMApiClientService.startMonitor(getBridgeId(), getDeviceId()); monitorV1Began = true; } } catch (LGThinqDeviceV1OfflineException e) { @@ -253,8 +252,8 @@ private WasherDryerSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGT while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = (WasherDryerSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, - monitorWorkId); + shot = (WasherDryerSnapshot) lgThinqWMApiClientService.getMonitorData(getBridgeId(), deviceId, + monitorWorkId, DeviceTypes.WASHING_MACHINE); if (shot != null) { return shot; } @@ -396,7 +395,7 @@ public void run() { switch (params.channelUID) { case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { - lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + lgThinqWMApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); } else { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 19bcf9a8797ce..8125ed3c9346c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -31,8 +31,8 @@ import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; import org.openhab.core.thing.*; @@ -74,7 +74,7 @@ public class LGThinqBridgeHandler extends ConfigStatusBridgeHandler implements L public LGThinqBridgeHandler(Bridge bridge) { super(bridge); tokenManager = TokenManager.getInstance(); - lgApiClient = LGThinqApiV1ClientServiceImpl.getInstance(); + lgApiClient = LGThinqACApiV1ClientServiceImpl.getInstance(); lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java new file mode 100644 index 0000000000000..0d0acddfbc3da --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; + +/** + * The {@link LGThinqACApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGThinqACApiClientService extends LGThinqApiClientService { + void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; + + void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; + + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGThinqApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java similarity index 55% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java index 8fbb559252428..fbf90659c718e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java @@ -24,54 +24,37 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotFactory; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - /** - * The {@link LGThinqApiV1ClientServiceImpl} + * The {@link LGThinqACApiV1ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqApiV1ClientServiceImpl extends LGThinqApiClientServiceImpl { - private static final LGThinqApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinqApiV1ClientServiceImpl.class); - private final ObjectMapper objectMapper = new ObjectMapper(); - private final TokenManager tokenManager; +public class LGThinqACApiV1ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqACApiClientService { + private static final LGThinqACApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinqACApiV1ClientServiceImpl.class); static { - instance = new LGThinqApiV1ClientServiceImpl(); - } - - private LGThinqApiV1ClientServiceImpl() { - tokenManager = TokenManager.getInstance(); + instance = new LGThinqACApiV1ClientServiceImpl(); } - public static LGThinqApiClientService getInstance() { + public static LGThinqACApiClientService getInstance() { return instance; } - @Override - protected TokenManager getTokenManager() { - return tokenManager; - } - /** * Get snapshot data from the device. * It works only for API V2 device versions! @@ -86,7 +69,7 @@ public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String device throw new UnsupportedOperationException("Method not supported in V1 API device."); } - public RestResult sendControlCommands(String bridgeName, String deviceId, String keyName, int value) + private RestResult sendControlCommands(String bridgeName, String deviceId, String keyName, int value) throws Exception { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); @@ -146,81 +129,10 @@ public void changeTargetTemperature(String bridgeName, String deviceId, ACTarget } } - /** - * Start monitor data form specific device. This is old one, works only on V1 API supported devices. - * - * @param deviceId Device ID - * @return Work1 to be uses to grab data during monitoring. - * @throws LGThinqApiException If some communication error occur. - */ - @Override - public String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String workerId = UUID.randomUUID().toString(); - String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," - + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - return Objects.requireNonNull((String) handleV1GenericErrorResult(resp).get("workId"), - "Unexpected StartMonitor json result. Node 'workId' not present"); - } - - @NonNull - private Map handleV1GenericErrorResult(@Nullable RestResult resp) - throws LGThinqApiException, LGThinqDeviceV1OfflineException { - Map metaResult; - Map envelope = Collections.emptyMap(); - if (resp == null) { - return envelope; - } - if (resp.getStatusCode() != 200) { - logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException( - String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - envelope = (Map) metaResult.get("lgedmRoot"); - if (envelope == null) { - throw new LGThinqApiException(String.format( - "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); - } else if (!"0000".equals(envelope.get("returnCd"))) { - logErrorResultCodeMessage((String) envelope.get("returnCd")); - if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { - // Disconnected Device - throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); - } - throw new LGThinqApiException( - String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", - metaResult.get("returnCd"))); - } - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream", e); - } - } - return envelope; - } - - @Override - public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," - + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - handleV1GenericErrorResult(resp); - } - @Override public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + @NonNull String workId, DeviceTypes deviceType) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -235,7 +147,7 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) try { envelop = handleV1GenericErrorResult(resp); } catch (LGThinqDeviceV1OfflineException e) { - ACSnapshot shot = new ACSnapshotV2(); + ACSnapshot shot = new ACSnapshot(); shot.setOnline(false); return shot; } @@ -252,7 +164,7 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) String jsonMonDataB64 = (String) workList.get("returnData"); String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); - ACSnapshot shot = objectMapper.readValue(jsonMon, ACSnapshotV1.class); + Snapshot shot = SnapshotFactory.getInstance().create(jsonMon, deviceType); shot.setOnline("E".equals(workList.get("deviceState"))); return shot; } else { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java similarity index 71% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java index 6014eb51f3903..9dcddb0ebe43a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java @@ -14,7 +14,6 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; -import java.io.File; import java.io.IOException; import java.util.Map; @@ -23,52 +22,38 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import org.openhab.binding.lgthinq.lgservices.model.SnapshotFactory; import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGThinqApiV2ClientServiceImpl} + * The {@link LGThinqACApiV2ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl { - private static final LGThinqApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinqApiV2ClientServiceImpl.class); - private final ObjectMapper objectMapper = new ObjectMapper(); - private final TokenManager tokenManager; +public class LGThinqACApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqACApiClientService { + private static final LGThinqACApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinqACApiV2ClientServiceImpl.class); static { - instance = new LGThinqApiV2ClientServiceImpl(); + instance = new LGThinqACApiV2ClientServiceImpl(); } - private LGThinqApiV2ClientServiceImpl() { - tokenManager = TokenManager.getInstance(); - } - - @Override - protected TokenManager getTokenManager() { - return tokenManager; - } - - public static LGThinqApiClientService getInstance() { + public static LGThinqACApiClientService getInstance() { return instance; } @@ -77,43 +62,8 @@ private Map getCommonV2Headers(String language, String country, return getCommonHeaders(language, country, accessToken, userNumber); } - /** - * Get snapshot data from the device. - * It works only for API V2 device versions! - * - * @param deviceId device ID for de desired V2 LG Thinq. - * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. - */ - @Override - @Nullable - public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { - Map deviceSettings = getDeviceSettings(bridgeName, deviceId); - if (deviceSettings.get("snapshot") != null) { - Map snapMap = (Map) deviceSettings.get("snapshot"); - if (logger.isDebugEnabled()) { - try { - objectMapper.writeValue(new File(String.format( - LGThinqBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", - deviceId)), deviceSettings); - } catch (IOException e) { - logger.error("Error saving data trace", e); - } - } - if (snapMap == null) { - // No snapshot value provided - return null; - } - - Snapshot shot = SnapshotFactory.getInstance().create(deviceSettings); - shot.setOnline((Boolean) snapMap.get("online")); - return shot; - } - return null; - } - - public RestResult sendControlCommands(String bridgeName, String deviceId, String command, String keyName, int value) - throws Exception { + private RestResult sendControlCommands(String bridgeName, String deviceId, String command, String keyName, + int value) throws Exception { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId)); @@ -213,10 +163,6 @@ private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThin } } - private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { - return genericHandleDeviceSettingsResult(resp, logger, objectMapper); - } - @Override public void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { @@ -225,7 +171,8 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) @Override public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + @NonNull String workId, DeviceTypes deviceType) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { throw new UnsupportedOperationException("Not supported in V2 API."); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java index 7cd41f254fd3a..8aa1e3d9aede3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java @@ -25,7 +25,6 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.*; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; /** * The {@link LGThinqApiClientService} @@ -51,13 +50,6 @@ public interface LGThinqApiClientService { void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; - void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; - - void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; - - void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGThinqApiException; - String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; @@ -69,6 +61,6 @@ File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; @Nullable - Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; + Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId, + DeviceTypes deviceType) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index fd27b44aab2ce..34474809a7267 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -24,16 +24,20 @@ import javax.ws.rs.core.UriBuilder; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.Capability; -import org.openhab.binding.lgthinq.lgservices.model.CapabilityFactory; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,16 +46,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGThinqApiV1ClientServiceImpl} + * The {@link LGThinqACApiV1ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault public abstract class LGThinqApiClientServiceImpl implements LGThinqApiClientService { private static final Logger logger = LoggerFactory.getLogger(LGThinqApiClientServiceImpl.class); - private final ObjectMapper objectMapper = new ObjectMapper(); + protected final ObjectMapper objectMapper = new ObjectMapper(); + protected final TokenManager tokenManager; - protected abstract TokenManager getTokenManager(); + protected LGThinqApiClientServiceImpl() { + this.tokenManager = TokenManager.getInstance(); + } static Map getCommonHeaders(String language, String country, String accessToken, String userNumber) { @@ -86,7 +93,7 @@ static Map getCommonHeaders(String language, String country, Str @Override public List listAccountDevices(String bridgeName) throws LGThinqApiException { try { - TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); @@ -123,7 +130,7 @@ public File loadDeviceCapability(String deviceId, String uri, boolean forceRecre @Override public Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException { try { - TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -224,4 +231,154 @@ public Capability getCapability(String deviceId, String uri, boolean forceRecrea throw new LGThinqApiException("Error reading IO interface", e); } } + + @NonNull + protected Map handleV1GenericErrorResult(@Nullable RestResult resp) + throws LGThinqApiException, LGThinqDeviceV1OfflineException { + Map metaResult; + Map envelope = Collections.emptyMap(); + if (resp == null) { + return envelope; + } + if (resp.getStatusCode() != 200) { + logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGThinqApiException( + String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + envelope = (Map) metaResult.get("lgedmRoot"); + if (envelope == null) { + throw new LGThinqApiException(String.format( + "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); + } else if (!"0000".equals(envelope.get("returnCd"))) { + logErrorResultCodeMessage((String) envelope.get("returnCd")); + if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { + // Disconnected Device + throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); + } + throw new LGThinqApiException( + String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", + metaResult.get("returnCd"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + } + return envelope; + } + + public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull String workId, DeviceTypes deviceType) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" + + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" + + " ]\n" + " }\n" + "}", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + Map envelop = null; + // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with + // offline flag. + try { + envelop = handleV1GenericErrorResult(resp); + } catch (LGThinqDeviceV1OfflineException e) { + ACSnapshot shot = new ACSnapshot(); + shot.setOnline(false); + return shot; + } + if (envelop.get("workList") != null + && ((Map) envelop.get("workList")).get("returnData") != null) { + Map workList = ((Map) envelop.get("workList")); + if (!"0000".equals(workList.get("returnCode"))) { + logErrorResultCodeMessage((String) workList.get("resultCode")); + LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( + String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); + logger.warn("{}", e.getMessage()); + throw e; + } + + String jsonMonDataB64 = (String) workList.get("returnData"); + String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); + Snapshot shot = SnapshotFactory.getInstance().create(jsonMon, deviceType); + shot.setOnline("E".equals(workList.get("deviceState"))); + return shot; + } else { + // no data available yet + return null; + } + } + + /** + * Get snapshot data from the device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGThinqApiException if some communication error occur. + */ + @Override + @Nullable + public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + Map deviceSettings = getDeviceSettings(bridgeName, deviceId); + if (deviceSettings.get("snapshot") != null) { + Map snapMap = (Map) deviceSettings.get("snapshot"); + if (logger.isDebugEnabled()) { + try { + objectMapper.writeValue(new File(String.format( + LGThinqBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", + deviceId)), deviceSettings); + } catch (IOException e) { + logger.error("Error saving data trace", e); + } + } + if (snapMap == null) { + // No snapshot value provided + return null; + } + + Snapshot shot = SnapshotFactory.getInstance().create(deviceSettings); + shot.setOnline((Boolean) snapMap.get("online")); + return shot; + } + return null; + } + + /** + * Start monitor data form specific device. This is old one, works only on V1 API supported devices. + * + * @param deviceId Device ID + * @return Work1 to be uses to grab data during monitoring. + * @throws LGThinqApiException If some communication error occur. + */ + @Override + public String startMonitor(String bridgeName, String deviceId) + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String workerId = UUID.randomUUID().toString(); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + return Objects.requireNonNull((String) handleV1GenericErrorResult(resp).get("workId"), + "Unexpected StartMonitor json result. Node 'workId' not present"); + } + + @Override + public void stopMonitor(String bridgeName, String deviceId, String workId) + throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + handleV1GenericErrorResult(resp); + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java new file mode 100644 index 0000000000000..9575166ed674e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices; + +/** + * The {@link LGThinqWMApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +public interface LGThinqWMApiClientService extends LGThinqApiClientService { +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..fdd617c2b15d4 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices; + +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; + +/** + * The {@link LGThinqWMApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +public class LGThinqWMApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqWMApiClientService { + + private static final LGThinqWMApiClientService instance; + static { + instance = new LGThinqWMApiV2ClientServiceImpl(); + } + + public static LGThinqWMApiClientService getInstance() { + return instance; + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet."); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 8e7205063f465..1542702e34172 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -14,15 +14,17 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -42,23 +44,37 @@ public static final SnapshotFactory getInstance() { return instance; } + /** + * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) + * + * @param snapshotDataJson V1: decoded returnedData; V2: snapshot body + * @param deviceType device type + * @return returns Snapshot implementation based on device type provided + * @throws LGThinqApiException any error. + */ + public Snapshot create(String snapshotDataJson, DeviceTypes deviceType) throws LGThinqApiException { + try { + Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { + }); + Map deviceSetting = new HashMap<>(); + deviceSetting.put("deviceType", deviceType.deviceTypeId()); + deviceSetting.put("snapshot", snapshotMap); + return create(deviceSetting); + } catch (JsonProcessingException e) { + throw new LGThinqApiException("Unexpected Error unmarshalling json to map", e); + } + } + public Snapshot create(Map deviceSettings) throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); - Map snapMap = (Map) deviceSettings.get("snapshot"); + Map snapMap = ((Map) deviceSettings.get("snapshot")); if (snapMap == null) { throw new LGThinqApiException("snapshot node not present in device monitoring result."); } LGAPIVerion version = discoveryAPIVersion(snapMap, type); switch (type) { case AIR_CONDITIONER: - switch (version) { - case V1_0: { - return objectMapper.convertValue(snapMap, ACSnapshotV2.class); - } - case V2_0: { - return objectMapper.convertValue(snapMap, ACSnapshotV1.class); - } - } + return objectMapper.convertValue(snapMap, ACSnapshot.class); case WASHING_MACHINE: switch (version) { case V1_0: { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java index 5a049417303ab..ec882915da164 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java @@ -17,8 +17,10 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; /** * The {@link ACSnapshot} @@ -27,7 +29,7 @@ */ @NonNullByDefault @JsonIgnoreProperties(ignoreUnknown = true) -public abstract class ACSnapshot implements Snapshot { +public class ACSnapshot implements Snapshot { private int airWindStrength; @@ -56,6 +58,8 @@ public ACFanSpeed getAcFanSpeed() { return ACFanSpeed.statusOf(airWindStrength); } + @JsonProperty("airState.windStrength") + @JsonAlias("WindStrength") public Integer getAirWindStrength() { return airWindStrength; } @@ -64,6 +68,8 @@ public void setAirWindStrength(Integer airWindStrength) { this.airWindStrength = airWindStrength; } + @JsonProperty("airState.tempState.target") + @JsonAlias("TempCfg") public Double getTargetTemperature() { return targetTemperature; } @@ -72,6 +78,8 @@ public void setTargetTemperature(Double targetTemperature) { this.targetTemperature = targetTemperature; } + @JsonProperty("airState.tempState.current") + @JsonAlias("TempCur") public Double getCurrentTemperature() { return currentTemperature; } @@ -80,6 +88,8 @@ public void setCurrentTemperature(Double currentTemperature) { this.currentTemperature = currentTemperature; } + @JsonProperty("airState.opMode") + @JsonAlias("OpMode") public Integer getOperationMode() { return operationMode; } @@ -89,6 +99,8 @@ public void setOperationMode(Integer operationMode) { } @Nullable + @JsonProperty("airState.operation") + @JsonAlias("Operation") public Integer getOperation() { return operation; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java deleted file mode 100644 index 8cec78795c997..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link ACSnapshotV1} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ACSnapshotV1 extends ACSnapshot { - - @Override - @JsonProperty("WindStrength") - public Integer getAirWindStrength() { - return super.getAirWindStrength(); - } - - @Override - @JsonProperty("TempCfg") - public Double getTargetTemperature() { - return super.getTargetTemperature(); - } - - @Override - @JsonProperty("TempCur") - public Double getCurrentTemperature() { - return super.getCurrentTemperature(); - } - - @Override - @JsonProperty("OpMode") - public Integer getOperationMode() { - return super.getOperationMode(); - } - - @Override - @JsonProperty("Operation") - @Nullable - public Integer getOperation() { - return super.getOperation(); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java deleted file mode 100644 index 3d5da370f5ebc..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link ACSnapshotV2} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ACSnapshotV2 extends ACSnapshot { - - @Override - @JsonProperty("airState.windStrength") - public Integer getAirWindStrength() { - return super.getAirWindStrength(); - } - - @Override - @JsonProperty("airState.tempState.target") - public Double getTargetTemperature() { - return super.getTargetTemperature(); - } - - @Override - @JsonProperty("airState.tempState.current") - public Double getCurrentTemperature() { - return super.getCurrentTemperature(); - } - - @Override - @JsonProperty("airState.opMode") - public Integer getOperationMode() { - return super.getOperationMode(); - } - - @Override - @JsonProperty("airState.operation") - @Nullable - public Integer getOperation() { - return super.getOperation(); - } -} From 296ce53d25f9921800bf908675d3f8050e15886b Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 21:27:00 -0300 Subject: [PATCH 040/130] [lgthinq][Fix] Fixing discovery process Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../lgthinq/internal/handler/LGThinqBridgeHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 8125ed3c9346c..181b19f3afc83 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -202,8 +202,8 @@ protected void doConnectedRun() throws LGThinqException { Map lastDevicesDiscoveredCopy = new HashMap<>(lastDevicesDiscovered); for (final LGDevice device : lgApiClient.listAccountDevices(bridgeName)) { String deviceId = device.getDeviceId(); - - if (lGDeviceRegister.get(deviceId) == null) { + // if not registered yet, and not discovered before, then add to discovery list. + if (lGDeviceRegister.get(deviceId) == null && !lastDevicesDiscovered.containsKey(deviceId)) { logger.debug("Adding new LG Device to things registry with id:{}", deviceId); if (discoveryService != null) { discoveryService.addLgDeviceDiscovery(device); From 02323c1aa7fe54bf006dad8a83adde2f6f176c76 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Mon, 14 Feb 2022 21:33:13 -0300 Subject: [PATCH 041/130] [lgthinq][Fix] Fixing mapping bugs on Washer; [Feat] refactoring design model, Dryer & Washer View Channel support Signed-off-by: nemerdaud --- ...uration.java => LGThinQConfiguration.java} | 8 +- ...inQDeviceDynStateDescriptionProvider.java} | 9 +- .../LGThinqAirConditionerHandler.java | 452 ------------------ .../internal/LGThinqBindingConstants.java | 190 -------- .../lgthinq/internal/LGThinqDeviceThing.java | 106 ---- .../internal/LGThinqHandlerFactory.java | 84 ---- .../internal/LGThinqWasherHandler.java | 419 ---------------- .../internal/handler/LGThinQDryerHandler.java | 217 +++++++++ .../handler/LGThinQWasherHandler.java | 196 ++++++++ .../internal/handler/LGThinqBridge.java | 31 -- .../handler/LGThinqBridgeHandler.java | 327 ------------- .../lgservices/LGThinqACApiClientService.java | 32 -- .../LGThinqACApiV1ClientServiceImpl.java | 175 ------- .../LGThinqACApiV2ClientServiceImpl.java | 178 ------- .../lgservices/LGThinqApiClientService.java | 66 --- .../LGThinqApiClientServiceImpl.java | 384 --------------- .../lgservices/LGThinqWMApiClientService.java | 21 - .../LGThinqWMApiV2ClientServiceImpl.java | 39 -- .../lgservices/model/SnapshotFactory.java | 34 +- .../model/dryer/DryerCapability.java | 123 +++++ .../lgservices/model/dryer/DryerSnapshot.java | 164 +++++++ ...MCapability.java => WasherCapability.java} | 4 +- ...DryerSnapshot.java => WasherSnapshot.java} | 34 +- .../main/resources/OH-INF/thing/washer.xml | 5 +- 24 files changed, 769 insertions(+), 2529 deletions(-) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGThinqConfiguration.java => LGThinQConfiguration.java} (91%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGThinqDeviceDynStateDescriptionProvider.java => LGThinQDeviceDynStateDescriptionProvider.java} (81%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/{WMCapability.java => WasherCapability.java} (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/{WasherDryerSnapshot.java => WasherSnapshot.java} (76%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java similarity index 91% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java index 8bd5ef613199a..b718d63d29068 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java @@ -15,12 +15,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link LGThinqConfiguration} class contains fields mapping thing configuration parameters. + * The {@link LGThinQConfiguration} class contains fields mapping thing configuration parameters. * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqConfiguration { +public class LGThinQConfiguration { /** * Sample configuration parameters. Replace with your own. */ @@ -31,10 +31,10 @@ public class LGThinqConfiguration { public Integer pollingIntervalSec = 0; public String alternativeServer = ""; - public LGThinqConfiguration() { + public LGThinQConfiguration() { } - public LGThinqConfiguration(String username, String password, String country, String language, + public LGThinQConfiguration(String username, String password, String country, String language, Integer pollingIntervalSec, String alternativeServer) { this.username = username; this.password = password; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java similarity index 81% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java index 2b50c6e63ca83..40c1f85c41332 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.lgthinq.internal; +import org.openhab.binding.lgthinq.internal.handler.LGThinQAbstractDeviceHandler; import org.openhab.core.events.EventPublisher; import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; @@ -22,14 +23,14 @@ import org.osgi.service.component.annotations.Reference; /** - * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things + * The {@link LGThinQAbstractDeviceHandler} is a main interface contract for all LG Thinq things * * @author Nemer Daud - Initial contribution */ -@Component(service = { DynamicStateDescriptionProvider.class, LGThinqDeviceDynStateDescriptionProvider.class }) -public class LGThinqDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { +@Component(service = { DynamicStateDescriptionProvider.class, LGThinQDeviceDynStateDescriptionProvider.class }) +public class LGThinQDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { @Activate - public LGThinqDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // + public LGThinQDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { this.eventPublisher = eventPublisher; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java deleted file mode 100644 index 840c555188275..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ /dev/null @@ -1,452 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.util.*; -import java.util.concurrent.*; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.LGThinqACApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.StateOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqAirConditionerHandler extends LGThinqDeviceThing { - - private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; - private final ChannelUID opModeChannelUID; - private final ChannelUID opModeFanSpeedUID; - @Nullable - private ACCapability acCapability; - private final String lgPlatfomType; - private final Logger logger = LoggerFactory.getLogger(LGThinqAirConditionerHandler.class); - @NonNullByDefault - private final LGThinqACApiClientService lgThinqACApiClientService; - private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; - // Bridges status that this thing must top scanning for state change - private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, - ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, - ThingStatusDetail.CONFIGURATION_ERROR); - private @Nullable ScheduledFuture thingStatePollingJob; - private @Nullable Future commandExecutorQueueJob; - // *** Long running isolated threadpools. - private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); - private final ExecutorService executorService = Executors.newFixedThreadPool(1); - - private boolean monitorV1Began = false; - private String monitorWorkId = ""; - private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); - @NonNullByDefault - private String bridgeId = ""; - - public LGThinqAirConditionerHandler(Thing thing, - LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing); - this.stateDescriptionProvider = stateDescriptionProvider; - lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgThinqACApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) - ? LGThinqACApiV1ClientServiceImpl.getInstance() - : LGThinqACApiV2ClientServiceImpl.getInstance(); - opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); - opModeFanSpeedUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); - } - - static class AsyncCommandParams { - final String channelUID; - final Command command; - - public AsyncCommandParams(String channelUUID, Command command) { - this.channelUID = channelUUID; - this.command = command; - } - } - - @Override - public Collection> getServices() { - return super.getServices(); - } - - @Override - public void initialize() { - logger.debug("Initializing Thinq thing."); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - - @Override - protected void startCommandExecutorQueueJob() { - if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { - commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); - } - } - - private ExecutorService getExecutorService() { - return executorService; - } - - private void stopCommandExecutorQueueJob() { - if (commandExecutorQueueJob != null) { - commandExecutorQueueJob.cancel(true); - } - } - - protected void startThingStatePolling() { - if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { - thingStatePollingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, - DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); - } - } - - private void updateThingStateFromLG() { - try { - ACSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); - if (shot == null) { - // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. - return; - } - if (!shot.isOnline()) { - if (getThing().getStatus() != ThingStatus.OFFLINE) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); - updateState(CHANNEL_POWER_ID, - OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_OFF)); - } - return; - } - - updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); - // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as - // soon as LG WebOs do) - updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(shot.getAirWindStrength())); - updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(shot.getCurrentTemperature())); - updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); - updateStatus(ThingStatus.ONLINE); - } catch (Exception e) { - logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", - getDeviceAlias(), getDeviceId(), e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - - private ScheduledExecutorService getLocalScheduler() { - return pollingScheduler; - } - - private String getBridgeId() { - if (bridgeId.isBlank() && getBridge() == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); - logger.error("Configuration error um Thinq Thing - No Bridge defined for the thing."); - return "UNKNOWN"; - } else if (bridgeId.isBlank() && getBridge() != null) { - bridgeId = getBridge().getUID().getId(); - } - return bridgeId; - } - - private void forceStopDeviceV1Monitor(String deviceId) { - try { - monitorV1Began = false; - lgThinqACApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); - } catch (Exception e) { - logger.error("Error stopping LG Device monitor", e); - } - } - - @NonNull - private String emptyIfNull(@Nullable String value) { - return value == null ? "" : "" + value; - } - - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - ACCapability acCap = getCapabilities(); - if (isLinked(opModeChannelUID)) { - List options = new ArrayList<>(); - acCap.getSupportedOpMode().forEach((v) -> options - .add(new StateOption(emptyIfNull(acCap.getOpMod().get(v)), emptyIfNull(CAP_AC_OP_MODE.get(v))))); - stateDescriptionProvider.setStateOptions(opModeChannelUID, options); - } - if (isLinked(opModeFanSpeedUID)) { - List options = new ArrayList<>(); - acCap.getSupportedFanSpeed().forEach((v) -> options.add( - new StateOption(emptyIfNull(acCap.getFanSpeed().get(v)), emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); - stateDescriptionProvider.setStateOptions(opModeFanSpeedUID, options); - } - } - - @Override - public ACCapability getCapabilities() throws LGThinqApiException { - if (acCapability == null) { - acCapability = (ACCapability) lgThinqACApiClientService.getCapability(getDeviceId(), - getDeviceUriJsonConfig(), false); - } - return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); - } - - @Override - protected Logger getLogger() { - return logger; - } - - @Nullable - private ACSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { - // analise de platform version - if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (ACSnapshot) lgThinqACApiClientService.getDeviceData(getBridgeId(), getDeviceId()); - } else { - try { - if (!monitorV1Began) { - monitorWorkId = lgThinqACApiClientService.startMonitor(getBridgeId(), getDeviceId()); - monitorV1Began = true; - } - } catch (LGThinqDeviceV1OfflineException e) { - forceStopDeviceV1Monitor(deviceId); - ACSnapshot shot = new ACSnapshot(); - shot.setOnline(false); - return shot; - } catch (Exception e) { - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); - } - int retries = 10; - ACSnapshot shot; - while (retries > 0) { - // try to get monitoring data result 3 times. - try { - shot = (ACSnapshot) lgThinqACApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId, - DeviceTypes.AIR_CONDITIONER); - if (shot != null) { - return shot; - } - Thread.sleep(500); - retries--; - } catch (LGThinqDeviceV1MonitorExpiredException e) { - forceStopDeviceV1Monitor(deviceId); - logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); - return null; - } catch (Exception e) { - // If it can't get monitor handler, then stop monitor and restart the process again in new - // interaction - // Force restart monitoring because of the errors returned (just in case) - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); - } - } - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Exhausted trying to get monitor data for the device:" + deviceId); - } - } - - protected void stopThingStatePolling() { - if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { - logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); - thingStatePollingJob.cancel(true); - } - } - - private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { - if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { - // start the thing polling - startThingStatePolling(); - } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE - && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { - // comunication error is not a specific Bridge error, then we must analise it to give - // this thinq the change to recovery from communication errors - if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR - || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { - // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if - // the bridge is out - stopThingStatePolling(); - } - - } - lastThingStatus = newStatus; - } - - @Override - protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { - handleStatusChanged(newStatus, statusDetail); - super.updateStatus(newStatus, statusDetail, description); - } - - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceId() { - return getThing().getUID().getId(); - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } - - @Override - public String getDeviceModelName() { - return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); - } - - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } - - @Override - public boolean onDeviceStateChanged() { - // TODO - HANDLE IT, Think if it's needed - return false; - } - - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void onDeviceGone() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void dispose() { - if (thingStatePollingJob != null) { - thingStatePollingJob.cancel(true); - stopThingStatePolling(); - stopCommandExecutorQueueJob(); - thingStatePollingJob = null; - } - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - updateThingStateFromLG(); - } else { - AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); - try { - // Ensure commands are send in a pipe per device. - commandBlockQueue.add(params); - } catch (IllegalStateException ex) { - logger.error( - "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); - updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, - "Device Command Queue is Busy"); - } - - } - } - - private final Runnable queuedCommandExecutor = new Runnable() { - @Override - public void run() { - while (true) { - AsyncCommandParams params; - try { - params = commandBlockQueue.take(); - } catch (InterruptedException e) { - logger.debug("Interrupting async command queue executor."); - return; - } - Command command = params.command; - - try { - switch (params.channelUID) { - case CHANNEL_MOD_OP_ID: { - if (params.command instanceof DecimalType) { - lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), - ((DecimalType) command).intValue()); - } else { - logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); - } - break; - } - case CHANNEL_FAN_SPEED_ID: { - if (command instanceof DecimalType) { - lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), - ((DecimalType) command).intValue()); - } else { - logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); - } - break; - } - case CHANNEL_POWER_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? DevicePowerState.DV_POWER_ON - : DevicePowerState.DV_POWER_OFF); - } else { - logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); - } - break; - } - case CHANNEL_TARGET_TEMP_ID: { - double targetTemp; - if (command instanceof DecimalType) { - targetTemp = ((DecimalType) command).doubleValue(); - } else if (command instanceof QuantityType) { - targetTemp = ((QuantityType) command).doubleValue(); - } else { - logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); - break; - } - lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), - ACTargetTmp.statusOf(targetTemp)); - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, - params.channelUID); - } - } - } catch (LGThinqException e) { - logger.error("Error executing Command {} to the channel {}. Thing goes offline until retry", - command, params.channelUID, e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - } - }; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java deleted file mode 100644 index 31ea1454d986a..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import java.io.File; -import java.util.Map; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.core.OpenHAB; -import org.openhab.core.thing.ThingTypeUID; - -/** - * The {@link LGThinqBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqBindingConstants { - - public static final String BINDING_ID = "lgthinq"; - public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); - public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner - public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.WASHING_MACHINE.deviceTypeId()); // deviceType from AirConditioner - public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, - THING_TYPE_WASHING_MACHINE, THING_TYPE_BRIDGE); - public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_AIR_CONDITIONER, - THING_TYPE_WASHING_MACHINE); - - public static String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; - public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; - public static String BASE_CAP_CONFIG_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-cap.json"; - public static final String V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; - public static final String V2_USER_INFO = "/users/profile"; - public static final String V2_API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; - public static final String V2_CLIENT_ID = "65260af7e8e6547b51fdccf930097c51eb9885a508d3fddfa9ee6cdec22ae1bd"; - public static final String V2_SVC_PHASE = "OP"; - public static final String V2_APP_LEVEL = "PRD"; - public static final String V2_APP_OS = "LINUX"; - public static final String V2_APP_TYPE = "NUTS"; - public static final String V2_APP_VER = "3.0.1700"; - public static final String V2_SESSION_LOGIN_PATH = "/emp/v2.0/account/session/"; - public static final String V2_LS_PATH = "/service/application/dashboard"; - public static final String V2_DEVICE_CONFIG_PATH = "service/devices/"; - public static final String V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/control-sync"; - public static final String V1_START_MON_PATH = "rti/rtiMon"; - public static final String V1_MON_DATA_PATH = "rti/rtiResult"; - public static final String V1_CONTROL_OP = "rti/rtiControl"; - public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; - public static final String GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; - public static final String GATEWAY_SERVICE_PATH_V1 = "/api/common/gatewayUriList"; - public static final String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; - public static final String PRE_LOGIN_PATH = "/preLogin"; - public static final String SECURITY_KEY = "nuts_securitykey"; - public static final String APP_KEY = "wideq"; - public static final String DATA_ROOT = "result"; - public static final String POST_DATA_ROOT = "lgedmRoot"; - public static final String RETURN_CODE_ROOT = "resultCode"; - public static final String RETURN_MESSAGE_ROOT = "returnMsg"; - public static final String SVC_CODE = "SVC202"; - public static final String OAUTH_SECRET_KEY = "c053c2a6ddeb7ad97cb0eed0dcb31cf8"; - public static final String OAUTH_CLIENT_KEY = "LGAO722A02"; - public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss +0000"; - public static final String DEFAULT_COUNTRY = "US"; - public static final String DEFAULT_LANGUAGE = "en-US"; - public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; - public static final String V2_EMP_SESS_PATH = "/emp/oauth2/token/empsession"; - public static final String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com" + V2_EMP_SESS_PATH; - public static final String API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; - - public static final String API_KEY_V1 = "wideq"; - public static final String API_SECURITY_KEY_V1 = "nuts_securitykey"; - - // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, - // and the build id of the thinq app it can also just be a random - // string, we use the same client id used for oauth - public static final String CLIENT_ID = "LGAO221A02"; - public static final String MESSAGE_ID = "wideq"; - public static final String SVC_PHASE = "OP"; - public static final String APP_LEVEL = "PRD"; - public static final String APP_OS = "ANDROID"; - public static final String APP_TYPE = "NUTS"; - public static final String APP_VER = "3.5.1200"; - - public static final String DEVICE_ID = "device_id"; - public static final String MODEL_NAME = "model_name"; - public static final String DEVICE_ALIAS = "device_alias"; - public static final String MODEL_URL_INFO = "model_url_info"; - public static final String PLATFORM_TYPE = "platform_type"; - public static final String PLATFORM_TYPE_V1 = "thinq1"; - public static final String PLATFORM_TYPE_V2 = "thinq2"; - static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); - - public static final int SEARCH_TIME = 20; - // delay between each devices's scan for state changes (in seconds) - public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 10; - - public static final Map ERROR_CODE_RESPONSE = Map.ofEntries(Map.entry("0000", "OK"), - Map.entry("0001", "PARTIAL_OK"), Map.entry("0103", "OPERATION_IN_PROGRESS_DEVICE"), - Map.entry("0007", "PORTAL_INTERWORKING_ERROR"), Map.entry("0104", "PROCESSING_REFRIGERATOR"), - Map.entry("0111", "RESPONSE_DELAY_DEVICE"), Map.entry("8107", "SERVICE_SERVER_ERROR"), - Map.entry("8102", "SSP_ERROR"), Map.entry("9020", "TIME_OUT"), Map.entry("8104", "WRONG_XML_OR_URI"), - Map.entry("9000", "AWS_IOT_ERROR"), Map.entry("8105", "AWS_S3_ERROR"), Map.entry("8106", "AWS_SQS_ERROR"), - Map.entry("9002", "BASE64_DECODING_ERROR"), Map.entry("9001", "BASE64_ENCODING_ERROR"), - Map.entry("8103", "CLIP_ERROR"), Map.entry("0105", "CONTROL_ERROR_REFRIGERATOR"), - Map.entry("9003", "CREATE_SESSION_FAIL"), Map.entry("9004", "DB_PROCESSING_FAIL"), - Map.entry("8101", "DM_ERROR"), Map.entry("0013", "DUPLICATED_ALIAS"), Map.entry("0008", "DUPLICATED_DATA"), - Map.entry("0004", "DUPLICATED_LOGIN"), Map.entry("0102", "EMP_AUTHENTICATION_FAILED"), - Map.entry("8900", "ETC_COMMUNICATION_ERROR"), Map.entry("9999", "ETC_ERROR"), - Map.entry("0112", "EXCEEDING_LIMIT"), Map.entry("0119", "EXPIRED_CUSTOMER_NUMBER"), - Map.entry("9005", "EXPIRES_SESSION_BY_WITHDRAWAL"), Map.entry("0100", "FAIL"), - Map.entry("8001", "INACTIVE_API"), Map.entry("0107", "INSUFFICIENT_STORAGE_SPACE"), - Map.entry("9010", "INVAILD_CSR"), Map.entry("0002", "INVALID_BODY"), - Map.entry("0118", "INVALID_CUSTOMER_NUMBER"), Map.entry("0003", "INVALID_HEADER"), - Map.entry("0301", "INVALID_PUSH_TOKEN"), Map.entry("0116", "INVALID_REQUEST_DATA_FOR_DIAGNOSIS"), - Map.entry("0014", "MISMATCH_DEVICE_GROUP"), Map.entry("0114", "MISMATCH_LOGIN_SESSION"), - Map.entry("0006", "MISMATCH_NONCE"), Map.entry("0115", "MISMATCH_REGISTRED_DEVICE"), - Map.entry("0110", "NOT_AGREED_TERMS"), Map.entry("0106", "NOT_CONNECTED_DEVICE"), - Map.entry("0120", "NOT_CONTRACT_CUSTOMER_NUMBER"), Map.entry("0010", "NOT_EXIST_DATA"), - Map.entry("0009", "NOT_EXIST_DEVICE"), Map.entry("0117", "NOT_EXIST_MODEL_JSON"), - Map.entry("0121", "NOT_REGISTERED_SMART_CARE"), Map.entry("0012", "NOT_SUPPORTED_COMMAND"), - Map.entry("8000", "NOT_SUPPORTED_COUNTRY"), Map.entry("0005", "NOT_SUPPORTED_SERVICE"), - Map.entry("0109", "NO_INFORMATION_DR"), Map.entry("0108", "NO_INFORMATION_SLEEP_MODE"), - Map.entry("0011", "NO_PERMISSION"), Map.entry("0113", "NO_PERMISION_MODIFY_RECIPE"), - Map.entry("0101", "NO_REGISTERED_DEVICE"), Map.entry("9006", "NO_USER_INFORMATION")); - - // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= - // CHANNEL IDS - public static final String CHANNEL_MOD_OP_ID = "op_mode"; - public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; - public static final String CHANNEL_POWER_ID = "power"; - public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; - public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; - - public static final Map CAP_AC_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", - "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", - "@AC_MAIN_OPERATION_MODE_HEAT_W", "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", - "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", "@AC_MAIN_OPERATION_MODE_AI_W", "AI", - "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", - "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); - - public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( - Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), - Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), Map.entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), - Map.entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), Map.entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Nature"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); - - // ====================== WASHING MACHINE CONSTANTS ============================= - public static final String WM_POWER_OFF_VALUE = "POWEROFF"; - public static final String WM_SNAPSHOT_WASHER_DRYER_NODE = "washerDryer"; - public static final String WM_CHANNEL_STATE_ID = "state"; - public static final String WM_CHANNEL_COURSE_ID = "course"; - public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; - public static final String WM_CHANNEL_TEMP_LEVEL_ID = "temperature-level"; - public static final String WM_CHANNEL_DOOR_LOCK_ID = "door-lock"; - - public static final Map CAP_WP_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), - Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), - Map.entry("@WM_STATE_RESERVE_W", "Reverse"), Map.entry("@WM_STATE_DETECTING_W", "Detecting"), - Map.entry("@WM_STATE_RUNNING_W", "Running"), Map.entry("@WM_STATE_RINSING_W", "Rinsing"), - Map.entry("@WM_STATE_SPINNING_W", "Spinning"), Map.entry("@WM_STATE_COOLDOWN_W", "Cool Down"), - Map.entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), Map.entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), - Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_END_W", "End"), - Map.entry("@WM_STATE_DRYING_W", "Drying"), Map.entry("@WM_STATE_DEMO_W", "Demonstration"), - Map.entry("@WM_STATE_ERROR_W", "Error")); - - // ============================================================================== -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java deleted file mode 100644 index 9b9dde78bc7df..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; -import org.openhab.binding.lgthinq.lgservices.model.Capability; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.slf4j.Logger; - -/** - * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public abstract class LGThinqDeviceThing extends BaseThingHandler { - - public LGThinqDeviceThing(Thing thing) { - super(thing); - } - - public abstract void onDeviceAdded(@NonNullByDefault LGDevice device); - - public abstract String getDeviceId(); - - public abstract String getDeviceAlias(); - - public abstract String getDeviceModelName(); - - public abstract String getDeviceUriJsonConfig(); - - public abstract boolean onDeviceStateChanged(); - - public abstract void onDeviceRemoved(); - - public abstract void onDeviceGone(); - - public abstract void updateChannelDynStateDescription() throws LGThinqApiException; - - public abstract Capability getCapabilities() throws LGThinqApiException; - - protected abstract Logger getLogger(); - - protected abstract void startCommandExecutorQueueJob(); - - protected void initializeThing(@Nullable ThingStatus bridgeStatus) { - getLogger().debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); - String deviceId = getThing().getUID().getId(); - - Bridge bridge = getBridge(); - if (!deviceId.isBlank()) { - try { - updateChannelDynStateDescription(); - } catch (LGThinqApiException e) { - getLogger().error( - "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values.", - e); - } - if (bridge != null) { - LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); - // registry this thing to the bridge - if (handler == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } else { - handler.registryListenerThing(this); - if (bridgeStatus == ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - } - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/offline.conf-error-no-device-id"); - } - // finally, start command queue, regardless of the thing state, as we can still try to send commands without - // property ONLINE (the successful result from command request can put the thing in ONLINE status). - startCommandExecutorQueueJob(); - } - - @Override - public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { - getLogger().debug("bridgeStatusChanged {}", bridgeStatusInfo); - super.bridgeStatusChanged(bridgeStatusInfo); - // restart scheduler - initializeThing(bridgeStatusInfo.getStatus()); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java deleted file mode 100644 index 68b203cf3eb5c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -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; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqHandlerFactory} is responsible for creating things and thing - * handlers. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.lgthinq") -public class LGThinqHandlerFactory extends BaseThingHandlerFactory { - - private final Logger logger = LoggerFactory.getLogger(LGThinqHandlerFactory.class); - private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return LGThinqBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - @Override - protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { - return new LGThinqAirConditionerHandler(thing, stateDescriptionProvider); - } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - return new LGThinqBridgeHandler((Bridge) thing); - } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { - return new LGThinqWasherHandler(thing, stateDescriptionProvider); - } - logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); - return null; - } - - @Override - public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, - @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { - if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - return super.createThing(thingTypeUID, configuration, thingUID, null); - } else if (LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { - return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); - } else if (LGThinqBindingConstants.THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { - return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); - } - return null; - } - - @Activate - public LGThinqHandlerFactory(final @Reference LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { - this.stateDescriptionProvider = stateDescriptionProvider; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java deleted file mode 100644 index 94927b10d52e2..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ /dev/null @@ -1,419 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.util.*; -import java.util.concurrent.*; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.LGThinqWMApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqWMApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.washer.WMCapability; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.StateOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqWasherHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqWasherHandler extends LGThinqDeviceThing { - - private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; - @Nullable - private WMCapability wmCapability; - private final ChannelUID stateChannelUUID; - private final ChannelUID courseChannelUUID; - private final ChannelUID smartCourseChannelUUID; - private final String lgPlatfomType; - private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); - @NonNullByDefault - private final LGThinqWMApiClientService lgThinqWMApiClientService; - private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; - // Bridges status that this thing must top scanning for state change - private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, - ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, - ThingStatusDetail.CONFIGURATION_ERROR); - private @Nullable ScheduledFuture thingStatePollingJob; - private @Nullable Future commandExecutorQueueJob; - // *** Long running isolated threadpools. - private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); - private final ExecutorService executorService = Executors.newFixedThreadPool(1); - - private boolean monitorV1Began = false; - private String monitorWorkId = ""; - private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); - @NonNullByDefault - private String bridgeId = ""; - - public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing); - this.stateDescriptionProvider = stateDescriptionProvider; - lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgThinqWMApiClientService = LGThinqWMApiV2ClientServiceImpl.getInstance(); - stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); - courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); - smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); - } - - static class AsyncCommandParams { - final String channelUID; - final Command command; - - public AsyncCommandParams(String channelUUID, Command command) { - this.channelUID = channelUUID; - this.command = command; - } - } - - @Override - public Collection> getServices() { - return super.getServices(); - } - - @Override - public void initialize() { - logger.debug("Initializing Thinq thing."); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - - @Override - protected void startCommandExecutorQueueJob() { - if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { - commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); - } - } - - private ExecutorService getExecutorService() { - return executorService; - } - - private void stopCommandExecutorQueueJob() { - if (commandExecutorQueueJob != null) { - commandExecutorQueueJob.cancel(true); - } - } - - protected void startThingStatePolling() { - if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { - thingStatePollingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, - DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); - } - } - - private void updateThingStateFromLG() { - try { - WasherDryerSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); - if (shot == null) { - // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. - return; - } - if (!shot.isOnline()) { - if (getThing().getStatus() != ThingStatus.OFFLINE) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); - updateState(CHANNEL_POWER_ID, - OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_OFF)); - } - return; - } - - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); - updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); - updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); - updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); - updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); - updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); - updateStatus(ThingStatus.ONLINE); - } catch (LGThinqException e) { - logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", - getDeviceAlias(), getDeviceId(), e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - - private ScheduledExecutorService getLocalScheduler() { - return pollingScheduler; - } - - private String getBridgeId() { - if (bridgeId.isBlank() && getBridge() == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); - logger.error("Configuration error um Thinq Thing - No Bridge defined for the thing."); - return "UNKNOWN"; - } else if (bridgeId.isBlank() && getBridge() != null) { - bridgeId = getBridge().getUID().getId(); - } - return bridgeId; - } - - private void forceStopDeviceV1Monitor(String deviceId) { - try { - monitorV1Began = false; - lgThinqWMApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); - } catch (Exception e) { - logger.error("Error stopping LG Device monitor", e); - } - } - - @NonNull - private String emptyIfNull(@Nullable String value) { - return value == null ? "" : "" + value; - } - - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - WMCapability wmCap = getCapabilities(); - if (isLinked(stateChannelUUID)) { - List options = new ArrayList<>(); - // invert key/value - wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); - stateDescriptionProvider.setStateOptions(stateChannelUUID, options); - } - if (isLinked(courseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(courseChannelUUID, options); - } - if (isLinked(smartCourseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); - } - } - - @Override - public WMCapability getCapabilities() throws LGThinqApiException { - if (wmCapability == null) { - wmCapability = (WMCapability) lgThinqWMApiClientService.getCapability(getDeviceId(), - getDeviceUriJsonConfig(), false); - } - return Objects.requireNonNull(wmCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); - } - - @Override - protected Logger getLogger() { - return logger; - } - - @Nullable - private WasherDryerSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { - // analise de platform version - if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (WasherDryerSnapshot) lgThinqWMApiClientService.getDeviceData(getBridgeId(), getDeviceId()); - } else { - try { - if (!monitorV1Began) { - monitorWorkId = lgThinqWMApiClientService.startMonitor(getBridgeId(), getDeviceId()); - monitorV1Began = true; - } - } catch (LGThinqDeviceV1OfflineException e) { - forceStopDeviceV1Monitor(deviceId); - WasherDryerSnapshot shot = new WasherDryerSnapshot(); - shot.setOnline(false); - return shot; - } catch (Exception e) { - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); - } - int retries = 10; - WasherDryerSnapshot shot; - while (retries > 0) { - // try to get monitoring data result 3 times. - try { - shot = (WasherDryerSnapshot) lgThinqWMApiClientService.getMonitorData(getBridgeId(), deviceId, - monitorWorkId, DeviceTypes.WASHING_MACHINE); - if (shot != null) { - return shot; - } - Thread.sleep(500); - retries--; - } catch (LGThinqDeviceV1MonitorExpiredException e) { - forceStopDeviceV1Monitor(deviceId); - logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); - return null; - } catch (Exception e) { - // If it can't get monitor handler, then stop monitor and restart the process again in new - // interaction - // Force restart monitoring because of the errors returned (just in case) - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); - } - } - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Exhausted trying to get monitor data for the device:" + deviceId); - } - } - - protected void stopThingStatePolling() { - if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { - logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); - thingStatePollingJob.cancel(true); - } - } - - private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { - if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { - // start the thing polling - startThingStatePolling(); - } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE - && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { - // comunication error is not a specific Bridge error, then we must analise it to give - // this thinq the change to recovery from communication errors - if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR - || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { - // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if - // the bridge is out - stopThingStatePolling(); - } - - } - lastThingStatus = newStatus; - } - - @Override - protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { - handleStatusChanged(newStatus, statusDetail); - super.updateStatus(newStatus, statusDetail, description); - } - - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceId() { - return getThing().getUID().getId(); - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } - - @Override - public String getDeviceModelName() { - return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); - } - - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } - - @Override - public boolean onDeviceStateChanged() { - // TODO - HANDLE IT, Think if it's needed - return false; - } - - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void onDeviceGone() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void dispose() { - if (thingStatePollingJob != null) { - thingStatePollingJob.cancel(true); - stopThingStatePolling(); - stopCommandExecutorQueueJob(); - thingStatePollingJob = null; - } - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - updateThingStateFromLG(); - } else { - AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); - try { - // Ensure commands are send in a pipe per device. - commandBlockQueue.add(params); - } catch (IllegalStateException ex) { - logger.error( - "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); - updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, - "Device Command Queue is Busy"); - } - - } - } - - private final Runnable queuedCommandExecutor = new Runnable() { - @Override - public void run() { - while (true) { - AsyncCommandParams params; - try { - params = commandBlockQueue.take(); - } catch (InterruptedException e) { - logger.debug("Interrupting async command queue executor."); - return; - } - Command command = params.command; - - try { - switch (params.channelUID) { - case CHANNEL_POWER_ID: { - if (command instanceof OnOffType) { - lgThinqWMApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? DevicePowerState.DV_POWER_ON - : DevicePowerState.DV_POWER_OFF); - } else { - logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); - } - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, - params.channelUID); - } - } - } catch (LGThinqException e) { - logger.error("Error executing Command {} to the channel {}. Thing goes offline until retry", - command, params.channelUID, e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - } - }; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java new file mode 100644 index 0000000000000..6c0c64d034bd5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -0,0 +1,217 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.*; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.*; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQDryerHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler { + + @Nullable + private DryerCapability dryerCapability; + private final ChannelUID stateChannelUUID; + private final ChannelUID processStateChannelUUID; + private final ChannelUID dryLevelChannelUUID; + private final ChannelUID errorChannelUUID; + private final ChannelUID courseChannelUUID; + private final ChannelUID smartCourseChannelUUID; + + private final Logger logger = LoggerFactory.getLogger(LGThinQDryerHandler.class); + @NonNullByDefault + private final LGThinQDRApiClientService lgThinqDRApiClientService; + // Bridges status that this thing must top scanning for state change + private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, + ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + ThingStatusDetail.CONFIGURATION_ERROR); + private @Nullable ScheduledFuture thingStatePollingJob; + private @Nullable Future commandExecutorQueueJob; + + public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing, stateDescriptionProvider); + lgThinqDRApiClientService = LGThinQDRApiV2ClientServiceImpl.getInstance(); + stateChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_STATE_ID); + courseChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_COURSE_ID); + smartCourseChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_SMART_COURSE_ID); + processStateChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_PROCESS_STATE_ID); + errorChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_ERROR_ID); + dryLevelChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_DRY_LEVEL_ID); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + protected void updateDeviceChannels(DryerSnapshot shot) { + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + updateState(DR_CHANNEL_STATE_ID, new StringType(shot.getState())); + updateState(DR_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); + updateState(DR_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); + updateState(DR_CHANNEL_PROCESS_STATE_ID, new StringType(shot.getProcessState())); + updateState(DR_CHANNEL_CHILD_LOCK_ID, new StringType(shot.getChildLock())); + updateState(DR_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateState(DR_CHANNEL_DRY_LEVEL_ID, new StringType(shot.getDryLevel())); + updateState(DR_CHANNEL_ERROR_ID, new StringType(shot.getError())); + updateStatus(ThingStatus.ONLINE); + } + + @Override + protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + switch (params.channelUID) { + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqDRApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } + + @NonNull + private String emptyIfNull(@Nullable String value) { + return value == null ? "" : "" + value; + } + + @NonNull + private String keyIfValueNotFound(Map map, @NonNull String key) { + return Objects.requireNonNullElse(map.get(key), key); + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + DryerCapability drCap = getCapabilities(); + if (isLinked(stateChannelUUID)) { + List options = new ArrayList<>(); + // invert key/value + drCap.getState().forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_STATE, v)))); + stateDescriptionProvider.setStateOptions(stateChannelUUID, options); + } + if (isLinked(courseChannelUUID)) { + List options = new ArrayList<>(); + drCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(courseChannelUUID, options); + } + if (isLinked(smartCourseChannelUUID)) { + List options = new ArrayList<>(); + drCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); + } + if (isLinked(processStateChannelUUID)) { + List options = new ArrayList<>(); + drCap.getProcessStates() + .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_PROCESS_STATE, v)))); + stateDescriptionProvider.setStateOptions(processStateChannelUUID, options); + } + if (isLinked(errorChannelUUID)) { + List options = new ArrayList<>(); + drCap.getErrors().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(errorChannelUUID, options); + } + if (isLinked(dryLevelChannelUUID)) { + List options = new ArrayList<>(); + drCap.getDryLevels() + .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); + stateDescriptionProvider.setStateOptions(dryLevelChannelUUID, options); + } + } + + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqDRApiClientService; + } + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceId() { + return getThing().getUID().getId(); + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceModelName() { + return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public boolean onDeviceStateChanged() { + // TODO - HANDLE IT, Think if it's needed + return false; + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void onDeviceGone() { + // TODO - HANDLE IT, Think if it's needed + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java new file mode 100644 index 0000000000000..9a6499fbaef8d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.*; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.*; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQWasherHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler { + + private final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final ChannelUID stateChannelUUID; + private final ChannelUID courseChannelUUID; + private final ChannelUID smartCourseChannelUUID; + private final Logger logger = LoggerFactory.getLogger(LGThinQWasherHandler.class); + @NonNullByDefault + private final LGThinQWMApiClientService lgThinqWMApiClientService; + + // *** Long running isolated threadpools. + private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); + + private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); + + @NonNullByDefault + + public LGThinQWasherHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing, stateDescriptionProvider); + this.stateDescriptionProvider = stateDescriptionProvider; + lgThinqWMApiClientService = LGThinQWMApiV2ClientServiceImpl.getInstance(); + stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); + courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); + smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); + } + + static class AsyncCommandParams { + final String channelUID; + final Command command; + + public AsyncCommandParams(String channelUUID, Command command) { + this.channelUID = channelUUID; + this.command = command; + } + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @NonNull + private String emptyIfNull(@Nullable String value) { + return value == null ? "" : "" + value; + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + WasherCapability wmCap = getCapabilities(); + if (isLinked(stateChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); + stateDescriptionProvider.setStateOptions(stateChannelUUID, options); + } + if (isLinked(courseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(courseChannelUUID, options); + } + if (isLinked(smartCourseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); + } + } + + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqWMApiClientService; + } + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + protected void updateDeviceChannels(WasherSnapshot shot) { + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); + updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); + updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); + updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); + updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); + updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateStatus(ThingStatus.ONLINE); + } + + @Override + protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + switch (params.channelUID) { + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqWMApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceId() { + return getThing().getUID().getId(); + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceModelName() { + return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public boolean onDeviceStateChanged() { + // TODO - HANDLE IT, Think if it's needed + return false; + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void onDeviceGone() { + // TODO - HANDLE IT, Think if it's needed + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java deleted file mode 100644 index 259307eb1819c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.handler; - -import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; -import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; - -/** - * The {@link LGThinqBridge} - * - * @author Nemer Daud - Initial contribution - */ -public interface LGThinqBridge { - void registerDiscoveryListener(LGThinqDiscoveryService listener); - - void registryListenerThing(LGThinqDeviceThing thing); - - void unRegistryListenerThing(LGThinqDeviceThing thing); - - LGThinqDeviceThing getThingByDeviceId(String deviceId); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java deleted file mode 100644 index 181b19f3afc83..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ /dev/null @@ -1,327 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.handler; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_CONNECTION_DATA_FILE; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; -import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; -import org.openhab.binding.lgthinq.internal.api.TokenManager; -import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.core.config.core.status.ConfigStatusMessage; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqBridgeHandler} - * - * @author Nemer Daud - Initial contribution - */ -public class LGThinqBridgeHandler extends ConfigStatusBridgeHandler implements LGThinqBridge { - - private Map lGDeviceRegister = new ConcurrentHashMap<>(); - private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); - - static { - var logger = LoggerFactory.getLogger(LGThinqBridgeHandler.class); - try { - File directory = new File(THINQ_USER_DATA_FOLDER); - if (!directory.exists()) { - directory.mkdir(); - } - } catch (Exception e) { - logger.warn("Unable to setup thinq userdata directory: {}", e.getMessage()); - } - } - private final Logger logger = LoggerFactory.getLogger(LGThinqBridgeHandler.class); - private LGThinqConfiguration lgthinqConfig; - private TokenManager tokenManager; - private LGThinqDiscoveryService discoveryService; - private LGThinqApiClientService lgApiClient; - private @Nullable Future initJob; - private @Nullable ScheduledFuture devicePollingJob; - - public LGThinqBridgeHandler(Bridge bridge) { - super(bridge); - tokenManager = TokenManager.getInstance(); - lgApiClient = LGThinqACApiV1ClientServiceImpl.getInstance(); - lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); - } - - final ReentrantLock pollingLock = new ReentrantLock(); - - /** - * Abstract Runnable Polling Class to schedule sincronization status of the Bridge Thing Kinds ! - */ - abstract class PollingRunnable implements Runnable { - protected final String bridgeName; - protected LGThinqConfiguration lgthinqConfig; - - PollingRunnable(String bridgeName) { - this.bridgeName = bridgeName; - } - - @Override - public void run() { - try { - pollingLock.lock(); - // check if configuration file already exists - if (tokenManager.isOauthTokenRegistered(bridgeName)) { - logger.debug( - "Token authentication process has been already done. Skip first authentication process."); - try { - tokenManager.getValidRegisteredToken(bridgeName); - } catch (IOException e) { - logger.error("Error reading LGThinq TokenFile", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, - "@text/error.toke-file-corrupted"); - return; - } catch (RefreshTokenException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, - "@text/error.toke-refresh"); - return; - } - } else { - try { - tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), - lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword(), - lgthinqConfig.getAlternativeServer()); - tokenManager.getValidRegisteredToken(bridgeName); - logger.debug("Successful getting token from LG API"); - } catch (IOException e) { - logger.debug( - "I/O error accessing json token configuration file. Updating Bridge Status to OFFLINE.", - e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/error.toke-file-access-error"); - return; - } catch (LGThinqException e) { - logger.debug("Error accessing LG API. Updating Bridge Status to OFFLINE.", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/error.lgapi-communication-error"); - return; - } - } - if (thing.getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } - - try { - doConnectedRun(); - } catch (Exception e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.lgapi-getting-devices"); - } - - } finally { - pollingLock.unlock(); - } - } - - protected abstract void doConnectedRun() throws IOException, LGThinqException; - } - - @Override - public void registerDiscoveryListener(LGThinqDiscoveryService listener) { - if (discoveryService == null) { - discoveryService = listener; - } - } - - /** - * Registry the OSGi services used by this Bridge. - * Eventually, the Discovery Service will be activated with this bridge as argument. - * - * @return Services to be registered to OSGi. - */ - @Override - public Collection> getServices() { - return Collections.singleton(LGThinqDiscoveryService.class); - } - - @Override - public void registryListenerThing(LGThinqDeviceThing thing) { - if (lGDeviceRegister.get(thing.getDeviceId()) == null) { - lGDeviceRegister.put(thing.getDeviceId(), thing); - // remove device from discovery list, if exists. - LGDevice device = lastDevicesDiscovered.get(thing.getDeviceId()); - if (device != null) { - discoveryService.removeLgDeviceDiscovery(device); - } - } - } - - @Override - public void unRegistryListenerThing(LGThinqDeviceThing thing) { - lGDeviceRegister.remove(thing.getDeviceId()); - } - - @Override - public LGThinqDeviceThing getThingByDeviceId(String deviceId) { - return lGDeviceRegister.get(deviceId); - } - - private LGDevicePollingRunnable lgDevicePollingRunnable; - - class LGDevicePollingRunnable extends PollingRunnable { - public LGDevicePollingRunnable(String bridgeName) { - super(bridgeName); - } - - @Override - protected void doConnectedRun() throws LGThinqException { - Map lastDevicesDiscoveredCopy = new HashMap<>(lastDevicesDiscovered); - for (final LGDevice device : lgApiClient.listAccountDevices(bridgeName)) { - String deviceId = device.getDeviceId(); - // if not registered yet, and not discovered before, then add to discovery list. - if (lGDeviceRegister.get(deviceId) == null && !lastDevicesDiscovered.containsKey(deviceId)) { - logger.debug("Adding new LG Device to things registry with id:{}", deviceId); - if (discoveryService != null) { - discoveryService.addLgDeviceDiscovery(device); - } - } - lastDevicesDiscovered.put(deviceId, device); - lastDevicesDiscoveredCopy.remove(deviceId); - } - // the rest in lastDevicesDiscoveredCopy is not more registered in LG API. Remove from discovery - lastDevicesDiscoveredCopy.forEach((deviceId, device) -> { - logger.trace("LG Device '{}' removed.", deviceId); - lastDevicesDiscovered.remove(deviceId); - - LGThinqDeviceThing deviceThing = lGDeviceRegister.get(deviceId); - if (deviceThing != null) { - deviceThing.onDeviceRemoved(); - } - if (discoveryService != null && deviceThing != null) { - discoveryService.removeLgDeviceDiscovery(device); - } - }); - } - }; - - @Override - public Collection getConfigStatus() { - List resultList = new ArrayList<>(); - if (lgthinqConfig.username.isEmpty()) { - resultList.add(ConfigStatusMessage.Builder.error("USERNAME").withMessageKeySuffix("missing field") - .withArguments("username").build()); - } - if (lgthinqConfig.password.isEmpty()) { - resultList.add(ConfigStatusMessage.Builder.error("PASSWORD").withMessageKeySuffix("missing field") - .withArguments("password").build()); - } - if (lgthinqConfig.language.isEmpty()) { - resultList.add(ConfigStatusMessage.Builder.error("LANGUAGE").withMessageKeySuffix("missing field") - .withArguments("language").build()); - } - if (lgthinqConfig.country.isEmpty()) { - resultList.add(ConfigStatusMessage.Builder.error("COUNTRY").withMessageKeySuffix("missing field") - .withArguments("country").build()); - - } - return resultList; - } - - @Override - public void handleRemoval() { - if (devicePollingJob != null) - devicePollingJob.cancel(true); - tokenManager.cleanupTokenRegistry(getBridge().getUID().getId()); - super.handleRemoval(); - } - - @Override - public void dispose() { - if (devicePollingJob != null) { - devicePollingJob.cancel(true); - devicePollingJob = null; - } - } - - @Override - public T getConfigAs(Class configurationClass) { - return super.getConfigAs(configurationClass); - } - - @Override - public void initialize() { - logger.debug("Initializing LGThinq bridge handler."); - lgthinqConfig = getConfigAs(LGThinqConfiguration.class); - lgDevicePollingRunnable.lgthinqConfig = lgthinqConfig; - - if (lgthinqConfig.username.isEmpty() || lgthinqConfig.password.isEmpty() || lgthinqConfig.language.isEmpty() - || lgthinqConfig.country.isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/error.mandotory-fields-missing"); - } else { - updateStatus(ThingStatus.UNKNOWN); - startLGThinqDevicePolling(); - } - } - - @Override - public void handleConfigurationUpdate(Map configurationParameters) { - logger.debug("Bridge Configuration was updated. Cleaning the token registry file"); - File f = new File(String.format(THINQ_CONNECTION_DATA_FILE, getThing().getUID().getId())); - if (f.isFile()) { - // file exists. Delete it - if (!f.delete()) { - logger.error("Error deleting file:{}", f.getAbsolutePath()); - } - } - super.handleConfigurationUpdate(configurationParameters); - } - - private void startLGThinqDevicePolling() { - // stop current scheduler, if any - if (devicePollingJob != null && !devicePollingJob.isDone()) { - devicePollingJob.cancel(true); - } - long pollingInterval; - int configPollingInterval = lgthinqConfig.getPollingIntervalSec(); - // It's not recommended to polling for resources in LG API short intervals to do not enter in BlackList - if (configPollingInterval < 300) { - pollingInterval = TimeUnit.SECONDS.toSeconds(300); - logger.info("Wrong configuration value for polling interval. Using default value: {}s", pollingInterval); - } else { - pollingInterval = configPollingInterval; - } - // submit instantlly and schedule for the next polling interval. - scheduler.submit(lgDevicePollingRunnable); - devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, pollingInterval, pollingInterval, - TimeUnit.SECONDS); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java deleted file mode 100644 index 0d0acddfbc3da..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; - -/** - * The {@link LGThinqACApiClientService} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface LGThinqACApiClientService extends LGThinqApiClientService { - void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; - - void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; - - void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGThinqApiException; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java deleted file mode 100644 index fbf90659c718e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.IOException; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import org.openhab.binding.lgthinq.lgservices.model.SnapshotFactory; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqACApiV1ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqACApiV1ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqACApiClientService { - private static final LGThinqACApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinqACApiV1ClientServiceImpl.class); - - static { - instance = new LGThinqACApiV1ClientServiceImpl(); - } - - public static LGThinqACApiClientService getInstance() { - return instance; - } - - /** - * Get snapshot data from the device. - * It works only for API V2 device versions! - * - * @param deviceId device ID for de desired V2 LG Thinq. - * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. - */ - @Override - @Nullable - public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { - throw new UnsupportedOperationException("Method not supported in V1 API device."); - } - - private RestResult sendControlCommands(String bridgeName, String deviceId, String keyName, int value) - throws Exception { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - - String payload = String.format( - "{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"Control\"," + " \"cmdOpt\": \"Set\"," - + " \"value\": {\"%s\": \"%d\"}," + " \"deviceId\": \"%s\"," - + " \"workId\": \"%s\"," + " \"data\": \"\"" + " }\n" + "}", - keyName, value, deviceId, UUID.randomUUID().toString()); - return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); - } - - @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", newPowerState.commandValue()); - handleV1GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting device power", e); - } - } - - @Override - public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "OpMode", newOpMode); - - handleV1GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting operation mode", e); - } - } - - @Override - public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "WindStrength", newFanSpeed); - - handleV1GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting fan speed", e); - } - } - - @Override - public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "TempCfg", newTargetTemp.commandValue()); - - handleV1GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting target temperature", e); - } - } - - @Override - public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId, DeviceTypes deviceType) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" - + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" - + " ]\n" + " }\n" + "}", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - Map envelop = null; - // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with - // offline flag. - try { - envelop = handleV1GenericErrorResult(resp); - } catch (LGThinqDeviceV1OfflineException e) { - ACSnapshot shot = new ACSnapshot(); - shot.setOnline(false); - return shot; - } - if (envelop.get("workList") != null - && ((Map) envelop.get("workList")).get("returnData") != null) { - Map workList = ((Map) envelop.get("workList")); - if (!"0000".equals(workList.get("returnCode"))) { - logErrorResultCodeMessage((String) workList.get("resultCode")); - LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( - String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); - logger.warn("{}", e.getMessage()); - throw e; - } - - String jsonMonDataB64 = (String) workList.get("returnData"); - String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); - Snapshot shot = SnapshotFactory.getInstance().create(jsonMon, deviceType); - shot.setOnline("E".equals(workList.get("deviceState"))); - return shot; - } else { - // no data available yet - return null; - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java deleted file mode 100644 index 9dcddb0ebe43a..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; - -import java.io.IOException; -import java.util.Map; - -import javax.ws.rs.core.UriBuilder; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; - -/** - * The {@link LGThinqACApiV2ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqACApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqACApiClientService { - private static final LGThinqACApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinqACApiV2ClientServiceImpl.class); - - static { - instance = new LGThinqACApiV2ClientServiceImpl(); - } - - public static LGThinqACApiClientService getInstance() { - return instance; - } - - private Map getCommonV2Headers(String language, String country, String accessToken, - String userNumber) { - return getCommonHeaders(language, country, accessToken, userNumber); - } - - private RestResult sendControlCommands(String bridgeName, String deviceId, String command, String keyName, - int value) throws Exception { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) - .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId)); - Map headers = getCommonV2Headers(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String payload = String.format("{\n" + "\"ctrlKey\": \"basicCtrl\",\n" + "\"command\": \"%s\",\n" - + "\"dataKey\": \"%s\",\n" + "\"dataValue\": %d}", command, keyName, value); - return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); - } - - @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", "airState.operation", - newPowerState.commandValue()); - handleV2GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting device power", e); - } - } - - @Override - public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.opMode", newOpMode); - handleV2GenericErrorResult(resp); - } catch (LGThinqApiException e) { - throw e; - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting operation mode", e); - } - } - - @Override - public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.windStrength", newFanSpeed); - handleV2GenericErrorResult(resp); - } catch (LGThinqApiException e) { - throw e; - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting operation mode", e); - } - } - - @Override - public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.tempState.target", - newTargetTemp.commandValue()); - handleV2GenericErrorResult(resp); - } catch (LGThinqApiException e) { - throw e; - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting operation mode", e); - } - } - - /** - * Start monitor data form specific device. This is old one, works only on V1 API supported devices. - * - * @param deviceId Device ID - * @return Work1 to be uses to grab data during monitoring. - * @throws LGThinqApiException If some communication error occur. - */ - @Override - public String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { - throw new UnsupportedOperationException("Not supported in V2 API."); - } - - private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { - Map metaResult; - if (resp == null) { - return; - } - if (resp.getStatusCode() != 200) { - logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException( - String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { - }); - if (!"0000".equals(metaResult.get("resultCode"))) { - logErrorResultCodeMessage((String) metaResult.get("resultCode")); - throw new LGThinqApiException( - String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", - metaResult.get("resultCode"))); - } - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream", e); - } - - } - } - - @Override - public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { - throw new UnsupportedOperationException("Not supported in V2 API."); - } - - @Override - public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId, DeviceTypes deviceType) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { - throw new UnsupportedOperationException("Not supported in V2 API."); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java deleted file mode 100644 index 8aa1e3d9aede3..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.*; - -/** - * The {@link LGThinqApiClientService} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface LGThinqApiClientService { - - List listAccountDevices(String bridgeName) throws LGThinqApiException; - - Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException; - - /** - * Retrieve actual data from device (its sensors and points states). - * - * @param deviceId device number - * @return return snapshot state of the device - * @throws LGThinqApiException if some error interacting with LG API Server occur. - */ - @Nullable - Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; - - void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; - - String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; - - Capability getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; - - File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) - throws LGThinqApiException, IOException; - - void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; - - @Nullable - Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId, - DeviceTypes deviceType) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java deleted file mode 100644 index 34474809a7267..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ /dev/null @@ -1,384 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.*; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * The {@link LGThinqACApiV1ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public abstract class LGThinqApiClientServiceImpl implements LGThinqApiClientService { - private static final Logger logger = LoggerFactory.getLogger(LGThinqApiClientServiceImpl.class); - protected final ObjectMapper objectMapper = new ObjectMapper(); - protected final TokenManager tokenManager; - - protected LGThinqApiClientServiceImpl() { - this.tokenManager = TokenManager.getInstance(); - } - - static Map getCommonHeaders(String language, String country, String accessToken, - String userNumber) { - Map headers = new HashMap<>(); - headers.put("Accept", "application/json"); - headers.put("Content-type", "application/json;charset=UTF-8"); - headers.put("x-api-key", V2_API_KEY); - headers.put("x-client-id", V2_CLIENT_ID); - headers.put("x-country-code", country); - headers.put("x-language-code", language); - headers.put("x-message-id", UUID.randomUUID().toString()); - headers.put("x-service-code", SVC_CODE); - headers.put("x-service-phase", V2_SVC_PHASE); - headers.put("x-thinq-app-level", V2_APP_LEVEL); - headers.put("x-thinq-app-os", V2_APP_OS); - headers.put("x-thinq-app-type", V2_APP_TYPE); - headers.put("x-thinq-app-ver", V2_APP_VER); - headers.put("x-thinq-security-key", SECURITY_KEY); - if (!accessToken.isBlank()) - headers.put("x-emp-token", accessToken); - if (!userNumber.isBlank()) - headers.put("x-user-no", userNumber); - return headers; - } - - /** - * Even using V2 URL, this endpoint support grab informations about account devices from V1 and V2. - * - * @return list os LG Devices. - * @throws LGThinqApiException if some communication error occur. - */ - @Override - public List listAccountDevices(String bridgeName) throws LGThinqApiException { - try { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); - return handleListAccountDevicesResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Erros list account devices from LG Server API", e); - } - } - - @Override - public File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { - File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); - try { - if (regFile.isFile() || forceRecreate) { - try (InputStream in = new URL(uri).openStream()) { - Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } - } catch (IOException e) { - throw new LGThinqApiException("Error reading IO interface", e); - } - return regFile; - } - - /** - * Get device settings and snapshot for a specific device. - * It works only for API V2 device versions! - * - * @param deviceId device ID for de desired V2 LG Thinq. - * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. - */ - @Override - public Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException { - try { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) - .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); - return handleDeviceSettingsResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Erros list account devices from LG Server API", e); - } - } - - private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { - return genericHandleDeviceSettingsResult(resp, logger, objectMapper); - } - - @SuppressWarnings("unchecked") - static Map genericHandleDeviceSettingsResult(RestResult resp, Logger logger, - ObjectMapper objectMapper) throws LGThinqApiException { - Map deviceSettings; - if (resp.getStatusCode() != 200) { - logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException(String.format( - "Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - if (!"0000".equals(deviceSettings.get("resultCode"))) { - logErrorResultCodeMessage((String) deviceSettings.get("resultCode")); - throw new LGThinqApiException( - String.format("Status error getting device list. resultCode must be 0000, but was:%s", - deviceSettings.get("resultCode"))); - } - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream", e); - } - - } - return Objects.requireNonNull((Map) deviceSettings.get("result"), - "Unexpected json result asking for Device Settings. Node 'result' no present"); - } - - @SuppressWarnings("unchecked") - private List handleListAccountDevicesResult(RestResult resp) throws LGThinqApiException { - Map devicesResult; - List devices; - if (resp.getStatusCode() != 200) { - logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException(String - .format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - if (!"0000".equals(devicesResult.get("resultCode"))) { - logErrorResultCodeMessage((String) devicesResult.get("resultCode")); - throw new LGThinqApiException( - String.format("Status error getting device list. resultCode must be 0000, but was:%s", - devicesResult.get("resultCode"))); - } - List> items = (List>) ((Map) devicesResult - .get("result")).get("item"); - devices = objectMapper.convertValue(items, new TypeReference<>() { - }); - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream.", e); - } - - } - - return devices; - } - - protected static void logErrorResultCodeMessage(@Nullable String resultCode) { - if (resultCode == null) { - return; - } - String errMessage = ERROR_CODE_RESPONSE.get(resultCode.trim()); - logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", resultCode, - errMessage == null ? "UNKNOW ERROR MESSAGE" : errMessage); - } - - /** - * Get capability em registry/cache on file for next consult - * - * @param deviceId ID of the device - * @param uri URI of the config capability - * @return return simplified capability - * @throws LGThinqApiException If some error occurr - */ - public Capability getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { - try { - File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); - Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { - }); - return CapabilityFactory.getInstance().create(mapper); - } catch (IOException e) { - throw new LGThinqApiException("Error reading IO interface", e); - } - } - - @NonNull - protected Map handleV1GenericErrorResult(@Nullable RestResult resp) - throws LGThinqApiException, LGThinqDeviceV1OfflineException { - Map metaResult; - Map envelope = Collections.emptyMap(); - if (resp == null) { - return envelope; - } - if (resp.getStatusCode() != 200) { - logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException( - String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - envelope = (Map) metaResult.get("lgedmRoot"); - if (envelope == null) { - throw new LGThinqApiException(String.format( - "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); - } else if (!"0000".equals(envelope.get("returnCd"))) { - logErrorResultCodeMessage((String) envelope.get("returnCd")); - if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { - // Disconnected Device - throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); - } - throw new LGThinqApiException( - String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", - metaResult.get("returnCd"))); - } - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream", e); - } - } - return envelope; - } - - public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId, DeviceTypes deviceType) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" - + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" - + " ]\n" + " }\n" + "}", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - Map envelop = null; - // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with - // offline flag. - try { - envelop = handleV1GenericErrorResult(resp); - } catch (LGThinqDeviceV1OfflineException e) { - ACSnapshot shot = new ACSnapshot(); - shot.setOnline(false); - return shot; - } - if (envelop.get("workList") != null - && ((Map) envelop.get("workList")).get("returnData") != null) { - Map workList = ((Map) envelop.get("workList")); - if (!"0000".equals(workList.get("returnCode"))) { - logErrorResultCodeMessage((String) workList.get("resultCode")); - LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( - String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); - logger.warn("{}", e.getMessage()); - throw e; - } - - String jsonMonDataB64 = (String) workList.get("returnData"); - String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); - Snapshot shot = SnapshotFactory.getInstance().create(jsonMon, deviceType); - shot.setOnline("E".equals(workList.get("deviceState"))); - return shot; - } else { - // no data available yet - return null; - } - } - - /** - * Get snapshot data from the device. - * It works only for API V2 device versions! - * - * @param deviceId device ID for de desired V2 LG Thinq. - * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. - */ - @Override - @Nullable - public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { - Map deviceSettings = getDeviceSettings(bridgeName, deviceId); - if (deviceSettings.get("snapshot") != null) { - Map snapMap = (Map) deviceSettings.get("snapshot"); - if (logger.isDebugEnabled()) { - try { - objectMapper.writeValue(new File(String.format( - LGThinqBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", - deviceId)), deviceSettings); - } catch (IOException e) { - logger.error("Error saving data trace", e); - } - } - if (snapMap == null) { - // No snapshot value provided - return null; - } - - Snapshot shot = SnapshotFactory.getInstance().create(deviceSettings); - shot.setOnline((Boolean) snapMap.get("online")); - return shot; - } - return null; - } - - /** - * Start monitor data form specific device. This is old one, works only on V1 API supported devices. - * - * @param deviceId Device ID - * @return Work1 to be uses to grab data during monitoring. - * @throws LGThinqApiException If some communication error occur. - */ - @Override - public String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String workerId = UUID.randomUUID().toString(); - String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," - + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - return Objects.requireNonNull((String) handleV1GenericErrorResult(resp).get("workId"), - "Unexpected StartMonitor json result. Node 'workId' not present"); - } - - @Override - public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," - + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - handleV1GenericErrorResult(resp); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java deleted file mode 100644 index 9575166ed674e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -/** - * The {@link LGThinqWMApiClientService} - * - * @author Nemer Daud - Initial contribution - */ -public interface LGThinqWMApiClientService extends LGThinqApiClientService { -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java deleted file mode 100644 index fdd617c2b15d4..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; - -/** - * The {@link LGThinqWMApiV2ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -public class LGThinqWMApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqWMApiClientService { - - private static final LGThinqWMApiClientService instance; - static { - instance = new LGThinqWMApiV2ClientServiceImpl(); - } - - public static LGThinqWMApiClientService getInstance() { - return instance; - } - - @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { - throw new UnsupportedOperationException("Not implemented yet."); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 1542702e34172..81d394ec43076 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; import java.util.HashMap; import java.util.Map; @@ -21,7 +21,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -40,7 +41,7 @@ public class SnapshotFactory { instance = new SnapshotFactory(); } - public static final SnapshotFactory getInstance() { + public static SnapshotFactory getInstance() { return instance; } @@ -52,20 +53,22 @@ public static final SnapshotFactory getInstance() { * @return returns Snapshot implementation based on device type provided * @throws LGThinqApiException any error. */ - public Snapshot create(String snapshotDataJson, DeviceTypes deviceType) throws LGThinqApiException { + public S create(String snapshotDataJson, DeviceTypes deviceType, Class clazz) + throws LGThinqApiException { try { Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { }); Map deviceSetting = new HashMap<>(); deviceSetting.put("deviceType", deviceType.deviceTypeId()); deviceSetting.put("snapshot", snapshotMap); - return create(deviceSetting); + return create(deviceSetting, clazz); } catch (JsonProcessingException e) { throw new LGThinqApiException("Unexpected Error unmarshalling json to map", e); } } - public Snapshot create(Map deviceSettings) throws LGThinqApiException { + public S create(Map deviceSettings, Class clazz) + throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); Map snapMap = ((Map) deviceSettings.get("snapshot")); if (snapMap == null) { @@ -74,7 +77,7 @@ public Snapshot create(Map deviceSettings) throws LGThinqApiExce LGAPIVerion version = discoveryAPIVersion(snapMap, type); switch (type) { case AIR_CONDITIONER: - return objectMapper.convertValue(snapMap, ACSnapshot.class); + return clazz.cast(objectMapper.convertValue(snapMap, ACSnapshot.class)); case WASHING_MACHINE: switch (version) { case V1_0: { @@ -84,10 +87,21 @@ public Snapshot create(Map deviceSettings) throws LGThinqApiExce Map washerDryerMap = Objects.requireNonNull( (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), "washerDryer node must be present in the snapshot"); - return objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class); + return clazz.cast(objectMapper.convertValue(washerDryerMap, WasherSnapshot.class)); + } + } + case DRYER: + switch (version) { + case V1_0: { + throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + } + case V2_0: { + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), + "washerDryer node must be present in the snapshot"); + return clazz.cast(objectMapper.convertValue(washerDryerMap, DryerSnapshot.class)); } } - default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } @@ -110,7 +124,7 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes throw new IllegalStateException( "Unexpected error. Can't find key node attributes to determine AC API version."); } - + case DRYER: case WASHING_MACHINE: return LGAPIVerion.V2_0; default: diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java new file mode 100644 index 0000000000000..e11dc05da9ab8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.dryer; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.Capability; + +/** + * The {@link DryerCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DryerCapability extends Capability { + public enum MonitoringCap { + STATE("state"), + PROCESS_STATE("processState"), + DRY_LEVEL("dryLevel"), + ERROR("error"); + + final String value; + + MonitoringCap(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + private static class MonitoringValue { + private final Map state = new LinkedHashMap(); + private final Map dryLevel = new LinkedHashMap(); + private final Map error = new LinkedHashMap(); + private final Map processState = new LinkedHashMap(); + private boolean hasChildLock; + private boolean hasRemoteStart; + } + + private final MonitoringValue monitoringValue = new MonitoringValue(); + private final Map courses = new LinkedHashMap(); + + private final Map smartCourses = new LinkedHashMap(); + + public Map getCourses() { + return courses; + } + + public Map getSmartCourses() { + return smartCourses; + } + + public void addCourse(String courseLabel, String courseName) { + courses.put(courseLabel, courseName); + } + + public void addSmartCourse(String courseLabel, String courseName) { + smartCourses.put(courseLabel, courseName); + } + + public Map getState() { + return monitoringValue.state; + } + + public Map getDryLevels() { + return monitoringValue.dryLevel; + } + + public Map getErrors() { + return monitoringValue.error; + } + + public Map getProcessStates() { + return monitoringValue.processState; + } + + public boolean hasRemoteStart() { + return monitoringValue.hasRemoteStart; + } + + public boolean hasChildLock() { + return monitoringValue.hasChildLock; + } + + public void setChildLock(boolean hasChildLock) { + monitoringValue.hasChildLock = hasChildLock; + } + + public void setRemoteStart(boolean hasRemoteStart) { + monitoringValue.hasRemoteStart = hasRemoteStart; + } + + public void addMonitoringValue(MonitoringCap monCap, String key, String value) { + switch (monCap) { + case STATE: + monitoringValue.state.put(key, value); + break; + case PROCESS_STATE: + monitoringValue.processState.put(key, value); + break; + case DRY_LEVEL: + monitoringValue.dryLevel.put(key, value); + break; + case ERROR: + monitoringValue.error.put(key, value); + break; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java new file mode 100644 index 0000000000000..70e60d595b95f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.dryer; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link DryerSnapshot} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->washerDryer, but this POJO expects + * to map field below washerDryer + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class DryerSnapshot implements Snapshot { + private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; + private String state = ""; + private boolean online; + private String course = ""; + private String smartCourse = ""; + private String childLock = ""; + private String processState = ""; + private Double remainingHour = 0.00; + private Double remainingMinute = 0.00; + private String dryLevel = ""; + private String error = ""; + + @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) + @JsonProperty("courseDryer24inchBase") + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } + + @JsonProperty("dryLevel") + public String getDryLevel() { + return dryLevel; + } + + public void setDryLevel(String dryLevel) { + this.dryLevel = dryLevel; + } + + public void setRemainingMinute(Double remainingMinute) { + this.remainingMinute = remainingMinute; + } + + @JsonProperty("error") + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + @JsonProperty("processState") + public String getProcessState() { + return processState; + } + + public void setProcessState(String processState) { + this.processState = processState; + } + + @Override + public DevicePowerState getPowerStatus() { + return powerState; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + throw new IllegalArgumentException("This method must not be accessed."); + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } + + @JsonProperty("state") + @JsonAlias({ "state", "State" }) + public String getState() { + return state; + } + + @JsonProperty("smartCourseDryer24inchBase") + @JsonAlias({ "smartCourseDryer24inchBase", "SmartCourse", "smartCourseDryer27inchBase" }) + public String getSmartCourse() { + return smartCourse; + } + + public void setSmartCourse(String smartCourse) { + this.smartCourse = smartCourse; + } + + @JsonProperty("childLock") + public String getChildLock() { + return childLock; + } + + public void setChildLock(String childLock) { + this.childLock = childLock; + } + + @JsonIgnore + public String getRemainingTime() { + return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); + } + + @JsonProperty("remainTimeHour") + @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + public Double getRemainingHour() { + return remainingHour; + } + + public void setRemainingHour(Double remainingHour) { + this.remainingHour = remainingHour; + } + + @JsonProperty("remainTimeMinute") + @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + public Double getRemainingMinute() { + return remainingMinute; + } + + public void setState(String state) { + this.state = state; + if (state.equals(WM_POWER_OFF_VALUE)) { + powerState = DevicePowerState.DV_POWER_OFF; + } else { + powerState = DevicePowerState.DV_POWER_ON; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java index 4294f98cc1b26..aa6dcbcb95501 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java @@ -19,12 +19,12 @@ import org.openhab.binding.lgthinq.lgservices.model.Capability; /** - * The {@link WMCapability} + * The {@link WasherCapability} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class WMCapability extends Capability { +public class WasherCapability extends Capability { public enum MonitoringCap { STATE("state"), SOIL_WASH("soilWash"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java similarity index 76% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java index 53943e435cb9f..5cc2df814d07a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java @@ -12,18 +12,19 @@ */ package org.openhab.binding.lgthinq.lgservices.model.washer; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_POWER_OFF_VALUE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** - * The {@link WasherDryerSnapshot} + * The {@link WasherSnapshot} * This map the snapshot result from Washing Machine devices * This json payload come with path: snapshot->washerDryer, but this POJO expects * to map field below washerDryer @@ -32,7 +33,7 @@ */ @NonNullByDefault @JsonIgnoreProperties(ignoreUnknown = true) -public class WasherDryerSnapshot implements Snapshot { +public class WasherSnapshot implements Snapshot { private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; private String state = ""; private boolean online; @@ -40,6 +41,8 @@ public class WasherDryerSnapshot implements Snapshot { private String smartCourse = ""; private String temperatureLevel = ""; private String doorLock = ""; + private Double remainingHour = 0.00; + private Double remainingMinute = 0.00; @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) @JsonProperty("courseFL24inchBaseTitan") @@ -83,6 +86,31 @@ public String getSmartCourse() { return smartCourse; } + @JsonIgnore + public String getRemainingTime() { + return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); + } + + @JsonProperty("remainTimeHour") + @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + public Double getRemainingHour() { + return remainingHour; + } + + public void setRemainingHour(Double remainingHour) { + this.remainingHour = remainingHour; + } + + @JsonProperty("remainTimeMinute") + @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + public Double getRemainingMinute() { + return remainingMinute; + } + + public void setRemainingMinute(Double remainingMinute) { + this.remainingMinute = remainingMinute; + } + public void setSmartCourse(String smartCourse) { this.smartCourse = smartCourse; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 11abb169992ed..533acc1269400 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -10,8 +10,8 @@ - - LG Thinq Washing Machine + + LG ThinQ Washing Machine @@ -20,6 +20,7 @@ + From e1191010e72281cde82b9a37ec964e14f48fe3ff Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 15 Feb 2022 09:07:21 -0300 Subject: [PATCH 042/130] [lgthinq][Fix] Fixed some Channel's mapping errors in Washing Machine Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 11 ----------- .../internal/handler/LGThinQWasherHandler.java | 17 +++++++++-------- .../src/main/resources/OH-INF/thing/washer.xml | 2 +- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 6c0c64d034bd5..2d04da2af8f69 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -17,7 +17,6 @@ import java.util.*; import java.util.concurrent.*; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; @@ -116,16 +115,6 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa } } - @NonNull - private String emptyIfNull(@Nullable String value) { - return value == null ? "" : "" + value; - } - - @NonNull - private String keyIfValueNotFound(Map map, @NonNull String key) { - return Objects.requireNonNullElse(map.get(key), key); - } - @Override public void updateChannelDynStateDescription() throws LGThinqApiException { DryerCapability drCap = getCapabilities(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 9a6499fbaef8d..6a9a38360c89e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -17,9 +17,7 @@ import java.util.*; import java.util.concurrent.*; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -50,6 +48,7 @@ public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler options = new ArrayList<>(); - wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); + wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_STATE, k)))); stateDescriptionProvider.setStateOptions(stateChannelUUID, options); } if (isLinked(courseChannelUUID)) { @@ -110,6 +105,12 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); } + if (isLinked(temperatureChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getTemperature() + .forEach((k, v) -> options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_TEMPERATURE, k)))); + stateDescriptionProvider.setStateOptions(temperatureChannelUUID, options); + } } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 533acc1269400..0024e9a2270e8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -20,7 +20,7 @@ - + From a34d0452d7f3fc7611162b27a4d1a9a759cdea2b Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 16 Feb 2022 11:04:56 -0300 Subject: [PATCH 043/130] [lgthinq][Fix] Fixed tests, some channal options translations and Power Channel switch. Signed-off-by: nemerdaud --- .../lgthinq/internal/handler/LGThinQWasherHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 6a9a38360c89e..3992ad3bb5cc6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -82,7 +82,7 @@ public AsyncCommandParams(String channelUUID, Command command) { @Override public void initialize() { - logger.debug("Initializing Thinq thing."); + logger.debug("Initializing Thinq thing. Washer Thing v0.1"); Bridge bridge = getBridge(); initializeThing((bridge == null) ? null : bridge.getStatus()); } @@ -125,7 +125,8 @@ protected Logger getLogger() { @Override protected void updateDeviceChannels(WasherSnapshot shot) { - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + updateState(CHANNEL_POWER_ID, + (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); From 50aa3f79a59b54071b366eec07f8be01321cf0cc Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Mon, 21 Feb 2022 16:43:58 -0300 Subject: [PATCH 044/130] [lgthinq][Fix] Fixed bug related to when device goes offline (lost connection); handle better error results, fixed others minor bugs. Signed-off-by: nemerdaud --- .../binding/lgthinq/internal/handler/LGThinQDryerHandler.java | 1 - .../binding/lgthinq/internal/handler/LGThinQWasherHandler.java | 1 - 2 files changed, 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 2d04da2af8f69..90124da21c140 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -93,7 +93,6 @@ protected void updateDeviceChannels(DryerSnapshot shot) { updateState(DR_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); updateState(DR_CHANNEL_DRY_LEVEL_ID, new StringType(shot.getDryLevel())); updateState(DR_CHANNEL_ERROR_ID, new StringType(shot.getError())); - updateStatus(ThingStatus.ONLINE); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 3992ad3bb5cc6..f3da5e19ff657 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -133,7 +133,6 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); - updateStatus(ThingStatus.ONLINE); } @Override From 4a32657d3f2be99fe52acb6c3599bb6aad419e05 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 22 Feb 2022 21:00:49 -0300 Subject: [PATCH 045/130] [lgthinq][Feat] Add new channel DownloadedCourse for Washers; reset WM channels to default values when the device disconnected (offline from LG API). Signed-off-by: nemerdaud --- .../internal/LGThinQBindingConstants.java | 1 + .../internal/handler/LGThinQDryerHandler.java | 13 +------- .../handler/LGThinQWasherHandler.java | 33 +++++++++++-------- .../model/washer/WasherSnapshot.java | 11 +++++++ .../main/resources/OH-INF/thing/washer.xml | 1 + 5 files changed, 34 insertions(+), 25 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 7e204e69bfb41..6c99842a54a2f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -260,6 +260,7 @@ public class LGThinQBindingConstants { public static final String WM_CHANNEL_COURSE_ID = "course"; public static final String DR_CHANNEL_DRY_LEVEL_ID = "dry-level"; public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; + public static final String WM_CHANNEL_DOWNLOADED_COURSE_ID = "downloaded-course"; public static final String WM_CHANNEL_TEMP_LEVEL_ID = "temperature-level"; public static final String WM_CHANNEL_DOOR_LOCK_ID = "door-lock"; public static final String DR_CHANNEL_CHILD_LOCK_ID = "child-lock"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 90124da21c140..8fd13567568fb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -177,29 +177,18 @@ public String getDeviceAlias() { return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); } - @Override - public String getDeviceModelName() { - return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); - } - @Override public String getDeviceUriJsonConfig() { return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); } - @Override - public boolean onDeviceStateChanged() { - // TODO - HANDLE IT, Think if it's needed - return false; - } - @Override public void onDeviceRemoved() { // TODO - HANDLE IT, Think if it's needed } @Override - public void onDeviceGone() { + public void onDeviceDisconnected() { // TODO - HANDLE IT, Think if it's needed } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index f3da5e19ff657..945ccff29639c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -48,6 +48,7 @@ public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler options.add(new StateOption(k, emptyIfNull(v)))); stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); } + if (isLinked(downloadedCourseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(downloadedCourseChannelUUID, options); + } if (isLinked(temperatureChannelUUID)) { List options = new ArrayList<>(); wmCap.getTemperature() @@ -133,6 +140,7 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); } @Override @@ -169,29 +177,28 @@ public String getDeviceAlias() { return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); } - @Override - public String getDeviceModelName() { - return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); - } - @Override public String getDeviceUriJsonConfig() { return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); } - @Override - public boolean onDeviceStateChanged() { - // TODO - HANDLE IT, Think if it's needed - return false; - } - @Override public void onDeviceRemoved() { // TODO - HANDLE IT, Think if it's needed } + /** + * Put the channels in default state if the device is disconnected or gone. + */ @Override - public void onDeviceGone() { - // TODO - HANDLE IT, Think if it's needed + public void onDeviceDisconnected() { + updateState(CHANNEL_POWER_ID, OnOffType.OFF); + updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); + updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); + updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); + updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType("NOT_SELECTED")); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java index 5cc2df814d07a..993b88f0cac3c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java @@ -39,6 +39,7 @@ public class WasherSnapshot implements Snapshot { private boolean online; private String course = ""; private String smartCourse = ""; + private String downloadedCourse = ""; private String temperatureLevel = ""; private String doorLock = ""; private Double remainingHour = 0.00; @@ -86,6 +87,16 @@ public String getSmartCourse() { return smartCourse; } + @JsonProperty("downloadedCourseFL24inchBaseTitan") + @JsonAlias({ "downloadedCourseFLUpper25inchBaseUS" }) + public String getDownloadedCourse() { + return downloadedCourse; + } + + public void setDownloadedCourse(String downloadedCourse) { + this.downloadedCourse = downloadedCourse; + } + @JsonIgnore public String getRemainingTime() { return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 0024e9a2270e8..a340acc12e9ef 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -18,6 +18,7 @@ + From 128624dbe7f107cff351933824dff004ad4a03d2 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Thu, 3 Mar 2022 08:29:02 -0300 Subject: [PATCH 046/130] [lgthinq][Feat] Added cool jet feature for Air Conditioners. Some changes to be compatible with 3.3-M1 Signed-off-by: nemerdaud --- .../lgservices/model/ac/ACCapability.java | 27 +++++++++++++++++++ .../lgservices/model/ac/ACSnapshot.java | 14 ++++++++++ 2 files changed, 41 insertions(+) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index 0e7d9f59d1049..372dbdc067a76 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -32,6 +32,25 @@ public class ACCapability extends Capability { private List supportedOpMode = Collections.emptyList(); private List supportedFanSpeed = Collections.emptyList(); + private boolean isJetModeAvailable; + private String coolJetModeCommandOn = ""; + private String coolJetModeCommandOff = ""; + + public String getCoolJetModeCommandOff() { + return coolJetModeCommandOff; + } + + public void setCoolJetModeCommandOff(String coolJetModeCommandOff) { + this.coolJetModeCommandOff = coolJetModeCommandOff; + } + + public String getCoolJetModeCommandOn() { + return coolJetModeCommandOn; + } + + public void setCoolJetModeCommandOn(String coolJetModeCommandOn) { + this.coolJetModeCommandOn = coolJetModeCommandOn; + } public Map getOpMod() { return opMod; @@ -64,4 +83,12 @@ public List getSupportedFanSpeed() { public void setSupportedFanSpeed(List supportedFanSpeed) { this.supportedFanSpeed = supportedFanSpeed; } + + public void setJetModeAvailable(boolean jetModeAvailable) { + this.isJetModeAvailable = jetModeAvailable; + } + + public boolean isJetModeAvailable() { + return this.isJetModeAvailable; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java index ec882915da164..8593c8be174c9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java @@ -37,6 +37,10 @@ public class ACSnapshot implements Snapshot { private double currentTemperature; + private boolean coolJetModeOn; + + private double coolJetMode; + private int operationMode; @Nullable private Integer operation; @@ -64,6 +68,16 @@ public Integer getAirWindStrength() { return airWindStrength; } + @JsonProperty("airState.wMode.jet") + @JsonAlias("Jet") + public Double getCoolJetMode() { + return coolJetMode; + } + + public void setCoolJetMode(Double coolJetMode) { + this.coolJetMode = coolJetMode; + } + public void setAirWindStrength(Integer airWindStrength) { this.airWindStrength = airWindStrength; } From 1d72f213b3b0f080d5b395cc294105faf4c09e67 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 23 Mar 2022 17:06:47 -0300 Subject: [PATCH 047/130] [lgthinq][Feat] Add optional language and country in the configuration to manual entry and start to implement energy consumption channel Signed-off-by: nemerdaud --- .../lgthinq/internal/LGThinQConfiguration.java | 8 ++++++++ .../lgthinq/lgservices/LGThinQApiClientService.java | 3 +++ .../lgthinq/lgservices/model/ac/ACSnapshot.java | 11 +++++++++++ 3 files changed, 22 insertions(+) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java index b718d63d29068..e0e93c7da7c87 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java @@ -28,6 +28,8 @@ public class LGThinQConfiguration { public String password = ""; public String country = ""; public String language = ""; + public String manualCountry = ""; + public String manualLanguage = ""; public Integer pollingIntervalSec = 0; public String alternativeServer = ""; @@ -53,10 +55,16 @@ public String getPassword() { } public String getCountry() { + if ("--".equals(country)) { + return manualCountry; + } return country; } public String getLanguage() { + if ("--".equals(language)) { + return manualLanguage; + } return language; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index b23f4902a5456..0404d7d141a0d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -49,6 +49,9 @@ public interface LGThinQApiClientService Date: Mon, 2 May 2022 23:09:05 -0300 Subject: [PATCH 048/130] [lgthinq][Feat] Addition of V1 Washer support and some bug fixes discovering API version of the devices. Signed-off-by: nemerdaud --- .../handler/LGThinQAirConditionerHandler.java | 4 ++ .../internal/handler/LGThinQDryerHandler.java | 5 +++ .../handler/LGThinQWasherHandler.java | 15 +++++++ .../lgservices/model/SnapshotFactory.java | 18 +++++--- .../model/dryer/DryerCapability.java | 22 ++++++---- .../lgservices/model/dryer/DryerSnapshot.java | 3 ++ .../model/washer/WasherCapability.java | 41 ++++++++++++++----- .../model/washer/WasherSnapshot.java | 2 + 8 files changed, 87 insertions(+), 23 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 2e189839122ad..e3d6b31b928e0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -249,6 +249,10 @@ protected DeviceTypes getDeviceType() { } } + protected DeviceTypes getDeviceType() { + return DeviceTypes.AIR_CONDITIONER; + } + @Override public void onDeviceAdded(LGDevice device) { // TODO - handle it. Think if it's needed diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 8fd13567568fb..857440ab75fac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -25,6 +25,7 @@ import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; @@ -75,6 +76,10 @@ public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider dryLevelChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_DRY_LEVEL_ID); } + protected DeviceTypes getDeviceType() { + return DeviceTypes.DRYER; + } + @Override public void initialize() { logger.debug("Initializing Thinq thing."); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 945ccff29639c..29bb2ab646b75 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -24,6 +24,7 @@ import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; @@ -50,6 +51,8 @@ public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_TEMPERATURE, k)))); stateDescriptionProvider.setStateOptions(temperatureChannelUUID, options); } + if (isLinked(doorLockChannelUUID)) { + List options = new ArrayList<>(); + options.add(new StateOption("0", "Unlocked")); + options.add(new StateOption("1", "Locked")); + stateDescriptionProvider.setStateOptions(doorLockChannelUUID, options); + } } @Override @@ -143,6 +153,11 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); } + @Override + protected DeviceTypes getDeviceType() { + return DeviceTypes.WASHING_MACHINE; + } + @Override protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { Command command = params.command; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 81d394ec43076..121c8e9acff4f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; import java.util.HashMap; import java.util.Map; @@ -81,11 +81,11 @@ public S create(Map deviceSettings, Class washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), "washerDryer node must be present in the snapshot"); return clazz.cast(objectMapper.convertValue(washerDryerMap, WasherSnapshot.class)); } @@ -97,7 +97,7 @@ public S create(Map deviceSettings, Class washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), "washerDryer node must be present in the snapshot"); return clazz.cast(objectMapper.convertValue(washerDryerMap, DryerSnapshot.class)); } @@ -125,8 +125,16 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes "Unexpected error. Can't find key node attributes to determine AC API version."); } case DRYER: - case WASHING_MACHINE: return LGAPIVerion.V2_0; + case WASHING_MACHINE: + if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { + return LGAPIVerion.V2_0; + } else if (snapMap.containsKey("State")) { + return LGAPIVerion.V1_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine AC API version."); + } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java index e11dc05da9ab8..1a3cdb581319a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java @@ -26,10 +26,13 @@ @NonNullByDefault public class DryerCapability extends Capability { public enum MonitoringCap { - STATE("state"), - PROCESS_STATE("processState"), - DRY_LEVEL("dryLevel"), - ERROR("error"); + STATE_V2("state"), + PROCESS_STATE_V2("processState"), + DRY_LEVEL_V2("dryLevel"), + ERROR_V2("error"), + STATE_V1("State"), + PROCESS_STATE_V1("PreState"), + ERROR_V1("Error"); final String value; @@ -106,16 +109,19 @@ public void setRemoteStart(boolean hasRemoteStart) { public void addMonitoringValue(MonitoringCap monCap, String key, String value) { switch (monCap) { - case STATE: + case STATE_V2: + case STATE_V1: monitoringValue.state.put(key, value); break; - case PROCESS_STATE: + case PROCESS_STATE_V2: + case PROCESS_STATE_V1: monitoringValue.processState.put(key, value); break; - case DRY_LEVEL: + case DRY_LEVEL_V2: monitoringValue.dryLevel.put(key, value); break; - case ERROR: + case ERROR_V1: + case ERROR_V2: monitoringValue.error.put(key, value); break; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java index 70e60d595b95f..f3972e10620d6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java @@ -57,6 +57,7 @@ public void setCourse(String course) { } @JsonProperty("dryLevel") + @JsonAlias({ "DryLevel" }) public String getDryLevel() { return dryLevel; } @@ -70,6 +71,7 @@ public void setRemainingMinute(Double remainingMinute) { } @JsonProperty("error") + @JsonAlias({ "Error" }) public String getError() { return error; } @@ -79,6 +81,7 @@ public void setError(String error) { } @JsonProperty("processState") + @JsonAlias({ "ProcessState" }) public String getProcessState() { return processState; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java index aa6dcbcb95501..b52ff310f779b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java @@ -26,11 +26,18 @@ @NonNullByDefault public class WasherCapability extends Capability { public enum MonitoringCap { - STATE("state"), - SOIL_WASH("soilWash"), - SPIN("spin"), - TEMPERATURE("temp"), - RINSE("rinse"); + STATE_V2("state"), + SOIL_WASH_V2("soilWash"), + SPIN_V2("spin"), + TEMPERATURE_V2("temp"), + RINSE_V2("rinse"), + ERROR_V2("error"), + STATE_V1("State"), + SOIL_WASH_V1("Wash"), + SPIN_V1("SpinSpeed"), + TEMPERATURE_V1("WaterTemp"), + RINSE_V1("RinseOption"), + ERROR_V1("Error"); final String value; @@ -49,6 +56,7 @@ private static class MonitoringValue { private Map spin = new LinkedHashMap(); private Map temperature = new LinkedHashMap(); private Map rinse = new LinkedHashMap(); + private Map error = new LinkedHashMap(); private boolean hasDoorLook; private boolean hasTurboWash; } @@ -94,6 +102,10 @@ public Map getRinse() { return monitoringValue.rinse; } + public Map getError() { + return monitoringValue.error; + } + public boolean hasDoorLook() { return monitoringValue.hasDoorLook; } @@ -112,21 +124,30 @@ public void setHasTurboWash(boolean hasTurboWash) { public void addMonitoringValue(MonitoringCap monCap, String key, String value) { switch (monCap) { - case STATE: + case STATE_V1: + case STATE_V2: monitoringValue.state.put(key, value); break; - case SOIL_WASH: + case SOIL_WASH_V2: + case SOIL_WASH_V1: monitoringValue.soilWash.put(key, value); break; - case SPIN: + case SPIN_V2: + case SPIN_V1: monitoringValue.spin.put(key, value); break; - case TEMPERATURE: + case TEMPERATURE_V2: + case TEMPERATURE_V1: monitoringValue.temperature.put(key, value); break; - case RINSE: + case RINSE_V2: + case RINSE_V1: monitoringValue.rinse.put(key, value); break; + case ERROR_V2: + case ERROR_V1: + monitoringValue.error.put(key, value); + break; } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java index 993b88f0cac3c..eb3130a182303 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java @@ -127,6 +127,7 @@ public void setSmartCourse(String smartCourse) { } @JsonProperty("temp") + @JsonAlias({ "WaterTemp" }) public String getTemperatureLevel() { return temperatureLevel; } @@ -136,6 +137,7 @@ public void setTemperatureLevel(String temperatureLevel) { } @JsonProperty("doorLock") + @JsonAlias({ "ChildLock" }) public String getDoorLock() { return doorLock; } From 4835713f791f480f8c4056c4458b066fe06a8040 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 3 May 2022 17:27:41 -0300 Subject: [PATCH 049/130] [lgthinq][Feat] Addition of support to binary data monitoring. Signed-off-by: nemerdaud --- .../lgthinq/lgservices/model/Capability.java | 22 +++++ .../lgservices/model/SnapshotFactory.java | 80 +++++++++++++++++-- 2 files changed, 94 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java index 2fe161c37684b..440dfa37e8dcf 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java @@ -12,11 +12,33 @@ */ package org.openhab.binding.lgthinq.lgservices.model; +import java.util.ArrayList; +import java.util.List; + /** * The {@link Capability} * * @author Nemer Daud - Initial contribution */ public abstract class Capability { + // default result format + private MonitoringResultFormat monitoringDataFormat = MonitoringResultFormat.JSON_FORMAT; + + private List monitoringBinaryProtocol = new ArrayList<>(); + + public MonitoringResultFormat getMonitoringDataFormat() { + return monitoringDataFormat; + } + + public void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat) { + this.monitoringDataFormat = monitoringDataFormat; + } + + public List getMonitoringBinaryProtocol() { + return monitoringBinaryProtocol; + } + public void setMonitoringBinaryProtocol(List monitoringBinaryProtocol) { + this.monitoringBinaryProtocol = monitoringBinaryProtocol; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 121c8e9acff4f..9f7fedfed076e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -14,16 +14,23 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,6 +52,63 @@ public static SnapshotFactory getInstance() { return instance; } + /** + * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) + * + * @param binaryData V1: decoded returnedData + * + * @return returns Snapshot implementation based on device type provided + * @throws LGThinqApiException any error. + */ + public S createFromBinary(String binaryData, List prot, + Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { + try { + byte[] data = binaryData.getBytes(); + BeanInfo beanInfo = Introspector.getBeanInfo(clazz); + S snap = clazz.getConstructor().newInstance(); + PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); + for (Iterator it = prot.iterator(); it.hasNext();) { + MonitoringBinaryProtocol protField = it.next(); + String fName = protField.fieldName; + for (PropertyDescriptor property : pds) { + // all attributes of class. + Method m = property.getReadMethod(); // getter + List aliases = new ArrayList<>(); + if (m.isAnnotationPresent(JsonProperty.class)) { + aliases.add(m.getAnnotation(JsonProperty.class).value()); + } + if (m.isAnnotationPresent(JsonAlias.class)) { + aliases.addAll(Arrays.asList(m.getAnnotation(JsonAlias.class).value())); + } + + if (aliases.contains(fName)) { + // found property. Get bit value + int value = 0; + for (int i = protField.startByte; i < protField.startByte + protField.length; i++) { + value = (value << 8) + data[i]; + } + m = property.getWriteMethod(); + if (m.getParameters()[0].getType() == String.class) { + m.invoke(snap, String.valueOf(value)); + } else if (m.getParameters()[0].getType() == Double.class) { + m.invoke(snap, (double) value); + } else if (m.getParameters()[0].getType() == Integer.class) { + m.invoke(snap, value); + } else { + throw new IllegalArgumentException( + String.format("Parameter type not supported for this factory:%s", + m.getParameters()[0].getType().toString())); + } + } + } + } + return snap; + } catch (IntrospectionException | InvocationTargetException | InstantiationException | IllegalAccessException + | NoSuchMethodException e) { + throw new LGThinqUnmarshallException("Unexpected Error unmarshalling binary data", e); + } + } + /** * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) * @@ -53,21 +117,21 @@ public static SnapshotFactory getInstance() { * @return returns Snapshot implementation based on device type provided * @throws LGThinqApiException any error. */ - public S create(String snapshotDataJson, DeviceTypes deviceType, Class clazz) - throws LGThinqApiException { + public S createFromJson(String snapshotDataJson, DeviceTypes deviceType, Class clazz) + throws LGThinqUnmarshallException, LGThinqApiException { try { Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { }); Map deviceSetting = new HashMap<>(); deviceSetting.put("deviceType", deviceType.deviceTypeId()); deviceSetting.put("snapshot", snapshotMap); - return create(deviceSetting, clazz); + return createFromJson(deviceSetting, clazz); } catch (JsonProcessingException e) { - throw new LGThinqApiException("Unexpected Error unmarshalling json to map", e); + throw new LGThinqUnmarshallException("Unexpected Error unmarshalling json to map", e); } } - public S create(Map deviceSettings, Class clazz) + public S createFromJson(Map deviceSettings, Class clazz) throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); Map snapMap = ((Map) deviceSettings.get("snapshot")); From c773be2d652c043bf70ab721ec02c9ac19b178b7 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 18 May 2022 17:36:25 -0300 Subject: [PATCH 050/130] [lgthinq][Fix] Monitor Lock on V1 devices and enforce monitoring timeout in some V2 devices to update temperature sensor more frequently Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 9 - .../model/fridge/FridgeCapability.java | 129 ++++++++++++++ .../model/fridge/FridgeSnapshot.java | 167 ++++++++++++++++++ 3 files changed, 296 insertions(+), 9 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 857440ab75fac..edcf50a12ee6f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -18,7 +18,6 @@ import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -46,8 +45,6 @@ @NonNullByDefault public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler { - @Nullable - private DryerCapability dryerCapability; private final ChannelUID stateChannelUUID; private final ChannelUID processStateChannelUUID; private final ChannelUID dryLevelChannelUUID; @@ -58,12 +55,6 @@ public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, - ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, - ThingStatusDetail.CONFIGURATION_ERROR); - private @Nullable ScheduledFuture thingStatePollingJob; - private @Nullable Future commandExecutorQueueJob; public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { super(thing, stateDescriptionProvider); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java new file mode 100644 index 0000000000000..76f403925858c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.Capability; + +/** + * The {@link FridgeCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FridgeCapability extends Capability { + public enum MonitoringCap { + STATE_V2("state"), + PROCESS_STATE_V2("processState"), + DRY_LEVEL_V2("dryLevel"), + ERROR_V2("error"), + STATE_V1("State"), + PROCESS_STATE_V1("PreState"), + ERROR_V1("Error"); + + final String value; + + MonitoringCap(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + private static class MonitoringValue { + private final Map state = new LinkedHashMap(); + private final Map dryLevel = new LinkedHashMap(); + private final Map error = new LinkedHashMap(); + private final Map processState = new LinkedHashMap(); + private boolean hasChildLock; + private boolean hasRemoteStart; + } + + private final MonitoringValue monitoringValue = new MonitoringValue(); + private final Map courses = new LinkedHashMap(); + + private final Map smartCourses = new LinkedHashMap(); + + public Map getCourses() { + return courses; + } + + public Map getSmartCourses() { + return smartCourses; + } + + public void addCourse(String courseLabel, String courseName) { + courses.put(courseLabel, courseName); + } + + public void addSmartCourse(String courseLabel, String courseName) { + smartCourses.put(courseLabel, courseName); + } + + public Map getState() { + return monitoringValue.state; + } + + public Map getDryLevels() { + return monitoringValue.dryLevel; + } + + public Map getErrors() { + return monitoringValue.error; + } + + public Map getProcessStates() { + return monitoringValue.processState; + } + + public boolean hasRemoteStart() { + return monitoringValue.hasRemoteStart; + } + + public boolean hasChildLock() { + return monitoringValue.hasChildLock; + } + + public void setChildLock(boolean hasChildLock) { + monitoringValue.hasChildLock = hasChildLock; + } + + public void setRemoteStart(boolean hasRemoteStart) { + monitoringValue.hasRemoteStart = hasRemoteStart; + } + + public void addMonitoringValue(MonitoringCap monCap, String key, String value) { + switch (monCap) { + case STATE_V2: + case STATE_V1: + monitoringValue.state.put(key, value); + break; + case PROCESS_STATE_V2: + case PROCESS_STATE_V1: + monitoringValue.processState.put(key, value); + break; + case DRY_LEVEL_V2: + monitoringValue.dryLevel.put(key, value); + break; + case ERROR_V1: + case ERROR_V2: + monitoringValue.error.put(key, value); + break; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java new file mode 100644 index 0000000000000..fa8104fed0dd8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link FridgeSnapshot} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->washerDryer, but this POJO expects + * to map field below washerDryer + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class FridgeSnapshot implements Snapshot { + private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; + private String state = ""; + private boolean online; + private String course = ""; + private String smartCourse = ""; + private String childLock = ""; + private String processState = ""; + private Double remainingHour = 0.00; + private Double remainingMinute = 0.00; + private String dryLevel = ""; + private String error = ""; + + @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) + @JsonProperty("courseDryer24inchBase") + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } + + @JsonProperty("dryLevel") + @JsonAlias({ "DryLevel" }) + public String getDryLevel() { + return dryLevel; + } + + public void setDryLevel(String dryLevel) { + this.dryLevel = dryLevel; + } + + public void setRemainingMinute(Double remainingMinute) { + this.remainingMinute = remainingMinute; + } + + @JsonProperty("error") + @JsonAlias({ "Error" }) + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + @JsonProperty("processState") + @JsonAlias({ "ProcessState" }) + public String getProcessState() { + return processState; + } + + public void setProcessState(String processState) { + this.processState = processState; + } + + @Override + public DevicePowerState getPowerStatus() { + return powerState; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + throw new IllegalArgumentException("This method must not be accessed."); + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } + + @JsonProperty("state") + @JsonAlias({ "state", "State" }) + public String getState() { + return state; + } + + @JsonProperty("smartCourseDryer24inchBase") + @JsonAlias({ "smartCourseDryer24inchBase", "SmartCourse", "smartCourseDryer27inchBase" }) + public String getSmartCourse() { + return smartCourse; + } + + public void setSmartCourse(String smartCourse) { + this.smartCourse = smartCourse; + } + + @JsonProperty("childLock") + public String getChildLock() { + return childLock; + } + + public void setChildLock(String childLock) { + this.childLock = childLock; + } + + @JsonIgnore + public String getRemainingTime() { + return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); + } + + @JsonProperty("remainTimeHour") + @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + public Double getRemainingHour() { + return remainingHour; + } + + public void setRemainingHour(Double remainingHour) { + this.remainingHour = remainingHour; + } + + @JsonProperty("remainTimeMinute") + @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + public Double getRemainingMinute() { + return remainingMinute; + } + + public void setState(String state) { + this.state = state; + if (state.equals(WM_POWER_OFF_VALUE)) { + powerState = DevicePowerState.DV_POWER_OFF; + } else { + powerState = DevicePowerState.DV_POWER_ON; + } + } +} From 7e42c63eb9d0b12ba13c446e889867fd2a21e18b Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 1 Jun 2022 08:23:15 -0300 Subject: [PATCH 051/130] [lgthinq][feat] implementing Refrigerator support thing Signed-off-by: nemerdaud --- .../lgthinq/lgservices/model/Capability.java | 24 +-- .../lgservices/model/SnapshotFactory.java | 22 ++- .../lgservices/model/ac/ACCapability.java | 4 +- .../model/dryer/DryerCapability.java | 4 +- .../model/fridge/FridgeCapability.java | 104 +------------ .../model/fridge/FridgeFactory.java | 47 ++++++ .../model/fridge/FridgeSnapshot.java | 147 +----------------- .../model/fridge/v2/FridgeCapabilityV2.java | 97 ++++++++++++ .../model/fridge/v2/FridgeSnapshotV2.java | 98 ++++++++++++ .../model/washer/WasherCapability.java | 4 +- 10 files changed, 286 insertions(+), 265 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java index 440dfa37e8dcf..a36bcfaea25d7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import java.util.ArrayList; import java.util.List; /** @@ -20,25 +19,12 @@ * * @author Nemer Daud - Initial contribution */ -public abstract class Capability { - // default result format - private MonitoringResultFormat monitoringDataFormat = MonitoringResultFormat.JSON_FORMAT; +public interface Capability { + MonitoringResultFormat getMonitoringDataFormat(); - private List monitoringBinaryProtocol = new ArrayList<>(); + void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat); - public MonitoringResultFormat getMonitoringDataFormat() { - return monitoringDataFormat; - } + List getMonitoringBinaryProtocol(); - public void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat) { - this.monitoringDataFormat = monitoringDataFormat; - } - - public List getMonitoringBinaryProtocol() { - return monitoringBinaryProtocol; - } - - public void setMonitoringBinaryProtocol(List monitoringBinaryProtocol) { - this.monitoringBinaryProtocol = monitoringBinaryProtocol; - } + void setMonitoringBinaryProtocol(List monitoringBinaryProtocol); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 9f7fedfed076e..b319279242d5f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import java.beans.BeanInfo; import java.beans.IntrospectionException; @@ -27,6 +27,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; import com.fasterxml.jackson.annotation.JsonAlias; @@ -166,6 +167,18 @@ public S createFromJson(Map deviceSettings, return clazz.cast(objectMapper.convertValue(washerDryerMap, DryerSnapshot.class)); } } + case REFRIGERATOR: + switch (version) { + case V1_0: { + throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + } + case V2_0: { + Map refMap = Objects.requireNonNull( + (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), + "washerDryer node must be present in the snapshot"); + return clazz.cast(objectMapper.convertValue(refMap, FridgeSnapshotV2.class)); + } + } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } @@ -199,6 +212,13 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes throw new IllegalStateException( "Unexpected error. Can't find key node attributes to determine AC API version."); } + case REFRIGERATOR: + if (snapMap.containsKey(REFRIGERATOR_SNAPSHOT_NODE_V2)) { + return LGAPIVerion.V2_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine AC API version."); + } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index 372dbdc067a76..054a4fb5ddb00 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -17,7 +17,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Capability; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; /** * The {@link ACCapability} @@ -25,7 +25,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class ACCapability extends Capability { +public class ACCapability extends AbstractCapability { private Map opMod = Collections.emptyMap(); private Map fanSpeed = Collections.emptyMap(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java index 1a3cdb581319a..bb8aa208a21d7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java @@ -16,7 +16,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Capability; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; /** * The {@link DryerCapability} @@ -24,7 +24,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class DryerCapability extends Capability { +public class DryerCapability extends AbstractCapability { public enum MonitoringCap { STATE_V2("state"), PROCESS_STATE_V2("processState"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java index 76f403925858c..4ba7702f9b582 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.lgthinq.lgservices.model.fridge; -import java.util.LinkedHashMap; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -24,106 +23,15 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class FridgeCapability extends Capability { - public enum MonitoringCap { - STATE_V2("state"), - PROCESS_STATE_V2("processState"), - DRY_LEVEL_V2("dryLevel"), - ERROR_V2("error"), - STATE_V1("State"), - PROCESS_STATE_V1("PreState"), - ERROR_V1("Error"); +public interface FridgeCapability extends Capability { - final String value; + public Map getFridgeTempCMap(); - MonitoringCap(String value) { - this.value = value; - } + public Map getFridgeTempFMap(); - public String getValue() { - return value; - } - } + public Map getFreezerTempCMap(); - private static class MonitoringValue { - private final Map state = new LinkedHashMap(); - private final Map dryLevel = new LinkedHashMap(); - private final Map error = new LinkedHashMap(); - private final Map processState = new LinkedHashMap(); - private boolean hasChildLock; - private boolean hasRemoteStart; - } + public Map getFreezerTempFMap(); - private final MonitoringValue monitoringValue = new MonitoringValue(); - private final Map courses = new LinkedHashMap(); - - private final Map smartCourses = new LinkedHashMap(); - - public Map getCourses() { - return courses; - } - - public Map getSmartCourses() { - return smartCourses; - } - - public void addCourse(String courseLabel, String courseName) { - courses.put(courseLabel, courseName); - } - - public void addSmartCourse(String courseLabel, String courseName) { - smartCourses.put(courseLabel, courseName); - } - - public Map getState() { - return monitoringValue.state; - } - - public Map getDryLevels() { - return monitoringValue.dryLevel; - } - - public Map getErrors() { - return monitoringValue.error; - } - - public Map getProcessStates() { - return monitoringValue.processState; - } - - public boolean hasRemoteStart() { - return monitoringValue.hasRemoteStart; - } - - public boolean hasChildLock() { - return monitoringValue.hasChildLock; - } - - public void setChildLock(boolean hasChildLock) { - monitoringValue.hasChildLock = hasChildLock; - } - - public void setRemoteStart(boolean hasRemoteStart) { - monitoringValue.hasRemoteStart = hasRemoteStart; - } - - public void addMonitoringValue(MonitoringCap monCap, String key, String value) { - switch (monCap) { - case STATE_V2: - case STATE_V1: - monitoringValue.state.put(key, value); - break; - case PROCESS_STATE_V2: - case PROCESS_STATE_V1: - monitoringValue.processState.put(key, value); - break; - case DRY_LEVEL_V2: - monitoringValue.dryLevel.put(key, value); - break; - case ERROR_V1: - case ERROR_V2: - monitoringValue.error.put(key, value); - break; - } - } + public void loadCapabilities(Object veryRootNode); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java new file mode 100644 index 0000000000000..c400c746be0c7 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; + +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeCapabilityV2; +import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; + +/** + * The {@link FridgeFactory} + * + * @author Nemer Daud - Initial contribution + */ +public class FridgeFactory { + + public static FridgeCapability getFridgeCapability(LGAPIVerion version) { + switch (version) { + case V1_0: + throw new IllegalArgumentException("V1_0 not supported by Fridge Thing yet"); + case V2_0: + return new FridgeCapabilityV2(); + default: + throw new IllegalArgumentException("Version " + version + " not expected"); + } + } + + public static FridgeSnapshot getFridgeSnapshot(LGAPIVerion version) { + switch (version) { + case V1_0: + throw new IllegalArgumentException("V1_0 not supported by Fridge Thing yet"); + case V2_0: + return new FridgeSnapshotV2(); + default: + throw new IllegalArgumentException("Version " + version + " not expected"); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java index fa8104fed0dd8..ca368339de28f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java @@ -12,156 +12,21 @@ */ package org.openhab.binding.lgthinq.lgservices.model.fridge; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; - import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * The {@link FridgeSnapshot} - * This map the snapshot result from Washing Machine devices - * This json payload come with path: snapshot->washerDryer, but this POJO expects - * to map field below washerDryer - * + * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class FridgeSnapshot implements Snapshot { - private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; - private String state = ""; - private boolean online; - private String course = ""; - private String smartCourse = ""; - private String childLock = ""; - private String processState = ""; - private Double remainingHour = 0.00; - private Double remainingMinute = 0.00; - private String dryLevel = ""; - private String error = ""; - - @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) - @JsonProperty("courseDryer24inchBase") - public String getCourse() { - return course; - } - - public void setCourse(String course) { - this.course = course; - } - - @JsonProperty("dryLevel") - @JsonAlias({ "DryLevel" }) - public String getDryLevel() { - return dryLevel; - } - - public void setDryLevel(String dryLevel) { - this.dryLevel = dryLevel; - } - - public void setRemainingMinute(Double remainingMinute) { - this.remainingMinute = remainingMinute; - } - - @JsonProperty("error") - @JsonAlias({ "Error" }) - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - - @JsonProperty("processState") - @JsonAlias({ "ProcessState" }) - public String getProcessState() { - return processState; - } - - public void setProcessState(String processState) { - this.processState = processState; - } - - @Override - public DevicePowerState getPowerStatus() { - return powerState; - } - - @Override - public void setPowerStatus(DevicePowerState value) { - throw new IllegalArgumentException("This method must not be accessed."); - } - - @Override - public boolean isOnline() { - return online; - } - - @Override - public void setOnline(boolean online) { - this.online = online; - } - - @JsonProperty("state") - @JsonAlias({ "state", "State" }) - public String getState() { - return state; - } - - @JsonProperty("smartCourseDryer24inchBase") - @JsonAlias({ "smartCourseDryer24inchBase", "SmartCourse", "smartCourseDryer27inchBase" }) - public String getSmartCourse() { - return smartCourse; - } - - public void setSmartCourse(String smartCourse) { - this.smartCourse = smartCourse; - } - - @JsonProperty("childLock") - public String getChildLock() { - return childLock; - } - - public void setChildLock(String childLock) { - this.childLock = childLock; - } - - @JsonIgnore - public String getRemainingTime() { - return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); - } - - @JsonProperty("remainTimeHour") - @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) - public Double getRemainingHour() { - return remainingHour; - } +public interface FridgeSnapshot extends Snapshot { + public String getTempUnit(); - public void setRemainingHour(Double remainingHour) { - this.remainingHour = remainingHour; - } + public String getFridgeTemp(); - @JsonProperty("remainTimeMinute") - @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) - public Double getRemainingMinute() { - return remainingMinute; - } + public String getFreezerTemp(); - public void setState(String state) { - this.state = state; - if (state.equals(WM_POWER_OFF_VALUE)) { - powerState = DevicePowerState.DV_POWER_OFF; - } else { - powerState = DevicePowerState.DV_POWER_ON; - } - } + public void loadSnapshot(Object veryRootNode); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java new file mode 100644 index 0000000000000..68840ebd698b5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge.v2; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link FridgeCapabilityV2} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FridgeCapabilityV2 extends AbstractCapability + implements org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeCapability { + + private static final Logger logger = LoggerFactory.getLogger(FridgeCapabilityV2.class); + private static final ObjectMapper mapper = new ObjectMapper(); + + private final Map fridgeTempCMap = new LinkedHashMap(); + private final Map fridgeTempFMap = new LinkedHashMap(); + private final Map freezerTempCMap = new LinkedHashMap(); + private final Map freezerTempFMap = new LinkedHashMap(); + + public Map getFridgeTempCMap() { + return fridgeTempCMap; + } + + public Map getFridgeTempFMap() { + return fridgeTempFMap; + } + + public Map getFreezerTempCMap() { + return freezerTempCMap; + } + + public Map getFreezerTempFMap() { + return freezerTempFMap; + } + + @Override + public void loadCapabilities(Object veryRootNode) { + JsonNode node = mapper.valueToTree(veryRootNode); + if (node.isNull()) { + logger.error("Can't parse json capability for Fridge V2. The payload has been ignored"); + logger.debug("payload {}", veryRootNode); + return; + } + /** + * iterate over valueMappings like: + * "valueMapping": { + * "1": {"index" : 1, "label" : "7", "_comment" : ""}, + * "2": {"index" : 2, "label" : "6", "_comment" : ""}, + * "3": {"index" : 3, "label" : "5", "_comment" : ""}, + * "4": {"index" : 4, "label" : "4", "_comment" : ""}, + * "5": {"index" : 5, "label" : "3", "_comment" : ""}, + * "6": {"index" : 6, "label" : "2", "_comment" : ""}, + * "7": {"index" : 7, "label" : "1", "_comment" : ""}, + * "255" : {"index" : 255, "label" : "IGNORE", "_comment" : ""} + * } + */ + + JsonNode fridgeTempCNode = node.path("MonitoringValue").path("fridgeTemp_C").path("valueMapping"); + JsonNode fridgeTempFNode = node.path("MonitoringValue").path("fridgeTemp_F").path("valueMapping"); + JsonNode freezerTempCNode = node.path("MonitoringValue").path("freezerTemp_C").path("valueMapping"); + JsonNode freezerTempFNode = node.path("MonitoringValue").path("freezerTemp_F").path("valueMapping"); + loadTempNode(fridgeTempCNode, fridgeTempCMap); + loadTempNode(fridgeTempFNode, fridgeTempFMap); + loadTempNode(freezerTempCNode, freezerTempCMap); + loadTempNode(freezerTempFNode, freezerTempFMap); + } + + private void loadTempNode(JsonNode tempNode, Map capMap) { + tempNode.forEach(v -> { + // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' + capMap.put(v.path("index").asText(), v.path("label").textValue()); + }); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java new file mode 100644 index 0000000000000..ea0d26c24854e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge.v2; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link FridgeSnapshotV2} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->fridge, but this POJO expects + * to map field below washerDryer + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class FridgeSnapshotV2 implements Snapshot, org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeSnapshot { + + private boolean online; + private String fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; + private String freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; + private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default + + @Override + @JsonAlias({ "TempUnit" }) + @JsonProperty("tempUnit") + public String getTempUnit() { + return tempUnit; + } + + public void setTempUnit(String tempUnit) { + this.tempUnit = tempUnit; + } + + @Override + @JsonAlias({ "TempRefrigerator" }) + @JsonProperty("fridgeTemp") + public String getFridgeTemp() { + return fridgeTemp; + } + + public void setFridgeTemp(String fridgeTemp) { + this.fridgeTemp = fridgeTemp; + } + + @Override + @JsonAlias({ "TempFreezer" }) + @JsonProperty("freezerTemp") + public String getFreezerTemp() { + return freezerTemp; + } + + public void setFreezerTemp(String freezerTemp) { + this.freezerTemp = freezerTemp; + } + + @Override + public void loadSnapshot(Object veryRootNode) { + } + + @Override + public DevicePowerState getPowerStatus() { + throw new IllegalStateException("Fridge has no Power state."); + } + + @Override + public void setPowerStatus(DevicePowerState value) { + throw new IllegalStateException("Fridge has no Power state."); + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java index b52ff310f779b..d232aaf70afda 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java @@ -16,7 +16,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Capability; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; /** * The {@link WasherCapability} @@ -24,7 +24,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class WasherCapability extends Capability { +public class WasherCapability extends AbstractCapability { public enum MonitoringCap { STATE_V2("state"), SOIL_WASH_V2("soilWash"), From 71bea4bcb883dfe447681c446037d15208987f88 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 7 Jun 2022 17:04:54 -0300 Subject: [PATCH 052/130] [lgthinq][feat] Delay Time for Washers, [fix] dynamic temperature unit for refrigerators; DateTime unit for Dryer and Washer time channels Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 3 +- .../handler/LGThinQWasherHandler.java | 4 ++- .../model/fridge/FridgeSnapshot.java | 4 +-- .../model/fridge/v2/FridgeCapabilityV2.java | 15 +++++---- .../model/fridge/v2/FridgeSnapshotV2.java | 32 ++++++++++++++----- .../model/washer/WasherSnapshot.java | 27 ++++++++++++++++ .../main/resources/OH-INF/thing/washer.xml | 1 + 7 files changed, 68 insertions(+), 18 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index edcf50a12ee6f..97725bf5ce357 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; @@ -86,7 +87,7 @@ protected void updateDeviceChannels(DryerSnapshot shot) { updateState(DR_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); updateState(DR_CHANNEL_PROCESS_STATE_ID, new StringType(shot.getProcessState())); updateState(DR_CHANNEL_CHILD_LOCK_ID, new StringType(shot.getChildLock())); - updateState(DR_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateState(DR_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); updateState(DR_CHANNEL_DRY_LEVEL_ID, new StringType(shot.getDryLevel())); updateState(DR_CHANNEL_ERROR_ID, new StringType(shot.getError())); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 29bb2ab646b75..046bb9e91056a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; @@ -149,7 +150,8 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); - updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateState(WM_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); + updateState(WM_CHANNEL_DELAY_TIME_ID, new DateTimeType(shot.getReserveTime())); updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java index ca368339de28f..b1d80faaf071e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java @@ -24,9 +24,9 @@ public interface FridgeSnapshot extends Snapshot { public String getTempUnit(); - public String getFridgeTemp(); + public String getFridgeStrTemp(); - public String getFreezerTemp(); + public String getFreezerStrTemp(); public void loadSnapshot(Object veryRootNode); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java index 68840ebd698b5..a32f887b017a8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.lgthinq.lgservices.model.fridge.v2; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_CELSIUS_SYMBOL; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_FAHRENHEIT_SYMBOL; + import java.util.LinkedHashMap; import java.util.Map; @@ -82,16 +85,16 @@ public void loadCapabilities(Object veryRootNode) { JsonNode fridgeTempFNode = node.path("MonitoringValue").path("fridgeTemp_F").path("valueMapping"); JsonNode freezerTempCNode = node.path("MonitoringValue").path("freezerTemp_C").path("valueMapping"); JsonNode freezerTempFNode = node.path("MonitoringValue").path("freezerTemp_F").path("valueMapping"); - loadTempNode(fridgeTempCNode, fridgeTempCMap); - loadTempNode(fridgeTempFNode, fridgeTempFMap); - loadTempNode(freezerTempCNode, freezerTempCMap); - loadTempNode(freezerTempFNode, freezerTempFMap); + loadTempNode(fridgeTempCNode, fridgeTempCMap, TEMP_UNIT_CELSIUS_SYMBOL); + loadTempNode(fridgeTempFNode, fridgeTempFMap, TEMP_UNIT_FAHRENHEIT_SYMBOL); + loadTempNode(freezerTempCNode, freezerTempCMap, TEMP_UNIT_CELSIUS_SYMBOL); + loadTempNode(freezerTempFNode, freezerTempFMap, TEMP_UNIT_FAHRENHEIT_SYMBOL); } - private void loadTempNode(JsonNode tempNode, Map capMap) { + private void loadTempNode(JsonNode tempNode, Map capMap, String unit) { tempNode.forEach(v -> { // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' - capMap.put(v.path("index").asText(), v.path("label").textValue()); + capMap.put(v.path("index").asText() + " " + unit, v.path("label").textValue() + " " + unit); }); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java index ea0d26c24854e..47e4acde699f9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java @@ -19,6 +19,7 @@ import org.openhab.binding.lgthinq.lgservices.model.Snapshot; import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -35,8 +36,8 @@ public class FridgeSnapshotV2 implements Snapshot, org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeSnapshot { private boolean online; - private String fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; - private String freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; + private Double fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; + private Double freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default @Override @@ -46,29 +47,44 @@ public String getTempUnit() { return tempUnit; } + private String getStrTempWithUnit(Double temp) { + return temp.intValue() + (TEMP_UNIT_CELSIUS.equals(tempUnit) ? " " + TEMP_UNIT_CELSIUS_SYMBOL + : (TEMP_UNIT_FAHRENHEIT).equals(tempUnit) ? " " + TEMP_UNIT_FAHRENHEIT_SYMBOL : ""); + } + + @Override + @JsonIgnore + public String getFridgeStrTemp() { + return getStrTempWithUnit(getFridgeTemp()); + } + + @Override + @JsonIgnore + public String getFreezerStrTemp() { + return getStrTempWithUnit(getFreezerTemp()); + } + public void setTempUnit(String tempUnit) { this.tempUnit = tempUnit; } - @Override @JsonAlias({ "TempRefrigerator" }) @JsonProperty("fridgeTemp") - public String getFridgeTemp() { + public Double getFridgeTemp() { return fridgeTemp; } - public void setFridgeTemp(String fridgeTemp) { + public void setFridgeTemp(Double fridgeTemp) { this.fridgeTemp = fridgeTemp; } - @Override @JsonAlias({ "TempFreezer" }) @JsonProperty("freezerTemp") - public String getFreezerTemp() { + public Double getFreezerTemp() { return freezerTemp; } - public void setFreezerTemp(String freezerTemp) { + public void setFreezerTemp(Double freezerTemp) { this.freezerTemp = freezerTemp; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java index eb3130a182303..84743ee0e0980 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java @@ -44,6 +44,8 @@ public class WasherSnapshot implements Snapshot { private String doorLock = ""; private Double remainingHour = 0.00; private Double remainingMinute = 0.00; + private Double reserveHour = 0.00; + private Double reserveMinute = 0.00; @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) @JsonProperty("courseFL24inchBaseTitan") @@ -102,6 +104,11 @@ public String getRemainingTime() { return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); } + @JsonIgnore + public String getReserveTime() { + return String.format("%02.0f:%02.0f", getReserveHour(), getReserveMinute()); + } + @JsonProperty("remainTimeHour") @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) public Double getRemainingHour() { @@ -122,6 +129,26 @@ public void setRemainingMinute(Double remainingMinute) { this.remainingMinute = remainingMinute; } + @JsonProperty("reserveTimeHour") + @JsonAlias({ "reserveTimeHour", "Reserve_Time_H" }) + public Double getReserveHour() { + return reserveHour; + } + + public void setReserveHour(Double reserveHour) { + this.reserveHour = reserveHour; + } + + @JsonProperty("reserveTimeMinute") + @JsonAlias({ "reserveTimeMinute", "Reserve_Time_M" }) + public Double getReserveMinute() { + return reserveMinute; + } + + public void setReserveMinute(Double reserveMinute) { + this.reserveMinute = reserveMinute; + } + public void setSmartCourse(String smartCourse) { this.smartCourse = smartCourse; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index a340acc12e9ef..031d873663ede 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -22,6 +22,7 @@ + From ff8d1dc5a8211256bac1cd91dae0e0caa4aa60b2 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 26 Oct 2022 16:57:20 -0300 Subject: [PATCH 053/130] [lgthinq][feat] Adding support to AC: AirClean, EnergySaving & AutoDry Signed-off-by: nemerdaud --- .../lgservices/model/ac/ACCapability.java | 84 +++++++++++++++++++ .../lgservices/model/ac/ACSnapshot.java | 33 ++++++++ 2 files changed, 117 insertions(+) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index 054a4fb5ddb00..fd6321d2018ec 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -33,9 +33,21 @@ public class ACCapability extends AbstractCapability { private List supportedOpMode = Collections.emptyList(); private List supportedFanSpeed = Collections.emptyList(); private boolean isJetModeAvailable; + private boolean isAutoDryModeAvailable; + private boolean isEnergySavingAvailable; + private boolean isAirCleanAvailable; private String coolJetModeCommandOn = ""; private String coolJetModeCommandOff = ""; + private String autoDryModeCommandOn = ""; + private String autoDryModeCommandOff = ""; + + private String energySavingModeCommandOn = ""; + private String energySavingModeCommandOff = ""; + + private String airCleanModeCommandOn = ""; + private String airCleanModeCommandOff = ""; + public String getCoolJetModeCommandOff() { return coolJetModeCommandOff; } @@ -88,7 +100,79 @@ public void setJetModeAvailable(boolean jetModeAvailable) { this.isJetModeAvailable = jetModeAvailable; } + public boolean isAutoDryModeAvailable() { + return isAutoDryModeAvailable; + } + + public void setAutoDryModeAvailable(boolean autoDryModeAvailable) { + isAutoDryModeAvailable = autoDryModeAvailable; + } + + public boolean isEnergySavingAvailable() { + return isEnergySavingAvailable; + } + + public void setEnergySavingAvailable(boolean energySavingAvailable) { + isEnergySavingAvailable = energySavingAvailable; + } + + public boolean isAirCleanAvailable() { + return isAirCleanAvailable; + } + + public void setAirCleanAvailable(boolean airCleanAvailable) { + isAirCleanAvailable = airCleanAvailable; + } + public boolean isJetModeAvailable() { return this.isJetModeAvailable; } + + public String getAutoDryModeCommandOn() { + return autoDryModeCommandOn; + } + + public void setAutoDryModeCommandOn(String autoDryModeCommandOn) { + this.autoDryModeCommandOn = autoDryModeCommandOn; + } + + public String getAutoDryModeCommandOff() { + return autoDryModeCommandOff; + } + + public void setAutoDryModeCommandOff(String autoDryModeCommandOff) { + this.autoDryModeCommandOff = autoDryModeCommandOff; + } + + public String getEnergySavingModeCommandOn() { + return energySavingModeCommandOn; + } + + public void setEnergySavingModeCommandOn(String energySavingModeCommandOn) { + this.energySavingModeCommandOn = energySavingModeCommandOn; + } + + public String getEnergySavingModeCommandOff() { + return energySavingModeCommandOff; + } + + public void setEnergySavingModeCommandOff(String energySavingModeCommandOff) { + this.energySavingModeCommandOff = energySavingModeCommandOff; + } + + public String getAirCleanModeCommandOn() { + return airCleanModeCommandOn; + } + + public void setAirCleanModeCommandOn(String airCleanModeCommandOn) { + this.airCleanModeCommandOn = airCleanModeCommandOn; + } + + public String getAirCleanModeCommandOff() { + return airCleanModeCommandOff; + } + + public void setAirCleanModeCommandOff(String airCleanModeCommandOff) { + this.airCleanModeCommandOff = airCleanModeCommandOff; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java index d8bb7e9187d63..f86e0bb255269 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java @@ -39,7 +39,10 @@ public class ACSnapshot implements Snapshot { private boolean coolJetModeOn; + private double airCleanMode; private double coolJetMode; + private double autoDryMode; + private double energySavingMode; private int operationMode; @Nullable @@ -76,6 +79,36 @@ public Double getCoolJetMode() { return coolJetMode; } + @JsonProperty("airState.wMode.airClean") + @JsonAlias("AirClean") + public Double getAirCleanMode() { + return airCleanMode; + } + + @JsonProperty("airState.miscFuncState.autoDry") + @JsonAlias("AutoDry") + public Double getAutoDryMode() { + return autoDryMode; + } + + @JsonProperty("airState.powerSave.basic") + @JsonAlias("PowerSave") + public Double getEnergySavingMode() { + return energySavingMode; + } + + public void setAirCleanMode(double airCleanMode) { + this.airCleanMode = airCleanMode; + } + + public void setAutoDryMode(double autoDryMode) { + this.autoDryMode = autoDryMode; + } + + public void setEnergySavingMode(double energySavingMode) { + this.energySavingMode = energySavingMode; + } + public void setCoolJetMode(Double coolJetMode) { this.coolJetMode = coolJetMode; } From 35f2c0a6d74b233d048deb02dc05b5ad100a649d Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 31 Oct 2022 08:49:39 -0300 Subject: [PATCH 054/130] [lgthinq][feat] Added support to washing/dryer tower Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 15 +++++++------ .../handler/LGThinQWasherHandler.java | 14 +++++++------ .../lgservices/model/SnapshotFactory.java | 4 ++++ .../main/resources/OH-INF/thing/washer.xml | 21 +++++++++++++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 97725bf5ce357..3e1d2e6098c4d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -68,8 +68,16 @@ public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider dryLevelChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_DRY_LEVEL_ID); } + @Override protected DeviceTypes getDeviceType() { - return DeviceTypes.DRYER; + if (THING_TYPE_DRYER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.DRYER; + } else if (THING_TYPE_DRYER_TOWER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.DRYER_TOWER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for DryerTower/Machine"); + } } @Override @@ -164,11 +172,6 @@ public void onDeviceAdded(LGDevice device) { // TODO - handle it. Think if it's needed } - @Override - public String getDeviceId() { - return getThing().getUID().getId(); - } - @Override public String getDeviceAlias() { return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 046bb9e91056a..4d904e3aa4504 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -157,7 +157,14 @@ protected void updateDeviceChannels(WasherSnapshot shot) { @Override protected DeviceTypes getDeviceType() { - return DeviceTypes.WASHING_MACHINE; + if (THING_TYPE_WASHING_MACHINE.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHING_MACHINE; + } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHING_TOWER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); + } } @Override @@ -184,11 +191,6 @@ public void onDeviceAdded(LGDevice device) { // TODO - handle it. Think if it's needed } - @Override - public String getDeviceId() { - return getThing().getUID().getId(); - } - @Override public String getDeviceAlias() { return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index b319279242d5f..29ad4eb31e18f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -143,6 +143,7 @@ public S createFromJson(Map deviceSettings, switch (type) { case AIR_CONDITIONER: return clazz.cast(objectMapper.convertValue(snapMap, ACSnapshot.class)); + case WASHING_TOWER: case WASHING_MACHINE: switch (version) { case V1_0: { @@ -155,6 +156,7 @@ public S createFromJson(Map deviceSettings, return clazz.cast(objectMapper.convertValue(washerDryerMap, WasherSnapshot.class)); } } + case DRYER_TOWER: case DRYER: switch (version) { case V1_0: { @@ -201,8 +203,10 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes throw new IllegalStateException( "Unexpected error. Can't find key node attributes to determine AC API version."); } + case DRYER_TOWER: case DRYER: return LGAPIVerion.V2_0; + case WASHING_TOWER: case WASHING_MACHINE: if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { return LGAPIVerion.V2_0; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 031d873663ede..0ed1ca753e4d0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -13,6 +13,26 @@ LG ThinQ Washing Machine + + + + + + + + + + + + + + + + + + + LG ThinQ Washing Tower + @@ -27,4 +47,5 @@ + From 09b8017aa371bf9ee00808a5f5d569c493951519 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 31 Oct 2022 15:34:53 -0300 Subject: [PATCH 055/130] [lgthinq][feat] Added support to heat pump Signed-off-by: nemerdaud --- .../lgthinq/internal/LGThinQConfiguration.java | 12 ++++++------ .../lgthinq/lgservices/model/SnapshotFactory.java | 6 +++++- .../lgthinq/lgservices/model/ac/ACCapability.java | 10 ++++++++++ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java index e0e93c7da7c87..fa67679d840f2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java @@ -30,7 +30,7 @@ public class LGThinQConfiguration { public String language = ""; public String manualCountry = ""; public String manualLanguage = ""; - public Integer pollingIntervalSec = 0; + public Integer poolingIntervalSec = 0; public String alternativeServer = ""; public LGThinQConfiguration() { @@ -42,7 +42,7 @@ public LGThinQConfiguration(String username, String password, String country, St this.password = password; this.country = country; this.language = language; - this.pollingIntervalSec = pollingIntervalSec; + this.poolingIntervalSec = pollingIntervalSec; this.alternativeServer = alternativeServer; } @@ -68,8 +68,8 @@ public String getLanguage() { return language; } - public Integer getPollingIntervalSec() { - return pollingIntervalSec; + public Integer getPoolingIntervalSec() { + return poolingIntervalSec; } public void setUsername(String username) { @@ -88,8 +88,8 @@ public void setLanguage(String language) { this.language = language; } - public void setPollingIntervalSec(Integer pollingIntervalSec) { - this.pollingIntervalSec = pollingIntervalSec; + public void setPoolingIntervalSec(Integer poolingIntervalSec) { + this.poolingIntervalSec = poolingIntervalSec; } public String getAlternativeServer() { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 29ad4eb31e18f..fc5fae60fb48e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -142,6 +142,7 @@ public S createFromJson(Map deviceSettings, LGAPIVerion version = discoveryAPIVersion(snapMap, type); switch (type) { case AIR_CONDITIONER: + case HEAT_PUMP: return clazz.cast(objectMapper.convertValue(snapMap, ACSnapshot.class)); case WASHING_TOWER: case WASHING_MACHINE: @@ -188,13 +189,16 @@ public S createFromJson(Map deviceSettings, private DeviceTypes getDeviceType(Map rootMap) { Integer deviceTypeId = (Integer) rootMap.get("deviceType"); + // device code is only present in v2 devices snapshot. + String deviceCode = Objects.requireNonNullElse((String) rootMap.get("deviceCode"), ""); Objects.requireNonNull(deviceTypeId, "Unexpected error. deviceType field not present in snapshot schema"); - return DeviceTypes.fromDeviceTypeId(deviceTypeId); + return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); } private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { switch (type) { case AIR_CONDITIONER: + case HEAT_PUMP: if (snapMap.containsKey("airState.opMode")) { return LGAPIVerion.V2_0; } else if (snapMap.containsKey("OpMode")) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index fd6321d2018ec..3b4f13ddfdc6e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -36,6 +36,8 @@ public class ACCapability extends AbstractCapability { private boolean isAutoDryModeAvailable; private boolean isEnergySavingAvailable; private boolean isAirCleanAvailable; + + private boolean isFanSpeedAvailable; private String coolJetModeCommandOn = ""; private String coolJetModeCommandOff = ""; @@ -116,6 +118,14 @@ public void setEnergySavingAvailable(boolean energySavingAvailable) { isEnergySavingAvailable = energySavingAvailable; } + public boolean isFanSpeedAvailable() { + return isFanSpeedAvailable; + } + + public void setFanSpeedAvailable(boolean fanSpeedAvailable) { + isFanSpeedAvailable = fanSpeedAvailable; + } + public boolean isAirCleanAvailable() { return isAirCleanAvailable; } From a789c715b7a6374ca2ea43458b9068877a6db8f6 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 1 Nov 2022 16:35:22 -0300 Subject: [PATCH 056/130] [lgthinq][feat] Added support to Washer & Dryer for the commands Wakeup & RemoteStart Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 90 ++++++++++++++++-- .../handler/LGThinQWasherHandler.java | 88 ++++++++++++++++-- .../lgservices/model/SnapshotFactory.java | 4 +- .../model/washerdryer/CommandCapability.java | 91 +++++++++++++++++++ .../DryerCapability.java | 2 +- .../{dryer => washerdryer}/DryerSnapshot.java | 38 +++++++- .../WasherCapability.java | 2 +- .../WasherSnapshot.java | 39 +++++++- 8 files changed, 334 insertions(+), 20 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{dryer => washerdryer}/DryerCapability.java (98%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{dryer => washerdryer}/DryerSnapshot.java (82%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{washer => washerdryer}/WasherCapability.java (98%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{washer => washerdryer}/WasherSnapshot.java (84%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 3e1d2e6098c4d..18f57f3593813 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -15,9 +15,9 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import java.util.*; -import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -26,12 +26,16 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; -import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.StateOption; import org.slf4j.Logger; @@ -52,7 +56,10 @@ public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); stateDescriptionProvider.setStateOptions(dryLevelChannelUUID, options); } + if (isLinked(remoteStartChannelUUID)) { + List options = new ArrayList<>(); + options.add(new StateOption("REMOTE_START_OFF", "OFF")); + options.add(new StateOption("REMOTE_START_ON", "ON")); + stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); + } + if (getThing().getChannel(standbyChannelUUID) == null) { + createDynChannel(WM_CHANNEL_STAND_BY_ID, standbyChannelUUID, "Switch"); + } + if (isLinked(standbyChannelUUID)) { + List options = new ArrayList<>(); + options.add(new StateOption("STANDBY_OFF", "OFF")); + options.add(new StateOption("STANDBY_ON", "ON")); + stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); + } } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 4d904e3aa4504..00d13bc21a22c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -18,6 +18,7 @@ import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -26,12 +27,16 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.StateOption; import org.slf4j.Logger; @@ -53,6 +58,10 @@ public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler options = new ArrayList<>(); + options.add(new StateOption("REMOTE_START_OFF", "OFF")); + options.add(new StateOption("REMOTE_START_ON", "ON")); + stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); + } + if (getThing().getChannel(standbyChannelUUID) == null) { + createDynChannel(WM_CHANNEL_STAND_BY_ID, standbyChannelUUID, "Switch"); + } + if (isLinked(standbyChannelUUID)) { + List options = new ArrayList<>(); + options.add(new StateOption("STANDBY_OFF", "OFF")); + options.add(new StateOption("STANDBY_ON", "ON")); + stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); + } } @Override @@ -143,6 +169,7 @@ protected Logger getLogger() { @Override protected void updateDeviceChannels(WasherSnapshot shot) { + lastShot = shot; updateState(CHANNEL_POWER_ID, (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); @@ -153,6 +180,27 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); updateState(WM_CHANNEL_DELAY_TIME_ID, new DateTimeType(shot.getReserveTime())); updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); + updateState(WM_CHANNEL_STAND_BY_ID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); + Channel remoteStartChannel = getThing().getChannel(remoteStartChannelUUID); + // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. + if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { + ThingHandlerCallback callback = getCallback(); + if (remoteStartChannel == null && callback != null) { + ChannelBuilder builder = getCallback().createChannelBuilder(remoteStartChannelUUID, + new ChannelTypeUID(BINDING_ID, WM_CHANNEL_REMOTE_START_ID)); + Channel newChannel = builder.build(); + ThingBuilder thingBuilder = editThing(); + updateThing(thingBuilder.withChannel(newChannel).build()); + } + if (isLinked(remoteStartChannelUUID)) { + updateState(WM_CHANNEL_REMOTE_START_ID, new StringType(shot.getRemoteStart())); + } + } else { + if (remoteStartChannel != null) { + ThingBuilder builder = editThing().withoutChannels(remoteStartChannel); + updateThing(builder.build()); + } + } } @Override @@ -171,12 +219,40 @@ protected DeviceTypes getDeviceType() { protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { Command command = params.command; switch (params.channelUID) { - case CHANNEL_POWER_ID: { + case WM_CHANNEL_REMOTE_START_ID: { + if (command instanceof StringType) { + if ("START".equalsIgnoreCase(command.toString())) { + if (lastShot != null && !lastShot.isStandBy()) { + lgThinqWMApiClientService.remoteStart(getBridgeId(), getDeviceId()); + } else { + logger.warn( + "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); + } + } else { + logger.warn( + "Command [{}] sent to Remote Start channel is invalid. Only command START is valid.", + command); + } + } else { + logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); + } + break; + } + case WM_CHANNEL_STAND_BY_ID: { if (command instanceof OnOffType) { - lgThinqWMApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + if (OnOffType.OFF.equals(command)) { + if (lastShot == null || !lastShot.isStandBy()) { + logger.warn( + "Command OFF was sent to StandBy channel, but the state of the WM is unknown or already waked up. Ignoring"); + break; + } + lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId()); + } else { + logger.warn("Command [{}] sent to StandBy channel is invalid. Only command OFF is valid.", + command); + } } else { - logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); } break; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index fc5fae60fb48e..9bf79c5a61b5f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -26,9 +26,9 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java new file mode 100644 index 0000000000000..ac615e7c2b034 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.ModelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link CommandCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CommandCapability { + private boolean isPowerCommandsAvailable = false; + private String powerOffCommand = ""; + private String stopCommand = ""; + private String wakeUpCommand = ""; + private static final Logger logger = LoggerFactory.getLogger(CommandCapability.class); + + public void loadCommands(JsonNode rootNode) throws LGThinqApiException { + LGAPIVerion version = ModelUtils.discoveryAPIVersion(rootNode); + switch (version) { + case V1_0: + logger.warn("Version {} for commands of Dryer/Washers not supported for this binding.", + version.getValue()); + return; + case V2_0: + JsonNode wifiNode = rootNode.path("ControlWifi"); + if (wifiNode.isMissingNode()) { + logger.warn( + "Dryer/Washer is missing ControlWifi node in the model. Commands are not supported for this model."); + return; + } + JsonNode wmOffNode = wifiNode.path("WMOff"); + JsonNode wmStopNode = wifiNode.path("WMStop"); + JsonNode wmWakeUpNode = wifiNode.path("WMWakeup"); + boolean isOffPresent = !wmOffNode.isMissingNode(); + boolean isStopPresent = !wmStopNode.isMissingNode(); + boolean isWakeUpPresent = !wmWakeUpNode.isMissingNode(); + if (isOffPresent || isStopPresent || isWakeUpPresent) { + isPowerCommandsAvailable = true; + powerOffCommand = isOffPresent + ? wmOffNode.path("data").path("washerDryer").path("controlDataType").textValue() + : ""; + stopCommand = isStopPresent + ? wmStopNode.path("data").path("washerDryer").path("controlDataType").textValue() + : ""; + wakeUpCommand = isWakeUpPresent + ? wmWakeUpNode.path("data").path("washerDryer").path("controlDataType").textValue() + : ""; + } + } + } + + public boolean isPowerCommandsAvailable() { + return isPowerCommandsAvailable; + } + + public void setPowerCommandsAvailable(boolean powerCommandsAvailable) { + isPowerCommandsAvailable = powerCommandsAvailable; + } + + public String getPowerOffCommand() { + return powerOffCommand; + } + + public String getStopCommand() { + return stopCommand; + } + + public String getWakeUpCommand() { + return wakeUpCommand; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java similarity index 98% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java index bb8aa208a21d7..d29b07e1f0124 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.dryer; +package org.openhab.binding.lgthinq.lgservices.model.washerdryer; import java.util.LinkedHashMap; import java.util.Map; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java similarity index 82% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java index f3972e10620d6..812340b2fec51 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.dryer; +package org.openhab.binding.lgthinq.lgservices.model.washerdryer; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; @@ -45,6 +45,12 @@ public class DryerSnapshot implements Snapshot { private Double remainingMinute = 0.00; private String dryLevel = ""; private String error = ""; + private String remoteStart = ""; + private boolean remoteStartEnabled = false; + + private String standByStatus = ""; + + private boolean standBy = false; @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) @JsonProperty("courseDryer24inchBase") @@ -164,4 +170,34 @@ public void setState(String state) { powerState = DevicePowerState.DV_POWER_ON; } } + + public boolean isRemoteStartEnabled() { + return remoteStartEnabled; + } + + @JsonProperty("remoteStart") + @JsonAlias({ "RemoteStart" }) + public String getRemoteStart() { + return remoteStart; + } + + public void setRemoteStart(String remoteStart) { + this.remoteStart = remoteStart; + remoteStartEnabled = remoteStart.contains("ON"); + } + + public String getStandByStatus() { + return standByStatus; + } + + @JsonProperty("standby") + @JsonAlias({ "Standby" }) + public void setStandByStatus(String standByStatus) { + this.standByStatus = standByStatus; + standBy = standByStatus.contains("ON"); + } + + public boolean isStandBy() { + return standBy; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java similarity index 98% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java index d232aaf70afda..05535d455c3ea 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.washer; +package org.openhab.binding.lgthinq.lgservices.model.washerdryer; import java.util.LinkedHashMap; import java.util.Map; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java similarity index 84% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java index 84743ee0e0980..004474dbf76fd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.washer; +package org.openhab.binding.lgthinq.lgservices.model.washerdryer; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; @@ -47,6 +47,13 @@ public class WasherSnapshot implements Snapshot { private Double reserveHour = 0.00; private Double reserveMinute = 0.00; + private String remoteStart = ""; + private boolean remoteStartEnabled = false; + + private String standByStatus = ""; + + private boolean standBy = false; + @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) @JsonProperty("courseFL24inchBaseTitan") public String getCourse() { @@ -181,4 +188,34 @@ public void setState(String state) { powerState = DevicePowerState.DV_POWER_ON; } } + + public boolean isRemoteStartEnabled() { + return remoteStartEnabled; + } + + @JsonProperty("remoteStart") + @JsonAlias({ "RemoteStart" }) + public String getRemoteStart() { + return remoteStart; + } + + public void setRemoteStart(String remoteStart) { + this.remoteStart = remoteStart; + remoteStartEnabled = remoteStart.contains("ON"); + } + + public String getStandByStatus() { + return standByStatus; + } + + @JsonProperty("standby") + @JsonAlias({ "Standby" }) + public void setStandByStatus(String standByStatus) { + this.standByStatus = standByStatus; + standBy = standByStatus.contains("ON"); + } + + public boolean isStandBy() { + return standBy; + } } From 81ebc54e94e6dac31ccfc0b4b52d6bdb878cddaf Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Sun, 26 Feb 2023 14:33:45 -0300 Subject: [PATCH 057/130] [lgthinq][feat] Refactory and WasherDryer commands Signed-off-by: nemerdaud --- .../internal/LGThinQConfiguration.java | 102 ------ .../internal/ThinqChannelTypeProvider.java | 68 ++++ .../lgthinq/internal/ThinqHandler.java | 61 ++++ .../internal/handler/LGThinQDryerHandler.java | 268 ---------------- .../handler/LGThinQWasherHandler.java | 299 ------------------ ...ility.java => AbstractJsonCapability.java} | 0 .../lgthinq/lgservices/model/Capability.java | 30 -- .../lgservices/model/SnapshotFactory.java | 68 ++-- .../lgservices/model/ac/ACCapability.java | 188 ----------- .../lgservices/model/ac/ACFanSpeed.java | 91 ------ .../lgthinq/lgservices/model/ac/ACOpMode.java | 89 ------ .../lgservices/model/ac/ACSnapshot.java | 186 ----------- .../lgservices/model/ac/ACTargetTmp.java | 93 ------ .../washerdryer/CommandDefinition.java | 51 +++ .../devices/washerdryer/CourseDefinition.java | 64 ++++ .../devices/washerdryer/CourseFunction.java | 63 ++++ .../washerdryer/CourseType.java} | 23 +- .../WasherDryerPropertyDiscovery.java} | 16 +- .../model/fridge/FridgeCapability.java | 37 --- .../model/fridge/FridgeFactory.java | 47 --- .../model/fridge/v2/FridgeCapabilityV2.java | 100 ------ .../model/fridge/v2/FridgeSnapshotV2.java | 114 ------- .../model/washerdryer/CommandCapability.java | 91 ------ .../model/washerdryer/DryerCapability.java | 129 -------- .../model/washerdryer/DryerSnapshot.java | 203 ------------ .../model/washerdryer/WasherCapability.java | 153 --------- .../model/washerdryer/WasherSnapshot.java | 221 ------------- .../main/resources/OH-INF/thing/washer.xml | 6 +- 28 files changed, 369 insertions(+), 2492 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqChannelTypeProvider.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{AbstractCapability.java => AbstractJsonCapability.java} (100%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CommandDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{fridge/FridgeSnapshot.java => devices/washerdryer/CourseType.java} (52%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{Snapshot.java => devices/washerdryer/WasherDryerPropertyDiscovery.java} (55%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java deleted file mode 100644 index fa67679d840f2..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGThinQConfiguration} class contains fields mapping thing configuration parameters. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinQConfiguration { - /** - * Sample configuration parameters. Replace with your own. - */ - public String username = ""; - public String password = ""; - public String country = ""; - public String language = ""; - public String manualCountry = ""; - public String manualLanguage = ""; - public Integer poolingIntervalSec = 0; - public String alternativeServer = ""; - - public LGThinQConfiguration() { - } - - public LGThinQConfiguration(String username, String password, String country, String language, - Integer pollingIntervalSec, String alternativeServer) { - this.username = username; - this.password = password; - this.country = country; - this.language = language; - this.poolingIntervalSec = pollingIntervalSec; - this.alternativeServer = alternativeServer; - } - - public String getUsername() { - return username; - } - - public String getPassword() { - return password; - } - - public String getCountry() { - if ("--".equals(country)) { - return manualCountry; - } - return country; - } - - public String getLanguage() { - if ("--".equals(language)) { - return manualLanguage; - } - return language; - } - - public Integer getPoolingIntervalSec() { - return poolingIntervalSec; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setCountry(String country) { - this.country = country; - } - - public void setLanguage(String language) { - this.language = language; - } - - public void setPoolingIntervalSec(Integer poolingIntervalSec) { - this.poolingIntervalSec = poolingIntervalSec; - } - - public String getAlternativeServer() { - return alternativeServer; - } - - public void setAlternativeServer(String alternativeServer) { - this.alternativeServer = alternativeServer; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqChannelTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqChannelTypeProvider.java new file mode 100644 index 0000000000000..e9326772118b4 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqChannelTypeProvider.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import java.util.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.handler.LGThinQAbstractDeviceHandler; +import org.openhab.core.thing.binding.ThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeProvider; +import org.openhab.core.thing.type.ChannelTypeUID; + +/** + * Provider class to provide channel types for user configured channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ThinqChannelTypeProvider implements ChannelTypeProvider, ThingHandlerService { + + private final Map map = new HashMap<>(); + private @Nullable ThingHandler handler; + + @Override + public Collection getChannelTypes(@Nullable final Locale locale) { + return Collections.unmodifiableCollection(map.values()); + } + + @Override + public @Nullable ChannelType getChannelType(final ChannelTypeUID channelTypeUID, @Nullable final Locale locale) { + return map.get(channelTypeUID); + } + + /** + * Add a channel type for a user configured channel. + * + * @param channelType channelType + */ + public void addChannelType(final ChannelType channelType) { + map.put(channelType.getUID(), channelType); + } + + @Override + public void setThingHandler(ThingHandler handler) { + if (handler instanceof LGThinQAbstractDeviceHandler) { + this.handler = handler; + ((LGThinQAbstractDeviceHandler) handler).setChannelTypeProvider(this); + } + } + + @Override + public @Nullable ThingHandler getThingHandler() { + return handler; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqHandler.java new file mode 100644 index 0000000000000..0829e7a825a29 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqHandler.java @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import java.util.Collection; +import java.util.Collections; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; + +/** + * The {@link ThinqHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ThinqHandler extends BaseThingHandler { + private @Nullable ThinqChannelTypeProvider channelTypeProvider; + + /** + * Creates a new instance of this class for the {@link Thing}. + * + * @param thing the thing that should be handled, not null + */ + public ThinqHandler(Thing thing) { + super(thing); + } + + @Override + public void initialize() { + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } + + @Override + public Collection> getServices() { + return Collections.singleton(ThinqChannelTypeProvider.class); + } + + public void setChannelTypeProvider(ThinqChannelTypeProvider thinqChannelTypeProvider) { + this.channelTypeProvider = thinqChannelTypeProvider; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java deleted file mode 100644 index 18f57f3593813..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.handler; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import java.util.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerCapability; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerCallback; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.types.Command; -import org.openhab.core.types.StateOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinQDryerHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler { - - private final ChannelUID stateChannelUUID; - private final ChannelUID processStateChannelUUID; - private final ChannelUID dryLevelChannelUUID; - private final ChannelUID errorChannelUUID; - private final ChannelUID courseChannelUUID; - private final ChannelUID smartCourseChannelUUID; - private final ChannelUID remoteStartChannelUUID; - private final ChannelUID standbyChannelUUID; - @Nullable - private DryerSnapshot lastShot; - private final Logger logger = LoggerFactory.getLogger(LGThinQDryerHandler.class); - @NonNullByDefault - private final LGThinQDRApiClientService lgThinqDRApiClientService; - - public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing, stateDescriptionProvider); - lgThinqDRApiClientService = LGThinQDRApiV2ClientServiceImpl.getInstance(); - stateChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_STATE_ID); - courseChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_COURSE_ID); - smartCourseChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_SMART_COURSE_ID); - processStateChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_PROCESS_STATE_ID); - errorChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_ERROR_ID); - dryLevelChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_DRY_LEVEL_ID); - remoteStartChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_ID); - standbyChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STAND_BY_ID); - } - - @Override - protected DeviceTypes getDeviceType() { - if (THING_TYPE_DRYER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.DRYER; - } else if (THING_TYPE_DRYER_TOWER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.DRYER_TOWER; - } else { - throw new IllegalArgumentException( - "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for DryerTower/Machine"); - } - } - - @Override - public void initialize() { - logger.debug("Initializing Thinq thing."); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - - @Override - protected void updateDeviceChannels(DryerSnapshot shot) { - lastShot = shot; - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); - updateState(DR_CHANNEL_STATE_ID, new StringType(shot.getState())); - updateState(DR_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); - updateState(DR_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); - updateState(DR_CHANNEL_PROCESS_STATE_ID, new StringType(shot.getProcessState())); - updateState(DR_CHANNEL_CHILD_LOCK_ID, new StringType(shot.getChildLock())); - updateState(DR_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); - updateState(DR_CHANNEL_DRY_LEVEL_ID, new StringType(shot.getDryLevel())); - updateState(DR_CHANNEL_ERROR_ID, new StringType(shot.getError())); - updateState(WM_CHANNEL_STAND_BY_ID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); - Channel remoteStartChannel = getThing().getChannel(remoteStartChannelUUID); - // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. - if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { - ThingHandlerCallback callback = getCallback(); - if (remoteStartChannel == null && callback != null) { - ChannelBuilder builder = getCallback().createChannelBuilder(remoteStartChannelUUID, - new ChannelTypeUID(BINDING_ID, WM_CHANNEL_REMOTE_START_ID)); - Channel newChannel = builder.build(); - ThingBuilder thingBuilder = editThing(); - updateThing(thingBuilder.withChannel(newChannel).build()); - } - if (isLinked(remoteStartChannelUUID)) { - updateState(WM_CHANNEL_REMOTE_START_ID, new StringType(shot.getRemoteStart())); - } - } else { - if (remoteStartChannel != null) { - ThingBuilder builder = editThing().withoutChannels(remoteStartChannel); - updateThing(builder.build()); - } - } - } - - @Override - protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { - Command command = params.command; - switch (params.channelUID) { - case WM_CHANNEL_REMOTE_START_ID: { - if (command instanceof StringType) { - if ("START".equalsIgnoreCase(command.toString())) { - if (lastShot != null && !lastShot.isStandBy()) { - lgThinqDRApiClientService.remoteStart(getBridgeId(), getDeviceId()); - } else { - logger.warn( - "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); - } - } else { - logger.warn( - "Command [{}] sent to Remote Start channel is invalid. Only command START is valid.", - command); - } - } else { - logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); - } - break; - } - case WM_CHANNEL_STAND_BY_ID: { - if (command instanceof OnOffType) { - if (OnOffType.OFF.equals(command)) { - if (lastShot == null || !lastShot.isStandBy()) { - logger.warn( - "Command OFF was sent to StandBy channel, but the state of the WM is unknown or already waked up. Ignoring"); - break; - } - lgThinqDRApiClientService.wakeUp(getBridgeId(), getDeviceId()); - } else { - logger.warn("Command [{}] sent to StandBy channel is invalid. Only command OFF is valid.", - command); - } - } else { - logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); - } - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); - } - } - } - - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - DryerCapability drCap = getCapabilities(); - if (isLinked(stateChannelUUID)) { - List options = new ArrayList<>(); - // invert key/value - drCap.getState().forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_STATE, v)))); - stateDescriptionProvider.setStateOptions(stateChannelUUID, options); - } - if (isLinked(courseChannelUUID)) { - List options = new ArrayList<>(); - drCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(courseChannelUUID, options); - } - if (isLinked(smartCourseChannelUUID)) { - List options = new ArrayList<>(); - drCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); - } - if (isLinked(processStateChannelUUID)) { - List options = new ArrayList<>(); - drCap.getProcessStates() - .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_PROCESS_STATE, v)))); - stateDescriptionProvider.setStateOptions(processStateChannelUUID, options); - } - if (isLinked(errorChannelUUID)) { - List options = new ArrayList<>(); - drCap.getErrors().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(errorChannelUUID, options); - } - if (isLinked(dryLevelChannelUUID)) { - List options = new ArrayList<>(); - drCap.getDryLevels() - .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); - stateDescriptionProvider.setStateOptions(dryLevelChannelUUID, options); - } - if (isLinked(remoteStartChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("REMOTE_START_OFF", "OFF")); - options.add(new StateOption("REMOTE_START_ON", "ON")); - stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); - } - if (getThing().getChannel(standbyChannelUUID) == null) { - createDynChannel(WM_CHANNEL_STAND_BY_ID, standbyChannelUUID, "Switch"); - } - if (isLinked(standbyChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("STANDBY_OFF", "OFF")); - options.add(new StateOption("STANDBY_ON", "ON")); - stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); - } - } - - @Override - public LGThinQApiClientService getLgThinQAPIClientService() { - return lgThinqDRApiClientService; - } - - @Override - protected Logger getLogger() { - return logger; - } - - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } - - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } - - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void onDeviceDisconnected() { - // TODO - HANDLE IT, Think if it's needed - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java deleted file mode 100644 index 00d13bc21a22c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.handler; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import java.util.*; -import java.util.concurrent.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherCapability; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerCallback; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.types.Command; -import org.openhab.core.types.StateOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinQWasherHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler { - - private final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; - private final ChannelUID stateChannelUUID; - private final ChannelUID courseChannelUUID; - private final ChannelUID smartCourseChannelUUID; - private final ChannelUID downloadedCourseChannelUUID; - private final ChannelUID temperatureChannelUUID; - private final ChannelUID doorLockChannelUUID; - private final ChannelUID remoteStartChannelUUID; - private final ChannelUID standbyChannelUUID; - @Nullable - private WasherSnapshot lastShot; - - private final Logger logger = LoggerFactory.getLogger(LGThinQWasherHandler.class); - @NonNullByDefault - private final LGThinQWMApiClientService lgThinqWMApiClientService; - - // *** Long running isolated threadpools. - private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); - - private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); - - @NonNullByDefault - - public LGThinQWasherHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing, stateDescriptionProvider); - this.stateDescriptionProvider = stateDescriptionProvider; - lgThinqWMApiClientService = LGThinQWMApiV2ClientServiceImpl.getInstance(); - stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); - courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); - smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); - downloadedCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_DOWNLOADED_COURSE_ID); - temperatureChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_TEMP_LEVEL_ID); - doorLockChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_DOOR_LOCK_ID); - remoteStartChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_ID); - standbyChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STAND_BY_ID); - } - - static class AsyncCommandParams { - final String channelUID; - final Command command; - - public AsyncCommandParams(String channelUUID, Command command) { - this.channelUID = channelUUID; - this.command = command; - } - } - - @Override - public void initialize() { - logger.debug("Initializing Thinq thing. Washer Thing v0.1"); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - WasherCapability wmCap = getCapabilities(); - if (isLinked(stateChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_STATE, k)))); - stateDescriptionProvider.setStateOptions(stateChannelUUID, options); - } - if (isLinked(courseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(courseChannelUUID, options); - } - if (isLinked(smartCourseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); - } - if (isLinked(downloadedCourseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(downloadedCourseChannelUUID, options); - } - if (isLinked(temperatureChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getTemperature() - .forEach((k, v) -> options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_TEMPERATURE, k)))); - stateDescriptionProvider.setStateOptions(temperatureChannelUUID, options); - } - if (isLinked(doorLockChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("0", "Unlocked")); - options.add(new StateOption("1", "Locked")); - stateDescriptionProvider.setStateOptions(doorLockChannelUUID, options); - } - if (isLinked(remoteStartChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("REMOTE_START_OFF", "OFF")); - options.add(new StateOption("REMOTE_START_ON", "ON")); - stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); - } - if (getThing().getChannel(standbyChannelUUID) == null) { - createDynChannel(WM_CHANNEL_STAND_BY_ID, standbyChannelUUID, "Switch"); - } - if (isLinked(standbyChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("STANDBY_OFF", "OFF")); - options.add(new StateOption("STANDBY_ON", "ON")); - stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); - } - } - - @Override - public LGThinQApiClientService getLgThinQAPIClientService() { - return lgThinqWMApiClientService; - } - - @Override - protected Logger getLogger() { - return logger; - } - - @Override - protected void updateDeviceChannels(WasherSnapshot shot) { - lastShot = shot; - updateState(CHANNEL_POWER_ID, - (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); - updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); - updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); - updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); - updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); - updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); - updateState(WM_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); - updateState(WM_CHANNEL_DELAY_TIME_ID, new DateTimeType(shot.getReserveTime())); - updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); - updateState(WM_CHANNEL_STAND_BY_ID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); - Channel remoteStartChannel = getThing().getChannel(remoteStartChannelUUID); - // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. - if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { - ThingHandlerCallback callback = getCallback(); - if (remoteStartChannel == null && callback != null) { - ChannelBuilder builder = getCallback().createChannelBuilder(remoteStartChannelUUID, - new ChannelTypeUID(BINDING_ID, WM_CHANNEL_REMOTE_START_ID)); - Channel newChannel = builder.build(); - ThingBuilder thingBuilder = editThing(); - updateThing(thingBuilder.withChannel(newChannel).build()); - } - if (isLinked(remoteStartChannelUUID)) { - updateState(WM_CHANNEL_REMOTE_START_ID, new StringType(shot.getRemoteStart())); - } - } else { - if (remoteStartChannel != null) { - ThingBuilder builder = editThing().withoutChannels(remoteStartChannel); - updateThing(builder.build()); - } - } - } - - @Override - protected DeviceTypes getDeviceType() { - if (THING_TYPE_WASHING_MACHINE.equals(getThing().getThingTypeUID())) { - return DeviceTypes.WASHING_MACHINE; - } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.WASHING_TOWER; - } else { - throw new IllegalArgumentException( - "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); - } - } - - @Override - protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { - Command command = params.command; - switch (params.channelUID) { - case WM_CHANNEL_REMOTE_START_ID: { - if (command instanceof StringType) { - if ("START".equalsIgnoreCase(command.toString())) { - if (lastShot != null && !lastShot.isStandBy()) { - lgThinqWMApiClientService.remoteStart(getBridgeId(), getDeviceId()); - } else { - logger.warn( - "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); - } - } else { - logger.warn( - "Command [{}] sent to Remote Start channel is invalid. Only command START is valid.", - command); - } - } else { - logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); - } - break; - } - case WM_CHANNEL_STAND_BY_ID: { - if (command instanceof OnOffType) { - if (OnOffType.OFF.equals(command)) { - if (lastShot == null || !lastShot.isStandBy()) { - logger.warn( - "Command OFF was sent to StandBy channel, but the state of the WM is unknown or already waked up. Ignoring"); - break; - } - lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId()); - } else { - logger.warn("Command [{}] sent to StandBy channel is invalid. Only command OFF is valid.", - command); - } - } else { - logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); - } - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); - } - } - } - - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } - - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } - - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } - - /** - * Put the channels in default state if the device is disconnected or gone. - */ - @Override - public void onDeviceDisconnected() { - updateState(CHANNEL_POWER_ID, OnOffType.OFF); - updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); - updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); - updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); - updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType("NOT_SELECTED")); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractJsonCapability.java similarity index 100% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractJsonCapability.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java deleted file mode 100644 index a36bcfaea25d7..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; - -import java.util.List; - -/** - * The {@link Capability} - * - * @author Nemer Daud - Initial contribution - */ -public interface Capability { - MonitoringResultFormat getMonitoringDataFormat(); - - void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat); - - List getMonitoringBinaryProtocol(); - - void setMonitoringBinaryProtocol(List monitoringBinaryProtocol); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 9bf79c5a61b5f..c68b6e141fe6b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,7 +12,8 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.REFRIGERATOR_SNAPSHOT_NODE_V2; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; import java.beans.BeanInfo; import java.beans.IntrospectionException; @@ -25,10 +26,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; @@ -61,15 +61,14 @@ public static SnapshotFactory getInstance() { * @return returns Snapshot implementation based on device type provided * @throws LGThinqApiException any error. */ - public S createFromBinary(String binaryData, List prot, + public S createFromBinary(String binaryData, List prot, Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { try { byte[] data = binaryData.getBytes(); BeanInfo beanInfo = Introspector.getBeanInfo(clazz); S snap = clazz.getConstructor().newInstance(); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); - for (Iterator it = prot.iterator(); it.hasNext();) { - MonitoringBinaryProtocol protField = it.next(); + for (MonitoringBinaryProtocol protField : prot) { String fName = protField.fieldName; for (PropertyDescriptor property : pds) { // all attributes of class. @@ -118,8 +117,8 @@ public S createFromBinary(String binaryData, List S createFromJson(String snapshotDataJson, DeviceTypes deviceType, Class clazz) - throws LGThinqUnmarshallException, LGThinqApiException { + public S createFromJson(String snapshotDataJson, DeviceTypes deviceType, + Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { try { Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { }); @@ -132,7 +131,7 @@ public S createFromJson(String snapshotDataJson, DeviceType } } - public S createFromJson(Map deviceSettings, Class clazz) + public S createFromJson(Map deviceSettings, Class clazz) throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); Map snapMap = ((Map) deviceSettings.get("snapshot")); @@ -140,21 +139,32 @@ public S createFromJson(Map deviceSettings, throw new LGThinqApiException("snapshot node not present in device monitoring result."); } LGAPIVerion version = discoveryAPIVersion(snapMap, type); + return getSnapshot(clazz, type, snapMap, version); + } + + private S getSnapshot(Class clazz, DeviceTypes type, + Map snapMap, LGAPIVerion version) { + S snap; switch (type) { case AIR_CONDITIONER: case HEAT_PUMP: - return clazz.cast(objectMapper.convertValue(snapMap, ACSnapshot.class)); + snap = clazz.cast(objectMapper.convertValue(snapMap, ACCanonicalSnapshot.class)); + snap.setRawData(snapMap); + return snap; case WASHING_TOWER: - case WASHING_MACHINE: + case WASHERDRYER_MACHINE: switch (version) { case V1_0: { - return clazz.cast(objectMapper.convertValue(snapMap, WasherSnapshot.class)); + snap = clazz.cast(objectMapper.convertValue(snapMap, WasherDryerSnapshot.class)); + snap.setRawData(snapMap); } case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), "washerDryer node must be present in the snapshot"); - return clazz.cast(objectMapper.convertValue(washerDryerMap, WasherSnapshot.class)); + snap = clazz.cast(objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class)); + snap.setRawData(washerDryerMap); + return snap; } } case DRYER_TOWER: @@ -164,10 +174,12 @@ public S createFromJson(Map deviceSettings, throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); } case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), "washerDryer node must be present in the snapshot"); - return clazz.cast(objectMapper.convertValue(washerDryerMap, DryerSnapshot.class)); + snap = clazz.cast(objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class)); + snap.setRawData(snapMap); + return snap; } } case REFRIGERATOR: @@ -176,10 +188,12 @@ public S createFromJson(Map deviceSettings, throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); } case V2_0: { - Map refMap = Objects.requireNonNull( - (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), + Map refMap = Objects.requireNonNull( + (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), "washerDryer node must be present in the snapshot"); - return clazz.cast(objectMapper.convertValue(refMap, FridgeSnapshotV2.class)); + snap = clazz.cast(objectMapper.convertValue(refMap, FridgeCanonicalSnapshot.class)); + snap.setRawData(snapMap); + return snap; } } default: @@ -205,27 +219,27 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes return LGAPIVerion.V1_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine AC API version."); + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } case DRYER_TOWER: case DRYER: return LGAPIVerion.V2_0; case WASHING_TOWER: - case WASHING_MACHINE: + case WASHERDRYER_MACHINE: if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { return LGAPIVerion.V2_0; } else if (snapMap.containsKey("State")) { return LGAPIVerion.V1_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine AC API version."); + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } case REFRIGERATOR: if (snapMap.containsKey(REFRIGERATOR_SNAPSHOT_NODE_V2)) { return LGAPIVerion.V2_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine AC API version."); + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java deleted file mode 100644 index 3b4f13ddfdc6e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; - -/** - * The {@link ACCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ACCapability extends AbstractCapability { - - private Map opMod = Collections.emptyMap(); - private Map fanSpeed = Collections.emptyMap(); - - private List supportedOpMode = Collections.emptyList(); - private List supportedFanSpeed = Collections.emptyList(); - private boolean isJetModeAvailable; - private boolean isAutoDryModeAvailable; - private boolean isEnergySavingAvailable; - private boolean isAirCleanAvailable; - - private boolean isFanSpeedAvailable; - private String coolJetModeCommandOn = ""; - private String coolJetModeCommandOff = ""; - - private String autoDryModeCommandOn = ""; - private String autoDryModeCommandOff = ""; - - private String energySavingModeCommandOn = ""; - private String energySavingModeCommandOff = ""; - - private String airCleanModeCommandOn = ""; - private String airCleanModeCommandOff = ""; - - public String getCoolJetModeCommandOff() { - return coolJetModeCommandOff; - } - - public void setCoolJetModeCommandOff(String coolJetModeCommandOff) { - this.coolJetModeCommandOff = coolJetModeCommandOff; - } - - public String getCoolJetModeCommandOn() { - return coolJetModeCommandOn; - } - - public void setCoolJetModeCommandOn(String coolJetModeCommandOn) { - this.coolJetModeCommandOn = coolJetModeCommandOn; - } - - public Map getOpMod() { - return opMod; - } - - public void setOpMod(Map opMod) { - this.opMod = opMod; - } - - public Map getFanSpeed() { - return fanSpeed; - } - - public void setFanSpeed(Map fanSpeed) { - this.fanSpeed = fanSpeed; - } - - public List getSupportedOpMode() { - return supportedOpMode; - } - - public void setSupportedOpMode(List supportedOpMode) { - this.supportedOpMode = supportedOpMode; - } - - public List getSupportedFanSpeed() { - return supportedFanSpeed; - } - - public void setSupportedFanSpeed(List supportedFanSpeed) { - this.supportedFanSpeed = supportedFanSpeed; - } - - public void setJetModeAvailable(boolean jetModeAvailable) { - this.isJetModeAvailable = jetModeAvailable; - } - - public boolean isAutoDryModeAvailable() { - return isAutoDryModeAvailable; - } - - public void setAutoDryModeAvailable(boolean autoDryModeAvailable) { - isAutoDryModeAvailable = autoDryModeAvailable; - } - - public boolean isEnergySavingAvailable() { - return isEnergySavingAvailable; - } - - public void setEnergySavingAvailable(boolean energySavingAvailable) { - isEnergySavingAvailable = energySavingAvailable; - } - - public boolean isFanSpeedAvailable() { - return isFanSpeedAvailable; - } - - public void setFanSpeedAvailable(boolean fanSpeedAvailable) { - isFanSpeedAvailable = fanSpeedAvailable; - } - - public boolean isAirCleanAvailable() { - return isAirCleanAvailable; - } - - public void setAirCleanAvailable(boolean airCleanAvailable) { - isAirCleanAvailable = airCleanAvailable; - } - - public boolean isJetModeAvailable() { - return this.isJetModeAvailable; - } - - public String getAutoDryModeCommandOn() { - return autoDryModeCommandOn; - } - - public void setAutoDryModeCommandOn(String autoDryModeCommandOn) { - this.autoDryModeCommandOn = autoDryModeCommandOn; - } - - public String getAutoDryModeCommandOff() { - return autoDryModeCommandOff; - } - - public void setAutoDryModeCommandOff(String autoDryModeCommandOff) { - this.autoDryModeCommandOff = autoDryModeCommandOff; - } - - public String getEnergySavingModeCommandOn() { - return energySavingModeCommandOn; - } - - public void setEnergySavingModeCommandOn(String energySavingModeCommandOn) { - this.energySavingModeCommandOn = energySavingModeCommandOn; - } - - public String getEnergySavingModeCommandOff() { - return energySavingModeCommandOff; - } - - public void setEnergySavingModeCommandOff(String energySavingModeCommandOff) { - this.energySavingModeCommandOff = energySavingModeCommandOff; - } - - public String getAirCleanModeCommandOn() { - return airCleanModeCommandOn; - } - - public void setAirCleanModeCommandOn(String airCleanModeCommandOn) { - this.airCleanModeCommandOn = airCleanModeCommandOn; - } - - public String getAirCleanModeCommandOff() { - return airCleanModeCommandOff; - } - - public void setAirCleanModeCommandOff(String airCleanModeCommandOff) { - this.airCleanModeCommandOff = airCleanModeCommandOff; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java deleted file mode 100644 index 3c16809f811e3..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link ACSnapshot} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum ACFanSpeed { - F1(2.0), - F2(3.0), - F3(4.0), - F4(5.0), - F5(6.0), - F_AUTO(8.0), - F_UNK(-1); - - private final double funStrength; - - ACFanSpeed(double v) { - this.funStrength = v; - } - - public static ACFanSpeed statusOf(double value) { - switch ((int) value) { - case 2: - return F1; - case 3: - return F2; - case 4: - return F3; - case 5: - return F4; - case 6: - return F5; - case 8: - return F_AUTO; - default: - return F_UNK; - } - } - - /** - * "0": "@AC_MAIN_WIND_STRENGTH_SLOW_W", - * "1": "@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", - * "2": "@AC_MAIN_WIND_STRENGTH_LOW_W", - * "3": "@AC_MAIN_WIND_STRENGTH_LOW_MID_W", - * "4": "@AC_MAIN_WIND_STRENGTH_MID_W", - * "5": "@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", - * "6": "@AC_MAIN_WIND_STRENGTH_HIGH_W", - * "7": "@AC_MAIN_WIND_STRENGTH_POWER_W", - * "8": "@AC_MAIN_WIND_STRENGTH_NATURE_W", - */ - /** - * Value of command (not state, but command to change the state of device) - * - * @return value of the command to reach the state - */ - public int commandValue() { - switch (this) { - case F1: - return 2; - case F2: - return 3; - case F3: - return 4; - case F4: - return 5; - case F5: - return 6; - case F_AUTO: - return 8; - default: - throw new IllegalArgumentException("Enum not accepted for command:" + this); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java deleted file mode 100644 index 38a8dac94a4ca..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link ACSnapshot} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum ACOpMode { - COOL(0), - DRY(1), - FAN(2), - AI(3), - HEAT(4), - AIRCLEAN(5), - ENSAV(8), - OP_UNK(-1); - - private final int opMode; - - ACOpMode(int v) { - this.opMode = v; - } - - public static ACOpMode statusOf(int value) { - switch ((int) value) { - case 0: - return COOL; - case 1: - return DRY; - case 2: - return FAN; - case 3: - return AI; - case 4: - return HEAT; - case 5: - return AIRCLEAN; - case 8: - return ENSAV; - default: - return OP_UNK; - } - } - - public int getValue() { - return this.opMode; - } - - /** - * Value of command (not state, but command to change the state of device) - * - * @return value of the command to reach the state - */ - public int commandValue() { - switch (this) { - case COOL: - return 0;// "@AC_MAIN_OPERATION_MODE_COOL_W" - case DRY: - return 1; // "@AC_MAIN_OPERATION_MODE_DRY_W" - case FAN: - return 2; // "@AC_MAIN_OPERATION_MODE_FAN_W" - case AI: - return 3; // "@AC_MAIN_OPERATION_MODE_AI_W" - case HEAT: - return 4; // "@AC_MAIN_OPERATION_MODE_HEAT_W" - case AIRCLEAN: - return 5; // "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W" - case ENSAV: - return 8; // "AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W" - default: - throw new IllegalArgumentException("Enum not accepted for command:" + this); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java deleted file mode 100644 index f86e0bb255269..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link ACSnapshot} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class ACSnapshot implements Snapshot { - - private int airWindStrength; - - private double targetTemperature; - - private double currentTemperature; - - private boolean coolJetModeOn; - - private double airCleanMode; - private double coolJetMode; - private double autoDryMode; - private double energySavingMode; - - private int operationMode; - @Nullable - private Integer operation; - @JsonIgnore - private boolean online; - - private double energyConsumption; - - @JsonIgnore - public DevicePowerState getPowerStatus() { - return operation == null ? DevicePowerState.DV_POWER_UNK : DevicePowerState.statusOf(operation); - } - - @JsonIgnore - public void setPowerStatus(DevicePowerState value) { - operation = (int) value.getValue(); - } - - @JsonIgnore - public ACFanSpeed getAcFanSpeed() { - return ACFanSpeed.statusOf(airWindStrength); - } - - @JsonProperty("airState.windStrength") - @JsonAlias("WindStrength") - public Integer getAirWindStrength() { - return airWindStrength; - } - - @JsonProperty("airState.wMode.jet") - @JsonAlias("Jet") - public Double getCoolJetMode() { - return coolJetMode; - } - - @JsonProperty("airState.wMode.airClean") - @JsonAlias("AirClean") - public Double getAirCleanMode() { - return airCleanMode; - } - - @JsonProperty("airState.miscFuncState.autoDry") - @JsonAlias("AutoDry") - public Double getAutoDryMode() { - return autoDryMode; - } - - @JsonProperty("airState.powerSave.basic") - @JsonAlias("PowerSave") - public Double getEnergySavingMode() { - return energySavingMode; - } - - public void setAirCleanMode(double airCleanMode) { - this.airCleanMode = airCleanMode; - } - - public void setAutoDryMode(double autoDryMode) { - this.autoDryMode = autoDryMode; - } - - public void setEnergySavingMode(double energySavingMode) { - this.energySavingMode = energySavingMode; - } - - public void setCoolJetMode(Double coolJetMode) { - this.coolJetMode = coolJetMode; - } - - public void setAirWindStrength(Integer airWindStrength) { - this.airWindStrength = airWindStrength; - } - - @JsonProperty("airState.energy.onCurrent") - public double getEnergyConsumption() { - return energyConsumption; - } - - public void setEnergyConsumption(double energyConsumption) { - this.energyConsumption = energyConsumption; - } - - @JsonProperty("airState.tempState.target") - @JsonAlias("TempCfg") - public Double getTargetTemperature() { - return targetTemperature; - } - - public void setTargetTemperature(Double targetTemperature) { - this.targetTemperature = targetTemperature; - } - - @JsonProperty("airState.tempState.current") - @JsonAlias("TempCur") - public Double getCurrentTemperature() { - return currentTemperature; - } - - public void setCurrentTemperature(Double currentTemperature) { - this.currentTemperature = currentTemperature; - } - - @JsonProperty("airState.opMode") - @JsonAlias("OpMode") - public Integer getOperationMode() { - return operationMode; - } - - public void setOperationMode(Integer operationMode) { - this.operationMode = operationMode; - } - - @Nullable - @JsonProperty("airState.operation") - @JsonAlias("Operation") - public Integer getOperation() { - return operation; - } - - public void setOperation(Integer operation) { - this.operation = operation; - } - - @JsonIgnore - public boolean isOnline() { - return online; - } - - public void setOnline(boolean online) { - this.online = online; - } - - @Override - public String toString() { - return "ACSnapShot{" + "airWindStrength=" + airWindStrength + ", targetTemperature=" + targetTemperature - + ", currentTemperature=" + currentTemperature + ", operationMode=" + operationMode + ", operation=" - + operation + ", acPowerStatus=" + getPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() + ", acOpMode=" - + ", online=" + isOnline() + " }"; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java deleted file mode 100644 index 4b2fbc27bd6e5..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link ACTargetTmp} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum ACTargetTmp { - _17(17.0), - _18(18.0), - _19(19.0), - _20(20.0), - _21(21.0), - _22(22.0), - _23(23.0), - _24(24.0), - _25(25.0), - _26(26.0), - _27(27.0), - _28(28.0), - _29(29.0), - _30(30.0), - UNK(-1); - - private final double targetTmp; - - ACTargetTmp(double v) { - this.targetTmp = v; - } - - public static ACTargetTmp statusOf(double value) { - switch ((int) value) { - case 17: - return _17; - case 18: - return _18; - case 19: - return _19; - case 20: - return _20; - case 21: - return _21; - case 22: - return _22; - case 23: - return _23; - case 24: - return _24; - case 25: - return _25; - case 26: - return _26; - case 27: - return _27; - case 28: - return _28; - case 29: - return _29; - case 30: - return _30; - default: - return UNK; - } - } - - public double getValue() { - return this.targetTmp; - } - - /** - * Value of command (not state, but command to change the state of device) - * - * @return value of the command to reach the state - */ - public int commandValue() { - return (int) this.targetTmp; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CommandDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CommandDefinition.java new file mode 100644 index 0000000000000..63e8f0979a2ae --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CommandDefinition.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.devices.washerdryer; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link CommandDefinition} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CommandDefinition { + private static final Logger logger = LoggerFactory.getLogger(CommandDefinition.class); + /** + * This is the command tag value that is used by the API to launch the command service + */ + private String command = ""; + private Map data = new HashMap<>(); + + public String getCommand() { + return command; + } + + public void setCommand(String command) { + this.command = command; + } + + public Map getData() { + return data; + } + + public void setData(Map data) { + this.data = data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java new file mode 100644 index 0000000000000..b50cee3a376d0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.devices.washerdryer; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CourseDefinition} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CourseDefinition { + private String courseName = ""; + // Name of the course this is based on. It's only used for SmartCourses + private String baseCourseName = ""; + private CourseType courseType = CourseType.UNDEF; + private List functions = new ArrayList<>(); + + public String getCourseName() { + return courseName; + } + + public String getBaseCourseName() { + return baseCourseName; + } + + public void setBaseCourseName(String baseCourseName) { + this.baseCourseName = baseCourseName; + } + + public void setCourseName(String courseName) { + this.courseName = courseName; + } + + public CourseType getCourseType() { + return courseType; + } + + public void setCourseType(CourseType courseType) { + this.courseType = courseType; + } + + public List getFunctions() { + return functions; + } + + public void setFunctions(List functions) { + this.functions = functions; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java new file mode 100644 index 0000000000000..cf9428a5a9e7c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java @@ -0,0 +1,63 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.devices.washerdryer; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CourseFunction} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CourseFunction { + private String value = ""; + private String defaultValue = ""; + private boolean isSelectable; + private List selectableValues = new ArrayList<>(); + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getDefaultValue() { + return defaultValue; + } + + public void setDefaultValue(String defaultValue) { + this.defaultValue = defaultValue; + } + + public boolean isSelectable() { + return isSelectable; + } + + public void setSelectable(boolean selectable) { + isSelectable = selectable; + } + + public List getSelectableValues() { + return selectableValues; + } + + public void setSelectableValues(List selectableValues) { + this.selectableValues = selectableValues; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java similarity index 52% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java index b1d80faaf071e..abaafba5793de 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java @@ -10,23 +10,30 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.fridge; +package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; /** - * The {@link FridgeSnapshot} + * The {@link CourseType} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface FridgeSnapshot extends Snapshot { - public String getTempUnit(); +public enum CourseType { + // TODO - review DownloadCourse value, in remote start debugging + COURSE("Course"), + SMART_COURSE("SmartCourse"), + DOWNLOADED_COURSE("DownloadedCourse"), + UNDEF("Undefined"); - public String getFridgeStrTemp(); + private final String value; - public String getFreezerStrTemp(); + CourseType(String s) { + value = s; + } - public void loadSnapshot(Object veryRootNode); + public String getValue() { + return value; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerPropertyDiscovery.java similarity index 55% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerPropertyDiscovery.java index da1212c16f879..2ecb7dc420af0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerPropertyDiscovery.java @@ -10,23 +10,13 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; +package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; /** - * The {@link Snapshot} + * The {@link WasherDryerPropertyDiscovery} * * @author Nemer Daud - Initial contribution */ -@NonNullByDefault -public interface Snapshot { - - public DevicePowerState getPowerStatus(); - - public void setPowerStatus(DevicePowerState value); - - public boolean isOnline(); +public class WasherDryerPropertyDiscovery { - public void setOnline(boolean online); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java deleted file mode 100644 index 4ba7702f9b582..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Capability; - -/** - * The {@link FridgeCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface FridgeCapability extends Capability { - - public Map getFridgeTempCMap(); - - public Map getFridgeTempFMap(); - - public Map getFreezerTempCMap(); - - public Map getFreezerTempFMap(); - - public void loadCapabilities(Object veryRootNode); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java deleted file mode 100644 index c400c746be0c7..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; - -import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeCapabilityV2; -import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; - -/** - * The {@link FridgeFactory} - * - * @author Nemer Daud - Initial contribution - */ -public class FridgeFactory { - - public static FridgeCapability getFridgeCapability(LGAPIVerion version) { - switch (version) { - case V1_0: - throw new IllegalArgumentException("V1_0 not supported by Fridge Thing yet"); - case V2_0: - return new FridgeCapabilityV2(); - default: - throw new IllegalArgumentException("Version " + version + " not expected"); - } - } - - public static FridgeSnapshot getFridgeSnapshot(LGAPIVerion version) { - switch (version) { - case V1_0: - throw new IllegalArgumentException("V1_0 not supported by Fridge Thing yet"); - case V2_0: - return new FridgeSnapshotV2(); - default: - throw new IllegalArgumentException("Version " + version + " not expected"); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java deleted file mode 100644 index a32f887b017a8..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge.v2; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_CELSIUS_SYMBOL; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_FAHRENHEIT_SYMBOL; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * The {@link FridgeCapabilityV2} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class FridgeCapabilityV2 extends AbstractCapability - implements org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeCapability { - - private static final Logger logger = LoggerFactory.getLogger(FridgeCapabilityV2.class); - private static final ObjectMapper mapper = new ObjectMapper(); - - private final Map fridgeTempCMap = new LinkedHashMap(); - private final Map fridgeTempFMap = new LinkedHashMap(); - private final Map freezerTempCMap = new LinkedHashMap(); - private final Map freezerTempFMap = new LinkedHashMap(); - - public Map getFridgeTempCMap() { - return fridgeTempCMap; - } - - public Map getFridgeTempFMap() { - return fridgeTempFMap; - } - - public Map getFreezerTempCMap() { - return freezerTempCMap; - } - - public Map getFreezerTempFMap() { - return freezerTempFMap; - } - - @Override - public void loadCapabilities(Object veryRootNode) { - JsonNode node = mapper.valueToTree(veryRootNode); - if (node.isNull()) { - logger.error("Can't parse json capability for Fridge V2. The payload has been ignored"); - logger.debug("payload {}", veryRootNode); - return; - } - /** - * iterate over valueMappings like: - * "valueMapping": { - * "1": {"index" : 1, "label" : "7", "_comment" : ""}, - * "2": {"index" : 2, "label" : "6", "_comment" : ""}, - * "3": {"index" : 3, "label" : "5", "_comment" : ""}, - * "4": {"index" : 4, "label" : "4", "_comment" : ""}, - * "5": {"index" : 5, "label" : "3", "_comment" : ""}, - * "6": {"index" : 6, "label" : "2", "_comment" : ""}, - * "7": {"index" : 7, "label" : "1", "_comment" : ""}, - * "255" : {"index" : 255, "label" : "IGNORE", "_comment" : ""} - * } - */ - - JsonNode fridgeTempCNode = node.path("MonitoringValue").path("fridgeTemp_C").path("valueMapping"); - JsonNode fridgeTempFNode = node.path("MonitoringValue").path("fridgeTemp_F").path("valueMapping"); - JsonNode freezerTempCNode = node.path("MonitoringValue").path("freezerTemp_C").path("valueMapping"); - JsonNode freezerTempFNode = node.path("MonitoringValue").path("freezerTemp_F").path("valueMapping"); - loadTempNode(fridgeTempCNode, fridgeTempCMap, TEMP_UNIT_CELSIUS_SYMBOL); - loadTempNode(fridgeTempFNode, fridgeTempFMap, TEMP_UNIT_FAHRENHEIT_SYMBOL); - loadTempNode(freezerTempCNode, freezerTempCMap, TEMP_UNIT_CELSIUS_SYMBOL); - loadTempNode(freezerTempFNode, freezerTempFMap, TEMP_UNIT_FAHRENHEIT_SYMBOL); - } - - private void loadTempNode(JsonNode tempNode, Map capMap, String unit) { - tempNode.forEach(v -> { - // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' - capMap.put(v.path("index").asText() + " " + unit, v.path("label").textValue() + " " + unit); - }); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java deleted file mode 100644 index 47e4acde699f9..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge.v2; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link FridgeSnapshotV2} - * This map the snapshot result from Washing Machine devices - * This json payload come with path: snapshot->fridge, but this POJO expects - * to map field below washerDryer - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class FridgeSnapshotV2 implements Snapshot, org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeSnapshot { - - private boolean online; - private Double fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; - private Double freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; - private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default - - @Override - @JsonAlias({ "TempUnit" }) - @JsonProperty("tempUnit") - public String getTempUnit() { - return tempUnit; - } - - private String getStrTempWithUnit(Double temp) { - return temp.intValue() + (TEMP_UNIT_CELSIUS.equals(tempUnit) ? " " + TEMP_UNIT_CELSIUS_SYMBOL - : (TEMP_UNIT_FAHRENHEIT).equals(tempUnit) ? " " + TEMP_UNIT_FAHRENHEIT_SYMBOL : ""); - } - - @Override - @JsonIgnore - public String getFridgeStrTemp() { - return getStrTempWithUnit(getFridgeTemp()); - } - - @Override - @JsonIgnore - public String getFreezerStrTemp() { - return getStrTempWithUnit(getFreezerTemp()); - } - - public void setTempUnit(String tempUnit) { - this.tempUnit = tempUnit; - } - - @JsonAlias({ "TempRefrigerator" }) - @JsonProperty("fridgeTemp") - public Double getFridgeTemp() { - return fridgeTemp; - } - - public void setFridgeTemp(Double fridgeTemp) { - this.fridgeTemp = fridgeTemp; - } - - @JsonAlias({ "TempFreezer" }) - @JsonProperty("freezerTemp") - public Double getFreezerTemp() { - return freezerTemp; - } - - public void setFreezerTemp(Double freezerTemp) { - this.freezerTemp = freezerTemp; - } - - @Override - public void loadSnapshot(Object veryRootNode) { - } - - @Override - public DevicePowerState getPowerStatus() { - throw new IllegalStateException("Fridge has no Power state."); - } - - @Override - public void setPowerStatus(DevicePowerState value) { - throw new IllegalStateException("Fridge has no Power state."); - } - - @Override - public boolean isOnline() { - return online; - } - - @Override - public void setOnline(boolean online) { - this.online = online; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java deleted file mode 100644 index ac615e7c2b034..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.openhab.binding.lgthinq.lgservices.model.ModelUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; - -/** - * The {@link CommandCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class CommandCapability { - private boolean isPowerCommandsAvailable = false; - private String powerOffCommand = ""; - private String stopCommand = ""; - private String wakeUpCommand = ""; - private static final Logger logger = LoggerFactory.getLogger(CommandCapability.class); - - public void loadCommands(JsonNode rootNode) throws LGThinqApiException { - LGAPIVerion version = ModelUtils.discoveryAPIVersion(rootNode); - switch (version) { - case V1_0: - logger.warn("Version {} for commands of Dryer/Washers not supported for this binding.", - version.getValue()); - return; - case V2_0: - JsonNode wifiNode = rootNode.path("ControlWifi"); - if (wifiNode.isMissingNode()) { - logger.warn( - "Dryer/Washer is missing ControlWifi node in the model. Commands are not supported for this model."); - return; - } - JsonNode wmOffNode = wifiNode.path("WMOff"); - JsonNode wmStopNode = wifiNode.path("WMStop"); - JsonNode wmWakeUpNode = wifiNode.path("WMWakeup"); - boolean isOffPresent = !wmOffNode.isMissingNode(); - boolean isStopPresent = !wmStopNode.isMissingNode(); - boolean isWakeUpPresent = !wmWakeUpNode.isMissingNode(); - if (isOffPresent || isStopPresent || isWakeUpPresent) { - isPowerCommandsAvailable = true; - powerOffCommand = isOffPresent - ? wmOffNode.path("data").path("washerDryer").path("controlDataType").textValue() - : ""; - stopCommand = isStopPresent - ? wmStopNode.path("data").path("washerDryer").path("controlDataType").textValue() - : ""; - wakeUpCommand = isWakeUpPresent - ? wmWakeUpNode.path("data").path("washerDryer").path("controlDataType").textValue() - : ""; - } - } - } - - public boolean isPowerCommandsAvailable() { - return isPowerCommandsAvailable; - } - - public void setPowerCommandsAvailable(boolean powerCommandsAvailable) { - isPowerCommandsAvailable = powerCommandsAvailable; - } - - public String getPowerOffCommand() { - return powerOffCommand; - } - - public String getStopCommand() { - return stopCommand; - } - - public String getWakeUpCommand() { - return wakeUpCommand; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java deleted file mode 100644 index d29b07e1f0124..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; - -/** - * The {@link DryerCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class DryerCapability extends AbstractCapability { - public enum MonitoringCap { - STATE_V2("state"), - PROCESS_STATE_V2("processState"), - DRY_LEVEL_V2("dryLevel"), - ERROR_V2("error"), - STATE_V1("State"), - PROCESS_STATE_V1("PreState"), - ERROR_V1("Error"); - - final String value; - - MonitoringCap(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - } - - private static class MonitoringValue { - private final Map state = new LinkedHashMap(); - private final Map dryLevel = new LinkedHashMap(); - private final Map error = new LinkedHashMap(); - private final Map processState = new LinkedHashMap(); - private boolean hasChildLock; - private boolean hasRemoteStart; - } - - private final MonitoringValue monitoringValue = new MonitoringValue(); - private final Map courses = new LinkedHashMap(); - - private final Map smartCourses = new LinkedHashMap(); - - public Map getCourses() { - return courses; - } - - public Map getSmartCourses() { - return smartCourses; - } - - public void addCourse(String courseLabel, String courseName) { - courses.put(courseLabel, courseName); - } - - public void addSmartCourse(String courseLabel, String courseName) { - smartCourses.put(courseLabel, courseName); - } - - public Map getState() { - return monitoringValue.state; - } - - public Map getDryLevels() { - return monitoringValue.dryLevel; - } - - public Map getErrors() { - return monitoringValue.error; - } - - public Map getProcessStates() { - return monitoringValue.processState; - } - - public boolean hasRemoteStart() { - return monitoringValue.hasRemoteStart; - } - - public boolean hasChildLock() { - return monitoringValue.hasChildLock; - } - - public void setChildLock(boolean hasChildLock) { - monitoringValue.hasChildLock = hasChildLock; - } - - public void setRemoteStart(boolean hasRemoteStart) { - monitoringValue.hasRemoteStart = hasRemoteStart; - } - - public void addMonitoringValue(MonitoringCap monCap, String key, String value) { - switch (monCap) { - case STATE_V2: - case STATE_V1: - monitoringValue.state.put(key, value); - break; - case PROCESS_STATE_V2: - case PROCESS_STATE_V1: - monitoringValue.processState.put(key, value); - break; - case DRY_LEVEL_V2: - monitoringValue.dryLevel.put(key, value); - break; - case ERROR_V1: - case ERROR_V2: - monitoringValue.error.put(key, value); - break; - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java deleted file mode 100644 index 812340b2fec51..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java +++ /dev/null @@ -1,203 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link DryerSnapshot} - * This map the snapshot result from Washing Machine devices - * This json payload come with path: snapshot->washerDryer, but this POJO expects - * to map field below washerDryer - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class DryerSnapshot implements Snapshot { - private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; - private String state = ""; - private boolean online; - private String course = ""; - private String smartCourse = ""; - private String childLock = ""; - private String processState = ""; - private Double remainingHour = 0.00; - private Double remainingMinute = 0.00; - private String dryLevel = ""; - private String error = ""; - private String remoteStart = ""; - private boolean remoteStartEnabled = false; - - private String standByStatus = ""; - - private boolean standBy = false; - - @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) - @JsonProperty("courseDryer24inchBase") - public String getCourse() { - return course; - } - - public void setCourse(String course) { - this.course = course; - } - - @JsonProperty("dryLevel") - @JsonAlias({ "DryLevel" }) - public String getDryLevel() { - return dryLevel; - } - - public void setDryLevel(String dryLevel) { - this.dryLevel = dryLevel; - } - - public void setRemainingMinute(Double remainingMinute) { - this.remainingMinute = remainingMinute; - } - - @JsonProperty("error") - @JsonAlias({ "Error" }) - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - - @JsonProperty("processState") - @JsonAlias({ "ProcessState" }) - public String getProcessState() { - return processState; - } - - public void setProcessState(String processState) { - this.processState = processState; - } - - @Override - public DevicePowerState getPowerStatus() { - return powerState; - } - - @Override - public void setPowerStatus(DevicePowerState value) { - throw new IllegalArgumentException("This method must not be accessed."); - } - - @Override - public boolean isOnline() { - return online; - } - - @Override - public void setOnline(boolean online) { - this.online = online; - } - - @JsonProperty("state") - @JsonAlias({ "state", "State" }) - public String getState() { - return state; - } - - @JsonProperty("smartCourseDryer24inchBase") - @JsonAlias({ "smartCourseDryer24inchBase", "SmartCourse", "smartCourseDryer27inchBase" }) - public String getSmartCourse() { - return smartCourse; - } - - public void setSmartCourse(String smartCourse) { - this.smartCourse = smartCourse; - } - - @JsonProperty("childLock") - public String getChildLock() { - return childLock; - } - - public void setChildLock(String childLock) { - this.childLock = childLock; - } - - @JsonIgnore - public String getRemainingTime() { - return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); - } - - @JsonProperty("remainTimeHour") - @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) - public Double getRemainingHour() { - return remainingHour; - } - - public void setRemainingHour(Double remainingHour) { - this.remainingHour = remainingHour; - } - - @JsonProperty("remainTimeMinute") - @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) - public Double getRemainingMinute() { - return remainingMinute; - } - - public void setState(String state) { - this.state = state; - if (state.equals(WM_POWER_OFF_VALUE)) { - powerState = DevicePowerState.DV_POWER_OFF; - } else { - powerState = DevicePowerState.DV_POWER_ON; - } - } - - public boolean isRemoteStartEnabled() { - return remoteStartEnabled; - } - - @JsonProperty("remoteStart") - @JsonAlias({ "RemoteStart" }) - public String getRemoteStart() { - return remoteStart; - } - - public void setRemoteStart(String remoteStart) { - this.remoteStart = remoteStart; - remoteStartEnabled = remoteStart.contains("ON"); - } - - public String getStandByStatus() { - return standByStatus; - } - - @JsonProperty("standby") - @JsonAlias({ "Standby" }) - public void setStandByStatus(String standByStatus) { - this.standByStatus = standByStatus; - standBy = standByStatus.contains("ON"); - } - - public boolean isStandBy() { - return standBy; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java deleted file mode 100644 index 05535d455c3ea..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; - -/** - * The {@link WasherCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WasherCapability extends AbstractCapability { - public enum MonitoringCap { - STATE_V2("state"), - SOIL_WASH_V2("soilWash"), - SPIN_V2("spin"), - TEMPERATURE_V2("temp"), - RINSE_V2("rinse"), - ERROR_V2("error"), - STATE_V1("State"), - SOIL_WASH_V1("Wash"), - SPIN_V1("SpinSpeed"), - TEMPERATURE_V1("WaterTemp"), - RINSE_V1("RinseOption"), - ERROR_V1("Error"); - - final String value; - - MonitoringCap(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - } - - private static class MonitoringValue { - private Map state = new LinkedHashMap(); - private Map soilWash = new LinkedHashMap(); - private Map spin = new LinkedHashMap(); - private Map temperature = new LinkedHashMap(); - private Map rinse = new LinkedHashMap(); - private Map error = new LinkedHashMap(); - private boolean hasDoorLook; - private boolean hasTurboWash; - } - - private MonitoringValue monitoringValue = new MonitoringValue(); - private Map courses = new LinkedHashMap(); - - private Map smartCourses = new LinkedHashMap(); - - public Map getCourses() { - return courses; - } - - public Map getSmartCourses() { - return smartCourses; - } - - public void addCourse(String courseLabel, String courseName) { - courses.put(courseLabel, courseName); - } - - public void addSmartCourse(String courseLabel, String courseName) { - smartCourses.put(courseLabel, courseName); - } - - public Map getState() { - return monitoringValue.state; - } - - public Map getSoilWash() { - return monitoringValue.soilWash; - } - - public Map getSpin() { - return monitoringValue.spin; - } - - public Map getTemperature() { - return monitoringValue.temperature; - } - - public Map getRinse() { - return monitoringValue.rinse; - } - - public Map getError() { - return monitoringValue.error; - } - - public boolean hasDoorLook() { - return monitoringValue.hasDoorLook; - } - - public void setHasDoorLook(boolean hasDoorLook) { - monitoringValue.hasDoorLook = hasDoorLook; - } - - public boolean hasTurboWash() { - return monitoringValue.hasTurboWash; - } - - public void setHasTurboWash(boolean hasTurboWash) { - monitoringValue.hasTurboWash = hasTurboWash; - } - - public void addMonitoringValue(MonitoringCap monCap, String key, String value) { - switch (monCap) { - case STATE_V1: - case STATE_V2: - monitoringValue.state.put(key, value); - break; - case SOIL_WASH_V2: - case SOIL_WASH_V1: - monitoringValue.soilWash.put(key, value); - break; - case SPIN_V2: - case SPIN_V1: - monitoringValue.spin.put(key, value); - break; - case TEMPERATURE_V2: - case TEMPERATURE_V1: - monitoringValue.temperature.put(key, value); - break; - case RINSE_V2: - case RINSE_V1: - monitoringValue.rinse.put(key, value); - break; - case ERROR_V2: - case ERROR_V1: - monitoringValue.error.put(key, value); - break; - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java deleted file mode 100644 index 004474dbf76fd..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java +++ /dev/null @@ -1,221 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link WasherSnapshot} - * This map the snapshot result from Washing Machine devices - * This json payload come with path: snapshot->washerDryer, but this POJO expects - * to map field below washerDryer - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class WasherSnapshot implements Snapshot { - private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; - private String state = ""; - private boolean online; - private String course = ""; - private String smartCourse = ""; - private String downloadedCourse = ""; - private String temperatureLevel = ""; - private String doorLock = ""; - private Double remainingHour = 0.00; - private Double remainingMinute = 0.00; - private Double reserveHour = 0.00; - private Double reserveMinute = 0.00; - - private String remoteStart = ""; - private boolean remoteStartEnabled = false; - - private String standByStatus = ""; - - private boolean standBy = false; - - @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) - @JsonProperty("courseFL24inchBaseTitan") - public String getCourse() { - return course; - } - - public void setCourse(String course) { - this.course = course; - } - - @Override - public DevicePowerState getPowerStatus() { - return powerState; - } - - @Override - public void setPowerStatus(DevicePowerState value) { - throw new IllegalArgumentException("This method must not be accessed."); - } - - @Override - public boolean isOnline() { - return online; - } - - @Override - public void setOnline(boolean online) { - this.online = online; - } - - @JsonProperty("state") - @JsonAlias({ "state", "State" }) - public String getState() { - return state; - } - - @JsonProperty("smartCourseFL24inchBaseTitan") - @JsonAlias({ "smartCourseFL24inchBaseTitan", "SmartCourse" }) - public String getSmartCourse() { - return smartCourse; - } - - @JsonProperty("downloadedCourseFL24inchBaseTitan") - @JsonAlias({ "downloadedCourseFLUpper25inchBaseUS" }) - public String getDownloadedCourse() { - return downloadedCourse; - } - - public void setDownloadedCourse(String downloadedCourse) { - this.downloadedCourse = downloadedCourse; - } - - @JsonIgnore - public String getRemainingTime() { - return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); - } - - @JsonIgnore - public String getReserveTime() { - return String.format("%02.0f:%02.0f", getReserveHour(), getReserveMinute()); - } - - @JsonProperty("remainTimeHour") - @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) - public Double getRemainingHour() { - return remainingHour; - } - - public void setRemainingHour(Double remainingHour) { - this.remainingHour = remainingHour; - } - - @JsonProperty("remainTimeMinute") - @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) - public Double getRemainingMinute() { - return remainingMinute; - } - - public void setRemainingMinute(Double remainingMinute) { - this.remainingMinute = remainingMinute; - } - - @JsonProperty("reserveTimeHour") - @JsonAlias({ "reserveTimeHour", "Reserve_Time_H" }) - public Double getReserveHour() { - return reserveHour; - } - - public void setReserveHour(Double reserveHour) { - this.reserveHour = reserveHour; - } - - @JsonProperty("reserveTimeMinute") - @JsonAlias({ "reserveTimeMinute", "Reserve_Time_M" }) - public Double getReserveMinute() { - return reserveMinute; - } - - public void setReserveMinute(Double reserveMinute) { - this.reserveMinute = reserveMinute; - } - - public void setSmartCourse(String smartCourse) { - this.smartCourse = smartCourse; - } - - @JsonProperty("temp") - @JsonAlias({ "WaterTemp" }) - public String getTemperatureLevel() { - return temperatureLevel; - } - - public void setTemperatureLevel(String temperatureLevel) { - this.temperatureLevel = temperatureLevel; - } - - @JsonProperty("doorLock") - @JsonAlias({ "ChildLock" }) - public String getDoorLock() { - return doorLock; - } - - public void setDoorLock(String doorLock) { - this.doorLock = doorLock; - } - - public void setState(String state) { - this.state = state; - if (state.equals(WM_POWER_OFF_VALUE)) { - powerState = DevicePowerState.DV_POWER_OFF; - } else { - powerState = DevicePowerState.DV_POWER_ON; - } - } - - public boolean isRemoteStartEnabled() { - return remoteStartEnabled; - } - - @JsonProperty("remoteStart") - @JsonAlias({ "RemoteStart" }) - public String getRemoteStart() { - return remoteStart; - } - - public void setRemoteStart(String remoteStart) { - this.remoteStart = remoteStart; - remoteStartEnabled = remoteStart.contains("ON"); - } - - public String getStandByStatus() { - return standByStatus; - } - - @JsonProperty("standby") - @JsonAlias({ "Standby" }) - public void setStandByStatus(String standByStatus) { - this.standByStatus = standByStatus; - standBy = standByStatus.contains("ON"); - } - - public boolean isStandBy() { - return standBy; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 0ed1ca753e4d0..8d79b03e4c5de 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -17,12 +17,13 @@ - - + + + @@ -38,7 +39,6 @@ - From fb350cc2b8008f4703bb6a8c5fba5eccc5fb6e57 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Fri, 28 Jan 2022 19:34:59 -0300 Subject: [PATCH 058/130] [lgthinq] Initial Contribution. Binding to integrate OpenHab to LG Thinq API to control Thinq compatible devices through OpenHab. Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 50 +- .../internal/LGAirConditionerHandler.java | 499 ++++++++++++++++++ .../LGDeviceDynStateDescriptionProvider.java | 39 ++ .../lgthinq/internal/LGDeviceThing.java | 47 ++ .../internal/LGThinqBindingConstants.java | 121 +++++ .../internal/LGThinqConfiguration.java | 86 +++ .../internal/LGThinqHandlerFactory.java | 85 +++ .../binding/lgthinq/internal/api/Gateway.java | 122 +++++ .../lgthinq/internal/api/TokenResult.java | 6 +- .../internal/errors/LGApiException.java | 31 ++ .../LGDeviceV1MonitorExpiredException.java | 32 ++ .../errors/LGDeviceV1OfflineException.java | 33 ++ .../internal/errors/LGGatewayException.java | 27 + .../errors/RefreshTokenException.java | 2 +- .../lgthinq/internal/handler/LGBridge.java | 31 ++ .../internal/handler/LGBridgeHandler.java | 315 +++++++++++ .../lgthinq/lgapi/LGApiClientService.java | 68 +++ .../lgthinq/lgapi/LGApiClientServiceImpl.java | 173 ++++++ .../lgapi/LGApiV1ClientServiceImpl.java | 330 ++++++++++++ .../lgapi/LGApiV2ClientServiceImpl.java | 274 ++++++++++ .../lgthinq/lgapi/model/ACCapability.java | 66 +++ .../lgthinq/lgapi/model/ACFanSpeed.java | 91 ++++ .../binding/lgthinq/lgapi/model/ACOpMode.java | 89 ++++ .../lgthinq/lgapi/model/ACSnapShot.java | 109 ++++ .../lgthinq/lgapi/model/ACSnapShotV1.java | 58 ++ .../lgthinq/lgapi/model/ACSnapShotV2.java | 58 ++ .../lgthinq/lgapi/model/ACTargetTmp.java | 93 ++++ .../lgthinq/lgapi/model/DevicePowerState.java | 71 +++ .../lgthinq/lgapi/model/DeviceTypes.java | 42 ++ .../binding/lgthinq/lgapi/model/LGDevice.java | 107 ++++ .../resources/OH-INF/i18n/lgthinq.properties | 28 +- .../lgthinq/handler/LGBridgeTests.java | 313 +++++++++++ 32 files changed, 3452 insertions(+), 44 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index bfcd12425a49f..f027ffb377bdf 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -1,11 +1,11 @@ -# LG ThinQ Bridge & Things +# LG Thinq Bridge & Things -This binding was developed to integrate de OpenHab framework to LG ThinQ API. Currently, only Air Conditioners (API V1 & V2) are supported, but this binding is under construction to support others LG ThinQ Device Kinds. -The ThinQ Bridge is necessary to work as a hub/bridge to discovery and first configure the LG ThinQ devices related with the LG's user account. -Then, the first thing is to create the LG ThinQ Bridge and then, it will discovery all Things you have related in your LG Account. +This binding was developed to integrate de OpenHab framework to LG Thinq API. Currently, only Air Conditioners (API V1 & V2) are supported, but this binding is under construction to support others LG Thinq Device Kinds. +The Thinq Bridge is necessary to work as a hub/bridge to discovery and first configure the LG Thinq devices related with the LG's user account. +Then, the first thing is to create the LG Thinq Bridge and then, it will discovery all Things you have related in your LG Account. ## Supported Things -LG ThinQ Devices V1 & V2 (currently only Air Conditioners are supported, but it's planned to support the other kinds as well) +LG Thinq Devices V1 & V2 (currently only Air Conditioners are supported, but it's planned to support the other kinds as well) ## Discovery @@ -17,13 +17,13 @@ This Bridge discovery Air Conditioner Things related to the user's account. To f The binding is represented by a bridge (LG GatewayBridge) and you must configure the following parameters: -| Bridge Parameter | Description | Constraints | -|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| -| User Language | User language configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | en-US, en-GB, pt-BR, de-DE, da-DK | -| User Country | User country configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | US, UK, BR, DE and DK | -| LG User name | The LG user's account (normally an email) | | -| LG Password | The LG user's password | | -| Pooling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | +| Bridge Parameter | Description | Constraints | +|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------| +| User Language | User language configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | en-US, en-GB, pt-BR and ge | +| User Country | User country configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | US, UK, BR and DE | +| LG User name | The LG user's account (normally an email) | | +| LG Password | The LG user's password | | +| Pooling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | @@ -34,27 +34,27 @@ There is currently no configuration available, as it is automatically obtained b ## Channels -LG ThinQ Air Conditioners support the following channels to interact with the OpenHab automation framework: +LG Thinq Air Conditioners support the following channels to interact with the OpenHab automation framework: -| channel | type | description | -|--------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -| Target Temperature | Temperature | Defines the desired target temperature for the device | -| Temperature | Temperature | Read-Only channel that indicates the current temperature informed by the device | -| Fan Speed | Number (Labeled) | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | -| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | -| Power | Switch | Define the device's current power state. | +| channel | type | description | +|--------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Target Temperature | Temperature | Defines the desired target temperature for the device | +| Temperature | Temperature | Read-Only channel that indicates the current temperature informed by the device | +| Fan Speed | Number (Labeled) | This channel let you choose the current label value for the fan speed (Low, Medium, High, Nature, etc.). These values are pre-configured in discovery time. | +| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | +| Power | Switch | Define the device's current power state. | -**Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart ThinQ way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: +**Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart Thinq way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: 1. **Internet Link** - if you OpenHab server doesn't have a good internet connection this binding will not work properly! In the same way, if the internet link goes down, your Things and Bridge going to be Offline as well, and you won't be able to control the devices though OpenHab until the link comes back. -2. **LG ThinQ App** - if you've already used the LG ThinQ App to control your devices and hold it constantly activated in your mobile phone, you may experience some instability because the App (and Binding) will try to lock the device in LG ThinQ API Server to get it's current state. In the app, you may see some information in the device informing that "The device is being used by other" (something like this) and in the OpenHab, the thing can go Offline for a while. +2. **LG Thinq App** - if you've already used the LG Thinq App to control your devices and hold it constantly activated in your mobile phone, you may experience some instability because the App (and Binding) will try to lock the device in LG Thinq API Server to get it's current state. In the app, you may see some information in the device informing that "The device is being used by other" (something like this) and in the OpenHab, the thing can go Offline for a while. 3. **Pooling time** - both Bridge and Thing use pooling strategy to get the current state information about the registered devices. Note that the Thing pooling time is internal and can't be changed (please, don't change in the source code) and the Bridge can be changed for something greater than 300 seconds, and it's recommended long pooling periods for the Bridge because the discovery process fetch a lot of information from the LG API Server, depending on the number of devices you have registered in your account. -About this last point, it's important to know that LG API is not Open & Public, i.e, only LG Official Partners with some agreement have access to their support and documentations. This binding was a hard (very hard actually) work to dig and reverse engineering in the LG's ThinQ API protocol. Because this, you must respect the hardcoded pool period to do not put your account in LG Blacklist. +About this last point, it's important to know that LG API is not Open & Public, i.e, only LG Official Partners with some agreement have access to their support and documentations. This binding was a hard (very hard actually) work to dig and reverse engineering in the LG's Thinq API protocol. Because this, you must respect the hardcoded pool period to do not put your account in LG Blacklist. ## Thanks and Inspirations This binding was inspired in the work of some brave opensource community people. I got some tips and helps from their codes: -* Adrian Sampson - [Wideq Project](https://github.com/sampsyo/wideq): I think it is the first reverse engineering of ThinQ protocol made in Python, but only works (currently) for API V1. -* Ollo69 - [LG ThinQ Integration for Home Assistant](https://github.com/ollo69/ha-smartthinq-sensors): Ollo69 took the Adrian code and refactor it to support API V2 in an HA plugin. +* Adrian Sampson - [Wideq Project](https://github.com/sampsyo/wideq): I think it is the first reverse engineering of Thinq protocol made in Python, but only works (currently) for API V1. +* Ollo69 - [LG Thinq Integration for Home Assistant](https://github.com/ollo69/ha-smartthinq-sensors): Ollo69 took the Adrian code and refactor it to support API V2 in an HA plugin. ## Be nice! If you like the binding, why don't you support me by buying me a coffee? diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java new file mode 100644 index 0000000000000..7ceeb3c631450 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java @@ -0,0 +1,499 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + +import java.util.*; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; +import org.openhab.binding.lgthinq.lgapi.LGApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.LGApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGAirConditionerHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGAirConditionerHandler extends BaseThingHandler implements LGDeviceThing { + public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner + + public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); + private final LGDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final ChannelUID opModeChannelUID; + private final ChannelUID opModeFanSpeedUID; + @Nullable + private ACCapability acCapability; + private final String lgPlatfomType; + private final Logger logger = LoggerFactory.getLogger(LGAirConditionerHandler.class); + @NonNullByDefault + private final LGApiClientService lgApiClientService; + private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; + // Bridges status that this thing must top scanning for state change + private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, + ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + ThingStatusDetail.CONFIGURATION_ERROR); + private static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); + private @Nullable ScheduledFuture thingStatePoolingJob; + private @Nullable Future commandExecutorQueueJob; + // *** Long running isolated threadpools. + private final ScheduledExecutorService poolingScheduler = Executors.newScheduledThreadPool(1); + private final ExecutorService executorService = Executors.newFixedThreadPool(1); + + private boolean monitorV1Began = false; + private String monitorWorkId = ""; + private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); + @NonNullByDefault + private String bridgeId = ""; + + public LGAirConditionerHandler(Thing thing, LGDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing); + this.stateDescriptionProvider = stateDescriptionProvider; + lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); + lgApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGApiV1ClientServiceImpl.getInstance() + : LGApiV2ClientServiceImpl.getInstance(); + opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); + opModeFanSpeedUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); + } + + static class AsyncCommandParams { + final String channelUID; + final Command command; + + public AsyncCommandParams(String channelUUID, Command command) { + this.channelUID = channelUUID; + this.command = command; + } + } + + @Override + public Collection> getServices() { + return super.getServices(); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + logger.debug("bridgeStatusChanged {}", bridgeStatusInfo); + initializeThing(bridgeStatusInfo.getStatus()); + } + + private void initializeThing(@Nullable ThingStatus bridgeStatus) { + logger.debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); + String deviceId = getThing().getUID().getId(); + if (!SUPPORTED_LG_PLATFORMS.contains(lgPlatfomType)) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "LG Platform [" + lgPlatfomType + "] not supported for this thing"); + return; + } + Bridge bridge = getBridge(); + if (!deviceId.isBlank()) { + try { + updateChannelDynStateDescription(); + } catch (LGApiException e) { + logger.error( + "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); + } + if (bridge != null) { + LGBridgeHandler handler = (LGBridgeHandler) bridge.getHandler(); + // registry this thing to the bridge + if (handler == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } else { + handler.registryListenerThing(this); + if (bridgeStatus == ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error-no-device-id"); + } + // finally, start command queue, regardless os the thing state, as we can still try to send commands without + // property ONLINE (the successful result from command request can put the thing in ONLINE status). + startCommandExecutorQueueJob(); + } + + private void startCommandExecutorQueueJob() { + if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { + commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); + } + } + + private ExecutorService getExecutorService() { + return executorService; + } + + private void stopCommandExecutorQueueJob() { + if (commandExecutorQueueJob != null) { + commandExecutorQueueJob.cancel(true); + } + } + + protected void startThingStatePooling() { + if (thingStatePoolingJob == null || thingStatePoolingJob.isDone()) { + thingStatePoolingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, + DEFAULT_STATE_POOLING_UPDATE_DELAY, TimeUnit.SECONDS); + } + } + + private void updateThingStateFromLG() { + try { + ACSnapShot shot = getSnapshotDeviceAdapter(getDeviceId()); + if (shot == null) { + // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. + return; + } + if (!shot.isOnline()) { + if (getThing().getStatus() != ThingStatus.OFFLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); + updateState(CHANNEL_POWER_ID, + OnOffType.from(shot.getAcPowerStatus() == DevicePowerState.DV_POWER_OFF)); + } + return; + } + if (shot.getOperationMode() != null) { + updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); + } + if (shot.getAcPowerStatus() != null) { + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getAcPowerStatus() == DevicePowerState.DV_POWER_ON)); + // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as + // soon as LG WebOs do) + } + if (shot.getAcFanSpeed() != null) { + updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(shot.getAirWindStrength())); + } + if (shot.getCurrentTemperature() != null) { + updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(shot.getCurrentTemperature())); + } + if (shot.getTargetTemperature() != null) { + updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); + } + updateStatus(ThingStatus.ONLINE); + } catch (LGThinqException e) { + logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", + getDeviceAlias(), getDeviceId(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + + private ScheduledExecutorService getLocalScheduler() { + return poolingScheduler; + } + + private String getBridgeId() { + if (bridgeId.isBlank() && getBridge() == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + logger.error("Configuration error um Thinq Thing - No Bridge defined for the thing."); + return "UNKNOWN"; + } else if (bridgeId.isBlank() && getBridge() != null) { + bridgeId = getBridge().getUID().getId(); + } + return bridgeId; + } + + private void forceStopDeviceV1Monitor(String deviceId) { + try { + monitorV1Began = false; + lgApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + } catch (Exception e) { + logger.error("Error stopping LG Device monitor", e); + } + } + + @NonNull + private String emptyIfNull(@Nullable String value) { + return value == null ? "" : "" + value; + } + + @Override + public void updateChannelDynStateDescription() throws LGApiException { + ACCapability acCap = getAcCapabilities(); + if (isLinked(opModeChannelUID)) { + List options = new ArrayList<>(); + acCap.getSupportedOpMode().forEach((v) -> options + .add(new StateOption(emptyIfNull(acCap.getOpMod().get(v)), emptyIfNull(CAP_OP_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(opModeChannelUID, options); + } + if (isLinked(opModeFanSpeedUID)) { + List options = new ArrayList<>(); + acCap.getSupportedFanSpeed().forEach((v) -> options + .add(new StateOption(emptyIfNull(acCap.getFanSpeed().get(v)), emptyIfNull(CAP_FAN_SPEED.get(v))))); + stateDescriptionProvider.setStateOptions(opModeFanSpeedUID, options); + } + } + + @Override + public ACCapability getAcCapabilities() throws LGApiException { + if (acCapability == null) { + acCapability = lgApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); + } + return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); + } + + @Nullable + private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiException { + // analise de platform version + if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { + return lgApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); + } else { + try { + if (!monitorV1Began) { + monitorWorkId = lgApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorV1Began = true; + } + } catch (LGDeviceV1OfflineException e) { + forceStopDeviceV1Monitor(deviceId); + ACSnapShot shot = new ACSnapShotV1(); + shot.setOnline(false); + return shot; + } catch (Exception e) { + forceStopDeviceV1Monitor(deviceId); + throw new LGApiException("Error starting device monitor in LG API for the device:" + deviceId, e); + } + int retries = 10; + ACSnapShot shot; + while (retries > 0) { + // try to get monitoring data result 3 times. + try { + shot = lgApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + if (shot != null) { + return shot; + } + Thread.sleep(500); + retries--; + } catch (LGDeviceV1MonitorExpiredException e) { + forceStopDeviceV1Monitor(deviceId); + logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); + return null; + } catch (Exception e) { + // If it can't get monitor handler, then stop monitor and restart the process again in new + // interaction + // Force restart monitoring because of the errors returned (just in case) + forceStopDeviceV1Monitor(deviceId); + throw new LGApiException("Error getting monitor data for the device:" + deviceId, e); + } + } + forceStopDeviceV1Monitor(deviceId); + throw new LGApiException("Exhausted trying to get monitor data for the device:" + deviceId); + } + } + + protected void stopThingStatePooling() { + if (thingStatePoolingJob != null && !thingStatePoolingJob.isDone()) { + logger.debug("Stopping LG thinq pooling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePoolingJob.cancel(true); + } + } + + private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { + if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { + // start the thing pooling + startThingStatePooling(); + } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE + && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { + // comunication error is not a specific Bridge error, then we must analise it to give + // this thinq the change to recovery from communication errors + if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR + || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { + // in case of status offline, I only stop the pooling if is not an COMMUNICATION_ERROR or if + // the bridge is out + stopThingStatePooling(); + } + + } + lastThingStatus = newStatus; + } + + @Override + protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { + handleStatusChanged(newStatus, statusDetail); + super.updateStatus(newStatus, statusDetail, description); + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it + } + + @Override + public String getDeviceId() { + return getThing().getUID().getId(); + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceModelName() { + return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public boolean onDeviceStateChanged() { + // TODO - HANDLE IT + return false; + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT + } + + @Override + public void onDeviceGone() { + // TODO - HANDLE IT + } + + @Override + public void dispose() { + if (thingStatePoolingJob != null) { + thingStatePoolingJob.cancel(true); + stopThingStatePooling(); + stopCommandExecutorQueueJob(); + thingStatePoolingJob = null; + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + updateThingStateFromLG(); + } else { + AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); + try { + // Ensure commands are send in a pipe per device. + commandBlockQueue.add(params); + } catch (IllegalStateException ex) { + logger.error( + "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, + "Device Command Queue is Busy"); + } + + } + } + + private final Runnable queuedCommandExecutor = new Runnable() { + @Override + public void run() { + while (true) { + AsyncCommandParams params; + try { + params = commandBlockQueue.take(); + } catch (InterruptedException e) { + logger.debug("Interrupting async command queue executor."); + return; + } + Command command = params.command; + + try { + switch (params.channelUID) { + case CHANNEL_MOD_OP_ID: { + if (params.command instanceof DecimalType) { + lgApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); + } + break; + } + case CHANNEL_FAN_SPEED_ID: { + if (command instanceof DecimalType) { + lgApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); + } + break; + } + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + case CHANNEL_TARGET_TEMP_ID: { + double targetTemp; + if (command instanceof DecimalType) { + targetTemp = ((DecimalType) command).doubleValue(); + } else if (command instanceof QuantityType) { + targetTemp = ((QuantityType) command).doubleValue(); + } else { + logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); + break; + } + lgApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + ACTargetTmp.statusOf(targetTemp)); + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, + params.channelUID); + } + } + } catch (LGThinqException e) { + logger.error("Error executing Command {} to the channel {}. Thing goes offline until retry", + command, params.channelUID, e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + } + }; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java new file mode 100644 index 0000000000000..a74b5c73c9ce8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import org.openhab.core.events.EventPublisher; +import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; +import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; +import org.openhab.core.thing.link.ItemChannelLinkRegistry; +import org.openhab.core.thing.type.DynamicStateDescriptionProvider; +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; + +/** + * The {@link LGDeviceThing} is a main interface contract for all LG Thinq things + * + * @author Nemer Daud - Initial contribution + */ +@Component(service = { DynamicStateDescriptionProvider.class, LGDeviceDynStateDescriptionProvider.class }) +public class LGDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { + @Activate + public LGDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // + final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // + final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { + this.eventPublisher = eventPublisher; + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java new file mode 100644 index 0000000000000..54cd67361e071 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.lgapi.model.ACCapability; +import org.openhab.binding.lgthinq.lgapi.model.LGDevice; + +/** + * The {@link LGDeviceThing} is a main interface contract for all LG Thinq things + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGDeviceThing { + + void onDeviceAdded(@NonNullByDefault LGDevice device); + + String getDeviceId(); + + String getDeviceAlias(); + + String getDeviceModelName(); + + String getDeviceUriJsonConfig(); + + boolean onDeviceStateChanged(); + + void onDeviceRemoved(); + + void onDeviceGone(); + + void updateChannelDynStateDescription() throws LGApiException; + + ACCapability getAcCapabilities() throws LGApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java new file mode 100644 index 0000000000000..5c2767108f97c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -0,0 +1,121 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import java.io.File; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.OpenHAB; + +/** + * The {@link LGThinqBindingConstants} class defines common constants, which are + * used across the whole binding. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqBindingConstants { + + public static final String BINDING_ID = "lgthinq"; + + public static final String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; + public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; + public static String BASE_CAP_CONFIG_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-cap.json"; + public static final String V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; + public static final String V2_USER_INFO = "/users/profile"; + public static final String V2_API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; + public static final String V2_CLIENT_ID = "65260af7e8e6547b51fdccf930097c51eb9885a508d3fddfa9ee6cdec22ae1bd"; + public static final String V2_SVC_PHASE = "OP"; + public static final String V2_APP_LEVEL = "PRD"; + public static final String V2_APP_OS = "LINUX"; + public static final String V2_APP_TYPE = "NUTS"; + public static final String V2_APP_VER = "3.0.1700"; + public static final String V2_SESSION_LOGIN_PATH = "/emp/v2.0/account/session/"; + public static final String V2_LS_PATH = "/service/application/dashboard"; + public static final String V2_DEVICE_CONFIG_PATH = "service/devices/"; + public static final String V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/control-sync"; + public static final String V1_START_MON_PATH = "rti/rtiMon"; + public static final String V1_POOL_MON_PATH = "rti/rtiResult"; + public static final String V1_CONTROL_OP = "rti/rtiControl"; + public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; + public static final String GATEWAY_SERVICE_PATH = "/v1/service/application/gateway-uri"; + public static String GATEWAY_URL = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH; + public static final String PRE_LOGIN_PATH = "/preLogin"; + public static final String SECURITY_KEY = "nuts_securitykey"; + public static final String APP_KEY = "wideq"; + public static final String DATA_ROOT = "result"; + public static final String POST_DATA_ROOT = "lgedmRoot"; + public static final String RETURN_CODE_ROOT = "resultCode"; + public static final String RETURN_MESSAGE_ROOT = "returnMsg"; + public static final String SVC_CODE = "SVC202"; + public static final String OAUTH_SECRET_KEY = "c053c2a6ddeb7ad97cb0eed0dcb31cf8"; + public static final String OAUTH_CLIENT_KEY = "LGAO722A02"; + public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss +0000"; + public static final String DEFAULT_COUNTRY = "US"; + public static final String DEFAULT_LANGUAGE = "en-US"; + public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; + public static String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com/emp/oauth2/token/empsession"; + // v2 + public static final String API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; + + // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, + // and the build id of the thinq app it can also just be a random + // string, we use the same client id used for oauth + public static final String CLIENT_ID = "LGAO221A02"; + public static final String MESSAGE_ID = "wideq"; + public static final String SVC_PHASE = "OP"; + public static final String APP_LEVEL = "PRD"; + public static final String APP_OS = "ANDROID"; + public static final String APP_TYPE = "NUTS"; + public static final String APP_VER = "3.5.1200"; + + public static final String DEVICE_ID = "device_id"; + public static final String MODEL_NAME = "model_name"; + public static final String DEVICE_ALIAS = "device_alias"; + public static final String MODEL_URL_INFO = "model_url_indo"; + public static final String PLATFORM_TYPE = "platform_type"; + public static final String PLATFORM_TYPE_V1 = "thinq1"; + public static final String PLATFORM_TYPE_V2 = "thinq2"; + + public static final int SEARCH_TIME = 20; + // delay between each devices's scan for state changes (in seconds) + public static final int DEFAULT_STATE_POOLING_UPDATE_DELAY = 30; + // CHANNEL IDS + public static final String CHANNEL_MOD_OP_ID = "op_mode"; + public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; + public static final String CHANNEL_POWER_ID = "power"; + public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; + public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; + + public static final Map CAP_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", + "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", + "@AC_MAIN_OPERATION_MODE_HEAT_W", "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", + "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", "@AC_MAIN_OPERATION_MODE_AI_W", "AI", + "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", + "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); + + public static final Map CAP_FAN_SPEED = Map.ofEntries( + Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), + Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), Map.entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), + Map.entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), Map.entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Nature"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), + Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), + Map.entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), + Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java new file mode 100644 index 0000000000000..c3a618b7dae5e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqConfiguration} class contains fields mapping thing configuration parameters. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqConfiguration { + /** + * Sample configuration parameters. Replace with your own. + */ + public String username = ""; + public String password = ""; + public String country = ""; + public String language = ""; + public Integer poolingIntervalSec = 0; + + public LGThinqConfiguration() { + } + + public LGThinqConfiguration(String username, String password, String country, String language, + Integer poolingIntervalSec) { + this.username = username; + this.password = password; + this.country = country; + this.language = language; + this.poolingIntervalSec = poolingIntervalSec; + } + + public String getUsername() { + return username; + } + + public String getPassword() { + return password; + } + + public String getCountry() { + return country; + } + + public String getLanguage() { + return language; + } + + public Integer getPoolingIntervalSec() { + return poolingIntervalSec; + } + + public void setUsername(String username) { + this.username = username; + } + + public void setPassword(String password) { + this.password = password; + } + + public void setCountry(String country) { + this.country = country; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setPoolingIntervalSec(Integer poolingIntervalSec) { + this.poolingIntervalSec = poolingIntervalSec; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java new file mode 100644 index 0000000000000..803d80a477971 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGAirConditionerHandler.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler.THING_TYPE_BRIDGE; + +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; +import org.openhab.core.config.core.Configuration; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; +import org.openhab.core.thing.ThingUID; +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; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinqHandlerFactory} is responsible for creating things and thing + * handlers. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.lgthinq") +public class LGThinqHandlerFactory extends BaseThingHandlerFactory { + + private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, + THING_TYPE_BRIDGE); + private final Logger logger = LoggerFactory.getLogger(LGThinqHandlerFactory.class); + private final LGDeviceDynStateDescriptionProvider stateDescriptionProvider; + + @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_AIR_CONDITIONER.equals(thingTypeUID)) { + return new LGAirConditionerHandler(thing, stateDescriptionProvider); + } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return new LGBridgeHandler((Bridge) thing); + } + logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); + return null; + } + + @Override + public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, + @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { + if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, null); + } else if (LGAirConditionerHandler.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } + return null; + } + + @Activate + public LGThinqHandlerFactory(final @Reference LGDeviceDynStateDescriptionProvider stateDescriptionProvider) { + this.stateDescriptionProvider = stateDescriptionProvider; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java new file mode 100644 index 0000000000000..64c5535923928 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.api; + +import java.io.Serializable; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link Gateway} hold informations about the LG Gateway + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class Gateway implements Serializable { + private String empBaseUri = ""; + private String loginBaseUri = ""; + private String apiRootV1 = ""; + private String apiRootV2 = ""; + private String authBase = ""; + private String language = ""; + private String country = ""; + private String username = ""; + private String password = ""; + + public Gateway() { + } + + public Gateway(Map params, String language, String country) { + this.apiRootV2 = Objects.requireNonNullElse(params.get("thinq2Uri"), ""); + this.apiRootV1 = Objects.requireNonNullElse(params.get("thinq1Uri"), ""); + this.loginBaseUri = Objects.requireNonNullElse(params.get("empSpxUri"), ""); + this.authBase = Objects.requireNonNullElse(params.get("empUri"), ""); + this.empBaseUri = Objects.requireNonNullElse(params.get("empTermsUri"), ""); + this.language = language; + this.country = country; + } + + public String getEmpBaseUri() { + return empBaseUri; + } + + public String getApiRootV2() { + return apiRootV2; + } + + public String getAuthBase() { + return authBase; + } + + public String getLanguage() { + return language; + } + + public String getCountry() { + return country; + } + + public String getLoginBaseUri() { + return loginBaseUri; + } + + public String getApiRootV1() { + return apiRootV1; + } + + public void setEmpBaseUri(String empBaseUri) { + this.empBaseUri = empBaseUri; + } + + public void setLoginBaseUri(String loginBaseUri) { + this.loginBaseUri = loginBaseUri; + } + + public void setApiRootV1(String apiRootV1) { + this.apiRootV1 = apiRootV1; + } + + public void setApiRootV2(String apiRootV2) { + this.apiRootV2 = apiRootV2; + } + + public void setAuthBase(String authBase) { + this.authBase = authBase; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java index 9f678b54f2b28..c008ecef60891 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java @@ -30,7 +30,7 @@ public class TokenResult implements Serializable { private Date generatedTime = new Date(); private String oauthBackendUrl = ""; private UserInfo userInfo = new UserInfo(); - private LGThinqGateway gatewayInfo = new LGThinqGateway(); + private Gateway gatewayInfo = new Gateway(); public TokenResult(String accessToken, String refreshToken, int expiresIn, Date generatedTime, String ouathBackendUrl) { @@ -45,11 +45,11 @@ public TokenResult(String accessToken, String refreshToken, int expiresIn, Date public TokenResult() { } - public LGThinqGateway getGatewayInfo() { + public Gateway getGatewayInfo() { return gatewayInfo; } - public void setGatewayInfo(LGThinqGateway gatewayInfo) { + public void setGatewayInfo(Gateway gatewayInfo) { this.gatewayInfo = gatewayInfo; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java new file mode 100644 index 0000000000000..95ca53e421209 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGApiException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGApiException extends LGThinqException { + public LGApiException(String message, Throwable cause) { + super(message, cause); + } + + public LGApiException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java new file mode 100644 index 0000000000000..3272c29338952 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGDeviceV1MonitorExpiredException} - Normally caught by V1 API in monitoring device. + * After long-running moniotor, it indicates the need to refresh the monitor. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGDeviceV1MonitorExpiredException extends LGThinqException { + public LGDeviceV1MonitorExpiredException(String message, Throwable cause) { + super(message, cause); + } + + public LGDeviceV1MonitorExpiredException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java new file mode 100644 index 0000000000000..dee34280a8c21 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java @@ -0,0 +1,33 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGDeviceV1OfflineException} - Normally caught by V1 API in monitoring device. + * When the device is OFFLINE (away from internet), the API doesn't return data information and this + * exception is thrown to indicate that this device is offline for monitoring + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGDeviceV1OfflineException extends LGThinqException { + public LGDeviceV1OfflineException(String message, Throwable cause) { + super(message, cause); + } + + public LGDeviceV1OfflineException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java new file mode 100644 index 0000000000000..20942017c4a1a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java @@ -0,0 +1,27 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.errors; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGGatewayException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGGatewayException extends LGThinqException { + public LGGatewayException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java index 03d018234ea18..b6f4e1dcb9da3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java @@ -20,7 +20,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class RefreshTokenException extends LGThinqApiException { +public class RefreshTokenException extends LGApiException { public RefreshTokenException(String message, Throwable cause) { super(message, cause); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java new file mode 100644 index 0000000000000..d9f8f36ab93f3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java @@ -0,0 +1,31 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.handler; + +import org.openhab.binding.lgthinq.internal.LGDeviceThing; +import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; + +/** + * The {@link LGBridge} + * + * @author Nemer Daud - Initial contribution + */ +public interface LGBridge { + void registerDiscoveryListener(LGThinqDiscoveryService listener); + + void registryListenerThing(LGDeviceThing thing); + + void unRegistryListenerThing(LGDeviceThing thing); + + LGDeviceThing getThingByDeviceId(String deviceId); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java new file mode 100644 index 0000000000000..d6a40ab443285 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java @@ -0,0 +1,315 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGDeviceThing; +import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; +import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgapi.LGApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.core.config.core.status.ConfigStatusMessage; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGBridgeHandler} + * + * @author Nemer Daud - Initial contribution + */ +public class LGBridgeHandler extends ConfigStatusBridgeHandler implements LGBridge { + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(LGThinqBindingConstants.BINDING_ID, "bridge"); + + private Map lGDeviceRegister = new ConcurrentHashMap<>(); + private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); + + static { + var logger = LoggerFactory.getLogger(LGBridgeHandler.class); + try { + File directory = new File(THINQ_USER_DATA_FOLDER); + if (!directory.exists()) { + directory.mkdir(); + } + } catch (Exception e) { + logger.warn("Unable to setup thinq userdata directory: {}", e.getMessage()); + } + } + private final Logger logger = LoggerFactory.getLogger(LGBridgeHandler.class); + private LGThinqConfiguration lgthinqConfig; + private TokenManager tokenManager; + private LGThinqDiscoveryService discoveryService; + private LGApiClientService lgApiClient; + private @Nullable Future initJob; + private @Nullable ScheduledFuture devicePollingJob; + + public LGBridgeHandler(Bridge bridge) { + super(bridge); + tokenManager = TokenManager.getInstance(); + lgApiClient = LGApiV1ClientServiceImpl.getInstance(); + lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); + } + + final ReentrantLock pollingLock = new ReentrantLock(); + + /** + * Abstract Runnable Pooling Class to schedule sincronization status of the Bridge Thing Kinds ! + */ + abstract class PollingRunnable implements Runnable { + protected final String bridgeName; + protected LGThinqConfiguration lgthinqConfig; + + PollingRunnable(String bridgeName) { + this.bridgeName = bridgeName; + } + + @Override + public void run() { + try { + pollingLock.lock(); + // check if configuration file already exists + if (tokenManager.isOauthTokenRegistered(bridgeName)) { + logger.debug( + "Token authentication process has been already done. Skip first authentication process."); + try { + tokenManager.getValidRegisteredToken(bridgeName); + } catch (IOException e) { + logger.error("Error reading LGThinq TokenFile", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, + "@text/error.toke-file-corrupted"); + return; + } catch (RefreshTokenException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, + "@text/error.toke-refresh"); + return; + } + } else { + try { + tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), + lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword()); + if (tokenManager.getValidRegisteredToken(bridgeName) != null) { + logger.debug("Successful getting token from LG API"); + } + } catch (IOException e) { + logger.debug( + "I/O error accessing json token configuration file. Updating Bridge Status to OFFLINE.", + e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.toke-file-access-error"); + return; + } catch (LGThinqException e) { + logger.debug("Error accessing LG API. Updating Bridge Status to OFFLINE.", e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, + "@text/error.lgapi-communication-error"); + return; + } + } + if (thing.getStatus() != ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } + + try { + doConnectedRun(); + } catch (Exception e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.lgapi-getting-devices"); + } + + } finally { + pollingLock.unlock(); + } + } + + protected abstract void doConnectedRun() throws IOException, LGThinqException; + } + + @Override + public void registerDiscoveryListener(LGThinqDiscoveryService listener) { + if (discoveryService == null) { + discoveryService = listener; + } + } + + /** + * Registry the OSGi services used by this Bridge. + * Eventually, the Discovery Service will be activated with this bridge as argument. + * + * @return Services to be registered to OSGi. + */ + @Override + public Collection> getServices() { + return Collections.singleton(LGThinqDiscoveryService.class); + } + + @Override + public void registryListenerThing(LGDeviceThing thing) { + if (lGDeviceRegister.get(thing.getDeviceId()) == null) { + lGDeviceRegister.put(thing.getDeviceId(), thing); + // remove device from discovery list, if exists. + LGDevice device = lastDevicesDiscovered.get(thing.getDeviceId()); + if (device != null) { + discoveryService.removeLgDeviceDiscovery(device); + } + } + } + + @Override + public void unRegistryListenerThing(LGDeviceThing thing) { + lGDeviceRegister.remove(thing.getDeviceId()); + } + + @Override + public LGDeviceThing getThingByDeviceId(String deviceId) { + return lGDeviceRegister.get(deviceId); + } + + private LGDevicePollingRunnable lgDevicePollingRunnable; + + class LGDevicePollingRunnable extends PollingRunnable { + public LGDevicePollingRunnable(String bridgeName) { + super(bridgeName); + } + + @Override + protected void doConnectedRun() throws LGThinqException { + Map lastDevicesDiscoveredCopy = new HashMap<>(lastDevicesDiscovered); + for (final LGDevice device : lgApiClient.listAccountDevices(bridgeName)) { + String deviceId = device.getDeviceId(); + + if (lGDeviceRegister.get(deviceId) == null) { + logger.debug("Adding new LG Device to things registry with id:{}", deviceId); + if (discoveryService != null) { + discoveryService.addLgDeviceDiscovery(bridgeName, device); + } + } + lastDevicesDiscovered.put(deviceId, device); + lastDevicesDiscoveredCopy.remove(deviceId); + } + // the rest in lastDevicesDiscoveredCopy is not more registered in LG API. Remove from discovery + lastDevicesDiscoveredCopy.forEach((deviceId, device) -> { + logger.trace("LG Device '{}' removed.", deviceId); + lastDevicesDiscovered.remove(deviceId); + + LGDeviceThing deviceThing = lGDeviceRegister.get(deviceId); + if (deviceThing != null) { + deviceThing.onDeviceRemoved(); + } + if (discoveryService != null && deviceThing != null) { + discoveryService.removeLgDeviceDiscovery(device); + } + }); + } + }; + + @Override + public Collection getConfigStatus() { + List resultList = new ArrayList<>(); + if (lgthinqConfig.username.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("USERNAME").withMessageKeySuffix("missing field") + .withArguments("username").build()); + } + if (lgthinqConfig.password.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("PASSWORD").withMessageKeySuffix("missing field") + .withArguments("password").build()); + } + if (lgthinqConfig.language.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("LANGUAGE").withMessageKeySuffix("missing field") + .withArguments("language").build()); + } + if (lgthinqConfig.country.isEmpty()) { + resultList.add(ConfigStatusMessage.Builder.error("COUNTRY").withMessageKeySuffix("missing field") + .withArguments("country").build()); + + } + return resultList; + } + + @Override + public void handleRemoval() { + if (devicePollingJob != null) + devicePollingJob.cancel(true); + tokenManager.cleanupTokenRegistry(getBridge().getUID().getId()); + super.handleRemoval(); + } + + @Override + public void dispose() { + if (devicePollingJob != null) { + devicePollingJob.cancel(true); + devicePollingJob = null; + } + } + + @Override + public T getConfigAs(Class configurationClass) { + return super.getConfigAs(configurationClass); + } + + @Override + public void initialize() { + logger.debug("Initializing LGThinq bridge handler."); + lgthinqConfig = getConfigAs(LGThinqConfiguration.class); + lgDevicePollingRunnable.lgthinqConfig = lgthinqConfig; + + if (lgthinqConfig.username.isEmpty() || lgthinqConfig.password.isEmpty() || lgthinqConfig.language.isEmpty() + || lgthinqConfig.country.isEmpty()) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/error.mandotory-fields-missing"); + } else { + updateStatus(ThingStatus.UNKNOWN); + startLGDevicePolling(); + } + } + + private void startLGDevicePolling() { + // stop current scheduler, if any + if (devicePollingJob != null && !devicePollingJob.isDone()) { + devicePollingJob.cancel(true); + } + long pollingInterval; + int configPollingInterval = lgthinqConfig.getPoolingIntervalSec(); + // It's not recommended to pool for resources in LG API short intervals to do not enter in BlackList + if (configPollingInterval < 300) { + pollingInterval = TimeUnit.SECONDS.toSeconds(300); + logger.info("Wrong configuration value for polling interval. Using default value: {}s", pollingInterval); + } else { + pollingInterval = configPollingInterval; + } + // submit instantlly and schedule for the next pooling interval. + scheduler.submit(lgDevicePollingRunnable); + devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, pollingInterval, pollingInterval, + TimeUnit.SECONDS); + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java new file mode 100644 index 0000000000000..38fa1d503026f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgapi.model.*; + +/** + * The {@link LGApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGApiClientService { + + List listAccountDevices(String bridgeName) throws LGApiException; + + Map getDeviceSettings(String bridgeName, String deviceId) throws LGApiException; + + /** + * Retrieve actual data from device (its sensors and points states). + * + * @param deviceId device number + * @return return snapshot state of the device + * @throws LGApiException if some error interacting with LG API Server occur. + */ + @Nullable + ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException; + + void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGApiException; + + void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException; + + void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException; + + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGApiException; + + String startMonitor(String bridgeName, String deviceId) + throws LGApiException, LGDeviceV1OfflineException, IOException; + + ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException; + + void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; + + @Nullable + ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) + throws LGApiException, LGDeviceV1MonitorExpiredException, IOException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java new file mode 100644 index 0000000000000..c048f707bd6f5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.lgapi.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link LGApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public abstract class LGApiClientServiceImpl implements LGApiClientService { + private static final Logger logger = LoggerFactory.getLogger(LGApiClientServiceImpl.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + + protected abstract TokenManager getTokenManager(); + + static Map getCommonHeaders(String language, String country, String accessToken, + String userNumber) { + Map headers = new HashMap<>(); + headers.put("Accept", "application/json"); + headers.put("Content-type", "application/json;charset=UTF-8"); + headers.put("x-api-key", V2_API_KEY); + headers.put("x-client-id", V2_CLIENT_ID); + headers.put("x-country-code", country); + headers.put("x-language-code", language); + headers.put("x-message-id", UUID.randomUUID().toString()); + headers.put("x-service-code", SVC_CODE); + headers.put("x-service-phase", V2_SVC_PHASE); + headers.put("x-thinq-app-level", V2_APP_LEVEL); + headers.put("x-thinq-app-os", V2_APP_OS); + headers.put("x-thinq-app-type", V2_APP_TYPE); + headers.put("x-thinq-app-ver", V2_APP_VER); + headers.put("x-thinq-security-key", SECURITY_KEY); + if (!accessToken.isBlank()) + headers.put("x-emp-token", accessToken); + if (!userNumber.isBlank()) + headers.put("x-user-no", userNumber); + return headers; + } + + /** + * Even using V2 URL, this endpoint support grab informations about account devices from V1 and V2. + * + * @return list os LG Devices. + * @throws LGApiException if some communication error occur. + */ + @Override + public List listAccountDevices(String bridgeName) throws LGApiException { + try { + TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); + return handleListAccountDevicesResult(resp); + } catch (Exception e) { + throw new LGApiException("Erros list account devices from LG Server API", e); + } + } + + /** + * Get device settings and snapshot for a specific device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGApiException if some communication error occur. + */ + @Override + public Map getDeviceSettings(String bridgeName, String deviceId) throws LGApiException { + try { + TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) + .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); + return handleDeviceSettingsResult(resp); + } catch (Exception e) { + throw new LGApiException("Erros list account devices from LG Server API", e); + } + } + + private Map handleDeviceSettingsResult(RestResult resp) throws LGApiException { + return genericHandleDeviceSettingsResult(resp, logger, objectMapper); + } + + @SuppressWarnings("unchecked") + static Map genericHandleDeviceSettingsResult(RestResult resp, Logger logger, + ObjectMapper objectMapper) throws LGApiException { + Map deviceSettings; + if (resp.getStatusCode() != 200) { + logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGApiException(String.format("Error calling device settings from LG Server API. The reason is:%s", + resp.getJsonResponse())); + } else { + try { + deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + if (!"0000".equals(deviceSettings.get("resultCode"))) { + throw new LGApiException( + String.format("Status error getting device list. resultCode must be 0000, but was:%s", + deviceSettings.get("resultCode"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + + } + return Objects.requireNonNull((Map) deviceSettings.get("result"), + "Unexpected json result asking for Device Settings. Node 'result' no present"); + } + + @SuppressWarnings("unchecked") + private List handleListAccountDevicesResult(RestResult resp) throws LGApiException { + Map devicesResult; + List devices; + if (resp.getStatusCode() != 200) { + logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGApiException(String.format("Error calling device list from LG Server API. The reason is:%s", + resp.getJsonResponse())); + } else { + try { + devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + if (!"0000".equals(devicesResult.get("resultCode"))) { + throw new LGApiException( + String.format("Status error getting device list. resultCode must be 0000, but was:%s", + devicesResult.get("resultCode"))); + } + List> items = (List>) ((Map) devicesResult + .get("result")).get("item"); + devices = objectMapper.convertValue(items, new TypeReference<>() { + }); + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream.", e); + } + + } + + return devices; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java new file mode 100644 index 0000000000000..628dddcb7d34e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java @@ -0,0 +1,330 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgapi.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link LGApiV1ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGApiV1ClientServiceImpl extends LGApiClientServiceImpl { + private static final LGApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGApiV1ClientServiceImpl.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + private final TokenManager tokenManager; + + static { + instance = new LGApiV1ClientServiceImpl(); + } + + private LGApiV1ClientServiceImpl() { + tokenManager = TokenManager.getInstance(); + } + + public static LGApiClientService getInstance() { + return instance; + } + + @Override + protected TokenManager getTokenManager() { + return tokenManager; + } + + /** + * Get snapshot data from the device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGApiException if some communication error occur. + */ + @Override + @Nullable + public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException { + throw new UnsupportedOperationException("Method not supported in V1 API device."); + } + + public RestResult sendControlCommands(String bridgeName, String deviceId, String keyName, int value) + throws Exception { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + + String payload = String.format( + "{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"Control\"," + " \"cmdOpt\": \"Set\"," + + " \"value\": {\"%s\": \"%d\"}," + " \"deviceId\": \"%s\"," + + " \"workId\": \"%s\"," + " \"data\": \"\"" + " }\n" + "}", + keyName, value, deviceId, UUID.randomUUID().toString()); + return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", newPowerState.commandValue()); + handleV1GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting device power", e); + } + } + + @Override + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "OpMode", newOpMode); + + handleV1GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "WindStrength", newFanSpeed); + + handleV1GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting fan speed", e); + } + } + + @Override + public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "TempCfg", newTargetTemp.commandValue()); + + handleV1GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting target temperature", e); + } + } + + /** + * Start monitor data form specific device. This is old one, works only on V1 API supported devices. + * + * @param deviceId Device ID + * @return Work1 to be uses to grab data during monitoring. + * @throws LGApiException If some communication error occur. + */ + @Override + public String startMonitor(String bridgeName, String deviceId) + throws LGApiException, LGDeviceV1OfflineException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String workerId = UUID.randomUUID().toString(); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + return Objects.requireNonNull((String) handleV1GenericErrorResult(resp).get("workId"), + "Unexpected StartMonitor json result. Node 'workId' not present"); + } + + @NonNull + private Map handleV1GenericErrorResult(@Nullable RestResult resp) + throws LGApiException, LGDeviceV1OfflineException { + Map metaResult; + Map envelope = Collections.emptyMap(); + if (resp == null) { + return envelope; + } + if (resp.getStatusCode() != 200) { + logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGApiException( + String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + envelope = (Map) metaResult.get("lgedmRoot"); + if (envelope == null) { + throw new LGApiException(String.format( + "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); + } else if (!"0000".equals(envelope.get("returnCd"))) { + if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { + // Disconnected Device + throw new LGDeviceV1OfflineException("Device is offline. No data available"); + } + throw new LGApiException( + String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", + metaResult.get("returnCd"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + } + return envelope; + } + + @Override + public void stopMonitor(String bridgeName, String deviceId, String workId) + throws LGApiException, RefreshTokenException, IOException, LGDeviceV1OfflineException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + handleV1GenericErrorResult(resp); + } + + @Override + @Nullable + public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) + throws LGApiException, LGDeviceV1MonitorExpiredException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_POOL_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" + + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" + + " ]\n" + " }\n" + "}", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + Map envelop = null; + // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with + // offline flag. + try { + envelop = handleV1GenericErrorResult(resp); + } catch (LGDeviceV1OfflineException e) { + ACSnapShot shot = new ACSnapShotV2(); + shot.setOnline(false); + return shot; + } + if (envelop.get("workList") != null + && ((Map) envelop.get("workList")).get("returnData") != null) { + Map workList = ((Map) envelop.get("workList")); + if (!"0000".equals(workList.get("returnCode"))) { + LGDeviceV1MonitorExpiredException e = new LGDeviceV1MonitorExpiredException( + String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); + logger.warn("{}", e.getMessage()); + throw e; + } + + String jsonMonDataB64 = (String) workList.get("returnData"); + String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); + ACSnapShot shot = objectMapper.readValue(jsonMon, ACSnapShotV1.class); + shot.setOnline("E".equals(workList.get("deviceState"))); + return shot; + } else { + // no data available yet + return null; + } + } + + private File getCapFileForDevice(String deviceId) { + return new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); + } + + /** + * Get capability em registry/cache on file for next consult + * + * @param deviceId ID of the device + * @param uri URI of the config capanility + * @return return simplified capability + * @throws LGApiException If some error occurr + */ + @Override + @NonNull + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException { + try { + File regFile = getCapFileForDevice(deviceId); + ACCapability acCap = new ACCapability(); + Map mapper; + if (regFile.isFile() && !forceRecreate) { + // reg exists. Retrieve from it + mapper = objectMapper.readValue(regFile, new TypeReference>() { + }); + } else { + RestResult res = RestUtils.getCall(uri, null, null); + mapper = objectMapper.readValue(res.getJsonResponse(), new TypeReference>() { + }); + // try save file + objectMapper.writeValue(getCapFileForDevice(deviceId), mapper); + } + Map cap = (Map) mapper.get("Value"); + if (cap == null) { + throw new LGApiException("Error extracting capabilities supported by the device"); + } + + Map opModes = (Map) cap.get("OpMode"); + if (opModes == null) { + throw new LGApiException("Error extracting opModes supported by the device"); + } else { + Map modes = new HashMap(); + ((Map) opModes.get("option")).forEach((k, v) -> { + modes.put(v, k); + }); + acCap.setOpMod(modes); + } + Map fanSpeed = (Map) cap.get("WindStrength"); + if (fanSpeed == null) { + throw new LGApiException("Error extracting fanSpeed supported by the device"); + } else { + Map fanModes = new HashMap(); + ((Map) fanSpeed.get("option")).forEach((k, v) -> { + fanModes.put(v, k); + }); + acCap.setFanSpeed(fanModes); + + } + // Set supported modes for the device + + Map> supOpModes = (Map>) cap.get("SupportOpMode"); + acCap.setSupportedOpMode(new ArrayList<>(supOpModes.get("option").values())); + acCap.getSupportedOpMode().remove("@NON"); + Map> supFanSpeeds = (Map>) cap + .get("SupportWindStrength"); + acCap.setSupportedFanSpeed(new ArrayList<>(supFanSpeeds.get("option").values())); + acCap.getSupportedFanSpeed().remove("@NON"); + + return acCap; + } catch (IOException e) { + throw new LGApiException("Error reading IO interface", e); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..4075036fb8b65 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java @@ -0,0 +1,274 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.api.RestResult; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.api.TokenResult; +import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgapi.model.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link LGApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGApiV2ClientServiceImpl extends LGApiClientServiceImpl { + private static final LGApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGApiV2ClientServiceImpl.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + private final TokenManager tokenManager; + + static { + instance = new LGApiV2ClientServiceImpl(); + } + + private LGApiV2ClientServiceImpl() { + tokenManager = TokenManager.getInstance(); + } + + @Override + protected TokenManager getTokenManager() { + return tokenManager; + } + + public static LGApiClientService getInstance() { + return instance; + } + + private Map getCommonV2Headers(String language, String country, String accessToken, + String userNumber) { + return getCommonHeaders(language, country, accessToken, userNumber); + } + + /** + * Get snapshot data from the device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGApiException if some communication error occur. + */ + @Override + @Nullable + public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException { + Map deviceSettings = getDeviceSettings(bridgeName, deviceId); + if (deviceSettings.get("snapshot") != null) { + Map snapMap = (Map) deviceSettings.get("snapshot"); + + ACSnapShot shot = objectMapper.convertValue(snapMap, ACSnapShotV2.class); + shot.setOnline((Boolean) snapMap.get("online")); + return shot; + } + return null; + } + + public RestResult sendControlCommands(String bridgeName, String deviceId, String command, String keyName, int value) + throws Exception { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) + .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId)); + Map headers = getCommonV2Headers(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String payload = String.format("{\n" + "\"ctrlKey\": \"basicCtrl\",\n" + "\"command\": \"%s\",\n" + + "\"dataKey\": \"%s\",\n" + "\"dataValue\": %d}", command, keyName, value); + return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", "airState.operation", + newPowerState.commandValue()); + handleV2GenericErrorResult(resp); + } catch (Exception e) { + throw new LGApiException("Error adjusting device power", e); + } + } + + @Override + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.opMode", newOpMode); + handleV2GenericErrorResult(resp); + } catch (LGApiException e) { + throw e; + } catch (Exception e) { + throw new LGApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.windStrength", newFanSpeed); + handleV2GenericErrorResult(resp); + } catch (LGApiException e) { + throw e; + } catch (Exception e) { + throw new LGApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGApiException { + try { + RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.tempState.target", + newTargetTemp.commandValue()); + handleV2GenericErrorResult(resp); + } catch (LGApiException e) { + throw e; + } catch (Exception e) { + throw new LGApiException("Error adjusting operation mode", e); + } + } + + /** + * Start monitor data form specific device. This is old one, works only on V1 API supported devices. + * + * @param deviceId Device ID + * @return Work1 to be uses to grab data during monitoring. + * @throws LGApiException If some communication error occur. + */ + @Override + public String startMonitor(String bridgeName, String deviceId) + throws LGApiException, LGDeviceV1OfflineException, IOException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } + + @Override + @NonNull + @SuppressWarnings("ignoring Map type check") + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException { + try { + File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); + ACCapability acCap = new ACCapability(); + Map mapper; + if (regFile.isFile() || forceRecreate) { + try (InputStream in = new URL(uri).openStream()) { + Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + mapper = objectMapper.readValue(regFile, new TypeReference<>() { + }); + Map cap = (Map) mapper.get("Value"); + if (cap == null) { + throw new LGApiException("Error extracting capabilities supported by the device"); + } + + Map opModes = (Map) cap.get("airState.opMode"); + if (opModes == null) { + throw new LGApiException("Error extracting opModes supported by the device"); + } else { + Map modes = new HashMap(); + ((Map) opModes.get("value_mapping")).forEach((k, v) -> { + modes.put(v, k); + }); + acCap.setOpMod(modes); + } + Map fanSpeed = (Map) cap.get("airState.windStrength"); + if (fanSpeed == null) { + throw new LGApiException("Error extracting fanSpeed supported by the device"); + } else { + Map fanModes = new HashMap(); + ((Map) fanSpeed.get("value_mapping")).forEach((k, v) -> { + fanModes.put(v, k); + }); + acCap.setFanSpeed(fanModes); + + } + // Set supported modes for the device + Map> supOpModes = (Map>) cap + .get("support.airState.opMode"); + acCap.setSupportedOpMode(new ArrayList<>(supOpModes.get("value_mapping").values())); + acCap.getSupportedOpMode().remove("@NON"); + Map> supFanSpeeds = (Map>) cap + .get("support.airState.windStrength"); + acCap.setSupportedFanSpeed(new ArrayList<>(supFanSpeeds.get("value_mapping").values())); + acCap.getSupportedFanSpeed().remove("@NON"); + return acCap; + } catch (IOException e) { + throw new LGApiException("Error reading IO interface", e); + } + } + + private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGApiException { + Map metaResult; + if (resp == null) { + return; + } + if (resp.getStatusCode() != 200) { + logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGApiException( + String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { + }); + if (!"0000".equals(metaResult.get("resultCode"))) { + throw new LGApiException( + String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", + metaResult.get("resultCode"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + + } + } + + private Map handleDeviceSettingsResult(RestResult resp) throws LGApiException { + return genericHandleDeviceSettingsResult(resp, logger, objectMapper); + } + + @Override + public void stopMonitor(String bridgeName, String deviceId, String workId) + throws LGApiException, RefreshTokenException, IOException, LGDeviceV1OfflineException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } + + @Override + @Nullable + public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) + throws LGApiException, LGDeviceV1MonitorExpiredException, IOException { + throw new UnsupportedOperationException("Not supported in V2 API."); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java new file mode 100644 index 0000000000000..8f047b9148c4c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACCapability { + + private Map opMod = Collections.emptyMap(); + private Map fanSpeed = Collections.emptyMap(); + + private List supportedOpMode = Collections.emptyList(); + private List supportedFanSpeed = Collections.emptyList(); + + public Map getOpMod() { + return opMod; + } + + public void setOpMod(Map opMod) { + this.opMod = opMod; + } + + public Map getFanSpeed() { + return fanSpeed; + } + + public void setFanSpeed(Map fanSpeed) { + this.fanSpeed = fanSpeed; + } + + public List getSupportedOpMode() { + return supportedOpMode; + } + + public void setSupportedOpMode(List supportedOpMode) { + this.supportedOpMode = supportedOpMode; + } + + public List getSupportedFanSpeed() { + return supportedFanSpeed; + } + + public void setSupportedFanSpeed(List supportedFanSpeed) { + this.supportedFanSpeed = supportedFanSpeed; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java new file mode 100644 index 0000000000000..1f072c631e5ea --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACSnapShot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACFanSpeed { + F1(2.0), + F2(3.0), + F3(4.0), + F4(5.0), + F5(6.0), + F_AUTO(8.0), + F_UNK(-1); + + private final double funStrength; + + ACFanSpeed(double v) { + this.funStrength = v; + } + + public static ACFanSpeed statusOf(double value) { + switch ((int) value) { + case 2: + return F1; + case 3: + return F2; + case 4: + return F3; + case 5: + return F4; + case 6: + return F5; + case 8: + return F_AUTO; + default: + return F_UNK; + } + } + + /** + * "0": "@AC_MAIN_WIND_STRENGTH_SLOW_W", + * "1": "@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", + * "2": "@AC_MAIN_WIND_STRENGTH_LOW_W", + * "3": "@AC_MAIN_WIND_STRENGTH_LOW_MID_W", + * "4": "@AC_MAIN_WIND_STRENGTH_MID_W", + * "5": "@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", + * "6": "@AC_MAIN_WIND_STRENGTH_HIGH_W", + * "7": "@AC_MAIN_WIND_STRENGTH_POWER_W", + * "8": "@AC_MAIN_WIND_STRENGTH_NATURE_W", + */ + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case F1: + return 2; + case F2: + return 3; + case F3: + return 4; + case F4: + return 5; + case F5: + return 6; + case F_AUTO: + return 8; + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java new file mode 100644 index 0000000000000..baf3c98f0210e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java @@ -0,0 +1,89 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACSnapShot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACOpMode { + COOL(0), + DRY(1), + FAN(2), + AI(3), + HEAT(4), + AIRCLEAN(5), + ENSAV(8), + OP_UNK(-1); + + private final int opMode; + + ACOpMode(int v) { + this.opMode = v; + } + + public static ACOpMode statusOf(int value) { + switch ((int) value) { + case 0: + return COOL; + case 1: + return DRY; + case 2: + return FAN; + case 3: + return AI; + case 4: + return HEAT; + case 5: + return AIRCLEAN; + case 8: + return ENSAV; + default: + return OP_UNK; + } + } + + public int getValue() { + return this.opMode; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case COOL: + return 0;// "@AC_MAIN_OPERATION_MODE_COOL_W" + case DRY: + return 1; // "@AC_MAIN_OPERATION_MODE_DRY_W" + case FAN: + return 2; // "@AC_MAIN_OPERATION_MODE_FAN_W" + case AI: + return 3; // "@AC_MAIN_OPERATION_MODE_AI_W" + case HEAT: + return 4; // "@AC_MAIN_OPERATION_MODE_HEAT_W" + case AIRCLEAN: + return 5; // "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W" + case ENSAV: + return 8; // "AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W" + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java new file mode 100644 index 0000000000000..df8926d89b97a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java @@ -0,0 +1,109 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + +/** + * The {@link ACSnapShot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public abstract class ACSnapShot { + + private int airWindStrength; + + private double targetTemperature; + + private double currentTemperature; + + private int operationMode; + @Nullable + private Integer operation; + @JsonIgnore + private boolean online; + + @JsonIgnore + public DevicePowerState getAcPowerStatus() { + return operation == null ? DevicePowerState.DV_POWER_UNK : DevicePowerState.statusOf(operation); + } + + @JsonIgnore + public ACFanSpeed getAcFanSpeed() { + return ACFanSpeed.statusOf(airWindStrength); + } + + public Integer getAirWindStrength() { + return airWindStrength; + } + + public void setAirWindStrength(Integer airWindStrength) { + this.airWindStrength = airWindStrength; + } + + public Double getTargetTemperature() { + return targetTemperature; + } + + public void setTargetTemperature(Double targetTemperature) { + this.targetTemperature = targetTemperature; + } + + public Double getCurrentTemperature() { + return currentTemperature; + } + + public void setCurrentTemperature(Double currentTemperature) { + this.currentTemperature = currentTemperature; + } + + public Integer getOperationMode() { + return operationMode; + } + + public void setOperationMode(Integer operationMode) { + this.operationMode = operationMode; + } + + @Nullable + public Integer getOperation() { + return operation; + } + + public void setOperation(Integer operation) { + this.operation = operation; + } + + @JsonIgnore + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } + + @Override + public String toString() { + return "ACSnapShot{" + "airWindStrength=" + airWindStrength + ", targetTemperature=" + targetTemperature + + ", currentTemperature=" + currentTemperature + ", operationMode=" + operationMode + ", operation=" + + operation + ", acPowerStatus=" + getAcPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() + + ", acOpMode=" + ", online=" + isOnline() + " }"; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java new file mode 100644 index 0000000000000..34002d3a58d42 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link ACSnapShotV1} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACSnapShotV1 extends ACSnapShot { + + @Override + @JsonProperty("WindStrength") + public Integer getAirWindStrength() { + return super.getAirWindStrength(); + } + + @Override + @JsonProperty("TempCfg") + public Double getTargetTemperature() { + return super.getTargetTemperature(); + } + + @Override + @JsonProperty("TempCur") + public Double getCurrentTemperature() { + return super.getCurrentTemperature(); + } + + @Override + @JsonProperty("OpMode") + public Integer getOperationMode() { + return super.getOperationMode(); + } + + @Override + @JsonProperty("Operation") + @Nullable + public Integer getOperation() { + return super.getOperation(); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java new file mode 100644 index 0000000000000..a373367eccbf5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link ACSnapShotV2} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ACSnapShotV2 extends ACSnapShot { + + @Override + @JsonProperty("airState.windStrength") + public Integer getAirWindStrength() { + return super.getAirWindStrength(); + } + + @Override + @JsonProperty("airState.tempState.target") + public Double getTargetTemperature() { + return super.getTargetTemperature(); + } + + @Override + @JsonProperty("airState.tempState.current") + public Double getCurrentTemperature() { + return super.getCurrentTemperature(); + } + + @Override + @JsonProperty("airState.opMode") + public Integer getOperationMode() { + return super.getOperationMode(); + } + + @Override + @JsonProperty("airState.operation") + @Nullable + public Integer getOperation() { + return super.getOperation(); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java new file mode 100644 index 0000000000000..b43fbe5b1c4a3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java @@ -0,0 +1,93 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link ACTargetTmp} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum ACTargetTmp { + _17(17.0), + _18(18.0), + _19(19.0), + _20(20.0), + _21(21.0), + _22(22.0), + _23(23.0), + _24(24.0), + _25(25.0), + _26(26.0), + _27(27.0), + _28(28.0), + _29(29.0), + _30(30.0), + UNK(-1); + + private final double targetTmp; + + ACTargetTmp(double v) { + this.targetTmp = v; + } + + public static ACTargetTmp statusOf(double value) { + switch ((int) value) { + case 17: + return _17; + case 18: + return _18; + case 19: + return _19; + case 20: + return _20; + case 21: + return _21; + case 22: + return _22; + case 23: + return _23; + case 24: + return _24; + case 25: + return _25; + case 26: + return _26; + case 27: + return _27; + case 28: + return _28; + case 29: + return _29; + case 30: + return _30; + default: + return UNK; + } + } + + public double getValue() { + return this.targetTmp; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + return (int) this.targetTmp; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java new file mode 100644 index 0000000000000..e597165334d71 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link DevicePowerState} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public enum DevicePowerState { + DV_POWER_ON(1), + DV_POWER_OFF(0), + DV_POWER_UNK(-1); + + private final int powerState; + + public double getValue() { + return powerState; + } + + DevicePowerState(int i) { + powerState = i; + } + + public static DevicePowerState statusOf(double value) { + switch ((int) value) { + case 0: + return DV_POWER_OFF; + case 1: + case 256: + case 257: + return DV_POWER_ON; + + default: + return DV_POWER_UNK; + } + } + + public static double valueOf(DevicePowerState dps) { + return dps.powerState; + } + + /** + * Value of command (not state, but command to change the state of device) + * + * @return value of the command to reach the state + */ + public int commandValue() { + switch (this) { + case DV_POWER_ON: + return 257;// "@AC_MAIN_OPERATION_ALL_ON_W" + case DV_POWER_OFF: + return 0; // "@AC_MAIN_OPERATION_OFF_W" + default: + throw new IllegalArgumentException("Enum not accepted for command:" + this); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java new file mode 100644 index 0000000000000..a034870766775 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java @@ -0,0 +1,42 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +/** + * The {@link DeviceTypes} + * + * @author Nemer Daud - Initial contribution + */ +public enum DeviceTypes { + AIR_CONDITIONER(401), + UNKNOWN(-1); + + private final int deviceTypeId; + + public int deviceTypeId() { + return deviceTypeId; + } + + public static DeviceTypes fromDeviceTypeId(int deviceTypeId) { + switch (deviceTypeId) { + case 401: + return AIR_CONDITIONER; + default: + return UNKNOWN; + } + } + + DeviceTypes(int i) { + this.deviceTypeId = i; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java new file mode 100644 index 0000000000000..3413aaeaf0700 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java @@ -0,0 +1,107 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link LGDevice} + * + * @author Nemer Daud - Initial contribution + */ +@JsonIgnoreProperties(ignoreUnknown = true) +@NonNullByDefault +public class LGDevice { + private String modelName = ""; + @JsonProperty("deviceType") + private int deviceTypeId; + private String deviceCode = ""; + private String alias = ""; + private String deviceId = ""; + private String platformType = ""; + private String modelJsonUri = ""; + private boolean online; + + public String getModelName() { + return modelName; + } + + @JsonIgnore + public DeviceTypes getDeviceType() { + return DeviceTypes.fromDeviceTypeId(deviceTypeId); + } + + public void setModelName(String modelName) { + this.modelName = modelName; + } + + public int getDeviceTypeId() { + return deviceTypeId; + } + + public void setDeviceTypeId(int deviceTypeId) { + this.deviceTypeId = deviceTypeId; + } + + public String getDeviceCode() { + return deviceCode; + } + + public void setDeviceCode(String deviceCode) { + this.deviceCode = deviceCode; + } + + public String getModelJsonUri() { + return modelJsonUri; + } + + public void setModelJsonUri(String modelJsonUri) { + this.modelJsonUri = modelJsonUri; + } + + public String getAlias() { + return alias; + } + + public void setAlias(String alias) { + this.alias = alias; + } + + public String getDeviceId() { + return deviceId; + } + + public void setDeviceId(String deviceId) { + this.deviceId = deviceId; + } + + public String getPlatformType() { + return platformType; + } + + public void setPlatformType(String platformType) { + this.platformType = platformType; + } + + public boolean isOnline() { + return online; + } + + public void setOnline(boolean online) { + this.online = online; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index 233bde0b5b4c4..7e8b77f8c8473 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -1,21 +1,22 @@ # binding binding.lgthinq.name = LG Thinq Binding -binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 & v2) +binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 & v1) # thing types -thing-type.lgthinq.401.label = LG Thinq Air Conditioner -thing-type.lgthinq.401.description = LG Thinq Air Conditioner V1 & V2 +thing-type.lgthinq.sample.label = +thing-type.lgthinq.sample.description = -# channel types -channel-type.lgthinq.current-temperature.label = Temperature -channel-type.lgthinq.current-temperature.description = Current Temperature -channel-type.lgthinq.fan-speed.label = Fan Speed -channel-type.lgthinq.fan-speed.description = Air Conditioner Fan Speed -channel-type.lgthinq.operation-mode.label = Operation Mode -channel-type.lgthinq.operation-mode.description = Air Contirioner Operation Mode -channel-type.lgthinq.cool-jet.label = Cool Jet -channel-type.lgthinq.cool-jet.description = Cool Jet Mode +# thing type config description +thing-type.config.lgthinq.sample.hostname.label = +thing-type.config.lgthinq.sample.hostname.description = +thing-type.config.lgthinq.sample.password.label = +thing-type.config.lgthinq.sample.password.description = +thing-type.config.lgthinq.sample.refreshInterval.label = +thing-type.config.lgthinq.sample.refreshInterval.description = +# channel types +channel-type.lgthinq.sample-channel.label = +channel-type.lgthinq.sample-channel.description = # ERRORS error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). @@ -25,6 +26,3 @@ error.toke-refresh = Error refreshing LGThinq Token. Try to delete it (in data d error.lgapi-communication-error = Generic Error in the LG API communication process. error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. error.lgapi-getting-devices = Error getting devices from LG API in scanner process. - -# OFFLINE Statuses -offline.device-disconnected = Device is disconnected. diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java new file mode 100644 index 0000000000000..ebc16454853a2 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java @@ -0,0 +1,313 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.handler; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.any; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.net.URI; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.junit.jupiter.MockitoExtension; +import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; +import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; +import org.openhab.binding.lgthinq.internal.api.RestUtils; +import org.openhab.binding.lgthinq.internal.api.TokenManager; +import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; +import org.openhab.binding.lgthinq.lgapi.LGApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.LGApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ThingUID; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; + +/** + * The {@link LGBridgeTests} + * + * @author Nemer Daud - Initial contribution + */ +@ExtendWith(MockitoExtension.class) +@WireMockTest(httpPort = 8880) +class LGBridgeTests { + private static final Logger logger = LoggerFactory.getLogger(LGBridgeTests.class); + private String fakeBridgeName = "fakeBridgeId"; + private String fakeLanguage = "pt-BR"; + private String fakeCountry = "BR"; + private String fakeUserName = "someone@some.url"; + private String fakePassword = "somepassword"; + private String gtwResponse = "{\n" + " \"resultCode\":\"0000\",\n" + " \"result\":{\n" + + " \"countryCode\":\"BR\",\n" + " \"languageCode\":\"pt-BR\",\n" + + " \"thinq1Uri\":\"http://localhost:8880/api\",\n" + + " \"thinq2Uri\":\"http://localhost:8880/v1\",\n" + " \"empUri\":\"http://localhost:8880\",\n" + + " \"empSpxUri\":\"http://localhost:8880/spx\",\n" + " \"rtiUri\":\"localhost:8880\",\n" + + " \"mediaUri\":\"localhost:8880\",\n" + " \"appLatestVer\":\"4.0.14230\",\n" + + " \"appUpdateYn\":\"N\",\n" + " \"appLink\":\"market://details?id=com.lgeha.nuts\",\n" + + " \"uuidLoginYn\":\"N\",\n" + " \"lineLoginYn\":\"N\",\n" + " \"lineChannelId\":\"\",\n" + + " \"cicTel\":\"4004-5400\",\n" + " \"cicUri\":\"\",\n" + " \"isSupportVideoYn\":\"N\",\n" + + " \"countryLangDescription\":\"Brasil/Português\",\n" + + " \"empTermsUri\":\"http://localhost:8880\",\n" + + " \"googleAssistantUri\":\"https://assistant.google.com/services/invoke/uid/000000d26892b8a3\",\n" + + " \"smartWorldUri\":\"\",\n" + " \"racUri\":\"br.rac.lgeapi.com\",\n" + + " \"cssUri\":\"https://aic-common.lgthinq.com\",\n" + + " \"cssWebUri\":\"http://s3-an2-op-t20-css-web-resource.s3-website.ap-northeast-2.amazonaws.com\",\n" + + " \"iotssUri\":\"https://aic-iotservice.lgthinq.com\",\n" + " \"chatBotUri\":\"\",\n" + + " \"autoOrderSetUri\":\"\",\n" + " \"autoOrderManageUri\":\"\",\n" + + " \"aiShoppingUri\":\"\",\n" + " \"onestopCall\":\"\",\n" + + " \"onestopEngineerUri\":\"\",\n" + " \"hdssUri\":\"\",\n" + " \"amazonDrsYn\":\"N\",\n" + + " \"features\":{\n" + " \"supportTvIoTServerYn\":\"Y\",\n" + + " \"disableWeatherCard\":\"Y\",\n" + " \"thinqCss\":\"Y\",\n" + + " \"bleConfirmYn\":\"Y\",\n" + " \"tvRcmdContentYn\":\"Y\",\n" + + " \"supportProductManualYn\":\"N\",\n" + " \"clientDbYn\":\"Y\",\n" + + " \"androidAutoYn\":\"Y\",\n" + " \"searchYn\":\"Y\",\n" + + " \"thinqFaq\":\"Y\",\n" + " \"thinqNotice\":\"Y\",\n" + + " \"groupControlYn\":\"Y\",\n" + " \"inAppReviewYn\":\"Y\",\n" + + " \"cicSupport\":\"Y\",\n" + " \"qrRegisterYn\":\"Y\",\n" + + " \"supportBleYn\":\"Y\"\n" + " },\n" + " \"serviceCards\":[\n" + " \n" + + " ],\n" + " \"uris\":{\n" + + " \"takeATourUri\":\"https://s3-us2-op-t20-css-contents.s3.us-west-2.amazonaws.com/workexperience-new/ios/no-version/index.html\",\n" + + " \"gscsUri\":\"https://gscs-america.lge.com\",\n" + + " \"amazonDartUri\":\"https://shs.lgthinq.com\"\n" + " }\n" + " }\n" + "}"; + private String preLoginResponse = "{\n" + " \"encrypted_pw\":\"SOME_DUMMY_ENC_PWD\",\n" + + " \"signature\":\"SOME_DUMMY_SIGNATURE\",\n" + " \"tStamp\":\"1643236928\"\n" + "}"; + private String userIdType = "LGE"; + private String loginSessionId = "emp;11111111;222222222"; + private String loginSessionResponse = "{\n" + " \"account\":{\n" + " \"loginSessionID\":\"" + loginSessionId + + "\",\n" + " \"userID\":\"" + fakeUserName + "\",\n" + " \"userIDType\":\"" + userIdType + + "\",\n" + " \"dateOfBirth\":\"05-05-1978\",\n" + " \"country\":\"BR\",\n" + + " \"countryName\":\"Brazil\",\n" + " \"blacklist\":\"N\",\n" + " \"age\":\"43\",\n" + + " \"isSubscribe\":\"N\",\n" + " \"changePw\":\"N\",\n" + " \"toEmailId\":\"N\",\n" + + " \"periodPW\":\"N\",\n" + " \"lgAccount\":\"Y\",\n" + " \"isService\":\"Y\",\n" + + " \"userNickName\":\"faker\",\n" + " \"termsList\":[\n" + " \n" + " ],\n" + + " \"userIDList\":[\n" + " {\n" + " \"lgeIDList\":[\n" + " {\n" + + " \"lgeIDType\":\"LGE\",\n" + " \"userID\":\"" + fakeUserName + "\"\n" + + " }\n" + " ]\n" + " }\n" + " ],\n" + " \"serviceList\":[\n" + + " {\n" + " \"svcCode\":\"SVC202\",\n" + " \"svcName\":\"LG ThinQ\",\n" + + " \"isService\":\"Y\",\n" + " \"joinDate\":\"30-04-2020\"\n" + " },\n" + + " {\n" + " \"svcCode\":\"SVC710\",\n" + " \"svcName\":\"EMP OAuth\",\n" + + " \"isService\":\"Y\",\n" + " \"joinDate\":\"30-04-2020\"\n" + " }\n" + + " ],\n" + " \"displayUserID\":\"faker\",\n" + " \"notiList\":{\n" + + " \"totCount\":\"0\",\n" + " \"list\":[\n" + " \n" + " ]\n" + + " },\n" + " \"authUser\":\"N\",\n" + " \"dummyIdFlag\":\"N\"\n" + " }\n" + "}"; + private String userInfoReturned = "{\n" + " \"status\":1,\n" + " \"account\":{\n" + " \"userID\":\"" + + fakeUserName + "\",\n" + " \"userNo\":\"BR2005200239023\",\n" + " \"userIDType\":\"LGE\",\n" + + " \"displayUserID\":\"faker\",\n" + " \"userIDList\":[\n" + " {\n" + + " \"lgeIDList\":[\n" + " {\n" + " \"lgeIDType\":\"LGE\",\n" + + " \"userID\":\"" + fakeUserName + "\"\n" + " }\n" + " ]\n" + + " }\n" + " ],\n" + " \"dateOfBirth\":\"05-05-1978\",\n" + " \"country\":\"BR\",\n" + + " \"countryName\":\"Brazil\",\n" + " \"blacklist\":\"N\",\n" + " \"age\":\"45\",\n" + + " \"isSubscribe\":\"N\",\n" + " \"changePw\":\"N\",\n" + " \"toEmailId\":\"N\",\n" + + " \"periodPW\":\"N\",\n" + " \"lgAccount\":\"Y\",\n" + " \"isService\":\"Y\",\n" + + " \"userNickName\":\"faker\",\n" + " \"authUser\":\"N\",\n" + " \"serviceList\":[\n" + + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"LG ThinQ\",\n" + + " \"svcCode\":\"SVC202\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " },\n" + + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"LG Developer\",\n" + + " \"svcCode\":\"SVC609\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " },\n" + + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"MC OAuth\",\n" + + " \"svcCode\":\"SVC710\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " }\n" + + " ]\n" + " }\n" + "}"; + private String dashboardListReturned = "{\n" + " \"resultCode\":\"0000\",\n" + " \"result\":{\n" + + " \"langPackCommonVer\":\"125.6\",\n" + + " \"langPackCommonUri\":\"https://objectcontent.lgthinq.com/f1cae877-1d1e-4c12-8010-acbcdcce2df1?hdnts=exp=1706183232~hmac=257aa8146a089de87496cb13aa0b43761a19e7db225558dfb8996919746b465b\",\n" + + " \"item\":[\n" + " {\n" + " \"modelName\":\"RAC_056905_WW\",\n" + + " \"subModelName\":\"\",\n" + " \"deviceType\":401,\n" + + " \"deviceCode\":\"AI01\",\n" + " \"alias\":\"Bedroom\",\n" + + " \"deviceId\":\"abra-cadabra-0001-5771\",\n" + " \"fwVer\":\"2.5.8_RTOS_3K\",\n" + + " \"imageFileName\":\"ac_home_wall_airconditioner_img.png\",\n" + + " \"imageUrl\":\"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6\",\n" + + " \"smallImageUrl\":\"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5\",\n" + + " \"ssid\":\"dummy-ssid\",\n" + " \"macAddress\":\"74:40:be:92:ac:08\",\n" + + " \"networkType\":\"02\",\n" + " \"timezoneCode\":\"America/Sao_Paulo\",\n" + + " \"timezoneCodeAlias\":\"Brazil/Sao Paulo\",\n" + " \"utcOffset\":-3,\n" + + " \"utcOffsetDisplay\":\"-03:00\",\n" + " \"dstOffset\":-2,\n" + + " \"dstOffsetDisplay\":\"-02:00\",\n" + " \"curOffset\":-2,\n" + + " \"curOffsetDisplay\":\"-02:00\",\n" + + " \"sdsGuide\":\"{\\\"deviceCode\\\":\\\"AI01\\\"}\",\n" + " \"newRegYn\":\"N\",\n" + + " \"remoteControlType\":\"\",\n" + " \"modelJsonVer\":7.13,\n" + + " \"modelJsonUri\":\"https://aic.lgthinq.com:46030/api/webContents/modelJSON?modelName=modelJSON_401&countryCode=KR&contentsId=abra-cadabra-0001-5771&authKey=thinq\",\n" + + " \"appModuleVer\":12.49,\n" + + " \"appModuleUri\":\"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155\",\n" + + " \"appRestartYn\":\"Y\",\n" + " \"appModuleSize\":6082481,\n" + + " \"langPackProductTypeVer\":59.9,\n" + + " \"langPackProductTypeUri\":\"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea\",\n" + + " \"langPackModelVer\":\"\",\n" + " \"langPackModelUri\":\"\",\n" + + " \"deviceState\":\"E\",\n" + " \"online\":false,\n" + + " \"platformType\":\"thinq1\",\n" + " \"regDt\":2.0200909053555E13,\n" + + " \"modelProtocol\":\"STANDARD\",\n" + " \"order\":0,\n" + + " \"drServiceYn\":\"N\",\n" + " \"fwInfoList\":[\n" + " {\n" + + " \"partNumber\":\"SAA38690433\",\n" + " \"checksum\":\"00000000\",\n" + + " \"verOrder\":0\n" + " }\n" + " ],\n" + + " \"guideTypeYn\":\"Y\",\n" + " \"guideType\":\"RAC_TYPE1\",\n" + + " \"regDtUtc\":\"20200909073555\",\n" + " \"regIndex\":0,\n" + + " \"groupableYn\":\"Y\",\n" + " \"controllableYn\":\"Y\",\n" + + " \"combinedProductYn\":\"N\",\n" + " \"masterYn\":\"Y\",\n" + + " \"pccModelYn\":\"N\",\n" + " \"sdsPid\":{\n" + " \"sds4\":\"\",\n" + + " \"sds3\":\"\",\n" + " \"sds2\":\"\",\n" + " \"sds1\":\"\"\n" + + " },\n" + " \"autoOrderYn\":\"N\",\n" + + " \"modelNm\":\"RAC_056905_WW\",\n" + " \"initDevice\":false,\n" + + " \"existsEntryPopup\":\"N\",\n" + " \"tclcount\":0\n" + " },\n" + + " {\n" + " \"appType\":\"NUTS\",\n" + " \"modelCountryCode\":\"WW\",\n" + + " \"countryCode\":\"BR\",\n" + " \"modelName\":\"RAC_056905_WW\",\n" + + " \"deviceType\":401,\n" + " \"deviceCode\":\"AI01\",\n" + + " \"alias\":\"Office\",\n" + " \"deviceId\":\"abra-cadabra-0001-5772\",\n" + + " \"fwVer\":\"\",\n" + + " \"imageFileName\":\"ac_home_wall_airconditioner_img.png\",\n" + + " \"imageUrl\":\"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6\",\n" + + " \"smallImageUrl\":\"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5\",\n" + + " \"ssid\":\"smart-gameficacao\",\n" + " \"softapId\":\"\",\n" + + " \"softapPass\":\"\",\n" + " \"macAddress\":\"\",\n" + + " \"networkType\":\"02\",\n" + " \"timezoneCode\":\"America/Sao_Paulo\",\n" + + " \"timezoneCodeAlias\":\"Brazil/Sao Paulo\",\n" + " \"utcOffset\":-3,\n" + + " \"utcOffsetDisplay\":\"-03:00\",\n" + " \"dstOffset\":-2,\n" + + " \"dstOffsetDisplay\":\"-02:00\",\n" + " \"curOffset\":-2,\n" + + " \"curOffsetDisplay\":\"-02:00\",\n" + + " \"sdsGuide\":\"{\\\"deviceCode\\\":\\\"AI01\\\"}\",\n" + " \"newRegYn\":\"N\",\n" + + " \"remoteControlType\":\"\",\n" + " \"userNo\":\"BR2004259832795\",\n" + + " \"tftYn\":\"N\",\n" + " \"modelJsonVer\":12.11,\n" + + " \"modelJsonUri\":\"https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f\",\n" + + " \"appModuleVer\":12.49,\n" + + " \"appModuleUri\":\"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155\",\n" + + " \"appRestartYn\":\"Y\",\n" + " \"appModuleSize\":6082481,\n" + + " \"langPackProductTypeVer\":59.9,\n" + + " \"langPackProductTypeUri\":\"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea\",\n" + + " \"deviceState\":\"E\",\n" + " \"snapshot\":{\n" + + " \"airState.windStrength\":8.0,\n" + " \"airState.wMode.lowHeating\":0.0,\n" + + " \"airState.diagCode\":0.0,\n" + + " \"airState.lightingState.displayControl\":1.0,\n" + + " \"airState.wDir.hStep\":0.0,\n" + " \"mid\":8.4615358E7,\n" + + " \"airState.energy.onCurrent\":476.0,\n" + + " \"airState.wMode.airClean\":0.0,\n" + + " \"airState.quality.sensorMon\":0.0,\n" + + " \"airState.energy.accumulatedTime\":0.0,\n" + + " \"airState.miscFuncState.antiBugs\":0.0,\n" + + " \"airState.tempState.target\":18.0,\n" + " \"airState.operation\":1.0,\n" + + " \"airState.wMode.jet\":0.0,\n" + " \"airState.wDir.vStep\":2.0,\n" + + " \"timestamp\":1.643248573766E12,\n" + " \"airState.powerSave.basic\":0.0,\n" + + " \"airState.quality.PM10\":0.0,\n" + " \"static\":{\n" + + " \"deviceType\":\"401\",\n" + " \"countryCode\":\"BR\"\n" + + " },\n" + " \"airState.quality.overall\":0.0,\n" + + " \"airState.tempState.current\":25.0,\n" + + " \"airState.miscFuncState.extraOp\":0.0,\n" + + " \"airState.energy.accumulated\":0.0,\n" + + " \"airState.reservation.sleepTime\":0.0,\n" + + " \"airState.miscFuncState.autoDry\":0.0,\n" + + " \"airState.reservation.targetTimeToStart\":0.0,\n" + " \"meta\":{\n" + + " \"allDeviceInfoUpdate\":false,\n" + + " \"messageId\":\"fVz2AE-2SC-rf3GnerGdeQ\"\n" + " },\n" + + " \"airState.quality.PM1\":0.0,\n" + " \"airState.wMode.smartCare\":0.0,\n" + + " \"airState.quality.PM2\":0.0,\n" + " \"online\":true,\n" + + " \"airState.opMode\":0.0,\n" + + " \"airState.reservation.targetTimeToStop\":0.0,\n" + + " \"airState.filterMngStates.maxTime\":0.0,\n" + + " \"airState.filterMngStates.useTime\":0.0\n" + " },\n" + + " \"online\":true,\n" + " \"platformType\":\"thinq2\",\n" + + " \"area\":45883,\n" + " \"regDt\":2.0220111184827E13,\n" + + " \"blackboxYn\":\"Y\",\n" + " \"modelProtocol\":\"STANDARD\",\n" + + " \"order\":0,\n" + " \"drServiceYn\":\"N\",\n" + " \"fwInfoList\":[\n" + + " {\n" + " \"checksum\":\"00004105\",\n" + + " \"order\":1.0,\n" + " \"partNumber\":\"SAA40128563\"\n" + + " }\n" + " ],\n" + " \"modemInfo\":{\n" + + " \"appVersion\":\"clip_hna_v1.9.116\",\n" + + " \"modelName\":\"RAC_056905_WW\",\n" + " \"modemType\":\"QCOM_QCA4010\",\n" + + " \"ruleEngine\":\"y\"\n" + " },\n" + " \"guideTypeYn\":\"Y\",\n" + + " \"guideType\":\"RAC_TYPE1\",\n" + " \"regDtUtc\":\"20220111204827\",\n" + + " \"regIndex\":0,\n" + " \"groupableYn\":\"Y\",\n" + + " \"controllableYn\":\"Y\",\n" + " \"combinedProductYn\":\"N\",\n" + + " \"masterYn\":\"Y\",\n" + " \"pccModelYn\":\"N\",\n" + " \"sdsPid\":{\n" + + " \"sds4\":\"\",\n" + " \"sds3\":\"\",\n" + " \"sds2\":\"\",\n" + + " \"sds1\":\"\"\n" + " },\n" + " \"autoOrderYn\":\"N\",\n" + + " \"initDevice\":false,\n" + " \"existsEntryPopup\":\"N\",\n" + + " \"tclcount\":0\n" + " }\n" + " ],\n" + " \"group\":[\n" + " \n" + + " ]\n" + " }\n" + "}"; + private String secretKey = "gregre9812012910291029120912091209"; + private String oauthTokenSearchKeyReturned = "{\"returnData\":\"" + secretKey + "\"}"; + private String refreshToken = "12897238974bb327862378ef290128390273aa7389723894734de"; + private String accessToken = "11a1222c39f16a5c8b3fa45bb4c9be2e00a29a69dced2fa7fe731f1728346ee669f1a96d1f0b4925e5aa330b6dbab882772"; + private String sessionTokenReturned = "{\"status\":1,\"access_token\":\"" + accessToken + + "\",\"expires_in\":\"3600\",\"refresh_token\":\"" + refreshToken + + "\",\"oauth2_backend_url\":\"http://localhost:8880/\"}"; + + private String getCurrentTimestamp() { + SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.format(new Date()); + } + + @Test + public void testDiscoveryThings() { + stubFor(get(GATEWAY_SERVICE_PATH).willReturn(ok(gtwResponse))); + String preLoginPwd = RestUtils.getPreLoginEncPwd(fakePassword); + stubFor(post("/spx" + PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) + .willReturn(ok(preLoginResponse))); + URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + OAUTH_SEARCH_KEY_PATH) + .queryParam("key_name", "OAUTH_SECRETKEY").queryParam("sever_type", "OP").build(); + stubFor(get(String.format("%s?%s", uri.getPath(), uri.getQuery())).willReturn(ok(oauthTokenSearchKeyReturned))); + stubFor(post(V2_SESSION_LOGIN_PATH + fakeUserName).withRequestBody(containing("user_auth2=SOME_DUMMY_ENC_PWD")) + .withHeader("X-Signature", equalTo("SOME_DUMMY_SIGNATURE")) + .withHeader("X-Timestamp", equalTo("1643236928")).willReturn(ok(loginSessionResponse))); + stubFor(get(V2_USER_INFO).willReturn(ok(userInfoReturned))); + stubFor(get("/v1" + V2_LS_PATH).willReturn(ok(dashboardListReturned))); + String currTimestamp = getCurrentTimestamp(); + Map empData = new LinkedHashMap<>(); + empData.put("account_type", userIdType); + empData.put("country_code", fakeCountry); + empData.put("username", fakeUserName); + + stubFor(post("/emp/oauth2/token/empsession").withRequestBody(containing("account_type=" + userIdType)) + .withRequestBody(containing("country_code=" + fakeCountry)) + .withRequestBody(containing("username=" + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8))) + .withHeader("lgemp-x-session-key", equalTo(loginSessionId)).willReturn(ok(sessionTokenReturned))); + // faking some constants + LGThinqBindingConstants.GATEWAY_URL = "http://localhost:8880" + GATEWAY_SERVICE_PATH; + LGThinqBindingConstants.V2_EMP_SESS_URL = "http://localhost:8880/emp/oauth2/token/empsession"; + Bridge fakeThing = mock(Bridge.class); + ThingUID fakeThingUid = mock(ThingUID.class); + when(fakeThingUid.getId()).thenReturn(fakeBridgeName); + when(fakeThing.getUID()).thenReturn(fakeThingUid); + String tempDir = System.getProperty("java.io.tmpdir"); + LGThinqBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; + LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; + LGBridgeHandler b = new LGBridgeHandler(fakeThing); + LGBridgeHandler spyBridge = spy(b); + doReturn(new LGThinqConfiguration(fakeUserName, fakePassword, fakeCountry, fakeLanguage, 60)).when(spyBridge) + .getConfigAs(any(Class.class)); + spyBridge.initialize(); + LGApiClientService service1 = LGApiV1ClientServiceImpl.getInstance(); + LGApiClientService service2 = LGApiV2ClientServiceImpl.getInstance(); + TokenManager tokenManager = TokenManager.getInstance(); + try { + if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { + tokenManager.oauthFirstRegistration(fakeBridgeName, fakeLanguage, fakeCountry, fakeUserName, + fakePassword); + } + List devices = service2.listAccountDevices("bridgeTest"); + assertEquals(devices.size(), 2); + } catch (Exception e) { + logger.error("Error testing facade", e); + } + } +} From 559332745a669b6e37a2e8355763ff12de5ff3f7 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 12:24:48 -0300 Subject: [PATCH 059/130] [lgthinq] i18n enhancement, adjusting class names & references, adjusted some tests, more code cleaning. Signed-off-by: nemerdaud --- ...java => LGThinqAirConditionerHandler.java} | 49 ++- .../internal/LGThinqBindingConstants.java | 8 + ...inqDeviceDynStateDescriptionProvider.java} | 8 +- ...viceThing.java => LGThinqDeviceThing.java} | 4 +- .../internal/LGThinqHandlerFactory.java | 16 +- .../binding/lgthinq/internal/api/Gateway.java | 122 ------- .../lgthinq/internal/api/LGThinqGateway.java | 34 +- .../lgthinq/internal/api/TokenResult.java | 6 +- .../{LGBridge.java => LGThinqBridge.java} | 12 +- ...Handler.java => LGThinqBridgeHandler.java} | 30 +- ...vice.java => LGThinqApiClientService.java} | 4 +- ....java => LGThinqApiClientServiceImpl.java} | 6 +- ...ava => LGThinqApiV1ClientServiceImpl.java} | 14 +- ...ava => LGThinqApiV2ClientServiceImpl.java} | 14 +- .../resources/OH-INF/i18n/lgthinq.properties | 22 +- .../OH-INF/i18n/lgthinq_pt_BR.properties | 7 +- .../binding/lgthinq/handler/JsonUtils.java | 3 - .../lgthinq/handler/LGBridgeTests.java | 313 ------------------ .../resources/dashboard-list-response-1.json | 4 +- 19 files changed, 109 insertions(+), 567 deletions(-) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGAirConditionerHandler.java => LGThinqAirConditionerHandler.java} (89%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGDeviceDynStateDescriptionProvider.java => LGThinqDeviceDynStateDescriptionProvider.java} (81%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGDeviceThing.java => LGThinqDeviceThing.java} (90%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/{LGBridge.java => LGThinqBridge.java} (68%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/{LGBridgeHandler.java => LGThinqBridgeHandler.java} (92%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/{LGApiClientService.java => LGThinqApiClientService.java} (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/{LGApiClientServiceImpl.java => LGThinqApiClientServiceImpl.java} (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/{LGApiV1ClientServiceImpl.java => LGThinqApiV1ClientServiceImpl.java} (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/{LGApiV2ClientServiceImpl.java => LGThinqApiV2ClientServiceImpl.java} (96%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java similarity index 89% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 7ceeb3c631450..ea0c1b7859654 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -25,10 +25,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; -import org.openhab.binding.lgthinq.lgapi.LGApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.LGApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgapi.model.*; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -43,32 +43,28 @@ import org.slf4j.LoggerFactory; /** - * The {@link LGAirConditionerHandler} is responsible for handling commands, which are + * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGAirConditionerHandler extends BaseThingHandler implements LGDeviceThing { - public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner +public class LGThinqAirConditionerHandler extends BaseThingHandler implements LGThinqDeviceThing { - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); - private final LGDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; private final ChannelUID opModeChannelUID; private final ChannelUID opModeFanSpeedUID; @Nullable private ACCapability acCapability; private final String lgPlatfomType; - private final Logger logger = LoggerFactory.getLogger(LGAirConditionerHandler.class); + private final Logger logger = LoggerFactory.getLogger(LGThinqAirConditionerHandler.class); @NonNullByDefault - private final LGApiClientService lgApiClientService; + private final LGThinqApiClientService lgThinqApiClientService; private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; // Bridges status that this thing must top scanning for state change private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, ThingStatusDetail.CONFIGURATION_ERROR); - private static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); private @Nullable ScheduledFuture thingStatePoolingJob; private @Nullable Future commandExecutorQueueJob; // *** Long running isolated threadpools. @@ -81,12 +77,13 @@ public class LGAirConditionerHandler extends BaseThingHandler implements LGDevic @NonNullByDefault private String bridgeId = ""; - public LGAirConditionerHandler(Thing thing, LGDeviceDynStateDescriptionProvider stateDescriptionProvider) { + public LGThinqAirConditionerHandler(Thing thing, + LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { super(thing); this.stateDescriptionProvider = stateDescriptionProvider; lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGApiV1ClientServiceImpl.getInstance() - : LGApiV2ClientServiceImpl.getInstance(); + lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() + : LGThinqApiV2ClientServiceImpl.getInstance(); opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); opModeFanSpeedUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); } @@ -136,7 +133,7 @@ private void initializeThing(@Nullable ThingStatus bridgeStatus) { "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); } if (bridge != null) { - LGBridgeHandler handler = (LGBridgeHandler) bridge.getHandler(); + LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); // registry this thing to the bridge if (handler == null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); @@ -241,7 +238,7 @@ private String getBridgeId() { private void forceStopDeviceV1Monitor(String deviceId) { try { monitorV1Began = false; - lgApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + lgThinqApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); } catch (Exception e) { logger.error("Error stopping LG Device monitor", e); } @@ -272,7 +269,7 @@ public void updateChannelDynStateDescription() throws LGApiException { @Override public ACCapability getAcCapabilities() throws LGApiException { if (acCapability == null) { - acCapability = lgApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); + acCapability = lgThinqApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); } return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } @@ -281,11 +278,11 @@ public ACCapability getAcCapabilities() throws LGApiException { private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return lgApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); + return lgThinqApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { - monitorWorkId = lgApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); monitorV1Began = true; } } catch (LGDeviceV1OfflineException e) { @@ -302,7 +299,7 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiExcepti while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = lgApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + shot = lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); if (shot != null) { return shot; } @@ -444,7 +441,7 @@ public void run() { switch (params.channelUID) { case CHANNEL_MOD_OP_ID: { if (params.command instanceof DecimalType) { - lgApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + lgThinqApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), ((DecimalType) command).intValue()); } else { logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); @@ -453,7 +450,7 @@ public void run() { } case CHANNEL_FAN_SPEED_ID: { if (command instanceof DecimalType) { - lgApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + lgThinqApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), ((DecimalType) command).intValue()); } else { logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); @@ -462,7 +459,7 @@ public void run() { } case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { - lgApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), command == ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); } else { logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); @@ -479,7 +476,7 @@ public void run() { logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); break; } - lgApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + lgThinqApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), ACTargetTmp.statusOf(targetTemp)); break; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 5c2767108f97c..4165da3a117c8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -13,10 +13,14 @@ package org.openhab.binding.lgthinq.internal; import java.io.File; +import java.util.Collections; import java.util.Map; +import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; import org.openhab.core.OpenHAB; +import org.openhab.core.thing.ThingTypeUID; /** * The {@link LGThinqBindingConstants} class defines common constants, which are @@ -28,6 +32,9 @@ public class LGThinqBindingConstants { public static final String BINDING_ID = "lgthinq"; + public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner + public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); public static final String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; @@ -87,6 +94,7 @@ public class LGThinqBindingConstants { public static final String PLATFORM_TYPE = "platform_type"; public static final String PLATFORM_TYPE_V1 = "thinq1"; public static final String PLATFORM_TYPE_V2 = "thinq2"; + static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); public static final int SEARCH_TIME = 20; // delay between each devices's scan for state changes (in seconds) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java similarity index 81% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java index a74b5c73c9ce8..2b50c6e63ca83 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceDynStateDescriptionProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java @@ -22,14 +22,14 @@ import org.osgi.service.component.annotations.Reference; /** - * The {@link LGDeviceThing} is a main interface contract for all LG Thinq things + * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things * * @author Nemer Daud - Initial contribution */ -@Component(service = { DynamicStateDescriptionProvider.class, LGDeviceDynStateDescriptionProvider.class }) -public class LGDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { +@Component(service = { DynamicStateDescriptionProvider.class, LGThinqDeviceDynStateDescriptionProvider.class }) +public class LGThinqDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { @Activate - public LGDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // + public LGThinqDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { this.eventPublisher = eventPublisher; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java similarity index 90% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index 54cd67361e071..64c77140d443a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -18,12 +18,12 @@ import org.openhab.binding.lgthinq.lgapi.model.LGDevice; /** - * The {@link LGDeviceThing} is a main interface contract for all LG Thinq things + * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGDeviceThing { +public interface LGThinqDeviceThing { void onDeviceAdded(@NonNullByDefault LGDevice device); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 803d80a477971..3b9b6ad3249d4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -12,14 +12,14 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGAirConditionerHandler.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler.THING_TYPE_BRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; +import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; import org.openhab.core.config.core.Configuration; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.Thing; @@ -47,7 +47,7 @@ public class LGThinqHandlerFactory extends BaseThingHandlerFactory { private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, THING_TYPE_BRIDGE); private final Logger logger = LoggerFactory.getLogger(LGThinqHandlerFactory.class); - private final LGDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { @@ -59,9 +59,9 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { ThingTypeUID thingTypeUID = thing.getThingTypeUID(); if (THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { - return new LGAirConditionerHandler(thing, stateDescriptionProvider); + return new LGThinqAirConditionerHandler(thing, stateDescriptionProvider); } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - return new LGBridgeHandler((Bridge) thing); + return new LGThinqBridgeHandler((Bridge) thing); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; @@ -72,14 +72,14 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { return super.createThing(thingTypeUID, configuration, thingUID, null); - } else if (LGAirConditionerHandler.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { + } else if (LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); } return null; } @Activate - public LGThinqHandlerFactory(final @Reference LGDeviceDynStateDescriptionProvider stateDescriptionProvider) { + public LGThinqHandlerFactory(final @Reference LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { this.stateDescriptionProvider = stateDescriptionProvider; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java deleted file mode 100644 index 64c5535923928..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/Gateway.java +++ /dev/null @@ -1,122 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.api; - -import java.io.Serializable; -import java.util.Map; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link Gateway} hold informations about the LG Gateway - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class Gateway implements Serializable { - private String empBaseUri = ""; - private String loginBaseUri = ""; - private String apiRootV1 = ""; - private String apiRootV2 = ""; - private String authBase = ""; - private String language = ""; - private String country = ""; - private String username = ""; - private String password = ""; - - public Gateway() { - } - - public Gateway(Map params, String language, String country) { - this.apiRootV2 = Objects.requireNonNullElse(params.get("thinq2Uri"), ""); - this.apiRootV1 = Objects.requireNonNullElse(params.get("thinq1Uri"), ""); - this.loginBaseUri = Objects.requireNonNullElse(params.get("empSpxUri"), ""); - this.authBase = Objects.requireNonNullElse(params.get("empUri"), ""); - this.empBaseUri = Objects.requireNonNullElse(params.get("empTermsUri"), ""); - this.language = language; - this.country = country; - } - - public String getEmpBaseUri() { - return empBaseUri; - } - - public String getApiRootV2() { - return apiRootV2; - } - - public String getAuthBase() { - return authBase; - } - - public String getLanguage() { - return language; - } - - public String getCountry() { - return country; - } - - public String getLoginBaseUri() { - return loginBaseUri; - } - - public String getApiRootV1() { - return apiRootV1; - } - - public void setEmpBaseUri(String empBaseUri) { - this.empBaseUri = empBaseUri; - } - - public void setLoginBaseUri(String loginBaseUri) { - this.loginBaseUri = loginBaseUri; - } - - public void setApiRootV1(String apiRootV1) { - this.apiRootV1 = apiRootV1; - } - - public void setApiRootV2(String apiRootV2) { - this.apiRootV2 = apiRootV2; - } - - public void setAuthBase(String authBase) { - this.authBase = authBase; - } - - public void setLanguage(String language) { - this.language = language; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java index 8e0c4b0bfe6dd..c7fd64caf8ab6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java @@ -12,15 +12,11 @@ */ package org.openhab.binding.lgthinq.internal.api; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_EMP_SESS_PATH; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_EMP_SESS_URL; - -import java.io.Serializable; - import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; -import com.fasterxml.jackson.annotation.JsonIgnore; +import java.io.Serializable; +import java.util.Map; +import java.util.Objects; /** * The {@link LGThinqGateway} hold informations about the LG Gateway @@ -38,36 +34,24 @@ public class LGThinqGateway implements Serializable { private String country = ""; private String username = ""; private String password = ""; - private String alternativeEmpServer = ""; - private int accountVersion; public LGThinqGateway() { } - public LGThinqGateway(GatewayResult gwResult, String language, String country, String alternativeEmpServer) { - this.apiRootV2 = gwResult.getThinq2Uri(); - this.apiRootV1 = gwResult.getThinq1Uri(); - this.loginBaseUri = gwResult.getEmpSpxUri(); - this.authBase = gwResult.getEmpUri(); - this.empBaseUri = gwResult.getEmpTermsUri(); + public LGThinqGateway(Map params, String language, String country) { + this.apiRootV2 = Objects.requireNonNullElse(params.get("thinq2Uri"), ""); + this.apiRootV1 = Objects.requireNonNullElse(params.get("thinq1Uri"), ""); + this.loginBaseUri = Objects.requireNonNullElse(params.get("empSpxUri"), ""); + this.authBase = Objects.requireNonNullElse(params.get("empUri"), ""); + this.empBaseUri = Objects.requireNonNullElse(params.get("empTermsUri"), ""); this.language = language; this.country = country; - this.alternativeEmpServer = alternativeEmpServer; - } - - @JsonIgnore - public String getTokenSessionEmpUrl() { - return alternativeEmpServer.isBlank() ? V2_EMP_SESS_URL : alternativeEmpServer + V2_EMP_SESS_PATH; } public String getEmpBaseUri() { return empBaseUri; } - public int getAccountVersion() { - return accountVersion; - } - public String getApiRootV2() { return apiRootV2; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java index c008ecef60891..9f678b54f2b28 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java @@ -30,7 +30,7 @@ public class TokenResult implements Serializable { private Date generatedTime = new Date(); private String oauthBackendUrl = ""; private UserInfo userInfo = new UserInfo(); - private Gateway gatewayInfo = new Gateway(); + private LGThinqGateway gatewayInfo = new LGThinqGateway(); public TokenResult(String accessToken, String refreshToken, int expiresIn, Date generatedTime, String ouathBackendUrl) { @@ -45,11 +45,11 @@ public TokenResult(String accessToken, String refreshToken, int expiresIn, Date public TokenResult() { } - public Gateway getGatewayInfo() { + public LGThinqGateway getGatewayInfo() { return gatewayInfo; } - public void setGatewayInfo(Gateway gatewayInfo) { + public void setGatewayInfo(LGThinqGateway gatewayInfo) { this.gatewayInfo = gatewayInfo; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java similarity index 68% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java index d9f8f36ab93f3..259307eb1819c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridge.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java @@ -12,20 +12,20 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import org.openhab.binding.lgthinq.internal.LGDeviceThing; +import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; /** - * The {@link LGBridge} + * The {@link LGThinqBridge} * * @author Nemer Daud - Initial contribution */ -public interface LGBridge { +public interface LGThinqBridge { void registerDiscoveryListener(LGThinqDiscoveryService listener); - void registryListenerThing(LGDeviceThing thing); + void registryListenerThing(LGThinqDeviceThing thing); - void unRegistryListenerThing(LGDeviceThing thing); + void unRegistryListenerThing(LGThinqDeviceThing thing); - LGDeviceThing getThingByDeviceId(String deviceId); + LGThinqDeviceThing getThingByDeviceId(String deviceId); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java similarity index 92% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index d6a40ab443285..de1ee7691abbe 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -24,15 +24,15 @@ import java.util.concurrent.locks.ReentrantLock; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGDeviceThing; import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; +import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgapi.LGApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgapi.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgapi.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; import org.openhab.core.thing.*; @@ -43,18 +43,18 @@ import org.slf4j.LoggerFactory; /** - * The {@link LGBridgeHandler} + * The {@link LGThinqBridgeHandler} * * @author Nemer Daud - Initial contribution */ -public class LGBridgeHandler extends ConfigStatusBridgeHandler implements LGBridge { +public class LGThinqBridgeHandler extends ConfigStatusBridgeHandler implements LGThinqBridge { public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(LGThinqBindingConstants.BINDING_ID, "bridge"); - private Map lGDeviceRegister = new ConcurrentHashMap<>(); + private Map lGDeviceRegister = new ConcurrentHashMap<>(); private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); static { - var logger = LoggerFactory.getLogger(LGBridgeHandler.class); + var logger = LoggerFactory.getLogger(LGThinqBridgeHandler.class); try { File directory = new File(THINQ_USER_DATA_FOLDER); if (!directory.exists()) { @@ -64,18 +64,18 @@ public class LGBridgeHandler extends ConfigStatusBridgeHandler implements LGBrid logger.warn("Unable to setup thinq userdata directory: {}", e.getMessage()); } } - private final Logger logger = LoggerFactory.getLogger(LGBridgeHandler.class); + private final Logger logger = LoggerFactory.getLogger(LGThinqBridgeHandler.class); private LGThinqConfiguration lgthinqConfig; private TokenManager tokenManager; private LGThinqDiscoveryService discoveryService; - private LGApiClientService lgApiClient; + private LGThinqApiClientService lgApiClient; private @Nullable Future initJob; private @Nullable ScheduledFuture devicePollingJob; - public LGBridgeHandler(Bridge bridge) { + public LGThinqBridgeHandler(Bridge bridge) { super(bridge); tokenManager = TokenManager.getInstance(); - lgApiClient = LGApiV1ClientServiceImpl.getInstance(); + lgApiClient = LGThinqApiV1ClientServiceImpl.getInstance(); lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); } @@ -170,7 +170,7 @@ public Collection> getServices() { } @Override - public void registryListenerThing(LGDeviceThing thing) { + public void registryListenerThing(LGThinqDeviceThing thing) { if (lGDeviceRegister.get(thing.getDeviceId()) == null) { lGDeviceRegister.put(thing.getDeviceId(), thing); // remove device from discovery list, if exists. @@ -182,12 +182,12 @@ public void registryListenerThing(LGDeviceThing thing) { } @Override - public void unRegistryListenerThing(LGDeviceThing thing) { + public void unRegistryListenerThing(LGThinqDeviceThing thing) { lGDeviceRegister.remove(thing.getDeviceId()); } @Override - public LGDeviceThing getThingByDeviceId(String deviceId) { + public LGThinqDeviceThing getThingByDeviceId(String deviceId) { return lGDeviceRegister.get(deviceId); } @@ -218,7 +218,7 @@ protected void doConnectedRun() throws LGThinqException { logger.trace("LG Device '{}' removed.", deviceId); lastDevicesDiscovered.remove(deviceId); - LGDeviceThing deviceThing = lGDeviceRegister.get(deviceId); + LGThinqDeviceThing deviceThing = lGDeviceRegister.get(deviceId); if (deviceThing != null) { deviceThing.onDeviceRemoved(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index 38fa1d503026f..6ecbf3e002343 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -26,12 +26,12 @@ import org.openhab.binding.lgthinq.lgapi.model.*; /** - * The {@link LGApiClientService} + * The {@link LGThinqApiClientService} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGApiClientService { +public interface LGThinqApiClientService { List listAccountDevices(String bridgeName) throws LGApiException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index c048f707bd6f5..edc2b07c5c5b0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -33,13 +33,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGApiV1ClientServiceImpl} + * The {@link LGThinqApiV1ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public abstract class LGApiClientServiceImpl implements LGApiClientService { - private static final Logger logger = LoggerFactory.getLogger(LGApiClientServiceImpl.class); +public abstract class LGThinqApiClientServiceImpl implements LGThinqApiClientService { + private static final Logger logger = LoggerFactory.getLogger(LGThinqApiClientServiceImpl.class); private final ObjectMapper objectMapper = new ObjectMapper(); protected abstract TokenManager getTokenManager(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index 628dddcb7d34e..51de488bb84f6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -40,26 +40,26 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGApiV1ClientServiceImpl} + * The {@link LGThinqApiV1ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGApiV1ClientServiceImpl extends LGApiClientServiceImpl { - private static final LGApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGApiV1ClientServiceImpl.class); +public class LGThinqApiV1ClientServiceImpl extends LGThinqApiClientServiceImpl { + private static final LGThinqApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinqApiV1ClientServiceImpl.class); private final ObjectMapper objectMapper = new ObjectMapper(); private final TokenManager tokenManager; static { - instance = new LGApiV1ClientServiceImpl(); + instance = new LGThinqApiV1ClientServiceImpl(); } - private LGApiV1ClientServiceImpl() { + private LGThinqApiV1ClientServiceImpl() { tokenManager = TokenManager.getInstance(); } - public static LGApiClientService getInstance() { + public static LGThinqApiClientService getInstance() { return instance; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index 4075036fb8b65..9e4a2dae575f4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -44,22 +44,22 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGApiV2ClientServiceImpl} + * The {@link LGThinqApiV2ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGApiV2ClientServiceImpl extends LGApiClientServiceImpl { - private static final LGApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGApiV2ClientServiceImpl.class); +public class LGThinqApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl { + private static final LGThinqApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinqApiV2ClientServiceImpl.class); private final ObjectMapper objectMapper = new ObjectMapper(); private final TokenManager tokenManager; static { - instance = new LGApiV2ClientServiceImpl(); + instance = new LGThinqApiV2ClientServiceImpl(); } - private LGApiV2ClientServiceImpl() { + private LGThinqApiV2ClientServiceImpl() { tokenManager = TokenManager.getInstance(); } @@ -68,7 +68,7 @@ protected TokenManager getTokenManager() { return tokenManager; } - public static LGApiClientService getInstance() { + public static LGThinqApiClientService getInstance() { return instance; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index 7e8b77f8c8473..5b548f54302e3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -1,22 +1,18 @@ # binding binding.lgthinq.name = LG Thinq Binding -binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 & v1) +binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 & v2) # thing types -thing-type.lgthinq.sample.label = -thing-type.lgthinq.sample.description = - -# thing type config description -thing-type.config.lgthinq.sample.hostname.label = -thing-type.config.lgthinq.sample.hostname.description = -thing-type.config.lgthinq.sample.password.label = -thing-type.config.lgthinq.sample.password.description = -thing-type.config.lgthinq.sample.refreshInterval.label = -thing-type.config.lgthinq.sample.refreshInterval.description = +thing-type.lgthinq.401.label = LG Thinq Air Conditioner +thing-type.lgthinq.401.description = LG Thinq Air Conditioner V1 & V2 # channel types -channel-type.lgthinq.sample-channel.label = -channel-type.lgthinq.sample-channel.description = +channel-type.lgthinq.current-temperature.label = Temperature +channel-type.lgthinq.current-temperature.description = Current Temperature +channel-type.lgthinq.fan-speed.label = Fan Speed +channel-type.lgthinq.fan-speed.description = Air Conditioner Fan Speed +channel-type.lgthinq.operation-mode.label = Operation Mode +channel-type.lgthinq.operation-mode.description = Air Contirioner Operation Mode # ERRORS error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties index d787bd6f204e5..de4244ee95061 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties @@ -13,8 +13,6 @@ channel-type.lgthinq.fan-speed.label = Velocidade Ar channel-type.lgthinq.fan-speed.description = Velocidade Ar channel-type.lgthinq.operation-mode.label = Modo de Opera\u00E7\u00E3o channel-type.lgthinq.operation-mode.description = Modo de Opera\u00E7\u00E3o do Ar Condicionado -channel-type.lgthinq.cool-jet.label = Jato Frio -channel-type.lgthinq.cool-jet.description = Modo Jato Frio # ERRORS error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). @@ -23,7 +21,4 @@ error.toke-file-access-error = Error Handling Token Configuration File. error.toke-refresh = Error refreshing LGThinq Token. Try to delete it (in data directory) to the bridge automatically recreate it. error.lgapi-communication-error = Generic Error in the LG API communication process. error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. -error.lgapi-getting-devices = Error getting devices from LG APIevice is disconnected in scanner process. - -# OFFLINE Statuses -offline.device-disconnected = Dispositivo desconectado. +error.lgapi-getting-devices = Error getting devices from LG API in scanner process. diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index b1d243ee49162..3e42a5779cbac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -19,8 +19,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import org.eclipse.jdt.annotation.NonNullByDefault; - import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -29,7 +27,6 @@ * * @author Nemer Daud - Initial contribution */ -@NonNullByDefault public class JsonUtils { public static T unmashallJson(String fileName) { InputStream inputStream = JsonUtils.class.getResourceAsStream(fileName); diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java deleted file mode 100644 index ebc16454853a2..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGBridgeTests.java +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.handler; - -import static com.github.tomakehurst.wiremock.client.WireMock.*; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; -import static org.mockito.Mockito.any; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.net.URI; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.junit.jupiter.MockitoExtension; -import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; -import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; -import org.openhab.binding.lgthinq.internal.handler.LGBridgeHandler; -import org.openhab.binding.lgthinq.lgapi.LGApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.LGApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.model.LGDevice; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ThingUID; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.github.tomakehurst.wiremock.junit5.WireMockTest; - -/** - * The {@link LGBridgeTests} - * - * @author Nemer Daud - Initial contribution - */ -@ExtendWith(MockitoExtension.class) -@WireMockTest(httpPort = 8880) -class LGBridgeTests { - private static final Logger logger = LoggerFactory.getLogger(LGBridgeTests.class); - private String fakeBridgeName = "fakeBridgeId"; - private String fakeLanguage = "pt-BR"; - private String fakeCountry = "BR"; - private String fakeUserName = "someone@some.url"; - private String fakePassword = "somepassword"; - private String gtwResponse = "{\n" + " \"resultCode\":\"0000\",\n" + " \"result\":{\n" - + " \"countryCode\":\"BR\",\n" + " \"languageCode\":\"pt-BR\",\n" - + " \"thinq1Uri\":\"http://localhost:8880/api\",\n" - + " \"thinq2Uri\":\"http://localhost:8880/v1\",\n" + " \"empUri\":\"http://localhost:8880\",\n" - + " \"empSpxUri\":\"http://localhost:8880/spx\",\n" + " \"rtiUri\":\"localhost:8880\",\n" - + " \"mediaUri\":\"localhost:8880\",\n" + " \"appLatestVer\":\"4.0.14230\",\n" - + " \"appUpdateYn\":\"N\",\n" + " \"appLink\":\"market://details?id=com.lgeha.nuts\",\n" - + " \"uuidLoginYn\":\"N\",\n" + " \"lineLoginYn\":\"N\",\n" + " \"lineChannelId\":\"\",\n" - + " \"cicTel\":\"4004-5400\",\n" + " \"cicUri\":\"\",\n" + " \"isSupportVideoYn\":\"N\",\n" - + " \"countryLangDescription\":\"Brasil/Português\",\n" - + " \"empTermsUri\":\"http://localhost:8880\",\n" - + " \"googleAssistantUri\":\"https://assistant.google.com/services/invoke/uid/000000d26892b8a3\",\n" - + " \"smartWorldUri\":\"\",\n" + " \"racUri\":\"br.rac.lgeapi.com\",\n" - + " \"cssUri\":\"https://aic-common.lgthinq.com\",\n" - + " \"cssWebUri\":\"http://s3-an2-op-t20-css-web-resource.s3-website.ap-northeast-2.amazonaws.com\",\n" - + " \"iotssUri\":\"https://aic-iotservice.lgthinq.com\",\n" + " \"chatBotUri\":\"\",\n" - + " \"autoOrderSetUri\":\"\",\n" + " \"autoOrderManageUri\":\"\",\n" - + " \"aiShoppingUri\":\"\",\n" + " \"onestopCall\":\"\",\n" - + " \"onestopEngineerUri\":\"\",\n" + " \"hdssUri\":\"\",\n" + " \"amazonDrsYn\":\"N\",\n" - + " \"features\":{\n" + " \"supportTvIoTServerYn\":\"Y\",\n" - + " \"disableWeatherCard\":\"Y\",\n" + " \"thinqCss\":\"Y\",\n" - + " \"bleConfirmYn\":\"Y\",\n" + " \"tvRcmdContentYn\":\"Y\",\n" - + " \"supportProductManualYn\":\"N\",\n" + " \"clientDbYn\":\"Y\",\n" - + " \"androidAutoYn\":\"Y\",\n" + " \"searchYn\":\"Y\",\n" - + " \"thinqFaq\":\"Y\",\n" + " \"thinqNotice\":\"Y\",\n" - + " \"groupControlYn\":\"Y\",\n" + " \"inAppReviewYn\":\"Y\",\n" - + " \"cicSupport\":\"Y\",\n" + " \"qrRegisterYn\":\"Y\",\n" - + " \"supportBleYn\":\"Y\"\n" + " },\n" + " \"serviceCards\":[\n" + " \n" - + " ],\n" + " \"uris\":{\n" - + " \"takeATourUri\":\"https://s3-us2-op-t20-css-contents.s3.us-west-2.amazonaws.com/workexperience-new/ios/no-version/index.html\",\n" - + " \"gscsUri\":\"https://gscs-america.lge.com\",\n" - + " \"amazonDartUri\":\"https://shs.lgthinq.com\"\n" + " }\n" + " }\n" + "}"; - private String preLoginResponse = "{\n" + " \"encrypted_pw\":\"SOME_DUMMY_ENC_PWD\",\n" - + " \"signature\":\"SOME_DUMMY_SIGNATURE\",\n" + " \"tStamp\":\"1643236928\"\n" + "}"; - private String userIdType = "LGE"; - private String loginSessionId = "emp;11111111;222222222"; - private String loginSessionResponse = "{\n" + " \"account\":{\n" + " \"loginSessionID\":\"" + loginSessionId - + "\",\n" + " \"userID\":\"" + fakeUserName + "\",\n" + " \"userIDType\":\"" + userIdType - + "\",\n" + " \"dateOfBirth\":\"05-05-1978\",\n" + " \"country\":\"BR\",\n" - + " \"countryName\":\"Brazil\",\n" + " \"blacklist\":\"N\",\n" + " \"age\":\"43\",\n" - + " \"isSubscribe\":\"N\",\n" + " \"changePw\":\"N\",\n" + " \"toEmailId\":\"N\",\n" - + " \"periodPW\":\"N\",\n" + " \"lgAccount\":\"Y\",\n" + " \"isService\":\"Y\",\n" - + " \"userNickName\":\"faker\",\n" + " \"termsList\":[\n" + " \n" + " ],\n" - + " \"userIDList\":[\n" + " {\n" + " \"lgeIDList\":[\n" + " {\n" - + " \"lgeIDType\":\"LGE\",\n" + " \"userID\":\"" + fakeUserName + "\"\n" - + " }\n" + " ]\n" + " }\n" + " ],\n" + " \"serviceList\":[\n" - + " {\n" + " \"svcCode\":\"SVC202\",\n" + " \"svcName\":\"LG ThinQ\",\n" - + " \"isService\":\"Y\",\n" + " \"joinDate\":\"30-04-2020\"\n" + " },\n" - + " {\n" + " \"svcCode\":\"SVC710\",\n" + " \"svcName\":\"EMP OAuth\",\n" - + " \"isService\":\"Y\",\n" + " \"joinDate\":\"30-04-2020\"\n" + " }\n" - + " ],\n" + " \"displayUserID\":\"faker\",\n" + " \"notiList\":{\n" - + " \"totCount\":\"0\",\n" + " \"list\":[\n" + " \n" + " ]\n" - + " },\n" + " \"authUser\":\"N\",\n" + " \"dummyIdFlag\":\"N\"\n" + " }\n" + "}"; - private String userInfoReturned = "{\n" + " \"status\":1,\n" + " \"account\":{\n" + " \"userID\":\"" - + fakeUserName + "\",\n" + " \"userNo\":\"BR2005200239023\",\n" + " \"userIDType\":\"LGE\",\n" - + " \"displayUserID\":\"faker\",\n" + " \"userIDList\":[\n" + " {\n" - + " \"lgeIDList\":[\n" + " {\n" + " \"lgeIDType\":\"LGE\",\n" - + " \"userID\":\"" + fakeUserName + "\"\n" + " }\n" + " ]\n" - + " }\n" + " ],\n" + " \"dateOfBirth\":\"05-05-1978\",\n" + " \"country\":\"BR\",\n" - + " \"countryName\":\"Brazil\",\n" + " \"blacklist\":\"N\",\n" + " \"age\":\"45\",\n" - + " \"isSubscribe\":\"N\",\n" + " \"changePw\":\"N\",\n" + " \"toEmailId\":\"N\",\n" - + " \"periodPW\":\"N\",\n" + " \"lgAccount\":\"Y\",\n" + " \"isService\":\"Y\",\n" - + " \"userNickName\":\"faker\",\n" + " \"authUser\":\"N\",\n" + " \"serviceList\":[\n" - + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"LG ThinQ\",\n" - + " \"svcCode\":\"SVC202\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " },\n" - + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"LG Developer\",\n" - + " \"svcCode\":\"SVC609\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " },\n" - + " {\n" + " \"isService\":\"Y\",\n" + " \"svcName\":\"MC OAuth\",\n" - + " \"svcCode\":\"SVC710\",\n" + " \"joinDate\":\"29-05-2018\"\n" + " }\n" - + " ]\n" + " }\n" + "}"; - private String dashboardListReturned = "{\n" + " \"resultCode\":\"0000\",\n" + " \"result\":{\n" - + " \"langPackCommonVer\":\"125.6\",\n" - + " \"langPackCommonUri\":\"https://objectcontent.lgthinq.com/f1cae877-1d1e-4c12-8010-acbcdcce2df1?hdnts=exp=1706183232~hmac=257aa8146a089de87496cb13aa0b43761a19e7db225558dfb8996919746b465b\",\n" - + " \"item\":[\n" + " {\n" + " \"modelName\":\"RAC_056905_WW\",\n" - + " \"subModelName\":\"\",\n" + " \"deviceType\":401,\n" - + " \"deviceCode\":\"AI01\",\n" + " \"alias\":\"Bedroom\",\n" - + " \"deviceId\":\"abra-cadabra-0001-5771\",\n" + " \"fwVer\":\"2.5.8_RTOS_3K\",\n" - + " \"imageFileName\":\"ac_home_wall_airconditioner_img.png\",\n" - + " \"imageUrl\":\"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6\",\n" - + " \"smallImageUrl\":\"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5\",\n" - + " \"ssid\":\"dummy-ssid\",\n" + " \"macAddress\":\"74:40:be:92:ac:08\",\n" - + " \"networkType\":\"02\",\n" + " \"timezoneCode\":\"America/Sao_Paulo\",\n" - + " \"timezoneCodeAlias\":\"Brazil/Sao Paulo\",\n" + " \"utcOffset\":-3,\n" - + " \"utcOffsetDisplay\":\"-03:00\",\n" + " \"dstOffset\":-2,\n" - + " \"dstOffsetDisplay\":\"-02:00\",\n" + " \"curOffset\":-2,\n" - + " \"curOffsetDisplay\":\"-02:00\",\n" - + " \"sdsGuide\":\"{\\\"deviceCode\\\":\\\"AI01\\\"}\",\n" + " \"newRegYn\":\"N\",\n" - + " \"remoteControlType\":\"\",\n" + " \"modelJsonVer\":7.13,\n" - + " \"modelJsonUri\":\"https://aic.lgthinq.com:46030/api/webContents/modelJSON?modelName=modelJSON_401&countryCode=KR&contentsId=abra-cadabra-0001-5771&authKey=thinq\",\n" - + " \"appModuleVer\":12.49,\n" - + " \"appModuleUri\":\"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155\",\n" - + " \"appRestartYn\":\"Y\",\n" + " \"appModuleSize\":6082481,\n" - + " \"langPackProductTypeVer\":59.9,\n" - + " \"langPackProductTypeUri\":\"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea\",\n" - + " \"langPackModelVer\":\"\",\n" + " \"langPackModelUri\":\"\",\n" - + " \"deviceState\":\"E\",\n" + " \"online\":false,\n" - + " \"platformType\":\"thinq1\",\n" + " \"regDt\":2.0200909053555E13,\n" - + " \"modelProtocol\":\"STANDARD\",\n" + " \"order\":0,\n" - + " \"drServiceYn\":\"N\",\n" + " \"fwInfoList\":[\n" + " {\n" - + " \"partNumber\":\"SAA38690433\",\n" + " \"checksum\":\"00000000\",\n" - + " \"verOrder\":0\n" + " }\n" + " ],\n" - + " \"guideTypeYn\":\"Y\",\n" + " \"guideType\":\"RAC_TYPE1\",\n" - + " \"regDtUtc\":\"20200909073555\",\n" + " \"regIndex\":0,\n" - + " \"groupableYn\":\"Y\",\n" + " \"controllableYn\":\"Y\",\n" - + " \"combinedProductYn\":\"N\",\n" + " \"masterYn\":\"Y\",\n" - + " \"pccModelYn\":\"N\",\n" + " \"sdsPid\":{\n" + " \"sds4\":\"\",\n" - + " \"sds3\":\"\",\n" + " \"sds2\":\"\",\n" + " \"sds1\":\"\"\n" - + " },\n" + " \"autoOrderYn\":\"N\",\n" - + " \"modelNm\":\"RAC_056905_WW\",\n" + " \"initDevice\":false,\n" - + " \"existsEntryPopup\":\"N\",\n" + " \"tclcount\":0\n" + " },\n" - + " {\n" + " \"appType\":\"NUTS\",\n" + " \"modelCountryCode\":\"WW\",\n" - + " \"countryCode\":\"BR\",\n" + " \"modelName\":\"RAC_056905_WW\",\n" - + " \"deviceType\":401,\n" + " \"deviceCode\":\"AI01\",\n" - + " \"alias\":\"Office\",\n" + " \"deviceId\":\"abra-cadabra-0001-5772\",\n" - + " \"fwVer\":\"\",\n" - + " \"imageFileName\":\"ac_home_wall_airconditioner_img.png\",\n" - + " \"imageUrl\":\"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6\",\n" - + " \"smallImageUrl\":\"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5\",\n" - + " \"ssid\":\"smart-gameficacao\",\n" + " \"softapId\":\"\",\n" - + " \"softapPass\":\"\",\n" + " \"macAddress\":\"\",\n" - + " \"networkType\":\"02\",\n" + " \"timezoneCode\":\"America/Sao_Paulo\",\n" - + " \"timezoneCodeAlias\":\"Brazil/Sao Paulo\",\n" + " \"utcOffset\":-3,\n" - + " \"utcOffsetDisplay\":\"-03:00\",\n" + " \"dstOffset\":-2,\n" - + " \"dstOffsetDisplay\":\"-02:00\",\n" + " \"curOffset\":-2,\n" - + " \"curOffsetDisplay\":\"-02:00\",\n" - + " \"sdsGuide\":\"{\\\"deviceCode\\\":\\\"AI01\\\"}\",\n" + " \"newRegYn\":\"N\",\n" - + " \"remoteControlType\":\"\",\n" + " \"userNo\":\"BR2004259832795\",\n" - + " \"tftYn\":\"N\",\n" + " \"modelJsonVer\":12.11,\n" - + " \"modelJsonUri\":\"https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f\",\n" - + " \"appModuleVer\":12.49,\n" - + " \"appModuleUri\":\"https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155\",\n" - + " \"appRestartYn\":\"Y\",\n" + " \"appModuleSize\":6082481,\n" - + " \"langPackProductTypeVer\":59.9,\n" - + " \"langPackProductTypeUri\":\"https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea\",\n" - + " \"deviceState\":\"E\",\n" + " \"snapshot\":{\n" - + " \"airState.windStrength\":8.0,\n" + " \"airState.wMode.lowHeating\":0.0,\n" - + " \"airState.diagCode\":0.0,\n" - + " \"airState.lightingState.displayControl\":1.0,\n" - + " \"airState.wDir.hStep\":0.0,\n" + " \"mid\":8.4615358E7,\n" - + " \"airState.energy.onCurrent\":476.0,\n" - + " \"airState.wMode.airClean\":0.0,\n" - + " \"airState.quality.sensorMon\":0.0,\n" - + " \"airState.energy.accumulatedTime\":0.0,\n" - + " \"airState.miscFuncState.antiBugs\":0.0,\n" - + " \"airState.tempState.target\":18.0,\n" + " \"airState.operation\":1.0,\n" - + " \"airState.wMode.jet\":0.0,\n" + " \"airState.wDir.vStep\":2.0,\n" - + " \"timestamp\":1.643248573766E12,\n" + " \"airState.powerSave.basic\":0.0,\n" - + " \"airState.quality.PM10\":0.0,\n" + " \"static\":{\n" - + " \"deviceType\":\"401\",\n" + " \"countryCode\":\"BR\"\n" - + " },\n" + " \"airState.quality.overall\":0.0,\n" - + " \"airState.tempState.current\":25.0,\n" - + " \"airState.miscFuncState.extraOp\":0.0,\n" - + " \"airState.energy.accumulated\":0.0,\n" - + " \"airState.reservation.sleepTime\":0.0,\n" - + " \"airState.miscFuncState.autoDry\":0.0,\n" - + " \"airState.reservation.targetTimeToStart\":0.0,\n" + " \"meta\":{\n" - + " \"allDeviceInfoUpdate\":false,\n" - + " \"messageId\":\"fVz2AE-2SC-rf3GnerGdeQ\"\n" + " },\n" - + " \"airState.quality.PM1\":0.0,\n" + " \"airState.wMode.smartCare\":0.0,\n" - + " \"airState.quality.PM2\":0.0,\n" + " \"online\":true,\n" - + " \"airState.opMode\":0.0,\n" - + " \"airState.reservation.targetTimeToStop\":0.0,\n" - + " \"airState.filterMngStates.maxTime\":0.0,\n" - + " \"airState.filterMngStates.useTime\":0.0\n" + " },\n" - + " \"online\":true,\n" + " \"platformType\":\"thinq2\",\n" - + " \"area\":45883,\n" + " \"regDt\":2.0220111184827E13,\n" - + " \"blackboxYn\":\"Y\",\n" + " \"modelProtocol\":\"STANDARD\",\n" - + " \"order\":0,\n" + " \"drServiceYn\":\"N\",\n" + " \"fwInfoList\":[\n" - + " {\n" + " \"checksum\":\"00004105\",\n" - + " \"order\":1.0,\n" + " \"partNumber\":\"SAA40128563\"\n" - + " }\n" + " ],\n" + " \"modemInfo\":{\n" - + " \"appVersion\":\"clip_hna_v1.9.116\",\n" - + " \"modelName\":\"RAC_056905_WW\",\n" + " \"modemType\":\"QCOM_QCA4010\",\n" - + " \"ruleEngine\":\"y\"\n" + " },\n" + " \"guideTypeYn\":\"Y\",\n" - + " \"guideType\":\"RAC_TYPE1\",\n" + " \"regDtUtc\":\"20220111204827\",\n" - + " \"regIndex\":0,\n" + " \"groupableYn\":\"Y\",\n" - + " \"controllableYn\":\"Y\",\n" + " \"combinedProductYn\":\"N\",\n" - + " \"masterYn\":\"Y\",\n" + " \"pccModelYn\":\"N\",\n" + " \"sdsPid\":{\n" - + " \"sds4\":\"\",\n" + " \"sds3\":\"\",\n" + " \"sds2\":\"\",\n" - + " \"sds1\":\"\"\n" + " },\n" + " \"autoOrderYn\":\"N\",\n" - + " \"initDevice\":false,\n" + " \"existsEntryPopup\":\"N\",\n" - + " \"tclcount\":0\n" + " }\n" + " ],\n" + " \"group\":[\n" + " \n" - + " ]\n" + " }\n" + "}"; - private String secretKey = "gregre9812012910291029120912091209"; - private String oauthTokenSearchKeyReturned = "{\"returnData\":\"" + secretKey + "\"}"; - private String refreshToken = "12897238974bb327862378ef290128390273aa7389723894734de"; - private String accessToken = "11a1222c39f16a5c8b3fa45bb4c9be2e00a29a69dced2fa7fe731f1728346ee669f1a96d1f0b4925e5aa330b6dbab882772"; - private String sessionTokenReturned = "{\"status\":1,\"access_token\":\"" + accessToken - + "\",\"expires_in\":\"3600\",\"refresh_token\":\"" + refreshToken - + "\",\"oauth2_backend_url\":\"http://localhost:8880/\"}"; - - private String getCurrentTimestamp() { - SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - return sdf.format(new Date()); - } - - @Test - public void testDiscoveryThings() { - stubFor(get(GATEWAY_SERVICE_PATH).willReturn(ok(gtwResponse))); - String preLoginPwd = RestUtils.getPreLoginEncPwd(fakePassword); - stubFor(post("/spx" + PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) - .willReturn(ok(preLoginResponse))); - URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + OAUTH_SEARCH_KEY_PATH) - .queryParam("key_name", "OAUTH_SECRETKEY").queryParam("sever_type", "OP").build(); - stubFor(get(String.format("%s?%s", uri.getPath(), uri.getQuery())).willReturn(ok(oauthTokenSearchKeyReturned))); - stubFor(post(V2_SESSION_LOGIN_PATH + fakeUserName).withRequestBody(containing("user_auth2=SOME_DUMMY_ENC_PWD")) - .withHeader("X-Signature", equalTo("SOME_DUMMY_SIGNATURE")) - .withHeader("X-Timestamp", equalTo("1643236928")).willReturn(ok(loginSessionResponse))); - stubFor(get(V2_USER_INFO).willReturn(ok(userInfoReturned))); - stubFor(get("/v1" + V2_LS_PATH).willReturn(ok(dashboardListReturned))); - String currTimestamp = getCurrentTimestamp(); - Map empData = new LinkedHashMap<>(); - empData.put("account_type", userIdType); - empData.put("country_code", fakeCountry); - empData.put("username", fakeUserName); - - stubFor(post("/emp/oauth2/token/empsession").withRequestBody(containing("account_type=" + userIdType)) - .withRequestBody(containing("country_code=" + fakeCountry)) - .withRequestBody(containing("username=" + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8))) - .withHeader("lgemp-x-session-key", equalTo(loginSessionId)).willReturn(ok(sessionTokenReturned))); - // faking some constants - LGThinqBindingConstants.GATEWAY_URL = "http://localhost:8880" + GATEWAY_SERVICE_PATH; - LGThinqBindingConstants.V2_EMP_SESS_URL = "http://localhost:8880/emp/oauth2/token/empsession"; - Bridge fakeThing = mock(Bridge.class); - ThingUID fakeThingUid = mock(ThingUID.class); - when(fakeThingUid.getId()).thenReturn(fakeBridgeName); - when(fakeThing.getUID()).thenReturn(fakeThingUid); - String tempDir = System.getProperty("java.io.tmpdir"); - LGThinqBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; - LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; - LGBridgeHandler b = new LGBridgeHandler(fakeThing); - LGBridgeHandler spyBridge = spy(b); - doReturn(new LGThinqConfiguration(fakeUserName, fakePassword, fakeCountry, fakeLanguage, 60)).when(spyBridge) - .getConfigAs(any(Class.class)); - spyBridge.initialize(); - LGApiClientService service1 = LGApiV1ClientServiceImpl.getInstance(); - LGApiClientService service2 = LGApiV2ClientServiceImpl.getInstance(); - TokenManager tokenManager = TokenManager.getInstance(); - try { - if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { - tokenManager.oauthFirstRegistration(fakeBridgeName, fakeLanguage, fakeCountry, fakeUserName, - fakePassword); - } - List devices = service2.listAccountDevices("bridgeTest"); - assertEquals(devices.size(), 2); - } catch (Exception e) { - logger.error("Error testing facade", e); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json index 6c1056fbe2375..fcb611b9d95c6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json @@ -87,7 +87,7 @@ "imageFileName":"ac_home_wall_airconditioner_img.png", "imageUrl":"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", "smallImageUrl":"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", - "ssid":"xxxxxxxxx", + "ssid":"smart-gameficacao", "softapId":"", "softapPass":"", "macAddress":"", @@ -103,7 +103,7 @@ "sdsGuide":"{\"deviceCode\":\"AI01\"}", "newRegYn":"N", "remoteControlType":"", - "userNo":"xxxxxxxxxxx", + "userNo":"BR2004259832795", "tftYn":"N", "modelJsonVer":12.11, "modelJsonUri":"https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f", From e03b7fabcd9efe3d7ed0eff97a187d6e94de8b21 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 12:54:10 -0300 Subject: [PATCH 060/130] [lgthinq] adjusting class names & references, removed portuguese TODOs. Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 82 +++++++++---------- .../internal/LGThinqBindingConstants.java | 14 ++-- .../internal/LGThinqConfiguration.java | 16 ++-- .../lgthinq/internal/LGThinqDeviceThing.java | 6 +- .../internal/errors/LGApiException.java | 31 ------- .../LGDeviceV1MonitorExpiredException.java | 32 -------- .../errors/LGDeviceV1OfflineException.java | 33 -------- .../internal/errors/LGGatewayException.java | 27 ------ .../LGThinqDeviceV1OfflineException.java | 2 +- .../errors/RefreshTokenException.java | 2 +- .../handler/LGThinqBridgeHandler.java | 34 ++++---- .../lgapi/LGThinqApiClientService.java | 28 +++---- .../lgapi/LGThinqApiClientServiceImpl.java | 28 +++---- .../lgapi/LGThinqApiV1ClientServiceImpl.java | 82 +++++++++---------- .../lgapi/LGThinqApiV2ClientServiceImpl.java | 58 ++++++------- 15 files changed, 174 insertions(+), 301 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index ea0c1b7859654..1ea6d4f58242d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -12,18 +12,12 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; - -import java.util.*; -import java.util.concurrent.*; - import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; @@ -42,6 +36,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.*; +import java.util.concurrent.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + /** * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. @@ -65,10 +65,10 @@ public class LGThinqAirConditionerHandler extends BaseThingHandler implements LG private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, ThingStatusDetail.CONFIGURATION_ERROR); - private @Nullable ScheduledFuture thingStatePoolingJob; + private @Nullable ScheduledFuture thingStatePollingJob; private @Nullable Future commandExecutorQueueJob; // *** Long running isolated threadpools. - private final ScheduledExecutorService poolingScheduler = Executors.newScheduledThreadPool(1); + private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); private final ExecutorService executorService = Executors.newFixedThreadPool(1); private boolean monitorV1Began = false; @@ -128,7 +128,7 @@ private void initializeThing(@Nullable ThingStatus bridgeStatus) { if (!deviceId.isBlank()) { try { updateChannelDynStateDescription(); - } catch (LGApiException e) { + } catch (LGThinqApiException e) { logger.error( "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); } @@ -173,10 +173,10 @@ private void stopCommandExecutorQueueJob() { } } - protected void startThingStatePooling() { - if (thingStatePoolingJob == null || thingStatePoolingJob.isDone()) { - thingStatePoolingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, - DEFAULT_STATE_POOLING_UPDATE_DELAY, TimeUnit.SECONDS); + protected void startThingStatePolling() { + if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { + thingStatePollingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, + DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); } } @@ -221,7 +221,7 @@ private void updateThingStateFromLG() { } private ScheduledExecutorService getLocalScheduler() { - return poolingScheduler; + return pollingScheduler; } private String getBridgeId() { @@ -250,7 +250,7 @@ private String emptyIfNull(@Nullable String value) { } @Override - public void updateChannelDynStateDescription() throws LGApiException { + public void updateChannelDynStateDescription() throws LGThinqApiException { ACCapability acCap = getAcCapabilities(); if (isLinked(opModeChannelUID)) { List options = new ArrayList<>(); @@ -267,7 +267,7 @@ public void updateChannelDynStateDescription() throws LGApiException { } @Override - public ACCapability getAcCapabilities() throws LGApiException { + public ACCapability getAcCapabilities() throws LGThinqApiException { if (acCapability == null) { acCapability = lgThinqApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); } @@ -275,7 +275,7 @@ public ACCapability getAcCapabilities() throws LGApiException { } @Nullable - private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiException { + private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { return lgThinqApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); @@ -285,14 +285,14 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiExcepti monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); monitorV1Began = true; } - } catch (LGDeviceV1OfflineException e) { + } catch (LGThinqDeviceV1OfflineException e) { forceStopDeviceV1Monitor(deviceId); ACSnapShot shot = new ACSnapShotV1(); shot.setOnline(false); return shot; } catch (Exception e) { forceStopDeviceV1Monitor(deviceId); - throw new LGApiException("Error starting device monitor in LG API for the device:" + deviceId, e); + throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); } int retries = 10; ACSnapShot shot; @@ -305,7 +305,7 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiExcepti } Thread.sleep(500); retries--; - } catch (LGDeviceV1MonitorExpiredException e) { + } catch (LGThinqDeviceV1MonitorExpiredException e) { forceStopDeviceV1Monitor(deviceId); logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); return null; @@ -314,34 +314,34 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGApiExcepti // interaction // Force restart monitoring because of the errors returned (just in case) forceStopDeviceV1Monitor(deviceId); - throw new LGApiException("Error getting monitor data for the device:" + deviceId, e); + throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); } } forceStopDeviceV1Monitor(deviceId); - throw new LGApiException("Exhausted trying to get monitor data for the device:" + deviceId); + throw new LGThinqApiException("Exhausted trying to get monitor data for the device:" + deviceId); } } - protected void stopThingStatePooling() { - if (thingStatePoolingJob != null && !thingStatePoolingJob.isDone()) { - logger.debug("Stopping LG thinq pooling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); - thingStatePoolingJob.cancel(true); + protected void stopThingStatePolling() { + if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { + logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePollingJob.cancel(true); } } private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { - // start the thing pooling - startThingStatePooling(); + // start the thing polling + startThingStatePolling(); } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { // comunication error is not a specific Bridge error, then we must analise it to give // this thinq the change to recovery from communication errors if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { - // in case of status offline, I only stop the pooling if is not an COMMUNICATION_ERROR or if + // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if // the bridge is out - stopThingStatePooling(); + stopThingStatePolling(); } } @@ -356,7 +356,7 @@ protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetai @Override public void onDeviceAdded(LGDevice device) { - // TODO - handle it + // TODO - handle it. Think if it's needed } @Override @@ -381,27 +381,27 @@ public String getDeviceUriJsonConfig() { @Override public boolean onDeviceStateChanged() { - // TODO - HANDLE IT + // TODO - HANDLE IT, Think if it's needed return false; } @Override public void onDeviceRemoved() { - // TODO - HANDLE IT + // TODO - HANDLE IT, Think if it's needed } @Override public void onDeviceGone() { - // TODO - HANDLE IT + // TODO - HANDLE IT, Think if it's needed } @Override public void dispose() { - if (thingStatePoolingJob != null) { - thingStatePoolingJob.cancel(true); - stopThingStatePooling(); + if (thingStatePollingJob != null) { + thingStatePollingJob.cancel(true); + stopThingStatePolling(); stopCommandExecutorQueueJob(); - thingStatePoolingJob = null; + thingStatePollingJob = null; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 4165da3a117c8..56cedc0759465 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.lgthinq.internal; -import java.io.File; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; import org.openhab.core.OpenHAB; import org.openhab.core.thing.ThingTypeUID; +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + /** * The {@link LGThinqBindingConstants} class defines common constants, which are * used across the whole binding. @@ -53,7 +53,7 @@ public class LGThinqBindingConstants { public static final String V2_DEVICE_CONFIG_PATH = "service/devices/"; public static final String V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/control-sync"; public static final String V1_START_MON_PATH = "rti/rtiMon"; - public static final String V1_POOL_MON_PATH = "rti/rtiResult"; + public static final String V1_MON_DATA_PATH = "rti/rtiResult"; public static final String V1_CONTROL_OP = "rti/rtiControl"; public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; public static final String GATEWAY_SERVICE_PATH = "/v1/service/application/gateway-uri"; @@ -98,7 +98,7 @@ public class LGThinqBindingConstants { public static final int SEARCH_TIME = 20; // delay between each devices's scan for state changes (in seconds) - public static final int DEFAULT_STATE_POOLING_UPDATE_DELAY = 30; + public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 30; // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java index c3a618b7dae5e..5bb094898006a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java @@ -12,8 +12,6 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - import org.eclipse.jdt.annotation.NonNullByDefault; /** @@ -30,18 +28,18 @@ public class LGThinqConfiguration { public String password = ""; public String country = ""; public String language = ""; - public Integer poolingIntervalSec = 0; + public Integer pollingIntervalSec = 0; public LGThinqConfiguration() { } public LGThinqConfiguration(String username, String password, String country, String language, - Integer poolingIntervalSec) { + Integer pollingIntervalSec) { this.username = username; this.password = password; this.country = country; this.language = language; - this.poolingIntervalSec = poolingIntervalSec; + this.pollingIntervalSec = pollingIntervalSec; } public String getUsername() { @@ -60,8 +58,8 @@ public String getLanguage() { return language; } - public Integer getPoolingIntervalSec() { - return poolingIntervalSec; + public Integer getPollingIntervalSec() { + return pollingIntervalSec; } public void setUsername(String username) { @@ -80,7 +78,7 @@ public void setLanguage(String language) { this.language = language; } - public void setPoolingIntervalSec(Integer poolingIntervalSec) { - this.poolingIntervalSec = poolingIntervalSec; + public void setPollingIntervalSec(Integer pollingIntervalSec) { + this.pollingIntervalSec = pollingIntervalSec; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index 64c77140d443a..ddfc7a165d4f7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -13,7 +13,7 @@ package org.openhab.binding.lgthinq.internal; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgapi.model.ACCapability; import org.openhab.binding.lgthinq.lgapi.model.LGDevice; @@ -41,7 +41,7 @@ public interface LGThinqDeviceThing { void onDeviceGone(); - void updateChannelDynStateDescription() throws LGApiException; + void updateChannelDynStateDescription() throws LGThinqApiException; - ACCapability getAcCapabilities() throws LGApiException; + ACCapability getAcCapabilities() throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java deleted file mode 100644 index 95ca53e421209..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGApiException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGApiException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGApiException extends LGThinqException { - public LGApiException(String message, Throwable cause) { - super(message, cause); - } - - public LGApiException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java deleted file mode 100644 index 3272c29338952..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1MonitorExpiredException.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGDeviceV1MonitorExpiredException} - Normally caught by V1 API in monitoring device. - * After long-running moniotor, it indicates the need to refresh the monitor. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGDeviceV1MonitorExpiredException extends LGThinqException { - public LGDeviceV1MonitorExpiredException(String message, Throwable cause) { - super(message, cause); - } - - public LGDeviceV1MonitorExpiredException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java deleted file mode 100644 index dee34280a8c21..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGDeviceV1OfflineException.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGDeviceV1OfflineException} - Normally caught by V1 API in monitoring device. - * When the device is OFFLINE (away from internet), the API doesn't return data information and this - * exception is thrown to indicate that this device is offline for monitoring - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGDeviceV1OfflineException extends LGThinqException { - public LGDeviceV1OfflineException(String message, Throwable cause) { - super(message, cause); - } - - public LGDeviceV1OfflineException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java deleted file mode 100644 index 20942017c4a1a..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGGatewayException.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGGatewayException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGGatewayException extends LGThinqException { - public LGGatewayException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java index 1bdbae5a9cc73..ef4d5163a0af6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java @@ -22,7 +22,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqDeviceV1OfflineException extends LGThinqApiException { +public class LGThinqDeviceV1OfflineException extends LGThinqException { public LGThinqDeviceV1OfflineException(String message, Throwable cause) { super(message, cause); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java index b6f4e1dcb9da3..03d018234ea18 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java @@ -20,7 +20,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class RefreshTokenException extends LGApiException { +public class RefreshTokenException extends LGThinqApiException { public RefreshTokenException(String message, Throwable cause) { super(message, cause); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index de1ee7691abbe..24c7c52adce07 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -12,17 +12,6 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; @@ -42,6 +31,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; + /** * The {@link LGThinqBridgeHandler} * @@ -82,7 +82,7 @@ public LGThinqBridgeHandler(Bridge bridge) { final ReentrantLock pollingLock = new ReentrantLock(); /** - * Abstract Runnable Pooling Class to schedule sincronization status of the Bridge Thing Kinds ! + * Abstract Runnable Polling Class to schedule sincronization status of the Bridge Thing Kinds ! */ abstract class PollingRunnable implements Runnable { protected final String bridgeName; @@ -285,25 +285,25 @@ public void initialize() { "@text/error.mandotory-fields-missing"); } else { updateStatus(ThingStatus.UNKNOWN); - startLGDevicePolling(); + startLGThinqDevicePolling(); } } - private void startLGDevicePolling() { + private void startLGThinqDevicePolling() { // stop current scheduler, if any if (devicePollingJob != null && !devicePollingJob.isDone()) { devicePollingJob.cancel(true); } long pollingInterval; - int configPollingInterval = lgthinqConfig.getPoolingIntervalSec(); - // It's not recommended to pool for resources in LG API short intervals to do not enter in BlackList + int configPollingInterval = lgthinqConfig.getPollingIntervalSec(); + // It's not recommended to polling for resources in LG API short intervals to do not enter in BlackList if (configPollingInterval < 300) { pollingInterval = TimeUnit.SECONDS.toSeconds(300); logger.info("Wrong configuration value for polling interval. Using default value: {}s", pollingInterval); } else { pollingInterval = configPollingInterval; } - // submit instantlly and schedule for the next pooling interval. + // submit instantlly and schedule for the next polling interval. scheduler.submit(lgDevicePollingRunnable); devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, pollingInterval, pollingInterval, TimeUnit.SECONDS); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index 6ecbf3e002343..8e5f80a8553bb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -19,9 +19,9 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgapi.model.*; @@ -33,36 +33,36 @@ @NonNullByDefault public interface LGThinqApiClientService { - List listAccountDevices(String bridgeName) throws LGApiException; + List listAccountDevices(String bridgeName) throws LGThinqApiException; - Map getDeviceSettings(String bridgeName, String deviceId) throws LGApiException; + Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException; /** * Retrieve actual data from device (its sensors and points states). * * @param deviceId device number * @return return snapshot state of the device - * @throws LGApiException if some error interacting with LG API Server occur. + * @throws LGThinqApiException if some error interacting with LG API Server occur. */ @Nullable - ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException; + ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; - void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGApiException; + void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; - void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException; + void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; - void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException; + void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; - void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGApiException; + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGThinqApiException; String startMonitor(String bridgeName, String deviceId) - throws LGApiException, LGDeviceV1OfflineException, IOException; + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; - ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException; + ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; @Nullable ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) - throws LGApiException, LGDeviceV1MonitorExpiredException, IOException; + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index edc2b07c5c5b0..bb5d0c4f97856 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -23,7 +23,7 @@ import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgapi.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -72,10 +72,10 @@ static Map getCommonHeaders(String language, String country, Str * Even using V2 URL, this endpoint support grab informations about account devices from V1 and V2. * * @return list os LG Devices. - * @throws LGApiException if some communication error occur. + * @throws LGThinqApiException if some communication error occur. */ @Override - public List listAccountDevices(String bridgeName) throws LGApiException { + public List listAccountDevices(String bridgeName) throws LGThinqApiException { try { TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); @@ -84,7 +84,7 @@ public List listAccountDevices(String bridgeName) throws LGApiExceptio RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); return handleListAccountDevicesResult(resp); } catch (Exception e) { - throw new LGApiException("Erros list account devices from LG Server API", e); + throw new LGThinqApiException("Erros list account devices from LG Server API", e); } } @@ -94,10 +94,10 @@ public List listAccountDevices(String bridgeName) throws LGApiExceptio * * @param deviceId device ID for de desired V2 LG Thinq. * @return return map containing metamodel of settings and snapshot - * @throws LGApiException if some communication error occur. + * @throws LGThinqApiException if some communication error occur. */ @Override - public Map getDeviceSettings(String bridgeName, String deviceId) throws LGApiException { + public Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException { try { TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) @@ -107,28 +107,28 @@ public Map getDeviceSettings(String bridgeName, String deviceId) RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); return handleDeviceSettingsResult(resp); } catch (Exception e) { - throw new LGApiException("Erros list account devices from LG Server API", e); + throw new LGThinqApiException("Erros list account devices from LG Server API", e); } } - private Map handleDeviceSettingsResult(RestResult resp) throws LGApiException { + private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { return genericHandleDeviceSettingsResult(resp, logger, objectMapper); } @SuppressWarnings("unchecked") static Map genericHandleDeviceSettingsResult(RestResult resp, Logger logger, - ObjectMapper objectMapper) throws LGApiException { + ObjectMapper objectMapper) throws LGThinqApiException { Map deviceSettings; if (resp.getStatusCode() != 200) { logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGApiException(String.format("Error calling device settings from LG Server API. The reason is:%s", + throw new LGThinqApiException(String.format("Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (!"0000".equals(deviceSettings.get("resultCode"))) { - throw new LGApiException( + throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", deviceSettings.get("resultCode"))); } @@ -142,19 +142,19 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo } @SuppressWarnings("unchecked") - private List handleListAccountDevicesResult(RestResult resp) throws LGApiException { + private List handleListAccountDevicesResult(RestResult resp) throws LGThinqApiException { Map devicesResult; List devices; if (resp.getStatusCode() != 200) { logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGApiException(String.format("Error calling device list from LG Server API. The reason is:%s", + throw new LGThinqApiException(String.format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (!"0000".equals(devicesResult.get("resultCode"))) { - throw new LGApiException( + throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", devicesResult.get("resultCode"))); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index 51de488bb84f6..ff8e1f3ce3479 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -12,14 +12,9 @@ */ package org.openhab.binding.lgthinq.lgapi; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -27,17 +22,20 @@ import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgapi.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.IOException; +import java.util.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; /** * The {@link LGThinqApiV1ClientServiceImpl} @@ -74,11 +72,11 @@ protected TokenManager getTokenManager() { * * @param deviceId device ID for de desired V2 LG Thinq. * @return return map containing metamodel of settings and snapshot - * @throws LGApiException if some communication error occur. + * @throws LGThinqApiException if some communication error occur. */ @Override @Nullable - public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException { + public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { throw new UnsupportedOperationException("Method not supported in V1 API device."); } @@ -99,46 +97,46 @@ public RestResult sendControlCommands(String bridgeName, String deviceId, String @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGApiException { + throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", newPowerState.commandValue()); handleV1GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting device power", e); + throw new LGThinqApiException("Error adjusting device power", e); } } @Override - public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException { + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "OpMode", newOpMode); handleV1GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting operation mode", e); + throw new LGThinqApiException("Error adjusting operation mode", e); } } @Override - public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException { + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "WindStrength", newFanSpeed); handleV1GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting fan speed", e); + throw new LGThinqApiException("Error adjusting fan speed", e); } } @Override public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGApiException { + throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "TempCfg", newTargetTemp.commandValue()); handleV1GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting target temperature", e); + throw new LGThinqApiException("Error adjusting target temperature", e); } } @@ -147,11 +145,11 @@ public void changeTargetTemperature(String bridgeName, String deviceId, ACTarget * * @param deviceId Device ID * @return Work1 to be uses to grab data during monitoring. - * @throws LGApiException If some communication error occur. + * @throws LGThinqApiException If some communication error occur. */ @Override public String startMonitor(String bridgeName, String deviceId) - throws LGApiException, LGDeviceV1OfflineException, IOException { + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -166,7 +164,7 @@ public String startMonitor(String bridgeName, String deviceId) @NonNull private Map handleV1GenericErrorResult(@Nullable RestResult resp) - throws LGApiException, LGDeviceV1OfflineException { + throws LGThinqApiException, LGThinqDeviceV1OfflineException { Map metaResult; Map envelope = Collections.emptyMap(); if (resp == null) { @@ -174,7 +172,7 @@ private Map handleV1GenericErrorResult(@Nullable RestResult resp } if (resp.getStatusCode() != 200) { logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGApiException( + throw new LGThinqApiException( String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { @@ -182,14 +180,14 @@ private Map handleV1GenericErrorResult(@Nullable RestResult resp }); envelope = (Map) metaResult.get("lgedmRoot"); if (envelope == null) { - throw new LGApiException(String.format( + throw new LGThinqApiException(String.format( "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); } else if (!"0000".equals(envelope.get("returnCd"))) { if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { // Disconnected Device - throw new LGDeviceV1OfflineException("Device is offline. No data available"); + throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); } - throw new LGApiException( + throw new LGThinqApiException( String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", metaResult.get("returnCd"))); } @@ -202,7 +200,7 @@ private Map handleV1GenericErrorResult(@Nullable RestResult resp @Override public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGApiException, RefreshTokenException, IOException, LGDeviceV1OfflineException { + throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -216,9 +214,9 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) @Override @Nullable public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) - throws LGApiException, LGDeviceV1MonitorExpiredException, IOException { + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_POOL_MON_PATH); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" @@ -230,7 +228,7 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev // offline flag. try { envelop = handleV1GenericErrorResult(resp); - } catch (LGDeviceV1OfflineException e) { + } catch (LGThinqDeviceV1OfflineException e) { ACSnapShot shot = new ACSnapShotV2(); shot.setOnline(false); return shot; @@ -239,7 +237,7 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev && ((Map) envelop.get("workList")).get("returnData") != null) { Map workList = ((Map) envelop.get("workList")); if (!"0000".equals(workList.get("returnCode"))) { - LGDeviceV1MonitorExpiredException e = new LGDeviceV1MonitorExpiredException( + LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); logger.warn("{}", e.getMessage()); throw e; @@ -266,11 +264,11 @@ private File getCapFileForDevice(String deviceId) { * @param deviceId ID of the device * @param uri URI of the config capanility * @return return simplified capability - * @throws LGApiException If some error occurr + * @throws LGThinqApiException If some error occurr */ @Override @NonNull - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException { + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { try { File regFile = getCapFileForDevice(deviceId); ACCapability acCap = new ACCapability(); @@ -288,12 +286,12 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for } Map cap = (Map) mapper.get("Value"); if (cap == null) { - throw new LGApiException("Error extracting capabilities supported by the device"); + throw new LGThinqApiException("Error extracting capabilities supported by the device"); } Map opModes = (Map) cap.get("OpMode"); if (opModes == null) { - throw new LGApiException("Error extracting opModes supported by the device"); + throw new LGThinqApiException("Error extracting opModes supported by the device"); } else { Map modes = new HashMap(); ((Map) opModes.get("option")).forEach((k, v) -> { @@ -303,7 +301,7 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for } Map fanSpeed = (Map) cap.get("WindStrength"); if (fanSpeed == null) { - throw new LGApiException("Error extracting fanSpeed supported by the device"); + throw new LGThinqApiException("Error extracting fanSpeed supported by the device"); } else { Map fanModes = new HashMap(); ((Map) fanSpeed.get("option")).forEach((k, v) -> { @@ -324,7 +322,7 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for return acCap; } catch (IOException e) { - throw new LGApiException("Error reading IO interface", e); + throw new LGThinqApiException("Error reading IO interface", e); } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index 9e4a2dae575f4..69e093b486303 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -31,9 +31,9 @@ import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGApiException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgapi.model.*; import org.slf4j.Logger; @@ -83,11 +83,11 @@ private Map getCommonV2Headers(String language, String country, * * @param deviceId device ID for de desired V2 LG Thinq. * @return return map containing metamodel of settings and snapshot - * @throws LGApiException if some communication error occur. + * @throws LGThinqApiException if some communication error occur. */ @Override @Nullable - public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGApiException { + public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { Map deviceSettings = getDeviceSettings(bridgeName, deviceId); if (deviceSettings.get("snapshot") != null) { Map snapMap = (Map) deviceSettings.get("snapshot"); @@ -113,51 +113,51 @@ public RestResult sendControlCommands(String bridgeName, String deviceId, String @Override public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGApiException { + throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", "airState.operation", newPowerState.commandValue()); handleV2GenericErrorResult(resp); } catch (Exception e) { - throw new LGApiException("Error adjusting device power", e); + throw new LGThinqApiException("Error adjusting device power", e); } } @Override - public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGApiException { + public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.opMode", newOpMode); handleV2GenericErrorResult(resp); - } catch (LGApiException e) { + } catch (LGThinqApiException e) { throw e; } catch (Exception e) { - throw new LGApiException("Error adjusting operation mode", e); + throw new LGThinqApiException("Error adjusting operation mode", e); } } @Override - public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGApiException { + public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.windStrength", newFanSpeed); handleV2GenericErrorResult(resp); - } catch (LGApiException e) { + } catch (LGThinqApiException e) { throw e; } catch (Exception e) { - throw new LGApiException("Error adjusting operation mode", e); + throw new LGThinqApiException("Error adjusting operation mode", e); } } @Override public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGApiException { + throws LGThinqApiException { try { RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.tempState.target", newTargetTemp.commandValue()); handleV2GenericErrorResult(resp); - } catch (LGApiException e) { + } catch (LGThinqApiException e) { throw e; } catch (Exception e) { - throw new LGApiException("Error adjusting operation mode", e); + throw new LGThinqApiException("Error adjusting operation mode", e); } } @@ -166,18 +166,18 @@ public void changeTargetTemperature(String bridgeName, String deviceId, ACTarget * * @param deviceId Device ID * @return Work1 to be uses to grab data during monitoring. - * @throws LGApiException If some communication error occur. + * @throws LGThinqApiException If some communication error occur. */ @Override public String startMonitor(String bridgeName, String deviceId) - throws LGApiException, LGDeviceV1OfflineException, IOException { + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { throw new UnsupportedOperationException("Not supported in V2 API."); } @Override @NonNull @SuppressWarnings("ignoring Map type check") - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGApiException { + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { try { File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); ACCapability acCap = new ACCapability(); @@ -191,12 +191,12 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for }); Map cap = (Map) mapper.get("Value"); if (cap == null) { - throw new LGApiException("Error extracting capabilities supported by the device"); + throw new LGThinqApiException("Error extracting capabilities supported by the device"); } Map opModes = (Map) cap.get("airState.opMode"); if (opModes == null) { - throw new LGApiException("Error extracting opModes supported by the device"); + throw new LGThinqApiException("Error extracting opModes supported by the device"); } else { Map modes = new HashMap(); ((Map) opModes.get("value_mapping")).forEach((k, v) -> { @@ -206,7 +206,7 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for } Map fanSpeed = (Map) cap.get("airState.windStrength"); if (fanSpeed == null) { - throw new LGApiException("Error extracting fanSpeed supported by the device"); + throw new LGThinqApiException("Error extracting fanSpeed supported by the device"); } else { Map fanModes = new HashMap(); ((Map) fanSpeed.get("value_mapping")).forEach((k, v) -> { @@ -226,25 +226,25 @@ public ACCapability getDeviceCapability(String deviceId, String uri, boolean for acCap.getSupportedFanSpeed().remove("@NON"); return acCap; } catch (IOException e) { - throw new LGApiException("Error reading IO interface", e); + throw new LGThinqApiException("Error reading IO interface", e); } } - private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGApiException { + private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { Map metaResult; if (resp == null) { return; } if (resp.getStatusCode() != 200) { logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGApiException( + throw new LGThinqApiException( String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { }); if (!"0000".equals(metaResult.get("resultCode"))) { - throw new LGApiException( + throw new LGThinqApiException( String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", metaResult.get("resultCode"))); } @@ -255,20 +255,20 @@ private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGApiE } } - private Map handleDeviceSettingsResult(RestResult resp) throws LGApiException { + private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { return genericHandleDeviceSettingsResult(resp, logger, objectMapper); } @Override public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGApiException, RefreshTokenException, IOException, LGDeviceV1OfflineException { + throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { throw new UnsupportedOperationException("Not supported in V2 API."); } @Override @Nullable public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) - throws LGApiException, LGDeviceV1MonitorExpiredException, IOException { + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { throw new UnsupportedOperationException("Not supported in V2 API."); } } From b5697b9a57c824f3445d57a1bced1a0b91b7dbc0 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 12:59:01 -0300 Subject: [PATCH 061/130] [lgthinq] Fixing code formatting. Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 12 +++++----- .../internal/LGThinqBindingConstants.java | 10 ++++---- .../lgapi/LGThinqApiClientService.java | 3 ++- .../lgapi/LGThinqApiClientServiceImpl.java | 8 +++---- .../lgapi/LGThinqApiV1ClientServiceImpl.java | 23 +++++++++++-------- .../lgapi/LGThinqApiV2ClientServiceImpl.java | 3 ++- 6 files changed, 32 insertions(+), 27 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 1ea6d4f58242d..04822cf0acb61 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -12,6 +12,12 @@ */ package org.openhab.binding.lgthinq.internal; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + +import java.util.*; +import java.util.concurrent.*; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -36,12 +42,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.concurrent.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; - /** * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 56cedc0759465..6b9bd4b85fa89 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.lgthinq.internal; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; -import org.openhab.core.OpenHAB; -import org.openhab.core.thing.ThingTypeUID; - import java.io.File; import java.util.Collections; import java.util.Map; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; +import org.openhab.core.OpenHAB; +import org.openhab.core.thing.ThingTypeUID; + /** * The {@link LGThinqBindingConstants} class defines common constants, which are * used across the whole binding. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index 8e5f80a8553bb..4bf11b52fa1c7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -53,7 +53,8 @@ public interface LGThinqApiClientService { void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; - void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGThinqApiException; + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGThinqApiException; String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index bb5d0c4f97856..ad584301199aa 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -121,8 +121,8 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo Map deviceSettings; if (resp.getStatusCode() != 200) { logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException(String.format("Error calling device settings from LG Server API. The reason is:%s", - resp.getJsonResponse())); + throw new LGThinqApiException(String.format( + "Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { @@ -147,8 +147,8 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG List devices; if (resp.getStatusCode() != 200) { logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException(String.format("Error calling device list from LG Server API. The reason is:%s", - resp.getJsonResponse())); + throw new LGThinqApiException(String + .format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); } else { try { devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index ff8e1f3ce3479..8c3fdb5cabbcd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -12,9 +12,14 @@ */ package org.openhab.binding.lgthinq.lgapi; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -30,12 +35,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.UriBuilder; -import java.io.File; -import java.io.IOException; -import java.util.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link LGThinqApiV1ClientServiceImpl} @@ -268,7 +270,8 @@ private File getCapFileForDevice(String deviceId) { */ @Override @NonNull - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) + throws LGThinqApiException { try { File regFile = getCapFileForDevice(deviceId); ACCapability acCap = new ACCapability(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index 69e093b486303..f9d080773cc06 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -177,7 +177,8 @@ public String startMonitor(String bridgeName, String deviceId) @Override @NonNull @SuppressWarnings("ignoring Map type check") - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) + throws LGThinqApiException { try { File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); ACCapability acCap = new ACCapability(); From b622693ba74229d76084e8944574c9bf95aa1c71 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 13:32:10 -0300 Subject: [PATCH 062/130] [lgthinq] Optimizing imports Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 12 +++---- .../internal/LGThinqBindingConstants.java | 10 +++--- .../internal/LGThinqHandlerFactory.java | 10 +++--- .../lgthinq/internal/api/TokenResult.java | 4 +-- .../lgthinq/internal/api/UserInfo.java | 4 +-- .../lgapi/LGThinqApiClientService.java | 8 ++--- .../lgapi/LGThinqApiClientServiceImpl.java | 18 +++++------ .../lgapi/LGThinqApiV1ClientServiceImpl.java | 20 ++++++------ .../lgapi/LGThinqApiV2ClientServiceImpl.java | 31 ++++++++++--------- .../lgthinq/lgapi/model/ACCapability.java | 4 +-- .../lgthinq/lgapi/model/ACSnapShot.java | 5 ++- .../lgthinq/lgapi/model/ACSnapShotV1.java | 3 +- .../lgthinq/lgapi/model/ACSnapShotV2.java | 3 +- .../binding/lgthinq/lgapi/model/LGDevice.java | 3 +- .../binding/lgthinq/handler/JsonUtils.java | 6 ++-- 15 files changed, 67 insertions(+), 74 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 04822cf0acb61..1ea6d4f58242d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -12,12 +12,6 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; - -import java.util.*; -import java.util.concurrent.*; - import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -42,6 +36,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.*; +import java.util.concurrent.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + /** * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 6b9bd4b85fa89..56cedc0759465 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.lgthinq.internal; -import java.io.File; -import java.util.Collections; -import java.util.Map; -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; import org.openhab.core.OpenHAB; import org.openhab.core.thing.ThingTypeUID; +import java.io.File; +import java.util.Collections; +import java.util.Map; +import java.util.Set; + /** * The {@link LGThinqBindingConstants} class defines common constants, which are * used across the whole binding. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 3b9b6ad3249d4..4d3364841c968 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -12,11 +12,6 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; - -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; @@ -34,6 +29,11 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Set; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; + /** * The {@link LGThinqHandlerFactory} is responsible for creating things and thing * handlers. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java index 9f678b54f2b28..004850e567886 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java @@ -12,11 +12,11 @@ */ package org.openhab.binding.lgthinq.internal.api; +import org.eclipse.jdt.annotation.NonNullByDefault; + import java.io.Serializable; import java.util.Date; -import org.eclipse.jdt.annotation.NonNullByDefault; - /** * The {@link TokenResult} Hold information about token and related entities * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java index e783df386503f..96869677fef93 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java @@ -12,10 +12,10 @@ */ package org.openhab.binding.lgthinq.internal.api; -import java.io.Serializable; - import org.eclipse.jdt.annotation.NonNullByDefault; +import java.io.Serializable; + /** * The {@link UserInfo} User Info (registered in LG Account) * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index 4bf11b52fa1c7..c2e2b2bb37669 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -12,10 +12,6 @@ */ package org.openhab.binding.lgthinq.lgapi; -import java.io.IOException; -import java.util.List; -import java.util.Map; - import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -25,6 +21,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgapi.model.*; +import java.io.IOException; +import java.util.List; +import java.util.Map; + /** * The {@link LGThinqApiClientService} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index ad584301199aa..cb3751a53d002 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -12,25 +12,23 @@ */ package org.openhab.binding.lgthinq.lgapi; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgapi.model.LGDevice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.core.UriBuilder; +import java.util.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; /** * The {@link LGThinqApiV1ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index 8c3fdb5cabbcd..53e5e8f4afad4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -12,14 +12,9 @@ */ package org.openhab.binding.lgthinq.lgapi; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -35,9 +30,12 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.IOException; +import java.util.*; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; /** * The {@link LGThinqApiV1ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index f9d080773cc06..baa4b2d5b9638 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -12,18 +12,9 @@ */ package org.openhab.binding.lgthinq.lgapi; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -39,9 +30,19 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import javax.ws.rs.core.UriBuilder; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; /** * The {@link LGThinqApiV2ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java index 8f047b9148c4c..ca18ab6a15de3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java @@ -12,12 +12,12 @@ */ package org.openhab.binding.lgthinq.lgapi.model; +import org.eclipse.jdt.annotation.NonNullByDefault; + import java.util.Collections; import java.util.List; import java.util.Map; -import org.eclipse.jdt.annotation.NonNullByDefault; - /** * The {@link ACCapability} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java index df8926d89b97a..3719415e454f8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * The {@link ACSnapShot} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java index 34002d3a58d42..4a9a5d5312a59 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi.model; +import com.fasterxml.jackson.annotation.JsonProperty; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * The {@link ACSnapShotV1} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java index a373367eccbf5..231e63dcad724 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi.model; +import com.fasterxml.jackson.annotation.JsonProperty; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * The {@link ACSnapShotV2} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java index 3413aaeaf0700..fcc42a5a770e7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import org.eclipse.jdt.annotation.NonNullByDefault; - import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; +import org.eclipse.jdt.annotation.NonNullByDefault; /** * The {@link LGDevice} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index 3e42a5779cbac..d0f58ce7213df 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.lgthinq.handler; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -19,9 +22,6 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - /** * The {@link JsonUtils} * From 5acf026f9141935fef75130462f4e9b062b2a6dc Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 13:36:52 -0300 Subject: [PATCH 063/130] [lgthinq] reformating coding by spotless Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 12 +++---- .../internal/LGThinqBindingConstants.java | 10 +++--- .../internal/LGThinqHandlerFactory.java | 10 +++--- .../lgthinq/internal/api/TokenResult.java | 4 +-- .../lgthinq/internal/api/UserInfo.java | 4 +-- .../handler/LGThinqBridgeHandler.java | 22 ++++++------ .../lgapi/LGThinqApiClientService.java | 8 ++--- .../lgapi/LGThinqApiClientServiceImpl.java | 16 +++++---- .../lgapi/LGThinqApiV1ClientServiceImpl.java | 20 ++++++----- .../lgapi/LGThinqApiV2ClientServiceImpl.java | 34 ++++++++++--------- .../lgthinq/lgapi/model/ACCapability.java | 4 +-- .../lgthinq/lgapi/model/ACSnapShot.java | 5 +-- .../lgthinq/lgapi/model/ACSnapShotV1.java | 3 +- .../lgthinq/lgapi/model/ACSnapShotV2.java | 3 +- .../binding/lgthinq/lgapi/model/LGDevice.java | 3 +- .../binding/lgthinq/handler/JsonUtils.java | 6 ++-- 16 files changed, 87 insertions(+), 77 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 1ea6d4f58242d..04822cf0acb61 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -12,6 +12,12 @@ */ package org.openhab.binding.lgthinq.internal; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import static org.openhab.core.library.types.OnOffType.ON; + +import java.util.*; +import java.util.concurrent.*; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -36,12 +42,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.*; -import java.util.concurrent.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; - /** * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are * sent to one of the channels. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 56cedc0759465..6b9bd4b85fa89 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,16 +12,16 @@ */ package org.openhab.binding.lgthinq.internal; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; -import org.openhab.core.OpenHAB; -import org.openhab.core.thing.ThingTypeUID; - import java.io.File; import java.util.Collections; import java.util.Map; import java.util.Set; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; +import org.openhab.core.OpenHAB; +import org.openhab.core.thing.ThingTypeUID; + /** * The {@link LGThinqBindingConstants} class defines common constants, which are * used across the whole binding. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 4d3364841c968..3b9b6ad3249d4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -12,6 +12,11 @@ */ package org.openhab.binding.lgthinq.internal; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; + +import java.util.Set; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; @@ -29,11 +34,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Set; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; - /** * The {@link LGThinqHandlerFactory} is responsible for creating things and thing * handlers. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java index 004850e567886..9f678b54f2b28 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java @@ -12,11 +12,11 @@ */ package org.openhab.binding.lgthinq.internal.api; -import org.eclipse.jdt.annotation.NonNullByDefault; - import java.io.Serializable; import java.util.Date; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link TokenResult} Hold information about token and related entities * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java index 96869677fef93..e783df386503f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java @@ -12,10 +12,10 @@ */ package org.openhab.binding.lgthinq.internal.api; -import org.eclipse.jdt.annotation.NonNullByDefault; - import java.io.Serializable; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link UserInfo} User Info (registered in LG Account) * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 24c7c52adce07..a76ff76453933 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -12,6 +12,17 @@ */ package org.openhab.binding.lgthinq.internal.handler; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; + +import java.io.File; +import java.io.IOException; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; @@ -31,17 +42,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; - /** * The {@link LGThinqBridgeHandler} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java index c2e2b2bb37669..4bf11b52fa1c7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java @@ -12,6 +12,10 @@ */ package org.openhab.binding.lgthinq.lgapi; +import java.io.IOException; +import java.util.List; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -21,10 +25,6 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgapi.model.*; -import java.io.IOException; -import java.util.List; -import java.util.Map; - /** * The {@link LGThinqApiClientService} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java index cb3751a53d002..699fba682e1f6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java @@ -12,9 +12,12 @@ */ package org.openhab.binding.lgthinq.lgapi; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; @@ -25,10 +28,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.UriBuilder; -import java.util.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link LGThinqApiV1ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java index 53e5e8f4afad4..8c3fdb5cabbcd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java @@ -12,9 +12,14 @@ */ package org.openhab.binding.lgthinq.lgapi; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.io.File; +import java.io.IOException; +import java.util.*; + +import javax.ws.rs.core.UriBuilder; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -30,12 +35,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.UriBuilder; -import java.io.File; -import java.io.IOException; -import java.util.*; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link LGThinqApiV1ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java index baa4b2d5b9638..b6d878e0c3452 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java @@ -12,9 +12,21 @@ */ package org.openhab.binding.lgthinq.lgapi; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; + +import javax.ws.rs.core.UriBuilder; + import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -30,19 +42,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.ws.rs.core.UriBuilder; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link LGThinqApiV2ClientServiceImpl} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java index ca18ab6a15de3..8f047b9148c4c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java @@ -12,12 +12,12 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import org.eclipse.jdt.annotation.NonNullByDefault; - import java.util.Collections; import java.util.List; import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link ACCapability} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java index 3719415e454f8..df8926d89b97a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java @@ -12,11 +12,12 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; + /** * The {@link ACSnapShot} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java index 4a9a5d5312a59..34002d3a58d42 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java @@ -12,10 +12,11 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import com.fasterxml.jackson.annotation.JsonProperty; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * The {@link ACSnapShotV1} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java index 231e63dcad724..a373367eccbf5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java @@ -12,10 +12,11 @@ */ package org.openhab.binding.lgthinq.lgapi.model; -import com.fasterxml.jackson.annotation.JsonProperty; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * The {@link ACSnapShotV2} * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java index fcc42a5a770e7..3413aaeaf0700 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java @@ -12,10 +12,11 @@ */ package org.openhab.binding.lgthinq.lgapi.model; +import org.eclipse.jdt.annotation.NonNullByDefault; + import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import org.eclipse.jdt.annotation.NonNullByDefault; /** * The {@link LGDevice} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index d0f58ce7213df..3e42a5779cbac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -12,9 +12,6 @@ */ package org.openhab.binding.lgthinq.handler; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - import java.io.File; import java.io.IOException; import java.io.InputStream; @@ -22,6 +19,9 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + /** * The {@link JsonUtils} * From d245392c6aeda16923440dcd95d27ead895b6d2b Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sat, 29 Jan 2022 14:21:19 -0300 Subject: [PATCH 064/130] [lgthinq] disabled IDE's auto optimize imports to stop screwing up work from spotless. Signed-off-by: nemerdaud --- .../openhab/binding/lgthinq/internal/api/LGThinqGateway.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java index c7fd64caf8ab6..5f121a3b15ebc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java @@ -12,12 +12,12 @@ */ package org.openhab.binding.lgthinq.internal.api; -import org.eclipse.jdt.annotation.NonNullByDefault; - import java.io.Serializable; import java.util.Map; import java.util.Objects; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link LGThinqGateway} hold informations about the LG Gateway * From 962d5a3938b1f0620b83d26b73cd0e1722095b2b Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sun, 30 Jan 2022 11:57:11 -0300 Subject: [PATCH 065/130] [lgthinq] Organizing imports by spotless, moving constants to LGThinqBindingConstants. Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../lgthinq/internal/LGThinqAirConditionerHandler.java | 4 ++-- .../binding/lgthinq/internal/LGThinqBindingConstants.java | 4 ++++ .../binding/lgthinq/internal/LGThinqHandlerFactory.java | 6 +----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 04822cf0acb61..a9382b661c88c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -13,7 +13,6 @@ package org.openhab.binding.lgthinq.internal; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import static org.openhab.core.library.types.OnOffType.ON; import java.util.*; import java.util.concurrent.*; @@ -460,7 +459,8 @@ public void run() { case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON + : DevicePowerState.DV_POWER_OFF); } else { logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 6b9bd4b85fa89..177b3f90c9f1a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.lgthinq.internal; +import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; + import java.io.File; import java.util.Collections; import java.util.Map; @@ -34,6 +36,8 @@ public class LGThinqBindingConstants { public static final String BINDING_ID = "lgthinq"; public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner + public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, + THING_TYPE_BRIDGE); public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); public static final String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 3b9b6ad3249d4..06a98cd4fd38b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -15,8 +15,6 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; -import java.util.Set; - import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; @@ -44,14 +42,12 @@ @Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.lgthinq") public class LGThinqHandlerFactory extends BaseThingHandlerFactory { - private static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, - THING_TYPE_BRIDGE); private final Logger logger = LoggerFactory.getLogger(LGThinqHandlerFactory.class); private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; @Override public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); + return LGThinqBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); } @Override From cf6f7d21198a963929d024e2c5ce4f11cb28fef8 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sun, 30 Jan 2022 15:02:42 -0300 Subject: [PATCH 066/130] [lgthinq] Moving constants to LGThinqBindingConstants. Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../binding/lgthinq/internal/LGThinqBindingConstants.java | 3 +-- .../binding/lgthinq/internal/LGThinqHandlerFactory.java | 2 +- .../binding/lgthinq/internal/handler/LGThinqBridgeHandler.java | 2 -- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 177b3f90c9f1a..54d717236767c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -12,8 +12,6 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; - import java.io.File; import java.util.Collections; import java.util.Map; @@ -34,6 +32,7 @@ public class LGThinqBindingConstants { public static final String BINDING_ID = "lgthinq"; + public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 06a98cd4fd38b..c89554b5e2c90 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -13,7 +13,7 @@ package org.openhab.binding.lgthinq.internal; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler.THING_TYPE_BRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_BRIDGE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index a76ff76453933..81d3b36fd9277 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -24,7 +24,6 @@ import java.util.concurrent.locks.ReentrantLock; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; import org.openhab.binding.lgthinq.internal.api.TokenManager; @@ -48,7 +47,6 @@ * @author Nemer Daud - Initial contribution */ public class LGThinqBridgeHandler extends ConfigStatusBridgeHandler implements LGThinqBridge { - public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(LGThinqBindingConstants.BINDING_ID, "bridge"); private Map lGDeviceRegister = new ConcurrentHashMap<>(); private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); From 6df1d73a4f14ee9445aefb1c3e460ef0cab30a65 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Thu, 3 Feb 2022 12:19:08 -0300 Subject: [PATCH 067/130] [lgthinq] Repackaging, Enhanced data binding and some logs for troubleshooting Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 8 +- .../internal/LGThinqBindingConstants.java | 13 ++- .../lgthinq/internal/LGThinqDeviceThing.java | 4 +- .../lgthinq/internal/api/LGThinqGateway.java | 20 ++-- .../handler/LGThinqBridgeHandler.java | 16 +-- .../lgthinq/lgapi/model/DevicePowerState.java | 71 ------------ .../lgthinq/lgapi/model/DeviceTypes.java | 42 ------- .../binding/lgthinq/lgapi/model/LGDevice.java | 107 ------------------ .../LGThinqApiClientService.java | 4 +- .../LGThinqApiClientServiceImpl.java | 4 +- .../LGThinqApiV1ClientServiceImpl.java | 4 +- .../LGThinqApiV2ClientServiceImpl.java | 4 +- .../model/ACCapability.java | 2 +- .../model/ACFanSpeed.java | 2 +- .../{lgapi => lgservices}/model/ACOpMode.java | 2 +- .../model/ACSnapShot.java | 2 +- .../model/ACSnapShotV1.java | 2 +- .../model/ACSnapShotV2.java | 2 +- .../model/ACTargetTmp.java | 2 +- .../lgthinq/lgservices/model/LGDevice.java | 2 +- 20 files changed, 51 insertions(+), 262 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/LGThinqApiClientService.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/LGThinqApiClientServiceImpl.java (98%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/LGThinqApiV1ClientServiceImpl.java (99%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/LGThinqApiV2ClientServiceImpl.java (99%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACCapability.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACFanSpeed.java (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACOpMode.java (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACSnapShot.java (98%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACSnapShotV1.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACSnapShotV2.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/{lgapi => lgservices}/model/ACTargetTmp.java (97%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index a9382b661c88c..a9d6282b084cc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -25,10 +25,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 54d717236767c..f9f6d136bbcbd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -18,7 +18,7 @@ import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgapi.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.core.OpenHAB; import org.openhab.core.thing.ThingTypeUID; @@ -59,8 +59,9 @@ public class LGThinqBindingConstants { public static final String V1_MON_DATA_PATH = "rti/rtiResult"; public static final String V1_CONTROL_OP = "rti/rtiControl"; public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; - public static final String GATEWAY_SERVICE_PATH = "/v1/service/application/gateway-uri"; - public static String GATEWAY_URL = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH; + public static final String GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; + public static final String GATEWAY_SERVICE_PATH_V1 = "/api/common/gatewayUriList"; + public static String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; public static final String PRE_LOGIN_PATH = "/preLogin"; public static final String SECURITY_KEY = "nuts_securitykey"; public static final String APP_KEY = "wideq"; @@ -76,8 +77,10 @@ public class LGThinqBindingConstants { public static final String DEFAULT_LANGUAGE = "en-US"; public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; public static String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com/emp/oauth2/token/empsession"; - // v2 - public static final String API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; + public static final String API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; + + public static final String API_KEY_V1 = "wideq"; + public static final String API_SECURITY_KEY_V1 = "nuts_securitykey"; // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, // and the build id of the thinq app it can also just be a random diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index ddfc7a165d4f7..8d879d1457ae5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -14,8 +14,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgapi.model.ACCapability; -import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.ACCapability; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; /** * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java index 5f121a3b15ebc..22389f8a5cf71 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java @@ -13,10 +13,9 @@ package org.openhab.binding.lgthinq.internal.api; import java.io.Serializable; -import java.util.Map; -import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; /** * The {@link LGThinqGateway} hold informations about the LG Gateway @@ -34,16 +33,17 @@ public class LGThinqGateway implements Serializable { private String country = ""; private String username = ""; private String password = ""; + private int accountVersion; public LGThinqGateway() { } - public LGThinqGateway(Map params, String language, String country) { - this.apiRootV2 = Objects.requireNonNullElse(params.get("thinq2Uri"), ""); - this.apiRootV1 = Objects.requireNonNullElse(params.get("thinq1Uri"), ""); - this.loginBaseUri = Objects.requireNonNullElse(params.get("empSpxUri"), ""); - this.authBase = Objects.requireNonNullElse(params.get("empUri"), ""); - this.empBaseUri = Objects.requireNonNullElse(params.get("empTermsUri"), ""); + public LGThinqGateway(GatewayResult gwResult, String language, String country) { + this.apiRootV2 = gwResult.getThinq2Uri(); + this.apiRootV1 = gwResult.getThinq1Uri(); + this.loginBaseUri = gwResult.getEmpSpxUri(); + this.authBase = gwResult.getEmpUri(); + this.empBaseUri = gwResult.getEmpTermsUri(); this.language = language; this.country = country; } @@ -52,6 +52,10 @@ public String getEmpBaseUri() { return empBaseUri; } + public int getAccountVersion() { + return accountVersion; + } + public String getApiRootV2() { return apiRootV2; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 81d3b36fd9277..bab834f8212fd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -30,11 +30,14 @@ import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgapi.LGThinqApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; -import org.openhab.core.thing.*; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.ThingStatus; +import org.openhab.core.thing.ThingStatusDetail; import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; @@ -114,9 +117,8 @@ public void run() { try { tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword()); - if (tokenManager.getValidRegisteredToken(bridgeName) != null) { - logger.debug("Successful getting token from LG API"); - } + tokenManager.getValidRegisteredToken(bridgeName); + logger.debug("Successful getting token from LG API"); } catch (IOException e) { logger.debug( "I/O error accessing json token configuration file. Updating Bridge Status to OFFLINE.", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java deleted file mode 100644 index e597165334d71..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DevicePowerState.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link DevicePowerState} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum DevicePowerState { - DV_POWER_ON(1), - DV_POWER_OFF(0), - DV_POWER_UNK(-1); - - private final int powerState; - - public double getValue() { - return powerState; - } - - DevicePowerState(int i) { - powerState = i; - } - - public static DevicePowerState statusOf(double value) { - switch ((int) value) { - case 0: - return DV_POWER_OFF; - case 1: - case 256: - case 257: - return DV_POWER_ON; - - default: - return DV_POWER_UNK; - } - } - - public static double valueOf(DevicePowerState dps) { - return dps.powerState; - } - - /** - * Value of command (not state, but command to change the state of device) - * - * @return value of the command to reach the state - */ - public int commandValue() { - switch (this) { - case DV_POWER_ON: - return 257;// "@AC_MAIN_OPERATION_ALL_ON_W" - case DV_POWER_OFF: - return 0; // "@AC_MAIN_OPERATION_OFF_W" - default: - throw new IllegalArgumentException("Enum not accepted for command:" + this); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java deleted file mode 100644 index a034870766775..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/DeviceTypes.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; - -/** - * The {@link DeviceTypes} - * - * @author Nemer Daud - Initial contribution - */ -public enum DeviceTypes { - AIR_CONDITIONER(401), - UNKNOWN(-1); - - private final int deviceTypeId; - - public int deviceTypeId() { - return deviceTypeId; - } - - public static DeviceTypes fromDeviceTypeId(int deviceTypeId) { - switch (deviceTypeId) { - case 401: - return AIR_CONDITIONER; - default: - return UNKNOWN; - } - } - - DeviceTypes(int i) { - this.deviceTypeId = i; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java deleted file mode 100644 index 3413aaeaf0700..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/LGDevice.java +++ /dev/null @@ -1,107 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgapi.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link LGDevice} - * - * @author Nemer Daud - Initial contribution - */ -@JsonIgnoreProperties(ignoreUnknown = true) -@NonNullByDefault -public class LGDevice { - private String modelName = ""; - @JsonProperty("deviceType") - private int deviceTypeId; - private String deviceCode = ""; - private String alias = ""; - private String deviceId = ""; - private String platformType = ""; - private String modelJsonUri = ""; - private boolean online; - - public String getModelName() { - return modelName; - } - - @JsonIgnore - public DeviceTypes getDeviceType() { - return DeviceTypes.fromDeviceTypeId(deviceTypeId); - } - - public void setModelName(String modelName) { - this.modelName = modelName; - } - - public int getDeviceTypeId() { - return deviceTypeId; - } - - public void setDeviceTypeId(int deviceTypeId) { - this.deviceTypeId = deviceTypeId; - } - - public String getDeviceCode() { - return deviceCode; - } - - public void setDeviceCode(String deviceCode) { - this.deviceCode = deviceCode; - } - - public String getModelJsonUri() { - return modelJsonUri; - } - - public void setModelJsonUri(String modelJsonUri) { - this.modelJsonUri = modelJsonUri; - } - - public String getAlias() { - return alias; - } - - public void setAlias(String alias) { - this.alias = alias; - } - - public String getDeviceId() { - return deviceId; - } - - public void setDeviceId(String deviceId) { - this.deviceId = deviceId; - } - - public String getPlatformType() { - return platformType; - } - - public void setPlatformType(String platformType) { - this.platformType = platformType; - } - - public boolean isOnline() { - return online; - } - - public void setOnline(boolean online) { - this.online = online; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java index 4bf11b52fa1c7..5ac07b5d8ac21 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi; +package org.openhab.binding.lgthinq.lgservices; import java.io.IOException; import java.util.List; @@ -23,7 +23,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgservices.model.*; /** * The {@link LGThinqApiClientService} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java similarity index 98% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index 699fba682e1f6..c3ef550b937a6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi; +package org.openhab.binding.lgthinq.lgservices; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; @@ -24,7 +24,7 @@ import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgapi.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java similarity index 99% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java index 8c3fdb5cabbcd..9984269e429f2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi; +package org.openhab.binding.lgthinq.lgservices; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; @@ -31,7 +31,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java similarity index 99% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java index b6d878e0c3452..e259308efccf3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi; +package org.openhab.binding.lgthinq.lgservices; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; @@ -38,7 +38,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgapi.model.*; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java index 8f047b9148c4c..aae3164658013 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import java.util.Collections; import java.util.List; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java index 1f072c631e5ea..3381241e9c39a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACFanSpeed.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java index baf3c98f0210e..b5d24a6691c02 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACOpMode.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java similarity index 98% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java index df8926d89b97a..754a80aa75b30 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java index 34002d3a58d42..2a4918838f78c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java index a373367eccbf5..78ab6cf2ea72b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACSnapShotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java index b43fbe5b1c4a3..72a24289c15ee 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgapi/model/ACTargetTmp.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgapi.model; +package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java index a0605abb5ff9f..18072548e4805 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java @@ -42,7 +42,7 @@ public String getModelName() { @JsonIgnore public DeviceTypes getDeviceType() { - return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); + return DeviceTypes.fromDeviceTypeId(deviceTypeId); } public void setModelName(String modelName) { From 0598b802bd84c7db880e09e6107ec18792a5b7a8 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Thu, 3 Feb 2022 14:20:49 -0300 Subject: [PATCH 068/130] [lgthinq][Feat] save capabilities of not supported Thinq devices. Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 2 +- .../handler/LGThinqBridgeHandler.java | 2 +- .../lgservices/LGThinqApiClientService.java | 6 ++- .../LGThinqApiClientServiceImpl.java | 21 +++++++++++ .../LGThinqApiV1ClientServiceImpl.java | 20 +++------- .../LGThinqApiV2ClientServiceImpl.java | 21 ++--------- .../main/resources/OH-INF/thing/washer.xml | 37 +++---------------- 7 files changed, 43 insertions(+), 66 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index a9d6282b084cc..d7b2881e73579 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -268,7 +268,7 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { @Override public ACCapability getAcCapabilities() throws LGThinqApiException { if (acCapability == null) { - acCapability = lgThinqApiClientService.getDeviceCapability(getDeviceId(), getDeviceUriJsonConfig(), false); + acCapability = lgThinqApiClientService.getACCapability(getDeviceId(), getDeviceUriJsonConfig(), false); } return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index bab834f8212fd..2fc12e6d24612 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -207,7 +207,7 @@ protected void doConnectedRun() throws LGThinqException { if (lGDeviceRegister.get(deviceId) == null) { logger.debug("Adding new LG Device to things registry with id:{}", deviceId); if (discoveryService != null) { - discoveryService.addLgDeviceDiscovery(bridgeName, device); + discoveryService.addLgDeviceDiscovery(device); } } lastDevicesDiscovered.put(deviceId, device); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java index 5ac07b5d8ac21..115794b5f6305 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices; +import java.io.File; import java.io.IOException; import java.util.List; import java.util.Map; @@ -59,7 +60,10 @@ void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp new String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; - ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; + ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; + + File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) + throws LGThinqApiException, IOException; void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index c3ef550b937a6..a36fd7ba0a0e7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -14,6 +14,12 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; import java.util.*; import javax.ws.rs.core.UriBuilder; @@ -88,6 +94,21 @@ public List listAccountDevices(String bridgeName) throws LGThinqApiExc } } + @Override + public File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); + try { + if (regFile.isFile() || forceRecreate) { + try (InputStream in = new URL(uri).openStream()) { + Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + } + } catch (IOException e) { + throw new LGThinqApiException("Error reading IO interface", e); + } + return regFile; + } + /** * Get device settings and snapshot for a specific device. * It works only for API V2 device versions! diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java index 9984269e429f2..030901c213cf1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java @@ -270,23 +270,13 @@ private File getCapFileForDevice(String deviceId) { */ @Override @NonNull - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) - throws LGThinqApiException { + public ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { try { - File regFile = getCapFileForDevice(deviceId); + File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); + Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { + }); ACCapability acCap = new ACCapability(); - Map mapper; - if (regFile.isFile() && !forceRecreate) { - // reg exists. Retrieve from it - mapper = objectMapper.readValue(regFile, new TypeReference>() { - }); - } else { - RestResult res = RestUtils.getCall(uri, null, null); - mapper = objectMapper.readValue(res.getJsonResponse(), new TypeReference>() { - }); - // try save file - objectMapper.writeValue(getCapFileForDevice(deviceId), mapper); - } + Map cap = (Map) mapper.get("Value"); if (cap == null) { throw new LGThinqApiException("Error extracting capabilities supported by the device"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java index e259308efccf3..af9406f539863 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java @@ -12,15 +12,10 @@ */ package org.openhab.binding.lgthinq.lgservices; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.BASE_CAP_CONFIG_DATA_FILE; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -180,24 +175,16 @@ public String startMonitor(String bridgeName, String deviceId) @Override @NonNull @SuppressWarnings("ignoring Map type check") - public ACCapability getDeviceCapability(String deviceId, String uri, boolean forceRecreate) - throws LGThinqApiException { + public ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { try { - File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); - ACCapability acCap = new ACCapability(); - Map mapper; - if (regFile.isFile() || forceRecreate) { - try (InputStream in = new URL(uri).openStream()) { - Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } - mapper = objectMapper.readValue(regFile, new TypeReference<>() { + File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); + Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { }); Map cap = (Map) mapper.get("Value"); if (cap == null) { throw new LGThinqApiException("Error extracting capabilities supported by the device"); } - + ACCapability acCap = new ACCapability(); Map opModes = (Map) cap.get("airState.opMode"); if (opModes == null) { throw new LGThinqApiException("Error extracting opModes supported by the device"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 8d79b03e4c5de..1ec2d87e1e528 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -10,42 +10,17 @@ - - LG ThinQ Washing Machine + + LG Thinq Washing Machine + + + + - - - - - - - - - - - - - - - - - - LG ThinQ Washing Tower - - - - - - - - - - - From 5f088c80a4f706df9556973186a4ba2edc09511c Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 00:31:16 -0300 Subject: [PATCH 069/130] [lgthinq][Feat] Introducing of Washin Machine support. Only monitoring for now. No Channel added Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 14 +-- .../LGThinqAirConditionerHandler.java | 94 ++++++------------- .../internal/LGThinqBindingConstants.java | 14 +-- .../lgthinq/internal/LGThinqDeviceThing.java | 82 +++++++++++++--- .../internal/LGThinqHandlerFactory.java | 7 +- .../handler/LGThinqBridgeHandler.java | 5 +- .../lgservices/LGThinqApiClientService.java | 7 +- .../LGThinqApiClientServiceImpl.java | 22 +++++ .../LGThinqApiV1ClientServiceImpl.java | 82 +++------------- .../LGThinqApiV2ClientServiceImpl.java | 79 +++++----------- .../model/{ => ac}/ACCapability.java | 5 +- .../lgservices/model/{ => ac}/ACFanSpeed.java | 4 +- .../lgservices/model/{ => ac}/ACOpMode.java | 4 +- .../{ACSnapShot.java => ac/ACSnapshot.java} | 19 ++-- .../ACSnapshotV1.java} | 6 +- .../ACSnapshotV2.java} | 6 +- .../model/{ => ac}/ACTargetTmp.java | 2 +- .../main/resources/OH-INF/thing/washer.xml | 4 - .../binding/lgthinq/handler/JsonUtils.java | 3 + .../resources/dashboard-list-response-1.json | 4 +- 20 files changed, 210 insertions(+), 253 deletions(-) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ => ac}/ACCapability.java (90%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ => ac}/ACFanSpeed.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ => ac}/ACOpMode.java (96%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ACSnapShot.java => ac/ACSnapshot.java} (81%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ACSnapShotV1.java => ac/ACSnapshotV1.java} (90%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ACSnapShotV2.java => ac/ACSnapshotV2.java} (91%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{ => ac}/ACTargetTmp.java (97%) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index f027ffb377bdf..96465f31c5f5e 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -17,13 +17,13 @@ This Bridge discovery Air Conditioner Things related to the user's account. To f The binding is represented by a bridge (LG GatewayBridge) and you must configure the following parameters: -| Bridge Parameter | Description | Constraints | -|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------| -| User Language | User language configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | en-US, en-GB, pt-BR and ge | -| User Country | User country configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | US, UK, BR and DE | -| LG User name | The LG user's account (normally an email) | | -| LG Password | The LG user's password | | -| Pooling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | +| Bridge Parameter | Description | Constraints | +|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| +| User Language | User language configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | en-US, en-GB, pt-BR, de-DE, da-DK | +| User Country | User country configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | US, UK, BR, DE and DK | +| LG User name | The LG user's account (normally an email) | | +| LG Password | The LG user's password | | +| Pooling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index d7b2881e73579..6dd0f08dc19cb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -24,16 +24,18 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.QuantityType; import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.BaseThingHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; @@ -48,7 +50,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqAirConditionerHandler extends BaseThingHandler implements LGThinqDeviceThing { +public class LGThinqAirConditionerHandler extends LGThinqDeviceThing { private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; private final ChannelUID opModeChannelUID; @@ -110,53 +112,7 @@ public void initialize() { } @Override - public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { - logger.debug("bridgeStatusChanged {}", bridgeStatusInfo); - initializeThing(bridgeStatusInfo.getStatus()); - } - - private void initializeThing(@Nullable ThingStatus bridgeStatus) { - logger.debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); - String deviceId = getThing().getUID().getId(); - if (!SUPPORTED_LG_PLATFORMS.contains(lgPlatfomType)) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "LG Platform [" + lgPlatfomType + "] not supported for this thing"); - return; - } - Bridge bridge = getBridge(); - if (!deviceId.isBlank()) { - try { - updateChannelDynStateDescription(); - } catch (LGThinqApiException e) { - logger.error( - "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); - } - if (bridge != null) { - LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); - // registry this thing to the bridge - if (handler == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } else { - handler.registryListenerThing(this); - if (bridgeStatus == ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - } - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/offline.conf-error-no-device-id"); - } - // finally, start command queue, regardless os the thing state, as we can still try to send commands without - // property ONLINE (the successful result from command request can put the thing in ONLINE status). - startCommandExecutorQueueJob(); - } - - private void startCommandExecutorQueueJob() { + protected void startCommandExecutorQueueJob() { if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); } @@ -181,7 +137,7 @@ protected void startThingStatePolling() { private void updateThingStateFromLG() { try { - ACSnapShot shot = getSnapshotDeviceAdapter(getDeviceId()); + ACSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); if (shot == null) { // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. return; @@ -190,15 +146,15 @@ private void updateThingStateFromLG() { if (getThing().getStatus() != ThingStatus.OFFLINE) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); updateState(CHANNEL_POWER_ID, - OnOffType.from(shot.getAcPowerStatus() == DevicePowerState.DV_POWER_OFF)); + OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_OFF)); } return; } if (shot.getOperationMode() != null) { updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); } - if (shot.getAcPowerStatus() != null) { - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getAcPowerStatus() == DevicePowerState.DV_POWER_ON)); + if (shot.getPowerStatus() != null) { + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as // soon as LG WebOs do) } @@ -212,7 +168,7 @@ private void updateThingStateFromLG() { updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); } updateStatus(ThingStatus.ONLINE); - } catch (LGThinqException e) { + } catch (Exception e) { logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", getDeviceAlias(), getDeviceId(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); @@ -250,34 +206,40 @@ private String emptyIfNull(@Nullable String value) { @Override public void updateChannelDynStateDescription() throws LGThinqApiException { - ACCapability acCap = getAcCapabilities(); + ACCapability acCap = getCapabilities(); if (isLinked(opModeChannelUID)) { List options = new ArrayList<>(); acCap.getSupportedOpMode().forEach((v) -> options - .add(new StateOption(emptyIfNull(acCap.getOpMod().get(v)), emptyIfNull(CAP_OP_MODE.get(v))))); + .add(new StateOption(emptyIfNull(acCap.getOpMod().get(v)), emptyIfNull(CAP_AC_OP_MODE.get(v))))); stateDescriptionProvider.setStateOptions(opModeChannelUID, options); } if (isLinked(opModeFanSpeedUID)) { List options = new ArrayList<>(); - acCap.getSupportedFanSpeed().forEach((v) -> options - .add(new StateOption(emptyIfNull(acCap.getFanSpeed().get(v)), emptyIfNull(CAP_FAN_SPEED.get(v))))); + acCap.getSupportedFanSpeed().forEach((v) -> options.add( + new StateOption(emptyIfNull(acCap.getFanSpeed().get(v)), emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); stateDescriptionProvider.setStateOptions(opModeFanSpeedUID, options); } } @Override - public ACCapability getAcCapabilities() throws LGThinqApiException { + public ACCapability getCapabilities() throws LGThinqApiException { if (acCapability == null) { - acCapability = lgThinqApiClientService.getACCapability(getDeviceId(), getDeviceUriJsonConfig(), false); + acCapability = (ACCapability) lgThinqApiClientService.getCapability(getDeviceId(), getDeviceUriJsonConfig(), + false); } return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } + @Override + protected Logger getLogger() { + return logger; + } + @Nullable - private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { + private ACSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return lgThinqApiClientService.getAcDeviceData(getBridgeId(), getDeviceId()); + return (ACSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { @@ -286,7 +248,7 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx } } catch (LGThinqDeviceV1OfflineException e) { forceStopDeviceV1Monitor(deviceId); - ACSnapShot shot = new ACSnapShotV1(); + ACSnapshot shot = new ACSnapshotV1(); shot.setOnline(false); return shot; } catch (Exception e) { @@ -294,11 +256,11 @@ private ACSnapShot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); } int retries = 10; - ACSnapShot shot; + ACSnapshot shot; while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + shot = (ACSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); if (shot != null) { return shot; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index f9f6d136bbcbd..f88735c8c8d8b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -13,7 +13,6 @@ package org.openhab.binding.lgthinq.internal; import java.io.File; -import java.util.Collections; import java.util.Map; import java.util.Set; @@ -35,11 +34,14 @@ public class LGThinqBindingConstants { public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner + public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, + "" + DeviceTypes.WASHING_MACHINE.deviceTypeId()); // deviceType from AirConditioner public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, - THING_TYPE_BRIDGE); - public static final Set SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_AIR_CONDITIONER); + THING_TYPE_WASHING_MACHINE, THING_TYPE_BRIDGE); + public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_AIR_CONDITIONER, + THING_TYPE_WASHING_MACHINE); - public static final String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; + public static String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; public static String BASE_CAP_CONFIG_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-cap.json"; public static final String V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; @@ -112,14 +114,14 @@ public class LGThinqBindingConstants { public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; - public static final Map CAP_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", + public static final Map CAP_AC_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", "@AC_MAIN_OPERATION_MODE_HEAT_W", "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", "@AC_MAIN_OPERATION_MODE_AI_W", "AI", "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); - public static final Map CAP_FAN_SPEED = Map.ofEntries( + public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index 8d879d1457ae5..a049d5cce642e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -13,9 +13,14 @@ package org.openhab.binding.lgthinq.internal; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.ACCapability; +import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; +import org.openhab.binding.lgthinq.lgservices.model.Capability; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.BaseThingHandler; +import org.slf4j.Logger; /** * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things @@ -23,25 +28,78 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGThinqDeviceThing { +public abstract class LGThinqDeviceThing extends BaseThingHandler { - void onDeviceAdded(@NonNullByDefault LGDevice device); + public LGThinqDeviceThing(Thing thing) { + super(thing); + } - String getDeviceId(); + public abstract void onDeviceAdded(@NonNullByDefault LGDevice device); - String getDeviceAlias(); + public abstract String getDeviceId(); - String getDeviceModelName(); + public abstract String getDeviceAlias(); - String getDeviceUriJsonConfig(); + public abstract String getDeviceModelName(); - boolean onDeviceStateChanged(); + public abstract String getDeviceUriJsonConfig(); - void onDeviceRemoved(); + public abstract boolean onDeviceStateChanged(); - void onDeviceGone(); + public abstract void onDeviceRemoved(); - void updateChannelDynStateDescription() throws LGThinqApiException; + public abstract void onDeviceGone(); - ACCapability getAcCapabilities() throws LGThinqApiException; + public abstract void updateChannelDynStateDescription() throws LGThinqApiException; + + public abstract T getCapabilities() throws LGThinqApiException; + + protected abstract Logger getLogger(); + + protected abstract void startCommandExecutorQueueJob(); + + protected void initializeThing(@Nullable ThingStatus bridgeStatus) { + getLogger().debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); + String deviceId = getThing().getUID().getId(); + + Bridge bridge = getBridge(); + if (!deviceId.isBlank()) { + try { + updateChannelDynStateDescription(); + } catch (LGThinqApiException e) { + getLogger().error( + "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); + } + if (bridge != null) { + LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); + // registry this thing to the bridge + if (handler == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } else { + handler.registryListenerThing(this); + if (bridgeStatus == ThingStatus.ONLINE) { + updateStatus(ThingStatus.ONLINE); + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + } + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + } + } else { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, + "@text/offline.conf-error-no-device-id"); + } + // finally, start command queue, regardless of the thing state, as we can still try to send commands without + // property ONLINE (the successful result from command request can put the thing in ONLINE status). + startCommandExecutorQueueJob(); + } + + @Override + public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { + getLogger().debug("bridgeStatusChanged {}", bridgeStatusInfo); + super.bridgeStatusChanged(bridgeStatusInfo); + // restart scheduler + initializeThing(bridgeStatusInfo.getStatus()); + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index c89554b5e2c90..77add4311bdb6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -12,8 +12,7 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THING_TYPE_BRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -58,6 +57,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return new LGThinqAirConditionerHandler(thing, stateDescriptionProvider); } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { return new LGThinqBridgeHandler((Bridge) thing); + } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { + return new LGThinqWasherHandler((Bridge) thing, stateDescriptionProvider); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; @@ -70,6 +71,8 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return super.createThing(thingTypeUID, configuration, thingUID, null); } else if (LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); + } else if (LGThinqBindingConstants.THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { + return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); } return null; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 2fc12e6d24612..8505ef6399939 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -34,10 +34,7 @@ import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.ThingStatus; -import org.openhab.core.thing.ThingStatusDetail; +import org.openhab.core.thing.*; import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java index 115794b5f6305..7cd41f254fd3a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java @@ -25,6 +25,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; /** * The {@link LGThinqApiClientService} @@ -46,7 +47,7 @@ public interface LGThinqApiClientService { * @throws LGThinqApiException if some error interacting with LG API Server occur. */ @Nullable - ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; + Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; @@ -60,7 +61,7 @@ void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp new String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; - ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; + Capability getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException, IOException; @@ -68,6 +69,6 @@ File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; @Nullable - ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) + Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index a36fd7ba0a0e7..be5ce38cecfff 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -30,7 +30,10 @@ import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.Capability; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -191,4 +194,23 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG return devices; } + + /** + * Get capability em registry/cache on file for next consult + * + * @param deviceId ID of the device + * @param uri URI of the config capability + * @return return simplified capability + * @throws LGThinqApiException If some error occurr + */ + public Capability getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { + try { + File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); + Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { + }); + return CapabilityFactory.getInstance().create(mapper, ACCapability.class); + } catch (IOException e) { + throw new LGThinqApiException("Error reading IO interface", e); + } + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java index 030901c213cf1..e98662a26e729 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java @@ -14,7 +14,6 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import java.io.File; import java.io.IOException; import java.util.*; @@ -31,7 +30,12 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,7 +82,7 @@ protected TokenManager getTokenManager() { */ @Override @Nullable - public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { throw new UnsupportedOperationException("Method not supported in V1 API device."); } @@ -214,9 +218,8 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) } @Override - @Nullable - public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull String workId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -231,7 +234,7 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev try { envelop = handleV1GenericErrorResult(resp); } catch (LGThinqDeviceV1OfflineException e) { - ACSnapShot shot = new ACSnapShotV2(); + ACSnapshot shot = new ACSnapshotV2(); shot.setOnline(false); return shot; } @@ -247,7 +250,7 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev String jsonMonDataB64 = (String) workList.get("returnData"); String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); - ACSnapShot shot = objectMapper.readValue(jsonMon, ACSnapShotV1.class); + ACSnapshot shot = objectMapper.readValue(jsonMon, ACSnapshotV1.class); shot.setOnline("E".equals(workList.get("deviceState"))); return shot; } else { @@ -255,67 +258,4 @@ public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String dev return null; } } - - private File getCapFileForDevice(String deviceId) { - return new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); - } - - /** - * Get capability em registry/cache on file for next consult - * - * @param deviceId ID of the device - * @param uri URI of the config capanility - * @return return simplified capability - * @throws LGThinqApiException If some error occurr - */ - @Override - @NonNull - public ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { - try { - File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); - Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { - }); - ACCapability acCap = new ACCapability(); - - Map cap = (Map) mapper.get("Value"); - if (cap == null) { - throw new LGThinqApiException("Error extracting capabilities supported by the device"); - } - - Map opModes = (Map) cap.get("OpMode"); - if (opModes == null) { - throw new LGThinqApiException("Error extracting opModes supported by the device"); - } else { - Map modes = new HashMap(); - ((Map) opModes.get("option")).forEach((k, v) -> { - modes.put(v, k); - }); - acCap.setOpMod(modes); - } - Map fanSpeed = (Map) cap.get("WindStrength"); - if (fanSpeed == null) { - throw new LGThinqApiException("Error extracting fanSpeed supported by the device"); - } else { - Map fanModes = new HashMap(); - ((Map) fanSpeed.get("option")).forEach((k, v) -> { - fanModes.put(v, k); - }); - acCap.setFanSpeed(fanModes); - - } - // Set supported modes for the device - - Map> supOpModes = (Map>) cap.get("SupportOpMode"); - acCap.setSupportedOpMode(new ArrayList<>(supOpModes.get("option").values())); - acCap.getSupportedOpMode().remove("@NON"); - Map> supFanSpeeds = (Map>) cap - .get("SupportWindStrength"); - acCap.setSupportedFanSpeed(new ArrayList<>(supFanSpeeds.get("option").values())); - acCap.getSupportedFanSpeed().remove("@NON"); - - return acCap; - } catch (IOException e) { - throw new LGThinqApiException("Error reading IO interface", e); - } - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java index af9406f539863..71f428bb3ca1d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java @@ -16,8 +16,6 @@ import java.io.File; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; import java.util.Map; import javax.ws.rs.core.UriBuilder; @@ -25,6 +23,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; @@ -33,7 +32,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotFactory; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -85,12 +87,25 @@ private Map getCommonV2Headers(String language, String country, */ @Override @Nullable - public ACSnapShot getAcDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { Map deviceSettings = getDeviceSettings(bridgeName, deviceId); if (deviceSettings.get("snapshot") != null) { Map snapMap = (Map) deviceSettings.get("snapshot"); + if (logger.isDebugEnabled()) { + try { + objectMapper.writeValue(new File(String.format( + LGThinqBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", + deviceId)), deviceSettings); + } catch (IOException e) { + logger.error("Error saving data trace", e); + } + } + if (snapMap == null) { + // No snapshot value provided + return null; + } - ACSnapShot shot = objectMapper.convertValue(snapMap, ACSnapShotV2.class); + Snapshot shot = SnapshotFactory.getInstance().create(deviceSettings); shot.setOnline((Boolean) snapMap.get("online")); return shot; } @@ -172,55 +187,6 @@ public String startMonitor(String bridgeName, String deviceId) throw new UnsupportedOperationException("Not supported in V2 API."); } - @Override - @NonNull - @SuppressWarnings("ignoring Map type check") - public ACCapability getACCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { - try { - File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); - Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { - }); - Map cap = (Map) mapper.get("Value"); - if (cap == null) { - throw new LGThinqApiException("Error extracting capabilities supported by the device"); - } - ACCapability acCap = new ACCapability(); - Map opModes = (Map) cap.get("airState.opMode"); - if (opModes == null) { - throw new LGThinqApiException("Error extracting opModes supported by the device"); - } else { - Map modes = new HashMap(); - ((Map) opModes.get("value_mapping")).forEach((k, v) -> { - modes.put(v, k); - }); - acCap.setOpMod(modes); - } - Map fanSpeed = (Map) cap.get("airState.windStrength"); - if (fanSpeed == null) { - throw new LGThinqApiException("Error extracting fanSpeed supported by the device"); - } else { - Map fanModes = new HashMap(); - ((Map) fanSpeed.get("value_mapping")).forEach((k, v) -> { - fanModes.put(v, k); - }); - acCap.setFanSpeed(fanModes); - - } - // Set supported modes for the device - Map> supOpModes = (Map>) cap - .get("support.airState.opMode"); - acCap.setSupportedOpMode(new ArrayList<>(supOpModes.get("value_mapping").values())); - acCap.getSupportedOpMode().remove("@NON"); - Map> supFanSpeeds = (Map>) cap - .get("support.airState.windStrength"); - acCap.setSupportedFanSpeed(new ArrayList<>(supFanSpeeds.get("value_mapping").values())); - acCap.getSupportedFanSpeed().remove("@NON"); - return acCap; - } catch (IOException e) { - throw new LGThinqApiException("Error reading IO interface", e); - } - } - private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { Map metaResult; if (resp == null) { @@ -257,9 +223,8 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) } @Override - @Nullable - public ACSnapShot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull String workId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { throw new UnsupportedOperationException("Not supported in V2 API."); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java similarity index 90% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index aae3164658013..0e7d9f59d1049 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -10,13 +10,14 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import java.util.Collections; import java.util.List; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.Capability; /** * The {@link ACCapability} @@ -24,7 +25,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class ACCapability { +public class ACCapability extends Capability { private Map opMod = Collections.emptyMap(); private Map fanSpeed = Collections.emptyMap(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java index 3381241e9c39a..3c16809f811e3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACFanSpeed.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link ACSnapShot} + * The {@link ACSnapshot} * * @author Nemer Daud - Initial contribution */ diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java similarity index 96% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java index b5d24a6691c02..38a8dac94a4ca 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACOpMode.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java @@ -10,12 +10,12 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link ACSnapShot} + * The {@link ACSnapshot} * * @author Nemer Daud - Initial contribution */ diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java similarity index 81% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java index 754a80aa75b30..5a049417303ab 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java @@ -10,22 +10,24 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; /** - * The {@link ACSnapShot} + * The {@link ACSnapshot} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault @JsonIgnoreProperties(ignoreUnknown = true) -public abstract class ACSnapShot { +public abstract class ACSnapshot implements Snapshot { private int airWindStrength; @@ -40,10 +42,15 @@ public abstract class ACSnapShot { private boolean online; @JsonIgnore - public DevicePowerState getAcPowerStatus() { + public DevicePowerState getPowerStatus() { return operation == null ? DevicePowerState.DV_POWER_UNK : DevicePowerState.statusOf(operation); } + @JsonIgnore + public void setPowerStatus(DevicePowerState value) { + operation = (int) value.getValue(); + } + @JsonIgnore public ACFanSpeed getAcFanSpeed() { return ACFanSpeed.statusOf(airWindStrength); @@ -103,7 +110,7 @@ public void setOnline(boolean online) { public String toString() { return "ACSnapShot{" + "airWindStrength=" + airWindStrength + ", targetTemperature=" + targetTemperature + ", currentTemperature=" + currentTemperature + ", operationMode=" + operationMode + ", operation=" - + operation + ", acPowerStatus=" + getAcPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() - + ", acOpMode=" + ", online=" + isOnline() + " }"; + + operation + ", acPowerStatus=" + getPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() + ", acOpMode=" + + ", online=" + isOnline() + " }"; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java similarity index 90% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java index 2a4918838f78c..8cec78795c997 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -18,12 +18,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * The {@link ACSnapShotV1} + * The {@link ACSnapshotV1} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class ACSnapShotV1 extends ACSnapShot { +public class ACSnapshotV1 extends ACSnapshot { @Override @JsonProperty("WindStrength") diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java similarity index 91% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java index 78ab6cf2ea72b..3d5da370f5ebc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACSnapShotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -18,12 +18,12 @@ import com.fasterxml.jackson.annotation.JsonProperty; /** - * The {@link ACSnapShotV2} + * The {@link ACSnapshotV2} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class ACSnapShotV2 extends ACSnapShot { +public class ACSnapshotV2 extends ACSnapshot { @Override @JsonProperty("airState.windStrength") diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java index 72a24289c15ee..4b2fbc27bd6e5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ACTargetTmp.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model; +package org.openhab.binding.lgthinq.lgservices.model.ac; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 1ec2d87e1e528..a6ed2656f36ac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -14,10 +14,6 @@ LG Thinq Washing Machine - - - - diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index 3e42a5779cbac..b1d243ee49162 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -19,6 +19,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.Files; +import org.eclipse.jdt.annotation.NonNullByDefault; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -27,6 +29,7 @@ * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public class JsonUtils { public static T unmashallJson(String fileName) { InputStream inputStream = JsonUtils.class.getResourceAsStream(fileName); diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json index fcb611b9d95c6..6c1056fbe2375 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-1.json @@ -87,7 +87,7 @@ "imageFileName":"ac_home_wall_airconditioner_img.png", "imageUrl":"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", "smallImageUrl":"https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", - "ssid":"smart-gameficacao", + "ssid":"xxxxxxxxx", "softapId":"", "softapPass":"", "macAddress":"", @@ -103,7 +103,7 @@ "sdsGuide":"{\"deviceCode\":\"AI01\"}", "newRegYn":"N", "remoteControlType":"", - "userNo":"BR2004259832795", + "userNo":"xxxxxxxxxxx", "tftYn":"N", "modelJsonVer":12.11, "modelJsonUri":"https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f", From 816fb90abf067d2a470bc9d3ba12617d31f39057 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 00:35:55 -0300 Subject: [PATCH 070/130] [lgthinq][Feat] Introducing of Washin Machine support. Only monitoring for now. No Channel added Signed-off-by: nemerdaud --- .../internal/LGThinqWasherHandler.java | 393 ++++++++++++++++++ .../lgthinq/lgservices/model/Capability.java | 22 + .../lgthinq/lgservices/model/LGAPIVerion.java | 3 +- .../lgthinq/lgservices/model/Snapshot.java | 32 ++ .../lgservices/model/SnapshotFactory.java | 183 +------- .../lgservices/model/washer/WMCapability.java | 122 ++++++ .../lgservices/model/washer/WMSnapshot.java | 55 +++ .../resources/dashboard-list-response-wm.json | 2 +- .../src/test/resources/wm-data-result.json | 229 +++++----- 9 files changed, 764 insertions(+), 277 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java new file mode 100644 index 0000000000000..4c39ae758902d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -0,0 +1,393 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal; + +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; + +import java.util.Collection; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.washer.WMCapability; +import org.openhab.binding.lgthinq.lgservices.model.washer.WMSnapshot; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ThingHandlerService; +import org.openhab.core.types.Command; +import org.openhab.core.types.RefreshType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinqWasherHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqWasherHandler extends LGThinqDeviceThing { + + private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; + @Nullable + private WMCapability wmCapability; + private final String lgPlatfomType; + private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); + @NonNullByDefault + private final LGThinqApiClientService lgThinqApiClientService; + private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; + // Bridges status that this thing must top scanning for state change + private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, + ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + ThingStatusDetail.CONFIGURATION_ERROR); + private @Nullable ScheduledFuture thingStatePollingJob; + private @Nullable Future commandExecutorQueueJob; + // *** Long running isolated threadpools. + private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); + private final ExecutorService executorService = Executors.newFixedThreadPool(1); + + private boolean monitorV1Began = false; + private String monitorWorkId = ""; + private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); + @NonNullByDefault + private String bridgeId = ""; + + public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing); + this.stateDescriptionProvider = stateDescriptionProvider; + lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); + lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() + : LGThinqApiV2ClientServiceImpl.getInstance(); + } + + static class AsyncCommandParams { + final String channelUID; + final Command command; + + public AsyncCommandParams(String channelUUID, Command command) { + this.channelUID = channelUUID; + this.command = command; + } + } + + @Override + public Collection> getServices() { + return super.getServices(); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + protected void startCommandExecutorQueueJob() { + if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { + commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); + } + } + + private ExecutorService getExecutorService() { + return executorService; + } + + private void stopCommandExecutorQueueJob() { + if (commandExecutorQueueJob != null) { + commandExecutorQueueJob.cancel(true); + } + } + + protected void startThingStatePolling() { + if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { + thingStatePollingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, + DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); + } + } + + private void updateThingStateFromLG() { + try { + WMSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); + if (shot == null) { + // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. + return; + } + if (!shot.isOnline()) { + if (getThing().getStatus() != ThingStatus.OFFLINE) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); + updateState(CHANNEL_POWER_ID, + OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_OFF)); + } + return; + } + + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + + updateStatus(ThingStatus.ONLINE); + } catch (LGThinqException e) { + logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", + getDeviceAlias(), getDeviceId(), e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + + private ScheduledExecutorService getLocalScheduler() { + return pollingScheduler; + } + + private String getBridgeId() { + if (bridgeId.isBlank() && getBridge() == null) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); + logger.error("Configuration error um Thinq Thing - No Bridge defined for the thing."); + return "UNKNOWN"; + } else if (bridgeId.isBlank() && getBridge() != null) { + bridgeId = getBridge().getUID().getId(); + } + return bridgeId; + } + + private void forceStopDeviceV1Monitor(String deviceId) { + try { + monitorV1Began = false; + lgThinqApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + } catch (Exception e) { + logger.error("Error stopping LG Device monitor", e); + } + } + + @NonNull + private String emptyIfNull(@Nullable String value) { + return value == null ? "" : "" + value; + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + // not dynamic state channel in this device + } + + @Override + public WMCapability getCapabilities() throws LGThinqApiException { + if (wmCapability == null) { + wmCapability = (WMCapability) lgThinqApiClientService.getCapability(getDeviceId(), getDeviceUriJsonConfig(), + false); + } + return Objects.requireNonNull(wmCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); + } + + @Override + protected Logger getLogger() { + return logger; + } + + @Nullable + private WMSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { + // analise de platform version + if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { + return (WMSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); + } else { + try { + if (!monitorV1Began) { + monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorV1Began = true; + } + } catch (LGThinqDeviceV1OfflineException e) { + forceStopDeviceV1Monitor(deviceId); + WMSnapshot shot = new WMSnapshot(); + shot.setOnline(false); + return shot; + } catch (Exception e) { + forceStopDeviceV1Monitor(deviceId); + throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); + } + int retries = 10; + WMSnapshot shot; + while (retries > 0) { + // try to get monitoring data result 3 times. + try { + shot = (WMSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + if (shot != null) { + return shot; + } + Thread.sleep(500); + retries--; + } catch (LGThinqDeviceV1MonitorExpiredException e) { + forceStopDeviceV1Monitor(deviceId); + logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); + return null; + } catch (Exception e) { + // If it can't get monitor handler, then stop monitor and restart the process again in new + // interaction + // Force restart monitoring because of the errors returned (just in case) + forceStopDeviceV1Monitor(deviceId); + throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); + } + } + forceStopDeviceV1Monitor(deviceId); + throw new LGThinqApiException("Exhausted trying to get monitor data for the device:" + deviceId); + } + } + + protected void stopThingStatePolling() { + if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { + logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); + thingStatePollingJob.cancel(true); + } + } + + private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { + if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { + // start the thing polling + startThingStatePolling(); + } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE + && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { + // comunication error is not a specific Bridge error, then we must analise it to give + // this thinq the change to recovery from communication errors + if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR + || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { + // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if + // the bridge is out + stopThingStatePolling(); + } + + } + lastThingStatus = newStatus; + } + + @Override + protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { + handleStatusChanged(newStatus, statusDetail); + super.updateStatus(newStatus, statusDetail, description); + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceId() { + return getThing().getUID().getId(); + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceModelName() { + return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public boolean onDeviceStateChanged() { + // TODO - HANDLE IT, Think if it's needed + return false; + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void onDeviceGone() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void dispose() { + if (thingStatePollingJob != null) { + thingStatePollingJob.cancel(true); + stopThingStatePolling(); + stopCommandExecutorQueueJob(); + thingStatePollingJob = null; + } + } + + @Override + public void handleCommand(ChannelUID channelUID, Command command) { + if (command instanceof RefreshType) { + updateThingStateFromLG(); + } else { + AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); + try { + // Ensure commands are send in a pipe per device. + commandBlockQueue.add(params); + } catch (IllegalStateException ex) { + logger.error( + "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); + updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, + "Device Command Queue is Busy"); + } + + } + } + + private final Runnable queuedCommandExecutor = new Runnable() { + @Override + public void run() { + while (true) { + AsyncCommandParams params; + try { + params = commandBlockQueue.take(); + } catch (InterruptedException e) { + logger.debug("Interrupting async command queue executor."); + return; + } + Command command = params.command; + + try { + switch (params.channelUID) { + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON + : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, + params.channelUID); + } + } + } catch (LGThinqException e) { + logger.error("Error executing Command {} to the channel {}. Thing goes offline until retry", + command, params.channelUID, e); + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); + } + } + } + }; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java new file mode 100644 index 0000000000000..2fe161c37684b --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java @@ -0,0 +1,22 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; + +/** + * The {@link Capability} + * + * @author Nemer Daud - Initial contribution + */ +public abstract class Capability { + +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java index 579f4f797e71b..6b223424bf7e5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java @@ -19,8 +19,7 @@ */ public enum LGAPIVerion { V1_0(1.0), - V2_0(2.0), - UNDEF(0.0); + V2_0(2.0); private final double version; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java new file mode 100644 index 0000000000000..da1212c16f879 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link Snapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface Snapshot { + + public DevicePowerState getPowerStatus(); + + public void setPowerStatus(DevicePowerState value); + + public boolean isOnline(); + + public void setOnline(boolean online); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index c68b6e141fe6b..5898ba88318e7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,28 +12,15 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.REFRIGERATOR_SNAPSHOT_NODE_V2; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; - -import java.beans.BeanInfo; -import java.beans.IntrospectionException; -import java.beans.Introspector; -import java.beans.PropertyDescriptor; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.util.*; +import java.util.Map; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; -import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; +import org.openhab.binding.lgthinq.lgservices.model.washer.WMSnapshot; -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -49,153 +36,37 @@ public class SnapshotFactory { instance = new SnapshotFactory(); } - public static SnapshotFactory getInstance() { + public static final SnapshotFactory getInstance() { return instance; } - /** - * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) - * - * @param binaryData V1: decoded returnedData - * - * @return returns Snapshot implementation based on device type provided - * @throws LGThinqApiException any error. - */ - public S createFromBinary(String binaryData, List prot, - Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { - try { - byte[] data = binaryData.getBytes(); - BeanInfo beanInfo = Introspector.getBeanInfo(clazz); - S snap = clazz.getConstructor().newInstance(); - PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); - for (MonitoringBinaryProtocol protField : prot) { - String fName = protField.fieldName; - for (PropertyDescriptor property : pds) { - // all attributes of class. - Method m = property.getReadMethod(); // getter - List aliases = new ArrayList<>(); - if (m.isAnnotationPresent(JsonProperty.class)) { - aliases.add(m.getAnnotation(JsonProperty.class).value()); - } - if (m.isAnnotationPresent(JsonAlias.class)) { - aliases.addAll(Arrays.asList(m.getAnnotation(JsonAlias.class).value())); - } - - if (aliases.contains(fName)) { - // found property. Get bit value - int value = 0; - for (int i = protField.startByte; i < protField.startByte + protField.length; i++) { - value = (value << 8) + data[i]; - } - m = property.getWriteMethod(); - if (m.getParameters()[0].getType() == String.class) { - m.invoke(snap, String.valueOf(value)); - } else if (m.getParameters()[0].getType() == Double.class) { - m.invoke(snap, (double) value); - } else if (m.getParameters()[0].getType() == Integer.class) { - m.invoke(snap, value); - } else { - throw new IllegalArgumentException( - String.format("Parameter type not supported for this factory:%s", - m.getParameters()[0].getType().toString())); - } - } - } - } - return snap; - } catch (IntrospectionException | InvocationTargetException | InstantiationException | IllegalAccessException - | NoSuchMethodException e) { - throw new LGThinqUnmarshallException("Unexpected Error unmarshalling binary data", e); - } - } - - /** - * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) - * - * @param snapshotDataJson V1: decoded returnedData; V2: snapshot body - * @param deviceType device type - * @return returns Snapshot implementation based on device type provided - * @throws LGThinqApiException any error. - */ - public S createFromJson(String snapshotDataJson, DeviceTypes deviceType, - Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { - try { - Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { - }); - Map deviceSetting = new HashMap<>(); - deviceSetting.put("deviceType", deviceType.deviceTypeId()); - deviceSetting.put("snapshot", snapshotMap); - return createFromJson(deviceSetting, clazz); - } catch (JsonProcessingException e) { - throw new LGThinqUnmarshallException("Unexpected Error unmarshalling json to map", e); - } - } - - public S createFromJson(Map deviceSettings, Class clazz) - throws LGThinqApiException { + public Snapshot create(Map deviceSettings) throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); - Map snapMap = ((Map) deviceSettings.get("snapshot")); + Map snapMap = (Map) deviceSettings.get("snapshot"); if (snapMap == null) { throw new LGThinqApiException("snapshot node not present in device monitoring result."); } LGAPIVerion version = discoveryAPIVersion(snapMap, type); - return getSnapshot(clazz, type, snapMap, version); - } - - private S getSnapshot(Class clazz, DeviceTypes type, - Map snapMap, LGAPIVerion version) { - S snap; switch (type) { case AIR_CONDITIONER: - case HEAT_PUMP: - snap = clazz.cast(objectMapper.convertValue(snapMap, ACCanonicalSnapshot.class)); - snap.setRawData(snapMap); - return snap; - case WASHING_TOWER: - case WASHERDRYER_MACHINE: - switch (version) { - case V1_0: { - snap = clazz.cast(objectMapper.convertValue(snapMap, WasherDryerSnapshot.class)); - snap.setRawData(snapMap); - } - case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = clazz.cast(objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class)); - snap.setRawData(washerDryerMap); - return snap; - } - } - case DRYER_TOWER: - case DRYER: switch (version) { case V1_0: { - throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + return objectMapper.convertValue(deviceSettings, ACSnapshotV2.class); } case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = clazz.cast(objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class)); - snap.setRawData(snapMap); - return snap; + return objectMapper.convertValue(deviceSettings, ACSnapshotV1.class); } } - case REFRIGERATOR: + case WASHING_MACHINE: switch (version) { case V1_0: { throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); } case V2_0: { - Map refMap = Objects.requireNonNull( - (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = clazz.cast(objectMapper.convertValue(refMap, FridgeCanonicalSnapshot.class)); - snap.setRawData(snapMap); - return snap; + return objectMapper.convertValue(deviceSettings, WMSnapshot.class); } } + default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } @@ -203,44 +74,24 @@ private S getSnapshot(Class clazz, Dev private DeviceTypes getDeviceType(Map rootMap) { Integer deviceTypeId = (Integer) rootMap.get("deviceType"); - // device code is only present in v2 devices snapshot. - String deviceCode = Objects.requireNonNullElse((String) rootMap.get("deviceCode"), ""); Objects.requireNonNull(deviceTypeId, "Unexpected error. deviceType field not present in snapshot schema"); - return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); + return DeviceTypes.fromDeviceTypeId(deviceTypeId); } private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { switch (type) { case AIR_CONDITIONER: - case HEAT_PUMP: if (snapMap.containsKey("airState.opMode")) { return LGAPIVerion.V2_0; } else if (snapMap.containsKey("OpMode")) { return LGAPIVerion.V1_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); + "Unexpected error. Can't find key node attributes to determine AC API version."); } - case DRYER_TOWER: - case DRYER: + + case WASHING_MACHINE: return LGAPIVerion.V2_0; - case WASHING_TOWER: - case WASHERDRYER_MACHINE: - if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { - return LGAPIVerion.V2_0; - } else if (snapMap.containsKey("State")) { - return LGAPIVerion.V1_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); - } - case REFRIGERATOR: - if (snapMap.containsKey(REFRIGERATOR_SNAPSHOT_NODE_V2)) { - return LGAPIVerion.V2_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); - } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java new file mode 100644 index 0000000000000..42d97011d10fd --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java @@ -0,0 +1,122 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.Capability; + +/** + * The {@link WMCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMCapability extends Capability { + public enum MonitoringCap { + STATE("state"), + SOIL_WASH("soilWash"), + SPIN("spin"), + TEMPERATURE("temp"), + RINSE("rinse"); + + final String value; + + MonitoringCap(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + private static class MonitoringValue { + private Map state = new LinkedHashMap(); + private Map soilWash = new LinkedHashMap(); + private Map spin = new LinkedHashMap(); + private Map temperature = new LinkedHashMap(); + private Map rinse = new LinkedHashMap(); + private boolean hasDoorLook; + private boolean hasTurboWash; + } + + private MonitoringValue monitoringValue = new MonitoringValue(); + private Map courses = new LinkedHashMap(); + + public Map getCourses() { + return courses; + } + + public void addCourse(String courseLabel, String courseName) { + courses.put(courseLabel, courseName); + } + + public Map getState() { + return monitoringValue.state; + } + + public Map getSoilWash() { + return monitoringValue.soilWash; + } + + public Map getSpin() { + return monitoringValue.spin; + } + + public Map getTemperature() { + return monitoringValue.temperature; + } + + public Map getRinse() { + return monitoringValue.rinse; + } + + public boolean hasDoorLook() { + return monitoringValue.hasDoorLook; + } + + public void setHasDoorLook(boolean hasDoorLook) { + monitoringValue.hasDoorLook = hasDoorLook; + } + + public boolean hasTurboWash() { + return monitoringValue.hasTurboWash; + } + + public void setHasTurboWash(boolean hasTurboWash) { + monitoringValue.hasTurboWash = hasTurboWash; + } + + public void addMonitoringValue(MonitoringCap monCap, String key, String value) { + switch (monCap) { + case STATE: + monitoringValue.state.put(key, value); + break; + case SOIL_WASH: + monitoringValue.soilWash.put(key, value); + break; + case SPIN: + monitoringValue.spin.put(key, value); + break; + case TEMPERATURE: + monitoringValue.temperature.put(key, value); + break; + case RINSE: + monitoringValue.rinse.put(key, value); + break; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java new file mode 100644 index 0000000000000..02e57b1cfb332 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; + +/** + * The {@link WMSnapshot} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMSnapshot implements Snapshot { + private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; + private String course = ""; + + @Override + public DevicePowerState getPowerStatus() { + return powerState; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + this.powerState = value; + } + + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } + + @Override + public boolean isOnline() { + return false; + } + + @Override + public void setOnline(boolean online) { + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json index 50f4050ce40ba..dfa4555bc867a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json @@ -10,7 +10,7 @@ "deviceType":201, "deviceCode":"WM01", "alias":"Bedroom", - "deviceId":"washer-0001-5771", + "deviceId":"abra-cadabra-0001-5771", "fwVer":"2.5.8_RTOS_3K", "imageFileName":"washmachine-1.png", "imageUrl":"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json index 17cb8f393da3c..31df215dd7ca6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json @@ -1,118 +1,131 @@ { "resultCode": "0000", "result": { - "appType":"NUTS", - "modelCountryCode":"WW", - "countryCode":"DK", - "modelName":"F_R7_Y___W.A__QEUK", - "deviceType":201, - "deviceCode":"LA02", - "alias":"Frontbetjent vaskemaskine", - "deviceId":"592bd2a4-d3e7-16e9-a69f-44cb8b2e0c43", - "fwVer":"", - "imageFileName":"home_appliances_img_wmdrum.png", - "ssid":"Kepler", - "softapId":"", - "softapPass":"", - "macAddress":"", - "networkType":"02", - "timezoneCode":"Europe/Copenhagen", - "timezoneCodeAlias":"Europe/Copenhagen", - "utcOffset":1, - "utcOffsetDisplay":"+01:00", - "dstOffset":2, - "dstOffsetDisplay":"+02:00", - "curOffset":1, - "curOffsetDisplay":"+01:00", - "sdsGuide":"{\"deviceCode\":\"LA02\"}", - "newRegYn":"N", - "remoteControlType":"", - "userNo":"DK2202075642801", - "tftYn":"N", - "deviceState":"E", - "snapshot":{ - "washerDryer":{ - "initialBit":"INITIAL_BIT_OFF", - "standby":"STANDBY_OFF", - "courseFL24inchBaseTitan":"MIXEDFABRIC", - "initialTimeMinute":24.0, - "preState":"SPINNING", - "error":"ERROR_NO", - "dryLevel":"NOT_SELECTED", - "creaseCare":"CREASECARE_OFF", - "remainTimeHour":0.0, - "smartCourseFL24inchBaseTitan":"NOT_SELECTED", - "preWash":"PREWASH_OFF", - "steam":"STEAM_OFF", - "state":"SPINNING", - "rinse":"NO_RINSE", - "wrinkleCare":"WRINKLECARE_OFF", - "loadItemWasher":"LOADITEM_OFF", - "temp":"NO_TEMP", - "doorLock":"DOOR_LOCK_ON", - "reserveTimeMinute":0.0, - "AIDDLed":"AIDDLed_OFF", - "TCLCount":33.0, - "downloadedCourseFL24inchBaseTitan":"RINSESPIN", - "medicRinse":"MEDICRINSE_OFF", - "turboWash":"TURBOWASH_OFF", - "ecoHybrid":"ECOHYBRID_OFF", - "remainTimeMinute":11.0, - "reserveTimeHour":0.0, - "steamSoftener":"STEAMSOFTENER_OFF", - "childLock":"CHILDLOCK_OFF", - "remoteStart":"REMOTE_START_ON", - "spin":"SPIN_1400", - "soilWash":"NO_SOILWASH", - "rinseSpin":"RINSE_SPIN_OFF", - "initialTimeHour":1.0 + "snapshot": { + "appType": "NUTS", + "modelCountryCode": "WW", + "countryCode": "BR", + "modelName": "RAC_056905_WW", + "deviceType": 201, + "deviceCode": "WM01", + "alias": "Kitchen", + "deviceId": "abra-cadabra-0001-5772", + "fwVer": "", + "imageFileName": "wm_img.png", + "imageUrl": "https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", + "smallImageUrl": "https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", + "ssid": "my_ssid", + "softapId": "", + "softapPass": "", + "macAddress": "", + "networkType": "02", + "timezoneCode": "America/Sao_Paulo", + "timezoneCodeAlias": "Brazil/Sao Paulo", + "utcOffset": -3, + "utcOffsetDisplay": "-03:00", + "dstOffset": -2, + "dstOffsetDisplay": "-02:00", + "curOffset": -2, + "curOffsetDisplay": "-02:00", + "sdsGuide": "{\"deviceCode\":\"WM01\"}", + "newRegYn": "N", + "remoteControlType": "", + "userNo": "XXXXXXXXX", + "tftYn": "N", + "modelJsonVer": 12.11, + "modelJsonUri": "https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f", + "appModuleVer": 12.49, + "appModuleUri": "https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155", + "appRestartYn": "Y", + "appModuleSize": 6082481, + "langPackProductTypeVer": 59.9, + "langPackProductTypeUri": "https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea", + "deviceState": "E", + "snapshot": { + "airState.windStrength": 8.0, + "airState.wMode.lowHeating": 0.0, + "airState.diagCode": 0.0, + "airState.lightingState.displayControl": 1.0, + "airState.wDir.hStep": 0.0, + "mid": 8.4615358E7, + "airState.energy.onCurrent": 476.0, + "airState.wMode.airClean": 0.0, + "airState.quality.sensorMon": 0.0, + "airState.energy.accumulatedTime": 0.0, + "airState.miscFuncState.antiBugs": 0.0, + "airState.tempState.target": 18.0, + "airState.operation": 1.0, + "airState.wMode.jet": 0.0, + "airState.wDir.vStep": 2.0, + "timestamp": 1.643248573766E12, + "airState.powerSave.basic": 0.0, + "airState.quality.PM10": 0.0, + "static": { + "deviceType": "401", + "countryCode": "BR" + }, + "airState.quality.overall": 0.0, + "airState.tempState.current": 25.0, + "airState.miscFuncState.extraOp": 0.0, + "airState.energy.accumulated": 0.0, + "airState.reservation.sleepTime": 0.0, + "airState.miscFuncState.autoDry": 0.0, + "airState.reservation.targetTimeToStart": 0.0, + "meta": { + "allDeviceInfoUpdate": false, + "messageId": "fVz2AE-2SC-rf3GnerGdeQ" + }, + "airState.quality.PM1": 0.0, + "airState.wMode.smartCare": 0.0, + "airState.quality.PM2": 0.0, + "online": true, + "airState.opMode": 0.0, + "airState.reservation.targetTimeToStop": 0.0, + "airState.filterMngStates.maxTime": 0.0, + "airState.filterMngStates.useTime": 0.0 }, - "mid":8.4022883E7, - "online":true, - "static":{ - "deviceType":"201", - "countryCode":"DK" + "online": true, + "platformType": "thinq2", + "area": 45883, + "regDt": 2.0220111184827E13, + "blackboxYn": "Y", + "modelProtocol": "STANDARD", + "order": 0, + "drServiceYn": "N", + "fwInfoList": [ + { + "checksum": "00004105", + "order": 1.0, + "partNumber": "SAA40128563" + } + ], + "modemInfo": { + "appVersion": "clip_hna_v1.9.116", + "modelName": "RAC_056905_WW", + "modemType": "QCOM_QCA4010", + "ruleEngine": "y" }, - "meta":{ - "allDeviceInfoUpdate":false, - "messageId":"TSmRTV6yTUq2obot8_Q9Qg" + "guideTypeYn": "Y", + "guideType": "RAC_TYPE1", + "regDtUtc": "20220111204827", + "regIndex": 0, + "groupableYn": "Y", + "controllableYn": "Y", + "combinedProductYn": "N", + "masterYn": "Y", + "pccModelYn": "N", + "sdsPid": { + "sds4": "", + "sds3": "", + "sds2": "", + "sds1": "" }, - "timestamp":1.644358361572E12 + "autoOrderYn": "N", + "initDevice": false, + "existsEntryPopup": "N", + "tclcount": 0 }, "online":true, - "platformType":"thinq2", - "area":125955, - "regDt":2.0220208013031E13, - "blackboxYn":"Y", - "modelProtocol":"courseFL24inchBaseTitan", - "receipeVersion":0, - "activeSaving":"OFF", - "smartCareV2":"OFF", - "order":0, - "nlpAlias":"none", - "drServiceYn":"N", - "fwInfoList":[ - { - "checksum":"016771B3", - "partNumber":"SAA41059310", - "order":2.0 - }, - { - "checksum":"000075A3", - "partNumber":"SAA41059211", - "order":1.0 - } - ], - "regDtUtc":"20220207233031", - "regIndex":0, - "groupableYn":"N", - "controllableYn":"N", - "combinedProductYn":"N", - "masterYn":"Y", - "controlGuideType":"TYPE4", - "initDevice":false, - "upgradableYn":"N", - "autoFwDownloadYn":"N", - "tclcount":0 + "platformType":"thinq2" } } \ No newline at end of file From e467e1d1ebb06a07902a16ef4472c80a9eb084f6 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 13:20:07 -0300 Subject: [PATCH 071/130] [lgthinq][Fix] Remove configuration token file if the bridge configuration is changes; fix classcast exception in Washer thing creation Signed-off-by: nemerdaud --- .../internal/LGThinqBindingConstants.java | 7 +- .../internal/LGThinqConfiguration.java | 12 +- .../internal/LGThinqHandlerFactory.java | 2 +- .../lgthinq/internal/api/LGThinqGateway.java | 14 +- .../handler/LGThinqBridgeHandler.java | 17 +- .../resources/dashboard-list-response-wm.json | 2 +- .../src/test/resources/wm-data-result.json | 244 +++++++++--------- 7 files changed, 167 insertions(+), 131 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index f88735c8c8d8b..319e29ce5c959 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -63,7 +63,7 @@ public class LGThinqBindingConstants { public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; public static final String GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; public static final String GATEWAY_SERVICE_PATH_V1 = "/api/common/gatewayUriList"; - public static String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; + public static final String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; public static final String PRE_LOGIN_PATH = "/preLogin"; public static final String SECURITY_KEY = "nuts_securitykey"; public static final String APP_KEY = "wideq"; @@ -78,7 +78,8 @@ public class LGThinqBindingConstants { public static final String DEFAULT_COUNTRY = "US"; public static final String DEFAULT_LANGUAGE = "en-US"; public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; - public static String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com/emp/oauth2/token/empsession"; + public static final String V2_EMP_SESS_PATH = "/emp/oauth2/token/empsession"; + public static final String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com" + V2_EMP_SESS_PATH; public static final String API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; public static final String API_KEY_V1 = "wideq"; @@ -98,7 +99,7 @@ public class LGThinqBindingConstants { public static final String DEVICE_ID = "device_id"; public static final String MODEL_NAME = "model_name"; public static final String DEVICE_ALIAS = "device_alias"; - public static final String MODEL_URL_INFO = "model_url_indo"; + public static final String MODEL_URL_INFO = "model_url_info"; public static final String PLATFORM_TYPE = "platform_type"; public static final String PLATFORM_TYPE_V1 = "thinq1"; public static final String PLATFORM_TYPE_V2 = "thinq2"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java index 5bb094898006a..8bd5ef613199a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java @@ -29,17 +29,19 @@ public class LGThinqConfiguration { public String country = ""; public String language = ""; public Integer pollingIntervalSec = 0; + public String alternativeServer = ""; public LGThinqConfiguration() { } public LGThinqConfiguration(String username, String password, String country, String language, - Integer pollingIntervalSec) { + Integer pollingIntervalSec, String alternativeServer) { this.username = username; this.password = password; this.country = country; this.language = language; this.pollingIntervalSec = pollingIntervalSec; + this.alternativeServer = alternativeServer; } public String getUsername() { @@ -81,4 +83,12 @@ public void setLanguage(String language) { public void setPollingIntervalSec(Integer pollingIntervalSec) { this.pollingIntervalSec = pollingIntervalSec; } + + public String getAlternativeServer() { + return alternativeServer; + } + + public void setAlternativeServer(String alternativeServer) { + this.alternativeServer = alternativeServer; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java index 77add4311bdb6..68b203cf3eb5c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java @@ -58,7 +58,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { return new LGThinqBridgeHandler((Bridge) thing); } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { - return new LGThinqWasherHandler((Bridge) thing, stateDescriptionProvider); + return new LGThinqWasherHandler(thing, stateDescriptionProvider); } logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java index 22389f8a5cf71..cf7b4557ef7df 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java @@ -12,11 +12,16 @@ */ package org.openhab.binding.lgthinq.internal.api; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_EMP_SESS_PATH; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_EMP_SESS_URL; + import java.io.Serializable; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; +import com.fasterxml.jackson.annotation.JsonIgnore; + /** * The {@link LGThinqGateway} hold informations about the LG Gateway * @@ -33,12 +38,13 @@ public class LGThinqGateway implements Serializable { private String country = ""; private String username = ""; private String password = ""; + private String alternativeEmpServer = ""; private int accountVersion; public LGThinqGateway() { } - public LGThinqGateway(GatewayResult gwResult, String language, String country) { + public LGThinqGateway(GatewayResult gwResult, String language, String country, String alternativeEmpServer) { this.apiRootV2 = gwResult.getThinq2Uri(); this.apiRootV1 = gwResult.getThinq1Uri(); this.loginBaseUri = gwResult.getEmpSpxUri(); @@ -46,6 +52,12 @@ public LGThinqGateway(GatewayResult gwResult, String language, String country) { this.empBaseUri = gwResult.getEmpTermsUri(); this.language = language; this.country = country; + this.alternativeEmpServer = alternativeEmpServer; + } + + @JsonIgnore + public String getTokenSessionEmpUrl() { + return alternativeEmpServer.isBlank() ? V2_EMP_SESS_URL : alternativeEmpServer + V2_EMP_SESS_PATH; } public String getEmpBaseUri() { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 8505ef6399939..19bcf9a8797ce 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -12,6 +12,7 @@ */ package org.openhab.binding.lgthinq.internal.handler; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_CONNECTION_DATA_FILE; import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; import java.io.File; @@ -113,7 +114,8 @@ public void run() { } else { try { tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), - lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword()); + lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword(), + lgthinqConfig.getAlternativeServer()); tokenManager.getValidRegisteredToken(bridgeName); logger.debug("Successful getting token from LG API"); } catch (IOException e) { @@ -286,6 +288,19 @@ public void initialize() { } } + @Override + public void handleConfigurationUpdate(Map configurationParameters) { + logger.debug("Bridge Configuration was updated. Cleaning the token registry file"); + File f = new File(String.format(THINQ_CONNECTION_DATA_FILE, getThing().getUID().getId())); + if (f.isFile()) { + // file exists. Delete it + if (!f.delete()) { + logger.error("Error deleting file:{}", f.getAbsolutePath()); + } + } + super.handleConfigurationUpdate(configurationParameters); + } + private void startLGThinqDevicePolling() { // stop current scheduler, if any if (devicePollingJob != null && !devicePollingJob.isDone()) { diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json index dfa4555bc867a..50f4050ce40ba 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/dashboard-list-response-wm.json @@ -10,7 +10,7 @@ "deviceType":201, "deviceCode":"WM01", "alias":"Bedroom", - "deviceId":"abra-cadabra-0001-5771", + "deviceId":"washer-0001-5771", "fwVer":"2.5.8_RTOS_3K", "imageFileName":"washmachine-1.png", "imageUrl":"https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json index 31df215dd7ca6..a8296ae9b1156 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json @@ -1,131 +1,129 @@ { "resultCode": "0000", "result": { + "appType": "NUTS", + "modelCountryCode": "WW", + "countryCode": "BR", + "modelName": "RAC_056905_WW", + "deviceType": 201, + "deviceCode": "WM01", + "alias": "Kitchen", + "deviceId": "washer-0001-5772", + "fwVer": "", + "imageFileName": "wm_img.png", + "imageUrl": "https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", + "smallImageUrl": "https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", + "ssid": "my_ssid", + "softapId": "", + "softapPass": "", + "macAddress": "", + "networkType": "02", + "timezoneCode": "America/Sao_Paulo", + "timezoneCodeAlias": "Brazil/Sao Paulo", + "utcOffset": -3, + "utcOffsetDisplay": "-03:00", + "dstOffset": -2, + "dstOffsetDisplay": "-02:00", + "curOffset": -2, + "curOffsetDisplay": "-02:00", + "sdsGuide": "{\"deviceCode\":\"WM01\"}", + "newRegYn": "N", + "remoteControlType": "", + "userNo": "XXXXXXXXX", + "tftYn": "N", + "modelJsonVer": 12.11, + "modelJsonUri": "https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f", + "appModuleVer": 12.49, + "appModuleUri": "https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155", + "appRestartYn": "Y", + "appModuleSize": 6082481, + "langPackProductTypeVer": 59.9, + "langPackProductTypeUri": "https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea", + "deviceState": "E", "snapshot": { - "appType": "NUTS", - "modelCountryCode": "WW", - "countryCode": "BR", - "modelName": "RAC_056905_WW", - "deviceType": 201, - "deviceCode": "WM01", - "alias": "Kitchen", - "deviceId": "abra-cadabra-0001-5772", - "fwVer": "", - "imageFileName": "wm_img.png", - "imageUrl": "https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", - "smallImageUrl": "https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", - "ssid": "my_ssid", - "softapId": "", - "softapPass": "", - "macAddress": "", - "networkType": "02", - "timezoneCode": "America/Sao_Paulo", - "timezoneCodeAlias": "Brazil/Sao Paulo", - "utcOffset": -3, - "utcOffsetDisplay": "-03:00", - "dstOffset": -2, - "dstOffsetDisplay": "-02:00", - "curOffset": -2, - "curOffsetDisplay": "-02:00", - "sdsGuide": "{\"deviceCode\":\"WM01\"}", - "newRegYn": "N", - "remoteControlType": "", - "userNo": "XXXXXXXXX", - "tftYn": "N", - "modelJsonVer": 12.11, - "modelJsonUri": "https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f", - "appModuleVer": 12.49, - "appModuleUri": "https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155", - "appRestartYn": "Y", - "appModuleSize": 6082481, - "langPackProductTypeVer": 59.9, - "langPackProductTypeUri": "https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea", - "deviceState": "E", - "snapshot": { - "airState.windStrength": 8.0, - "airState.wMode.lowHeating": 0.0, - "airState.diagCode": 0.0, - "airState.lightingState.displayControl": 1.0, - "airState.wDir.hStep": 0.0, - "mid": 8.4615358E7, - "airState.energy.onCurrent": 476.0, - "airState.wMode.airClean": 0.0, - "airState.quality.sensorMon": 0.0, - "airState.energy.accumulatedTime": 0.0, - "airState.miscFuncState.antiBugs": 0.0, - "airState.tempState.target": 18.0, - "airState.operation": 1.0, - "airState.wMode.jet": 0.0, - "airState.wDir.vStep": 2.0, - "timestamp": 1.643248573766E12, - "airState.powerSave.basic": 0.0, - "airState.quality.PM10": 0.0, - "static": { - "deviceType": "401", - "countryCode": "BR" - }, - "airState.quality.overall": 0.0, - "airState.tempState.current": 25.0, - "airState.miscFuncState.extraOp": 0.0, - "airState.energy.accumulated": 0.0, - "airState.reservation.sleepTime": 0.0, - "airState.miscFuncState.autoDry": 0.0, - "airState.reservation.targetTimeToStart": 0.0, - "meta": { - "allDeviceInfoUpdate": false, - "messageId": "fVz2AE-2SC-rf3GnerGdeQ" - }, - "airState.quality.PM1": 0.0, - "airState.wMode.smartCare": 0.0, - "airState.quality.PM2": 0.0, - "online": true, - "airState.opMode": 0.0, - "airState.reservation.targetTimeToStop": 0.0, - "airState.filterMngStates.maxTime": 0.0, - "airState.filterMngStates.useTime": 0.0 - }, - "online": true, - "platformType": "thinq2", - "area": 45883, - "regDt": 2.0220111184827E13, - "blackboxYn": "Y", - "modelProtocol": "STANDARD", - "order": 0, - "drServiceYn": "N", - "fwInfoList": [ - { - "checksum": "00004105", - "order": 1.0, - "partNumber": "SAA40128563" - } - ], - "modemInfo": { - "appVersion": "clip_hna_v1.9.116", - "modelName": "RAC_056905_WW", - "modemType": "QCOM_QCA4010", - "ruleEngine": "y" + "airState.windStrength": 8.0, + "airState.wMode.lowHeating": 0.0, + "airState.diagCode": 0.0, + "airState.lightingState.displayControl": 1.0, + "airState.wDir.hStep": 0.0, + "mid": 8.4615358E7, + "airState.energy.onCurrent": 476.0, + "airState.wMode.airClean": 0.0, + "airState.quality.sensorMon": 0.0, + "airState.energy.accumulatedTime": 0.0, + "airState.miscFuncState.antiBugs": 0.0, + "airState.tempState.target": 18.0, + "airState.operation": 1.0, + "airState.wMode.jet": 0.0, + "airState.wDir.vStep": 2.0, + "timestamp": 1.643248573766E12, + "airState.powerSave.basic": 0.0, + "airState.quality.PM10": 0.0, + "static": { + "deviceType": "401", + "countryCode": "BR" }, - "guideTypeYn": "Y", - "guideType": "RAC_TYPE1", - "regDtUtc": "20220111204827", - "regIndex": 0, - "groupableYn": "Y", - "controllableYn": "Y", - "combinedProductYn": "N", - "masterYn": "Y", - "pccModelYn": "N", - "sdsPid": { - "sds4": "", - "sds3": "", - "sds2": "", - "sds1": "" + "airState.quality.overall": 0.0, + "airState.tempState.current": 25.0, + "airState.miscFuncState.extraOp": 0.0, + "airState.energy.accumulated": 0.0, + "airState.reservation.sleepTime": 0.0, + "airState.miscFuncState.autoDry": 0.0, + "airState.reservation.targetTimeToStart": 0.0, + "meta": { + "allDeviceInfoUpdate": false, + "messageId": "fVz2AE-2SC-rf3GnerGdeQ" }, - "autoOrderYn": "N", - "initDevice": false, - "existsEntryPopup": "N", - "tclcount": 0 + "airState.quality.PM1": 0.0, + "airState.wMode.smartCare": 0.0, + "airState.quality.PM2": 0.0, + "online": true, + "airState.opMode": 0.0, + "airState.reservation.targetTimeToStop": 0.0, + "airState.filterMngStates.maxTime": 0.0, + "airState.filterMngStates.useTime": 0.0 + }, + "online": true, + "platformType": "thinq2", + "area": 45883, + "regDt": 2.0220111184827E13, + "blackboxYn": "Y", + "modelProtocol": "STANDARD", + "order": 0, + "drServiceYn": "N", + "fwInfoList": [ + { + "checksum": "00004105", + "order": 1.0, + "partNumber": "SAA40128563" + } + ], + "modemInfo": { + "appVersion": "clip_hna_v1.9.116", + "modelName": "RAC_056905_WW", + "modemType": "QCOM_QCA4010", + "ruleEngine": "y" + }, + "guideTypeYn": "Y", + "guideType": "RAC_TYPE1", + "regDtUtc": "20220111204827", + "regIndex": 0, + "groupableYn": "Y", + "controllableYn": "Y", + "combinedProductYn": "N", + "masterYn": "Y", + "pccModelYn": "N", + "sdsPid": { + "sds4": "", + "sds3": "", + "sds2": "", + "sds1": "" }, - "online":true, - "platformType":"thinq2" - } + "autoOrderYn": "N", + "initDevice": false, + "existsEntryPopup": "N", + "tclcount": 0 + }, + "online": true, + "platformType": "thinq2" } \ No newline at end of file From 13aaced7050ad599eb3922a37a0cf2f1b27b6e36 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 16:24:37 -0300 Subject: [PATCH 072/130] [lgthinq][Feat] Add Polish coyntry support and WM Snapshot DataModel Signed-off-by: nemerdaud --- .../lgservices/model/washer/ControlWifi.java | 82 +++++++++++++++++++ .../lgthinq/lgservices/model/washer/Data.java | 39 +++++++++ .../lgservices/model/washer/WMDownload.java | 43 ++++++++++ .../lgservices/model/washer/WMOff.java | 43 ++++++++++ .../lgservices/model/washer/WMSnapshot.java | 15 ++++ .../lgservices/model/washer/WMStart.java | 43 ++++++++++ .../lgservices/model/washer/WMStop.java | 43 ++++++++++ .../lgservices/model/washer/WMWakeup.java | 45 ++++++++++ .../lgservices/model/washer/WasherDryer.java | 47 +++++++++++ 9 files changed, 400 insertions(+) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java new file mode 100644 index 0000000000000..ebecc18eb3842 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java @@ -0,0 +1,82 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import static org.openhab.binding.lgthinq.lgservices.model.washer.WMDownload.EMPTY_WM_DOWNLOAD; +import static org.openhab.binding.lgthinq.lgservices.model.washer.WMStart.EMPTY_WM_START; +import static org.openhab.binding.lgthinq.lgservices.model.washer.WMStop.EMPTY_WM_STOP; +import static org.openhab.binding.lgthinq.lgservices.model.washer.WMWakeup.EMPTY_WM_WAKEUP; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link ControlWifi} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class ControlWifi { + static final ControlWifi EMPTY_CONTROL_WIFI = new ControlWifi(); + @JsonProperty("WMStart") + private WMStart wmStart = EMPTY_WM_START; + @JsonProperty("WMDownload") + private WMDownload wmDownload = EMPTY_WM_DOWNLOAD; + @JsonProperty("WMOff") + private WMOff wmOff = WMOff.EMPTY_WM_OFF; + @JsonProperty("WMStop") + private WMStop wmStop = EMPTY_WM_STOP; + @JsonProperty("WMWakeup") + private WMWakeup wmWakeup = EMPTY_WM_WAKEUP; + + public void setWmStart(WMStart wmStart) { + this.wmStart = wmStart; + } + + public WMStart getWmStart() { + return wmStart; + } + + public void setWmDownload(WMDownload wmDownload) { + this.wmDownload = wmDownload; + } + + public WMDownload getWmDownload() { + return wmDownload; + } + + public void setWmOff(WMOff wmOff) { + this.wmOff = wmOff; + } + + public WMOff getWmOff() { + return wmOff; + } + + public void setWmStop(WMStop wmStop) { + this.wmStop = wmStop; + } + + public WMStop getWmStop() { + return wmStop; + } + + public void setWmWakeup(WMWakeup wmWakeup) { + this.wmWakeup = wmWakeup; + } + + public WMWakeup getWmWakeup() { + return wmWakeup; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java new file mode 100644 index 0000000000000..4ef5c57e9ae90 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import static org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryer.EMPTY_WASHER_DRYER; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link Data} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class Data { + public static final Data EMPTY_DATA = new Data(); + @JsonProperty("washerDryer") + private WasherDryer washerdryer = EMPTY_WASHER_DRYER; + + public void setWasherDryer(WasherDryer washerdryer) { + this.washerdryer = washerdryer; + } + + public WasherDryer getWasherDryer() { + return washerdryer; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java new file mode 100644 index 0000000000000..a2ee326b500d0 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMDownload} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMDownload { + static final WMDownload EMPTY_WM_DOWNLOAD = new WMDownload(); + private String command = ""; + private Data data = Data.EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java new file mode 100644 index 0000000000000..49c9e3ea8f1e7 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMOff} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMOff { + static final WMOff EMPTY_WM_OFF = new WMOff(); + private String command = ""; + private Data data = Data.EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java index 02e57b1cfb332..b8a2f1223649d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java @@ -12,10 +12,14 @@ */ package org.openhab.binding.lgthinq.lgservices.model.washer; +import static org.openhab.binding.lgthinq.lgservices.model.washer.ControlWifi.EMPTY_CONTROL_WIFI; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * The {@link WMSnapshot} * @@ -26,6 +30,17 @@ public class WMSnapshot implements Snapshot { private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; private String course = ""; + @JsonProperty("ControlWifi") + private ControlWifi controlWifi = EMPTY_CONTROL_WIFI; + + public void setControlWifi(ControlWifi controlWifi) { + this.controlWifi = controlWifi; + } + + public ControlWifi getControlWifi() { + return controlWifi; + } + @Override public DevicePowerState getPowerStatus() { return powerState; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java new file mode 100644 index 0000000000000..7470f40790204 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMStart} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMStart { + static final WMStart EMPTY_WM_START = new WMStart(); + private String command = ""; + private Data data = Data.EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java new file mode 100644 index 0000000000000..2712a745a6cf1 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMStop} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMStop { + static final WMStop EMPTY_WM_STOP = new WMStop(); + private String command = ""; + private Data data = Data.EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java new file mode 100644 index 0000000000000..e685a981fda8a --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import static org.openhab.binding.lgthinq.lgservices.model.washer.Data.EMPTY_DATA; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link WMWakeup} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WMWakeup { + static final WMWakeup EMPTY_WM_WAKEUP = new WMWakeup(); + private String command = ""; + private Data data = EMPTY_DATA; + + public void setCommand(String command) { + this.command = command; + } + + public String getCommand() { + return command; + } + + public void setData(Data data) { + this.data = data; + } + + public Data getData() { + return data; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java new file mode 100644 index 0000000000000..ce14c4cb29243 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link WasherDryer} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class WasherDryer { + static final WasherDryer EMPTY_WASHER_DRYER = new WasherDryer(); + @JsonProperty("controlDataType") + private String controlDataType = ""; + @JsonProperty("controlDataValueLength") + private int controlDataValueLength; + + public void setControlDataType(String controlDataType) { + this.controlDataType = controlDataType; + } + + public String getControlDataType() { + return controlDataType; + } + + public void setControlDataValueLength(int controlDataValueLength) { + this.controlDataValueLength = controlDataValueLength; + } + + public int getControlDataValueLength() { + return controlDataValueLength; + } +} From 2746e371c57fe193dbc968bdf4d41d9a2d372c83 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 8 Feb 2022 23:08:56 -0300 Subject: [PATCH 073/130] [lgthinq][Feat] Mapped Power and State channels (read only) Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 28 +-- .../internal/LGThinqBindingConstants.java | 17 +- .../lgthinq/internal/LGThinqDeviceThing.java | 5 +- .../internal/LGThinqWasherHandler.java | 32 ++- .../LGThinqApiClientServiceImpl.java | 3 +- .../lgservices/model/SnapshotFactory.java | 13 +- .../lgservices/model/washer/ControlWifi.java | 82 ------- .../lgthinq/lgservices/model/washer/Data.java | 39 --- .../lgservices/model/washer/WMDownload.java | 43 ---- .../lgservices/model/washer/WMOff.java | 43 ---- .../lgservices/model/washer/WMStart.java | 43 ---- .../lgservices/model/washer/WMStop.java | 43 ---- .../lgservices/model/washer/WMWakeup.java | 45 ---- .../lgservices/model/washer/WasherDryer.java | 47 ---- ...Snapshot.java => WasherDryerSnapshot.java} | 56 +++-- .../main/resources/OH-INF/thing/washer.xml | 1 + .../src/test/resources/wm-data-result.json | 227 +++++++++--------- 17 files changed, 199 insertions(+), 568 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/{WMSnapshot.java => WasherDryerSnapshot.java} (51%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 6dd0f08dc19cb..43d9a8bc0b245 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -27,7 +27,8 @@ import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; @@ -150,23 +151,14 @@ private void updateThingStateFromLG() { } return; } - if (shot.getOperationMode() != null) { - updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); - } - if (shot.getPowerStatus() != null) { - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); - // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as - // soon as LG WebOs do) - } - if (shot.getAcFanSpeed() != null) { - updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(shot.getAirWindStrength())); - } - if (shot.getCurrentTemperature() != null) { - updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(shot.getCurrentTemperature())); - } - if (shot.getTargetTemperature() != null) { - updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); - } + + updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as + // soon as LG WebOs do) + updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(shot.getAirWindStrength())); + updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(shot.getCurrentTemperature())); + updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); updateStatus(ThingStatus.ONLINE); } catch (Exception e) { logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 319e29ce5c959..d14ed9fa93d6c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -111,7 +111,7 @@ public class LGThinqBindingConstants { // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; - public static final String CHANNEL_POWER_ID = "power"; + public static final String CHANNEL_POWER_ID = "startThingStatePolling"; public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; @@ -135,4 +135,19 @@ public class LGThinqBindingConstants { Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); + + // ====================== WASHING MACHINE CONSTANTS ============================= + public static final String WM_POWER_OFF_VALUE = "POWEROFF"; + public static final String WM_SNAPSHOT_WASHER_DRYER_NODE = "washerDryer"; + public static final String WM_CHANNEL_STATE_ID = "state"; + public static final Map CAP_WP_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), + Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), + Map.entry("@WM_STATE_RESERVE_W", "Reverse"), Map.entry("@WM_STATE_DETECTING_W", "Detecting"), + Map.entry("@WM_STATE_RUNNING_W", "Running"), Map.entry("@WM_STATE_RINSING_W", "Rinsing"), + Map.entry("@WM_STATE_SPINNING_W", "Spinning"), Map.entry("@WM_STATE_COOLDOWN_W", "Cool Down"), + Map.entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), Map.entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), + Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_END_W", "End"), + Map.entry("@WM_STATE_DRYING_W", "Drying"), Map.entry("@WM_STATE_DEMO_W", "Demonstration"), + Map.entry("@WM_STATE_ERROR_W", "Error")); + // ============================================================================== } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java index a049d5cce642e..9b9dde78bc7df 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java @@ -52,7 +52,7 @@ public LGThinqDeviceThing(Thing thing) { public abstract void updateChannelDynStateDescription() throws LGThinqApiException; - public abstract T getCapabilities() throws LGThinqApiException; + public abstract Capability getCapabilities() throws LGThinqApiException; protected abstract Logger getLogger(); @@ -68,7 +68,8 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { updateChannelDynStateDescription(); } catch (LGThinqApiException e) { getLogger().error( - "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values."); + "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values.", + e); } if (bridge != null) { LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java index 4c39ae758902d..3c0e44d48c8f9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -14,9 +14,7 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; -import java.util.Collection; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNull; @@ -32,12 +30,14 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.washer.WMCapability; -import org.openhab.binding.lgthinq.lgservices.model.washer.WMSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; import org.openhab.core.thing.binding.ThingHandlerService; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; +import org.openhab.core.types.StateOption; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,6 +53,7 @@ public class LGThinqWasherHandler extends LGThinqDeviceThing { private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; @Nullable private WMCapability wmCapability; + private final ChannelUID stateChannelUUID; private final String lgPlatfomType; private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); @NonNullByDefault @@ -80,6 +81,7 @@ public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvide lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() : LGThinqApiV2ClientServiceImpl.getInstance(); + stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); } static class AsyncCommandParams { @@ -130,7 +132,7 @@ protected void startThingStatePolling() { private void updateThingStateFromLG() { try { - WMSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); + WasherDryerSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); if (shot == null) { // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. return; @@ -145,6 +147,7 @@ private void updateThingStateFromLG() { } updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); updateStatus(ThingStatus.ONLINE); } catch (LGThinqException e) { @@ -185,7 +188,13 @@ private String emptyIfNull(@Nullable String value) { @Override public void updateChannelDynStateDescription() throws LGThinqApiException { - // not dynamic state channel in this device + WMCapability wmCap = getCapabilities(); + if (isLinked(stateChannelUUID)) { + List options = new ArrayList<>(); + // invert key/value + wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); + stateDescriptionProvider.setStateOptions(stateChannelUUID, options); + } } @Override @@ -203,10 +212,10 @@ protected Logger getLogger() { } @Nullable - private WMSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { + private WasherDryerSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (WMSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); + return (WasherDryerSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { @@ -215,7 +224,7 @@ private WMSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx } } catch (LGThinqDeviceV1OfflineException e) { forceStopDeviceV1Monitor(deviceId); - WMSnapshot shot = new WMSnapshot(); + WasherDryerSnapshot shot = new WasherDryerSnapshot(); shot.setOnline(false); return shot; } catch (Exception e) { @@ -223,11 +232,12 @@ private WMSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); } int retries = 10; - WMSnapshot shot; + WasherDryerSnapshot shot; while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = (WMSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + shot = (WasherDryerSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, + monitorWorkId); if (shot != null) { return shot; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index be5ce38cecfff..d87983e2c686e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -33,7 +33,6 @@ import org.openhab.binding.lgthinq.lgservices.model.Capability; import org.openhab.binding.lgthinq.lgservices.model.CapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -208,7 +207,7 @@ public Capability getCapability(String deviceId, String uri, boolean forceRecrea File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { }); - return CapabilityFactory.getInstance().create(mapper, ACCapability.class); + return CapabilityFactory.getInstance().create(mapper); } catch (IOException e) { throw new LGThinqApiException("Error reading IO interface", e); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 5898ba88318e7..8e7205063f465 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,6 +12,8 @@ */ package org.openhab.binding.lgthinq.lgservices.model; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; + import java.util.Map; import java.util.Objects; @@ -19,7 +21,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; -import org.openhab.binding.lgthinq.lgservices.model.washer.WMSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; import com.fasterxml.jackson.databind.ObjectMapper; @@ -51,10 +53,10 @@ public Snapshot create(Map deviceSettings) throws LGThinqApiExce case AIR_CONDITIONER: switch (version) { case V1_0: { - return objectMapper.convertValue(deviceSettings, ACSnapshotV2.class); + return objectMapper.convertValue(snapMap, ACSnapshotV2.class); } case V2_0: { - return objectMapper.convertValue(deviceSettings, ACSnapshotV1.class); + return objectMapper.convertValue(snapMap, ACSnapshotV1.class); } } case WASHING_MACHINE: @@ -63,7 +65,10 @@ public Snapshot create(Map deviceSettings) throws LGThinqApiExce throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); } case V2_0: { - return objectMapper.convertValue(deviceSettings, WMSnapshot.class); + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), + "washerDryer node must be present in the snapshot"); + return objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java deleted file mode 100644 index ebecc18eb3842..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/ControlWifi.java +++ /dev/null @@ -1,82 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import static org.openhab.binding.lgthinq.lgservices.model.washer.WMDownload.EMPTY_WM_DOWNLOAD; -import static org.openhab.binding.lgthinq.lgservices.model.washer.WMStart.EMPTY_WM_START; -import static org.openhab.binding.lgthinq.lgservices.model.washer.WMStop.EMPTY_WM_STOP; -import static org.openhab.binding.lgthinq.lgservices.model.washer.WMWakeup.EMPTY_WM_WAKEUP; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link ControlWifi} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ControlWifi { - static final ControlWifi EMPTY_CONTROL_WIFI = new ControlWifi(); - @JsonProperty("WMStart") - private WMStart wmStart = EMPTY_WM_START; - @JsonProperty("WMDownload") - private WMDownload wmDownload = EMPTY_WM_DOWNLOAD; - @JsonProperty("WMOff") - private WMOff wmOff = WMOff.EMPTY_WM_OFF; - @JsonProperty("WMStop") - private WMStop wmStop = EMPTY_WM_STOP; - @JsonProperty("WMWakeup") - private WMWakeup wmWakeup = EMPTY_WM_WAKEUP; - - public void setWmStart(WMStart wmStart) { - this.wmStart = wmStart; - } - - public WMStart getWmStart() { - return wmStart; - } - - public void setWmDownload(WMDownload wmDownload) { - this.wmDownload = wmDownload; - } - - public WMDownload getWmDownload() { - return wmDownload; - } - - public void setWmOff(WMOff wmOff) { - this.wmOff = wmOff; - } - - public WMOff getWmOff() { - return wmOff; - } - - public void setWmStop(WMStop wmStop) { - this.wmStop = wmStop; - } - - public WMStop getWmStop() { - return wmStop; - } - - public void setWmWakeup(WMWakeup wmWakeup) { - this.wmWakeup = wmWakeup; - } - - public WMWakeup getWmWakeup() { - return wmWakeup; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java deleted file mode 100644 index 4ef5c57e9ae90..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/Data.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import static org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryer.EMPTY_WASHER_DRYER; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link Data} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class Data { - public static final Data EMPTY_DATA = new Data(); - @JsonProperty("washerDryer") - private WasherDryer washerdryer = EMPTY_WASHER_DRYER; - - public void setWasherDryer(WasherDryer washerdryer) { - this.washerdryer = washerdryer; - } - - public WasherDryer getWasherDryer() { - return washerdryer; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java deleted file mode 100644 index a2ee326b500d0..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMDownload.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMDownload} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMDownload { - static final WMDownload EMPTY_WM_DOWNLOAD = new WMDownload(); - private String command = ""; - private Data data = Data.EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java deleted file mode 100644 index 49c9e3ea8f1e7..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMOff.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMOff} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMOff { - static final WMOff EMPTY_WM_OFF = new WMOff(); - private String command = ""; - private Data data = Data.EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java deleted file mode 100644 index 7470f40790204..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStart.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMStart} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMStart { - static final WMStart EMPTY_WM_START = new WMStart(); - private String command = ""; - private Data data = Data.EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java deleted file mode 100644 index 2712a745a6cf1..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMStop.java +++ /dev/null @@ -1,43 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMStop} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMStop { - static final WMStop EMPTY_WM_STOP = new WMStop(); - private String command = ""; - private Data data = Data.EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java deleted file mode 100644 index e685a981fda8a..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMWakeup.java +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import static org.openhab.binding.lgthinq.lgservices.model.washer.Data.EMPTY_DATA; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link WMWakeup} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WMWakeup { - static final WMWakeup EMPTY_WM_WAKEUP = new WMWakeup(); - private String command = ""; - private Data data = EMPTY_DATA; - - public void setCommand(String command) { - this.command = command; - } - - public String getCommand() { - return command; - } - - public void setData(Data data) { - this.data = data; - } - - public Data getData() { - return data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java deleted file mode 100644 index ce14c4cb29243..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryer.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link WasherDryer} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WasherDryer { - static final WasherDryer EMPTY_WASHER_DRYER = new WasherDryer(); - @JsonProperty("controlDataType") - private String controlDataType = ""; - @JsonProperty("controlDataValueLength") - private int controlDataValueLength; - - public void setControlDataType(String controlDataType) { - this.controlDataType = controlDataType; - } - - public String getControlDataType() { - return controlDataType; - } - - public void setControlDataValueLength(int controlDataValueLength) { - this.controlDataValueLength = controlDataValueLength; - } - - public int getControlDataValueLength() { - return controlDataValueLength; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java similarity index 51% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java index b8a2f1223649d..762db7d85a11b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java @@ -12,34 +12,30 @@ */ package org.openhab.binding.lgthinq.lgservices.model.washer; -import static org.openhab.binding.lgthinq.lgservices.model.washer.ControlWifi.EMPTY_CONTROL_WIFI; +import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_POWER_OFF_VALUE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** - * The {@link WMSnapshot} - * + * The {@link WasherDryerSnapshot} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->washerDryer, but this POJO expects + * to map field below washerDryer + * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class WMSnapshot implements Snapshot { +@JsonIgnoreProperties(ignoreUnknown = true) +public class WasherDryerSnapshot implements Snapshot { private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; - private String course = ""; - - @JsonProperty("ControlWifi") - private ControlWifi controlWifi = EMPTY_CONTROL_WIFI; - - public void setControlWifi(ControlWifi controlWifi) { - this.controlWifi = controlWifi; - } - - public ControlWifi getControlWifi() { - return controlWifi; - } + private String state = ""; + private boolean online; @Override public DevicePowerState getPowerStatus() { @@ -48,23 +44,31 @@ public DevicePowerState getPowerStatus() { @Override public void setPowerStatus(DevicePowerState value) { - this.powerState = value; - } - - public String getCourse() { - return course; - } - - public void setCourse(String course) { - this.course = course; + throw new IllegalArgumentException("This method must not be accessed."); } @Override public boolean isOnline() { - return false; + return online; } @Override public void setOnline(boolean online) { + this.online = online; + } + + @JsonProperty("state") + @JsonGetter + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + if (state.equals(WM_POWER_OFF_VALUE)) { + powerState = DevicePowerState.DV_POWER_OFF; + } else { + powerState = DevicePowerState.DV_POWER_ON; + } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index a6ed2656f36ac..1cbd8f9bf783e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -15,6 +15,7 @@ + diff --git a/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json b/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json index a8296ae9b1156..17cb8f393da3c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json +++ b/bundles/org.openhab.binding.lgthinq/src/test/resources/wm-data-result.json @@ -1,129 +1,118 @@ { "resultCode": "0000", "result": { - "appType": "NUTS", - "modelCountryCode": "WW", - "countryCode": "BR", - "modelName": "RAC_056905_WW", - "deviceType": 201, - "deviceCode": "WM01", - "alias": "Kitchen", - "deviceId": "washer-0001-5772", - "fwVer": "", - "imageFileName": "wm_img.png", - "imageUrl": "https://objectcontent.lgthinq.com/9e0177e7-0956-4284-916d-61e213f1f5ab?hdnts=exp=1702098013~hmac=e14659e3ccf369930e4cc92ca2511203037d3c258b75c627af013e4656fc49d6", - "smallImageUrl": "https://objectcontent.lgthinq.com/c7e214d7-99f0-4641-b954-f238f9d55b64?hdnts=exp=1701658820~hmac=646137b7b590866c772649d03114184628b1477eb974ca8507c0dc4ede6807c5", - "ssid": "my_ssid", - "softapId": "", - "softapPass": "", - "macAddress": "", - "networkType": "02", - "timezoneCode": "America/Sao_Paulo", - "timezoneCodeAlias": "Brazil/Sao Paulo", - "utcOffset": -3, - "utcOffsetDisplay": "-03:00", - "dstOffset": -2, - "dstOffsetDisplay": "-02:00", - "curOffset": -2, - "curOffsetDisplay": "-02:00", - "sdsGuide": "{\"deviceCode\":\"WM01\"}", - "newRegYn": "N", - "remoteControlType": "", - "userNo": "XXXXXXXXX", - "tftYn": "N", - "modelJsonVer": 12.11, - "modelJsonUri": "https://objectcontent.lgthinq.com/544a6f1c-1b10-4244-a584-d103c8519910?hdnts=exp=1706145774~hmac=bf5e96e83ffdac724b7159b8ed3d7c52f5b9a2a0ef8b67cdbbcf96b1113bd25f", - "appModuleVer": 12.49, - "appModuleUri": "https://objectcontent.lgthinq.com/19b24102-f2c5-4ac4-97aa-bb1abe5b4c2e?hdnts=exp=1704438018~hmac=050615be890fedc1669a632310dc837b9c6c6ebfd428ed202e2b4b19c2e05155", - "appRestartYn": "Y", - "appModuleSize": 6082481, - "langPackProductTypeVer": 59.9, - "langPackProductTypeUri": "https://objectcontent.lgthinq.com/5642d2e1-cb10-41b4-8e99-f1831f20afe6?hdnts=exp=1705462185~hmac=68fe0ae9ef3fd02355c87668cff6d36c2ad8c312144d7406b9c040be992a15ea", - "deviceState": "E", - "snapshot": { - "airState.windStrength": 8.0, - "airState.wMode.lowHeating": 0.0, - "airState.diagCode": 0.0, - "airState.lightingState.displayControl": 1.0, - "airState.wDir.hStep": 0.0, - "mid": 8.4615358E7, - "airState.energy.onCurrent": 476.0, - "airState.wMode.airClean": 0.0, - "airState.quality.sensorMon": 0.0, - "airState.energy.accumulatedTime": 0.0, - "airState.miscFuncState.antiBugs": 0.0, - "airState.tempState.target": 18.0, - "airState.operation": 1.0, - "airState.wMode.jet": 0.0, - "airState.wDir.vStep": 2.0, - "timestamp": 1.643248573766E12, - "airState.powerSave.basic": 0.0, - "airState.quality.PM10": 0.0, - "static": { - "deviceType": "401", - "countryCode": "BR" + "appType":"NUTS", + "modelCountryCode":"WW", + "countryCode":"DK", + "modelName":"F_R7_Y___W.A__QEUK", + "deviceType":201, + "deviceCode":"LA02", + "alias":"Frontbetjent vaskemaskine", + "deviceId":"592bd2a4-d3e7-16e9-a69f-44cb8b2e0c43", + "fwVer":"", + "imageFileName":"home_appliances_img_wmdrum.png", + "ssid":"Kepler", + "softapId":"", + "softapPass":"", + "macAddress":"", + "networkType":"02", + "timezoneCode":"Europe/Copenhagen", + "timezoneCodeAlias":"Europe/Copenhagen", + "utcOffset":1, + "utcOffsetDisplay":"+01:00", + "dstOffset":2, + "dstOffsetDisplay":"+02:00", + "curOffset":1, + "curOffsetDisplay":"+01:00", + "sdsGuide":"{\"deviceCode\":\"LA02\"}", + "newRegYn":"N", + "remoteControlType":"", + "userNo":"DK2202075642801", + "tftYn":"N", + "deviceState":"E", + "snapshot":{ + "washerDryer":{ + "initialBit":"INITIAL_BIT_OFF", + "standby":"STANDBY_OFF", + "courseFL24inchBaseTitan":"MIXEDFABRIC", + "initialTimeMinute":24.0, + "preState":"SPINNING", + "error":"ERROR_NO", + "dryLevel":"NOT_SELECTED", + "creaseCare":"CREASECARE_OFF", + "remainTimeHour":0.0, + "smartCourseFL24inchBaseTitan":"NOT_SELECTED", + "preWash":"PREWASH_OFF", + "steam":"STEAM_OFF", + "state":"SPINNING", + "rinse":"NO_RINSE", + "wrinkleCare":"WRINKLECARE_OFF", + "loadItemWasher":"LOADITEM_OFF", + "temp":"NO_TEMP", + "doorLock":"DOOR_LOCK_ON", + "reserveTimeMinute":0.0, + "AIDDLed":"AIDDLed_OFF", + "TCLCount":33.0, + "downloadedCourseFL24inchBaseTitan":"RINSESPIN", + "medicRinse":"MEDICRINSE_OFF", + "turboWash":"TURBOWASH_OFF", + "ecoHybrid":"ECOHYBRID_OFF", + "remainTimeMinute":11.0, + "reserveTimeHour":0.0, + "steamSoftener":"STEAMSOFTENER_OFF", + "childLock":"CHILDLOCK_OFF", + "remoteStart":"REMOTE_START_ON", + "spin":"SPIN_1400", + "soilWash":"NO_SOILWASH", + "rinseSpin":"RINSE_SPIN_OFF", + "initialTimeHour":1.0 }, - "airState.quality.overall": 0.0, - "airState.tempState.current": 25.0, - "airState.miscFuncState.extraOp": 0.0, - "airState.energy.accumulated": 0.0, - "airState.reservation.sleepTime": 0.0, - "airState.miscFuncState.autoDry": 0.0, - "airState.reservation.targetTimeToStart": 0.0, - "meta": { - "allDeviceInfoUpdate": false, - "messageId": "fVz2AE-2SC-rf3GnerGdeQ" + "mid":8.4022883E7, + "online":true, + "static":{ + "deviceType":"201", + "countryCode":"DK" }, - "airState.quality.PM1": 0.0, - "airState.wMode.smartCare": 0.0, - "airState.quality.PM2": 0.0, - "online": true, - "airState.opMode": 0.0, - "airState.reservation.targetTimeToStop": 0.0, - "airState.filterMngStates.maxTime": 0.0, - "airState.filterMngStates.useTime": 0.0 + "meta":{ + "allDeviceInfoUpdate":false, + "messageId":"TSmRTV6yTUq2obot8_Q9Qg" + }, + "timestamp":1.644358361572E12 }, - "online": true, - "platformType": "thinq2", - "area": 45883, - "regDt": 2.0220111184827E13, - "blackboxYn": "Y", - "modelProtocol": "STANDARD", - "order": 0, - "drServiceYn": "N", - "fwInfoList": [ + "online":true, + "platformType":"thinq2", + "area":125955, + "regDt":2.0220208013031E13, + "blackboxYn":"Y", + "modelProtocol":"courseFL24inchBaseTitan", + "receipeVersion":0, + "activeSaving":"OFF", + "smartCareV2":"OFF", + "order":0, + "nlpAlias":"none", + "drServiceYn":"N", + "fwInfoList":[ + { + "checksum":"016771B3", + "partNumber":"SAA41059310", + "order":2.0 + }, { - "checksum": "00004105", - "order": 1.0, - "partNumber": "SAA40128563" + "checksum":"000075A3", + "partNumber":"SAA41059211", + "order":1.0 } ], - "modemInfo": { - "appVersion": "clip_hna_v1.9.116", - "modelName": "RAC_056905_WW", - "modemType": "QCOM_QCA4010", - "ruleEngine": "y" - }, - "guideTypeYn": "Y", - "guideType": "RAC_TYPE1", - "regDtUtc": "20220111204827", - "regIndex": 0, - "groupableYn": "Y", - "controllableYn": "Y", - "combinedProductYn": "N", - "masterYn": "Y", - "pccModelYn": "N", - "sdsPid": { - "sds4": "", - "sds3": "", - "sds2": "", - "sds1": "" - }, - "autoOrderYn": "N", - "initDevice": false, - "existsEntryPopup": "N", - "tclcount": 0 - }, - "online": true, - "platformType": "thinq2" + "regDtUtc":"20220207233031", + "regIndex":0, + "groupableYn":"N", + "controllableYn":"N", + "combinedProductYn":"N", + "masterYn":"Y", + "controlGuideType":"TYPE4", + "initDevice":false, + "upgradableYn":"N", + "autoFwDownloadYn":"N", + "tclcount":0 + } } \ No newline at end of file From 2feb9e9c647b7e0973cc998a6639b85bafeb2fe0 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 08:52:35 -0300 Subject: [PATCH 074/130] [lgthinq][Feat] Added Course and SmartCourse channels. Error message handler Signed-off-by: nemerdaud --- .../internal/LGThinqBindingConstants.java | 35 +++++++++++++++ .../internal/LGThinqWasherHandler.java | 16 +++++++ .../LGThinqApiClientServiceImpl.java | 12 +++++ .../LGThinqApiV1ClientServiceImpl.java | 2 + .../LGThinqApiV2ClientServiceImpl.java | 1 + .../lgservices/model/washer/WMCapability.java | 10 +++++ .../model/washer/WasherDryerSnapshot.java | 26 ++++++++++- .../main/resources/OH-INF/thing/channels.xml | 44 +++++++++++++++++++ .../main/resources/OH-INF/thing/washer.xml | 2 + 9 files changed, 146 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index d14ed9fa93d6c..4073085beb132 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -108,6 +108,37 @@ public class LGThinqBindingConstants { public static final int SEARCH_TIME = 20; // delay between each devices's scan for state changes (in seconds) public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 30; + + public static final Map ERROR_CODE_RESPONSE = Map.ofEntries(Map.entry("0000", "OK"), + Map.entry("0001", "PARTIAL_OK"), Map.entry("0103", "OPERATION_IN_PROGRESS_DEVICE"), + Map.entry("0007", "PORTAL_INTERWORKING_ERROR"), Map.entry("0104", "PROCESSING_REFRIGERATOR"), + Map.entry("0111", "RESPONSE_DELAY_DEVICE"), Map.entry("8107", "SERVICE_SERVER_ERROR"), + Map.entry("8102", "SSP_ERROR"), Map.entry("9020", "TIME_OUT"), Map.entry("8104", "WRONG_XML_OR_URI"), + Map.entry("9000", "AWS_IOT_ERROR"), Map.entry("8105", "AWS_S3_ERROR"), Map.entry("8106", "AWS_SQS_ERROR"), + Map.entry("9002", "BASE64_DECODING_ERROR"), Map.entry("9001", "BASE64_ENCODING_ERROR"), + Map.entry("8103", "CLIP_ERROR"), Map.entry("0105", "CONTROL_ERROR_REFRIGERATOR"), + Map.entry("9003", "CREATE_SESSION_FAIL"), Map.entry("9004", "DB_PROCESSING_FAIL"), + Map.entry("8101", "DM_ERROR"), Map.entry("0013", "DUPLICATED_ALIAS"), Map.entry("0008", "DUPLICATED_DATA"), + Map.entry("0004", "DUPLICATED_LOGIN"), Map.entry("0102", "EMP_AUTHENTICATION_FAILED"), + Map.entry("8900", "ETC_COMMUNICATION_ERROR"), Map.entry("9999", "ETC_ERROR"), + Map.entry("0112", "EXCEEDING_LIMIT"), Map.entry("0119", "EXPIRED_CUSTOMER_NUMBER"), + Map.entry("9005", "EXPIRES_SESSION_BY_WITHDRAWAL"), Map.entry("0100", "FAIL"), + Map.entry("8001", "INACTIVE_API"), Map.entry("0107", "INSUFFICIENT_STORAGE_SPACE"), + Map.entry("9010", "INVAILD_CSR"), Map.entry("0002", "INVALID_BODY"), + Map.entry("0118", "INVALID_CUSTOMER_NUMBER"), Map.entry("0003", "INVALID_HEADER"), + Map.entry("0301", "INVALID_PUSH_TOKEN"), Map.entry("0116", "INVALID_REQUEST_DATA_FOR_DIAGNOSIS"), + Map.entry("0014", "MISMATCH_DEVICE_GROUP"), Map.entry("0114", "MISMATCH_LOGIN_SESSION"), + Map.entry("0006", "MISMATCH_NONCE"), Map.entry("0115", "MISMATCH_REGISTRED_DEVICE"), + Map.entry("0110", "NOT_AGREED_TERMS"), Map.entry("0106", "NOT_CONNECTED_DEVICE"), + Map.entry("0120", "NOT_CONTRACT_CUSTOMER_NUMBER"), Map.entry("0010", "NOT_EXIST_DATA"), + Map.entry("0009", "NOT_EXIST_DEVICE"), Map.entry("0117", "NOT_EXIST_MODEL_JSON"), + Map.entry("0121", "NOT_REGISTERED_SMART_CARE"), Map.entry("0012", "NOT_SUPPORTED_COMMAND"), + Map.entry("8000", "NOT_SUPPORTED_COUNTRY"), Map.entry("0005", "NOT_SUPPORTED_SERVICE"), + Map.entry("0109", "NO_INFORMATION_DR"), Map.entry("0108", "NO_INFORMATION_SLEEP_MODE"), + Map.entry("0011", "NO_PERMISSION"), Map.entry("0113", "NO_PERMISION_MODIFY_RECIPE"), + Map.entry("0101", "NO_REGISTERED_DEVICE"), Map.entry("9006", "NO_USER_INFORMATION")); + + // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; @@ -140,6 +171,9 @@ public class LGThinqBindingConstants { public static final String WM_POWER_OFF_VALUE = "POWEROFF"; public static final String WM_SNAPSHOT_WASHER_DRYER_NODE = "washerDryer"; public static final String WM_CHANNEL_STATE_ID = "state"; + public static final String WM_CHANNEL_COURSE_ID = "course"; + public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; + public static final Map CAP_WP_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), Map.entry("@WM_STATE_RESERVE_W", "Reverse"), Map.entry("@WM_STATE_DETECTING_W", "Detecting"), @@ -149,5 +183,6 @@ public class LGThinqBindingConstants { Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_END_W", "End"), Map.entry("@WM_STATE_DRYING_W", "Drying"), Map.entry("@WM_STATE_DEMO_W", "Demonstration"), Map.entry("@WM_STATE_ERROR_W", "Error")); + // ============================================================================== } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java index 3c0e44d48c8f9..d3f848e7f15b7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -54,6 +54,8 @@ public class LGThinqWasherHandler extends LGThinqDeviceThing { @Nullable private WMCapability wmCapability; private final ChannelUID stateChannelUUID; + private final ChannelUID courseChannelUUID; + private final ChannelUID smartCourseChannelUUID; private final String lgPlatfomType; private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); @NonNullByDefault @@ -82,6 +84,8 @@ public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvide lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() : LGThinqApiV2ClientServiceImpl.getInstance(); stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); + courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); + smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); } static class AsyncCommandParams { @@ -148,6 +152,8 @@ private void updateThingStateFromLG() { updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); + updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); + updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); updateStatus(ThingStatus.ONLINE); } catch (LGThinqException e) { @@ -195,6 +201,16 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); stateDescriptionProvider.setStateOptions(stateChannelUUID, options); } + if (isLinked(courseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(courseChannelUUID, options); + } + if (isLinked(smartCourseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); + } } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index d87983e2c686e..fd27b44aab2ce 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -25,6 +25,7 @@ import javax.ws.rs.core.UriBuilder; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; @@ -151,6 +152,7 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (!"0000".equals(deviceSettings.get("resultCode"))) { + logErrorResultCodeMessage((String) deviceSettings.get("resultCode")); throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", deviceSettings.get("resultCode"))); @@ -177,6 +179,7 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (!"0000".equals(devicesResult.get("resultCode"))) { + logErrorResultCodeMessage((String) devicesResult.get("resultCode")); throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", devicesResult.get("resultCode"))); @@ -194,6 +197,15 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG return devices; } + protected static void logErrorResultCodeMessage(@Nullable String resultCode) { + if (resultCode == null) { + return; + } + String errMessage = ERROR_CODE_RESPONSE.get(resultCode.trim()); + logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", resultCode, + errMessage == null ? "UNKNOW ERROR MESSAGE" : errMessage); + } + /** * Get capability em registry/cache on file for next consult * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java index e98662a26e729..8fbb559252428 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java @@ -189,6 +189,7 @@ private Map handleV1GenericErrorResult(@Nullable RestResult resp throw new LGThinqApiException(String.format( "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); } else if (!"0000".equals(envelope.get("returnCd"))) { + logErrorResultCodeMessage((String) envelope.get("returnCd")); if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { // Disconnected Device throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); @@ -242,6 +243,7 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) && ((Map) envelop.get("workList")).get("returnData") != null) { Map workList = ((Map) envelop.get("workList")); if (!"0000".equals(workList.get("returnCode"))) { + logErrorResultCodeMessage((String) workList.get("resultCode")); LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); logger.warn("{}", e.getMessage()); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java index 71f428bb3ca1d..6014eb51f3903 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java @@ -201,6 +201,7 @@ private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThin metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { }); if (!"0000".equals(metaResult.get("resultCode"))) { + logErrorResultCodeMessage((String) metaResult.get("resultCode")); throw new LGThinqApiException( String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", metaResult.get("resultCode"))); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java index 42d97011d10fd..4294f98cc1b26 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java @@ -56,14 +56,24 @@ private static class MonitoringValue { private MonitoringValue monitoringValue = new MonitoringValue(); private Map courses = new LinkedHashMap(); + private Map smartCourses = new LinkedHashMap(); + public Map getCourses() { return courses; } + public Map getSmartCourses() { + return smartCourses; + } + public void addCourse(String courseLabel, String courseName) { courses.put(courseLabel, courseName); } + public void addSmartCourse(String courseLabel, String courseName) { + smartCourses.put(courseLabel, courseName); + } + public Map getState() { return monitoringValue.state; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java index 762db7d85a11b..00b36df380e9f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java @@ -18,7 +18,7 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import com.fasterxml.jackson.annotation.JsonGetter; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -36,6 +36,18 @@ public class WasherDryerSnapshot implements Snapshot { private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; private String state = ""; private boolean online; + private String course = ""; + private String smartCourse = ""; + + @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) + @JsonProperty("courseFL24inchBaseTitan") + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } @Override public DevicePowerState getPowerStatus() { @@ -58,11 +70,21 @@ public void setOnline(boolean online) { } @JsonProperty("state") - @JsonGetter + @JsonAlias({ "state", "State" }) public String getState() { return state; } + @JsonProperty("smartCourseFL24inchBaseTitan") + @JsonAlias({ "smartCourseFL24inchBaseTitan", "SmartCourse" }) + public String getSmartCourse() { + return smartCourse; + } + + public void setSmartCourse(String smartCourse) { + this.smartCourse = smartCourse; + } + public void setState(String state) { this.state = state; if (state.equals(WM_POWER_OFF_VALUE)) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index fe41577a6fd60..9c31fdefa1dee 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -464,4 +464,48 @@ + + + String + + Washer Course + + + + + + + + + + + + + + + + + + + + String + + Washer Smart Course + + + + + + + + + + + + + + + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 1cbd8f9bf783e..44818829f1fad 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -16,6 +16,8 @@ + + From f94a2d02f4e42482834dba188397789a3dfc5b3a Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 08:54:24 -0300 Subject: [PATCH 075/130] [lgthinq][Feat] Setting channels to readonly Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../src/main/resources/OH-INF/thing/channels.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 9c31fdefa1dee..ddde50496ffdb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -469,7 +469,7 @@ String Washer Course - + @@ -491,7 +491,7 @@ String Washer Smart Course - + From ef41dc2ba3ad0cba644c496404aa03e376193ca8 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 14:35:11 -0300 Subject: [PATCH 076/130] [lgthinq][Feat] Adding support for Temperature Level and Dook Lock channels Signed-off-by: nemerdaud --- .../internal/LGThinqBindingConstants.java | 4 +++- .../internal/LGThinqWasherHandler.java | 3 ++- .../model/washer/WasherDryerSnapshot.java | 20 +++++++++++++++++++ .../main/resources/OH-INF/thing/washer.xml | 2 ++ 4 files changed, 27 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 4073085beb132..284d0b26edaa5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -107,7 +107,7 @@ public class LGThinqBindingConstants { public static final int SEARCH_TIME = 20; // delay between each devices's scan for state changes (in seconds) - public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 30; + public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 10; public static final Map ERROR_CODE_RESPONSE = Map.ofEntries(Map.entry("0000", "OK"), Map.entry("0001", "PARTIAL_OK"), Map.entry("0103", "OPERATION_IN_PROGRESS_DEVICE"), @@ -173,6 +173,8 @@ public class LGThinqBindingConstants { public static final String WM_CHANNEL_STATE_ID = "state"; public static final String WM_CHANNEL_COURSE_ID = "course"; public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; + public static final String WM_CHANNEL_TEMP_LEVEL_ID = "temperature-level"; + public static final String WM_CHANNEL_DOOR_LOCK_ID = "door-lock"; public static final Map CAP_WP_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java index d3f848e7f15b7..0269c63f015eb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -154,7 +154,8 @@ private void updateThingStateFromLG() { updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); - + updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); + updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); updateStatus(ThingStatus.ONLINE); } catch (LGThinqException e) { logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java index 00b36df380e9f..53943e435cb9f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java @@ -38,6 +38,8 @@ public class WasherDryerSnapshot implements Snapshot { private boolean online; private String course = ""; private String smartCourse = ""; + private String temperatureLevel = ""; + private String doorLock = ""; @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) @JsonProperty("courseFL24inchBaseTitan") @@ -85,6 +87,24 @@ public void setSmartCourse(String smartCourse) { this.smartCourse = smartCourse; } + @JsonProperty("temp") + public String getTemperatureLevel() { + return temperatureLevel; + } + + public void setTemperatureLevel(String temperatureLevel) { + this.temperatureLevel = temperatureLevel; + } + + @JsonProperty("doorLock") + public String getDoorLock() { + return doorLock; + } + + public void setDoorLock(String doorLock) { + this.doorLock = doorLock; + } + public void setState(String state) { this.state = state; if (state.equals(WM_POWER_OFF_VALUE)) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 44818829f1fad..11abb169992ed 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -18,6 +18,8 @@ + + From 32c78f572afccafe91a6daa4334b7f8af98e29f2 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 19:17:27 -0300 Subject: [PATCH 077/130] [lgthinq][Fix] Fixing bug in mapping Power channel; Fix AC service monitoring after Snapshot refactory Signed-off-by: nemerdaud --- .../LGThinqAirConditionerHandler.java | 38 ++-- .../internal/LGThinqBindingConstants.java | 2 +- .../internal/LGThinqWasherHandler.java | 27 ++- .../handler/LGThinqBridgeHandler.java | 4 +- .../lgservices/LGThinqACApiClientService.java | 32 ++++ ...a => LGThinqACApiV1ClientServiceImpl.java} | 114 ++---------- ...a => LGThinqACApiV2ClientServiceImpl.java} | 75 ++------ .../lgservices/LGThinqApiClientService.java | 12 +- .../LGThinqApiClientServiceImpl.java | 173 +++++++++++++++++- .../lgservices/LGThinqWMApiClientService.java | 21 +++ .../LGThinqWMApiV2ClientServiceImpl.java | 39 ++++ .../lgservices/model/SnapshotFactory.java | 38 ++-- .../lgservices/model/ac/ACSnapshot.java | 14 +- .../lgservices/model/ac/ACSnapshotV1.java | 58 ------ .../lgservices/model/ac/ACSnapshotV2.java | 58 ------ 15 files changed, 359 insertions(+), 346 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/{LGThinqApiV1ClientServiceImpl.java => LGThinqACApiV1ClientServiceImpl.java} (55%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/{LGThinqApiV2ClientServiceImpl.java => LGThinqACApiV2ClientServiceImpl.java} (71%) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java index 43d9a8bc0b245..840c555188275 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java @@ -24,14 +24,14 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqACApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV1ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.OnOffType; @@ -61,7 +61,7 @@ public class LGThinqAirConditionerHandler extends LGThinqDeviceThing { private final String lgPlatfomType; private final Logger logger = LoggerFactory.getLogger(LGThinqAirConditionerHandler.class); @NonNullByDefault - private final LGThinqApiClientService lgThinqApiClientService; + private final LGThinqACApiClientService lgThinqACApiClientService; private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; // Bridges status that this thing must top scanning for state change private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, @@ -84,8 +84,9 @@ public LGThinqAirConditionerHandler(Thing thing, super(thing); this.stateDescriptionProvider = stateDescriptionProvider; lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() - : LGThinqApiV2ClientServiceImpl.getInstance(); + lgThinqACApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) + ? LGThinqACApiV1ClientServiceImpl.getInstance() + : LGThinqACApiV2ClientServiceImpl.getInstance(); opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); opModeFanSpeedUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); } @@ -185,7 +186,7 @@ private String getBridgeId() { private void forceStopDeviceV1Monitor(String deviceId) { try { monitorV1Began = false; - lgThinqApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + lgThinqACApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); } catch (Exception e) { logger.error("Error stopping LG Device monitor", e); } @@ -216,8 +217,8 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { @Override public ACCapability getCapabilities() throws LGThinqApiException { if (acCapability == null) { - acCapability = (ACCapability) lgThinqApiClientService.getCapability(getDeviceId(), getDeviceUriJsonConfig(), - false); + acCapability = (ACCapability) lgThinqACApiClientService.getCapability(getDeviceId(), + getDeviceUriJsonConfig(), false); } return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } @@ -231,16 +232,16 @@ protected Logger getLogger() { private ACSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (ACSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); + return (ACSnapshot) lgThinqACApiClientService.getDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { - monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorWorkId = lgThinqACApiClientService.startMonitor(getBridgeId(), getDeviceId()); monitorV1Began = true; } } catch (LGThinqDeviceV1OfflineException e) { forceStopDeviceV1Monitor(deviceId); - ACSnapshot shot = new ACSnapshotV1(); + ACSnapshot shot = new ACSnapshot(); shot.setOnline(false); return shot; } catch (Exception e) { @@ -252,7 +253,8 @@ private ACSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiEx while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = (ACSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId); + shot = (ACSnapshot) lgThinqACApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId, + DeviceTypes.AIR_CONDITIONER); if (shot != null) { return shot; } @@ -394,7 +396,7 @@ public void run() { switch (params.channelUID) { case CHANNEL_MOD_OP_ID: { if (params.command instanceof DecimalType) { - lgThinqApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), + lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), ((DecimalType) command).intValue()); } else { logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); @@ -403,7 +405,7 @@ public void run() { } case CHANNEL_FAN_SPEED_ID: { if (command instanceof DecimalType) { - lgThinqApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), + lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), ((DecimalType) command).intValue()); } else { logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); @@ -412,7 +414,7 @@ public void run() { } case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { - lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); } else { @@ -430,7 +432,7 @@ public void run() { logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); break; } - lgThinqApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), + lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), ACTargetTmp.statusOf(targetTemp)); break; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java index 284d0b26edaa5..31ea1454d986a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java @@ -142,7 +142,7 @@ public class LGThinqBindingConstants { // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op_mode"; public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; - public static final String CHANNEL_POWER_ID = "startThingStatePolling"; + public static final String CHANNEL_POWER_ID = "power"; public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java index 0269c63f015eb..94927b10d52e2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java @@ -24,10 +24,10 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.LGThinqWMApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinqWMApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.washer.WMCapability; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; @@ -59,7 +59,7 @@ public class LGThinqWasherHandler extends LGThinqDeviceThing { private final String lgPlatfomType; private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); @NonNullByDefault - private final LGThinqApiClientService lgThinqApiClientService; + private final LGThinqWMApiClientService lgThinqWMApiClientService; private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; // Bridges status that this thing must top scanning for state change private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, @@ -81,8 +81,7 @@ public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvide super(thing); this.stateDescriptionProvider = stateDescriptionProvider; lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgThinqApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) ? LGThinqApiV1ClientServiceImpl.getInstance() - : LGThinqApiV2ClientServiceImpl.getInstance(); + lgThinqWMApiClientService = LGThinqWMApiV2ClientServiceImpl.getInstance(); stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); @@ -182,7 +181,7 @@ private String getBridgeId() { private void forceStopDeviceV1Monitor(String deviceId) { try { monitorV1Began = false; - lgThinqApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); + lgThinqWMApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); } catch (Exception e) { logger.error("Error stopping LG Device monitor", e); } @@ -217,8 +216,8 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { @Override public WMCapability getCapabilities() throws LGThinqApiException { if (wmCapability == null) { - wmCapability = (WMCapability) lgThinqApiClientService.getCapability(getDeviceId(), getDeviceUriJsonConfig(), - false); + wmCapability = (WMCapability) lgThinqWMApiClientService.getCapability(getDeviceId(), + getDeviceUriJsonConfig(), false); } return Objects.requireNonNull(wmCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); } @@ -232,11 +231,11 @@ protected Logger getLogger() { private WasherDryerSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { // analise de platform version if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (WasherDryerSnapshot) lgThinqApiClientService.getDeviceData(getBridgeId(), getDeviceId()); + return (WasherDryerSnapshot) lgThinqWMApiClientService.getDeviceData(getBridgeId(), getDeviceId()); } else { try { if (!monitorV1Began) { - monitorWorkId = lgThinqApiClientService.startMonitor(getBridgeId(), getDeviceId()); + monitorWorkId = lgThinqWMApiClientService.startMonitor(getBridgeId(), getDeviceId()); monitorV1Began = true; } } catch (LGThinqDeviceV1OfflineException e) { @@ -253,8 +252,8 @@ private WasherDryerSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGT while (retries > 0) { // try to get monitoring data result 3 times. try { - shot = (WasherDryerSnapshot) lgThinqApiClientService.getMonitorData(getBridgeId(), deviceId, - monitorWorkId); + shot = (WasherDryerSnapshot) lgThinqWMApiClientService.getMonitorData(getBridgeId(), deviceId, + monitorWorkId, DeviceTypes.WASHING_MACHINE); if (shot != null) { return shot; } @@ -396,7 +395,7 @@ public void run() { switch (params.channelUID) { case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { - lgThinqApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + lgThinqWMApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); } else { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 19bcf9a8797ce..8125ed3c9346c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -31,8 +31,8 @@ import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiV1ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.core.status.ConfigStatusMessage; import org.openhab.core.thing.*; @@ -74,7 +74,7 @@ public class LGThinqBridgeHandler extends ConfigStatusBridgeHandler implements L public LGThinqBridgeHandler(Bridge bridge) { super(bridge); tokenManager = TokenManager.getInstance(); - lgApiClient = LGThinqApiV1ClientServiceImpl.getInstance(); + lgApiClient = LGThinqACApiV1ClientServiceImpl.getInstance(); lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java new file mode 100644 index 0000000000000..0d0acddfbc3da --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; + +/** + * The {@link LGThinqACApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public interface LGThinqACApiClientService extends LGThinqApiClientService { + void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; + + void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; + + void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) + throws LGThinqApiException; +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java similarity index 55% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java index 8fbb559252428..fbf90659c718e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java @@ -24,54 +24,37 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotFactory; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - /** - * The {@link LGThinqApiV1ClientServiceImpl} + * The {@link LGThinqACApiV1ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqApiV1ClientServiceImpl extends LGThinqApiClientServiceImpl { - private static final LGThinqApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinqApiV1ClientServiceImpl.class); - private final ObjectMapper objectMapper = new ObjectMapper(); - private final TokenManager tokenManager; +public class LGThinqACApiV1ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqACApiClientService { + private static final LGThinqACApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinqACApiV1ClientServiceImpl.class); static { - instance = new LGThinqApiV1ClientServiceImpl(); - } - - private LGThinqApiV1ClientServiceImpl() { - tokenManager = TokenManager.getInstance(); + instance = new LGThinqACApiV1ClientServiceImpl(); } - public static LGThinqApiClientService getInstance() { + public static LGThinqACApiClientService getInstance() { return instance; } - @Override - protected TokenManager getTokenManager() { - return tokenManager; - } - /** * Get snapshot data from the device. * It works only for API V2 device versions! @@ -86,7 +69,7 @@ public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String device throw new UnsupportedOperationException("Method not supported in V1 API device."); } - public RestResult sendControlCommands(String bridgeName, String deviceId, String keyName, int value) + private RestResult sendControlCommands(String bridgeName, String deviceId, String keyName, int value) throws Exception { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); @@ -146,81 +129,10 @@ public void changeTargetTemperature(String bridgeName, String deviceId, ACTarget } } - /** - * Start monitor data form specific device. This is old one, works only on V1 API supported devices. - * - * @param deviceId Device ID - * @return Work1 to be uses to grab data during monitoring. - * @throws LGThinqApiException If some communication error occur. - */ - @Override - public String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String workerId = UUID.randomUUID().toString(); - String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," - + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - return Objects.requireNonNull((String) handleV1GenericErrorResult(resp).get("workId"), - "Unexpected StartMonitor json result. Node 'workId' not present"); - } - - @NonNull - private Map handleV1GenericErrorResult(@Nullable RestResult resp) - throws LGThinqApiException, LGThinqDeviceV1OfflineException { - Map metaResult; - Map envelope = Collections.emptyMap(); - if (resp == null) { - return envelope; - } - if (resp.getStatusCode() != 200) { - logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException( - String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - envelope = (Map) metaResult.get("lgedmRoot"); - if (envelope == null) { - throw new LGThinqApiException(String.format( - "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); - } else if (!"0000".equals(envelope.get("returnCd"))) { - logErrorResultCodeMessage((String) envelope.get("returnCd")); - if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { - // Disconnected Device - throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); - } - throw new LGThinqApiException( - String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", - metaResult.get("returnCd"))); - } - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream", e); - } - } - return envelope; - } - - @Override - public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," - + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - handleV1GenericErrorResult(resp); - } - @Override public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + @NonNull String workId, DeviceTypes deviceType) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -235,7 +147,7 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) try { envelop = handleV1GenericErrorResult(resp); } catch (LGThinqDeviceV1OfflineException e) { - ACSnapshot shot = new ACSnapshotV2(); + ACSnapshot shot = new ACSnapshot(); shot.setOnline(false); return shot; } @@ -252,7 +164,7 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) String jsonMonDataB64 = (String) workList.get("returnData"); String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); - ACSnapshot shot = objectMapper.readValue(jsonMon, ACSnapshotV1.class); + Snapshot shot = SnapshotFactory.getInstance().create(jsonMon, deviceType); shot.setOnline("E".equals(workList.get("deviceState"))); return shot; } else { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java similarity index 71% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java index 6014eb51f3903..9dcddb0ebe43a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java @@ -14,7 +14,6 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; -import java.io.File; import java.io.IOException; import java.util.Map; @@ -23,52 +22,38 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import org.openhab.binding.lgthinq.lgservices.model.SnapshotFactory; import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGThinqApiV2ClientServiceImpl} + * The {@link LGThinqACApiV2ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl { - private static final LGThinqApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinqApiV2ClientServiceImpl.class); - private final ObjectMapper objectMapper = new ObjectMapper(); - private final TokenManager tokenManager; +public class LGThinqACApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqACApiClientService { + private static final LGThinqACApiClientService instance; + private static final Logger logger = LoggerFactory.getLogger(LGThinqACApiV2ClientServiceImpl.class); static { - instance = new LGThinqApiV2ClientServiceImpl(); + instance = new LGThinqACApiV2ClientServiceImpl(); } - private LGThinqApiV2ClientServiceImpl() { - tokenManager = TokenManager.getInstance(); - } - - @Override - protected TokenManager getTokenManager() { - return tokenManager; - } - - public static LGThinqApiClientService getInstance() { + public static LGThinqACApiClientService getInstance() { return instance; } @@ -77,43 +62,8 @@ private Map getCommonV2Headers(String language, String country, return getCommonHeaders(language, country, accessToken, userNumber); } - /** - * Get snapshot data from the device. - * It works only for API V2 device versions! - * - * @param deviceId device ID for de desired V2 LG Thinq. - * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. - */ - @Override - @Nullable - public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { - Map deviceSettings = getDeviceSettings(bridgeName, deviceId); - if (deviceSettings.get("snapshot") != null) { - Map snapMap = (Map) deviceSettings.get("snapshot"); - if (logger.isDebugEnabled()) { - try { - objectMapper.writeValue(new File(String.format( - LGThinqBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", - deviceId)), deviceSettings); - } catch (IOException e) { - logger.error("Error saving data trace", e); - } - } - if (snapMap == null) { - // No snapshot value provided - return null; - } - - Snapshot shot = SnapshotFactory.getInstance().create(deviceSettings); - shot.setOnline((Boolean) snapMap.get("online")); - return shot; - } - return null; - } - - public RestResult sendControlCommands(String bridgeName, String deviceId, String command, String keyName, int value) - throws Exception { + private RestResult sendControlCommands(String bridgeName, String deviceId, String command, String keyName, + int value) throws Exception { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId)); @@ -213,10 +163,6 @@ private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThin } } - private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { - return genericHandleDeviceSettingsResult(resp, logger, objectMapper); - } - @Override public void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { @@ -225,7 +171,8 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) @Override public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + @NonNull String workId, DeviceTypes deviceType) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { throw new UnsupportedOperationException("Not supported in V2 API."); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java index 7cd41f254fd3a..8aa1e3d9aede3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java @@ -25,7 +25,6 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.*; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; /** * The {@link LGThinqApiClientService} @@ -51,13 +50,6 @@ public interface LGThinqApiClientService { void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; - void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; - - void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; - - void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGThinqApiException; - String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; @@ -69,6 +61,6 @@ File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; @Nullable - Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; + Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId, + DeviceTypes deviceType) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java index fd27b44aab2ce..34474809a7267 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java @@ -24,16 +24,20 @@ import javax.ws.rs.core.UriBuilder; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; import org.openhab.binding.lgthinq.internal.api.RestResult; import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.api.TokenResult; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.Capability; -import org.openhab.binding.lgthinq.lgservices.model.CapabilityFactory; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -42,16 +46,19 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGThinqApiV1ClientServiceImpl} + * The {@link LGThinqACApiV1ClientServiceImpl} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault public abstract class LGThinqApiClientServiceImpl implements LGThinqApiClientService { private static final Logger logger = LoggerFactory.getLogger(LGThinqApiClientServiceImpl.class); - private final ObjectMapper objectMapper = new ObjectMapper(); + protected final ObjectMapper objectMapper = new ObjectMapper(); + protected final TokenManager tokenManager; - protected abstract TokenManager getTokenManager(); + protected LGThinqApiClientServiceImpl() { + this.tokenManager = TokenManager.getInstance(); + } static Map getCommonHeaders(String language, String country, String accessToken, String userNumber) { @@ -86,7 +93,7 @@ static Map getCommonHeaders(String language, String country, Str @Override public List listAccountDevices(String bridgeName) throws LGThinqApiException { try { - TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); @@ -123,7 +130,7 @@ public File loadDeviceCapability(String deviceId, String uri, boolean forceRecre @Override public Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException { try { - TokenResult token = getTokenManager().getValidRegisteredToken(bridgeName); + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), @@ -224,4 +231,154 @@ public Capability getCapability(String deviceId, String uri, boolean forceRecrea throw new LGThinqApiException("Error reading IO interface", e); } } + + @NonNull + protected Map handleV1GenericErrorResult(@Nullable RestResult resp) + throws LGThinqApiException, LGThinqDeviceV1OfflineException { + Map metaResult; + Map envelope = Collections.emptyMap(); + if (resp == null) { + return envelope; + } + if (resp.getStatusCode() != 200) { + logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); + throw new LGThinqApiException( + String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); + } else { + try { + metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + envelope = (Map) metaResult.get("lgedmRoot"); + if (envelope == null) { + throw new LGThinqApiException(String.format( + "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); + } else if (!"0000".equals(envelope.get("returnCd"))) { + logErrorResultCodeMessage((String) envelope.get("returnCd")); + if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { + // Disconnected Device + throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); + } + throw new LGThinqApiException( + String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", + metaResult.get("returnCd"))); + } + } catch (JsonProcessingException e) { + throw new IllegalStateException("Unknown error occurred deserializing json stream", e); + } + } + return envelope; + } + + public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, + @NonNull String workId, DeviceTypes deviceType) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" + + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" + + " ]\n" + " }\n" + "}", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + Map envelop = null; + // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with + // offline flag. + try { + envelop = handleV1GenericErrorResult(resp); + } catch (LGThinqDeviceV1OfflineException e) { + ACSnapshot shot = new ACSnapshot(); + shot.setOnline(false); + return shot; + } + if (envelop.get("workList") != null + && ((Map) envelop.get("workList")).get("returnData") != null) { + Map workList = ((Map) envelop.get("workList")); + if (!"0000".equals(workList.get("returnCode"))) { + logErrorResultCodeMessage((String) workList.get("resultCode")); + LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( + String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); + logger.warn("{}", e.getMessage()); + throw e; + } + + String jsonMonDataB64 = (String) workList.get("returnData"); + String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); + Snapshot shot = SnapshotFactory.getInstance().create(jsonMon, deviceType); + shot.setOnline("E".equals(workList.get("deviceState"))); + return shot; + } else { + // no data available yet + return null; + } + } + + /** + * Get snapshot data from the device. + * It works only for API V2 device versions! + * + * @param deviceId device ID for de desired V2 LG Thinq. + * @return return map containing metamodel of settings and snapshot + * @throws LGThinqApiException if some communication error occur. + */ + @Override + @Nullable + public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + Map deviceSettings = getDeviceSettings(bridgeName, deviceId); + if (deviceSettings.get("snapshot") != null) { + Map snapMap = (Map) deviceSettings.get("snapshot"); + if (logger.isDebugEnabled()) { + try { + objectMapper.writeValue(new File(String.format( + LGThinqBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", + deviceId)), deviceSettings); + } catch (IOException e) { + logger.error("Error saving data trace", e); + } + } + if (snapMap == null) { + // No snapshot value provided + return null; + } + + Snapshot shot = SnapshotFactory.getInstance().create(deviceSettings); + shot.setOnline((Boolean) snapMap.get("online")); + return shot; + } + return null; + } + + /** + * Start monitor data form specific device. This is old one, works only on V1 API supported devices. + * + * @param deviceId Device ID + * @return Work1 to be uses to grab data during monitoring. + * @throws LGThinqApiException If some communication error occur. + */ + @Override + public String startMonitor(String bridgeName, String deviceId) + throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String workerId = UUID.randomUUID().toString(); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + return Objects.requireNonNull((String) handleV1GenericErrorResult(resp).get("workId"), + "Unexpected StartMonitor json result. Node 'workId' not present"); + } + + @Override + public void stopMonitor(String bridgeName, String deviceId, String workId) + throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { + TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), + token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); + String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," + + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); + RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); + handleV1GenericErrorResult(resp); + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java new file mode 100644 index 0000000000000..9575166ed674e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java @@ -0,0 +1,21 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices; + +/** + * The {@link LGThinqWMApiClientService} + * + * @author Nemer Daud - Initial contribution + */ +public interface LGThinqWMApiClientService extends LGThinqApiClientService { +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java new file mode 100644 index 0000000000000..fdd617c2b15d4 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices; + +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; + +/** + * The {@link LGThinqWMApiV2ClientServiceImpl} + * + * @author Nemer Daud - Initial contribution + */ +public class LGThinqWMApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqWMApiClientService { + + private static final LGThinqWMApiClientService instance; + static { + instance = new LGThinqWMApiV2ClientServiceImpl(); + } + + public static LGThinqWMApiClientService getInstance() { + return instance; + } + + @Override + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) + throws LGThinqApiException { + throw new UnsupportedOperationException("Not implemented yet."); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 8e7205063f465..1542702e34172 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -14,15 +14,17 @@ import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV1; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshotV2; +import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; /** @@ -42,23 +44,37 @@ public static final SnapshotFactory getInstance() { return instance; } + /** + * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) + * + * @param snapshotDataJson V1: decoded returnedData; V2: snapshot body + * @param deviceType device type + * @return returns Snapshot implementation based on device type provided + * @throws LGThinqApiException any error. + */ + public Snapshot create(String snapshotDataJson, DeviceTypes deviceType) throws LGThinqApiException { + try { + Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { + }); + Map deviceSetting = new HashMap<>(); + deviceSetting.put("deviceType", deviceType.deviceTypeId()); + deviceSetting.put("snapshot", snapshotMap); + return create(deviceSetting); + } catch (JsonProcessingException e) { + throw new LGThinqApiException("Unexpected Error unmarshalling json to map", e); + } + } + public Snapshot create(Map deviceSettings) throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); - Map snapMap = (Map) deviceSettings.get("snapshot"); + Map snapMap = ((Map) deviceSettings.get("snapshot")); if (snapMap == null) { throw new LGThinqApiException("snapshot node not present in device monitoring result."); } LGAPIVerion version = discoveryAPIVersion(snapMap, type); switch (type) { case AIR_CONDITIONER: - switch (version) { - case V1_0: { - return objectMapper.convertValue(snapMap, ACSnapshotV2.class); - } - case V2_0: { - return objectMapper.convertValue(snapMap, ACSnapshotV1.class); - } - } + return objectMapper.convertValue(snapMap, ACSnapshot.class); case WASHING_MACHINE: switch (version) { case V1_0: { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java index 5a049417303ab..ec882915da164 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java @@ -17,8 +17,10 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; +import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; /** * The {@link ACSnapshot} @@ -27,7 +29,7 @@ */ @NonNullByDefault @JsonIgnoreProperties(ignoreUnknown = true) -public abstract class ACSnapshot implements Snapshot { +public class ACSnapshot implements Snapshot { private int airWindStrength; @@ -56,6 +58,8 @@ public ACFanSpeed getAcFanSpeed() { return ACFanSpeed.statusOf(airWindStrength); } + @JsonProperty("airState.windStrength") + @JsonAlias("WindStrength") public Integer getAirWindStrength() { return airWindStrength; } @@ -64,6 +68,8 @@ public void setAirWindStrength(Integer airWindStrength) { this.airWindStrength = airWindStrength; } + @JsonProperty("airState.tempState.target") + @JsonAlias("TempCfg") public Double getTargetTemperature() { return targetTemperature; } @@ -72,6 +78,8 @@ public void setTargetTemperature(Double targetTemperature) { this.targetTemperature = targetTemperature; } + @JsonProperty("airState.tempState.current") + @JsonAlias("TempCur") public Double getCurrentTemperature() { return currentTemperature; } @@ -80,6 +88,8 @@ public void setCurrentTemperature(Double currentTemperature) { this.currentTemperature = currentTemperature; } + @JsonProperty("airState.opMode") + @JsonAlias("OpMode") public Integer getOperationMode() { return operationMode; } @@ -89,6 +99,8 @@ public void setOperationMode(Integer operationMode) { } @Nullable + @JsonProperty("airState.operation") + @JsonAlias("Operation") public Integer getOperation() { return operation; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java deleted file mode 100644 index 8cec78795c997..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV1.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link ACSnapshotV1} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ACSnapshotV1 extends ACSnapshot { - - @Override - @JsonProperty("WindStrength") - public Integer getAirWindStrength() { - return super.getAirWindStrength(); - } - - @Override - @JsonProperty("TempCfg") - public Double getTargetTemperature() { - return super.getTargetTemperature(); - } - - @Override - @JsonProperty("TempCur") - public Double getCurrentTemperature() { - return super.getCurrentTemperature(); - } - - @Override - @JsonProperty("OpMode") - public Integer getOperationMode() { - return super.getOperationMode(); - } - - @Override - @JsonProperty("Operation") - @Nullable - public Integer getOperation() { - return super.getOperation(); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java deleted file mode 100644 index 3d5da370f5ebc..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshotV2.java +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link ACSnapshotV2} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ACSnapshotV2 extends ACSnapshot { - - @Override - @JsonProperty("airState.windStrength") - public Integer getAirWindStrength() { - return super.getAirWindStrength(); - } - - @Override - @JsonProperty("airState.tempState.target") - public Double getTargetTemperature() { - return super.getTargetTemperature(); - } - - @Override - @JsonProperty("airState.tempState.current") - public Double getCurrentTemperature() { - return super.getCurrentTemperature(); - } - - @Override - @JsonProperty("airState.opMode") - public Integer getOperationMode() { - return super.getOperationMode(); - } - - @Override - @JsonProperty("airState.operation") - @Nullable - public Integer getOperation() { - return super.getOperation(); - } -} From 6f56773079de3e07b9a3d488fc89b0f7a39acc2b Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 9 Feb 2022 21:27:00 -0300 Subject: [PATCH 078/130] [lgthinq][Fix] Fixing discovery process Signed-off-by: Nemer Daud Signed-off-by: nemerdaud --- .../lgthinq/internal/handler/LGThinqBridgeHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java index 8125ed3c9346c..181b19f3afc83 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java @@ -202,8 +202,8 @@ protected void doConnectedRun() throws LGThinqException { Map lastDevicesDiscoveredCopy = new HashMap<>(lastDevicesDiscovered); for (final LGDevice device : lgApiClient.listAccountDevices(bridgeName)) { String deviceId = device.getDeviceId(); - - if (lGDeviceRegister.get(deviceId) == null) { + // if not registered yet, and not discovered before, then add to discovery list. + if (lGDeviceRegister.get(deviceId) == null && !lastDevicesDiscovered.containsKey(deviceId)) { logger.debug("Adding new LG Device to things registry with id:{}", deviceId); if (discoveryService != null) { discoveryService.addLgDeviceDiscovery(device); From f8b54e7921e4074be7bfac4652ca57c95ffab906 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Mon, 14 Feb 2022 21:33:13 -0300 Subject: [PATCH 079/130] [lgthinq][Fix] Fixing mapping bugs on Washer; [Feat] refactoring design model, Dryer & Washer View Channel support Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 22 +- ...uration.java => LGThinQConfiguration.java} | 8 +- .../LGThinqAirConditionerHandler.java | 452 ------------------ .../internal/LGThinqBindingConstants.java | 190 -------- ...hinqDeviceDynStateDescriptionProvider.java | 39 -- .../lgthinq/internal/LGThinqDeviceThing.java | 106 ---- .../internal/LGThinqHandlerFactory.java | 84 ---- .../internal/LGThinqWasherHandler.java | 419 ---------------- .../lgthinq/internal/api/LGThinqGateway.java | 4 +- .../internal/handler/LGThinQDryerHandler.java | 217 +++++++++ .../handler/LGThinQWasherHandler.java | 196 ++++++++ .../internal/handler/LGThinqBridge.java | 31 -- .../handler/LGThinqBridgeHandler.java | 327 ------------- .../lgservices/LGThinQApiClientService.java | 13 +- .../lgservices/LGThinQDRApiClientService.java | 10 +- .../lgservices/LGThinQWMApiClientService.java | 13 +- .../lgservices/LGThinqACApiClientService.java | 32 -- .../LGThinqACApiV1ClientServiceImpl.java | 175 ------- .../LGThinqACApiV2ClientServiceImpl.java | 178 ------- .../lgservices/LGThinqApiClientService.java | 66 --- .../LGThinqApiClientServiceImpl.java | 384 --------------- .../lgservices/LGThinqWMApiClientService.java | 21 - .../LGThinqWMApiV2ClientServiceImpl.java | 39 -- .../lgservices/model/SnapshotFactory.java | 34 +- .../model/dryer/DryerCapability.java | 123 +++++ .../lgservices/model/dryer/DryerSnapshot.java | 164 +++++++ ...MCapability.java => WasherCapability.java} | 4 +- ...DryerSnapshot.java => WasherSnapshot.java} | 34 +- .../main/resources/OH-INF/thing/washer.xml | 5 +- 29 files changed, 789 insertions(+), 2601 deletions(-) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/{LGThinqConfiguration.java => LGThinQConfiguration.java} (91%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/{WMCapability.java => WasherCapability.java} (97%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/{WasherDryerSnapshot.java => WasherSnapshot.java} (76%) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index 96465f31c5f5e..76aa362d2a96d 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -1,11 +1,11 @@ -# LG Thinq Bridge & Things +# LG ThinQ Bridge & Things -This binding was developed to integrate de OpenHab framework to LG Thinq API. Currently, only Air Conditioners (API V1 & V2) are supported, but this binding is under construction to support others LG Thinq Device Kinds. -The Thinq Bridge is necessary to work as a hub/bridge to discovery and first configure the LG Thinq devices related with the LG's user account. -Then, the first thing is to create the LG Thinq Bridge and then, it will discovery all Things you have related in your LG Account. +This binding was developed to integrate de OpenHab framework to LG ThinQ API. Currently, only Air Conditioners (API V1 & V2) are supported, but this binding is under construction to support others LG ThinQ Device Kinds. +The ThinQ Bridge is necessary to work as a hub/bridge to discovery and first configure the LG ThinQ devices related with the LG's user account. +Then, the first thing is to create the LG ThinQ Bridge and then, it will discovery all Things you have related in your LG Account. ## Supported Things -LG Thinq Devices V1 & V2 (currently only Air Conditioners are supported, but it's planned to support the other kinds as well) +LG ThinQ Devices V1 & V2 (currently only Air Conditioners are supported, but it's planned to support the other kinds as well) ## Discovery @@ -34,7 +34,7 @@ There is currently no configuration available, as it is automatically obtained b ## Channels -LG Thinq Air Conditioners support the following channels to interact with the OpenHab automation framework: +LG ThinQ Air Conditioners support the following channels to interact with the OpenHab automation framework: | channel | type | description | |--------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -44,17 +44,17 @@ LG Thinq Air Conditioners support the following channels to interact with the Op | Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | | Power | Switch | Define the device's current power state. | -**Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart Thinq way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: +**Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart ThinQ way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: 1. **Internet Link** - if you OpenHab server doesn't have a good internet connection this binding will not work properly! In the same way, if the internet link goes down, your Things and Bridge going to be Offline as well, and you won't be able to control the devices though OpenHab until the link comes back. -2. **LG Thinq App** - if you've already used the LG Thinq App to control your devices and hold it constantly activated in your mobile phone, you may experience some instability because the App (and Binding) will try to lock the device in LG Thinq API Server to get it's current state. In the app, you may see some information in the device informing that "The device is being used by other" (something like this) and in the OpenHab, the thing can go Offline for a while. +2. **LG ThinQ App** - if you've already used the LG ThinQ App to control your devices and hold it constantly activated in your mobile phone, you may experience some instability because the App (and Binding) will try to lock the device in LG ThinQ API Server to get it's current state. In the app, you may see some information in the device informing that "The device is being used by other" (something like this) and in the OpenHab, the thing can go Offline for a while. 3. **Pooling time** - both Bridge and Thing use pooling strategy to get the current state information about the registered devices. Note that the Thing pooling time is internal and can't be changed (please, don't change in the source code) and the Bridge can be changed for something greater than 300 seconds, and it's recommended long pooling periods for the Bridge because the discovery process fetch a lot of information from the LG API Server, depending on the number of devices you have registered in your account. -About this last point, it's important to know that LG API is not Open & Public, i.e, only LG Official Partners with some agreement have access to their support and documentations. This binding was a hard (very hard actually) work to dig and reverse engineering in the LG's Thinq API protocol. Because this, you must respect the hardcoded pool period to do not put your account in LG Blacklist. +About this last point, it's important to know that LG API is not Open & Public, i.e, only LG Official Partners with some agreement have access to their support and documentations. This binding was a hard (very hard actually) work to dig and reverse engineering in the LG's ThinQ API protocol. Because this, you must respect the hardcoded pool period to do not put your account in LG Blacklist. ## Thanks and Inspirations This binding was inspired in the work of some brave opensource community people. I got some tips and helps from their codes: -* Adrian Sampson - [Wideq Project](https://github.com/sampsyo/wideq): I think it is the first reverse engineering of Thinq protocol made in Python, but only works (currently) for API V1. -* Ollo69 - [LG Thinq Integration for Home Assistant](https://github.com/ollo69/ha-smartthinq-sensors): Ollo69 took the Adrian code and refactor it to support API V2 in an HA plugin. +* Adrian Sampson - [Wideq Project](https://github.com/sampsyo/wideq): I think it is the first reverse engineering of ThinQ protocol made in Python, but only works (currently) for API V1. +* Ollo69 - [LG ThinQ Integration for Home Assistant](https://github.com/ollo69/ha-smartthinq-sensors): Ollo69 took the Adrian code and refactor it to support API V2 in an HA plugin. ## Be nice! If you like the binding, why don't you support me by buying me a coffee? diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java similarity index 91% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java index 8bd5ef613199a..b718d63d29068 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java @@ -15,12 +15,12 @@ import org.eclipse.jdt.annotation.NonNullByDefault; /** - * The {@link LGThinqConfiguration} class contains fields mapping thing configuration parameters. + * The {@link LGThinQConfiguration} class contains fields mapping thing configuration parameters. * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqConfiguration { +public class LGThinQConfiguration { /** * Sample configuration parameters. Replace with your own. */ @@ -31,10 +31,10 @@ public class LGThinqConfiguration { public Integer pollingIntervalSec = 0; public String alternativeServer = ""; - public LGThinqConfiguration() { + public LGThinQConfiguration() { } - public LGThinqConfiguration(String username, String password, String country, String language, + public LGThinQConfiguration(String username, String password, String country, String language, Integer pollingIntervalSec, String alternativeServer) { this.username = username; this.password = password; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java deleted file mode 100644 index 840c555188275..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqAirConditionerHandler.java +++ /dev/null @@ -1,452 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.util.*; -import java.util.concurrent.*; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.LGThinqACApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACCapability; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; -import org.openhab.core.library.types.DecimalType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.QuantityType; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.StateOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqAirConditionerHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqAirConditionerHandler extends LGThinqDeviceThing { - - private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; - private final ChannelUID opModeChannelUID; - private final ChannelUID opModeFanSpeedUID; - @Nullable - private ACCapability acCapability; - private final String lgPlatfomType; - private final Logger logger = LoggerFactory.getLogger(LGThinqAirConditionerHandler.class); - @NonNullByDefault - private final LGThinqACApiClientService lgThinqACApiClientService; - private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; - // Bridges status that this thing must top scanning for state change - private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, - ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, - ThingStatusDetail.CONFIGURATION_ERROR); - private @Nullable ScheduledFuture thingStatePollingJob; - private @Nullable Future commandExecutorQueueJob; - // *** Long running isolated threadpools. - private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); - private final ExecutorService executorService = Executors.newFixedThreadPool(1); - - private boolean monitorV1Began = false; - private String monitorWorkId = ""; - private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); - @NonNullByDefault - private String bridgeId = ""; - - public LGThinqAirConditionerHandler(Thing thing, - LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing); - this.stateDescriptionProvider = stateDescriptionProvider; - lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgThinqACApiClientService = lgPlatfomType.equals(PLATFORM_TYPE_V1) - ? LGThinqACApiV1ClientServiceImpl.getInstance() - : LGThinqACApiV2ClientServiceImpl.getInstance(); - opModeChannelUID = new ChannelUID(getThing().getUID(), CHANNEL_MOD_OP_ID); - opModeFanSpeedUID = new ChannelUID(getThing().getUID(), CHANNEL_FAN_SPEED_ID); - } - - static class AsyncCommandParams { - final String channelUID; - final Command command; - - public AsyncCommandParams(String channelUUID, Command command) { - this.channelUID = channelUUID; - this.command = command; - } - } - - @Override - public Collection> getServices() { - return super.getServices(); - } - - @Override - public void initialize() { - logger.debug("Initializing Thinq thing."); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - - @Override - protected void startCommandExecutorQueueJob() { - if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { - commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); - } - } - - private ExecutorService getExecutorService() { - return executorService; - } - - private void stopCommandExecutorQueueJob() { - if (commandExecutorQueueJob != null) { - commandExecutorQueueJob.cancel(true); - } - } - - protected void startThingStatePolling() { - if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { - thingStatePollingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, - DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); - } - } - - private void updateThingStateFromLG() { - try { - ACSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); - if (shot == null) { - // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. - return; - } - if (!shot.isOnline()) { - if (getThing().getStatus() != ThingStatus.OFFLINE) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); - updateState(CHANNEL_POWER_ID, - OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_OFF)); - } - return; - } - - updateState(CHANNEL_MOD_OP_ID, new DecimalType(shot.getOperationMode())); - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); - // TODO - validate if is needed to change the status of the thing from OFFLINE to ONLINE (as - // soon as LG WebOs do) - updateState(CHANNEL_FAN_SPEED_ID, new DecimalType(shot.getAirWindStrength())); - updateState(CHANNEL_CURRENT_TEMP_ID, new DecimalType(shot.getCurrentTemperature())); - updateState(CHANNEL_TARGET_TEMP_ID, new DecimalType(shot.getTargetTemperature())); - updateStatus(ThingStatus.ONLINE); - } catch (Exception e) { - logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", - getDeviceAlias(), getDeviceId(), e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - - private ScheduledExecutorService getLocalScheduler() { - return pollingScheduler; - } - - private String getBridgeId() { - if (bridgeId.isBlank() && getBridge() == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); - logger.error("Configuration error um Thinq Thing - No Bridge defined for the thing."); - return "UNKNOWN"; - } else if (bridgeId.isBlank() && getBridge() != null) { - bridgeId = getBridge().getUID().getId(); - } - return bridgeId; - } - - private void forceStopDeviceV1Monitor(String deviceId) { - try { - monitorV1Began = false; - lgThinqACApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); - } catch (Exception e) { - logger.error("Error stopping LG Device monitor", e); - } - } - - @NonNull - private String emptyIfNull(@Nullable String value) { - return value == null ? "" : "" + value; - } - - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - ACCapability acCap = getCapabilities(); - if (isLinked(opModeChannelUID)) { - List options = new ArrayList<>(); - acCap.getSupportedOpMode().forEach((v) -> options - .add(new StateOption(emptyIfNull(acCap.getOpMod().get(v)), emptyIfNull(CAP_AC_OP_MODE.get(v))))); - stateDescriptionProvider.setStateOptions(opModeChannelUID, options); - } - if (isLinked(opModeFanSpeedUID)) { - List options = new ArrayList<>(); - acCap.getSupportedFanSpeed().forEach((v) -> options.add( - new StateOption(emptyIfNull(acCap.getFanSpeed().get(v)), emptyIfNull(CAP_AC_FAN_SPEED.get(v))))); - stateDescriptionProvider.setStateOptions(opModeFanSpeedUID, options); - } - } - - @Override - public ACCapability getCapabilities() throws LGThinqApiException { - if (acCapability == null) { - acCapability = (ACCapability) lgThinqACApiClientService.getCapability(getDeviceId(), - getDeviceUriJsonConfig(), false); - } - return Objects.requireNonNull(acCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); - } - - @Override - protected Logger getLogger() { - return logger; - } - - @Nullable - private ACSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { - // analise de platform version - if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (ACSnapshot) lgThinqACApiClientService.getDeviceData(getBridgeId(), getDeviceId()); - } else { - try { - if (!monitorV1Began) { - monitorWorkId = lgThinqACApiClientService.startMonitor(getBridgeId(), getDeviceId()); - monitorV1Began = true; - } - } catch (LGThinqDeviceV1OfflineException e) { - forceStopDeviceV1Monitor(deviceId); - ACSnapshot shot = new ACSnapshot(); - shot.setOnline(false); - return shot; - } catch (Exception e) { - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); - } - int retries = 10; - ACSnapshot shot; - while (retries > 0) { - // try to get monitoring data result 3 times. - try { - shot = (ACSnapshot) lgThinqACApiClientService.getMonitorData(getBridgeId(), deviceId, monitorWorkId, - DeviceTypes.AIR_CONDITIONER); - if (shot != null) { - return shot; - } - Thread.sleep(500); - retries--; - } catch (LGThinqDeviceV1MonitorExpiredException e) { - forceStopDeviceV1Monitor(deviceId); - logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); - return null; - } catch (Exception e) { - // If it can't get monitor handler, then stop monitor and restart the process again in new - // interaction - // Force restart monitoring because of the errors returned (just in case) - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); - } - } - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Exhausted trying to get monitor data for the device:" + deviceId); - } - } - - protected void stopThingStatePolling() { - if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { - logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); - thingStatePollingJob.cancel(true); - } - } - - private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { - if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { - // start the thing polling - startThingStatePolling(); - } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE - && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { - // comunication error is not a specific Bridge error, then we must analise it to give - // this thinq the change to recovery from communication errors - if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR - || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { - // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if - // the bridge is out - stopThingStatePolling(); - } - - } - lastThingStatus = newStatus; - } - - @Override - protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { - handleStatusChanged(newStatus, statusDetail); - super.updateStatus(newStatus, statusDetail, description); - } - - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceId() { - return getThing().getUID().getId(); - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } - - @Override - public String getDeviceModelName() { - return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); - } - - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } - - @Override - public boolean onDeviceStateChanged() { - // TODO - HANDLE IT, Think if it's needed - return false; - } - - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void onDeviceGone() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void dispose() { - if (thingStatePollingJob != null) { - thingStatePollingJob.cancel(true); - stopThingStatePolling(); - stopCommandExecutorQueueJob(); - thingStatePollingJob = null; - } - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - updateThingStateFromLG(); - } else { - AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); - try { - // Ensure commands are send in a pipe per device. - commandBlockQueue.add(params); - } catch (IllegalStateException ex) { - logger.error( - "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); - updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, - "Device Command Queue is Busy"); - } - - } - } - - private final Runnable queuedCommandExecutor = new Runnable() { - @Override - public void run() { - while (true) { - AsyncCommandParams params; - try { - params = commandBlockQueue.take(); - } catch (InterruptedException e) { - logger.debug("Interrupting async command queue executor."); - return; - } - Command command = params.command; - - try { - switch (params.channelUID) { - case CHANNEL_MOD_OP_ID: { - if (params.command instanceof DecimalType) { - lgThinqACApiClientService.changeOperationMode(getBridgeId(), getDeviceId(), - ((DecimalType) command).intValue()); - } else { - logger.warn("Received command different of Numeric in Mod Operation. Ignoring"); - } - break; - } - case CHANNEL_FAN_SPEED_ID: { - if (command instanceof DecimalType) { - lgThinqACApiClientService.changeFanSpeed(getBridgeId(), getDeviceId(), - ((DecimalType) command).intValue()); - } else { - logger.warn("Received command different of Numeric in FanSpeed Channel. Ignoring"); - } - break; - } - case CHANNEL_POWER_ID: { - if (command instanceof OnOffType) { - lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? DevicePowerState.DV_POWER_ON - : DevicePowerState.DV_POWER_OFF); - } else { - logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); - } - break; - } - case CHANNEL_TARGET_TEMP_ID: { - double targetTemp; - if (command instanceof DecimalType) { - targetTemp = ((DecimalType) command).doubleValue(); - } else if (command instanceof QuantityType) { - targetTemp = ((QuantityType) command).doubleValue(); - } else { - logger.warn("Received command different of Numeric in TargetTemp Channel. Ignoring"); - break; - } - lgThinqACApiClientService.changeTargetTemperature(getBridgeId(), getDeviceId(), - ACTargetTmp.statusOf(targetTemp)); - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, - params.channelUID); - } - } - } catch (LGThinqException e) { - logger.error("Error executing Command {} to the channel {}. Thing goes offline until retry", - command, params.channelUID, e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - } - }; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java deleted file mode 100644 index 31ea1454d986a..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqBindingConstants.java +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import java.io.File; -import java.util.Map; -import java.util.Set; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.core.OpenHAB; -import org.openhab.core.thing.ThingTypeUID; - -/** - * The {@link LGThinqBindingConstants} class defines common constants, which are - * used across the whole binding. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqBindingConstants { - - public static final String BINDING_ID = "lgthinq"; - public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); - public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.AIR_CONDITIONER.deviceTypeId()); // deviceType from AirConditioner - public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.WASHING_MACHINE.deviceTypeId()); // deviceType from AirConditioner - public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, - THING_TYPE_WASHING_MACHINE, THING_TYPE_BRIDGE); - public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_AIR_CONDITIONER, - THING_TYPE_WASHING_MACHINE); - - public static String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; - public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; - public static String BASE_CAP_CONFIG_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-cap.json"; - public static final String V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; - public static final String V2_USER_INFO = "/users/profile"; - public static final String V2_API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; - public static final String V2_CLIENT_ID = "65260af7e8e6547b51fdccf930097c51eb9885a508d3fddfa9ee6cdec22ae1bd"; - public static final String V2_SVC_PHASE = "OP"; - public static final String V2_APP_LEVEL = "PRD"; - public static final String V2_APP_OS = "LINUX"; - public static final String V2_APP_TYPE = "NUTS"; - public static final String V2_APP_VER = "3.0.1700"; - public static final String V2_SESSION_LOGIN_PATH = "/emp/v2.0/account/session/"; - public static final String V2_LS_PATH = "/service/application/dashboard"; - public static final String V2_DEVICE_CONFIG_PATH = "service/devices/"; - public static final String V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/control-sync"; - public static final String V1_START_MON_PATH = "rti/rtiMon"; - public static final String V1_MON_DATA_PATH = "rti/rtiResult"; - public static final String V1_CONTROL_OP = "rti/rtiControl"; - public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; - public static final String GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; - public static final String GATEWAY_SERVICE_PATH_V1 = "/api/common/gatewayUriList"; - public static final String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; - public static final String PRE_LOGIN_PATH = "/preLogin"; - public static final String SECURITY_KEY = "nuts_securitykey"; - public static final String APP_KEY = "wideq"; - public static final String DATA_ROOT = "result"; - public static final String POST_DATA_ROOT = "lgedmRoot"; - public static final String RETURN_CODE_ROOT = "resultCode"; - public static final String RETURN_MESSAGE_ROOT = "returnMsg"; - public static final String SVC_CODE = "SVC202"; - public static final String OAUTH_SECRET_KEY = "c053c2a6ddeb7ad97cb0eed0dcb31cf8"; - public static final String OAUTH_CLIENT_KEY = "LGAO722A02"; - public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss +0000"; - public static final String DEFAULT_COUNTRY = "US"; - public static final String DEFAULT_LANGUAGE = "en-US"; - public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; - public static final String V2_EMP_SESS_PATH = "/emp/oauth2/token/empsession"; - public static final String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com" + V2_EMP_SESS_PATH; - public static final String API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; - - public static final String API_KEY_V1 = "wideq"; - public static final String API_SECURITY_KEY_V1 = "nuts_securitykey"; - - // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, - // and the build id of the thinq app it can also just be a random - // string, we use the same client id used for oauth - public static final String CLIENT_ID = "LGAO221A02"; - public static final String MESSAGE_ID = "wideq"; - public static final String SVC_PHASE = "OP"; - public static final String APP_LEVEL = "PRD"; - public static final String APP_OS = "ANDROID"; - public static final String APP_TYPE = "NUTS"; - public static final String APP_VER = "3.5.1200"; - - public static final String DEVICE_ID = "device_id"; - public static final String MODEL_NAME = "model_name"; - public static final String DEVICE_ALIAS = "device_alias"; - public static final String MODEL_URL_INFO = "model_url_info"; - public static final String PLATFORM_TYPE = "platform_type"; - public static final String PLATFORM_TYPE_V1 = "thinq1"; - public static final String PLATFORM_TYPE_V2 = "thinq2"; - static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); - - public static final int SEARCH_TIME = 20; - // delay between each devices's scan for state changes (in seconds) - public static final int DEFAULT_STATE_POLLING_UPDATE_DELAY = 10; - - public static final Map ERROR_CODE_RESPONSE = Map.ofEntries(Map.entry("0000", "OK"), - Map.entry("0001", "PARTIAL_OK"), Map.entry("0103", "OPERATION_IN_PROGRESS_DEVICE"), - Map.entry("0007", "PORTAL_INTERWORKING_ERROR"), Map.entry("0104", "PROCESSING_REFRIGERATOR"), - Map.entry("0111", "RESPONSE_DELAY_DEVICE"), Map.entry("8107", "SERVICE_SERVER_ERROR"), - Map.entry("8102", "SSP_ERROR"), Map.entry("9020", "TIME_OUT"), Map.entry("8104", "WRONG_XML_OR_URI"), - Map.entry("9000", "AWS_IOT_ERROR"), Map.entry("8105", "AWS_S3_ERROR"), Map.entry("8106", "AWS_SQS_ERROR"), - Map.entry("9002", "BASE64_DECODING_ERROR"), Map.entry("9001", "BASE64_ENCODING_ERROR"), - Map.entry("8103", "CLIP_ERROR"), Map.entry("0105", "CONTROL_ERROR_REFRIGERATOR"), - Map.entry("9003", "CREATE_SESSION_FAIL"), Map.entry("9004", "DB_PROCESSING_FAIL"), - Map.entry("8101", "DM_ERROR"), Map.entry("0013", "DUPLICATED_ALIAS"), Map.entry("0008", "DUPLICATED_DATA"), - Map.entry("0004", "DUPLICATED_LOGIN"), Map.entry("0102", "EMP_AUTHENTICATION_FAILED"), - Map.entry("8900", "ETC_COMMUNICATION_ERROR"), Map.entry("9999", "ETC_ERROR"), - Map.entry("0112", "EXCEEDING_LIMIT"), Map.entry("0119", "EXPIRED_CUSTOMER_NUMBER"), - Map.entry("9005", "EXPIRES_SESSION_BY_WITHDRAWAL"), Map.entry("0100", "FAIL"), - Map.entry("8001", "INACTIVE_API"), Map.entry("0107", "INSUFFICIENT_STORAGE_SPACE"), - Map.entry("9010", "INVAILD_CSR"), Map.entry("0002", "INVALID_BODY"), - Map.entry("0118", "INVALID_CUSTOMER_NUMBER"), Map.entry("0003", "INVALID_HEADER"), - Map.entry("0301", "INVALID_PUSH_TOKEN"), Map.entry("0116", "INVALID_REQUEST_DATA_FOR_DIAGNOSIS"), - Map.entry("0014", "MISMATCH_DEVICE_GROUP"), Map.entry("0114", "MISMATCH_LOGIN_SESSION"), - Map.entry("0006", "MISMATCH_NONCE"), Map.entry("0115", "MISMATCH_REGISTRED_DEVICE"), - Map.entry("0110", "NOT_AGREED_TERMS"), Map.entry("0106", "NOT_CONNECTED_DEVICE"), - Map.entry("0120", "NOT_CONTRACT_CUSTOMER_NUMBER"), Map.entry("0010", "NOT_EXIST_DATA"), - Map.entry("0009", "NOT_EXIST_DEVICE"), Map.entry("0117", "NOT_EXIST_MODEL_JSON"), - Map.entry("0121", "NOT_REGISTERED_SMART_CARE"), Map.entry("0012", "NOT_SUPPORTED_COMMAND"), - Map.entry("8000", "NOT_SUPPORTED_COUNTRY"), Map.entry("0005", "NOT_SUPPORTED_SERVICE"), - Map.entry("0109", "NO_INFORMATION_DR"), Map.entry("0108", "NO_INFORMATION_SLEEP_MODE"), - Map.entry("0011", "NO_PERMISSION"), Map.entry("0113", "NO_PERMISION_MODIFY_RECIPE"), - Map.entry("0101", "NO_REGISTERED_DEVICE"), Map.entry("9006", "NO_USER_INFORMATION")); - - // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= - // CHANNEL IDS - public static final String CHANNEL_MOD_OP_ID = "op_mode"; - public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; - public static final String CHANNEL_POWER_ID = "power"; - public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; - public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; - - public static final Map CAP_AC_OP_MODE = Map.of("@AC_MAIN_OPERATION_MODE_COOL_W", "Cool", - "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", - "@AC_MAIN_OPERATION_MODE_HEAT_W", "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", - "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", "@AC_MAIN_OPERATION_MODE_AI_W", "AI", - "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", - "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); - - public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( - Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), - Map.entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), Map.entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), Map.entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), - Map.entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), Map.entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Nature"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), - Map.entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), - Map.entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), - Map.entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); - - // ====================== WASHING MACHINE CONSTANTS ============================= - public static final String WM_POWER_OFF_VALUE = "POWEROFF"; - public static final String WM_SNAPSHOT_WASHER_DRYER_NODE = "washerDryer"; - public static final String WM_CHANNEL_STATE_ID = "state"; - public static final String WM_CHANNEL_COURSE_ID = "course"; - public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; - public static final String WM_CHANNEL_TEMP_LEVEL_ID = "temperature-level"; - public static final String WM_CHANNEL_DOOR_LOCK_ID = "door-lock"; - - public static final Map CAP_WP_STATE = Map.ofEntries(Map.entry("@WM_STATE_POWER_OFF_W", "Off"), - Map.entry("@WM_STATE_INITIAL_W", "Initial"), Map.entry("@WM_STATE_PAUSE_W", "Pause"), - Map.entry("@WM_STATE_RESERVE_W", "Reverse"), Map.entry("@WM_STATE_DETECTING_W", "Detecting"), - Map.entry("@WM_STATE_RUNNING_W", "Running"), Map.entry("@WM_STATE_RINSING_W", "Rinsing"), - Map.entry("@WM_STATE_SPINNING_W", "Spinning"), Map.entry("@WM_STATE_COOLDOWN_W", "Cool Down"), - Map.entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), Map.entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), - Map.entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), Map.entry("@WM_STATE_END_W", "End"), - Map.entry("@WM_STATE_DRYING_W", "Drying"), Map.entry("@WM_STATE_DEMO_W", "Demonstration"), - Map.entry("@WM_STATE_ERROR_W", "Error")); - - // ============================================================================== -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java deleted file mode 100644 index 2b50c6e63ca83..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceDynStateDescriptionProvider.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import org.openhab.core.events.EventPublisher; -import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; -import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; -import org.openhab.core.thing.link.ItemChannelLinkRegistry; -import org.openhab.core.thing.type.DynamicStateDescriptionProvider; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; - -/** - * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things - * - * @author Nemer Daud - Initial contribution - */ -@Component(service = { DynamicStateDescriptionProvider.class, LGThinqDeviceDynStateDescriptionProvider.class }) -public class LGThinqDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { - @Activate - public LGThinqDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // - final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // - final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { - this.eventPublisher = eventPublisher; - this.itemChannelLinkRegistry = itemChannelLinkRegistry; - this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java deleted file mode 100644 index 9b9dde78bc7df..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqDeviceThing.java +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; -import org.openhab.binding.lgthinq.lgservices.model.Capability; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.slf4j.Logger; - -/** - * The {@link LGThinqDeviceThing} is a main interface contract for all LG Thinq things - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public abstract class LGThinqDeviceThing extends BaseThingHandler { - - public LGThinqDeviceThing(Thing thing) { - super(thing); - } - - public abstract void onDeviceAdded(@NonNullByDefault LGDevice device); - - public abstract String getDeviceId(); - - public abstract String getDeviceAlias(); - - public abstract String getDeviceModelName(); - - public abstract String getDeviceUriJsonConfig(); - - public abstract boolean onDeviceStateChanged(); - - public abstract void onDeviceRemoved(); - - public abstract void onDeviceGone(); - - public abstract void updateChannelDynStateDescription() throws LGThinqApiException; - - public abstract Capability getCapabilities() throws LGThinqApiException; - - protected abstract Logger getLogger(); - - protected abstract void startCommandExecutorQueueJob(); - - protected void initializeThing(@Nullable ThingStatus bridgeStatus) { - getLogger().debug("initializeThing LQ Thinq {}. Bridge status {}", getThing().getUID(), bridgeStatus); - String deviceId = getThing().getUID().getId(); - - Bridge bridge = getBridge(); - if (!deviceId.isBlank()) { - try { - updateChannelDynStateDescription(); - } catch (LGThinqApiException e) { - getLogger().error( - "Error updating channels dynamic options descriptions based on capabilities of the device. Fallback to default values.", - e); - } - if (bridge != null) { - LGThinqBridgeHandler handler = (LGThinqBridgeHandler) bridge.getHandler(); - // registry this thing to the bridge - if (handler == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } else { - handler.registryListenerThing(this); - if (bridgeStatus == ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - } - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - } - } else { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/offline.conf-error-no-device-id"); - } - // finally, start command queue, regardless of the thing state, as we can still try to send commands without - // property ONLINE (the successful result from command request can put the thing in ONLINE status). - startCommandExecutorQueueJob(); - } - - @Override - public void bridgeStatusChanged(ThingStatusInfo bridgeStatusInfo) { - getLogger().debug("bridgeStatusChanged {}", bridgeStatusInfo); - super.bridgeStatusChanged(bridgeStatusInfo); - // restart scheduler - initializeThing(bridgeStatusInfo.getStatus()); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java deleted file mode 100644 index 68b203cf3eb5c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqHandlerFactory.java +++ /dev/null @@ -1,84 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.handler.LGThinqBridgeHandler; -import org.openhab.core.config.core.Configuration; -import org.openhab.core.thing.Bridge; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -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; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqHandlerFactory} is responsible for creating things and thing - * handlers. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.lgthinq") -public class LGThinqHandlerFactory extends BaseThingHandlerFactory { - - private final Logger logger = LoggerFactory.getLogger(LGThinqHandlerFactory.class); - private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; - - @Override - public boolean supportsThingType(ThingTypeUID thingTypeUID) { - return LGThinqBindingConstants.SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID); - } - - @Override - protected @Nullable ThingHandler createHandler(Thing thing) { - ThingTypeUID thingTypeUID = thing.getThingTypeUID(); - - if (THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { - return new LGThinqAirConditionerHandler(thing, stateDescriptionProvider); - } else if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - return new LGThinqBridgeHandler((Bridge) thing); - } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { - return new LGThinqWasherHandler(thing, stateDescriptionProvider); - } - logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); - return null; - } - - @Override - public @Nullable Thing createThing(ThingTypeUID thingTypeUID, Configuration configuration, - @Nullable ThingUID thingUID, @Nullable ThingUID bridgeUID) { - if (THING_TYPE_BRIDGE.equals(thingTypeUID)) { - return super.createThing(thingTypeUID, configuration, thingUID, null); - } else if (LGThinqBindingConstants.THING_TYPE_AIR_CONDITIONER.equals(thingTypeUID)) { - return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); - } else if (LGThinqBindingConstants.THING_TYPE_WASHING_MACHINE.equals(thingTypeUID)) { - return super.createThing(thingTypeUID, configuration, thingUID, bridgeUID); - } - return null; - } - - @Activate - public LGThinqHandlerFactory(final @Reference LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { - this.stateDescriptionProvider = stateDescriptionProvider; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java deleted file mode 100644 index 94927b10d52e2..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinqWasherHandler.java +++ /dev/null @@ -1,419 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.util.*; -import java.util.concurrent.*; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.LGThinqWMApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinqWMApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.washer.WMCapability; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; -import org.openhab.core.types.RefreshType; -import org.openhab.core.types.StateOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqWasherHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqWasherHandler extends LGThinqDeviceThing { - - private final LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider; - @Nullable - private WMCapability wmCapability; - private final ChannelUID stateChannelUUID; - private final ChannelUID courseChannelUUID; - private final ChannelUID smartCourseChannelUUID; - private final String lgPlatfomType; - private final Logger logger = LoggerFactory.getLogger(LGThinqWasherHandler.class); - @NonNullByDefault - private final LGThinqWMApiClientService lgThinqWMApiClientService; - private ThingStatus lastThingStatus = ThingStatus.UNKNOWN; - // Bridges status that this thing must top scanning for state change - private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, - ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, - ThingStatusDetail.CONFIGURATION_ERROR); - private @Nullable ScheduledFuture thingStatePollingJob; - private @Nullable Future commandExecutorQueueJob; - // *** Long running isolated threadpools. - private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); - private final ExecutorService executorService = Executors.newFixedThreadPool(1); - - private boolean monitorV1Began = false; - private String monitorWorkId = ""; - private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); - @NonNullByDefault - private String bridgeId = ""; - - public LGThinqWasherHandler(Thing thing, LGThinqDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing); - this.stateDescriptionProvider = stateDescriptionProvider; - lgPlatfomType = "" + thing.getProperties().get(PLATFORM_TYPE); - lgThinqWMApiClientService = LGThinqWMApiV2ClientServiceImpl.getInstance(); - stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); - courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); - smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); - } - - static class AsyncCommandParams { - final String channelUID; - final Command command; - - public AsyncCommandParams(String channelUUID, Command command) { - this.channelUID = channelUUID; - this.command = command; - } - } - - @Override - public Collection> getServices() { - return super.getServices(); - } - - @Override - public void initialize() { - logger.debug("Initializing Thinq thing."); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - - @Override - protected void startCommandExecutorQueueJob() { - if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { - commandExecutorQueueJob = getExecutorService().submit(queuedCommandExecutor); - } - } - - private ExecutorService getExecutorService() { - return executorService; - } - - private void stopCommandExecutorQueueJob() { - if (commandExecutorQueueJob != null) { - commandExecutorQueueJob.cancel(true); - } - } - - protected void startThingStatePolling() { - if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { - thingStatePollingJob = getLocalScheduler().scheduleWithFixedDelay(this::updateThingStateFromLG, 10, - DEFAULT_STATE_POLLING_UPDATE_DELAY, TimeUnit.SECONDS); - } - } - - private void updateThingStateFromLG() { - try { - WasherDryerSnapshot shot = getSnapshotDeviceAdapter(getDeviceId()); - if (shot == null) { - // no data to update. Maybe, the monitor stopped, then it gonna be restarted next try. - return; - } - if (!shot.isOnline()) { - if (getThing().getStatus() != ThingStatus.OFFLINE) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.GONE); - updateState(CHANNEL_POWER_ID, - OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_OFF)); - } - return; - } - - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); - updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); - updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); - updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); - updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); - updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); - updateStatus(ThingStatus.ONLINE); - } catch (LGThinqException e) { - logger.error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry.", - getDeviceAlias(), getDeviceId(), e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - - private ScheduledExecutorService getLocalScheduler() { - return pollingScheduler; - } - - private String getBridgeId() { - if (bridgeId.isBlank() && getBridge() == null) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR); - logger.error("Configuration error um Thinq Thing - No Bridge defined for the thing."); - return "UNKNOWN"; - } else if (bridgeId.isBlank() && getBridge() != null) { - bridgeId = getBridge().getUID().getId(); - } - return bridgeId; - } - - private void forceStopDeviceV1Monitor(String deviceId) { - try { - monitorV1Began = false; - lgThinqWMApiClientService.stopMonitor(getBridgeId(), deviceId, monitorWorkId); - } catch (Exception e) { - logger.error("Error stopping LG Device monitor", e); - } - } - - @NonNull - private String emptyIfNull(@Nullable String value) { - return value == null ? "" : "" + value; - } - - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - WMCapability wmCap = getCapabilities(); - if (isLinked(stateChannelUUID)) { - List options = new ArrayList<>(); - // invert key/value - wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); - stateDescriptionProvider.setStateOptions(stateChannelUUID, options); - } - if (isLinked(courseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(courseChannelUUID, options); - } - if (isLinked(smartCourseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); - } - } - - @Override - public WMCapability getCapabilities() throws LGThinqApiException { - if (wmCapability == null) { - wmCapability = (WMCapability) lgThinqWMApiClientService.getCapability(getDeviceId(), - getDeviceUriJsonConfig(), false); - } - return Objects.requireNonNull(wmCapability, "Unexpected error. Return ac-capability shouldn't ever be null"); - } - - @Override - protected Logger getLogger() { - return logger; - } - - @Nullable - private WasherDryerSnapshot getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException { - // analise de platform version - if (PLATFORM_TYPE_V2.equals(lgPlatfomType)) { - return (WasherDryerSnapshot) lgThinqWMApiClientService.getDeviceData(getBridgeId(), getDeviceId()); - } else { - try { - if (!monitorV1Began) { - monitorWorkId = lgThinqWMApiClientService.startMonitor(getBridgeId(), getDeviceId()); - monitorV1Began = true; - } - } catch (LGThinqDeviceV1OfflineException e) { - forceStopDeviceV1Monitor(deviceId); - WasherDryerSnapshot shot = new WasherDryerSnapshot(); - shot.setOnline(false); - return shot; - } catch (Exception e) { - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); - } - int retries = 10; - WasherDryerSnapshot shot; - while (retries > 0) { - // try to get monitoring data result 3 times. - try { - shot = (WasherDryerSnapshot) lgThinqWMApiClientService.getMonitorData(getBridgeId(), deviceId, - monitorWorkId, DeviceTypes.WASHING_MACHINE); - if (shot != null) { - return shot; - } - Thread.sleep(500); - retries--; - } catch (LGThinqDeviceV1MonitorExpiredException e) { - forceStopDeviceV1Monitor(deviceId); - logger.info("Monitor for device {} was expired. Forcing stop and start to next cycle.", deviceId); - return null; - } catch (Exception e) { - // If it can't get monitor handler, then stop monitor and restart the process again in new - // interaction - // Force restart monitoring because of the errors returned (just in case) - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Error getting monitor data for the device:" + deviceId, e); - } - } - forceStopDeviceV1Monitor(deviceId); - throw new LGThinqApiException("Exhausted trying to get monitor data for the device:" + deviceId); - } - } - - protected void stopThingStatePolling() { - if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { - logger.debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); - thingStatePollingJob.cancel(true); - } - } - - private void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { - if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { - // start the thing polling - startThingStatePolling(); - } else if (lastThingStatus == ThingStatus.ONLINE && newStatus == ThingStatus.OFFLINE - && BRIDGE_STATUS_DETAIL_ERROR.contains(statusDetail)) { - // comunication error is not a specific Bridge error, then we must analise it to give - // this thinq the change to recovery from communication errors - if (statusDetail != ThingStatusDetail.COMMUNICATION_ERROR - || (getBridge() != null && getBridge().getStatus() != ThingStatus.ONLINE)) { - // in case of status offline, I only stop the polling if is not an COMMUNICATION_ERROR or if - // the bridge is out - stopThingStatePolling(); - } - - } - lastThingStatus = newStatus; - } - - @Override - protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetail, @Nullable String description) { - handleStatusChanged(newStatus, statusDetail); - super.updateStatus(newStatus, statusDetail, description); - } - - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceId() { - return getThing().getUID().getId(); - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } - - @Override - public String getDeviceModelName() { - return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); - } - - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } - - @Override - public boolean onDeviceStateChanged() { - // TODO - HANDLE IT, Think if it's needed - return false; - } - - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void onDeviceGone() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void dispose() { - if (thingStatePollingJob != null) { - thingStatePollingJob.cancel(true); - stopThingStatePolling(); - stopCommandExecutorQueueJob(); - thingStatePollingJob = null; - } - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - if (command instanceof RefreshType) { - updateThingStateFromLG(); - } else { - AsyncCommandParams params = new AsyncCommandParams(channelUID.getId(), command); - try { - // Ensure commands are send in a pipe per device. - commandBlockQueue.add(params); - } catch (IllegalStateException ex) { - logger.error( - "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); - updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, - "Device Command Queue is Busy"); - } - - } - } - - private final Runnable queuedCommandExecutor = new Runnable() { - @Override - public void run() { - while (true) { - AsyncCommandParams params; - try { - params = commandBlockQueue.take(); - } catch (InterruptedException e) { - logger.debug("Interrupting async command queue executor."); - return; - } - Command command = params.command; - - try { - switch (params.channelUID) { - case CHANNEL_POWER_ID: { - if (command instanceof OnOffType) { - lgThinqWMApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? DevicePowerState.DV_POWER_ON - : DevicePowerState.DV_POWER_OFF); - } else { - logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); - } - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, - params.channelUID); - } - } - } catch (LGThinqException e) { - logger.error("Error executing Command {} to the channel {}. Thing goes offline until retry", - command, params.channelUID, e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } - } - } - }; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java index cf7b4557ef7df..8e0c4b0bfe6dd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java @@ -12,8 +12,8 @@ */ package org.openhab.binding.lgthinq.internal.api; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_EMP_SESS_PATH; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_EMP_SESS_URL; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_EMP_SESS_PATH; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_EMP_SESS_URL; import java.io.Serializable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java new file mode 100644 index 0000000000000..6c0c64d034bd5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -0,0 +1,217 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.*; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.*; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQDryerHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler { + + @Nullable + private DryerCapability dryerCapability; + private final ChannelUID stateChannelUUID; + private final ChannelUID processStateChannelUUID; + private final ChannelUID dryLevelChannelUUID; + private final ChannelUID errorChannelUUID; + private final ChannelUID courseChannelUUID; + private final ChannelUID smartCourseChannelUUID; + + private final Logger logger = LoggerFactory.getLogger(LGThinQDryerHandler.class); + @NonNullByDefault + private final LGThinQDRApiClientService lgThinqDRApiClientService; + // Bridges status that this thing must top scanning for state change + private static final Set BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, + ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, + ThingStatusDetail.CONFIGURATION_ERROR); + private @Nullable ScheduledFuture thingStatePollingJob; + private @Nullable Future commandExecutorQueueJob; + + public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing, stateDescriptionProvider); + lgThinqDRApiClientService = LGThinQDRApiV2ClientServiceImpl.getInstance(); + stateChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_STATE_ID); + courseChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_COURSE_ID); + smartCourseChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_SMART_COURSE_ID); + processStateChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_PROCESS_STATE_ID); + errorChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_ERROR_ID); + dryLevelChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_DRY_LEVEL_ID); + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @Override + protected void updateDeviceChannels(DryerSnapshot shot) { + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + updateState(DR_CHANNEL_STATE_ID, new StringType(shot.getState())); + updateState(DR_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); + updateState(DR_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); + updateState(DR_CHANNEL_PROCESS_STATE_ID, new StringType(shot.getProcessState())); + updateState(DR_CHANNEL_CHILD_LOCK_ID, new StringType(shot.getChildLock())); + updateState(DR_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateState(DR_CHANNEL_DRY_LEVEL_ID, new StringType(shot.getDryLevel())); + updateState(DR_CHANNEL_ERROR_ID, new StringType(shot.getError())); + updateStatus(ThingStatus.ONLINE); + } + + @Override + protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + switch (params.channelUID) { + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqDRApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } + + @NonNull + private String emptyIfNull(@Nullable String value) { + return value == null ? "" : "" + value; + } + + @NonNull + private String keyIfValueNotFound(Map map, @NonNull String key) { + return Objects.requireNonNullElse(map.get(key), key); + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + DryerCapability drCap = getCapabilities(); + if (isLinked(stateChannelUUID)) { + List options = new ArrayList<>(); + // invert key/value + drCap.getState().forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_STATE, v)))); + stateDescriptionProvider.setStateOptions(stateChannelUUID, options); + } + if (isLinked(courseChannelUUID)) { + List options = new ArrayList<>(); + drCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(courseChannelUUID, options); + } + if (isLinked(smartCourseChannelUUID)) { + List options = new ArrayList<>(); + drCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); + } + if (isLinked(processStateChannelUUID)) { + List options = new ArrayList<>(); + drCap.getProcessStates() + .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_PROCESS_STATE, v)))); + stateDescriptionProvider.setStateOptions(processStateChannelUUID, options); + } + if (isLinked(errorChannelUUID)) { + List options = new ArrayList<>(); + drCap.getErrors().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(errorChannelUUID, options); + } + if (isLinked(dryLevelChannelUUID)) { + List options = new ArrayList<>(); + drCap.getDryLevels() + .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); + stateDescriptionProvider.setStateOptions(dryLevelChannelUUID, options); + } + } + + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqDRApiClientService; + } + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceId() { + return getThing().getUID().getId(); + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceModelName() { + return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public boolean onDeviceStateChanged() { + // TODO - HANDLE IT, Think if it's needed + return false; + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void onDeviceGone() { + // TODO - HANDLE IT, Think if it's needed + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java new file mode 100644 index 0000000000000..9a6499fbaef8d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -0,0 +1,196 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.internal.handler; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import java.util.*; +import java.util.concurrent.*; + +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiV2ClientServiceImpl; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.StringType; +import org.openhab.core.thing.*; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link LGThinQWasherHandler} is responsible for handling commands, which are + * sent to one of the channels. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler { + + private final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; + private final ChannelUID stateChannelUUID; + private final ChannelUID courseChannelUUID; + private final ChannelUID smartCourseChannelUUID; + private final Logger logger = LoggerFactory.getLogger(LGThinQWasherHandler.class); + @NonNullByDefault + private final LGThinQWMApiClientService lgThinqWMApiClientService; + + // *** Long running isolated threadpools. + private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); + + private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); + + @NonNullByDefault + + public LGThinQWasherHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { + super(thing, stateDescriptionProvider); + this.stateDescriptionProvider = stateDescriptionProvider; + lgThinqWMApiClientService = LGThinQWMApiV2ClientServiceImpl.getInstance(); + stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); + courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); + smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); + } + + static class AsyncCommandParams { + final String channelUID; + final Command command; + + public AsyncCommandParams(String channelUUID, Command command) { + this.channelUID = channelUUID; + this.command = command; + } + } + + @Override + public void initialize() { + logger.debug("Initializing Thinq thing."); + Bridge bridge = getBridge(); + initializeThing((bridge == null) ? null : bridge.getStatus()); + } + + @NonNull + private String emptyIfNull(@Nullable String value) { + return value == null ? "" : "" + value; + } + + @Override + public void updateChannelDynStateDescription() throws LGThinqApiException { + WasherCapability wmCap = getCapabilities(); + if (isLinked(stateChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); + stateDescriptionProvider.setStateOptions(stateChannelUUID, options); + } + if (isLinked(courseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(courseChannelUUID, options); + } + if (isLinked(smartCourseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); + } + } + + @Override + public LGThinQApiClientService getLgThinQAPIClientService() { + return lgThinqWMApiClientService; + } + + @Override + protected Logger getLogger() { + return logger; + } + + @Override + protected void updateDeviceChannels(WasherSnapshot shot) { + updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); + updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); + updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); + updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); + updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); + updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateStatus(ThingStatus.ONLINE); + } + + @Override + protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { + Command command = params.command; + switch (params.channelUID) { + case CHANNEL_POWER_ID: { + if (command instanceof OnOffType) { + lgThinqWMApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), + command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + } else { + logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + } + break; + } + default: { + logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + } + } + } + + @Override + public void onDeviceAdded(LGDevice device) { + // TODO - handle it. Think if it's needed + } + + @Override + public String getDeviceId() { + return getThing().getUID().getId(); + } + + @Override + public String getDeviceAlias() { + return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + } + + @Override + public String getDeviceModelName() { + return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); + } + + @Override + public String getDeviceUriJsonConfig() { + return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + } + + @Override + public boolean onDeviceStateChanged() { + // TODO - HANDLE IT, Think if it's needed + return false; + } + + @Override + public void onDeviceRemoved() { + // TODO - HANDLE IT, Think if it's needed + } + + @Override + public void onDeviceGone() { + // TODO - HANDLE IT, Think if it's needed + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java deleted file mode 100644 index 259307eb1819c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridge.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.handler; - -import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; -import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; - -/** - * The {@link LGThinqBridge} - * - * @author Nemer Daud - Initial contribution - */ -public interface LGThinqBridge { - void registerDiscoveryListener(LGThinqDiscoveryService listener); - - void registryListenerThing(LGThinqDeviceThing thing); - - void unRegistryListenerThing(LGThinqDeviceThing thing); - - LGThinqDeviceThing getThingByDeviceId(String deviceId); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java deleted file mode 100644 index 181b19f3afc83..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinqBridgeHandler.java +++ /dev/null @@ -1,327 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.handler; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_CONNECTION_DATA_FILE; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.THINQ_USER_DATA_FOLDER; - -import java.io.File; -import java.io.IOException; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinqConfiguration; -import org.openhab.binding.lgthinq.internal.LGThinqDeviceThing; -import org.openhab.binding.lgthinq.internal.api.TokenManager; -import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.LGThinqACApiV1ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.LGThinqApiClientService; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.core.config.core.status.ConfigStatusMessage; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ConfigStatusBridgeHandler; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqBridgeHandler} - * - * @author Nemer Daud - Initial contribution - */ -public class LGThinqBridgeHandler extends ConfigStatusBridgeHandler implements LGThinqBridge { - - private Map lGDeviceRegister = new ConcurrentHashMap<>(); - private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); - - static { - var logger = LoggerFactory.getLogger(LGThinqBridgeHandler.class); - try { - File directory = new File(THINQ_USER_DATA_FOLDER); - if (!directory.exists()) { - directory.mkdir(); - } - } catch (Exception e) { - logger.warn("Unable to setup thinq userdata directory: {}", e.getMessage()); - } - } - private final Logger logger = LoggerFactory.getLogger(LGThinqBridgeHandler.class); - private LGThinqConfiguration lgthinqConfig; - private TokenManager tokenManager; - private LGThinqDiscoveryService discoveryService; - private LGThinqApiClientService lgApiClient; - private @Nullable Future initJob; - private @Nullable ScheduledFuture devicePollingJob; - - public LGThinqBridgeHandler(Bridge bridge) { - super(bridge); - tokenManager = TokenManager.getInstance(); - lgApiClient = LGThinqACApiV1ClientServiceImpl.getInstance(); - lgDevicePollingRunnable = new LGDevicePollingRunnable(bridge.getUID().getId()); - } - - final ReentrantLock pollingLock = new ReentrantLock(); - - /** - * Abstract Runnable Polling Class to schedule sincronization status of the Bridge Thing Kinds ! - */ - abstract class PollingRunnable implements Runnable { - protected final String bridgeName; - protected LGThinqConfiguration lgthinqConfig; - - PollingRunnable(String bridgeName) { - this.bridgeName = bridgeName; - } - - @Override - public void run() { - try { - pollingLock.lock(); - // check if configuration file already exists - if (tokenManager.isOauthTokenRegistered(bridgeName)) { - logger.debug( - "Token authentication process has been already done. Skip first authentication process."); - try { - tokenManager.getValidRegisteredToken(bridgeName); - } catch (IOException e) { - logger.error("Error reading LGThinq TokenFile", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, - "@text/error.toke-file-corrupted"); - return; - } catch (RefreshTokenException e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, - "@text/error.toke-refresh"); - return; - } - } else { - try { - tokenManager.oauthFirstRegistration(bridgeName, lgthinqConfig.getLanguage(), - lgthinqConfig.getCountry(), lgthinqConfig.getUsername(), lgthinqConfig.getPassword(), - lgthinqConfig.getAlternativeServer()); - tokenManager.getValidRegisteredToken(bridgeName); - logger.debug("Successful getting token from LG API"); - } catch (IOException e) { - logger.debug( - "I/O error accessing json token configuration file. Updating Bridge Status to OFFLINE.", - e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/error.toke-file-access-error"); - return; - } catch (LGThinqException e) { - logger.debug("Error accessing LG API. Updating Bridge Status to OFFLINE.", e); - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, - "@text/error.lgapi-communication-error"); - return; - } - } - if (thing.getStatus() != ThingStatus.ONLINE) { - updateStatus(ThingStatus.ONLINE); - } - - try { - doConnectedRun(); - } catch (Exception e) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/error.lgapi-getting-devices"); - } - - } finally { - pollingLock.unlock(); - } - } - - protected abstract void doConnectedRun() throws IOException, LGThinqException; - } - - @Override - public void registerDiscoveryListener(LGThinqDiscoveryService listener) { - if (discoveryService == null) { - discoveryService = listener; - } - } - - /** - * Registry the OSGi services used by this Bridge. - * Eventually, the Discovery Service will be activated with this bridge as argument. - * - * @return Services to be registered to OSGi. - */ - @Override - public Collection> getServices() { - return Collections.singleton(LGThinqDiscoveryService.class); - } - - @Override - public void registryListenerThing(LGThinqDeviceThing thing) { - if (lGDeviceRegister.get(thing.getDeviceId()) == null) { - lGDeviceRegister.put(thing.getDeviceId(), thing); - // remove device from discovery list, if exists. - LGDevice device = lastDevicesDiscovered.get(thing.getDeviceId()); - if (device != null) { - discoveryService.removeLgDeviceDiscovery(device); - } - } - } - - @Override - public void unRegistryListenerThing(LGThinqDeviceThing thing) { - lGDeviceRegister.remove(thing.getDeviceId()); - } - - @Override - public LGThinqDeviceThing getThingByDeviceId(String deviceId) { - return lGDeviceRegister.get(deviceId); - } - - private LGDevicePollingRunnable lgDevicePollingRunnable; - - class LGDevicePollingRunnable extends PollingRunnable { - public LGDevicePollingRunnable(String bridgeName) { - super(bridgeName); - } - - @Override - protected void doConnectedRun() throws LGThinqException { - Map lastDevicesDiscoveredCopy = new HashMap<>(lastDevicesDiscovered); - for (final LGDevice device : lgApiClient.listAccountDevices(bridgeName)) { - String deviceId = device.getDeviceId(); - // if not registered yet, and not discovered before, then add to discovery list. - if (lGDeviceRegister.get(deviceId) == null && !lastDevicesDiscovered.containsKey(deviceId)) { - logger.debug("Adding new LG Device to things registry with id:{}", deviceId); - if (discoveryService != null) { - discoveryService.addLgDeviceDiscovery(device); - } - } - lastDevicesDiscovered.put(deviceId, device); - lastDevicesDiscoveredCopy.remove(deviceId); - } - // the rest in lastDevicesDiscoveredCopy is not more registered in LG API. Remove from discovery - lastDevicesDiscoveredCopy.forEach((deviceId, device) -> { - logger.trace("LG Device '{}' removed.", deviceId); - lastDevicesDiscovered.remove(deviceId); - - LGThinqDeviceThing deviceThing = lGDeviceRegister.get(deviceId); - if (deviceThing != null) { - deviceThing.onDeviceRemoved(); - } - if (discoveryService != null && deviceThing != null) { - discoveryService.removeLgDeviceDiscovery(device); - } - }); - } - }; - - @Override - public Collection getConfigStatus() { - List resultList = new ArrayList<>(); - if (lgthinqConfig.username.isEmpty()) { - resultList.add(ConfigStatusMessage.Builder.error("USERNAME").withMessageKeySuffix("missing field") - .withArguments("username").build()); - } - if (lgthinqConfig.password.isEmpty()) { - resultList.add(ConfigStatusMessage.Builder.error("PASSWORD").withMessageKeySuffix("missing field") - .withArguments("password").build()); - } - if (lgthinqConfig.language.isEmpty()) { - resultList.add(ConfigStatusMessage.Builder.error("LANGUAGE").withMessageKeySuffix("missing field") - .withArguments("language").build()); - } - if (lgthinqConfig.country.isEmpty()) { - resultList.add(ConfigStatusMessage.Builder.error("COUNTRY").withMessageKeySuffix("missing field") - .withArguments("country").build()); - - } - return resultList; - } - - @Override - public void handleRemoval() { - if (devicePollingJob != null) - devicePollingJob.cancel(true); - tokenManager.cleanupTokenRegistry(getBridge().getUID().getId()); - super.handleRemoval(); - } - - @Override - public void dispose() { - if (devicePollingJob != null) { - devicePollingJob.cancel(true); - devicePollingJob = null; - } - } - - @Override - public T getConfigAs(Class configurationClass) { - return super.getConfigAs(configurationClass); - } - - @Override - public void initialize() { - logger.debug("Initializing LGThinq bridge handler."); - lgthinqConfig = getConfigAs(LGThinqConfiguration.class); - lgDevicePollingRunnable.lgthinqConfig = lgthinqConfig; - - if (lgthinqConfig.username.isEmpty() || lgthinqConfig.password.isEmpty() || lgthinqConfig.language.isEmpty() - || lgthinqConfig.country.isEmpty()) { - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, - "@text/error.mandotory-fields-missing"); - } else { - updateStatus(ThingStatus.UNKNOWN); - startLGThinqDevicePolling(); - } - } - - @Override - public void handleConfigurationUpdate(Map configurationParameters) { - logger.debug("Bridge Configuration was updated. Cleaning the token registry file"); - File f = new File(String.format(THINQ_CONNECTION_DATA_FILE, getThing().getUID().getId())); - if (f.isFile()) { - // file exists. Delete it - if (!f.delete()) { - logger.error("Error deleting file:{}", f.getAbsolutePath()); - } - } - super.handleConfigurationUpdate(configurationParameters); - } - - private void startLGThinqDevicePolling() { - // stop current scheduler, if any - if (devicePollingJob != null && !devicePollingJob.isDone()) { - devicePollingJob.cancel(true); - } - long pollingInterval; - int configPollingInterval = lgthinqConfig.getPollingIntervalSec(); - // It's not recommended to polling for resources in LG API short intervals to do not enter in BlackList - if (configPollingInterval < 300) { - pollingInterval = TimeUnit.SECONDS.toSeconds(300); - logger.info("Wrong configuration value for polling interval. Using default value: {}s", pollingInterval); - } else { - pollingInterval = configPollingInterval; - } - // submit instantlly and schedule for the next polling interval. - scheduler.submit(lgDevicePollingRunnable); - devicePollingJob = scheduler.scheduleWithFixedDelay(lgDevicePollingRunnable, pollingInterval, pollingInterval, - TimeUnit.SECONDS); - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index 0404d7d141a0d..f0610362fba58 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -20,7 +20,10 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.*; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.*; /** @@ -29,7 +32,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGThinQApiClientService { +public interface LGThinQApiClientService { List listAccountDevices(String bridgeName) throws LGThinqApiException; @@ -49,9 +52,6 @@ public interface LGThinQApiClientService { - void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException; - - void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException; +public interface LGThinQDRApiClientService extends LGThinQApiClientService { } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java index 29d197c4662ee..6010a18b83663 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java @@ -12,12 +12,9 @@ */ package org.openhab.binding.lgthinq.lgservices; -import java.util.Map; - import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; -import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; /** * The {@link LGThinQWMApiClientService} @@ -25,9 +22,5 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGThinQWMApiClientService extends LGThinQApiClientService { - void remoteStart(String bridgeName, WasherDryerCapability cap, String deviceId, Map data) - throws LGThinqApiException; - - void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException; +public interface LGThinQWMApiClientService extends LGThinQApiClientService { } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java deleted file mode 100644 index 0d0acddfbc3da..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiClientService.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; - -/** - * The {@link LGThinqACApiClientService} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface LGThinqACApiClientService extends LGThinqApiClientService { - void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; - - void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; - - void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGThinqApiException; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java deleted file mode 100644 index fbf90659c718e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV1ClientServiceImpl.java +++ /dev/null @@ -1,175 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.IOException; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import org.openhab.binding.lgthinq.lgservices.model.SnapshotFactory; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinqACApiV1ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqACApiV1ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqACApiClientService { - private static final LGThinqACApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinqACApiV1ClientServiceImpl.class); - - static { - instance = new LGThinqACApiV1ClientServiceImpl(); - } - - public static LGThinqACApiClientService getInstance() { - return instance; - } - - /** - * Get snapshot data from the device. - * It works only for API V2 device versions! - * - * @param deviceId device ID for de desired V2 LG Thinq. - * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. - */ - @Override - @Nullable - public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { - throw new UnsupportedOperationException("Method not supported in V1 API device."); - } - - private RestResult sendControlCommands(String bridgeName, String deviceId, String keyName, int value) - throws Exception { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - - String payload = String.format( - "{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"Control\"," + " \"cmdOpt\": \"Set\"," - + " \"value\": {\"%s\": \"%d\"}," + " \"deviceId\": \"%s\"," - + " \"workId\": \"%s\"," + " \"data\": \"\"" + " }\n" + "}", - keyName, value, deviceId, UUID.randomUUID().toString()); - return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); - } - - @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", newPowerState.commandValue()); - handleV1GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting device power", e); - } - } - - @Override - public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "OpMode", newOpMode); - - handleV1GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting operation mode", e); - } - } - - @Override - public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "WindStrength", newFanSpeed); - - handleV1GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting fan speed", e); - } - } - - @Override - public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "TempCfg", newTargetTemp.commandValue()); - - handleV1GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting target temperature", e); - } - } - - @Override - public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId, DeviceTypes deviceType) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" - + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" - + " ]\n" + " }\n" + "}", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - Map envelop = null; - // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with - // offline flag. - try { - envelop = handleV1GenericErrorResult(resp); - } catch (LGThinqDeviceV1OfflineException e) { - ACSnapshot shot = new ACSnapshot(); - shot.setOnline(false); - return shot; - } - if (envelop.get("workList") != null - && ((Map) envelop.get("workList")).get("returnData") != null) { - Map workList = ((Map) envelop.get("workList")); - if (!"0000".equals(workList.get("returnCode"))) { - logErrorResultCodeMessage((String) workList.get("resultCode")); - LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( - String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); - logger.warn("{}", e.getMessage()); - throw e; - } - - String jsonMonDataB64 = (String) workList.get("returnData"); - String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); - Snapshot shot = SnapshotFactory.getInstance().create(jsonMon, deviceType); - shot.setOnline("E".equals(workList.get("deviceState"))); - return shot; - } else { - // no data available yet - return null; - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java deleted file mode 100644 index 9dcddb0ebe43a..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqACApiV2ClientServiceImpl.java +++ /dev/null @@ -1,178 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; - -import java.io.IOException; -import java.util.Map; - -import javax.ws.rs.core.UriBuilder; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACTargetTmp; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; - -/** - * The {@link LGThinqACApiV2ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqACApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqACApiClientService { - private static final LGThinqACApiClientService instance; - private static final Logger logger = LoggerFactory.getLogger(LGThinqACApiV2ClientServiceImpl.class); - - static { - instance = new LGThinqACApiV2ClientServiceImpl(); - } - - public static LGThinqACApiClientService getInstance() { - return instance; - } - - private Map getCommonV2Headers(String language, String country, String accessToken, - String userNumber) { - return getCommonHeaders(language, country, accessToken, userNumber); - } - - private RestResult sendControlCommands(String bridgeName, String deviceId, String command, String keyName, - int value) throws Exception { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) - .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId)); - Map headers = getCommonV2Headers(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String payload = String.format("{\n" + "\"ctrlKey\": \"basicCtrl\",\n" + "\"command\": \"%s\",\n" - + "\"dataKey\": \"%s\",\n" + "\"dataValue\": %d}", command, keyName, value); - return RestUtils.postCall(builder.build().toURL().toString(), headers, payload); - } - - @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Operation", "airState.operation", - newPowerState.commandValue()); - handleV2GenericErrorResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting device power", e); - } - } - - @Override - public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.opMode", newOpMode); - handleV2GenericErrorResult(resp); - } catch (LGThinqApiException e) { - throw e; - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting operation mode", e); - } - } - - @Override - public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.windStrength", newFanSpeed); - handleV2GenericErrorResult(resp); - } catch (LGThinqApiException e) { - throw e; - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting operation mode", e); - } - } - - @Override - public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) - throws LGThinqApiException { - try { - RestResult resp = sendControlCommands(bridgeName, deviceId, "Set", "airState.tempState.target", - newTargetTemp.commandValue()); - handleV2GenericErrorResult(resp); - } catch (LGThinqApiException e) { - throw e; - } catch (Exception e) { - throw new LGThinqApiException("Error adjusting operation mode", e); - } - } - - /** - * Start monitor data form specific device. This is old one, works only on V1 API supported devices. - * - * @param deviceId Device ID - * @return Work1 to be uses to grab data during monitoring. - * @throws LGThinqApiException If some communication error occur. - */ - @Override - public String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { - throw new UnsupportedOperationException("Not supported in V2 API."); - } - - private void handleV2GenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { - Map metaResult; - if (resp == null) { - return; - } - if (resp.getStatusCode() != 200) { - logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException( - String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference>() { - }); - if (!"0000".equals(metaResult.get("resultCode"))) { - logErrorResultCodeMessage((String) metaResult.get("resultCode")); - throw new LGThinqApiException( - String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", - metaResult.get("resultCode"))); - } - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream", e); - } - - } - } - - @Override - public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { - throw new UnsupportedOperationException("Not supported in V2 API."); - } - - @Override - public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId, DeviceTypes deviceType) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { - throw new UnsupportedOperationException("Not supported in V2 API."); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java deleted file mode 100644 index 8aa1e3d9aede3..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientService.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import java.io.File; -import java.io.IOException; -import java.util.List; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.*; - -/** - * The {@link LGThinqApiClientService} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface LGThinqApiClientService { - - List listAccountDevices(String bridgeName) throws LGThinqApiException; - - Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException; - - /** - * Retrieve actual data from device (its sensors and points states). - * - * @param deviceId device number - * @return return snapshot state of the device - * @throws LGThinqApiException if some error interacting with LG API Server occur. - */ - @Nullable - Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; - - void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) throws LGThinqApiException; - - String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException; - - Capability getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException; - - File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) - throws LGThinqApiException, IOException; - - void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqException, IOException; - - @Nullable - Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId, - DeviceTypes deviceType) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java deleted file mode 100644 index 34474809a7267..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqApiClientServiceImpl.java +++ /dev/null @@ -1,384 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.StandardCopyOption; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinqBindingConstants; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.*; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * The {@link LGThinqACApiV1ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public abstract class LGThinqApiClientServiceImpl implements LGThinqApiClientService { - private static final Logger logger = LoggerFactory.getLogger(LGThinqApiClientServiceImpl.class); - protected final ObjectMapper objectMapper = new ObjectMapper(); - protected final TokenManager tokenManager; - - protected LGThinqApiClientServiceImpl() { - this.tokenManager = TokenManager.getInstance(); - } - - static Map getCommonHeaders(String language, String country, String accessToken, - String userNumber) { - Map headers = new HashMap<>(); - headers.put("Accept", "application/json"); - headers.put("Content-type", "application/json;charset=UTF-8"); - headers.put("x-api-key", V2_API_KEY); - headers.put("x-client-id", V2_CLIENT_ID); - headers.put("x-country-code", country); - headers.put("x-language-code", language); - headers.put("x-message-id", UUID.randomUUID().toString()); - headers.put("x-service-code", SVC_CODE); - headers.put("x-service-phase", V2_SVC_PHASE); - headers.put("x-thinq-app-level", V2_APP_LEVEL); - headers.put("x-thinq-app-os", V2_APP_OS); - headers.put("x-thinq-app-type", V2_APP_TYPE); - headers.put("x-thinq-app-ver", V2_APP_VER); - headers.put("x-thinq-security-key", SECURITY_KEY); - if (!accessToken.isBlank()) - headers.put("x-emp-token", accessToken); - if (!userNumber.isBlank()) - headers.put("x-user-no", userNumber); - return headers; - } - - /** - * Even using V2 URL, this endpoint support grab informations about account devices from V1 and V2. - * - * @return list os LG Devices. - * @throws LGThinqApiException if some communication error occur. - */ - @Override - public List listAccountDevices(String bridgeName) throws LGThinqApiException { - try { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); - return handleListAccountDevicesResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Erros list account devices from LG Server API", e); - } - } - - @Override - public File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { - File regFile = new File(String.format(BASE_CAP_CONFIG_DATA_FILE, deviceId)); - try { - if (regFile.isFile() || forceRecreate) { - try (InputStream in = new URL(uri).openStream()) { - Files.copy(in, regFile.toPath(), StandardCopyOption.REPLACE_EXISTING); - } - } - } catch (IOException e) { - throw new LGThinqApiException("Error reading IO interface", e); - } - return regFile; - } - - /** - * Get device settings and snapshot for a specific device. - * It works only for API V2 device versions! - * - * @param deviceId device ID for de desired V2 LG Thinq. - * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. - */ - @Override - public Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException { - try { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) - .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - RestResult resp = RestUtils.getCall(builder.build().toURL().toString(), headers, null); - return handleDeviceSettingsResult(resp); - } catch (Exception e) { - throw new LGThinqApiException("Erros list account devices from LG Server API", e); - } - } - - private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { - return genericHandleDeviceSettingsResult(resp, logger, objectMapper); - } - - @SuppressWarnings("unchecked") - static Map genericHandleDeviceSettingsResult(RestResult resp, Logger logger, - ObjectMapper objectMapper) throws LGThinqApiException { - Map deviceSettings; - if (resp.getStatusCode() != 200) { - logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException(String.format( - "Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - deviceSettings = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - if (!"0000".equals(deviceSettings.get("resultCode"))) { - logErrorResultCodeMessage((String) deviceSettings.get("resultCode")); - throw new LGThinqApiException( - String.format("Status error getting device list. resultCode must be 0000, but was:%s", - deviceSettings.get("resultCode"))); - } - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream", e); - } - - } - return Objects.requireNonNull((Map) deviceSettings.get("result"), - "Unexpected json result asking for Device Settings. Node 'result' no present"); - } - - @SuppressWarnings("unchecked") - private List handleListAccountDevicesResult(RestResult resp) throws LGThinqApiException { - Map devicesResult; - List devices; - if (resp.getStatusCode() != 200) { - logger.error("Error calling device list from LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException(String - .format("Error calling device list from LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - if (!"0000".equals(devicesResult.get("resultCode"))) { - logErrorResultCodeMessage((String) devicesResult.get("resultCode")); - throw new LGThinqApiException( - String.format("Status error getting device list. resultCode must be 0000, but was:%s", - devicesResult.get("resultCode"))); - } - List> items = (List>) ((Map) devicesResult - .get("result")).get("item"); - devices = objectMapper.convertValue(items, new TypeReference<>() { - }); - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream.", e); - } - - } - - return devices; - } - - protected static void logErrorResultCodeMessage(@Nullable String resultCode) { - if (resultCode == null) { - return; - } - String errMessage = ERROR_CODE_RESPONSE.get(resultCode.trim()); - logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", resultCode, - errMessage == null ? "UNKNOW ERROR MESSAGE" : errMessage); - } - - /** - * Get capability em registry/cache on file for next consult - * - * @param deviceId ID of the device - * @param uri URI of the config capability - * @return return simplified capability - * @throws LGThinqApiException If some error occurr - */ - public Capability getCapability(String deviceId, String uri, boolean forceRecreate) throws LGThinqApiException { - try { - File regFile = loadDeviceCapability(deviceId, uri, forceRecreate); - Map mapper = objectMapper.readValue(regFile, new TypeReference<>() { - }); - return CapabilityFactory.getInstance().create(mapper); - } catch (IOException e) { - throw new LGThinqApiException("Error reading IO interface", e); - } - } - - @NonNull - protected Map handleV1GenericErrorResult(@Nullable RestResult resp) - throws LGThinqApiException, LGThinqDeviceV1OfflineException { - Map metaResult; - Map envelope = Collections.emptyMap(); - if (resp == null) { - return envelope; - } - if (resp.getStatusCode() != 200) { - logger.error("Error returned by LG Server API. The reason is:{}", resp.getJsonResponse()); - throw new LGThinqApiException( - String.format("Error returned by LG Server API. The reason is:%s", resp.getJsonResponse())); - } else { - try { - metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - envelope = (Map) metaResult.get("lgedmRoot"); - if (envelope == null) { - throw new LGThinqApiException(String.format( - "Unexpected json body returned (without root node lgedmRoot): %s", resp.getJsonResponse())); - } else if (!"0000".equals(envelope.get("returnCd"))) { - logErrorResultCodeMessage((String) envelope.get("returnCd")); - if ("0106".equals(envelope.get("returnCd")) || "D".equals(envelope.get("deviceState"))) { - // Disconnected Device - throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); - } - throw new LGThinqApiException( - String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", - metaResult.get("returnCd"))); - } - } catch (JsonProcessingException e) { - throw new IllegalStateException("Unknown error occurred deserializing json stream", e); - } - } - return envelope; - } - - public @Nullable Snapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId, DeviceTypes deviceType) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" - + " \"deviceId\":\"%s\",\n" + " \"workId\":\"%s\"\n" + " }\n" - + " ]\n" + " }\n" + "}", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - Map envelop = null; - // to unify the same behaviour then V2, this method handle Offline Exception and return a dummy shot with - // offline flag. - try { - envelop = handleV1GenericErrorResult(resp); - } catch (LGThinqDeviceV1OfflineException e) { - ACSnapshot shot = new ACSnapshot(); - shot.setOnline(false); - return shot; - } - if (envelop.get("workList") != null - && ((Map) envelop.get("workList")).get("returnData") != null) { - Map workList = ((Map) envelop.get("workList")); - if (!"0000".equals(workList.get("returnCode"))) { - logErrorResultCodeMessage((String) workList.get("resultCode")); - LGThinqDeviceV1MonitorExpiredException e = new LGThinqDeviceV1MonitorExpiredException( - String.format("Monitor for device %s has expired. Please, refresh the monitor.", deviceId)); - logger.warn("{}", e.getMessage()); - throw e; - } - - String jsonMonDataB64 = (String) workList.get("returnData"); - String jsonMon = new String(Base64.getDecoder().decode(jsonMonDataB64)); - Snapshot shot = SnapshotFactory.getInstance().create(jsonMon, deviceType); - shot.setOnline("E".equals(workList.get("deviceState"))); - return shot; - } else { - // no data available yet - return null; - } - } - - /** - * Get snapshot data from the device. - * It works only for API V2 device versions! - * - * @param deviceId device ID for de desired V2 LG Thinq. - * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. - */ - @Override - @Nullable - public Snapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { - Map deviceSettings = getDeviceSettings(bridgeName, deviceId); - if (deviceSettings.get("snapshot") != null) { - Map snapMap = (Map) deviceSettings.get("snapshot"); - if (logger.isDebugEnabled()) { - try { - objectMapper.writeValue(new File(String.format( - LGThinqBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", - deviceId)), deviceSettings); - } catch (IOException e) { - logger.error("Error saving data trace", e); - } - } - if (snapMap == null) { - // No snapshot value provided - return null; - } - - Snapshot shot = SnapshotFactory.getInstance().create(deviceSettings); - shot.setOnline((Boolean) snapMap.get("online")); - return shot; - } - return null; - } - - /** - * Start monitor data form specific device. This is old one, works only on V1 API supported devices. - * - * @param deviceId Device ID - * @return Work1 to be uses to grab data during monitoring. - * @throws LGThinqApiException If some communication error occur. - */ - @Override - public String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String workerId = UUID.randomUUID().toString(); - String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Start\"," - + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workerId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - return Objects.requireNonNull((String) handleV1GenericErrorResult(resp).get("workId"), - "Unexpected StartMonitor json result. Node 'workId' not present"); - } - - @Override - public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { - TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); - Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), - token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," - + "\"deviceId\": \"%s\"," + "\"workId\": \"%s\"" + "} }", deviceId, workId); - RestResult resp = RestUtils.postCall(builder.build().toURL().toString(), headers, jsonData); - handleV1GenericErrorResult(resp); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java deleted file mode 100644 index 9575166ed674e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiClientService.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -/** - * The {@link LGThinqWMApiClientService} - * - * @author Nemer Daud - Initial contribution - */ -public interface LGThinqWMApiClientService extends LGThinqApiClientService { -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java deleted file mode 100644 index fdd617c2b15d4..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinqWMApiV2ClientServiceImpl.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices; - -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; - -/** - * The {@link LGThinqWMApiV2ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -public class LGThinqWMApiV2ClientServiceImpl extends LGThinqApiClientServiceImpl implements LGThinqWMApiClientService { - - private static final LGThinqWMApiClientService instance; - static { - instance = new LGThinqWMApiV2ClientServiceImpl(); - } - - public static LGThinqWMApiClientService getInstance() { - return instance; - } - - @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { - throw new UnsupportedOperationException("Not implemented yet."); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 1542702e34172..81d394ec43076 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; import java.util.HashMap; import java.util.Map; @@ -21,7 +21,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherDryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; @@ -40,7 +41,7 @@ public class SnapshotFactory { instance = new SnapshotFactory(); } - public static final SnapshotFactory getInstance() { + public static SnapshotFactory getInstance() { return instance; } @@ -52,20 +53,22 @@ public static final SnapshotFactory getInstance() { * @return returns Snapshot implementation based on device type provided * @throws LGThinqApiException any error. */ - public Snapshot create(String snapshotDataJson, DeviceTypes deviceType) throws LGThinqApiException { + public S create(String snapshotDataJson, DeviceTypes deviceType, Class clazz) + throws LGThinqApiException { try { Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { }); Map deviceSetting = new HashMap<>(); deviceSetting.put("deviceType", deviceType.deviceTypeId()); deviceSetting.put("snapshot", snapshotMap); - return create(deviceSetting); + return create(deviceSetting, clazz); } catch (JsonProcessingException e) { throw new LGThinqApiException("Unexpected Error unmarshalling json to map", e); } } - public Snapshot create(Map deviceSettings) throws LGThinqApiException { + public S create(Map deviceSettings, Class clazz) + throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); Map snapMap = ((Map) deviceSettings.get("snapshot")); if (snapMap == null) { @@ -74,7 +77,7 @@ public Snapshot create(Map deviceSettings) throws LGThinqApiExce LGAPIVerion version = discoveryAPIVersion(snapMap, type); switch (type) { case AIR_CONDITIONER: - return objectMapper.convertValue(snapMap, ACSnapshot.class); + return clazz.cast(objectMapper.convertValue(snapMap, ACSnapshot.class)); case WASHING_MACHINE: switch (version) { case V1_0: { @@ -84,10 +87,21 @@ public Snapshot create(Map deviceSettings) throws LGThinqApiExce Map washerDryerMap = Objects.requireNonNull( (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), "washerDryer node must be present in the snapshot"); - return objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class); + return clazz.cast(objectMapper.convertValue(washerDryerMap, WasherSnapshot.class)); + } + } + case DRYER: + switch (version) { + case V1_0: { + throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + } + case V2_0: { + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), + "washerDryer node must be present in the snapshot"); + return clazz.cast(objectMapper.convertValue(washerDryerMap, DryerSnapshot.class)); } } - default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } @@ -110,7 +124,7 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes throw new IllegalStateException( "Unexpected error. Can't find key node attributes to determine AC API version."); } - + case DRYER: case WASHING_MACHINE: return LGAPIVerion.V2_0; default: diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java new file mode 100644 index 0000000000000..e11dc05da9ab8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.dryer; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.Capability; + +/** + * The {@link DryerCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class DryerCapability extends Capability { + public enum MonitoringCap { + STATE("state"), + PROCESS_STATE("processState"), + DRY_LEVEL("dryLevel"), + ERROR("error"); + + final String value; + + MonitoringCap(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + private static class MonitoringValue { + private final Map state = new LinkedHashMap(); + private final Map dryLevel = new LinkedHashMap(); + private final Map error = new LinkedHashMap(); + private final Map processState = new LinkedHashMap(); + private boolean hasChildLock; + private boolean hasRemoteStart; + } + + private final MonitoringValue monitoringValue = new MonitoringValue(); + private final Map courses = new LinkedHashMap(); + + private final Map smartCourses = new LinkedHashMap(); + + public Map getCourses() { + return courses; + } + + public Map getSmartCourses() { + return smartCourses; + } + + public void addCourse(String courseLabel, String courseName) { + courses.put(courseLabel, courseName); + } + + public void addSmartCourse(String courseLabel, String courseName) { + smartCourses.put(courseLabel, courseName); + } + + public Map getState() { + return monitoringValue.state; + } + + public Map getDryLevels() { + return monitoringValue.dryLevel; + } + + public Map getErrors() { + return monitoringValue.error; + } + + public Map getProcessStates() { + return monitoringValue.processState; + } + + public boolean hasRemoteStart() { + return monitoringValue.hasRemoteStart; + } + + public boolean hasChildLock() { + return monitoringValue.hasChildLock; + } + + public void setChildLock(boolean hasChildLock) { + monitoringValue.hasChildLock = hasChildLock; + } + + public void setRemoteStart(boolean hasRemoteStart) { + monitoringValue.hasRemoteStart = hasRemoteStart; + } + + public void addMonitoringValue(MonitoringCap monCap, String key, String value) { + switch (monCap) { + case STATE: + monitoringValue.state.put(key, value); + break; + case PROCESS_STATE: + monitoringValue.processState.put(key, value); + break; + case DRY_LEVEL: + monitoringValue.dryLevel.put(key, value); + break; + case ERROR: + monitoringValue.error.put(key, value); + break; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java new file mode 100644 index 0000000000000..70e60d595b95f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java @@ -0,0 +1,164 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.dryer; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link DryerSnapshot} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->washerDryer, but this POJO expects + * to map field below washerDryer + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class DryerSnapshot implements Snapshot { + private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; + private String state = ""; + private boolean online; + private String course = ""; + private String smartCourse = ""; + private String childLock = ""; + private String processState = ""; + private Double remainingHour = 0.00; + private Double remainingMinute = 0.00; + private String dryLevel = ""; + private String error = ""; + + @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) + @JsonProperty("courseDryer24inchBase") + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } + + @JsonProperty("dryLevel") + public String getDryLevel() { + return dryLevel; + } + + public void setDryLevel(String dryLevel) { + this.dryLevel = dryLevel; + } + + public void setRemainingMinute(Double remainingMinute) { + this.remainingMinute = remainingMinute; + } + + @JsonProperty("error") + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + @JsonProperty("processState") + public String getProcessState() { + return processState; + } + + public void setProcessState(String processState) { + this.processState = processState; + } + + @Override + public DevicePowerState getPowerStatus() { + return powerState; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + throw new IllegalArgumentException("This method must not be accessed."); + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } + + @JsonProperty("state") + @JsonAlias({ "state", "State" }) + public String getState() { + return state; + } + + @JsonProperty("smartCourseDryer24inchBase") + @JsonAlias({ "smartCourseDryer24inchBase", "SmartCourse", "smartCourseDryer27inchBase" }) + public String getSmartCourse() { + return smartCourse; + } + + public void setSmartCourse(String smartCourse) { + this.smartCourse = smartCourse; + } + + @JsonProperty("childLock") + public String getChildLock() { + return childLock; + } + + public void setChildLock(String childLock) { + this.childLock = childLock; + } + + @JsonIgnore + public String getRemainingTime() { + return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); + } + + @JsonProperty("remainTimeHour") + @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + public Double getRemainingHour() { + return remainingHour; + } + + public void setRemainingHour(Double remainingHour) { + this.remainingHour = remainingHour; + } + + @JsonProperty("remainTimeMinute") + @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + public Double getRemainingMinute() { + return remainingMinute; + } + + public void setState(String state) { + this.state = state; + if (state.equals(WM_POWER_OFF_VALUE)) { + powerState = DevicePowerState.DV_POWER_OFF; + } else { + powerState = DevicePowerState.DV_POWER_ON; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java similarity index 97% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java index 4294f98cc1b26..aa6dcbcb95501 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WMCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java @@ -19,12 +19,12 @@ import org.openhab.binding.lgthinq.lgservices.model.Capability; /** - * The {@link WMCapability} + * The {@link WasherCapability} * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class WMCapability extends Capability { +public class WasherCapability extends Capability { public enum MonitoringCap { STATE("state"), SOIL_WASH("soilWash"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java similarity index 76% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java index 53943e435cb9f..5cc2df814d07a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java @@ -12,18 +12,19 @@ */ package org.openhab.binding.lgthinq.lgservices.model.washer; -import static org.openhab.binding.lgthinq.internal.LGThinqBindingConstants.WM_POWER_OFF_VALUE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; /** - * The {@link WasherDryerSnapshot} + * The {@link WasherSnapshot} * This map the snapshot result from Washing Machine devices * This json payload come with path: snapshot->washerDryer, but this POJO expects * to map field below washerDryer @@ -32,7 +33,7 @@ */ @NonNullByDefault @JsonIgnoreProperties(ignoreUnknown = true) -public class WasherDryerSnapshot implements Snapshot { +public class WasherSnapshot implements Snapshot { private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; private String state = ""; private boolean online; @@ -40,6 +41,8 @@ public class WasherDryerSnapshot implements Snapshot { private String smartCourse = ""; private String temperatureLevel = ""; private String doorLock = ""; + private Double remainingHour = 0.00; + private Double remainingMinute = 0.00; @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) @JsonProperty("courseFL24inchBaseTitan") @@ -83,6 +86,31 @@ public String getSmartCourse() { return smartCourse; } + @JsonIgnore + public String getRemainingTime() { + return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); + } + + @JsonProperty("remainTimeHour") + @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + public Double getRemainingHour() { + return remainingHour; + } + + public void setRemainingHour(Double remainingHour) { + this.remainingHour = remainingHour; + } + + @JsonProperty("remainTimeMinute") + @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + public Double getRemainingMinute() { + return remainingMinute; + } + + public void setRemainingMinute(Double remainingMinute) { + this.remainingMinute = remainingMinute; + } + public void setSmartCourse(String smartCourse) { this.smartCourse = smartCourse; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 11abb169992ed..533acc1269400 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -10,8 +10,8 @@ - - LG Thinq Washing Machine + + LG ThinQ Washing Machine @@ -20,6 +20,7 @@ + From 367aa4639cf01d8bb379a849c48e685ce950b147 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 15 Feb 2022 09:07:21 -0300 Subject: [PATCH 080/130] [lgthinq][Fix] Fixed some Channel's mapping errors in Washing Machine Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 11 ----------- .../internal/handler/LGThinQWasherHandler.java | 17 +++++++++-------- .../src/main/resources/OH-INF/thing/washer.xml | 2 +- 3 files changed, 10 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 6c0c64d034bd5..2d04da2af8f69 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -17,7 +17,6 @@ import java.util.*; import java.util.concurrent.*; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; @@ -116,16 +115,6 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa } } - @NonNull - private String emptyIfNull(@Nullable String value) { - return value == null ? "" : "" + value; - } - - @NonNull - private String keyIfValueNotFound(Map map, @NonNull String key) { - return Objects.requireNonNullElse(map.get(key), key); - } - @Override public void updateChannelDynStateDescription() throws LGThinqApiException { DryerCapability drCap = getCapabilities(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 9a6499fbaef8d..6a9a38360c89e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -17,9 +17,7 @@ import java.util.*; import java.util.concurrent.*; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -50,6 +48,7 @@ public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler options = new ArrayList<>(); - wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, emptyIfNull(CAP_WP_STATE.get(k))))); + wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_STATE, k)))); stateDescriptionProvider.setStateOptions(stateChannelUUID, options); } if (isLinked(courseChannelUUID)) { @@ -110,6 +105,12 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); } + if (isLinked(temperatureChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getTemperature() + .forEach((k, v) -> options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_TEMPERATURE, k)))); + stateDescriptionProvider.setStateOptions(temperatureChannelUUID, options); + } } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 533acc1269400..0024e9a2270e8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -20,7 +20,7 @@ - + From b856f8bd4ed6cba5b1a8e5247a455edaadc2d7ab Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 16 Feb 2022 11:04:56 -0300 Subject: [PATCH 081/130] [lgthinq][Fix] Fixed tests, some channal options translations and Power Channel switch. Signed-off-by: nemerdaud --- .../lgthinq/internal/handler/LGThinQWasherHandler.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 6a9a38360c89e..3992ad3bb5cc6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -82,7 +82,7 @@ public AsyncCommandParams(String channelUUID, Command command) { @Override public void initialize() { - logger.debug("Initializing Thinq thing."); + logger.debug("Initializing Thinq thing. Washer Thing v0.1"); Bridge bridge = getBridge(); initializeThing((bridge == null) ? null : bridge.getStatus()); } @@ -125,7 +125,8 @@ protected Logger getLogger() { @Override protected void updateDeviceChannels(WasherSnapshot shot) { - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); + updateState(CHANNEL_POWER_ID, + (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); From 2f2002ff96c30138b60b34b79cafa644c8d9b819 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Mon, 21 Feb 2022 16:43:58 -0300 Subject: [PATCH 082/130] [lgthinq][Fix] Fixed bug related to when device goes offline (lost connection); handle better error results, fixed others minor bugs. Signed-off-by: nemerdaud --- .../lgthinq/internal/handler/LGThinQDryerHandler.java | 1 - .../lgthinq/internal/handler/LGThinQWasherHandler.java | 1 - .../src/main/resources/OH-INF/i18n/lgthinq.properties | 3 +++ .../src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties | 5 ++++- 4 files changed, 7 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 2d04da2af8f69..90124da21c140 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -93,7 +93,6 @@ protected void updateDeviceChannels(DryerSnapshot shot) { updateState(DR_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); updateState(DR_CHANNEL_DRY_LEVEL_ID, new StringType(shot.getDryLevel())); updateState(DR_CHANNEL_ERROR_ID, new StringType(shot.getError())); - updateStatus(ThingStatus.ONLINE); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 3992ad3bb5cc6..f3da5e19ff657 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -133,7 +133,6 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); - updateStatus(ThingStatus.ONLINE); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index 5b548f54302e3..168deba8582ec 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -22,3 +22,6 @@ error.toke-refresh = Error refreshing LGThinq Token. Try to delete it (in data d error.lgapi-communication-error = Generic Error in the LG API communication process. error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. error.lgapi-getting-devices = Error getting devices from LG API in scanner process. + +# OFFLINE Statuses +offline.device-disconnected = Device is disconnected. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties index de4244ee95061..17957c319bdb7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties @@ -21,4 +21,7 @@ error.toke-file-access-error = Error Handling Token Configuration File. error.toke-refresh = Error refreshing LGThinq Token. Try to delete it (in data directory) to the bridge automatically recreate it. error.lgapi-communication-error = Generic Error in the LG API communication process. error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. -error.lgapi-getting-devices = Error getting devices from LG API in scanner process. +error.lgapi-getting-devices = Error getting devices from LG APIevice is disconnected in scanner process. + +# OFFLINE Statuses +offline.device-disconnected = Dispositivo desconectado. From 9e319d59c3d2db9771c3a643203b1af83e87aaee Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Tue, 22 Feb 2022 21:00:49 -0300 Subject: [PATCH 083/130] [lgthinq][Feat] Add new channel DownloadedCourse for Washers; reset WM channels to default values when the device disconnected (offline from LG API). Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 13 +------- .../handler/LGThinQWasherHandler.java | 33 +++++++++++-------- .../model/washer/WasherSnapshot.java | 11 +++++++ .../main/resources/OH-INF/thing/washer.xml | 1 + 4 files changed, 33 insertions(+), 25 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 90124da21c140..8fd13567568fb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -177,29 +177,18 @@ public String getDeviceAlias() { return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); } - @Override - public String getDeviceModelName() { - return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); - } - @Override public String getDeviceUriJsonConfig() { return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); } - @Override - public boolean onDeviceStateChanged() { - // TODO - HANDLE IT, Think if it's needed - return false; - } - @Override public void onDeviceRemoved() { // TODO - HANDLE IT, Think if it's needed } @Override - public void onDeviceGone() { + public void onDeviceDisconnected() { // TODO - HANDLE IT, Think if it's needed } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index f3da5e19ff657..945ccff29639c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -48,6 +48,7 @@ public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler options.add(new StateOption(k, emptyIfNull(v)))); stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); } + if (isLinked(downloadedCourseChannelUUID)) { + List options = new ArrayList<>(); + wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); + stateDescriptionProvider.setStateOptions(downloadedCourseChannelUUID, options); + } if (isLinked(temperatureChannelUUID)) { List options = new ArrayList<>(); wmCap.getTemperature() @@ -133,6 +140,7 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); } @Override @@ -169,29 +177,28 @@ public String getDeviceAlias() { return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); } - @Override - public String getDeviceModelName() { - return emptyIfNull(getThing().getProperties().get(MODEL_NAME)); - } - @Override public String getDeviceUriJsonConfig() { return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); } - @Override - public boolean onDeviceStateChanged() { - // TODO - HANDLE IT, Think if it's needed - return false; - } - @Override public void onDeviceRemoved() { // TODO - HANDLE IT, Think if it's needed } + /** + * Put the channels in default state if the device is disconnected or gone. + */ @Override - public void onDeviceGone() { - // TODO - HANDLE IT, Think if it's needed + public void onDeviceDisconnected() { + updateState(CHANNEL_POWER_ID, OnOffType.OFF); + updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); + updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); + updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); + updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); + updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType("NOT_SELECTED")); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java index 5cc2df814d07a..993b88f0cac3c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java @@ -39,6 +39,7 @@ public class WasherSnapshot implements Snapshot { private boolean online; private String course = ""; private String smartCourse = ""; + private String downloadedCourse = ""; private String temperatureLevel = ""; private String doorLock = ""; private Double remainingHour = 0.00; @@ -86,6 +87,16 @@ public String getSmartCourse() { return smartCourse; } + @JsonProperty("downloadedCourseFL24inchBaseTitan") + @JsonAlias({ "downloadedCourseFLUpper25inchBaseUS" }) + public String getDownloadedCourse() { + return downloadedCourse; + } + + public void setDownloadedCourse(String downloadedCourse) { + this.downloadedCourse = downloadedCourse; + } + @JsonIgnore public String getRemainingTime() { return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 0024e9a2270e8..a340acc12e9ef 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -18,6 +18,7 @@ + From 86ec7183cea2b50716edd7580e3eabbc2707fc7a Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Thu, 3 Mar 2022 08:29:02 -0300 Subject: [PATCH 084/130] [lgthinq][Feat] Added cool jet feature for Air Conditioners. Some changes to be compatible with 3.3-M1 Signed-off-by: nemerdaud --- .../lgservices/model/ac/ACCapability.java | 27 +++++++++++++++++++ .../lgservices/model/ac/ACSnapshot.java | 14 ++++++++++ .../resources/OH-INF/i18n/lgthinq.properties | 3 +++ .../OH-INF/i18n/lgthinq_pt_BR.properties | 2 ++ 4 files changed, 46 insertions(+) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index 0e7d9f59d1049..372dbdc067a76 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -32,6 +32,25 @@ public class ACCapability extends Capability { private List supportedOpMode = Collections.emptyList(); private List supportedFanSpeed = Collections.emptyList(); + private boolean isJetModeAvailable; + private String coolJetModeCommandOn = ""; + private String coolJetModeCommandOff = ""; + + public String getCoolJetModeCommandOff() { + return coolJetModeCommandOff; + } + + public void setCoolJetModeCommandOff(String coolJetModeCommandOff) { + this.coolJetModeCommandOff = coolJetModeCommandOff; + } + + public String getCoolJetModeCommandOn() { + return coolJetModeCommandOn; + } + + public void setCoolJetModeCommandOn(String coolJetModeCommandOn) { + this.coolJetModeCommandOn = coolJetModeCommandOn; + } public Map getOpMod() { return opMod; @@ -64,4 +83,12 @@ public List getSupportedFanSpeed() { public void setSupportedFanSpeed(List supportedFanSpeed) { this.supportedFanSpeed = supportedFanSpeed; } + + public void setJetModeAvailable(boolean jetModeAvailable) { + this.isJetModeAvailable = jetModeAvailable; + } + + public boolean isJetModeAvailable() { + return this.isJetModeAvailable; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java index ec882915da164..8593c8be174c9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java @@ -37,6 +37,10 @@ public class ACSnapshot implements Snapshot { private double currentTemperature; + private boolean coolJetModeOn; + + private double coolJetMode; + private int operationMode; @Nullable private Integer operation; @@ -64,6 +68,16 @@ public Integer getAirWindStrength() { return airWindStrength; } + @JsonProperty("airState.wMode.jet") + @JsonAlias("Jet") + public Double getCoolJetMode() { + return coolJetMode; + } + + public void setCoolJetMode(Double coolJetMode) { + this.coolJetMode = coolJetMode; + } + public void setAirWindStrength(Integer airWindStrength) { this.airWindStrength = airWindStrength; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index 168deba8582ec..233bde0b5b4c4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -13,6 +13,9 @@ channel-type.lgthinq.fan-speed.label = Fan Speed channel-type.lgthinq.fan-speed.description = Air Conditioner Fan Speed channel-type.lgthinq.operation-mode.label = Operation Mode channel-type.lgthinq.operation-mode.description = Air Contirioner Operation Mode +channel-type.lgthinq.cool-jet.label = Cool Jet +channel-type.lgthinq.cool-jet.description = Cool Jet Mode + # ERRORS error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties index 17957c319bdb7..d787bd6f204e5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties @@ -13,6 +13,8 @@ channel-type.lgthinq.fan-speed.label = Velocidade Ar channel-type.lgthinq.fan-speed.description = Velocidade Ar channel-type.lgthinq.operation-mode.label = Modo de Opera\u00E7\u00E3o channel-type.lgthinq.operation-mode.description = Modo de Opera\u00E7\u00E3o do Ar Condicionado +channel-type.lgthinq.cool-jet.label = Jato Frio +channel-type.lgthinq.cool-jet.description = Modo Jato Frio # ERRORS error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). From 199fbd7bed965e42a0de798042ef5a40b0bad412 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 23 Mar 2022 17:06:47 -0300 Subject: [PATCH 085/130] [lgthinq][Feat] Add optional language and country in the configuration to manual entry and start to implement energy consumption channel Signed-off-by: nemerdaud --- .../lgthinq/internal/LGThinQConfiguration.java | 8 ++++++++ .../lgthinq/lgservices/LGThinQApiClientService.java | 3 +++ .../lgthinq/lgservices/model/ac/ACSnapshot.java | 11 +++++++++++ 3 files changed, 22 insertions(+) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java index b718d63d29068..e0e93c7da7c87 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java @@ -28,6 +28,8 @@ public class LGThinQConfiguration { public String password = ""; public String country = ""; public String language = ""; + public String manualCountry = ""; + public String manualLanguage = ""; public Integer pollingIntervalSec = 0; public String alternativeServer = ""; @@ -53,10 +55,16 @@ public String getPassword() { } public String getCountry() { + if ("--".equals(country)) { + return manualCountry; + } return country; } public String getLanguage() { + if ("--".equals(language)) { + return manualLanguage; + } return language; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index f0610362fba58..4ffc84e5f1925 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -52,6 +52,9 @@ public interface LGThinQApiClientService Date: Mon, 2 May 2022 23:09:05 -0300 Subject: [PATCH 086/130] [lgthinq][Feat] Addition of V1 Washer support and some bug fixes discovering API version of the devices. Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 5 +++ .../handler/LGThinQWasherHandler.java | 15 +++++++ .../lgservices/model/SnapshotFactory.java | 18 +++++--- .../model/dryer/DryerCapability.java | 22 ++++++---- .../lgservices/model/dryer/DryerSnapshot.java | 3 ++ .../model/washer/WasherCapability.java | 41 ++++++++++++++----- .../model/washer/WasherSnapshot.java | 2 + 7 files changed, 83 insertions(+), 23 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 8fd13567568fb..857440ab75fac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -25,6 +25,7 @@ import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; @@ -75,6 +76,10 @@ public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider dryLevelChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_DRY_LEVEL_ID); } + protected DeviceTypes getDeviceType() { + return DeviceTypes.DRYER; + } + @Override public void initialize() { logger.debug("Initializing Thinq thing."); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 945ccff29639c..29bb2ab646b75 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -24,6 +24,7 @@ import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiV2ClientServiceImpl; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; @@ -50,6 +51,8 @@ public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_TEMPERATURE, k)))); stateDescriptionProvider.setStateOptions(temperatureChannelUUID, options); } + if (isLinked(doorLockChannelUUID)) { + List options = new ArrayList<>(); + options.add(new StateOption("0", "Unlocked")); + options.add(new StateOption("1", "Locked")); + stateDescriptionProvider.setStateOptions(doorLockChannelUUID, options); + } } @Override @@ -143,6 +153,11 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); } + @Override + protected DeviceTypes getDeviceType() { + return DeviceTypes.WASHING_MACHINE; + } + @Override protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { Command command = params.command; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 81d394ec43076..121c8e9acff4f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; import java.util.HashMap; import java.util.Map; @@ -81,11 +81,11 @@ public S create(Map deviceSettings, Class washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), "washerDryer node must be present in the snapshot"); return clazz.cast(objectMapper.convertValue(washerDryerMap, WasherSnapshot.class)); } @@ -97,7 +97,7 @@ public S create(Map deviceSettings, Class washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE), + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), "washerDryer node must be present in the snapshot"); return clazz.cast(objectMapper.convertValue(washerDryerMap, DryerSnapshot.class)); } @@ -125,8 +125,16 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes "Unexpected error. Can't find key node attributes to determine AC API version."); } case DRYER: - case WASHING_MACHINE: return LGAPIVerion.V2_0; + case WASHING_MACHINE: + if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { + return LGAPIVerion.V2_0; + } else if (snapMap.containsKey("State")) { + return LGAPIVerion.V1_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine AC API version."); + } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java index e11dc05da9ab8..1a3cdb581319a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java @@ -26,10 +26,13 @@ @NonNullByDefault public class DryerCapability extends Capability { public enum MonitoringCap { - STATE("state"), - PROCESS_STATE("processState"), - DRY_LEVEL("dryLevel"), - ERROR("error"); + STATE_V2("state"), + PROCESS_STATE_V2("processState"), + DRY_LEVEL_V2("dryLevel"), + ERROR_V2("error"), + STATE_V1("State"), + PROCESS_STATE_V1("PreState"), + ERROR_V1("Error"); final String value; @@ -106,16 +109,19 @@ public void setRemoteStart(boolean hasRemoteStart) { public void addMonitoringValue(MonitoringCap monCap, String key, String value) { switch (monCap) { - case STATE: + case STATE_V2: + case STATE_V1: monitoringValue.state.put(key, value); break; - case PROCESS_STATE: + case PROCESS_STATE_V2: + case PROCESS_STATE_V1: monitoringValue.processState.put(key, value); break; - case DRY_LEVEL: + case DRY_LEVEL_V2: monitoringValue.dryLevel.put(key, value); break; - case ERROR: + case ERROR_V1: + case ERROR_V2: monitoringValue.error.put(key, value); break; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java index 70e60d595b95f..f3972e10620d6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java @@ -57,6 +57,7 @@ public void setCourse(String course) { } @JsonProperty("dryLevel") + @JsonAlias({ "DryLevel" }) public String getDryLevel() { return dryLevel; } @@ -70,6 +71,7 @@ public void setRemainingMinute(Double remainingMinute) { } @JsonProperty("error") + @JsonAlias({ "Error" }) public String getError() { return error; } @@ -79,6 +81,7 @@ public void setError(String error) { } @JsonProperty("processState") + @JsonAlias({ "ProcessState" }) public String getProcessState() { return processState; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java index aa6dcbcb95501..b52ff310f779b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java @@ -26,11 +26,18 @@ @NonNullByDefault public class WasherCapability extends Capability { public enum MonitoringCap { - STATE("state"), - SOIL_WASH("soilWash"), - SPIN("spin"), - TEMPERATURE("temp"), - RINSE("rinse"); + STATE_V2("state"), + SOIL_WASH_V2("soilWash"), + SPIN_V2("spin"), + TEMPERATURE_V2("temp"), + RINSE_V2("rinse"), + ERROR_V2("error"), + STATE_V1("State"), + SOIL_WASH_V1("Wash"), + SPIN_V1("SpinSpeed"), + TEMPERATURE_V1("WaterTemp"), + RINSE_V1("RinseOption"), + ERROR_V1("Error"); final String value; @@ -49,6 +56,7 @@ private static class MonitoringValue { private Map spin = new LinkedHashMap(); private Map temperature = new LinkedHashMap(); private Map rinse = new LinkedHashMap(); + private Map error = new LinkedHashMap(); private boolean hasDoorLook; private boolean hasTurboWash; } @@ -94,6 +102,10 @@ public Map getRinse() { return monitoringValue.rinse; } + public Map getError() { + return monitoringValue.error; + } + public boolean hasDoorLook() { return monitoringValue.hasDoorLook; } @@ -112,21 +124,30 @@ public void setHasTurboWash(boolean hasTurboWash) { public void addMonitoringValue(MonitoringCap monCap, String key, String value) { switch (monCap) { - case STATE: + case STATE_V1: + case STATE_V2: monitoringValue.state.put(key, value); break; - case SOIL_WASH: + case SOIL_WASH_V2: + case SOIL_WASH_V1: monitoringValue.soilWash.put(key, value); break; - case SPIN: + case SPIN_V2: + case SPIN_V1: monitoringValue.spin.put(key, value); break; - case TEMPERATURE: + case TEMPERATURE_V2: + case TEMPERATURE_V1: monitoringValue.temperature.put(key, value); break; - case RINSE: + case RINSE_V2: + case RINSE_V1: monitoringValue.rinse.put(key, value); break; + case ERROR_V2: + case ERROR_V1: + monitoringValue.error.put(key, value); + break; } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java index 993b88f0cac3c..eb3130a182303 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java @@ -127,6 +127,7 @@ public void setSmartCourse(String smartCourse) { } @JsonProperty("temp") + @JsonAlias({ "WaterTemp" }) public String getTemperatureLevel() { return temperatureLevel; } @@ -136,6 +137,7 @@ public void setTemperatureLevel(String temperatureLevel) { } @JsonProperty("doorLock") + @JsonAlias({ "ChildLock" }) public String getDoorLock() { return doorLock; } From 3acff403d2a8ecf086fd9059e764de03ebfdec06 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 3 May 2022 17:27:41 -0300 Subject: [PATCH 087/130] [lgthinq][Feat] Addition of support to binary data monitoring. Signed-off-by: nemerdaud --- .../lgservices/LGThinQApiClientService.java | 8 +- .../lgthinq/lgservices/model/Capability.java | 22 +++++ .../lgservices/model/SnapshotFactory.java | 80 +++++++++++++++++-- 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index 4ffc84e5f1925..0189893ea6e87 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -20,10 +20,7 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.internal.errors.*; import org.openhab.binding.lgthinq.lgservices.model.*; /** @@ -68,5 +65,6 @@ File loadDeviceCapability(String deviceId, String uri, boolean forceRecreate) @Nullable S getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workerId, - DeviceTypes deviceType) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException; + DeviceTypes deviceType, @NonNull C deviceCapability) + throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException, LGThinqUnmarshallException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java index 2fe161c37684b..440dfa37e8dcf 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java @@ -12,11 +12,33 @@ */ package org.openhab.binding.lgthinq.lgservices.model; +import java.util.ArrayList; +import java.util.List; + /** * The {@link Capability} * * @author Nemer Daud - Initial contribution */ public abstract class Capability { + // default result format + private MonitoringResultFormat monitoringDataFormat = MonitoringResultFormat.JSON_FORMAT; + + private List monitoringBinaryProtocol = new ArrayList<>(); + + public MonitoringResultFormat getMonitoringDataFormat() { + return monitoringDataFormat; + } + + public void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat) { + this.monitoringDataFormat = monitoringDataFormat; + } + + public List getMonitoringBinaryProtocol() { + return monitoringBinaryProtocol; + } + public void setMonitoringBinaryProtocol(List monitoringBinaryProtocol) { + this.monitoringBinaryProtocol = monitoringBinaryProtocol; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 121c8e9acff4f..9f7fedfed076e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -14,16 +14,23 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.beans.BeanInfo; +import java.beans.IntrospectionException; +import java.beans.Introspector; +import java.beans.PropertyDescriptor; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @@ -45,6 +52,63 @@ public static SnapshotFactory getInstance() { return instance; } + /** + * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) + * + * @param binaryData V1: decoded returnedData + * + * @return returns Snapshot implementation based on device type provided + * @throws LGThinqApiException any error. + */ + public S createFromBinary(String binaryData, List prot, + Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { + try { + byte[] data = binaryData.getBytes(); + BeanInfo beanInfo = Introspector.getBeanInfo(clazz); + S snap = clazz.getConstructor().newInstance(); + PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); + for (Iterator it = prot.iterator(); it.hasNext();) { + MonitoringBinaryProtocol protField = it.next(); + String fName = protField.fieldName; + for (PropertyDescriptor property : pds) { + // all attributes of class. + Method m = property.getReadMethod(); // getter + List aliases = new ArrayList<>(); + if (m.isAnnotationPresent(JsonProperty.class)) { + aliases.add(m.getAnnotation(JsonProperty.class).value()); + } + if (m.isAnnotationPresent(JsonAlias.class)) { + aliases.addAll(Arrays.asList(m.getAnnotation(JsonAlias.class).value())); + } + + if (aliases.contains(fName)) { + // found property. Get bit value + int value = 0; + for (int i = protField.startByte; i < protField.startByte + protField.length; i++) { + value = (value << 8) + data[i]; + } + m = property.getWriteMethod(); + if (m.getParameters()[0].getType() == String.class) { + m.invoke(snap, String.valueOf(value)); + } else if (m.getParameters()[0].getType() == Double.class) { + m.invoke(snap, (double) value); + } else if (m.getParameters()[0].getType() == Integer.class) { + m.invoke(snap, value); + } else { + throw new IllegalArgumentException( + String.format("Parameter type not supported for this factory:%s", + m.getParameters()[0].getType().toString())); + } + } + } + } + return snap; + } catch (IntrospectionException | InvocationTargetException | InstantiationException | IllegalAccessException + | NoSuchMethodException e) { + throw new LGThinqUnmarshallException("Unexpected Error unmarshalling binary data", e); + } + } + /** * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) * @@ -53,21 +117,21 @@ public static SnapshotFactory getInstance() { * @return returns Snapshot implementation based on device type provided * @throws LGThinqApiException any error. */ - public S create(String snapshotDataJson, DeviceTypes deviceType, Class clazz) - throws LGThinqApiException { + public S createFromJson(String snapshotDataJson, DeviceTypes deviceType, Class clazz) + throws LGThinqUnmarshallException, LGThinqApiException { try { Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { }); Map deviceSetting = new HashMap<>(); deviceSetting.put("deviceType", deviceType.deviceTypeId()); deviceSetting.put("snapshot", snapshotMap); - return create(deviceSetting, clazz); + return createFromJson(deviceSetting, clazz); } catch (JsonProcessingException e) { - throw new LGThinqApiException("Unexpected Error unmarshalling json to map", e); + throw new LGThinqUnmarshallException("Unexpected Error unmarshalling json to map", e); } } - public S create(Map deviceSettings, Class clazz) + public S createFromJson(Map deviceSettings, Class clazz) throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); Map snapMap = ((Map) deviceSettings.get("snapshot")); From e529cbd57ddbdb73697abae303a46c03c93774d6 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 18 May 2022 17:36:25 -0300 Subject: [PATCH 088/130] [lgthinq][Fix] Monitor Lock on V1 devices and enforce monitoring timeout in some V2 devices to update temperature sensor more frequently Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 9 - .../model/fridge/FridgeCapability.java | 129 ++++++++++++++ .../model/fridge/FridgeSnapshot.java | 167 ++++++++++++++++++ 3 files changed, 296 insertions(+), 9 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 857440ab75fac..edcf50a12ee6f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -18,7 +18,6 @@ import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -46,8 +45,6 @@ @NonNullByDefault public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler { - @Nullable - private DryerCapability dryerCapability; private final ChannelUID stateChannelUUID; private final ChannelUID processStateChannelUUID; private final ChannelUID dryLevelChannelUUID; @@ -58,12 +55,6 @@ public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler BRIDGE_STATUS_DETAIL_ERROR = Set.of(ThingStatusDetail.BRIDGE_OFFLINE, - ThingStatusDetail.BRIDGE_UNINITIALIZED, ThingStatusDetail.COMMUNICATION_ERROR, - ThingStatusDetail.CONFIGURATION_ERROR); - private @Nullable ScheduledFuture thingStatePollingJob; - private @Nullable Future commandExecutorQueueJob; public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { super(thing, stateDescriptionProvider); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java new file mode 100644 index 0000000000000..76f403925858c --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java @@ -0,0 +1,129 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.Capability; + +/** + * The {@link FridgeCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FridgeCapability extends Capability { + public enum MonitoringCap { + STATE_V2("state"), + PROCESS_STATE_V2("processState"), + DRY_LEVEL_V2("dryLevel"), + ERROR_V2("error"), + STATE_V1("State"), + PROCESS_STATE_V1("PreState"), + ERROR_V1("Error"); + + final String value; + + MonitoringCap(String value) { + this.value = value; + } + + public String getValue() { + return value; + } + } + + private static class MonitoringValue { + private final Map state = new LinkedHashMap(); + private final Map dryLevel = new LinkedHashMap(); + private final Map error = new LinkedHashMap(); + private final Map processState = new LinkedHashMap(); + private boolean hasChildLock; + private boolean hasRemoteStart; + } + + private final MonitoringValue monitoringValue = new MonitoringValue(); + private final Map courses = new LinkedHashMap(); + + private final Map smartCourses = new LinkedHashMap(); + + public Map getCourses() { + return courses; + } + + public Map getSmartCourses() { + return smartCourses; + } + + public void addCourse(String courseLabel, String courseName) { + courses.put(courseLabel, courseName); + } + + public void addSmartCourse(String courseLabel, String courseName) { + smartCourses.put(courseLabel, courseName); + } + + public Map getState() { + return monitoringValue.state; + } + + public Map getDryLevels() { + return monitoringValue.dryLevel; + } + + public Map getErrors() { + return monitoringValue.error; + } + + public Map getProcessStates() { + return monitoringValue.processState; + } + + public boolean hasRemoteStart() { + return monitoringValue.hasRemoteStart; + } + + public boolean hasChildLock() { + return monitoringValue.hasChildLock; + } + + public void setChildLock(boolean hasChildLock) { + monitoringValue.hasChildLock = hasChildLock; + } + + public void setRemoteStart(boolean hasRemoteStart) { + monitoringValue.hasRemoteStart = hasRemoteStart; + } + + public void addMonitoringValue(MonitoringCap monCap, String key, String value) { + switch (monCap) { + case STATE_V2: + case STATE_V1: + monitoringValue.state.put(key, value); + break; + case PROCESS_STATE_V2: + case PROCESS_STATE_V1: + monitoringValue.processState.put(key, value); + break; + case DRY_LEVEL_V2: + monitoringValue.dryLevel.put(key, value); + break; + case ERROR_V1: + case ERROR_V2: + monitoringValue.error.put(key, value); + break; + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java new file mode 100644 index 0000000000000..fa8104fed0dd8 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java @@ -0,0 +1,167 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link FridgeSnapshot} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->washerDryer, but this POJO expects + * to map field below washerDryer + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class FridgeSnapshot implements Snapshot { + private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; + private String state = ""; + private boolean online; + private String course = ""; + private String smartCourse = ""; + private String childLock = ""; + private String processState = ""; + private Double remainingHour = 0.00; + private Double remainingMinute = 0.00; + private String dryLevel = ""; + private String error = ""; + + @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) + @JsonProperty("courseDryer24inchBase") + public String getCourse() { + return course; + } + + public void setCourse(String course) { + this.course = course; + } + + @JsonProperty("dryLevel") + @JsonAlias({ "DryLevel" }) + public String getDryLevel() { + return dryLevel; + } + + public void setDryLevel(String dryLevel) { + this.dryLevel = dryLevel; + } + + public void setRemainingMinute(Double remainingMinute) { + this.remainingMinute = remainingMinute; + } + + @JsonProperty("error") + @JsonAlias({ "Error" }) + public String getError() { + return error; + } + + public void setError(String error) { + this.error = error; + } + + @JsonProperty("processState") + @JsonAlias({ "ProcessState" }) + public String getProcessState() { + return processState; + } + + public void setProcessState(String processState) { + this.processState = processState; + } + + @Override + public DevicePowerState getPowerStatus() { + return powerState; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + throw new IllegalArgumentException("This method must not be accessed."); + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } + + @JsonProperty("state") + @JsonAlias({ "state", "State" }) + public String getState() { + return state; + } + + @JsonProperty("smartCourseDryer24inchBase") + @JsonAlias({ "smartCourseDryer24inchBase", "SmartCourse", "smartCourseDryer27inchBase" }) + public String getSmartCourse() { + return smartCourse; + } + + public void setSmartCourse(String smartCourse) { + this.smartCourse = smartCourse; + } + + @JsonProperty("childLock") + public String getChildLock() { + return childLock; + } + + public void setChildLock(String childLock) { + this.childLock = childLock; + } + + @JsonIgnore + public String getRemainingTime() { + return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); + } + + @JsonProperty("remainTimeHour") + @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) + public Double getRemainingHour() { + return remainingHour; + } + + public void setRemainingHour(Double remainingHour) { + this.remainingHour = remainingHour; + } + + @JsonProperty("remainTimeMinute") + @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) + public Double getRemainingMinute() { + return remainingMinute; + } + + public void setState(String state) { + this.state = state; + if (state.equals(WM_POWER_OFF_VALUE)) { + powerState = DevicePowerState.DV_POWER_OFF; + } else { + powerState = DevicePowerState.DV_POWER_ON; + } + } +} From 8748c4784d6af7d5fc8a6b9a132348ff2f606aba Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 1 Jun 2022 08:23:15 -0300 Subject: [PATCH 089/130] [lgthinq][feat] implementing Refrigerator support thing Signed-off-by: nemerdaud --- .../lgservices/model/AbstractCapability.java | 48 ++++++ .../lgthinq/lgservices/model/Capability.java | 24 +-- .../lgservices/model/SnapshotFactory.java | 22 ++- .../lgservices/model/ac/ACCapability.java | 4 +- .../model/dryer/DryerCapability.java | 4 +- .../model/fridge/FridgeCapability.java | 104 +------------ .../model/fridge/FridgeFactory.java | 47 ++++++ .../model/fridge/FridgeSnapshot.java | 147 +----------------- .../model/fridge/v2/FridgeCapabilityV2.java | 97 ++++++++++++ .../model/fridge/v2/FridgeSnapshotV2.java | 98 ++++++++++++ .../model/washer/WasherCapability.java | 4 +- 11 files changed, 334 insertions(+), 265 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java new file mode 100644 index 0000000000000..f3ee28162257d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; + +import java.util.ArrayList; +import java.util.List; + +/** + * The {@link AbstractCapability} + * + * @author Nemer Daud - Initial contribution + */ +public abstract class AbstractCapability implements Capability { + // default result format + private MonitoringResultFormat monitoringDataFormat = MonitoringResultFormat.JSON_FORMAT; + + private List monitoringBinaryProtocol = new ArrayList<>(); + + @Override + public MonitoringResultFormat getMonitoringDataFormat() { + return monitoringDataFormat; + } + + @Override + public void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat) { + this.monitoringDataFormat = monitoringDataFormat; + } + + @Override + public List getMonitoringBinaryProtocol() { + return monitoringBinaryProtocol; + } + + @Override + public void setMonitoringBinaryProtocol(List monitoringBinaryProtocol) { + this.monitoringBinaryProtocol = monitoringBinaryProtocol; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java index 440dfa37e8dcf..a36bcfaea25d7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import java.util.ArrayList; import java.util.List; /** @@ -20,25 +19,12 @@ * * @author Nemer Daud - Initial contribution */ -public abstract class Capability { - // default result format - private MonitoringResultFormat monitoringDataFormat = MonitoringResultFormat.JSON_FORMAT; +public interface Capability { + MonitoringResultFormat getMonitoringDataFormat(); - private List monitoringBinaryProtocol = new ArrayList<>(); + void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat); - public MonitoringResultFormat getMonitoringDataFormat() { - return monitoringDataFormat; - } + List getMonitoringBinaryProtocol(); - public void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat) { - this.monitoringDataFormat = monitoringDataFormat; - } - - public List getMonitoringBinaryProtocol() { - return monitoringBinaryProtocol; - } - - public void setMonitoringBinaryProtocol(List monitoringBinaryProtocol) { - this.monitoringBinaryProtocol = monitoringBinaryProtocol; - } + void setMonitoringBinaryProtocol(List monitoringBinaryProtocol); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 9f7fedfed076e..b319279242d5f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import java.beans.BeanInfo; import java.beans.IntrospectionException; @@ -27,6 +27,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; import com.fasterxml.jackson.annotation.JsonAlias; @@ -166,6 +167,18 @@ public S createFromJson(Map deviceSettings, return clazz.cast(objectMapper.convertValue(washerDryerMap, DryerSnapshot.class)); } } + case REFRIGERATOR: + switch (version) { + case V1_0: { + throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); + } + case V2_0: { + Map refMap = Objects.requireNonNull( + (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), + "washerDryer node must be present in the snapshot"); + return clazz.cast(objectMapper.convertValue(refMap, FridgeSnapshotV2.class)); + } + } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } @@ -199,6 +212,13 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes throw new IllegalStateException( "Unexpected error. Can't find key node attributes to determine AC API version."); } + case REFRIGERATOR: + if (snapMap.containsKey(REFRIGERATOR_SNAPSHOT_NODE_V2)) { + return LGAPIVerion.V2_0; + } else { + throw new IllegalStateException( + "Unexpected error. Can't find key node attributes to determine AC API version."); + } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index 372dbdc067a76..054a4fb5ddb00 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -17,7 +17,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Capability; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; /** * The {@link ACCapability} @@ -25,7 +25,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class ACCapability extends Capability { +public class ACCapability extends AbstractCapability { private Map opMod = Collections.emptyMap(); private Map fanSpeed = Collections.emptyMap(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java index 1a3cdb581319a..bb8aa208a21d7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java @@ -16,7 +16,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Capability; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; /** * The {@link DryerCapability} @@ -24,7 +24,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class DryerCapability extends Capability { +public class DryerCapability extends AbstractCapability { public enum MonitoringCap { STATE_V2("state"), PROCESS_STATE_V2("processState"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java index 76f403925858c..4ba7702f9b582 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.lgthinq.lgservices.model.fridge; -import java.util.LinkedHashMap; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -24,106 +23,15 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class FridgeCapability extends Capability { - public enum MonitoringCap { - STATE_V2("state"), - PROCESS_STATE_V2("processState"), - DRY_LEVEL_V2("dryLevel"), - ERROR_V2("error"), - STATE_V1("State"), - PROCESS_STATE_V1("PreState"), - ERROR_V1("Error"); +public interface FridgeCapability extends Capability { - final String value; + public Map getFridgeTempCMap(); - MonitoringCap(String value) { - this.value = value; - } + public Map getFridgeTempFMap(); - public String getValue() { - return value; - } - } + public Map getFreezerTempCMap(); - private static class MonitoringValue { - private final Map state = new LinkedHashMap(); - private final Map dryLevel = new LinkedHashMap(); - private final Map error = new LinkedHashMap(); - private final Map processState = new LinkedHashMap(); - private boolean hasChildLock; - private boolean hasRemoteStart; - } + public Map getFreezerTempFMap(); - private final MonitoringValue monitoringValue = new MonitoringValue(); - private final Map courses = new LinkedHashMap(); - - private final Map smartCourses = new LinkedHashMap(); - - public Map getCourses() { - return courses; - } - - public Map getSmartCourses() { - return smartCourses; - } - - public void addCourse(String courseLabel, String courseName) { - courses.put(courseLabel, courseName); - } - - public void addSmartCourse(String courseLabel, String courseName) { - smartCourses.put(courseLabel, courseName); - } - - public Map getState() { - return monitoringValue.state; - } - - public Map getDryLevels() { - return monitoringValue.dryLevel; - } - - public Map getErrors() { - return monitoringValue.error; - } - - public Map getProcessStates() { - return monitoringValue.processState; - } - - public boolean hasRemoteStart() { - return monitoringValue.hasRemoteStart; - } - - public boolean hasChildLock() { - return monitoringValue.hasChildLock; - } - - public void setChildLock(boolean hasChildLock) { - monitoringValue.hasChildLock = hasChildLock; - } - - public void setRemoteStart(boolean hasRemoteStart) { - monitoringValue.hasRemoteStart = hasRemoteStart; - } - - public void addMonitoringValue(MonitoringCap monCap, String key, String value) { - switch (monCap) { - case STATE_V2: - case STATE_V1: - monitoringValue.state.put(key, value); - break; - case PROCESS_STATE_V2: - case PROCESS_STATE_V1: - monitoringValue.processState.put(key, value); - break; - case DRY_LEVEL_V2: - monitoringValue.dryLevel.put(key, value); - break; - case ERROR_V1: - case ERROR_V2: - monitoringValue.error.put(key, value); - break; - } - } + public void loadCapabilities(Object veryRootNode); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java new file mode 100644 index 0000000000000..c400c746be0c7 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; + +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeCapabilityV2; +import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; + +/** + * The {@link FridgeFactory} + * + * @author Nemer Daud - Initial contribution + */ +public class FridgeFactory { + + public static FridgeCapability getFridgeCapability(LGAPIVerion version) { + switch (version) { + case V1_0: + throw new IllegalArgumentException("V1_0 not supported by Fridge Thing yet"); + case V2_0: + return new FridgeCapabilityV2(); + default: + throw new IllegalArgumentException("Version " + version + " not expected"); + } + } + + public static FridgeSnapshot getFridgeSnapshot(LGAPIVerion version) { + switch (version) { + case V1_0: + throw new IllegalArgumentException("V1_0 not supported by Fridge Thing yet"); + case V2_0: + return new FridgeSnapshotV2(); + default: + throw new IllegalArgumentException("Version " + version + " not expected"); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java index fa8104fed0dd8..ca368339de28f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java @@ -12,156 +12,21 @@ */ package org.openhab.binding.lgthinq.lgservices.model.fridge; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; - import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.Snapshot; -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - /** * The {@link FridgeSnapshot} - * This map the snapshot result from Washing Machine devices - * This json payload come with path: snapshot->washerDryer, but this POJO expects - * to map field below washerDryer - * + * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class FridgeSnapshot implements Snapshot { - private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; - private String state = ""; - private boolean online; - private String course = ""; - private String smartCourse = ""; - private String childLock = ""; - private String processState = ""; - private Double remainingHour = 0.00; - private Double remainingMinute = 0.00; - private String dryLevel = ""; - private String error = ""; - - @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) - @JsonProperty("courseDryer24inchBase") - public String getCourse() { - return course; - } - - public void setCourse(String course) { - this.course = course; - } - - @JsonProperty("dryLevel") - @JsonAlias({ "DryLevel" }) - public String getDryLevel() { - return dryLevel; - } - - public void setDryLevel(String dryLevel) { - this.dryLevel = dryLevel; - } - - public void setRemainingMinute(Double remainingMinute) { - this.remainingMinute = remainingMinute; - } - - @JsonProperty("error") - @JsonAlias({ "Error" }) - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - - @JsonProperty("processState") - @JsonAlias({ "ProcessState" }) - public String getProcessState() { - return processState; - } - - public void setProcessState(String processState) { - this.processState = processState; - } - - @Override - public DevicePowerState getPowerStatus() { - return powerState; - } - - @Override - public void setPowerStatus(DevicePowerState value) { - throw new IllegalArgumentException("This method must not be accessed."); - } - - @Override - public boolean isOnline() { - return online; - } - - @Override - public void setOnline(boolean online) { - this.online = online; - } - - @JsonProperty("state") - @JsonAlias({ "state", "State" }) - public String getState() { - return state; - } - - @JsonProperty("smartCourseDryer24inchBase") - @JsonAlias({ "smartCourseDryer24inchBase", "SmartCourse", "smartCourseDryer27inchBase" }) - public String getSmartCourse() { - return smartCourse; - } - - public void setSmartCourse(String smartCourse) { - this.smartCourse = smartCourse; - } - - @JsonProperty("childLock") - public String getChildLock() { - return childLock; - } - - public void setChildLock(String childLock) { - this.childLock = childLock; - } - - @JsonIgnore - public String getRemainingTime() { - return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); - } - - @JsonProperty("remainTimeHour") - @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) - public Double getRemainingHour() { - return remainingHour; - } +public interface FridgeSnapshot extends Snapshot { + public String getTempUnit(); - public void setRemainingHour(Double remainingHour) { - this.remainingHour = remainingHour; - } + public String getFridgeTemp(); - @JsonProperty("remainTimeMinute") - @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) - public Double getRemainingMinute() { - return remainingMinute; - } + public String getFreezerTemp(); - public void setState(String state) { - this.state = state; - if (state.equals(WM_POWER_OFF_VALUE)) { - powerState = DevicePowerState.DV_POWER_OFF; - } else { - powerState = DevicePowerState.DV_POWER_ON; - } - } + public void loadSnapshot(Object veryRootNode); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java new file mode 100644 index 0000000000000..68840ebd698b5 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java @@ -0,0 +1,97 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge.v2; + +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link FridgeCapabilityV2} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class FridgeCapabilityV2 extends AbstractCapability + implements org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeCapability { + + private static final Logger logger = LoggerFactory.getLogger(FridgeCapabilityV2.class); + private static final ObjectMapper mapper = new ObjectMapper(); + + private final Map fridgeTempCMap = new LinkedHashMap(); + private final Map fridgeTempFMap = new LinkedHashMap(); + private final Map freezerTempCMap = new LinkedHashMap(); + private final Map freezerTempFMap = new LinkedHashMap(); + + public Map getFridgeTempCMap() { + return fridgeTempCMap; + } + + public Map getFridgeTempFMap() { + return fridgeTempFMap; + } + + public Map getFreezerTempCMap() { + return freezerTempCMap; + } + + public Map getFreezerTempFMap() { + return freezerTempFMap; + } + + @Override + public void loadCapabilities(Object veryRootNode) { + JsonNode node = mapper.valueToTree(veryRootNode); + if (node.isNull()) { + logger.error("Can't parse json capability for Fridge V2. The payload has been ignored"); + logger.debug("payload {}", veryRootNode); + return; + } + /** + * iterate over valueMappings like: + * "valueMapping": { + * "1": {"index" : 1, "label" : "7", "_comment" : ""}, + * "2": {"index" : 2, "label" : "6", "_comment" : ""}, + * "3": {"index" : 3, "label" : "5", "_comment" : ""}, + * "4": {"index" : 4, "label" : "4", "_comment" : ""}, + * "5": {"index" : 5, "label" : "3", "_comment" : ""}, + * "6": {"index" : 6, "label" : "2", "_comment" : ""}, + * "7": {"index" : 7, "label" : "1", "_comment" : ""}, + * "255" : {"index" : 255, "label" : "IGNORE", "_comment" : ""} + * } + */ + + JsonNode fridgeTempCNode = node.path("MonitoringValue").path("fridgeTemp_C").path("valueMapping"); + JsonNode fridgeTempFNode = node.path("MonitoringValue").path("fridgeTemp_F").path("valueMapping"); + JsonNode freezerTempCNode = node.path("MonitoringValue").path("freezerTemp_C").path("valueMapping"); + JsonNode freezerTempFNode = node.path("MonitoringValue").path("freezerTemp_F").path("valueMapping"); + loadTempNode(fridgeTempCNode, fridgeTempCMap); + loadTempNode(fridgeTempFNode, fridgeTempFMap); + loadTempNode(freezerTempCNode, freezerTempCMap); + loadTempNode(freezerTempFNode, freezerTempFMap); + } + + private void loadTempNode(JsonNode tempNode, Map capMap) { + tempNode.forEach(v -> { + // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' + capMap.put(v.path("index").asText(), v.path("label").textValue()); + }); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java new file mode 100644 index 0000000000000..ea0d26c24854e --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java @@ -0,0 +1,98 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge.v2; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.Snapshot; + +import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The {@link FridgeSnapshotV2} + * This map the snapshot result from Washing Machine devices + * This json payload come with path: snapshot->fridge, but this POJO expects + * to map field below washerDryer + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +@JsonIgnoreProperties(ignoreUnknown = true) +public class FridgeSnapshotV2 implements Snapshot, org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeSnapshot { + + private boolean online; + private String fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; + private String freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; + private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default + + @Override + @JsonAlias({ "TempUnit" }) + @JsonProperty("tempUnit") + public String getTempUnit() { + return tempUnit; + } + + public void setTempUnit(String tempUnit) { + this.tempUnit = tempUnit; + } + + @Override + @JsonAlias({ "TempRefrigerator" }) + @JsonProperty("fridgeTemp") + public String getFridgeTemp() { + return fridgeTemp; + } + + public void setFridgeTemp(String fridgeTemp) { + this.fridgeTemp = fridgeTemp; + } + + @Override + @JsonAlias({ "TempFreezer" }) + @JsonProperty("freezerTemp") + public String getFreezerTemp() { + return freezerTemp; + } + + public void setFreezerTemp(String freezerTemp) { + this.freezerTemp = freezerTemp; + } + + @Override + public void loadSnapshot(Object veryRootNode) { + } + + @Override + public DevicePowerState getPowerStatus() { + throw new IllegalStateException("Fridge has no Power state."); + } + + @Override + public void setPowerStatus(DevicePowerState value) { + throw new IllegalStateException("Fridge has no Power state."); + } + + @Override + public boolean isOnline() { + return online; + } + + @Override + public void setOnline(boolean online) { + this.online = online; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java index b52ff310f779b..d232aaf70afda 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java @@ -16,7 +16,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Capability; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; /** * The {@link WasherCapability} @@ -24,7 +24,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class WasherCapability extends Capability { +public class WasherCapability extends AbstractCapability { public enum MonitoringCap { STATE_V2("state"), SOIL_WASH_V2("soilWash"), From 3e195399f13d3f46ca932884750fe994ac966386 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 7 Jun 2022 17:04:54 -0300 Subject: [PATCH 090/130] [lgthinq][feat] Delay Time for Washers, [fix] dynamic temperature unit for refrigerators; DateTime unit for Dryer and Washer time channels Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 3 +- .../handler/LGThinQWasherHandler.java | 4 ++- .../model/fridge/FridgeSnapshot.java | 4 +-- .../model/fridge/v2/FridgeCapabilityV2.java | 15 +++++---- .../model/fridge/v2/FridgeSnapshotV2.java | 32 ++++++++++++++----- .../model/washer/WasherSnapshot.java | 27 ++++++++++++++++ .../main/resources/OH-INF/thing/washer.xml | 1 + 7 files changed, 68 insertions(+), 18 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index edcf50a12ee6f..97725bf5ce357 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; @@ -86,7 +87,7 @@ protected void updateDeviceChannels(DryerSnapshot shot) { updateState(DR_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); updateState(DR_CHANNEL_PROCESS_STATE_ID, new StringType(shot.getProcessState())); updateState(DR_CHANNEL_CHILD_LOCK_ID, new StringType(shot.getChildLock())); - updateState(DR_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateState(DR_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); updateState(DR_CHANNEL_DRY_LEVEL_ID, new StringType(shot.getDryLevel())); updateState(DR_CHANNEL_ERROR_ID, new StringType(shot.getError())); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 29bb2ab646b75..046bb9e91056a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -28,6 +28,7 @@ import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; @@ -149,7 +150,8 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); - updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType(shot.getRemainingTime())); + updateState(WM_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); + updateState(WM_CHANNEL_DELAY_TIME_ID, new DateTimeType(shot.getReserveTime())); updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java index ca368339de28f..b1d80faaf071e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java @@ -24,9 +24,9 @@ public interface FridgeSnapshot extends Snapshot { public String getTempUnit(); - public String getFridgeTemp(); + public String getFridgeStrTemp(); - public String getFreezerTemp(); + public String getFreezerStrTemp(); public void loadSnapshot(Object veryRootNode); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java index 68840ebd698b5..a32f887b017a8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java @@ -12,6 +12,9 @@ */ package org.openhab.binding.lgthinq.lgservices.model.fridge.v2; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_CELSIUS_SYMBOL; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_FAHRENHEIT_SYMBOL; + import java.util.LinkedHashMap; import java.util.Map; @@ -82,16 +85,16 @@ public void loadCapabilities(Object veryRootNode) { JsonNode fridgeTempFNode = node.path("MonitoringValue").path("fridgeTemp_F").path("valueMapping"); JsonNode freezerTempCNode = node.path("MonitoringValue").path("freezerTemp_C").path("valueMapping"); JsonNode freezerTempFNode = node.path("MonitoringValue").path("freezerTemp_F").path("valueMapping"); - loadTempNode(fridgeTempCNode, fridgeTempCMap); - loadTempNode(fridgeTempFNode, fridgeTempFMap); - loadTempNode(freezerTempCNode, freezerTempCMap); - loadTempNode(freezerTempFNode, freezerTempFMap); + loadTempNode(fridgeTempCNode, fridgeTempCMap, TEMP_UNIT_CELSIUS_SYMBOL); + loadTempNode(fridgeTempFNode, fridgeTempFMap, TEMP_UNIT_FAHRENHEIT_SYMBOL); + loadTempNode(freezerTempCNode, freezerTempCMap, TEMP_UNIT_CELSIUS_SYMBOL); + loadTempNode(freezerTempFNode, freezerTempFMap, TEMP_UNIT_FAHRENHEIT_SYMBOL); } - private void loadTempNode(JsonNode tempNode, Map capMap) { + private void loadTempNode(JsonNode tempNode, Map capMap, String unit) { tempNode.forEach(v -> { // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' - capMap.put(v.path("index").asText(), v.path("label").textValue()); + capMap.put(v.path("index").asText() + " " + unit, v.path("label").textValue() + " " + unit); }); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java index ea0d26c24854e..47e4acde699f9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java @@ -19,6 +19,7 @@ import org.openhab.binding.lgthinq.lgservices.model.Snapshot; import com.fasterxml.jackson.annotation.JsonAlias; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; @@ -35,8 +36,8 @@ public class FridgeSnapshotV2 implements Snapshot, org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeSnapshot { private boolean online; - private String fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; - private String freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; + private Double fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; + private Double freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default @Override @@ -46,29 +47,44 @@ public String getTempUnit() { return tempUnit; } + private String getStrTempWithUnit(Double temp) { + return temp.intValue() + (TEMP_UNIT_CELSIUS.equals(tempUnit) ? " " + TEMP_UNIT_CELSIUS_SYMBOL + : (TEMP_UNIT_FAHRENHEIT).equals(tempUnit) ? " " + TEMP_UNIT_FAHRENHEIT_SYMBOL : ""); + } + + @Override + @JsonIgnore + public String getFridgeStrTemp() { + return getStrTempWithUnit(getFridgeTemp()); + } + + @Override + @JsonIgnore + public String getFreezerStrTemp() { + return getStrTempWithUnit(getFreezerTemp()); + } + public void setTempUnit(String tempUnit) { this.tempUnit = tempUnit; } - @Override @JsonAlias({ "TempRefrigerator" }) @JsonProperty("fridgeTemp") - public String getFridgeTemp() { + public Double getFridgeTemp() { return fridgeTemp; } - public void setFridgeTemp(String fridgeTemp) { + public void setFridgeTemp(Double fridgeTemp) { this.fridgeTemp = fridgeTemp; } - @Override @JsonAlias({ "TempFreezer" }) @JsonProperty("freezerTemp") - public String getFreezerTemp() { + public Double getFreezerTemp() { return freezerTemp; } - public void setFreezerTemp(String freezerTemp) { + public void setFreezerTemp(Double freezerTemp) { this.freezerTemp = freezerTemp; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java index eb3130a182303..84743ee0e0980 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java @@ -44,6 +44,8 @@ public class WasherSnapshot implements Snapshot { private String doorLock = ""; private Double remainingHour = 0.00; private Double remainingMinute = 0.00; + private Double reserveHour = 0.00; + private Double reserveMinute = 0.00; @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) @JsonProperty("courseFL24inchBaseTitan") @@ -102,6 +104,11 @@ public String getRemainingTime() { return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); } + @JsonIgnore + public String getReserveTime() { + return String.format("%02.0f:%02.0f", getReserveHour(), getReserveMinute()); + } + @JsonProperty("remainTimeHour") @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) public Double getRemainingHour() { @@ -122,6 +129,26 @@ public void setRemainingMinute(Double remainingMinute) { this.remainingMinute = remainingMinute; } + @JsonProperty("reserveTimeHour") + @JsonAlias({ "reserveTimeHour", "Reserve_Time_H" }) + public Double getReserveHour() { + return reserveHour; + } + + public void setReserveHour(Double reserveHour) { + this.reserveHour = reserveHour; + } + + @JsonProperty("reserveTimeMinute") + @JsonAlias({ "reserveTimeMinute", "Reserve_Time_M" }) + public Double getReserveMinute() { + return reserveMinute; + } + + public void setReserveMinute(Double reserveMinute) { + this.reserveMinute = reserveMinute; + } + public void setSmartCourse(String smartCourse) { this.smartCourse = smartCourse; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index a340acc12e9ef..031d873663ede 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -22,6 +22,7 @@ + From c51c638682b51cf3332ae5e7c28e07832946ef40 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 26 Oct 2022 16:57:20 -0300 Subject: [PATCH 091/130] [lgthinq][feat] Adding support to AC: AirClean, EnergySaving & AutoDry Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 14 ++-- .../lgservices/model/ac/ACCapability.java | 84 +++++++++++++++++++ .../lgservices/model/ac/ACSnapshot.java | 33 ++++++++ 3 files changed, 124 insertions(+), 7 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index 76aa362d2a96d..bfcd12425a49f 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -36,13 +36,13 @@ There is currently no configuration available, as it is automatically obtained b LG ThinQ Air Conditioners support the following channels to interact with the OpenHab automation framework: -| channel | type | description | -|--------------------|------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Target Temperature | Temperature | Defines the desired target temperature for the device | -| Temperature | Temperature | Read-Only channel that indicates the current temperature informed by the device | -| Fan Speed | Number (Labeled) | This channel let you choose the current label value for the fan speed (Low, Medium, High, Nature, etc.). These values are pre-configured in discovery time. | -| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | -| Power | Switch | Define the device's current power state. | +| channel | type | description | +|--------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| Target Temperature | Temperature | Defines the desired target temperature for the device | +| Temperature | Temperature | Read-Only channel that indicates the current temperature informed by the device | +| Fan Speed | Number (Labeled) | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | +| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | +| Power | Switch | Define the device's current power state. | **Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart ThinQ way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: 1. **Internet Link** - if you OpenHab server doesn't have a good internet connection this binding will not work properly! In the same way, if the internet link goes down, your Things and Bridge going to be Offline as well, and you won't be able to control the devices though OpenHab until the link comes back. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index 054a4fb5ddb00..fd6321d2018ec 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -33,9 +33,21 @@ public class ACCapability extends AbstractCapability { private List supportedOpMode = Collections.emptyList(); private List supportedFanSpeed = Collections.emptyList(); private boolean isJetModeAvailable; + private boolean isAutoDryModeAvailable; + private boolean isEnergySavingAvailable; + private boolean isAirCleanAvailable; private String coolJetModeCommandOn = ""; private String coolJetModeCommandOff = ""; + private String autoDryModeCommandOn = ""; + private String autoDryModeCommandOff = ""; + + private String energySavingModeCommandOn = ""; + private String energySavingModeCommandOff = ""; + + private String airCleanModeCommandOn = ""; + private String airCleanModeCommandOff = ""; + public String getCoolJetModeCommandOff() { return coolJetModeCommandOff; } @@ -88,7 +100,79 @@ public void setJetModeAvailable(boolean jetModeAvailable) { this.isJetModeAvailable = jetModeAvailable; } + public boolean isAutoDryModeAvailable() { + return isAutoDryModeAvailable; + } + + public void setAutoDryModeAvailable(boolean autoDryModeAvailable) { + isAutoDryModeAvailable = autoDryModeAvailable; + } + + public boolean isEnergySavingAvailable() { + return isEnergySavingAvailable; + } + + public void setEnergySavingAvailable(boolean energySavingAvailable) { + isEnergySavingAvailable = energySavingAvailable; + } + + public boolean isAirCleanAvailable() { + return isAirCleanAvailable; + } + + public void setAirCleanAvailable(boolean airCleanAvailable) { + isAirCleanAvailable = airCleanAvailable; + } + public boolean isJetModeAvailable() { return this.isJetModeAvailable; } + + public String getAutoDryModeCommandOn() { + return autoDryModeCommandOn; + } + + public void setAutoDryModeCommandOn(String autoDryModeCommandOn) { + this.autoDryModeCommandOn = autoDryModeCommandOn; + } + + public String getAutoDryModeCommandOff() { + return autoDryModeCommandOff; + } + + public void setAutoDryModeCommandOff(String autoDryModeCommandOff) { + this.autoDryModeCommandOff = autoDryModeCommandOff; + } + + public String getEnergySavingModeCommandOn() { + return energySavingModeCommandOn; + } + + public void setEnergySavingModeCommandOn(String energySavingModeCommandOn) { + this.energySavingModeCommandOn = energySavingModeCommandOn; + } + + public String getEnergySavingModeCommandOff() { + return energySavingModeCommandOff; + } + + public void setEnergySavingModeCommandOff(String energySavingModeCommandOff) { + this.energySavingModeCommandOff = energySavingModeCommandOff; + } + + public String getAirCleanModeCommandOn() { + return airCleanModeCommandOn; + } + + public void setAirCleanModeCommandOn(String airCleanModeCommandOn) { + this.airCleanModeCommandOn = airCleanModeCommandOn; + } + + public String getAirCleanModeCommandOff() { + return airCleanModeCommandOff; + } + + public void setAirCleanModeCommandOff(String airCleanModeCommandOff) { + this.airCleanModeCommandOff = airCleanModeCommandOff; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java index d8bb7e9187d63..f86e0bb255269 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java @@ -39,7 +39,10 @@ public class ACSnapshot implements Snapshot { private boolean coolJetModeOn; + private double airCleanMode; private double coolJetMode; + private double autoDryMode; + private double energySavingMode; private int operationMode; @Nullable @@ -76,6 +79,36 @@ public Double getCoolJetMode() { return coolJetMode; } + @JsonProperty("airState.wMode.airClean") + @JsonAlias("AirClean") + public Double getAirCleanMode() { + return airCleanMode; + } + + @JsonProperty("airState.miscFuncState.autoDry") + @JsonAlias("AutoDry") + public Double getAutoDryMode() { + return autoDryMode; + } + + @JsonProperty("airState.powerSave.basic") + @JsonAlias("PowerSave") + public Double getEnergySavingMode() { + return energySavingMode; + } + + public void setAirCleanMode(double airCleanMode) { + this.airCleanMode = airCleanMode; + } + + public void setAutoDryMode(double autoDryMode) { + this.autoDryMode = autoDryMode; + } + + public void setEnergySavingMode(double energySavingMode) { + this.energySavingMode = energySavingMode; + } + public void setCoolJetMode(Double coolJetMode) { this.coolJetMode = coolJetMode; } From e0b6fcb96296e18b76c4438a478e90121678aac6 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 31 Oct 2022 08:49:39 -0300 Subject: [PATCH 092/130] [lgthinq][feat] Added support to washing/dryer tower Signed-off-by: nemerdaud --- .../internal/handler/LGThinQDryerHandler.java | 15 +++++++------ .../handler/LGThinQWasherHandler.java | 14 +++++++------ .../lgservices/model/SnapshotFactory.java | 4 ++++ .../main/resources/OH-INF/thing/washer.xml | 21 +++++++++++++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 97725bf5ce357..3e1d2e6098c4d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -68,8 +68,16 @@ public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider dryLevelChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_DRY_LEVEL_ID); } + @Override protected DeviceTypes getDeviceType() { - return DeviceTypes.DRYER; + if (THING_TYPE_DRYER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.DRYER; + } else if (THING_TYPE_DRYER_TOWER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.DRYER_TOWER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for DryerTower/Machine"); + } } @Override @@ -164,11 +172,6 @@ public void onDeviceAdded(LGDevice device) { // TODO - handle it. Think if it's needed } - @Override - public String getDeviceId() { - return getThing().getUID().getId(); - } - @Override public String getDeviceAlias() { return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 046bb9e91056a..4d904e3aa4504 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -157,7 +157,14 @@ protected void updateDeviceChannels(WasherSnapshot shot) { @Override protected DeviceTypes getDeviceType() { - return DeviceTypes.WASHING_MACHINE; + if (THING_TYPE_WASHING_MACHINE.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHING_MACHINE; + } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { + return DeviceTypes.WASHING_TOWER; + } else { + throw new IllegalArgumentException( + "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); + } } @Override @@ -184,11 +191,6 @@ public void onDeviceAdded(LGDevice device) { // TODO - handle it. Think if it's needed } - @Override - public String getDeviceId() { - return getThing().getUID().getId(); - } - @Override public String getDeviceAlias() { return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index b319279242d5f..29ad4eb31e18f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -143,6 +143,7 @@ public S createFromJson(Map deviceSettings, switch (type) { case AIR_CONDITIONER: return clazz.cast(objectMapper.convertValue(snapMap, ACSnapshot.class)); + case WASHING_TOWER: case WASHING_MACHINE: switch (version) { case V1_0: { @@ -155,6 +156,7 @@ public S createFromJson(Map deviceSettings, return clazz.cast(objectMapper.convertValue(washerDryerMap, WasherSnapshot.class)); } } + case DRYER_TOWER: case DRYER: switch (version) { case V1_0: { @@ -201,8 +203,10 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes throw new IllegalStateException( "Unexpected error. Can't find key node attributes to determine AC API version."); } + case DRYER_TOWER: case DRYER: return LGAPIVerion.V2_0; + case WASHING_TOWER: case WASHING_MACHINE: if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { return LGAPIVerion.V2_0; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 031d873663ede..0ed1ca753e4d0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -13,6 +13,26 @@ LG ThinQ Washing Machine + + + + + + + + + + + + + + + + + + + LG ThinQ Washing Tower + @@ -27,4 +47,5 @@ + From db3dbdb3b077423e7dee880b4007b15979c99c33 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 31 Oct 2022 15:34:53 -0300 Subject: [PATCH 093/130] [lgthinq][feat] Added support to heat pump Signed-off-by: nemerdaud --- .../lgthinq/internal/LGThinQConfiguration.java | 12 ++++++------ .../binding/lgthinq/lgservices/model/LGDevice.java | 2 +- .../lgthinq/lgservices/model/SnapshotFactory.java | 6 +++++- .../lgthinq/lgservices/model/ac/ACCapability.java | 10 ++++++++++ 4 files changed, 22 insertions(+), 8 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java index e0e93c7da7c87..fa67679d840f2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java @@ -30,7 +30,7 @@ public class LGThinQConfiguration { public String language = ""; public String manualCountry = ""; public String manualLanguage = ""; - public Integer pollingIntervalSec = 0; + public Integer poolingIntervalSec = 0; public String alternativeServer = ""; public LGThinQConfiguration() { @@ -42,7 +42,7 @@ public LGThinQConfiguration(String username, String password, String country, St this.password = password; this.country = country; this.language = language; - this.pollingIntervalSec = pollingIntervalSec; + this.poolingIntervalSec = pollingIntervalSec; this.alternativeServer = alternativeServer; } @@ -68,8 +68,8 @@ public String getLanguage() { return language; } - public Integer getPollingIntervalSec() { - return pollingIntervalSec; + public Integer getPoolingIntervalSec() { + return poolingIntervalSec; } public void setUsername(String username) { @@ -88,8 +88,8 @@ public void setLanguage(String language) { this.language = language; } - public void setPollingIntervalSec(Integer pollingIntervalSec) { - this.pollingIntervalSec = pollingIntervalSec; + public void setPoolingIntervalSec(Integer poolingIntervalSec) { + this.poolingIntervalSec = poolingIntervalSec; } public String getAlternativeServer() { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java index 18072548e4805..a0605abb5ff9f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGDevice.java @@ -42,7 +42,7 @@ public String getModelName() { @JsonIgnore public DeviceTypes getDeviceType() { - return DeviceTypes.fromDeviceTypeId(deviceTypeId); + return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); } public void setModelName(String modelName) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 29ad4eb31e18f..fc5fae60fb48e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -142,6 +142,7 @@ public S createFromJson(Map deviceSettings, LGAPIVerion version = discoveryAPIVersion(snapMap, type); switch (type) { case AIR_CONDITIONER: + case HEAT_PUMP: return clazz.cast(objectMapper.convertValue(snapMap, ACSnapshot.class)); case WASHING_TOWER: case WASHING_MACHINE: @@ -188,13 +189,16 @@ public S createFromJson(Map deviceSettings, private DeviceTypes getDeviceType(Map rootMap) { Integer deviceTypeId = (Integer) rootMap.get("deviceType"); + // device code is only present in v2 devices snapshot. + String deviceCode = Objects.requireNonNullElse((String) rootMap.get("deviceCode"), ""); Objects.requireNonNull(deviceTypeId, "Unexpected error. deviceType field not present in snapshot schema"); - return DeviceTypes.fromDeviceTypeId(deviceTypeId); + return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); } private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { switch (type) { case AIR_CONDITIONER: + case HEAT_PUMP: if (snapMap.containsKey("airState.opMode")) { return LGAPIVerion.V2_0; } else if (snapMap.containsKey("OpMode")) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java index fd6321d2018ec..3b4f13ddfdc6e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java @@ -36,6 +36,8 @@ public class ACCapability extends AbstractCapability { private boolean isAutoDryModeAvailable; private boolean isEnergySavingAvailable; private boolean isAirCleanAvailable; + + private boolean isFanSpeedAvailable; private String coolJetModeCommandOn = ""; private String coolJetModeCommandOff = ""; @@ -116,6 +118,14 @@ public void setEnergySavingAvailable(boolean energySavingAvailable) { isEnergySavingAvailable = energySavingAvailable; } + public boolean isFanSpeedAvailable() { + return isFanSpeedAvailable; + } + + public void setFanSpeedAvailable(boolean fanSpeedAvailable) { + isFanSpeedAvailable = fanSpeedAvailable; + } + public boolean isAirCleanAvailable() { return isAirCleanAvailable; } From c25ac4438fddfc200cb359d26770cd017fbf0a76 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 1 Nov 2022 16:35:22 -0300 Subject: [PATCH 094/130] [lgthinq][feat] Added support to Washer & Dryer for the commands Wakeup & RemoteStart Signed-off-by: nemerdaud --- .../LGThinqDeviceV1OfflineException.java | 2 +- .../internal/handler/LGThinQDryerHandler.java | 90 ++++++++++++++++-- .../handler/LGThinQWasherHandler.java | 88 ++++++++++++++++-- .../lgservices/LGThinQDRApiClientService.java | 8 +- .../lgservices/LGThinQWMApiClientService.java | 8 +- .../lgthinq/lgservices/model/ModelUtils.java | 12 +-- .../lgservices/model/SnapshotFactory.java | 4 +- .../model/washerdryer/CommandCapability.java | 91 +++++++++++++++++++ .../DryerCapability.java | 2 +- .../{dryer => washerdryer}/DryerSnapshot.java | 38 +++++++- .../WasherCapability.java | 2 +- .../WasherSnapshot.java | 39 +++++++- 12 files changed, 350 insertions(+), 34 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{dryer => washerdryer}/DryerCapability.java (98%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{dryer => washerdryer}/DryerSnapshot.java (82%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{washer => washerdryer}/WasherCapability.java (98%) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{washer => washerdryer}/WasherSnapshot.java (84%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java index ef4d5163a0af6..1bdbae5a9cc73 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java @@ -22,7 +22,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinqDeviceV1OfflineException extends LGThinqException { +public class LGThinqDeviceV1OfflineException extends LGThinqApiException { public LGThinqDeviceV1OfflineException(String message, Throwable cause) { super(message, cause); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java index 3e1d2e6098c4d..18f57f3593813 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java @@ -15,9 +15,9 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import java.util.*; -import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -26,12 +26,16 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; -import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.StateOption; import org.slf4j.Logger; @@ -52,7 +56,10 @@ public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); stateDescriptionProvider.setStateOptions(dryLevelChannelUUID, options); } + if (isLinked(remoteStartChannelUUID)) { + List options = new ArrayList<>(); + options.add(new StateOption("REMOTE_START_OFF", "OFF")); + options.add(new StateOption("REMOTE_START_ON", "ON")); + stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); + } + if (getThing().getChannel(standbyChannelUUID) == null) { + createDynChannel(WM_CHANNEL_STAND_BY_ID, standbyChannelUUID, "Switch"); + } + if (isLinked(standbyChannelUUID)) { + List options = new ArrayList<>(); + options.add(new StateOption("STANDBY_OFF", "OFF")); + options.add(new StateOption("STANDBY_ON", "ON")); + stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); + } } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java index 4d904e3aa4504..00d13bc21a22c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java @@ -18,6 +18,7 @@ import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; @@ -26,12 +27,16 @@ import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; import org.openhab.core.library.types.DateTimeType; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; import org.openhab.core.thing.*; +import org.openhab.core.thing.binding.ThingHandlerCallback; +import org.openhab.core.thing.binding.builder.ChannelBuilder; +import org.openhab.core.thing.binding.builder.ThingBuilder; +import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.StateOption; import org.slf4j.Logger; @@ -53,6 +58,10 @@ public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler options = new ArrayList<>(); + options.add(new StateOption("REMOTE_START_OFF", "OFF")); + options.add(new StateOption("REMOTE_START_ON", "ON")); + stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); + } + if (getThing().getChannel(standbyChannelUUID) == null) { + createDynChannel(WM_CHANNEL_STAND_BY_ID, standbyChannelUUID, "Switch"); + } + if (isLinked(standbyChannelUUID)) { + List options = new ArrayList<>(); + options.add(new StateOption("STANDBY_OFF", "OFF")); + options.add(new StateOption("STANDBY_ON", "ON")); + stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); + } } @Override @@ -143,6 +169,7 @@ protected Logger getLogger() { @Override protected void updateDeviceChannels(WasherSnapshot shot) { + lastShot = shot; updateState(CHANNEL_POWER_ID, (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); @@ -153,6 +180,27 @@ protected void updateDeviceChannels(WasherSnapshot shot) { updateState(WM_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); updateState(WM_CHANNEL_DELAY_TIME_ID, new DateTimeType(shot.getReserveTime())); updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); + updateState(WM_CHANNEL_STAND_BY_ID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); + Channel remoteStartChannel = getThing().getChannel(remoteStartChannelUUID); + // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. + if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { + ThingHandlerCallback callback = getCallback(); + if (remoteStartChannel == null && callback != null) { + ChannelBuilder builder = getCallback().createChannelBuilder(remoteStartChannelUUID, + new ChannelTypeUID(BINDING_ID, WM_CHANNEL_REMOTE_START_ID)); + Channel newChannel = builder.build(); + ThingBuilder thingBuilder = editThing(); + updateThing(thingBuilder.withChannel(newChannel).build()); + } + if (isLinked(remoteStartChannelUUID)) { + updateState(WM_CHANNEL_REMOTE_START_ID, new StringType(shot.getRemoteStart())); + } + } else { + if (remoteStartChannel != null) { + ThingBuilder builder = editThing().withoutChannels(remoteStartChannel); + updateThing(builder.build()); + } + } } @Override @@ -171,12 +219,40 @@ protected DeviceTypes getDeviceType() { protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { Command command = params.command; switch (params.channelUID) { - case CHANNEL_POWER_ID: { + case WM_CHANNEL_REMOTE_START_ID: { + if (command instanceof StringType) { + if ("START".equalsIgnoreCase(command.toString())) { + if (lastShot != null && !lastShot.isStandBy()) { + lgThinqWMApiClientService.remoteStart(getBridgeId(), getDeviceId()); + } else { + logger.warn( + "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); + } + } else { + logger.warn( + "Command [{}] sent to Remote Start channel is invalid. Only command START is valid.", + command); + } + } else { + logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); + } + break; + } + case WM_CHANNEL_STAND_BY_ID: { if (command instanceof OnOffType) { - lgThinqWMApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), - command == OnOffType.ON ? DevicePowerState.DV_POWER_ON : DevicePowerState.DV_POWER_OFF); + if (OnOffType.OFF.equals(command)) { + if (lastShot == null || !lastShot.isStandBy()) { + logger.warn( + "Command OFF was sent to StandBy channel, but the state of the WM is unknown or already waked up. Ignoring"); + break; + } + lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId()); + } else { + logger.warn("Command [{}] sent to StandBy channel is invalid. Only command OFF is valid.", + command); + } } else { - logger.warn("Received command different of OnOffType in Power Channel. Ignoring"); + logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); } break; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java index b296cb6d8c75a..560c59aacdc14 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java @@ -13,8 +13,9 @@ package org.openhab.binding.lgthinq.lgservices; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerCapability; -import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; /** * The {@link LGThinQDRApiClientService} @@ -23,4 +24,7 @@ */ @NonNullByDefault public interface LGThinQDRApiClientService extends LGThinQApiClientService { + void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException; + + void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java index 6010a18b83663..9cead6737241f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java @@ -13,8 +13,9 @@ package org.openhab.binding.lgthinq.lgservices; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherCapability; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherCapability; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; /** * The {@link LGThinQWMApiClientService} @@ -23,4 +24,7 @@ */ @NonNullByDefault public interface LGThinQWMApiClientService extends LGThinQApiClientService { + void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException; + + void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java index 6518a0d1c47ea..c182b2d1b26ec 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java @@ -38,12 +38,6 @@ public static DeviceTypes getDeviceType(Map rootMap) { return fromDeviceTypeAcron(productType, modelType); } - public static DeviceTypes getDeviceType(JsonNode rootNode) { - Map mapper = objectMapper.convertValue(rootNode, new TypeReference<>() { - }); - return getDeviceType(mapper); - } - public static LGAPIVerion discoveryAPIVersion(JsonNode rootNode) { Map mapper = objectMapper.convertValue(rootNode, new TypeReference<>() { }); @@ -62,10 +56,10 @@ public static LGAPIVerion discoveryAPIVersion(Map rootMap) { return LGAPIVerion.V1_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); + "Unexpected error. Can't find key node attributes to determine AC API version."); } - case WASHERDRYER_MACHINE: + case WASHING_MACHINE: case DRYER: case REFRIGERATOR: if (rootMap.containsKey("Value")) { @@ -74,7 +68,7 @@ public static LGAPIVerion discoveryAPIVersion(Map rootMap) { return LGAPIVerion.V2_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); + "Unexpected error. Can't find key node attributes to determine AC API version."); } case DISH_WASHER: return LGAPIVerion.V2_0; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index fc5fae60fb48e..9bf79c5a61b5f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -26,9 +26,9 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.dryer.DryerSnapshot; import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; -import org.openhab.binding.lgthinq.lgservices.model.washer.WasherSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java new file mode 100644 index 0000000000000..ac615e7c2b034 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java @@ -0,0 +1,91 @@ +/** + * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.ModelUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The {@link CommandCapability} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class CommandCapability { + private boolean isPowerCommandsAvailable = false; + private String powerOffCommand = ""; + private String stopCommand = ""; + private String wakeUpCommand = ""; + private static final Logger logger = LoggerFactory.getLogger(CommandCapability.class); + + public void loadCommands(JsonNode rootNode) throws LGThinqApiException { + LGAPIVerion version = ModelUtils.discoveryAPIVersion(rootNode); + switch (version) { + case V1_0: + logger.warn("Version {} for commands of Dryer/Washers not supported for this binding.", + version.getValue()); + return; + case V2_0: + JsonNode wifiNode = rootNode.path("ControlWifi"); + if (wifiNode.isMissingNode()) { + logger.warn( + "Dryer/Washer is missing ControlWifi node in the model. Commands are not supported for this model."); + return; + } + JsonNode wmOffNode = wifiNode.path("WMOff"); + JsonNode wmStopNode = wifiNode.path("WMStop"); + JsonNode wmWakeUpNode = wifiNode.path("WMWakeup"); + boolean isOffPresent = !wmOffNode.isMissingNode(); + boolean isStopPresent = !wmStopNode.isMissingNode(); + boolean isWakeUpPresent = !wmWakeUpNode.isMissingNode(); + if (isOffPresent || isStopPresent || isWakeUpPresent) { + isPowerCommandsAvailable = true; + powerOffCommand = isOffPresent + ? wmOffNode.path("data").path("washerDryer").path("controlDataType").textValue() + : ""; + stopCommand = isStopPresent + ? wmStopNode.path("data").path("washerDryer").path("controlDataType").textValue() + : ""; + wakeUpCommand = isWakeUpPresent + ? wmWakeUpNode.path("data").path("washerDryer").path("controlDataType").textValue() + : ""; + } + } + } + + public boolean isPowerCommandsAvailable() { + return isPowerCommandsAvailable; + } + + public void setPowerCommandsAvailable(boolean powerCommandsAvailable) { + isPowerCommandsAvailable = powerCommandsAvailable; + } + + public String getPowerOffCommand() { + return powerOffCommand; + } + + public String getStopCommand() { + return stopCommand; + } + + public String getWakeUpCommand() { + return wakeUpCommand; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java similarity index 98% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java index bb8aa208a21d7..d29b07e1f0124 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.dryer; +package org.openhab.binding.lgthinq.lgservices.model.washerdryer; import java.util.LinkedHashMap; import java.util.Map; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java similarity index 82% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java index f3972e10620d6..812340b2fec51 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/dryer/DryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.dryer; +package org.openhab.binding.lgthinq.lgservices.model.washerdryer; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; @@ -45,6 +45,12 @@ public class DryerSnapshot implements Snapshot { private Double remainingMinute = 0.00; private String dryLevel = ""; private String error = ""; + private String remoteStart = ""; + private boolean remoteStartEnabled = false; + + private String standByStatus = ""; + + private boolean standBy = false; @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) @JsonProperty("courseDryer24inchBase") @@ -164,4 +170,34 @@ public void setState(String state) { powerState = DevicePowerState.DV_POWER_ON; } } + + public boolean isRemoteStartEnabled() { + return remoteStartEnabled; + } + + @JsonProperty("remoteStart") + @JsonAlias({ "RemoteStart" }) + public String getRemoteStart() { + return remoteStart; + } + + public void setRemoteStart(String remoteStart) { + this.remoteStart = remoteStart; + remoteStartEnabled = remoteStart.contains("ON"); + } + + public String getStandByStatus() { + return standByStatus; + } + + @JsonProperty("standby") + @JsonAlias({ "Standby" }) + public void setStandByStatus(String standByStatus) { + this.standByStatus = standByStatus; + standBy = standByStatus.contains("ON"); + } + + public boolean isStandBy() { + return standBy; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java similarity index 98% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java index d232aaf70afda..05535d455c3ea 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.washer; +package org.openhab.binding.lgthinq.lgservices.model.washerdryer; import java.util.LinkedHashMap; import java.util.Map; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java similarity index 84% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java index 84743ee0e0980..004474dbf76fd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washer/WasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java @@ -10,7 +10,7 @@ * * SPDX-License-Identifier: EPL-2.0 */ -package org.openhab.binding.lgthinq.lgservices.model.washer; +package org.openhab.binding.lgthinq.lgservices.model.washerdryer; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; @@ -47,6 +47,13 @@ public class WasherSnapshot implements Snapshot { private Double reserveHour = 0.00; private Double reserveMinute = 0.00; + private String remoteStart = ""; + private boolean remoteStartEnabled = false; + + private String standByStatus = ""; + + private boolean standBy = false; + @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) @JsonProperty("courseFL24inchBaseTitan") public String getCourse() { @@ -181,4 +188,34 @@ public void setState(String state) { powerState = DevicePowerState.DV_POWER_ON; } } + + public boolean isRemoteStartEnabled() { + return remoteStartEnabled; + } + + @JsonProperty("remoteStart") + @JsonAlias({ "RemoteStart" }) + public String getRemoteStart() { + return remoteStart; + } + + public void setRemoteStart(String remoteStart) { + this.remoteStart = remoteStart; + remoteStartEnabled = remoteStart.contains("ON"); + } + + public String getStandByStatus() { + return standByStatus; + } + + @JsonProperty("standby") + @JsonAlias({ "Standby" }) + public void setStandByStatus(String standByStatus) { + this.standByStatus = standByStatus; + standBy = standByStatus.contains("ON"); + } + + public boolean isStandBy() { + return standBy; + } } From 497cec501cec443b5770604f9355c0c0ae1f59af Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Sun, 26 Feb 2023 14:33:45 -0300 Subject: [PATCH 095/130] [lgthinq][feat] Refactory and WasherDryer commands Signed-off-by: nemerdaud --- .../internal/LGThinQConfiguration.java | 102 ------ .../internal/handler/LGThinQDryerHandler.java | 268 ---------------- .../handler/LGThinQWasherHandler.java | 299 ------------------ .../lgservices/LGThinQApiClientService.java | 2 +- .../lgservices/LGThinQDRApiClientService.java | 6 +- .../lgservices/LGThinQWMApiClientService.java | 13 +- .../lgservices/model/AbstractCapability.java | 48 --- .../lgthinq/lgservices/model/Capability.java | 30 -- .../lgthinq/lgservices/model/LGAPIVerion.java | 3 +- .../lgthinq/lgservices/model/ModelUtils.java | 12 +- .../lgthinq/lgservices/model/Snapshot.java | 32 -- .../lgservices/model/SnapshotFactory.java | 68 ++-- .../lgservices/model/ac/ACCapability.java | 188 ----------- .../lgservices/model/ac/ACFanSpeed.java | 91 ------ .../lgthinq/lgservices/model/ac/ACOpMode.java | 89 ------ .../lgservices/model/ac/ACSnapshot.java | 186 ----------- .../lgservices/model/ac/ACTargetTmp.java | 93 ------ .../model/fridge/FridgeCapability.java | 37 --- .../model/fridge/FridgeFactory.java | 47 --- .../model/fridge/FridgeSnapshot.java | 32 -- .../model/fridge/v2/FridgeCapabilityV2.java | 100 ------ .../model/fridge/v2/FridgeSnapshotV2.java | 114 ------- .../model/washerdryer/CommandCapability.java | 91 ------ .../model/washerdryer/DryerCapability.java | 129 -------- .../model/washerdryer/DryerSnapshot.java | 203 ------------ .../model/washerdryer/WasherCapability.java | 153 --------- .../model/washerdryer/WasherSnapshot.java | 221 ------------- .../main/resources/OH-INF/thing/washer.xml | 6 +- 28 files changed, 67 insertions(+), 2596 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java deleted file mode 100644 index fa67679d840f2..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQConfiguration.java +++ /dev/null @@ -1,102 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGThinQConfiguration} class contains fields mapping thing configuration parameters. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinQConfiguration { - /** - * Sample configuration parameters. Replace with your own. - */ - public String username = ""; - public String password = ""; - public String country = ""; - public String language = ""; - public String manualCountry = ""; - public String manualLanguage = ""; - public Integer poolingIntervalSec = 0; - public String alternativeServer = ""; - - public LGThinQConfiguration() { - } - - public LGThinQConfiguration(String username, String password, String country, String language, - Integer pollingIntervalSec, String alternativeServer) { - this.username = username; - this.password = password; - this.country = country; - this.language = language; - this.poolingIntervalSec = pollingIntervalSec; - this.alternativeServer = alternativeServer; - } - - public String getUsername() { - return username; - } - - public String getPassword() { - return password; - } - - public String getCountry() { - if ("--".equals(country)) { - return manualCountry; - } - return country; - } - - public String getLanguage() { - if ("--".equals(language)) { - return manualLanguage; - } - return language; - } - - public Integer getPoolingIntervalSec() { - return poolingIntervalSec; - } - - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setCountry(String country) { - this.country = country; - } - - public void setLanguage(String language) { - this.language = language; - } - - public void setPoolingIntervalSec(Integer poolingIntervalSec) { - this.poolingIntervalSec = poolingIntervalSec; - } - - public String getAlternativeServer() { - return alternativeServer; - } - - public void setAlternativeServer(String alternativeServer) { - this.alternativeServer = alternativeServer; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java deleted file mode 100644 index 18f57f3593813..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDryerHandler.java +++ /dev/null @@ -1,268 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.handler; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import java.util.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQDRApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerCapability; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerCallback; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.types.Command; -import org.openhab.core.types.StateOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinQDryerHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinQDryerHandler extends LGThinQAbstractDeviceHandler { - - private final ChannelUID stateChannelUUID; - private final ChannelUID processStateChannelUUID; - private final ChannelUID dryLevelChannelUUID; - private final ChannelUID errorChannelUUID; - private final ChannelUID courseChannelUUID; - private final ChannelUID smartCourseChannelUUID; - private final ChannelUID remoteStartChannelUUID; - private final ChannelUID standbyChannelUUID; - @Nullable - private DryerSnapshot lastShot; - private final Logger logger = LoggerFactory.getLogger(LGThinQDryerHandler.class); - @NonNullByDefault - private final LGThinQDRApiClientService lgThinqDRApiClientService; - - public LGThinQDryerHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing, stateDescriptionProvider); - lgThinqDRApiClientService = LGThinQDRApiV2ClientServiceImpl.getInstance(); - stateChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_STATE_ID); - courseChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_COURSE_ID); - smartCourseChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_SMART_COURSE_ID); - processStateChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_PROCESS_STATE_ID); - errorChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_ERROR_ID); - dryLevelChannelUUID = new ChannelUID(getThing().getUID(), DR_CHANNEL_DRY_LEVEL_ID); - remoteStartChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_ID); - standbyChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STAND_BY_ID); - } - - @Override - protected DeviceTypes getDeviceType() { - if (THING_TYPE_DRYER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.DRYER; - } else if (THING_TYPE_DRYER_TOWER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.DRYER_TOWER; - } else { - throw new IllegalArgumentException( - "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for DryerTower/Machine"); - } - } - - @Override - public void initialize() { - logger.debug("Initializing Thinq thing."); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - - @Override - protected void updateDeviceChannels(DryerSnapshot shot) { - lastShot = shot; - updateState(CHANNEL_POWER_ID, OnOffType.from(shot.getPowerStatus() == DevicePowerState.DV_POWER_ON)); - updateState(DR_CHANNEL_STATE_ID, new StringType(shot.getState())); - updateState(DR_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); - updateState(DR_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); - updateState(DR_CHANNEL_PROCESS_STATE_ID, new StringType(shot.getProcessState())); - updateState(DR_CHANNEL_CHILD_LOCK_ID, new StringType(shot.getChildLock())); - updateState(DR_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); - updateState(DR_CHANNEL_DRY_LEVEL_ID, new StringType(shot.getDryLevel())); - updateState(DR_CHANNEL_ERROR_ID, new StringType(shot.getError())); - updateState(WM_CHANNEL_STAND_BY_ID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); - Channel remoteStartChannel = getThing().getChannel(remoteStartChannelUUID); - // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. - if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { - ThingHandlerCallback callback = getCallback(); - if (remoteStartChannel == null && callback != null) { - ChannelBuilder builder = getCallback().createChannelBuilder(remoteStartChannelUUID, - new ChannelTypeUID(BINDING_ID, WM_CHANNEL_REMOTE_START_ID)); - Channel newChannel = builder.build(); - ThingBuilder thingBuilder = editThing(); - updateThing(thingBuilder.withChannel(newChannel).build()); - } - if (isLinked(remoteStartChannelUUID)) { - updateState(WM_CHANNEL_REMOTE_START_ID, new StringType(shot.getRemoteStart())); - } - } else { - if (remoteStartChannel != null) { - ThingBuilder builder = editThing().withoutChannels(remoteStartChannel); - updateThing(builder.build()); - } - } - } - - @Override - protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { - Command command = params.command; - switch (params.channelUID) { - case WM_CHANNEL_REMOTE_START_ID: { - if (command instanceof StringType) { - if ("START".equalsIgnoreCase(command.toString())) { - if (lastShot != null && !lastShot.isStandBy()) { - lgThinqDRApiClientService.remoteStart(getBridgeId(), getDeviceId()); - } else { - logger.warn( - "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); - } - } else { - logger.warn( - "Command [{}] sent to Remote Start channel is invalid. Only command START is valid.", - command); - } - } else { - logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); - } - break; - } - case WM_CHANNEL_STAND_BY_ID: { - if (command instanceof OnOffType) { - if (OnOffType.OFF.equals(command)) { - if (lastShot == null || !lastShot.isStandBy()) { - logger.warn( - "Command OFF was sent to StandBy channel, but the state of the WM is unknown or already waked up. Ignoring"); - break; - } - lgThinqDRApiClientService.wakeUp(getBridgeId(), getDeviceId()); - } else { - logger.warn("Command [{}] sent to StandBy channel is invalid. Only command OFF is valid.", - command); - } - } else { - logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); - } - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); - } - } - } - - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - DryerCapability drCap = getCapabilities(); - if (isLinked(stateChannelUUID)) { - List options = new ArrayList<>(); - // invert key/value - drCap.getState().forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_STATE, v)))); - stateDescriptionProvider.setStateOptions(stateChannelUUID, options); - } - if (isLinked(courseChannelUUID)) { - List options = new ArrayList<>(); - drCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(courseChannelUUID, options); - } - if (isLinked(smartCourseChannelUUID)) { - List options = new ArrayList<>(); - drCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); - } - if (isLinked(processStateChannelUUID)) { - List options = new ArrayList<>(); - drCap.getProcessStates() - .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_PROCESS_STATE, v)))); - stateDescriptionProvider.setStateOptions(processStateChannelUUID, options); - } - if (isLinked(errorChannelUUID)) { - List options = new ArrayList<>(); - drCap.getErrors().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(errorChannelUUID, options); - } - if (isLinked(dryLevelChannelUUID)) { - List options = new ArrayList<>(); - drCap.getDryLevels() - .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_DR_DRY_LEVEL, v)))); - stateDescriptionProvider.setStateOptions(dryLevelChannelUUID, options); - } - if (isLinked(remoteStartChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("REMOTE_START_OFF", "OFF")); - options.add(new StateOption("REMOTE_START_ON", "ON")); - stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); - } - if (getThing().getChannel(standbyChannelUUID) == null) { - createDynChannel(WM_CHANNEL_STAND_BY_ID, standbyChannelUUID, "Switch"); - } - if (isLinked(standbyChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("STANDBY_OFF", "OFF")); - options.add(new StateOption("STANDBY_ON", "ON")); - stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); - } - } - - @Override - public LGThinQApiClientService getLgThinQAPIClientService() { - return lgThinqDRApiClientService; - } - - @Override - protected Logger getLogger() { - return logger; - } - - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } - - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } - - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } - - @Override - public void onDeviceDisconnected() { - // TODO - HANDLE IT, Think if it's needed - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java deleted file mode 100644 index 00d13bc21a22c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherHandler.java +++ /dev/null @@ -1,299 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal.handler; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import java.util.*; -import java.util.concurrent.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQDeviceDynStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; -import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiV2ClientServiceImpl; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherCapability; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; -import org.openhab.core.library.types.DateTimeType; -import org.openhab.core.library.types.OnOffType; -import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.*; -import org.openhab.core.thing.binding.ThingHandlerCallback; -import org.openhab.core.thing.binding.builder.ChannelBuilder; -import org.openhab.core.thing.binding.builder.ThingBuilder; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.types.Command; -import org.openhab.core.types.StateOption; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link LGThinQWasherHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinQWasherHandler extends LGThinQAbstractDeviceHandler { - - private final LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider; - private final ChannelUID stateChannelUUID; - private final ChannelUID courseChannelUUID; - private final ChannelUID smartCourseChannelUUID; - private final ChannelUID downloadedCourseChannelUUID; - private final ChannelUID temperatureChannelUUID; - private final ChannelUID doorLockChannelUUID; - private final ChannelUID remoteStartChannelUUID; - private final ChannelUID standbyChannelUUID; - @Nullable - private WasherSnapshot lastShot; - - private final Logger logger = LoggerFactory.getLogger(LGThinQWasherHandler.class); - @NonNullByDefault - private final LGThinQWMApiClientService lgThinqWMApiClientService; - - // *** Long running isolated threadpools. - private final ScheduledExecutorService pollingScheduler = Executors.newScheduledThreadPool(1); - - private final LinkedBlockingQueue commandBlockQueue = new LinkedBlockingQueue<>(20); - - @NonNullByDefault - - public LGThinQWasherHandler(Thing thing, LGThinQDeviceDynStateDescriptionProvider stateDescriptionProvider) { - super(thing, stateDescriptionProvider); - this.stateDescriptionProvider = stateDescriptionProvider; - lgThinqWMApiClientService = LGThinQWMApiV2ClientServiceImpl.getInstance(); - stateChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STATE_ID); - courseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_COURSE_ID); - smartCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_SMART_COURSE_ID); - downloadedCourseChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_DOWNLOADED_COURSE_ID); - temperatureChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_TEMP_LEVEL_ID); - doorLockChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_DOOR_LOCK_ID); - remoteStartChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_ID); - standbyChannelUUID = new ChannelUID(getThing().getUID(), WM_CHANNEL_STAND_BY_ID); - } - - static class AsyncCommandParams { - final String channelUID; - final Command command; - - public AsyncCommandParams(String channelUUID, Command command) { - this.channelUID = channelUUID; - this.command = command; - } - } - - @Override - public void initialize() { - logger.debug("Initializing Thinq thing. Washer Thing v0.1"); - Bridge bridge = getBridge(); - initializeThing((bridge == null) ? null : bridge.getStatus()); - } - - @Override - public void updateChannelDynStateDescription() throws LGThinqApiException { - WasherCapability wmCap = getCapabilities(); - if (isLinked(stateChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getState().forEach((k, v) -> options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_STATE, k)))); - stateDescriptionProvider.setStateOptions(stateChannelUUID, options); - } - if (isLinked(courseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(courseChannelUUID, options); - } - if (isLinked(smartCourseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(smartCourseChannelUUID, options); - } - if (isLinked(downloadedCourseChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getSmartCourses().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(v)))); - stateDescriptionProvider.setStateOptions(downloadedCourseChannelUUID, options); - } - if (isLinked(temperatureChannelUUID)) { - List options = new ArrayList<>(); - wmCap.getTemperature() - .forEach((k, v) -> options.add(new StateOption(v, keyIfValueNotFound(CAP_WP_TEMPERATURE, k)))); - stateDescriptionProvider.setStateOptions(temperatureChannelUUID, options); - } - if (isLinked(doorLockChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("0", "Unlocked")); - options.add(new StateOption("1", "Locked")); - stateDescriptionProvider.setStateOptions(doorLockChannelUUID, options); - } - if (isLinked(remoteStartChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("REMOTE_START_OFF", "OFF")); - options.add(new StateOption("REMOTE_START_ON", "ON")); - stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); - } - if (getThing().getChannel(standbyChannelUUID) == null) { - createDynChannel(WM_CHANNEL_STAND_BY_ID, standbyChannelUUID, "Switch"); - } - if (isLinked(standbyChannelUUID)) { - List options = new ArrayList<>(); - options.add(new StateOption("STANDBY_OFF", "OFF")); - options.add(new StateOption("STANDBY_ON", "ON")); - stateDescriptionProvider.setStateOptions(remoteStartChannelUUID, options); - } - } - - @Override - public LGThinQApiClientService getLgThinQAPIClientService() { - return lgThinqWMApiClientService; - } - - @Override - protected Logger getLogger() { - return logger; - } - - @Override - protected void updateDeviceChannels(WasherSnapshot shot) { - lastShot = shot; - updateState(CHANNEL_POWER_ID, - (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); - updateState(WM_CHANNEL_STATE_ID, new StringType(shot.getState())); - updateState(WM_CHANNEL_COURSE_ID, new StringType(shot.getCourse())); - updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType(shot.getSmartCourse())); - updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType(shot.getTemperatureLevel())); - updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType(shot.getDoorLock())); - updateState(WM_CHANNEL_REMAIN_TIME_ID, new DateTimeType(shot.getRemainingTime())); - updateState(WM_CHANNEL_DELAY_TIME_ID, new DateTimeType(shot.getReserveTime())); - updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType(shot.getDownloadedCourse())); - updateState(WM_CHANNEL_STAND_BY_ID, shot.isStandBy() ? OnOffType.ON : OnOffType.OFF); - Channel remoteStartChannel = getThing().getChannel(remoteStartChannelUUID); - // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. - if (shot.isRemoteStartEnabled() && !shot.isStandBy()) { - ThingHandlerCallback callback = getCallback(); - if (remoteStartChannel == null && callback != null) { - ChannelBuilder builder = getCallback().createChannelBuilder(remoteStartChannelUUID, - new ChannelTypeUID(BINDING_ID, WM_CHANNEL_REMOTE_START_ID)); - Channel newChannel = builder.build(); - ThingBuilder thingBuilder = editThing(); - updateThing(thingBuilder.withChannel(newChannel).build()); - } - if (isLinked(remoteStartChannelUUID)) { - updateState(WM_CHANNEL_REMOTE_START_ID, new StringType(shot.getRemoteStart())); - } - } else { - if (remoteStartChannel != null) { - ThingBuilder builder = editThing().withoutChannels(remoteStartChannel); - updateThing(builder.build()); - } - } - } - - @Override - protected DeviceTypes getDeviceType() { - if (THING_TYPE_WASHING_MACHINE.equals(getThing().getThingTypeUID())) { - return DeviceTypes.WASHING_MACHINE; - } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.WASHING_TOWER; - } else { - throw new IllegalArgumentException( - "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); - } - } - - @Override - protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams params) throws LGThinqApiException { - Command command = params.command; - switch (params.channelUID) { - case WM_CHANNEL_REMOTE_START_ID: { - if (command instanceof StringType) { - if ("START".equalsIgnoreCase(command.toString())) { - if (lastShot != null && !lastShot.isStandBy()) { - lgThinqWMApiClientService.remoteStart(getBridgeId(), getDeviceId()); - } else { - logger.warn( - "WM is in StandBy mode. Command START can't be sent to Remote Start channel. Ignoring"); - } - } else { - logger.warn( - "Command [{}] sent to Remote Start channel is invalid. Only command START is valid.", - command); - } - } else { - logger.warn("Received command different of StringType in Remote Start Channel. Ignoring"); - } - break; - } - case WM_CHANNEL_STAND_BY_ID: { - if (command instanceof OnOffType) { - if (OnOffType.OFF.equals(command)) { - if (lastShot == null || !lastShot.isStandBy()) { - logger.warn( - "Command OFF was sent to StandBy channel, but the state of the WM is unknown or already waked up. Ignoring"); - break; - } - lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId()); - } else { - logger.warn("Command [{}] sent to StandBy channel is invalid. Only command OFF is valid.", - command); - } - } else { - logger.warn("Received command different of OnOffType in StandBy Channel. Ignoring"); - } - break; - } - default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); - } - } - } - - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - - @Override - public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); - } - - @Override - public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); - } - - @Override - public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed - } - - /** - * Put the channels in default state if the device is disconnected or gone. - */ - @Override - public void onDeviceDisconnected() { - updateState(CHANNEL_POWER_ID, OnOffType.OFF); - updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); - updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); - updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); - updateState(WM_CHANNEL_DOWNLOADED_COURSE_ID, new StringType("NOT_SELECTED")); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index 0189893ea6e87..0404d7d141a0d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -29,7 +29,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGThinQApiClientService { +public interface LGThinQApiClientService { List listAccountDevices(String bridgeName) throws LGThinqApiException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java index 560c59aacdc14..e1a2dd156a559 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java @@ -14,8 +14,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerCapability; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; /** * The {@link LGThinQDRApiClientService} @@ -23,7 +23,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGThinQDRApiClientService extends LGThinQApiClientService { +public interface LGThinQDRApiClientService extends LGThinQApiClientService { void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException; void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java index 9cead6737241f..29d197c4662ee 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java @@ -12,10 +12,12 @@ */ package org.openhab.binding.lgthinq.lgservices; +import java.util.Map; + import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherCapability; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; /** * The {@link LGThinQWMApiClientService} @@ -23,8 +25,9 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public interface LGThinQWMApiClientService extends LGThinQApiClientService { - void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException; +public interface LGThinQWMApiClientService extends LGThinQApiClientService { + void remoteStart(String bridgeName, WasherDryerCapability cap, String deviceId, Map data) + throws LGThinqApiException; - void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException; + void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java deleted file mode 100644 index f3ee28162257d..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; - -import java.util.ArrayList; -import java.util.List; - -/** - * The {@link AbstractCapability} - * - * @author Nemer Daud - Initial contribution - */ -public abstract class AbstractCapability implements Capability { - // default result format - private MonitoringResultFormat monitoringDataFormat = MonitoringResultFormat.JSON_FORMAT; - - private List monitoringBinaryProtocol = new ArrayList<>(); - - @Override - public MonitoringResultFormat getMonitoringDataFormat() { - return monitoringDataFormat; - } - - @Override - public void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat) { - this.monitoringDataFormat = monitoringDataFormat; - } - - @Override - public List getMonitoringBinaryProtocol() { - return monitoringBinaryProtocol; - } - - @Override - public void setMonitoringBinaryProtocol(List monitoringBinaryProtocol) { - this.monitoringBinaryProtocol = monitoringBinaryProtocol; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java deleted file mode 100644 index a36bcfaea25d7..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Capability.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; - -import java.util.List; - -/** - * The {@link Capability} - * - * @author Nemer Daud - Initial contribution - */ -public interface Capability { - MonitoringResultFormat getMonitoringDataFormat(); - - void setMonitoringDataFormat(MonitoringResultFormat monitoringDataFormat); - - List getMonitoringBinaryProtocol(); - - void setMonitoringBinaryProtocol(List monitoringBinaryProtocol); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java index 6b223424bf7e5..579f4f797e71b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java @@ -19,7 +19,8 @@ */ public enum LGAPIVerion { V1_0(1.0), - V2_0(2.0); + V2_0(2.0), + UNDEF(0.0); private final double version; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java index c182b2d1b26ec..6518a0d1c47ea 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java @@ -38,6 +38,12 @@ public static DeviceTypes getDeviceType(Map rootMap) { return fromDeviceTypeAcron(productType, modelType); } + public static DeviceTypes getDeviceType(JsonNode rootNode) { + Map mapper = objectMapper.convertValue(rootNode, new TypeReference<>() { + }); + return getDeviceType(mapper); + } + public static LGAPIVerion discoveryAPIVersion(JsonNode rootNode) { Map mapper = objectMapper.convertValue(rootNode, new TypeReference<>() { }); @@ -56,10 +62,10 @@ public static LGAPIVerion discoveryAPIVersion(Map rootMap) { return LGAPIVerion.V1_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine AC API version."); + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } - case WASHING_MACHINE: + case WASHERDRYER_MACHINE: case DRYER: case REFRIGERATOR: if (rootMap.containsKey("Value")) { @@ -68,7 +74,7 @@ public static LGAPIVerion discoveryAPIVersion(Map rootMap) { return LGAPIVerion.V2_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine AC API version."); + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } case DISH_WASHER: return LGAPIVerion.V2_0; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java deleted file mode 100644 index da1212c16f879..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/Snapshot.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link Snapshot} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface Snapshot { - - public DevicePowerState getPowerStatus(); - - public void setPowerStatus(DevicePowerState value); - - public boolean isOnline(); - - public void setOnline(boolean online); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java index 9bf79c5a61b5f..c68b6e141fe6b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java @@ -12,7 +12,8 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.REFRIGERATOR_SNAPSHOT_NODE_V2; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; import java.beans.BeanInfo; import java.beans.IntrospectionException; @@ -25,10 +26,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; -import org.openhab.binding.lgthinq.lgservices.model.ac.ACSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.DryerSnapshot; -import org.openhab.binding.lgthinq.lgservices.model.washerdryer.WasherSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonProperty; @@ -61,15 +61,14 @@ public static SnapshotFactory getInstance() { * @return returns Snapshot implementation based on device type provided * @throws LGThinqApiException any error. */ - public S createFromBinary(String binaryData, List prot, + public S createFromBinary(String binaryData, List prot, Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { try { byte[] data = binaryData.getBytes(); BeanInfo beanInfo = Introspector.getBeanInfo(clazz); S snap = clazz.getConstructor().newInstance(); PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); - for (Iterator it = prot.iterator(); it.hasNext();) { - MonitoringBinaryProtocol protField = it.next(); + for (MonitoringBinaryProtocol protField : prot) { String fName = protField.fieldName; for (PropertyDescriptor property : pds) { // all attributes of class. @@ -118,8 +117,8 @@ public S createFromBinary(String binaryData, List S createFromJson(String snapshotDataJson, DeviceTypes deviceType, Class clazz) - throws LGThinqUnmarshallException, LGThinqApiException { + public S createFromJson(String snapshotDataJson, DeviceTypes deviceType, + Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { try { Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { }); @@ -132,7 +131,7 @@ public S createFromJson(String snapshotDataJson, DeviceType } } - public S createFromJson(Map deviceSettings, Class clazz) + public S createFromJson(Map deviceSettings, Class clazz) throws LGThinqApiException { DeviceTypes type = getDeviceType(deviceSettings); Map snapMap = ((Map) deviceSettings.get("snapshot")); @@ -140,21 +139,32 @@ public S createFromJson(Map deviceSettings, throw new LGThinqApiException("snapshot node not present in device monitoring result."); } LGAPIVerion version = discoveryAPIVersion(snapMap, type); + return getSnapshot(clazz, type, snapMap, version); + } + + private S getSnapshot(Class clazz, DeviceTypes type, + Map snapMap, LGAPIVerion version) { + S snap; switch (type) { case AIR_CONDITIONER: case HEAT_PUMP: - return clazz.cast(objectMapper.convertValue(snapMap, ACSnapshot.class)); + snap = clazz.cast(objectMapper.convertValue(snapMap, ACCanonicalSnapshot.class)); + snap.setRawData(snapMap); + return snap; case WASHING_TOWER: - case WASHING_MACHINE: + case WASHERDRYER_MACHINE: switch (version) { case V1_0: { - return clazz.cast(objectMapper.convertValue(snapMap, WasherSnapshot.class)); + snap = clazz.cast(objectMapper.convertValue(snapMap, WasherDryerSnapshot.class)); + snap.setRawData(snapMap); } case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), "washerDryer node must be present in the snapshot"); - return clazz.cast(objectMapper.convertValue(washerDryerMap, WasherSnapshot.class)); + snap = clazz.cast(objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class)); + snap.setRawData(washerDryerMap); + return snap; } } case DRYER_TOWER: @@ -164,10 +174,12 @@ public S createFromJson(Map deviceSettings, throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); } case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), + Map washerDryerMap = Objects.requireNonNull( + (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), "washerDryer node must be present in the snapshot"); - return clazz.cast(objectMapper.convertValue(washerDryerMap, DryerSnapshot.class)); + snap = clazz.cast(objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class)); + snap.setRawData(snapMap); + return snap; } } case REFRIGERATOR: @@ -176,10 +188,12 @@ public S createFromJson(Map deviceSettings, throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); } case V2_0: { - Map refMap = Objects.requireNonNull( - (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), + Map refMap = Objects.requireNonNull( + (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), "washerDryer node must be present in the snapshot"); - return clazz.cast(objectMapper.convertValue(refMap, FridgeSnapshotV2.class)); + snap = clazz.cast(objectMapper.convertValue(refMap, FridgeCanonicalSnapshot.class)); + snap.setRawData(snapMap); + return snap; } } default: @@ -205,27 +219,27 @@ private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes return LGAPIVerion.V1_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine AC API version."); + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } case DRYER_TOWER: case DRYER: return LGAPIVerion.V2_0; case WASHING_TOWER: - case WASHING_MACHINE: + case WASHERDRYER_MACHINE: if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { return LGAPIVerion.V2_0; } else if (snapMap.containsKey("State")) { return LGAPIVerion.V1_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine AC API version."); + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } case REFRIGERATOR: if (snapMap.containsKey(REFRIGERATOR_SNAPSHOT_NODE_V2)) { return LGAPIVerion.V2_0; } else { throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine AC API version."); + "Unexpected error. Can't find key node attributes to determine ACCapability API version."); } default: throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java deleted file mode 100644 index 3b4f13ddfdc6e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACCapability.java +++ /dev/null @@ -1,188 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; - -/** - * The {@link ACCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ACCapability extends AbstractCapability { - - private Map opMod = Collections.emptyMap(); - private Map fanSpeed = Collections.emptyMap(); - - private List supportedOpMode = Collections.emptyList(); - private List supportedFanSpeed = Collections.emptyList(); - private boolean isJetModeAvailable; - private boolean isAutoDryModeAvailable; - private boolean isEnergySavingAvailable; - private boolean isAirCleanAvailable; - - private boolean isFanSpeedAvailable; - private String coolJetModeCommandOn = ""; - private String coolJetModeCommandOff = ""; - - private String autoDryModeCommandOn = ""; - private String autoDryModeCommandOff = ""; - - private String energySavingModeCommandOn = ""; - private String energySavingModeCommandOff = ""; - - private String airCleanModeCommandOn = ""; - private String airCleanModeCommandOff = ""; - - public String getCoolJetModeCommandOff() { - return coolJetModeCommandOff; - } - - public void setCoolJetModeCommandOff(String coolJetModeCommandOff) { - this.coolJetModeCommandOff = coolJetModeCommandOff; - } - - public String getCoolJetModeCommandOn() { - return coolJetModeCommandOn; - } - - public void setCoolJetModeCommandOn(String coolJetModeCommandOn) { - this.coolJetModeCommandOn = coolJetModeCommandOn; - } - - public Map getOpMod() { - return opMod; - } - - public void setOpMod(Map opMod) { - this.opMod = opMod; - } - - public Map getFanSpeed() { - return fanSpeed; - } - - public void setFanSpeed(Map fanSpeed) { - this.fanSpeed = fanSpeed; - } - - public List getSupportedOpMode() { - return supportedOpMode; - } - - public void setSupportedOpMode(List supportedOpMode) { - this.supportedOpMode = supportedOpMode; - } - - public List getSupportedFanSpeed() { - return supportedFanSpeed; - } - - public void setSupportedFanSpeed(List supportedFanSpeed) { - this.supportedFanSpeed = supportedFanSpeed; - } - - public void setJetModeAvailable(boolean jetModeAvailable) { - this.isJetModeAvailable = jetModeAvailable; - } - - public boolean isAutoDryModeAvailable() { - return isAutoDryModeAvailable; - } - - public void setAutoDryModeAvailable(boolean autoDryModeAvailable) { - isAutoDryModeAvailable = autoDryModeAvailable; - } - - public boolean isEnergySavingAvailable() { - return isEnergySavingAvailable; - } - - public void setEnergySavingAvailable(boolean energySavingAvailable) { - isEnergySavingAvailable = energySavingAvailable; - } - - public boolean isFanSpeedAvailable() { - return isFanSpeedAvailable; - } - - public void setFanSpeedAvailable(boolean fanSpeedAvailable) { - isFanSpeedAvailable = fanSpeedAvailable; - } - - public boolean isAirCleanAvailable() { - return isAirCleanAvailable; - } - - public void setAirCleanAvailable(boolean airCleanAvailable) { - isAirCleanAvailable = airCleanAvailable; - } - - public boolean isJetModeAvailable() { - return this.isJetModeAvailable; - } - - public String getAutoDryModeCommandOn() { - return autoDryModeCommandOn; - } - - public void setAutoDryModeCommandOn(String autoDryModeCommandOn) { - this.autoDryModeCommandOn = autoDryModeCommandOn; - } - - public String getAutoDryModeCommandOff() { - return autoDryModeCommandOff; - } - - public void setAutoDryModeCommandOff(String autoDryModeCommandOff) { - this.autoDryModeCommandOff = autoDryModeCommandOff; - } - - public String getEnergySavingModeCommandOn() { - return energySavingModeCommandOn; - } - - public void setEnergySavingModeCommandOn(String energySavingModeCommandOn) { - this.energySavingModeCommandOn = energySavingModeCommandOn; - } - - public String getEnergySavingModeCommandOff() { - return energySavingModeCommandOff; - } - - public void setEnergySavingModeCommandOff(String energySavingModeCommandOff) { - this.energySavingModeCommandOff = energySavingModeCommandOff; - } - - public String getAirCleanModeCommandOn() { - return airCleanModeCommandOn; - } - - public void setAirCleanModeCommandOn(String airCleanModeCommandOn) { - this.airCleanModeCommandOn = airCleanModeCommandOn; - } - - public String getAirCleanModeCommandOff() { - return airCleanModeCommandOff; - } - - public void setAirCleanModeCommandOff(String airCleanModeCommandOff) { - this.airCleanModeCommandOff = airCleanModeCommandOff; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java deleted file mode 100644 index 3c16809f811e3..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACFanSpeed.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link ACSnapshot} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum ACFanSpeed { - F1(2.0), - F2(3.0), - F3(4.0), - F4(5.0), - F5(6.0), - F_AUTO(8.0), - F_UNK(-1); - - private final double funStrength; - - ACFanSpeed(double v) { - this.funStrength = v; - } - - public static ACFanSpeed statusOf(double value) { - switch ((int) value) { - case 2: - return F1; - case 3: - return F2; - case 4: - return F3; - case 5: - return F4; - case 6: - return F5; - case 8: - return F_AUTO; - default: - return F_UNK; - } - } - - /** - * "0": "@AC_MAIN_WIND_STRENGTH_SLOW_W", - * "1": "@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", - * "2": "@AC_MAIN_WIND_STRENGTH_LOW_W", - * "3": "@AC_MAIN_WIND_STRENGTH_LOW_MID_W", - * "4": "@AC_MAIN_WIND_STRENGTH_MID_W", - * "5": "@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", - * "6": "@AC_MAIN_WIND_STRENGTH_HIGH_W", - * "7": "@AC_MAIN_WIND_STRENGTH_POWER_W", - * "8": "@AC_MAIN_WIND_STRENGTH_NATURE_W", - */ - /** - * Value of command (not state, but command to change the state of device) - * - * @return value of the command to reach the state - */ - public int commandValue() { - switch (this) { - case F1: - return 2; - case F2: - return 3; - case F3: - return 4; - case F4: - return 5; - case F5: - return 6; - case F_AUTO: - return 8; - default: - throw new IllegalArgumentException("Enum not accepted for command:" + this); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java deleted file mode 100644 index 38a8dac94a4ca..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACOpMode.java +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link ACSnapshot} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum ACOpMode { - COOL(0), - DRY(1), - FAN(2), - AI(3), - HEAT(4), - AIRCLEAN(5), - ENSAV(8), - OP_UNK(-1); - - private final int opMode; - - ACOpMode(int v) { - this.opMode = v; - } - - public static ACOpMode statusOf(int value) { - switch ((int) value) { - case 0: - return COOL; - case 1: - return DRY; - case 2: - return FAN; - case 3: - return AI; - case 4: - return HEAT; - case 5: - return AIRCLEAN; - case 8: - return ENSAV; - default: - return OP_UNK; - } - } - - public int getValue() { - return this.opMode; - } - - /** - * Value of command (not state, but command to change the state of device) - * - * @return value of the command to reach the state - */ - public int commandValue() { - switch (this) { - case COOL: - return 0;// "@AC_MAIN_OPERATION_MODE_COOL_W" - case DRY: - return 1; // "@AC_MAIN_OPERATION_MODE_DRY_W" - case FAN: - return 2; // "@AC_MAIN_OPERATION_MODE_FAN_W" - case AI: - return 3; // "@AC_MAIN_OPERATION_MODE_AI_W" - case HEAT: - return 4; // "@AC_MAIN_OPERATION_MODE_HEAT_W" - case AIRCLEAN: - return 5; // "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W" - case ENSAV: - return 8; // "AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W" - default: - throw new IllegalArgumentException("Enum not accepted for command:" + this); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java deleted file mode 100644 index f86e0bb255269..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACSnapshot.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link ACSnapshot} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class ACSnapshot implements Snapshot { - - private int airWindStrength; - - private double targetTemperature; - - private double currentTemperature; - - private boolean coolJetModeOn; - - private double airCleanMode; - private double coolJetMode; - private double autoDryMode; - private double energySavingMode; - - private int operationMode; - @Nullable - private Integer operation; - @JsonIgnore - private boolean online; - - private double energyConsumption; - - @JsonIgnore - public DevicePowerState getPowerStatus() { - return operation == null ? DevicePowerState.DV_POWER_UNK : DevicePowerState.statusOf(operation); - } - - @JsonIgnore - public void setPowerStatus(DevicePowerState value) { - operation = (int) value.getValue(); - } - - @JsonIgnore - public ACFanSpeed getAcFanSpeed() { - return ACFanSpeed.statusOf(airWindStrength); - } - - @JsonProperty("airState.windStrength") - @JsonAlias("WindStrength") - public Integer getAirWindStrength() { - return airWindStrength; - } - - @JsonProperty("airState.wMode.jet") - @JsonAlias("Jet") - public Double getCoolJetMode() { - return coolJetMode; - } - - @JsonProperty("airState.wMode.airClean") - @JsonAlias("AirClean") - public Double getAirCleanMode() { - return airCleanMode; - } - - @JsonProperty("airState.miscFuncState.autoDry") - @JsonAlias("AutoDry") - public Double getAutoDryMode() { - return autoDryMode; - } - - @JsonProperty("airState.powerSave.basic") - @JsonAlias("PowerSave") - public Double getEnergySavingMode() { - return energySavingMode; - } - - public void setAirCleanMode(double airCleanMode) { - this.airCleanMode = airCleanMode; - } - - public void setAutoDryMode(double autoDryMode) { - this.autoDryMode = autoDryMode; - } - - public void setEnergySavingMode(double energySavingMode) { - this.energySavingMode = energySavingMode; - } - - public void setCoolJetMode(Double coolJetMode) { - this.coolJetMode = coolJetMode; - } - - public void setAirWindStrength(Integer airWindStrength) { - this.airWindStrength = airWindStrength; - } - - @JsonProperty("airState.energy.onCurrent") - public double getEnergyConsumption() { - return energyConsumption; - } - - public void setEnergyConsumption(double energyConsumption) { - this.energyConsumption = energyConsumption; - } - - @JsonProperty("airState.tempState.target") - @JsonAlias("TempCfg") - public Double getTargetTemperature() { - return targetTemperature; - } - - public void setTargetTemperature(Double targetTemperature) { - this.targetTemperature = targetTemperature; - } - - @JsonProperty("airState.tempState.current") - @JsonAlias("TempCur") - public Double getCurrentTemperature() { - return currentTemperature; - } - - public void setCurrentTemperature(Double currentTemperature) { - this.currentTemperature = currentTemperature; - } - - @JsonProperty("airState.opMode") - @JsonAlias("OpMode") - public Integer getOperationMode() { - return operationMode; - } - - public void setOperationMode(Integer operationMode) { - this.operationMode = operationMode; - } - - @Nullable - @JsonProperty("airState.operation") - @JsonAlias("Operation") - public Integer getOperation() { - return operation; - } - - public void setOperation(Integer operation) { - this.operation = operation; - } - - @JsonIgnore - public boolean isOnline() { - return online; - } - - public void setOnline(boolean online) { - this.online = online; - } - - @Override - public String toString() { - return "ACSnapShot{" + "airWindStrength=" + airWindStrength + ", targetTemperature=" + targetTemperature - + ", currentTemperature=" + currentTemperature + ", operationMode=" + operationMode + ", operation=" - + operation + ", acPowerStatus=" + getPowerStatus() + ", acFanSpeed=" + getAcFanSpeed() + ", acOpMode=" - + ", online=" + isOnline() + " }"; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java deleted file mode 100644 index 4b2fbc27bd6e5..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ac/ACTargetTmp.java +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.ac; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link ACTargetTmp} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum ACTargetTmp { - _17(17.0), - _18(18.0), - _19(19.0), - _20(20.0), - _21(21.0), - _22(22.0), - _23(23.0), - _24(24.0), - _25(25.0), - _26(26.0), - _27(27.0), - _28(28.0), - _29(29.0), - _30(30.0), - UNK(-1); - - private final double targetTmp; - - ACTargetTmp(double v) { - this.targetTmp = v; - } - - public static ACTargetTmp statusOf(double value) { - switch ((int) value) { - case 17: - return _17; - case 18: - return _18; - case 19: - return _19; - case 20: - return _20; - case 21: - return _21; - case 22: - return _22; - case 23: - return _23; - case 24: - return _24; - case 25: - return _25; - case 26: - return _26; - case 27: - return _27; - case 28: - return _28; - case 29: - return _29; - case 30: - return _30; - default: - return UNK; - } - } - - public double getValue() { - return this.targetTmp; - } - - /** - * Value of command (not state, but command to change the state of device) - * - * @return value of the command to reach the state - */ - public int commandValue() { - return (int) this.targetTmp; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java deleted file mode 100644 index 4ba7702f9b582..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeCapability.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; - -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Capability; - -/** - * The {@link FridgeCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface FridgeCapability extends Capability { - - public Map getFridgeTempCMap(); - - public Map getFridgeTempFMap(); - - public Map getFreezerTempCMap(); - - public Map getFreezerTempFMap(); - - public void loadCapabilities(Object veryRootNode); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java deleted file mode 100644 index c400c746be0c7..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; - -import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeCapabilityV2; -import org.openhab.binding.lgthinq.lgservices.model.fridge.v2.FridgeSnapshotV2; - -/** - * The {@link FridgeFactory} - * - * @author Nemer Daud - Initial contribution - */ -public class FridgeFactory { - - public static FridgeCapability getFridgeCapability(LGAPIVerion version) { - switch (version) { - case V1_0: - throw new IllegalArgumentException("V1_0 not supported by Fridge Thing yet"); - case V2_0: - return new FridgeCapabilityV2(); - default: - throw new IllegalArgumentException("Version " + version + " not expected"); - } - } - - public static FridgeSnapshot getFridgeSnapshot(LGAPIVerion version) { - switch (version) { - case V1_0: - throw new IllegalArgumentException("V1_0 not supported by Fridge Thing yet"); - case V2_0: - return new FridgeSnapshotV2(); - default: - throw new IllegalArgumentException("Version " + version + " not expected"); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java deleted file mode 100644 index b1d80faaf071e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/FridgeSnapshot.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -/** - * The {@link FridgeSnapshot} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface FridgeSnapshot extends Snapshot { - public String getTempUnit(); - - public String getFridgeStrTemp(); - - public String getFreezerStrTemp(); - - public void loadSnapshot(Object veryRootNode); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java deleted file mode 100644 index a32f887b017a8..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeCapabilityV2.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge.v2; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_CELSIUS_SYMBOL; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_FAHRENHEIT_SYMBOL; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * The {@link FridgeCapabilityV2} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class FridgeCapabilityV2 extends AbstractCapability - implements org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeCapability { - - private static final Logger logger = LoggerFactory.getLogger(FridgeCapabilityV2.class); - private static final ObjectMapper mapper = new ObjectMapper(); - - private final Map fridgeTempCMap = new LinkedHashMap(); - private final Map fridgeTempFMap = new LinkedHashMap(); - private final Map freezerTempCMap = new LinkedHashMap(); - private final Map freezerTempFMap = new LinkedHashMap(); - - public Map getFridgeTempCMap() { - return fridgeTempCMap; - } - - public Map getFridgeTempFMap() { - return fridgeTempFMap; - } - - public Map getFreezerTempCMap() { - return freezerTempCMap; - } - - public Map getFreezerTempFMap() { - return freezerTempFMap; - } - - @Override - public void loadCapabilities(Object veryRootNode) { - JsonNode node = mapper.valueToTree(veryRootNode); - if (node.isNull()) { - logger.error("Can't parse json capability for Fridge V2. The payload has been ignored"); - logger.debug("payload {}", veryRootNode); - return; - } - /** - * iterate over valueMappings like: - * "valueMapping": { - * "1": {"index" : 1, "label" : "7", "_comment" : ""}, - * "2": {"index" : 2, "label" : "6", "_comment" : ""}, - * "3": {"index" : 3, "label" : "5", "_comment" : ""}, - * "4": {"index" : 4, "label" : "4", "_comment" : ""}, - * "5": {"index" : 5, "label" : "3", "_comment" : ""}, - * "6": {"index" : 6, "label" : "2", "_comment" : ""}, - * "7": {"index" : 7, "label" : "1", "_comment" : ""}, - * "255" : {"index" : 255, "label" : "IGNORE", "_comment" : ""} - * } - */ - - JsonNode fridgeTempCNode = node.path("MonitoringValue").path("fridgeTemp_C").path("valueMapping"); - JsonNode fridgeTempFNode = node.path("MonitoringValue").path("fridgeTemp_F").path("valueMapping"); - JsonNode freezerTempCNode = node.path("MonitoringValue").path("freezerTemp_C").path("valueMapping"); - JsonNode freezerTempFNode = node.path("MonitoringValue").path("freezerTemp_F").path("valueMapping"); - loadTempNode(fridgeTempCNode, fridgeTempCMap, TEMP_UNIT_CELSIUS_SYMBOL); - loadTempNode(fridgeTempFNode, fridgeTempFMap, TEMP_UNIT_FAHRENHEIT_SYMBOL); - loadTempNode(freezerTempCNode, freezerTempCMap, TEMP_UNIT_CELSIUS_SYMBOL); - loadTempNode(freezerTempFNode, freezerTempFMap, TEMP_UNIT_FAHRENHEIT_SYMBOL); - } - - private void loadTempNode(JsonNode tempNode, Map capMap, String unit) { - tempNode.forEach(v -> { - // for each node like ' "1": {"index" : 1, "label" : "7", "_comment" : ""} ' - capMap.put(v.path("index").asText() + " " + unit, v.path("label").textValue() + " " + unit); - }); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java deleted file mode 100644 index 47e4acde699f9..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/fridge/v2/FridgeSnapshotV2.java +++ /dev/null @@ -1,114 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.fridge.v2; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link FridgeSnapshotV2} - * This map the snapshot result from Washing Machine devices - * This json payload come with path: snapshot->fridge, but this POJO expects - * to map field below washerDryer - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class FridgeSnapshotV2 implements Snapshot, org.openhab.binding.lgthinq.lgservices.model.fridge.FridgeSnapshot { - - private boolean online; - private Double fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; - private Double freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; - private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default - - @Override - @JsonAlias({ "TempUnit" }) - @JsonProperty("tempUnit") - public String getTempUnit() { - return tempUnit; - } - - private String getStrTempWithUnit(Double temp) { - return temp.intValue() + (TEMP_UNIT_CELSIUS.equals(tempUnit) ? " " + TEMP_UNIT_CELSIUS_SYMBOL - : (TEMP_UNIT_FAHRENHEIT).equals(tempUnit) ? " " + TEMP_UNIT_FAHRENHEIT_SYMBOL : ""); - } - - @Override - @JsonIgnore - public String getFridgeStrTemp() { - return getStrTempWithUnit(getFridgeTemp()); - } - - @Override - @JsonIgnore - public String getFreezerStrTemp() { - return getStrTempWithUnit(getFreezerTemp()); - } - - public void setTempUnit(String tempUnit) { - this.tempUnit = tempUnit; - } - - @JsonAlias({ "TempRefrigerator" }) - @JsonProperty("fridgeTemp") - public Double getFridgeTemp() { - return fridgeTemp; - } - - public void setFridgeTemp(Double fridgeTemp) { - this.fridgeTemp = fridgeTemp; - } - - @JsonAlias({ "TempFreezer" }) - @JsonProperty("freezerTemp") - public Double getFreezerTemp() { - return freezerTemp; - } - - public void setFreezerTemp(Double freezerTemp) { - this.freezerTemp = freezerTemp; - } - - @Override - public void loadSnapshot(Object veryRootNode) { - } - - @Override - public DevicePowerState getPowerStatus() { - throw new IllegalStateException("Fridge has no Power state."); - } - - @Override - public void setPowerStatus(DevicePowerState value) { - throw new IllegalStateException("Fridge has no Power state."); - } - - @Override - public boolean isOnline() { - return online; - } - - @Override - public void setOnline(boolean online) { - this.online = online; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java deleted file mode 100644 index ac615e7c2b034..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/CommandCapability.java +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.openhab.binding.lgthinq.lgservices.model.ModelUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; - -/** - * The {@link CommandCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class CommandCapability { - private boolean isPowerCommandsAvailable = false; - private String powerOffCommand = ""; - private String stopCommand = ""; - private String wakeUpCommand = ""; - private static final Logger logger = LoggerFactory.getLogger(CommandCapability.class); - - public void loadCommands(JsonNode rootNode) throws LGThinqApiException { - LGAPIVerion version = ModelUtils.discoveryAPIVersion(rootNode); - switch (version) { - case V1_0: - logger.warn("Version {} for commands of Dryer/Washers not supported for this binding.", - version.getValue()); - return; - case V2_0: - JsonNode wifiNode = rootNode.path("ControlWifi"); - if (wifiNode.isMissingNode()) { - logger.warn( - "Dryer/Washer is missing ControlWifi node in the model. Commands are not supported for this model."); - return; - } - JsonNode wmOffNode = wifiNode.path("WMOff"); - JsonNode wmStopNode = wifiNode.path("WMStop"); - JsonNode wmWakeUpNode = wifiNode.path("WMWakeup"); - boolean isOffPresent = !wmOffNode.isMissingNode(); - boolean isStopPresent = !wmStopNode.isMissingNode(); - boolean isWakeUpPresent = !wmWakeUpNode.isMissingNode(); - if (isOffPresent || isStopPresent || isWakeUpPresent) { - isPowerCommandsAvailable = true; - powerOffCommand = isOffPresent - ? wmOffNode.path("data").path("washerDryer").path("controlDataType").textValue() - : ""; - stopCommand = isStopPresent - ? wmStopNode.path("data").path("washerDryer").path("controlDataType").textValue() - : ""; - wakeUpCommand = isWakeUpPresent - ? wmWakeUpNode.path("data").path("washerDryer").path("controlDataType").textValue() - : ""; - } - } - } - - public boolean isPowerCommandsAvailable() { - return isPowerCommandsAvailable; - } - - public void setPowerCommandsAvailable(boolean powerCommandsAvailable) { - isPowerCommandsAvailable = powerCommandsAvailable; - } - - public String getPowerOffCommand() { - return powerOffCommand; - } - - public String getStopCommand() { - return stopCommand; - } - - public String getWakeUpCommand() { - return wakeUpCommand; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java deleted file mode 100644 index d29b07e1f0124..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerCapability.java +++ /dev/null @@ -1,129 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; - -/** - * The {@link DryerCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class DryerCapability extends AbstractCapability { - public enum MonitoringCap { - STATE_V2("state"), - PROCESS_STATE_V2("processState"), - DRY_LEVEL_V2("dryLevel"), - ERROR_V2("error"), - STATE_V1("State"), - PROCESS_STATE_V1("PreState"), - ERROR_V1("Error"); - - final String value; - - MonitoringCap(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - } - - private static class MonitoringValue { - private final Map state = new LinkedHashMap(); - private final Map dryLevel = new LinkedHashMap(); - private final Map error = new LinkedHashMap(); - private final Map processState = new LinkedHashMap(); - private boolean hasChildLock; - private boolean hasRemoteStart; - } - - private final MonitoringValue monitoringValue = new MonitoringValue(); - private final Map courses = new LinkedHashMap(); - - private final Map smartCourses = new LinkedHashMap(); - - public Map getCourses() { - return courses; - } - - public Map getSmartCourses() { - return smartCourses; - } - - public void addCourse(String courseLabel, String courseName) { - courses.put(courseLabel, courseName); - } - - public void addSmartCourse(String courseLabel, String courseName) { - smartCourses.put(courseLabel, courseName); - } - - public Map getState() { - return monitoringValue.state; - } - - public Map getDryLevels() { - return monitoringValue.dryLevel; - } - - public Map getErrors() { - return monitoringValue.error; - } - - public Map getProcessStates() { - return monitoringValue.processState; - } - - public boolean hasRemoteStart() { - return monitoringValue.hasRemoteStart; - } - - public boolean hasChildLock() { - return monitoringValue.hasChildLock; - } - - public void setChildLock(boolean hasChildLock) { - monitoringValue.hasChildLock = hasChildLock; - } - - public void setRemoteStart(boolean hasRemoteStart) { - monitoringValue.hasRemoteStart = hasRemoteStart; - } - - public void addMonitoringValue(MonitoringCap monCap, String key, String value) { - switch (monCap) { - case STATE_V2: - case STATE_V1: - monitoringValue.state.put(key, value); - break; - case PROCESS_STATE_V2: - case PROCESS_STATE_V1: - monitoringValue.processState.put(key, value); - break; - case DRY_LEVEL_V2: - monitoringValue.dryLevel.put(key, value); - break; - case ERROR_V1: - case ERROR_V2: - monitoringValue.error.put(key, value); - break; - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java deleted file mode 100644 index 812340b2fec51..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/DryerSnapshot.java +++ /dev/null @@ -1,203 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link DryerSnapshot} - * This map the snapshot result from Washing Machine devices - * This json payload come with path: snapshot->washerDryer, but this POJO expects - * to map field below washerDryer - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class DryerSnapshot implements Snapshot { - private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; - private String state = ""; - private boolean online; - private String course = ""; - private String smartCourse = ""; - private String childLock = ""; - private String processState = ""; - private Double remainingHour = 0.00; - private Double remainingMinute = 0.00; - private String dryLevel = ""; - private String error = ""; - private String remoteStart = ""; - private boolean remoteStartEnabled = false; - - private String standByStatus = ""; - - private boolean standBy = false; - - @JsonAlias({ "Course", "courseDryer24inchBase", "courseDryer27inchBase" }) - @JsonProperty("courseDryer24inchBase") - public String getCourse() { - return course; - } - - public void setCourse(String course) { - this.course = course; - } - - @JsonProperty("dryLevel") - @JsonAlias({ "DryLevel" }) - public String getDryLevel() { - return dryLevel; - } - - public void setDryLevel(String dryLevel) { - this.dryLevel = dryLevel; - } - - public void setRemainingMinute(Double remainingMinute) { - this.remainingMinute = remainingMinute; - } - - @JsonProperty("error") - @JsonAlias({ "Error" }) - public String getError() { - return error; - } - - public void setError(String error) { - this.error = error; - } - - @JsonProperty("processState") - @JsonAlias({ "ProcessState" }) - public String getProcessState() { - return processState; - } - - public void setProcessState(String processState) { - this.processState = processState; - } - - @Override - public DevicePowerState getPowerStatus() { - return powerState; - } - - @Override - public void setPowerStatus(DevicePowerState value) { - throw new IllegalArgumentException("This method must not be accessed."); - } - - @Override - public boolean isOnline() { - return online; - } - - @Override - public void setOnline(boolean online) { - this.online = online; - } - - @JsonProperty("state") - @JsonAlias({ "state", "State" }) - public String getState() { - return state; - } - - @JsonProperty("smartCourseDryer24inchBase") - @JsonAlias({ "smartCourseDryer24inchBase", "SmartCourse", "smartCourseDryer27inchBase" }) - public String getSmartCourse() { - return smartCourse; - } - - public void setSmartCourse(String smartCourse) { - this.smartCourse = smartCourse; - } - - @JsonProperty("childLock") - public String getChildLock() { - return childLock; - } - - public void setChildLock(String childLock) { - this.childLock = childLock; - } - - @JsonIgnore - public String getRemainingTime() { - return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); - } - - @JsonProperty("remainTimeHour") - @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) - public Double getRemainingHour() { - return remainingHour; - } - - public void setRemainingHour(Double remainingHour) { - this.remainingHour = remainingHour; - } - - @JsonProperty("remainTimeMinute") - @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) - public Double getRemainingMinute() { - return remainingMinute; - } - - public void setState(String state) { - this.state = state; - if (state.equals(WM_POWER_OFF_VALUE)) { - powerState = DevicePowerState.DV_POWER_OFF; - } else { - powerState = DevicePowerState.DV_POWER_ON; - } - } - - public boolean isRemoteStartEnabled() { - return remoteStartEnabled; - } - - @JsonProperty("remoteStart") - @JsonAlias({ "RemoteStart" }) - public String getRemoteStart() { - return remoteStart; - } - - public void setRemoteStart(String remoteStart) { - this.remoteStart = remoteStart; - remoteStartEnabled = remoteStart.contains("ON"); - } - - public String getStandByStatus() { - return standByStatus; - } - - @JsonProperty("standby") - @JsonAlias({ "Standby" }) - public void setStandByStatus(String standByStatus) { - this.standByStatus = standByStatus; - standBy = standByStatus.contains("ON"); - } - - public boolean isStandBy() { - return standBy; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java deleted file mode 100644 index 05535d455c3ea..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherCapability.java +++ /dev/null @@ -1,153 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import java.util.LinkedHashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; - -/** - * The {@link WasherCapability} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class WasherCapability extends AbstractCapability { - public enum MonitoringCap { - STATE_V2("state"), - SOIL_WASH_V2("soilWash"), - SPIN_V2("spin"), - TEMPERATURE_V2("temp"), - RINSE_V2("rinse"), - ERROR_V2("error"), - STATE_V1("State"), - SOIL_WASH_V1("Wash"), - SPIN_V1("SpinSpeed"), - TEMPERATURE_V1("WaterTemp"), - RINSE_V1("RinseOption"), - ERROR_V1("Error"); - - final String value; - - MonitoringCap(String value) { - this.value = value; - } - - public String getValue() { - return value; - } - } - - private static class MonitoringValue { - private Map state = new LinkedHashMap(); - private Map soilWash = new LinkedHashMap(); - private Map spin = new LinkedHashMap(); - private Map temperature = new LinkedHashMap(); - private Map rinse = new LinkedHashMap(); - private Map error = new LinkedHashMap(); - private boolean hasDoorLook; - private boolean hasTurboWash; - } - - private MonitoringValue monitoringValue = new MonitoringValue(); - private Map courses = new LinkedHashMap(); - - private Map smartCourses = new LinkedHashMap(); - - public Map getCourses() { - return courses; - } - - public Map getSmartCourses() { - return smartCourses; - } - - public void addCourse(String courseLabel, String courseName) { - courses.put(courseLabel, courseName); - } - - public void addSmartCourse(String courseLabel, String courseName) { - smartCourses.put(courseLabel, courseName); - } - - public Map getState() { - return monitoringValue.state; - } - - public Map getSoilWash() { - return monitoringValue.soilWash; - } - - public Map getSpin() { - return monitoringValue.spin; - } - - public Map getTemperature() { - return monitoringValue.temperature; - } - - public Map getRinse() { - return monitoringValue.rinse; - } - - public Map getError() { - return monitoringValue.error; - } - - public boolean hasDoorLook() { - return monitoringValue.hasDoorLook; - } - - public void setHasDoorLook(boolean hasDoorLook) { - monitoringValue.hasDoorLook = hasDoorLook; - } - - public boolean hasTurboWash() { - return monitoringValue.hasTurboWash; - } - - public void setHasTurboWash(boolean hasTurboWash) { - monitoringValue.hasTurboWash = hasTurboWash; - } - - public void addMonitoringValue(MonitoringCap monCap, String key, String value) { - switch (monCap) { - case STATE_V1: - case STATE_V2: - monitoringValue.state.put(key, value); - break; - case SOIL_WASH_V2: - case SOIL_WASH_V1: - monitoringValue.soilWash.put(key, value); - break; - case SPIN_V2: - case SPIN_V1: - monitoringValue.spin.put(key, value); - break; - case TEMPERATURE_V2: - case TEMPERATURE_V1: - monitoringValue.temperature.put(key, value); - break; - case RINSE_V2: - case RINSE_V1: - monitoringValue.rinse.put(key, value); - break; - case ERROR_V2: - case ERROR_V1: - monitoringValue.error.put(key, value); - break; - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java deleted file mode 100644 index 004474dbf76fd..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/washerdryer/WasherSnapshot.java +++ /dev/null @@ -1,221 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.washerdryer; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.Snapshot; - -import com.fasterxml.jackson.annotation.JsonAlias; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The {@link WasherSnapshot} - * This map the snapshot result from Washing Machine devices - * This json payload come with path: snapshot->washerDryer, but this POJO expects - * to map field below washerDryer - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@JsonIgnoreProperties(ignoreUnknown = true) -public class WasherSnapshot implements Snapshot { - private DevicePowerState powerState = DevicePowerState.DV_POWER_UNK; - private String state = ""; - private boolean online; - private String course = ""; - private String smartCourse = ""; - private String downloadedCourse = ""; - private String temperatureLevel = ""; - private String doorLock = ""; - private Double remainingHour = 0.00; - private Double remainingMinute = 0.00; - private Double reserveHour = 0.00; - private Double reserveMinute = 0.00; - - private String remoteStart = ""; - private boolean remoteStartEnabled = false; - - private String standByStatus = ""; - - private boolean standBy = false; - - @JsonAlias({ "Course", "courseFL24inchBaseTitan" }) - @JsonProperty("courseFL24inchBaseTitan") - public String getCourse() { - return course; - } - - public void setCourse(String course) { - this.course = course; - } - - @Override - public DevicePowerState getPowerStatus() { - return powerState; - } - - @Override - public void setPowerStatus(DevicePowerState value) { - throw new IllegalArgumentException("This method must not be accessed."); - } - - @Override - public boolean isOnline() { - return online; - } - - @Override - public void setOnline(boolean online) { - this.online = online; - } - - @JsonProperty("state") - @JsonAlias({ "state", "State" }) - public String getState() { - return state; - } - - @JsonProperty("smartCourseFL24inchBaseTitan") - @JsonAlias({ "smartCourseFL24inchBaseTitan", "SmartCourse" }) - public String getSmartCourse() { - return smartCourse; - } - - @JsonProperty("downloadedCourseFL24inchBaseTitan") - @JsonAlias({ "downloadedCourseFLUpper25inchBaseUS" }) - public String getDownloadedCourse() { - return downloadedCourse; - } - - public void setDownloadedCourse(String downloadedCourse) { - this.downloadedCourse = downloadedCourse; - } - - @JsonIgnore - public String getRemainingTime() { - return String.format("%02.0f:%02.0f", getRemainingHour(), getRemainingMinute()); - } - - @JsonIgnore - public String getReserveTime() { - return String.format("%02.0f:%02.0f", getReserveHour(), getReserveMinute()); - } - - @JsonProperty("remainTimeHour") - @JsonAlias({ "remainTimeHour", "Remain_Time_H" }) - public Double getRemainingHour() { - return remainingHour; - } - - public void setRemainingHour(Double remainingHour) { - this.remainingHour = remainingHour; - } - - @JsonProperty("remainTimeMinute") - @JsonAlias({ "remainTimeMinute", "Remain_Time_M" }) - public Double getRemainingMinute() { - return remainingMinute; - } - - public void setRemainingMinute(Double remainingMinute) { - this.remainingMinute = remainingMinute; - } - - @JsonProperty("reserveTimeHour") - @JsonAlias({ "reserveTimeHour", "Reserve_Time_H" }) - public Double getReserveHour() { - return reserveHour; - } - - public void setReserveHour(Double reserveHour) { - this.reserveHour = reserveHour; - } - - @JsonProperty("reserveTimeMinute") - @JsonAlias({ "reserveTimeMinute", "Reserve_Time_M" }) - public Double getReserveMinute() { - return reserveMinute; - } - - public void setReserveMinute(Double reserveMinute) { - this.reserveMinute = reserveMinute; - } - - public void setSmartCourse(String smartCourse) { - this.smartCourse = smartCourse; - } - - @JsonProperty("temp") - @JsonAlias({ "WaterTemp" }) - public String getTemperatureLevel() { - return temperatureLevel; - } - - public void setTemperatureLevel(String temperatureLevel) { - this.temperatureLevel = temperatureLevel; - } - - @JsonProperty("doorLock") - @JsonAlias({ "ChildLock" }) - public String getDoorLock() { - return doorLock; - } - - public void setDoorLock(String doorLock) { - this.doorLock = doorLock; - } - - public void setState(String state) { - this.state = state; - if (state.equals(WM_POWER_OFF_VALUE)) { - powerState = DevicePowerState.DV_POWER_OFF; - } else { - powerState = DevicePowerState.DV_POWER_ON; - } - } - - public boolean isRemoteStartEnabled() { - return remoteStartEnabled; - } - - @JsonProperty("remoteStart") - @JsonAlias({ "RemoteStart" }) - public String getRemoteStart() { - return remoteStart; - } - - public void setRemoteStart(String remoteStart) { - this.remoteStart = remoteStart; - remoteStartEnabled = remoteStart.contains("ON"); - } - - public String getStandByStatus() { - return standByStatus; - } - - @JsonProperty("standby") - @JsonAlias({ "Standby" }) - public void setStandByStatus(String standByStatus) { - this.standByStatus = standByStatus; - standBy = standByStatus.contains("ON"); - } - - public boolean isStandBy() { - return standBy; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml index 0ed1ca753e4d0..8d79b03e4c5de 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml @@ -17,12 +17,13 @@ - - + + + @@ -38,7 +39,6 @@ - From 357d6ce0f48ff6010312c06e02fc02ac43775cd3 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Thu, 16 May 2024 15:50:16 -0300 Subject: [PATCH 096/130] [lgthinq][fix] Removing unused classes and methods Signed-off-by: nemerdaud --- .../internal/LGThinQBindingConstants.java | 1 - ...hinQDeviceDynStateDescriptionProvider.java | 40 --- .../internal/ThinqChannelTypeProvider.java | 68 ----- .../lgthinq/internal/ThinqHandler.java | 61 ----- .../handler/LGThinQAirConditionerHandler.java | 4 - .../lgservices/LGThinQApiClientService.java | 3 - ...apability.java => AbstractCapability.java} | 0 .../lgservices/model/SnapshotFactory.java | 248 ------------------ .../washerdryer/CommandDefinition.java | 51 ---- .../devices/washerdryer/CourseDefinition.java | 64 ----- .../devices/washerdryer/CourseFunction.java | 63 ----- .../model/devices/washerdryer/CourseType.java | 39 --- .../WasherDryerPropertyDiscovery.java | 22 -- .../main/resources/OH-INF/thing/channels.xml | 88 ------- .../main/resources/OH-INF/thing/washer.xml | 51 ---- 15 files changed, 803 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqChannelTypeProvider.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqHandler.java rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/{AbstractJsonCapability.java => AbstractCapability.java} (100%) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotFactory.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CommandDefinition.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerPropertyDiscovery.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 6c99842a54a2f..7e204e69bfb41 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -260,7 +260,6 @@ public class LGThinQBindingConstants { public static final String WM_CHANNEL_COURSE_ID = "course"; public static final String DR_CHANNEL_DRY_LEVEL_ID = "dry-level"; public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; - public static final String WM_CHANNEL_DOWNLOADED_COURSE_ID = "downloaded-course"; public static final String WM_CHANNEL_TEMP_LEVEL_ID = "temperature-level"; public static final String WM_CHANNEL_DOOR_LOCK_ID = "door-lock"; public static final String DR_CHANNEL_CHILD_LOCK_ID = "child-lock"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java deleted file mode 100644 index 40c1f85c41332..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQDeviceDynStateDescriptionProvider.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import org.openhab.binding.lgthinq.internal.handler.LGThinQAbstractDeviceHandler; -import org.openhab.core.events.EventPublisher; -import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; -import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; -import org.openhab.core.thing.link.ItemChannelLinkRegistry; -import org.openhab.core.thing.type.DynamicStateDescriptionProvider; -import org.osgi.service.component.annotations.Activate; -import org.osgi.service.component.annotations.Component; -import org.osgi.service.component.annotations.Reference; - -/** - * The {@link LGThinQAbstractDeviceHandler} is a main interface contract for all LG Thinq things - * - * @author Nemer Daud - Initial contribution - */ -@Component(service = { DynamicStateDescriptionProvider.class, LGThinQDeviceDynStateDescriptionProvider.class }) -public class LGThinQDeviceDynStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { - @Activate - public LGThinQDeviceDynStateDescriptionProvider(final @Reference EventPublisher eventPublisher, // - final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, // - final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) { - this.eventPublisher = eventPublisher; - this.itemChannelLinkRegistry = itemChannelLinkRegistry; - this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqChannelTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqChannelTypeProvider.java deleted file mode 100644 index e9326772118b4..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqChannelTypeProvider.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import java.util.*; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.handler.LGThinQAbstractDeviceHandler; -import org.openhab.core.thing.binding.ThingHandler; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.thing.type.ChannelType; -import org.openhab.core.thing.type.ChannelTypeProvider; -import org.openhab.core.thing.type.ChannelTypeUID; - -/** - * Provider class to provide channel types for user configured channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ThinqChannelTypeProvider implements ChannelTypeProvider, ThingHandlerService { - - private final Map map = new HashMap<>(); - private @Nullable ThingHandler handler; - - @Override - public Collection getChannelTypes(@Nullable final Locale locale) { - return Collections.unmodifiableCollection(map.values()); - } - - @Override - public @Nullable ChannelType getChannelType(final ChannelTypeUID channelTypeUID, @Nullable final Locale locale) { - return map.get(channelTypeUID); - } - - /** - * Add a channel type for a user configured channel. - * - * @param channelType channelType - */ - public void addChannelType(final ChannelType channelType) { - map.put(channelType.getUID(), channelType); - } - - @Override - public void setThingHandler(ThingHandler handler) { - if (handler instanceof LGThinQAbstractDeviceHandler) { - this.handler = handler; - ((LGThinQAbstractDeviceHandler) handler).setChannelTypeProvider(this); - } - } - - @Override - public @Nullable ThingHandler getThingHandler() { - return handler; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqHandler.java deleted file mode 100644 index 0829e7a825a29..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/ThinqHandler.java +++ /dev/null @@ -1,61 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.internal; - -import java.util.Collection; -import java.util.Collections; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.binding.BaseThingHandler; -import org.openhab.core.thing.binding.ThingHandlerService; -import org.openhab.core.types.Command; - -/** - * The {@link ThinqHandler} is responsible for handling commands, which are - * sent to one of the channels. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ThinqHandler extends BaseThingHandler { - private @Nullable ThinqChannelTypeProvider channelTypeProvider; - - /** - * Creates a new instance of this class for the {@link Thing}. - * - * @param thing the thing that should be handled, not null - */ - public ThinqHandler(Thing thing) { - super(thing); - } - - @Override - public void initialize() { - } - - @Override - public void handleCommand(ChannelUID channelUID, Command command) { - } - - @Override - public Collection> getServices() { - return Collections.singleton(ThinqChannelTypeProvider.class); - } - - public void setChannelTypeProvider(ThinqChannelTypeProvider thinqChannelTypeProvider) { - this.channelTypeProvider = thinqChannelTypeProvider; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index e3d6b31b928e0..2e189839122ad 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -249,10 +249,6 @@ protected DeviceTypes getDeviceType() { } } - protected DeviceTypes getDeviceType() { - return DeviceTypes.AIR_CONDITIONER; - } - @Override public void onDeviceAdded(LGDevice device) { // TODO - handle it. Think if it's needed diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index 0404d7d141a0d..b23f4902a5456 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -49,9 +49,6 @@ public interface LGThinQApiClientService S createFromBinary(String binaryData, List prot, - Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { - try { - byte[] data = binaryData.getBytes(); - BeanInfo beanInfo = Introspector.getBeanInfo(clazz); - S snap = clazz.getConstructor().newInstance(); - PropertyDescriptor[] pds = beanInfo.getPropertyDescriptors(); - for (MonitoringBinaryProtocol protField : prot) { - String fName = protField.fieldName; - for (PropertyDescriptor property : pds) { - // all attributes of class. - Method m = property.getReadMethod(); // getter - List aliases = new ArrayList<>(); - if (m.isAnnotationPresent(JsonProperty.class)) { - aliases.add(m.getAnnotation(JsonProperty.class).value()); - } - if (m.isAnnotationPresent(JsonAlias.class)) { - aliases.addAll(Arrays.asList(m.getAnnotation(JsonAlias.class).value())); - } - - if (aliases.contains(fName)) { - // found property. Get bit value - int value = 0; - for (int i = protField.startByte; i < protField.startByte + protField.length; i++) { - value = (value << 8) + data[i]; - } - m = property.getWriteMethod(); - if (m.getParameters()[0].getType() == String.class) { - m.invoke(snap, String.valueOf(value)); - } else if (m.getParameters()[0].getType() == Double.class) { - m.invoke(snap, (double) value); - } else if (m.getParameters()[0].getType() == Integer.class) { - m.invoke(snap, value); - } else { - throw new IllegalArgumentException( - String.format("Parameter type not supported for this factory:%s", - m.getParameters()[0].getType().toString())); - } - } - } - } - return snap; - } catch (IntrospectionException | InvocationTargetException | InstantiationException | IllegalAccessException - | NoSuchMethodException e) { - throw new LGThinqUnmarshallException("Unexpected Error unmarshalling binary data", e); - } - } - - /** - * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) - * - * @param snapshotDataJson V1: decoded returnedData; V2: snapshot body - * @param deviceType device type - * @return returns Snapshot implementation based on device type provided - * @throws LGThinqApiException any error. - */ - public S createFromJson(String snapshotDataJson, DeviceTypes deviceType, - Class clazz) throws LGThinqUnmarshallException, LGThinqApiException { - try { - Map snapshotMap = objectMapper.readValue(snapshotDataJson, new TypeReference<>() { - }); - Map deviceSetting = new HashMap<>(); - deviceSetting.put("deviceType", deviceType.deviceTypeId()); - deviceSetting.put("snapshot", snapshotMap); - return createFromJson(deviceSetting, clazz); - } catch (JsonProcessingException e) { - throw new LGThinqUnmarshallException("Unexpected Error unmarshalling json to map", e); - } - } - - public S createFromJson(Map deviceSettings, Class clazz) - throws LGThinqApiException { - DeviceTypes type = getDeviceType(deviceSettings); - Map snapMap = ((Map) deviceSettings.get("snapshot")); - if (snapMap == null) { - throw new LGThinqApiException("snapshot node not present in device monitoring result."); - } - LGAPIVerion version = discoveryAPIVersion(snapMap, type); - return getSnapshot(clazz, type, snapMap, version); - } - - private S getSnapshot(Class clazz, DeviceTypes type, - Map snapMap, LGAPIVerion version) { - S snap; - switch (type) { - case AIR_CONDITIONER: - case HEAT_PUMP: - snap = clazz.cast(objectMapper.convertValue(snapMap, ACCanonicalSnapshot.class)); - snap.setRawData(snapMap); - return snap; - case WASHING_TOWER: - case WASHERDRYER_MACHINE: - switch (version) { - case V1_0: { - snap = clazz.cast(objectMapper.convertValue(snapMap, WasherDryerSnapshot.class)); - snap.setRawData(snapMap); - } - case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = clazz.cast(objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class)); - snap.setRawData(washerDryerMap); - return snap; - } - } - case DRYER_TOWER: - case DRYER: - switch (version) { - case V1_0: { - throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); - } - case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = clazz.cast(objectMapper.convertValue(washerDryerMap, WasherDryerSnapshot.class)); - snap.setRawData(snapMap); - return snap; - } - } - case REFRIGERATOR: - switch (version) { - case V1_0: { - throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); - } - case V2_0: { - Map refMap = Objects.requireNonNull( - (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = clazz.cast(objectMapper.convertValue(refMap, FridgeCanonicalSnapshot.class)); - snap.setRawData(snapMap); - return snap; - } - } - default: - throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); - } - } - - private DeviceTypes getDeviceType(Map rootMap) { - Integer deviceTypeId = (Integer) rootMap.get("deviceType"); - // device code is only present in v2 devices snapshot. - String deviceCode = Objects.requireNonNullElse((String) rootMap.get("deviceCode"), ""); - Objects.requireNonNull(deviceTypeId, "Unexpected error. deviceType field not present in snapshot schema"); - return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); - } - - private LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { - switch (type) { - case AIR_CONDITIONER: - case HEAT_PUMP: - if (snapMap.containsKey("airState.opMode")) { - return LGAPIVerion.V2_0; - } else if (snapMap.containsKey("OpMode")) { - return LGAPIVerion.V1_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); - } - case DRYER_TOWER: - case DRYER: - return LGAPIVerion.V2_0; - case WASHING_TOWER: - case WASHERDRYER_MACHINE: - if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { - return LGAPIVerion.V2_0; - } else if (snapMap.containsKey("State")) { - return LGAPIVerion.V1_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); - } - case REFRIGERATOR: - if (snapMap.containsKey(REFRIGERATOR_SNAPSHOT_NODE_V2)) { - return LGAPIVerion.V2_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); - } - default: - throw new IllegalStateException("Unexpected capability. The type " + type + " was not implemented yet"); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CommandDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CommandDefinition.java deleted file mode 100644 index 63e8f0979a2ae..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CommandDefinition.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.devices.washerdryer; - -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link CommandDefinition} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class CommandDefinition { - private static final Logger logger = LoggerFactory.getLogger(CommandDefinition.class); - /** - * This is the command tag value that is used by the API to launch the command service - */ - private String command = ""; - private Map data = new HashMap<>(); - - public String getCommand() { - return command; - } - - public void setCommand(String command) { - this.command = command; - } - - public Map getData() { - return data; - } - - public void setData(Map data) { - this.data = data; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java deleted file mode 100644 index b50cee3a376d0..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseDefinition.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.devices.washerdryer; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link CourseDefinition} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class CourseDefinition { - private String courseName = ""; - // Name of the course this is based on. It's only used for SmartCourses - private String baseCourseName = ""; - private CourseType courseType = CourseType.UNDEF; - private List functions = new ArrayList<>(); - - public String getCourseName() { - return courseName; - } - - public String getBaseCourseName() { - return baseCourseName; - } - - public void setBaseCourseName(String baseCourseName) { - this.baseCourseName = baseCourseName; - } - - public void setCourseName(String courseName) { - this.courseName = courseName; - } - - public CourseType getCourseType() { - return courseType; - } - - public void setCourseType(CourseType courseType) { - this.courseType = courseType; - } - - public List getFunctions() { - return functions; - } - - public void setFunctions(List functions) { - this.functions = functions; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java deleted file mode 100644 index cf9428a5a9e7c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseFunction.java +++ /dev/null @@ -1,63 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.devices.washerdryer; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link CourseFunction} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class CourseFunction { - private String value = ""; - private String defaultValue = ""; - private boolean isSelectable; - private List selectableValues = new ArrayList<>(); - - public String getValue() { - return value; - } - - public void setValue(String value) { - this.value = value; - } - - public String getDefaultValue() { - return defaultValue; - } - - public void setDefaultValue(String defaultValue) { - this.defaultValue = defaultValue; - } - - public boolean isSelectable() { - return isSelectable; - } - - public void setSelectable(boolean selectable) { - isSelectable = selectable; - } - - public List getSelectableValues() { - return selectableValues; - } - - public void setSelectableValues(List selectableValues) { - this.selectableValues = selectableValues; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java deleted file mode 100644 index abaafba5793de..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/CourseType.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.devices.washerdryer; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link CourseType} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public enum CourseType { - // TODO - review DownloadCourse value, in remote start debugging - COURSE("Course"), - SMART_COURSE("SmartCourse"), - DOWNLOADED_COURSE("DownloadedCourse"), - UNDEF("Undefined"); - - private final String value; - - CourseType(String s) { - value = s; - } - - public String getValue() { - return value; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerPropertyDiscovery.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerPropertyDiscovery.java deleted file mode 100644 index 2ecb7dc420af0..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerPropertyDiscovery.java +++ /dev/null @@ -1,22 +0,0 @@ -/** - * Copyright (c) 2010-2022 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.lgthinq.lgservices.model.devices.washerdryer; - -/** - * The {@link WasherDryerPropertyDiscovery} - * - * @author Nemer Daud - Initial contribution - */ -public class WasherDryerPropertyDiscovery { - -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index ddde50496ffdb..3e864e50b8b35 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -420,92 +420,4 @@ Active Saving - - - String - - Washer Course - - - - - - - - - - - - - - - - - - - - String - - Washer Smart Course - - - - - - - - - - - - - - - - - - - - String - - Washer Course - - - - - - - - - - - - - - - - - - - - String - - Washer Smart Course - - - - - - - - - - - - - - - - - diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml deleted file mode 100644 index 8d79b03e4c5de..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer.xml +++ /dev/null @@ -1,51 +0,0 @@ - - - - - - - - - - - LG ThinQ Washing Machine - - - - - - - - - - - - - - - - - - - - - LG ThinQ Washing Tower - - - - - - - - - - - - - - - - From 0b1b7fb6bce36c2857ef9720cf83a87d23138d3e Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 21 May 2024 11:59:30 -0300 Subject: [PATCH 097/130] [lgthinq][feat] Complementing the documentation Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 190 ++++++++++++++---- .../lgservices/model/CapabilityFactory.java | 2 +- .../resources/OH-INF/thing/washer-dryer.xml | 1 - 3 files changed, 154 insertions(+), 39 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index bfcd12425a49f..da15c65770ac6 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -1,15 +1,27 @@ # LG ThinQ Bridge & Things -This binding was developed to integrate de OpenHab framework to LG ThinQ API. Currently, only Air Conditioners (API V1 & V2) are supported, but this binding is under construction to support others LG ThinQ Device Kinds. +This binding was developed to integrate de OpenHab framework to LG ThinQ API. The ThinQ Bridge is necessary to work as a hub/bridge to discovery and first configure the LG ThinQ devices related with the LG's user account. -Then, the first thing is to create the LG ThinQ Bridge and then, it will discovery all Things you have related in your LG Account. +Then, the first thing is to create the LG ThinQ Bridge and then, it will discover all Things you have related in your LG Account. ## Supported Things -LG ThinQ Devices V1 & V2 (currently only Air Conditioners are supported, but it's planned to support the other kinds as well) +This binding support several devices from the LG ThinQ Devices V1 & V2 line. Se the table bellow: + +| Device Name | Versions | Special Functions | Commands | Obs | +|-----------------|----------|------------------------------|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Air Conditioner | V1 & V2 | Filter and Energy Monitoring | All features in LG App, except Wind Direction | | +| Dish Washer | V2 | None | None | Provide only some channels to follow the cycle | +| Dryer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | +| Washer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | +| Washer Machine | V1 & V2 | None | All features in LG App (including remote start) | | +| Dryer Machine | V1 & V2 | None | All features in LG App (including remote start) | | +| Refrigerator | V1 & V2 | None | All features in LG App | | +| Heat Pump | V1 & V2 | None | All features in LG App | | + ## Discovery -This Bridge discovery Air Conditioner Things related to the user's account. To force the immediate discovery, you can disable & enable the bridge +This binding bas auto-discovering for the supported devices ## Binding Configuration @@ -17,24 +29,30 @@ This Bridge discovery Air Conditioner Things related to the user's account. To f The binding is represented by a bridge (LG GatewayBridge) and you must configure the following parameters: -| Bridge Parameter | Description | Constraints | -|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------| -| User Language | User language configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | en-US, en-GB, pt-BR, de-DE, da-DK | -| User Country | User country configured for the LG's account. Actually we have an limited number of language values available. If you need some specific, please let me know | US, UK, BR, DE and DK | -| LG User name | The LG user's account (normally an email) | | -| LG Password | The LG user's password | | -| Pooling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | - +| Bridge Parameter | Description | Obs | +|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| +| User Language | More frequent languages used | If you choose other, you can fill Manual user language (only if your language was not pre-defined in this combo | +| User Country | More frequent countries used | If you choose other, you can fill Manual user country (only if your country was not pre-defined in this combo | +| Manual User Language | The acronym for the language (PT, EN, IT, etc) | | +| Manual User Country | The acronym for the country (UK, US, BR, etc) | | +| LG User name | The LG user's account (normally an email) | | +| LG Password | The LG user's password | | +| LG Password | The LG user's password | | +| Pooling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | ## Thing Configuration -For now, only Air Conditioners are supported and must implement versions 1 or 2 of LG API (currently, there are only this 2 versions). We are working hard to release in a next version supportability for others devices like: refrigerators, washing machines, etc. -There is currently no configuration available, as it is automatically obtained by the bridge discovery process. +All the configurations are pre-defined by the discovery process. But you can customize some parameters to fine-tune the device's state polling process: +Polling period in seconds when the device is off: is the period that the binding wait until hit the LG API to get the latest device's state when the device is actually turned off +Polling period in seconds when the device is oon: is the period that the binding wait until hit the LG API to get the latest device's state when the device is actually turned on ## Channels -LG ThinQ Air Conditioners support the following channels to interact with the OpenHab automation framework: +### Air Conditioner +LG ThinQ Air Conditioners supports the following channels + +#### Dashboard Channels | channel | type | description | |--------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -43,6 +61,126 @@ LG ThinQ Air Conditioners support the following channels to interact with the Op | Fan Speed | Number (Labeled) | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | | Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | | Power | Switch | Define the device's current power state. | +| Cool Jet | Switch | Switch Cool Jet ON/OFF | +| Auto Dry | Switch | Switch Auto Dry ON/OFF | +| Energy Saving | Switch | Switch Energy Saving ON/OFF | + +#### More Information Channel + +| channel | type | description | +|--------------------------------|----------------------|-----------------------------------------------------------------------------------------------------------| +| Enable Extended Info Collector | Switch | Enable/Disable the extra information collector to update the bellow channels | +| Current Power | Number:Energy | The current power consumption | +| Remaining Filter | Number:Dimensionless | Per percentage of the filter remaining | + +### Heat Pump +LG ThinQ Heat Pump supports the following channels + +#### Dashboard Channels + +| channel | type | description | +|---------------------|------------------|-----------------------------------------------------------------------------------------------------------| +| Target Temperature | Temperature | Defines the desired target temperature for the device | +| Minimum Temperature | Temperature | Minimum temperature for the current operation mode | +| Maximum Temperature | Temperature | Maximum temperature for the current operation mode | +| Temperature | Temperature | Read-Only channel that indicates the current temperature informed by the device | +| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | +| Power | Switch | Define the device's current power state. | +| Air/Water Switch | Switch | Switch the heat pump operation between Air or Water | + +#### More Information Channel + +| channel | type | description | +|--------------------------------|----------------------|-----------------------------------------------------------------------------------------------------------| +| Enable Extended Info Collector | Switch | Enable/Disable the extra information collector to update the bellow channels | +| Current Power | Number:Energy | The current power consumption | + +### Washer Machine +LG ThinQ Washer Machine supports the following channels + +#### Dashboard Channels + +| channel | type | description | +|-------------------|--------|------------------------------------------------------------------------| +| Washer State | String | General State of the Washer | +| Process State | String | States of the running cycle | +| Course | String | Course set up to work | +| Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | +| Door Lock | Switch | Display if the Door is Locked. | +| Rinse | String | The Rinse set program | +| Spin | String | The Spin set option | +| Delay Time | String | Delay time programmed to start the cycle | +| Remaining Time | String | Remaining time to finish the course | +| Stand By Mode | Switch | If the Washer is in stand-by-mode | +| Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | + +#### Remote Start Option + +This Channel Group is only available if the Washer is configured to Remote Start + +| channel | type | description | +|-------------------|--------------------|---------------------------------------------------------------------------------------------------------| +| Remote Start/Stop | Switch | Switch to control if you want to start/stop the cycle remotely | +| Course to Run | String (Selection) | The pre-programmed course (or default) is shown. You can change-it if you want before remote start | +| Temperature Level | String (Selection) | The pre-programmed temperature (or default) is shown. You can change-it if you want before remote start | +| Spin | String | The pre-programmed spin (or default) is shown. You can change-it if you want before remote start | +| Rinse | String | The pre-programmed rinse (or default) is shown. You can change-it if you want before remote start | + +### Dryer Machine +LG ThinQ Dryer Machine supports the following channels + +#### Dashboard Channels + +| channel | type | description | +|-------------------|--------|------------------------------------------------------------------------| +| Dryer State | String | General State of the Washer | +| Process State | String | States of the running cycle | +| Course | String | Course set up to work | +| Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | +| Chiel Lock | Switch | Display if the Door is Locked. | +| Dry Level Course | String | Dry level set to work in the course | +| Delay Time | String | Delay time programmed to start the cycle | +| Remaining Time | String | Remaining time to finish the course | +| Stand By Mode | Switch | If the Washer is in stand-by-mode | +| Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | + +#### Remote Start Option + +This Channel Group is only available if the Dryer is configured to Remote Start + +| channel | type | description | +|-------------------|--------------------|---------------------------------------------------------------------------------------------------------| +| Remote Start/Stop | Switch | Switch to control if you want to start/stop the cycle remotely | +| Course to Run | String (Selection) | The pre-programmed course (or default) is shown. You can change-it if you want before remote start | + +### Dryer/Washer Tower +LG ThinQ Dryer/Washer is recognized as 2 different things: Dryer & Washer machines. Thus, for this device, follow the sessions for Dryer Machine and Washer Machine + +### Refrigerator +LG ThinQ Refrigerator supports the following channels + +#### Dashboard Channels + +| channel | type | description | +|-------------------------------|-------------|--------------------------------------------------------------------------------| +| Door Open | Contact | Advice if the door is opened | +| Freezer Set Point Temperature | Temperature | Temperature level chosen. This channel supports commands to change temperature | +| Fridge Set Point Temperature | Temperature | Temperature level chosen. This channel supports commands to change temperature | +| Temp. Unit | String | Temperature Unit (°C/F). Supports command to change the unit | +| Express Freeze | Switch | Channel to change the express freeze function (ON/OFF/Rapid) | +| Express Cool | Switch | Channel to switch ON/OFF express cool function | +| Vacation | Switch | Channel to switch ON/OFF Vacation function (unit will work in eco mode) | + +#### More Information + +This Channel Group is reports useful information data for the device: + +| channel | type | description | +|------------------|-----------|------------------------------------------------------------| +| Fresh Air Filter | String | Shows the Fresh Air filter status (OFF/AUTO/POWER/REPLACE) | +| Water Filter | String | Shows the filter's used months | + +OBS: some versions of this device can not support all the channels, depending on the model's capabilities. **Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart ThinQ way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: 1. **Internet Link** - if you OpenHab server doesn't have a good internet connection this binding will not work properly! In the same way, if the internet link goes down, your Things and Bridge going to be Offline as well, and you won't be able to control the devices though OpenHab until the link comes back. @@ -50,30 +188,8 @@ LG ThinQ Air Conditioners support the following channels to interact with the Op 3. **Pooling time** - both Bridge and Thing use pooling strategy to get the current state information about the registered devices. Note that the Thing pooling time is internal and can't be changed (please, don't change in the source code) and the Bridge can be changed for something greater than 300 seconds, and it's recommended long pooling periods for the Bridge because the discovery process fetch a lot of information from the LG API Server, depending on the number of devices you have registered in your account. About this last point, it's important to know that LG API is not Open & Public, i.e, only LG Official Partners with some agreement have access to their support and documentations. This binding was a hard (very hard actually) work to dig and reverse engineering in the LG's ThinQ API protocol. Because this, you must respect the hardcoded pool period to do not put your account in LG Blacklist. -## Thanks and Inspirations - -This binding was inspired in the work of some brave opensource community people. I got some tips and helps from their codes: -* Adrian Sampson - [Wideq Project](https://github.com/sampsyo/wideq): I think it is the first reverse engineering of ThinQ protocol made in Python, but only works (currently) for API V1. -* Ollo69 - [LG ThinQ Integration for Home Assistant](https://github.com/ollo69/ha-smartthinq-sensors): Ollo69 took the Adrian code and refactor it to support API V2 in an HA plugin. - ## Be nice! If you like the binding, why don't you support me by buying me a coffee? It would certainly motivate me to further improve this work or to create new others cool bindings for OpenHab ! [![Buy me a coffee!](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/nemerdaud) - - -For openHAB 4.0 just enter - -mvn clean install -pl :org.openhab.binding.lgthinq - -for openHAB 3.4.x enter - -mvn clean install -pl :org.openhab.binding.lgthinq -Dohc.version=3.4.0 -Doh.java.version=11 -Dkaraf.version=4.3.7 -Just be carefull, the clean command deletes the content of the target folder, so you’d better copy files before issuing the second command. -Only culprit, the openHAB 3 Binding version is still named - -org.openhab.binding.lgthinq-4.0.0-SNAPSHOT.jar -So you need to manually change it to - -org.openhab.binding.lgthinq-3.4.5-SNAPSHOT.jar diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java index ed78a93bc5883..9c641ebb19541 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java @@ -45,7 +45,7 @@ private CapabilityFactory() { List> factories = Arrays.asList(new ACCapabilityFactoryV1(), new ACCapabilityFactoryV2(), new FridgeCapabilityFactoryV1(), new FridgeCapabilityFactoryV2(), new WasherDryerCapabilityFactoryV1(), new WasherDryerCapabilityFactoryV2(), - new WasherDryerCapabilityFactoryV1(), new DishWasherCapabilityFactoryV2()); + new DishWasherCapabilityFactoryV2()); factories.forEach(f -> { f.getSupportedDeviceTypes().forEach(d -> { Map> versionMap = capabilityDeviceFactories.get(d); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml index c14d280cd7ac8..8bab5a16590e6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml @@ -122,7 +122,6 @@ - From 0e589e4269a810ab6aa5d72ed7b89eca39832bf8 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 29 May 2024 10:07:46 -0300 Subject: [PATCH 098/130] [lgthinq][feat] AC Fan Direction (H/V) Signed-off-by: nemerdaud --- .../internal/LGThinQBindingConstants.java | 17 +++++- .../handler/LGThinQAirConditionerHandler.java | 24 +++++++++ .../model/devices/ac/ACCanonicalSnapshot.java | 27 ++++++++-- .../model/devices/ac/ACCapability.java | 36 +++++++++++++ .../devices/ac/ACCapabilityFactoryV1.java | 10 ++++ .../devices/ac/ACCapabilityFactoryV2.java | 10 ++++ .../ac/AbstractACCapabilityFactory.java | 54 +++++++++++++++---- .../dishwasher/DishWasherSnapshot.java | 7 +-- .../main/resources/OH-INF/thing/channels.xml | 14 +++++ 9 files changed, 179 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 7e204e69bfb41..200cbada0a2fa 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -209,6 +209,8 @@ public class LGThinQBindingConstants { public static final String CHANNEL_AIR_CLEAN_ID = "air_clean"; public static final String CHANNEL_AUTO_DRY_ID = "auto_dry"; public static final String CHANNEL_ENERGY_SAVING_ID = "energy_saving"; + public static final String CHANNEL_STEP_UP_DOWN_ID = "fan_step_up_down"; + public static final String CHANNEL_STEP_LEFT_RIGHT_ID = "fan_step_left_right"; public static final String CAP_ACHP_OP_MODE_COOL_KEY = "@AC_MAIN_OPERATION_MODE_COOL_W"; public static final String CAP_ACHP_OP_MODE_HEAT_KEY = "@AC_MAIN_OPERATION_MODE_HEAT_W"; @@ -218,6 +220,17 @@ public class LGThinQBindingConstants { "@AC_MAIN_OPERATION_MODE_AI_W", "AI", "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); + public static final Map CAP_AC_STEP_UP_DOWN_MODE = Map.of("@OFF", "Off", "@1", "Upper", "@2", "Up", + "@3", "Middle", "@4", "Down", "@5", "Far Down", "@100", "Circular"); + public static final Map CAP_AC_STEP_LEFT_RIGHT_MODE = Map.of("@OFF", "Off", "@1", "Lefter", "@2", + "Left", "@3", "Middle", "@4", "Right", "@5", "Righter", "@13", "Left to Middle", "@35", "Middle to Right", + "@100", "Circular"); + + // Sub Modes support + public static final String AC_SUB_MODE_COOL_JET = "@AC_MAIN_WIND_MODE_COOL_JET_W"; + public static final String AC_SUB_MODE_STEP_UP_DOWN = "@AC_MAIN_WIND_DIRECTION_STEP_UP_DOWN_W"; + public static final String AC_SUB_MODE_STEP_LEFT_RIGHT = "@AC_MAIN_WIND_DIRECTION_STEP_LEFT_RIGHT_W"; + public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), @@ -350,6 +363,8 @@ public class LGThinQBindingConstants { // ====================== WASHING MACHINE CONSTANTS ============================= public static final String DW_SNAPSHOT_WASHER_DRYER_NODE_V2 = "dishwasher"; + public static final String DW_POWER_OFF_VALUE = "POWEROFF"; + public static final String DW_STATE_COMPLETE = "END"; public static final Map CAP_DW_DOOR_STATE = Map.of("@CP_OFF_EN_W", "Close", "@CP_ON_EN_W", "Opened"); public static final Map CAP_DW_PROCESS_STATE = Map.ofEntries(entry("@DW_STATE_INITIAL_W", "None"), @@ -359,7 +374,7 @@ public class LGThinQBindingConstants { entry("@DW_STATE_CANCEL_W", "Cancelled")); public static final Map CAP_DW_STATE = Map.ofEntries(entry("@DW_STATE_POWER_OFF_W", "Off"), - entry("@WM_STATE_INITIAL_W", "Initial"), entry("@DW_STATE_RUNNING_W", "Running"), + entry("@DW_STATE_INITIAL_W", "Initial"), entry("@DW_STATE_RUNNING_W", "Running"), entry("@DW_STATE_PAUSE_W", "Paused"), entry("@DW_STATE_STANDBY_W", "Stand By"), entry("@DW_STATE_COMPLETE_W", "Complete"), entry("@DW_STATE_POWER_FAIL_W", "Power Fail")); // ============================================================================== diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 2e189839122ad..70abe1ca890f1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -73,6 +73,8 @@ public class LGThinQAirConditionerHandler extends LGThinQAbstractDeviceHandler
options = new ArrayList<>(); + acCap.getStepUpDown() + .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_STEP_UP_DOWN_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(stepUpDownChannelUID, options); + } + if (getThing().getChannel(stepLeftRightChannelUID) == null && acCap.isStepLeftRightAvailable()) { + createDynSwitchChannel(CHANNEL_STEP_LEFT_RIGHT_ID, stepLeftRightChannelUID); + List options = new ArrayList<>(); + acCap.getStepLeftRight().forEach( + (k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_STEP_LEFT_RIGHT_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(stepLeftRightChannelUID, options); + } if (!acCap.getFanSpeed().isEmpty()) { List options = new ArrayList<>(); acCap.getFanSpeed() diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java index 9a305d42adf70..cfcd1bcf5adb3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java @@ -44,17 +44,14 @@ public class ACCanonicalSnapshot extends AbstractSnapshotDefinition { // =============================================== private int airWindStrength; - private double targetTemperature; - private double currentTemperature; - - private boolean coolJetModeOn; - private double airCleanMode; private double coolJetMode; private double autoDryMode; private double energySavingMode; + private double stepUpDownMode; + private double stepLeftRightMode; private int operationMode; @Nullable @@ -179,6 +176,26 @@ public void setOperation(Integer operation) { this.operation = operation; } + @JsonProperty("airState.wDir.vStep") + @JsonAlias("WDirVStep") + public double getStepUpDownMode() { + return stepUpDownMode; + } + + public void setStepUpDownMode(double stepUpDownMode) { + this.stepUpDownMode = stepUpDownMode; + } + + @JsonProperty("airState.wDir.hStep") + @JsonAlias("WDirHStep") + public double getStepLeftRightMode() { + return stepLeftRightMode; + } + + public void setStepLeftRightMode(double stepLeftRightMode) { + this.stepLeftRightMode = stepLeftRightMode; + } + @JsonIgnore public boolean isOnline() { return online; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java index e935f375011b7..85581e40cad44 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapability.java @@ -28,7 +28,11 @@ public class ACCapability extends AbstractCapability { private Map opMod = Collections.emptyMap(); private Map fanSpeed = Collections.emptyMap(); + private Map stepUpDown = Collections.emptyMap(); + private Map stepLeftRight = Collections.emptyMap(); private boolean isJetModeAvailable; + private boolean isStepUpDownAvailable; + private boolean isStepLeftRightAvailable; private boolean isEnergyMonitorAvailable; private boolean isFilterMonitorAvailable; private boolean isAutoDryModeAvailable; @@ -45,6 +49,22 @@ public class ACCapability extends AbstractCapability { private String airCleanModeCommandOn = ""; private String airCleanModeCommandOff = ""; + public Map getStepLeftRight() { + return stepLeftRight; + } + + public void setStepLeftRight(Map stepLeftRight) { + this.stepLeftRight = stepLeftRight; + } + + public Map getStepUpDown() { + return stepUpDown; + } + + public void setStepUpDown(Map stepUpDown) { + this.stepUpDown = stepUpDown; + } + public String getCoolJetModeCommandOff() { return coolJetModeCommandOff; } @@ -61,6 +81,22 @@ public void setCoolJetModeCommandOn(String coolJetModeCommandOn) { this.coolJetModeCommandOn = coolJetModeCommandOn; } + public boolean isStepUpDownAvailable() { + return isStepUpDownAvailable; + } + + public void setStepUpDownAvailable(boolean stepUpDownAvailable) { + isStepUpDownAvailable = stepUpDownAvailable; + } + + public boolean isStepLeftRightAvailable() { + return isStepLeftRightAvailable; + } + + public void setStepLeftRightAvailable(boolean stepLeftRightAvailable) { + isStepLeftRightAvailable = stepLeftRightAvailable; + } + public Map getOpMode() { return opMod; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java index b936930d7fbd8..f0bfde402905d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java @@ -96,6 +96,16 @@ protected String getJetModeNodeName() { return "Jet"; } + @Override + protected String getStepUpDownNodeName() { + return "WDirVStep"; + } + + @Override + protected String getStepLeftRightNodeName() { + return "WDirHStep"; + } + @Override protected String getSupSubRacModeNodeName() { return "SupportRACSubMode"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java index 88b4d55abc09e..e29828a67676c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java @@ -88,6 +88,16 @@ protected String getJetModeNodeName() { return "airState.wMode.jet"; } + @Override + protected String getStepUpDownNodeName() { + return "airState.wDir.vStep"; + } + + @Override + protected String getStepLeftRightNodeName() { + return "airState.wDir.hStep"; + } + @Override protected String getSupSubRacModeNodeName() { return "support.racSubMode"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java index 4eeedce5d2498..ee081b179603f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java @@ -79,14 +79,18 @@ private List extractValueOptions(JsonNode optionsNode) throws LGThinqApi } } - private Map extractInvertedOptions(JsonNode optionsNode) { + private Map extractOptions(JsonNode optionsNode, boolean invertKeyValue) { if (optionsNode.isMissingNode()) { logger.warn("Error extracting options supported by the device"); return Collections.EMPTY_MAP; } else { Map modes = new HashMap(); optionsNode.fields().forEachRemaining(e -> { - modes.put(e.getValue().asText(), e.getKey()); + if (invertKeyValue) { + modes.put(e.getValue().asText(), e.getKey()); + } else { + modes.put(e.getKey(), e.getValue().asText()); + } }); return modes; } @@ -101,10 +105,10 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { throw new LGThinqApiException("Error extracting capabilities supported by the device"); } // supported operation modes - Map allOpModes = extractInvertedOptions( - valuesNode.path(getOpModeNodeName()).path(getOptionsMapNodeName())); - Map allFanSpeeds = extractInvertedOptions( - valuesNode.path(getFanSpeedNodeName()).path(getOptionsMapNodeName())); + Map allOpModes = extractOptions( + valuesNode.path(getOpModeNodeName()).path(getOptionsMapNodeName()), true); + Map allFanSpeeds = extractOptions( + valuesNode.path(getFanSpeedNodeName()).path(getOptionsMapNodeName()), true); List supOpModeValues = extractValueOptions( valuesNode.path(getSupOpModeNodeName()).path(getOptionsMapNodeName())); @@ -139,9 +143,15 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { JsonNode supRacSubModeOps = valuesNode.path(getSupSubRacModeNodeName()).path(getOptionsMapNodeName()); if (!supRacSubModeOps.isMissingNode()) { supRacSubModeOps.fields().forEachRemaining(f -> { - if ("@AC_MAIN_WIND_MODE_COOL_JET_W".equals(f.getValue().asText())) { + if (AC_SUB_MODE_COOL_JET.equals(f.getValue().asText())) { acCap.setJetModeAvailable(true); } + if (AC_SUB_MODE_STEP_UP_DOWN.equals(f.getValue().asText())) { + acCap.setStepUpDownAvailable(true); + } + if (AC_SUB_MODE_STEP_LEFT_RIGHT.equals(f.getValue().asText())) { + acCap.setStepLeftRightAvailable(true); + } }); } @@ -159,6 +169,24 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { }); } } + // ============== Collect Wind Direction (Up-Down, Left-Right) if supported ================== + if (acCap.isStepUpDownAvailable()) { + Map stepUpDownValueMap = extractOptions( + valuesNode.path(getStepUpDownNodeName()).path(getOptionsMapNodeName()), false); + // remove options who value doesn't start with @, that indicates for this feature that is not supported + stepUpDownValueMap.values().removeIf(v -> !v.startsWith("@")); + acCap.setStepUpDown(stepUpDownValueMap); + } + + if (acCap.isStepLeftRightAvailable()) { + Map stepLeftRightValueMap = extractOptions( + valuesNode.path(getStepLeftRightNodeName()).path(getOptionsMapNodeName()), false); + // remove options who value doesn't start with @, that indicates for this feature that is not supported + stepLeftRightValueMap.values().removeIf(v -> !v.startsWith("@")); + acCap.setStepLeftRight(stepLeftRightValueMap); + } + // =================================================== // + // get Supported RAC Mode JsonNode supRACModeOps = valuesNode.path(getSupRacModeNodeName()).path(getOptionsMapNodeName()); @@ -167,8 +195,8 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { String racOpValue = r.getValue().asText(); switch (racOpValue) { case CAP_AC_AUTODRY: - Map dryStates = extractInvertedOptions( - valuesNode.path(getAutoDryStateNodeName()).path(getOptionsMapNodeName())); + Map dryStates = extractOptions( + valuesNode.path(getAutoDryStateNodeName()).path(getOptionsMapNodeName()), true); if (!dryStates.isEmpty()) { // sanity check acCap.setAutoDryModeAvailable(true); dryStates.forEach((cmdKey, cmdValue) -> { @@ -183,8 +211,8 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { } break; case CAP_AC_AIRCLEAN: - Map airCleanStates = extractInvertedOptions( - valuesNode.path(getAirCleanStateNodeName()).path(getOptionsMapNodeName())); + Map airCleanStates = extractOptions( + valuesNode.path(getAirCleanStateNodeName()).path(getOptionsMapNodeName()), true); if (!airCleanStates.isEmpty()) { acCap.setAirCleanAvailable(true); airCleanStates.forEach((cmdKey, cmdValue) -> { @@ -242,6 +270,10 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { protected abstract String getJetModeNodeName(); + protected abstract String getStepUpDownNodeName(); + + protected abstract String getStepLeftRightNodeName(); + protected abstract String getSupSubRacModeNodeName(); protected abstract String getSupRacModeNodeName(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java index 0a56e876106a1..18db4b80da8b8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; @@ -118,7 +118,8 @@ public void setRemainingHour(Double remainingHour) { @JsonProperty("remainTimeMinute") @JsonAlias({ "Remain_Time_M" }) public Double getRemainingMinute() { - return remainingMinute; + // Issue in some DW when the remainingMinute stay in 1 after complete in some cases + return DW_STATE_COMPLETE.equals(getState()) ? 0.0 : remainingMinute; } public void setRemainingMinute(Double remainingMinute) { @@ -161,7 +162,7 @@ public void setDoorLock(String doorLock) { public void setState(String state) { this.state = state; - if (state.equals(WM_POWER_OFF_VALUE)) { + if (state.equals(DW_POWER_OFF_VALUE)) { powerState = DevicePowerState.DV_POWER_OFF; } else { powerState = DevicePowerState.DV_POWER_ON; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 3e864e50b8b35..86325d68ab64f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -81,6 +81,20 @@ Switch + + + Number + + Fan Vertical Direction + + + + + Number + + Fan Horizontal Direction + + Number:Temperature From 3d502f96bb4158e3c47cbabea4ce536fa013ab70 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 29 May 2024 16:23:00 -0300 Subject: [PATCH 099/130] [lgthinq][feat] Commands for AC Fan Direction (H/V) Signed-off-by: nemerdaud --- .../internal/LGThinQBindingConstants.java | 2 +- .../handler/LGThinQAirConditionerHandler.java | 57 +++++++++++++------ .../lgservices/LGThinQACApiClientService.java | 8 ++- .../LGThinQACApiV1ClientServiceImpl.java | 34 +++++++++-- .../LGThinQACApiV2ClientServiceImpl.java | 31 ++++++++-- .../LGThinQAbstractApiV1ClientService.java | 41 ++++++++++--- .../main/resources/OH-INF/thing/channels.xml | 2 + 7 files changed, 137 insertions(+), 38 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 200cbada0a2fa..584d02d75ca15 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -221,7 +221,7 @@ public class LGThinQBindingConstants { "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); public static final Map CAP_AC_STEP_UP_DOWN_MODE = Map.of("@OFF", "Off", "@1", "Upper", "@2", "Up", - "@3", "Middle", "@4", "Down", "@5", "Far Down", "@100", "Circular"); + "@3", "Middle Up", "@4", "Middle Down", "@5", "Down", "@6", "Far Down", "@100", "Circular"); public static final Map CAP_AC_STEP_LEFT_RIGHT_MODE = Map.of("@OFF", "Off", "@1", "Lefter", "@2", "Left", "@3", "Middle", "@4", "Right", "@5", "Righter", "@13", "Left to Middle", "@35", "Middle to Right", "@100", "Circular"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 70abe1ca890f1..66eebdb05fc8b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -139,21 +139,20 @@ public void initialize() { protected void updateDeviceChannels(ACCanonicalSnapshot shot) { updateState(powerChannelUID, DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF); - updateState(opModeChannelUID, new DecimalType(BigDecimal.valueOf(shot.getOperationMode()))); + updateState(opModeChannelUID, new DecimalType(shot.getOperationMode())); if (DeviceTypes.HEAT_PUMP.equals(getDeviceType())) { - updateState(hpAirWaterSwitchChannelUID, - new DecimalType(BigDecimal.valueOf(shot.getHpAirWaterTempSwitch()))); + updateState(hpAirWaterSwitchChannelUID, new DecimalType(shot.getHpAirWaterTempSwitch())); } - updateState(fanSpeedChannelUID, new DecimalType(BigDecimal.valueOf(shot.getAirWindStrength()))); - updateState(currTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getCurrentTemperature()))); - updateState(targetTempChannelUID, new DecimalType(BigDecimal.valueOf(shot.getTargetTemperature()))); + updateState(fanSpeedChannelUID, new DecimalType(shot.getAirWindStrength())); + updateState(currTempChannelUID, new DecimalType(shot.getCurrentTemperature())); + updateState(targetTempChannelUID, new DecimalType(shot.getTargetTemperature())); try { ACCapability acCap = getCapabilities(); if (getThing().getChannel(stepUpDownChannelUID) != null) { - updateState(stepUpDownChannelUID, new DecimalType(shot.getStepUpDownMode())); + updateState(stepUpDownChannelUID, new DecimalType((int) shot.getStepUpDownMode())); } if (getThing().getChannel(stepLeftRightChannelUID) != null) { - updateState(stepLeftRightChannelUID, new DecimalType(shot.getStepLeftRightMode())); + updateState(stepLeftRightChannelUID, new DecimalType((int) shot.getStepLeftRightMode())); } if (getThing().getChannel(jetModeChannelUID) != null) { Double commandCoolJetOn = Double.valueOf(acCap.getCoolJetModeCommandOn()); @@ -226,18 +225,10 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { createDynSwitchChannel(CHANNEL_ENERGY_SAVING_ID, energySavingChannelUID); } if (getThing().getChannel(stepUpDownChannelUID) == null && acCap.isStepUpDownAvailable()) { - createDynSwitchChannel(CHANNEL_STEP_UP_DOWN_ID, stepUpDownChannelUID); - List options = new ArrayList<>(); - acCap.getStepUpDown() - .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_STEP_UP_DOWN_MODE.get(v))))); - stateDescriptionProvider.setStateOptions(stepUpDownChannelUID, options); + createDynChannel(CHANNEL_STEP_UP_DOWN_ID, stepUpDownChannelUID, "Number"); } if (getThing().getChannel(stepLeftRightChannelUID) == null && acCap.isStepLeftRightAvailable()) { - createDynSwitchChannel(CHANNEL_STEP_LEFT_RIGHT_ID, stepLeftRightChannelUID); - List options = new ArrayList<>(); - acCap.getStepLeftRight().forEach( - (k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_STEP_LEFT_RIGHT_MODE.get(v))))); - stateDescriptionProvider.setStateOptions(stepLeftRightChannelUID, options); + createDynChannel(CHANNEL_STEP_LEFT_RIGHT_ID, stepLeftRightChannelUID, "Number"); } if (!acCap.getFanSpeed().isEmpty()) { List options = new ArrayList<>(); @@ -250,6 +241,18 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { acCap.getOpMode().forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_OP_MODE.get(v))))); stateDescriptionProvider.setStateOptions(opModeChannelUID, options); } + if (!acCap.getStepLeftRight().isEmpty()) { + List options = new ArrayList<>(); + acCap.getStepLeftRight().forEach( + (k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_STEP_LEFT_RIGHT_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(stepLeftRightChannelUID, options); + } + if (!acCap.getStepUpDown().isEmpty()) { + List options = new ArrayList<>(); + acCap.getStepUpDown() + .forEach((k, v) -> options.add(new StateOption(k, emptyIfNull(CAP_AC_STEP_UP_DOWN_MODE.get(v))))); + stateDescriptionProvider.setStateOptions(stepUpDownChannelUID, options); + } } @Override @@ -326,6 +329,24 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept } break; } + case CHANNEL_STEP_UP_DOWN_ID: { + if (command instanceof DecimalType) { + lgThinqACApiClientService.changeStepUpDown(getBridgeId(), getDeviceId(), getLastShot(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in Step Up/Down Channel. Ignoring"); + } + break; + } + case CHANNEL_STEP_LEFT_RIGHT_ID: { + if (command instanceof DecimalType) { + lgThinqACApiClientService.changeStepLeftRight(getBridgeId(), getDeviceId(), getLastShot(), + ((DecimalType) command).intValue()); + } else { + logger.warn("Received command different of Numeric in Step Left/Right Channel. Ignoring"); + } + break; + } case CHANNEL_POWER_ID: { if (command instanceof OnOffType) { lgThinqACApiClientService.turnDevicePower(getBridgeId(), getDeviceId(), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java index d8fed543ceb63..f7283368d3fcc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java @@ -31,6 +31,12 @@ public interface LGThinQACApiClientService extends LGThinQApiClientService subModeFeatures = Map.of("Jet", currentSnap.getCoolJetMode().intValue(), + "PowerSave", currentSnap.getEnergySavingMode().intValue(), "WDirVStep", newStep, "WDirHStep", + (int)currentSnap.getStepLeftRightMode()); + try { + RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", subModeFeatures, null); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error stepUpDown", e); + } + } + + @Override + public void changeStepLeftRight(String bridgeName, String deviceId, ACCanonicalSnapshot currentSnap, int newStep) + throws LGThinqApiException { + Map<@Nullable String, @Nullable Object> subModeFeatures = Map.of("Jet", currentSnap.getCoolJetMode().intValue(), + "PowerSave", currentSnap.getEnergySavingMode().intValue(), "WDirVStep", (int)currentSnap.getStepUpDownMode(), + "WDirHStep", newStep); + try { + RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", subModeFeatures, null); + handleGenericErrorResult(resp); + } catch (Exception e) { + throw new LGThinqApiException("Error stepUpDown", e); + } + } + @Override public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGThinqApiException { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java index f2c30a97ffc0b..b81a9ee83822a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -91,11 +91,6 @@ protected void turnGenericMode(String bridgeName, String deviceId, String modeNa } } - @Override - public void turnBellOnOff(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException { - turnGenericMode(bridgeName, deviceId, "airState.bellSound.appControl", modeOnOff); - } - @Override public void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException { try { @@ -121,6 +116,32 @@ public void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) } } + @Override + public void changeStepUpDown(String bridgeName, String deviceId, ACCanonicalSnapshot currentSnap, int newStep) + throws LGThinqApiException { + try { + RestResult resp = sendBasicControlCommands(bridgeName, deviceId, "Set", "airState.wDir.vStep", newStep); + handleGenericErrorResult(resp); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting operation mode", e); + } + } + + @Override + public void changeStepLeftRight(String bridgeName, String deviceId, ACCanonicalSnapshot currentSnap, int newStep) + throws LGThinqApiException { + try { + RestResult resp = sendBasicControlCommands(bridgeName, deviceId, "Set", "airState.wDir.hStep", newStep); + handleGenericErrorResult(resp); + } catch (LGThinqApiException e) { + throw e; + } catch (Exception e) { + throw new LGThinqApiException("Error adjusting operation mode", e); + } + } + @Override public void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGThinqApiException { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index 2da62d5ade836..af40a920677a3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -36,6 +36,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -61,16 +62,34 @@ protected RestResult sendCommand(String bridgeName, String deviceId, String cont } protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, - String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) + String command, Map<@Nullable String, @Nullable Object> keyValue, @Nullable ObjectNode extraNode) throws Exception { ObjectNode payloadNode = JsonNodeFactory.instance.objectNode(); payloadNode.put("cmd", controlKey).put("cmdOpt", command); - if (keyName == null || keyName.isEmpty()) { - // value is a simple text - payloadNode.put("value", value); - } else { - payloadNode.putObject("value").put(keyName, value); - } + keyValue.forEach((k, v) -> { + if (k == null || k.isEmpty()) { + // value is a simple text + if (v instanceof Integer i) { + payloadNode.put("value", i); + } else if (v instanceof Double d) { + payloadNode.put("value", d); + } else { + payloadNode.put("value", "" + v); + } + } else { + JsonNode valueNode = payloadNode.path("value"); + if (valueNode.isMissingNode()) { + valueNode = payloadNode.putObject("value"); + } + if (v instanceof Integer i) { + ((ObjectNode) valueNode).put(k, i); + } else if (v instanceof Double d) { + ((ObjectNode) valueNode).put(k, d); + } else { + ((ObjectNode) valueNode).put(k, "" + v); + } + } + }); // String payload = String.format( // "{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"%s\"," + " \"cmdOpt\": \"%s\"," // + " \"value\": {\"%s\": \"%s\"}," + " \"deviceId\": \"%s\"," @@ -82,6 +101,14 @@ protected RestResult sendCommand(String bridgeName, String deviceId, String cont return sendCommand(bridgeName, deviceId, payloadNode); } + protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, + String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) + throws Exception { + Map<@Nullable String, @Nullable Object> values = new HashMap<>(1); + values.put(keyName, value); + return sendCommand(bridgeName, deviceId, controlPath, controlKey, command, values, extraNode); + } + protected RestResult sendCommand(String bridgeName, String deviceId, Object cmdPayload) throws Exception { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 86325d68ab64f..19ccaaa6f9529 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -86,6 +86,7 @@ Number Fan Vertical Direction + wind @@ -93,6 +94,7 @@ Number Fan Horizontal Direction + wind From e64da9a8c19ef9f058efc5c58f843a1d82237246 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 29 May 2024 16:49:18 -0300 Subject: [PATCH 100/130] [lgthinq][fix] Code format Signed-off-by: nemerdaud --- .../lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java index 0d3cf5a5430af..8733b9d313b54 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java @@ -176,7 +176,7 @@ public void changeStepUpDown(String bridgeName, String deviceId, ACCanonicalSnap throws LGThinqApiException { Map<@Nullable String, @Nullable Object> subModeFeatures = Map.of("Jet", currentSnap.getCoolJetMode().intValue(), "PowerSave", currentSnap.getEnergySavingMode().intValue(), "WDirVStep", newStep, "WDirHStep", - (int)currentSnap.getStepLeftRightMode()); + (int) currentSnap.getStepLeftRightMode()); try { RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", subModeFeatures, null); handleGenericErrorResult(resp); @@ -189,8 +189,8 @@ public void changeStepUpDown(String bridgeName, String deviceId, ACCanonicalSnap public void changeStepLeftRight(String bridgeName, String deviceId, ACCanonicalSnapshot currentSnap, int newStep) throws LGThinqApiException { Map<@Nullable String, @Nullable Object> subModeFeatures = Map.of("Jet", currentSnap.getCoolJetMode().intValue(), - "PowerSave", currentSnap.getEnergySavingMode().intValue(), "WDirVStep", (int)currentSnap.getStepUpDownMode(), - "WDirHStep", newStep); + "PowerSave", currentSnap.getEnergySavingMode().intValue(), "WDirVStep", + (int) currentSnap.getStepUpDownMode(), "WDirHStep", newStep); try { RestResult resp = sendCommand(bridgeName, deviceId, "", "Control", "Set", subModeFeatures, null); handleGenericErrorResult(resp); From f957ae470a3da8f7498bbbf0f62f46c8f7af9d3a Mon Sep 17 00:00:00 2001 From: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> Date: Mon, 29 Jul 2024 18:33:16 -0300 Subject: [PATCH 101/130] [lgthinq][fox] Updating version 4.2.0 -> 4.3.0 Co-authored-by: Wouter Born Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml index 71c06a05b1e3d..7bd6c4a839ee4 100644 --- a/bundles/org.openhab.binding.lgthinq/pom.xml +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -6,7 +6,7 @@ org.openhab.addons.bundles org.openhab.addons.reactor.bundles - 4.2.0-SNAPSHOT + 4.3.0-SNAPSHOT org.openhab.binding.lgthinq From 5557c28c8355911c75965e21abed7ea76d94a806 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 29 Jul 2024 19:14:33 -0300 Subject: [PATCH 102/130] [lgthinq][fix] Optimising imports. Signed-off-by: nemerdaud --- .../internal/discovery/LGThinqDiscoveryService.java | 5 +++-- .../handler/LGThinQAbstractDeviceHandler.java | 8 ++++++-- .../handler/LGThinQAirConditionerHandler.java | 4 +++- .../internal/handler/LGThinQBridgeHandler.java | 3 ++- .../internal/handler/LGThinQDishWasherHandler.java | 12 +++++++++--- .../internal/handler/LGThinQWasherDryerHandler.java | 13 ++++++------- .../lgthinq/internal/type/ThingModelTypeUtils.java | 3 ++- .../lgthinq/lgservices/LGThinQApiClientService.java | 5 ++++- .../lgservices/LGThinQWMApiV1ClientServiceImpl.java | 3 ++- .../model/devices/ac/ACCapabilityFactoryV2.java | 4 +++- .../devices/ac/AbstractACCapabilityFactory.java | 6 +----- .../dishwasher/DishWasherCapabilityFactoryV1.java | 5 ++++- .../devices/dishwasher/DishWasherSnapshot.java | 3 ++- .../dishwasher/DishWasherSnapshotBuilder.java | 5 ++++- .../devices/fridge/FridgeCapabilityFactoryV1.java | 4 +++- .../washerdryer/WasherDryerCapabilityFactoryV1.java | 11 +++++------ .../washerdryer/WasherDryerCapabilityFactoryV2.java | 6 +----- .../binding/lgthinq/handler/LGThinqBridgeTests.java | 4 +++- .../handler/LGThinQWasherDryerHandlerTest.java | 2 -- 19 files changed, 63 insertions(+), 43 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index aea47b6a02fb8..762118a06b1eb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -16,7 +16,9 @@ import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; import java.time.Instant; -import java.util.*; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; @@ -25,7 +27,6 @@ import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory.LGThinQGeneralApiClientService; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; -import org.openhab.core.config.discovery.*; import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; import org.openhab.core.config.discovery.DiscoveryResultBuilder; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 35e3335559978..5b7cbe2b4abbd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -19,7 +19,10 @@ import java.lang.management.ThreadMXBean; import java.lang.reflect.ParameterizedType; import java.math.BigDecimal; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.*; import org.eclipse.jdt.annotation.NonNull; @@ -36,7 +39,8 @@ import org.openhab.core.thing.*; import org.openhab.core.thing.binding.builder.ChannelBuilder; import org.openhab.core.thing.link.ItemChannelLinkRegistry; -import org.openhab.core.thing.type.*; +import org.openhab.core.thing.type.ChannelKind; +import org.openhab.core.thing.type.ChannelTypeUID; import org.openhab.core.types.Command; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 66eebdb05fc8b..353f1ba5ea94f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -44,7 +44,9 @@ import org.openhab.core.thing.Thing; import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.thing.link.ItemChannelLinkRegistry; -import org.openhab.core.types.*; +import org.openhab.core.types.Command; +import org.openhab.core.types.StateOption; +import org.openhab.core.types.UnDefType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index 6fdce58d189f9..93a85456431da 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -12,7 +12,8 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THINQ_CONNECTION_DATA_FILE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THINQ_USER_DATA_FOLDER; import java.io.File; import java.io.IOException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java index b2ff0a58d6c53..f69034634ba8d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -14,7 +14,8 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; -import java.util.*; +import java.util.ArrayList; +import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; @@ -24,13 +25,18 @@ import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQDishWasherApiClientService; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.*; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.openhab.core.types.StateOption; import org.slf4j.Logger; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index ed8a5092900e2..b2133c5350ff6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -25,16 +25,15 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; -import org.openhab.binding.lgthinq.lgservices.*; -import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; +import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseFunction; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseType; -import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.*; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; +import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java index e1de12cd9036d..da36f84ed8141 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java @@ -26,7 +26,8 @@ import org.openhab.binding.lgthinq.internal.model.*; import org.openhab.core.config.core.*; import org.openhab.core.library.CoreItemFactory; -import org.openhab.core.thing.*; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.type.*; import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.osgi.service.component.annotations.Reference; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index b23f4902a5456..ff3482385e0d5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -20,7 +20,10 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.*; +import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; import org.openhab.binding.lgthinq.lgservices.model.*; /** diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java index 3f199d93708b4..d2664f14f8a0b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java @@ -12,7 +12,8 @@ */ package org.openhab.binding.lgthinq.lgservices; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.Map; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java index e29828a67676c..b5794be455c9c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java @@ -12,7 +12,9 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.ac; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java index ee081b179603f..fef8b31709a8f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java @@ -21,11 +21,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; -import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; -import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java index ce10fa50f8468..672caea04be9b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java @@ -12,7 +12,10 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java index 18db4b80da8b8..3ec618a937165 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java @@ -12,7 +12,8 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.DW_POWER_OFF_VALUE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.DW_STATE_COMPLETE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java index 0d625b4f5fba1..1acc27a991972 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java @@ -18,7 +18,10 @@ import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DefaultSnapshotBuilder; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; /** * The {@link DishWasherSnapshotBuilder} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index f0eb6613941b7..087045461844b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -14,7 +14,9 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java index 94f2af41c8e7f..1696c2d4df65a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -12,18 +12,17 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; -import java.util.*; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; -import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java index 43c4fe8b38c91..c93493474992f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java @@ -17,11 +17,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; -import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; -import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java index 3937aa480e0b4..12a7642d7b2d0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java @@ -37,7 +37,9 @@ import org.openhab.binding.lgthinq.internal.api.RestUtils; import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.handler.LGThinQBridgeHandler; -import org.openhab.binding.lgthinq.lgservices.*; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; +import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java index db9d334471937..22614b9ce2f18 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java @@ -12,8 +12,6 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.junit.jupiter.api.Assertions.*; - import org.junit.jupiter.api.Test; import org.openhab.core.library.types.DateTimeType; From 0b68e25f14877b5691058c1e541be8b7b1ae2e1a Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 29 Jul 2024 22:32:49 -0300 Subject: [PATCH 103/130] [lgthinq][fix] Added Washing state in Washing Machine; removed some unused methods Signed-off-by: nemerdaud --- .../internal/LGThinQBindingConstants.java | 6 ++++- .../model/DefaultSnapshotBuilder.java | 3 --- .../model/DeviceRegistryService.java | 21 ------------------ .../model/devices/ac/ACSnapshotBuilder.java | 19 ---------------- .../dishwasher/DishWasherSnapshotBuilder.java | 14 ------------ .../devices/fridge/FridgeSnapshotBuilder.java | 14 ------------ .../AbstractWasherDryerCapabilityFactory.java | 5 ++++- .../WasherDryerSnapshotBuilder.java | 22 ------------------- 8 files changed, 9 insertions(+), 95 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 584d02d75ca15..9f9e7cdbd3cc2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -292,6 +292,8 @@ public class LGThinQBindingConstants { public static final String WM_CHANNEL_REMAIN_TIME_ID = "remain-time"; public static final String WM_CHANNEL_DELAY_TIME_ID = "delay-time"; + public static final String WM_LOST_WASHING_STATE_VALUE = "@WM_STATE_WASHING_W"; + public static final String WM_LOST_WASHING_STATE_KEY = "WASHING"; public static final Map CAP_WDM_STATE = Map.ofEntries(entry("@WM_STATE_POWER_OFF_W", "Off"), entry("@WM_STATE_INITIAL_W", "Initial"), entry("@WM_STATE_PAUSE_W", "Pause"), entry("@WM_STATE_RESERVE_W", "Reserved"), entry("@WM_STATE_DETECTING_W", "Detecting"), @@ -305,7 +307,9 @@ public class LGThinQBindingConstants { entry("@WM_STATE_FROZEN_PREVENT_INITIAL_W", "Frozen Preventing"), entry("@FROZEN_PREVENT_PAUSE", "Frozen Preventing Paused"), entry("@FROZEN_PREVENT_RUNNING", "Frozen Preventing Running"), entry("@AUDIBLE_DIAGNOSIS", "Diagnosing"), - entry("@WM_STATE_ERROR_W", "Error")); + entry("@WM_STATE_ERROR_W", "Error"), + // This last one is not defined in the cap file + entry(WM_LOST_WASHING_STATE_VALUE, "Washing")); public static final Map CAP_WDM_PROCESS_STATE = Map.ofEntries( entry("@WM_STATE_DETECTING_W", "Detecting"), entry("@WM_STATE_STEAM_W", "Steam"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java index 9e4251e802d04..cb0cefaa1ca95 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java @@ -149,7 +149,6 @@ public S createFromJson(Map deviceSettings, CapabilityDefinition if (snapMap == null) { throw new LGThinqApiException("snapshot node not present in device monitoring result."); } - LGAPIVerion version = discoveryAPIVersion(snapMap, type); return getSnapshot(snapMap, capDef); } @@ -163,8 +162,6 @@ protected DeviceTypes getDeviceType(Map rootMap) { return DeviceTypes.fromDeviceTypeId(deviceTypeId, deviceCode); } - protected abstract LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type); - /** * Used * diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java deleted file mode 100644 index 67fc36261b145..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceRegistryService.java +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.lgservices.model; - -/** - * The {@link DeviceRegistryService} - * - * @author Nemer Daud - Initial contribution - */ -public class DeviceRegistryService { -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java index f9c65f54b4b10..7a9a4889e16e0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java @@ -50,23 +50,4 @@ protected ACCanonicalSnapshot getSnapshot(Map snapMap, Capabilit throw new IllegalStateException("Snapshot for device type " + capDef.getDeviceType() + " not supported for this builder. It most likely a bug"); } - - @Override - protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { - switch (type) { - case AIR_CONDITIONER: - case HEAT_PUMP: - if (snapMap.containsKey("airState.opMode")) { - return LGAPIVerion.V2_0; - } else if (snapMap.containsKey("OpMode")) { - return LGAPIVerion.V1_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); - } - default: - throw new IllegalStateException("Discovery version for device type " + type - + " not supported for this builder. It most likely a bug"); - } - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java index 1acc27a991972..ba44dfa8e6fc8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java @@ -58,18 +58,4 @@ protected DishWasherSnapshot getSnapshot(Map snapMap, Capability String.format("Version %s for DishWasher is not supported.", version)); } } - - @Override - protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { - if (type == DeviceTypes.DISH_WASHER) { - if (snapMap.containsKey(DW_SNAPSHOT_WASHER_DRYER_NODE_V2)) { - return LGAPIVerion.V2_0; - } else { - throw new IllegalStateException( - "DishWasher supports only Thinq V1. Can't find key node attributes to determine DishWasher V2 API."); - } - } - throw new IllegalStateException( - "Discovery version for device type " + type + " not supported for this builder. It most likely a bug"); - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java index 222a01240eacf..ae2ee08e2fc9d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java @@ -63,18 +63,4 @@ protected FridgeCanonicalSnapshot getSnapshot(Map snapMap, Capab throw new IllegalStateException("Snapshot for device type " + capDef.getDeviceType() + " not supported for this builder. It most likely a bug"); } - - @Override - protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { - if (REFRIGERATOR.equals(type)) { - if (snapMap.containsKey(REFRIGERATOR_SNAPSHOT_NODE_V2)) { - return LGAPIVerion.V2_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine ACCapability API version."); - } - } - throw new IllegalStateException( - "Unexpected capability. The type " + type + " is not supported by this builder. It most likely a bug"); - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java index 11ca90afa91fb..dca883b165434 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java @@ -22,6 +22,7 @@ import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseType; @@ -87,7 +88,9 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { throw new LGThinqException("MonitoringValue node not found in the V2 WashingDryer cap definition."); } // mapping possible states - wdCap.setState(newFeatureDefinition(getStateFeatureNodeName(), monitorValueNode)); + FeatureDefinition fd = newFeatureDefinition(getStateFeatureNodeName(), monitorValueNode); + fd.getValuesMapping().put(WM_LOST_WASHING_STATE_KEY, WM_LOST_WASHING_STATE_VALUE); + wdCap.setState(fd); wdCap.setProcessState(newFeatureDefinition(getProcessStateNodeName(), monitorValueNode)); // --- Selectable features ----- wdCap.setRinseFeat(newFeatureDefinition(getRinseFeatureNodeName(), monitorValueNode, diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java index 3f133ddacb0d9..79e3bd4986fcd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java @@ -101,26 +101,4 @@ private static void setAltCourseNodeName(CapabilityDefinition capDef, WasherDrye snap.setSmartCourse(Objects.requireNonNullElse((String) washerDryerMap.get(altSmartCourseNodeName), "")); } } - - @Override - protected LGAPIVerion discoveryAPIVersion(Map snapMap, DeviceTypes type) { - switch (type) { - case DRYER_TOWER: - case DRYER: - return LGAPIVerion.V2_0; - case WASHING_TOWER: - case WASHERDRYER_MACHINE: - if (snapMap.containsKey(WM_SNAPSHOT_WASHER_DRYER_NODE_V2)) { - return LGAPIVerion.V2_0; - } else if (snapMap.containsKey("State")) { - return LGAPIVerion.V1_0; - } else { - throw new IllegalStateException( - "Unexpected error. Can't find key node attributes to determine WASHERDRYER_MACHINE API version."); - } - default: - throw new IllegalStateException("Discovery version for device type " + type - + " not supported for this builder. It most likely a bug"); - } - } } From d7c4875993e517c2018fdb37306d26c8597e4ed8 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Tue, 30 Jul 2024 10:31:14 -0300 Subject: [PATCH 104/130] [lgthinq][fix] remove dyn channel when it's not available in AC. Signed-off-by: nemerdaud --- .../handler/LGThinQAbstractDeviceHandler.java | 10 +++++++ .../handler/LGThinQAirConditionerHandler.java | 28 +++++++------------ 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 5b7cbe2b4abbd..3aa367e43e9ea 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -795,4 +795,14 @@ protected Channel createDynChannel(String channelNameAndTypeName, ChannelUID cha return channel; } } + + protected void manageDynChannel(ChannelUID channelUid, String channelName, String itemType, + boolean isFeatureAvailable) { + Channel chan = getThing().getChannel(channelUid); + if (chan == null && isFeatureAvailable) { + createDynChannel(channelName, channelUid, itemType); + } else if (chan != null && (!isFeatureAvailable)) { + updateThing(editThing().withoutChannel(chan.getUID()).build()); + } + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 353f1ba5ea94f..049bd9f75dc9c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -214,24 +214,16 @@ protected void updateDeviceChannels(ACCanonicalSnapshot shot) { @Override public void updateChannelDynStateDescription() throws LGThinqApiException { ACCapability acCap = getCapabilities(); - if (getThing().getChannel(jetModeChannelUID) == null && acCap.isJetModeAvailable()) { - createDynSwitchChannel(CHANNEL_COOL_JET_ID, jetModeChannelUID); - } - if (getThing().getChannel(autoDryChannelUID) == null && acCap.isAutoDryModeAvailable()) { - createDynSwitchChannel(CHANNEL_AUTO_DRY_ID, autoDryChannelUID); - } - if (getThing().getChannel(airCleanChannelUID) == null && acCap.isAirCleanAvailable()) { - createDynSwitchChannel(CHANNEL_AIR_CLEAN_ID, airCleanChannelUID); - } - if (getThing().getChannel(energySavingChannelUID) == null && acCap.isEnergySavingAvailable()) { - createDynSwitchChannel(CHANNEL_ENERGY_SAVING_ID, energySavingChannelUID); - } - if (getThing().getChannel(stepUpDownChannelUID) == null && acCap.isStepUpDownAvailable()) { - createDynChannel(CHANNEL_STEP_UP_DOWN_ID, stepUpDownChannelUID, "Number"); - } - if (getThing().getChannel(stepLeftRightChannelUID) == null && acCap.isStepLeftRightAvailable()) { - createDynChannel(CHANNEL_STEP_LEFT_RIGHT_ID, stepLeftRightChannelUID, "Number"); - } + manageDynChannel(jetModeChannelUID, CHANNEL_COOL_JET_ID, "Switch", acCap.isJetModeAvailable()); + manageDynChannel(autoDryChannelUID, CHANNEL_AUTO_DRY_ID, "Switch", acCap.isAutoDryModeAvailable()); + manageDynChannel(airCleanChannelUID, CHANNEL_AIR_CLEAN_ID, "Switch", acCap.isAirCleanAvailable()); + manageDynChannel(energySavingChannelUID, CHANNEL_ENERGY_SAVING_ID, "Switch", acCap.isEnergySavingAvailable()); + manageDynChannel(stepUpDownChannelUID, CHANNEL_STEP_UP_DOWN_ID, "Number", acCap.isStepUpDownAvailable()); + manageDynChannel(stepLeftRightChannelUID, CHANNEL_STEP_LEFT_RIGHT_ID, "Number", + acCap.isStepLeftRightAvailable()); + manageDynChannel(stepLeftRightChannelUID, CHANNEL_STEP_LEFT_RIGHT_ID, "Number", + acCap.isStepLeftRightAvailable()); + if (!acCap.getFanSpeed().isEmpty()) { List options = new ArrayList<>(); acCap.getFanSpeed() From 6f4ff2531ee78780dd55a0c6bcbc174fae650e43 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Wed, 21 Aug 2024 12:00:52 -0300 Subject: [PATCH 105/130] [lgthinq][dec] encapsule dyn channel creation Signed-off-by: nemerdaud --- .../handler/LGThinQFridgeHandler.java | 25 ++++++------------- .../handler/LGThinQWasherDryerHandler.java | 2 +- 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index 047c5f75c96fd..b0e051d0acd15 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -275,18 +275,13 @@ public void onDeviceDisconnected() { @Override public void updateChannelDynStateDescription() throws LGThinqApiException { FridgeCapability cap = getCapabilities(); - if (!cap.getIcePlusMap().isEmpty() && getThing().getChannel(icePlusChannelUID) == null) { - createDynChannel(FR_CHANNEL_ICE_PLUS, icePlusChannelUID, "Switch"); - } - if (!cap.getExpressFreezeModeMap().isEmpty() && getThing().getChannel(expressFreezeModeChannelUID) == null) { - createDynChannel(FR_CHANNEL_EXPRESS_FREEZE_MODE, expressFreezeModeChannelUID, "String"); - } - if (cap.isExpressCoolModePresent() && getThing().getChannel(expressCoolModeChannelUID) == null) { - createDynChannel(FR_CHANNEL_EXPRESS_COOL_MODE, expressCoolModeChannelUID, "Switch"); - } - if (cap.isEcoFriendlyModePresent() && getThing().getChannel(vacationModeChannelUID) == null) { - createDynChannel(FR_CHANNEL_VACATION_MODE, vacationModeChannelUID, "Switch"); - } + manageDynChannel(icePlusChannelUID, FR_CHANNEL_ICE_PLUS, "Switch", !cap.getIcePlusMap().isEmpty()); + manageDynChannel(expressFreezeModeChannelUID, FR_CHANNEL_EXPRESS_FREEZE_MODE, "String", + !cap.getExpressFreezeModeMap().isEmpty()); + manageDynChannel(expressCoolModeChannelUID, FR_CHANNEL_EXPRESS_COOL_MODE, "Switch", + cap.isExpressCoolModePresent()); + manageDynChannel(vacationModeChannelUID, FR_CHANNEL_VACATION_MODE, "Switch", cap.isEcoFriendlyModePresent()); + Unit unTemp = getTemperatureUnit(getLastShot()); if (SIUnits.CELSIUS.equals(unTemp)) { loadChannelTempStateOption(cap.getFridgeTempCMap(), fridgeTempChannelUID, unTemp); @@ -296,17 +291,11 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { loadChannelTempStateOption(cap.getFreezerTempFMap(), freezerTempChannelUID, unTemp); } loadChannelStateOption(cap.getActiveSavingMap(), activeSavingChannelUID); - loadChannelStateOption(cap.getExpressFreezeModeMap(), expressFreezeModeChannelUID); - loadChannelStateOption(cap.getActiveSavingMap(), activeSavingChannelUID); - loadChannelStateOption(cap.getSmartSavingMap(), smartSavingModeChannelUID); - loadChannelStateOption(cap.getTempUnitMap(), tempUnitUID); - loadChannelStateOption(CAP_FR_FRESH_AIR_FILTER_MAP, freshAirFilterChannelUID); - loadChannelStateOption(CAP_FR_WATER_FILTER, waterFilterChannelUID); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index b2133c5350ff6..b784203e6a770 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -280,7 +280,7 @@ protected void updateDeviceChannels(WasherDryerSnapshot shot) { remoteStartEnabledChannels.addAll(dynChannels); } - } else if (remoteStartEnabledChannels.size() > 0) { + } else if (!remoteStartEnabledChannels.isEmpty()) { ThingBuilder builder = editThing().withoutChannels(remoteStartEnabledChannels); updateThing(builder.build()); remoteStartEnabledChannels.clear(); From 5ba60f14f43f6a1415e9ff4d63c28d576734eef3 Mon Sep 17 00:00:00 2001 From: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> Date: Fri, 23 Aug 2024 12:46:47 -0300 Subject: [PATCH 106/130] Update bom/openhab-addons/pom.xml Co-authored-by: lsiepel Signed-off-by: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> --- bom/openhab-addons/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml index 157c159f0a67d..e4a184c8cdc62 100644 --- a/bom/openhab-addons/pom.xml +++ b/bom/openhab-addons/pom.xml @@ -946,11 +946,6 @@ org.openhab.binding.lgtvserial ${project.version} - - org.openhab.addons.bundles - org.openhab.binding.lgthinq - ${project.version} - org.openhab.addons.bundles org.openhab.binding.lgwebos From 2e476bf7707444b34b85780a540b66c9e71eabb4 Mon Sep 17 00:00:00 2001 From: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> Date: Fri, 23 Aug 2024 17:49:03 -0300 Subject: [PATCH 107/130] Update CODEOWNERS Co-authored-by: lsiepel Signed-off-by: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> --- CODEOWNERS | 1 - 1 file changed, 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index aefc22a7defe7..455883f4a48d9 100755 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -190,7 +190,6 @@ /bundles/org.openhab.binding.lghombot/ @FluBBaOfWard /bundles/org.openhab.binding.lgthinq/ @nemerdaud /bundles/org.openhab.binding.lgtvserial/ @fa2k -/bundles/org.openhab.binding.lgthinq/ @nemerdaud /bundles/org.openhab.binding.lgwebos/ @sprehn /bundles/org.openhab.binding.lifx/ @wborn /bundles/org.openhab.binding.linky/ @clinique @lolodomo From 15799add16090b46c37390d76eee6fe39e5ae405 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Fri, 23 Aug 2024 18:05:03 -0300 Subject: [PATCH 108/130] [lgthinq][fix] Notice update Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/NOTICE | 12 ++++++++++++ .../src/main/feature/feature.xml | 2 +- .../lgthinq/internal/LGThinQHandlerFactory.java | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/NOTICE b/bundles/org.openhab.binding.lgthinq/NOTICE index 38d625e349232..443a6b2cff1bb 100644 --- a/bundles/org.openhab.binding.lgthinq/NOTICE +++ b/bundles/org.openhab.binding.lgthinq/NOTICE @@ -11,3 +11,15 @@ https://www.eclipse.org/legal/epl-2.0/. == Source Code https://github.com/openhab/openhab-addons + +== Third-party Content + +jackson +* License: Apache 2.0 License +* Project: https://github.com/FasterXML/jackson +* Source: https://github.com/FasterXML/jackson + +Wiremock +* License: Apache 2.0 License +* Project: https://github.com/wiremock/wiremock +* Source: https://github.com/wiremock/wiremock diff --git a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml index df53a90d37a9c..558227a9e3a2d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml @@ -4,7 +4,7 @@ openhab-runtime-base - openhab.tp-jackson + mvn:org.openhab.addons.bundles/org.openhab.binding.lgthinq/${project.version} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index bdfa0fffa4176..a67ccccd1ff73 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -44,7 +44,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -@Component(service = { ThingHandlerFactory.class }, configurationPid = "binding.lgthinq") +@Component(service = ThingHandlerFactory.class, configurationPid = "binding.lgthinq") public class LGThinQHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(LGThinQHandlerFactory.class); From 64d6d06af2486c4fd298202d97d8f5a5f26e0b60 Mon Sep 17 00:00:00 2001 From: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:30:18 -0300 Subject: [PATCH 109/130] [lgthinq][fix] Win File compatibility Co-authored-by: Jacob Laursen Signed-off-by: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> --- .../org/openhab/binding/lgthinq/handler/JsonUtils.java | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index b1d243ee49162..a3a94d3d20bdd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -43,16 +43,14 @@ public static T unmashallJson(String fileName) { } public static String loadJson(String fileName) { - try { - ClassLoader classLoader = JsonUtils.class.getClassLoader(); - URL fileUrl = classLoader.getResource(fileName); - if (fileUrl == null) { + ClassLoader classLoader = JsonUtils.class.getClassLoader(); + try (InputStream inputStream = classLoader.getResourceAsStream(fileName)) { + if (inputStream == null) { throw new IllegalArgumentException( "Unexpected error. It is not expected this behaviour since json test files must be present: " + fileName); } - byte[] encoded = Files.readAllBytes(new File(fileUrl.getFile()).toPath()); - return new String(encoded, StandardCharsets.UTF_8); + return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); } catch (IOException e) { throw new IllegalArgumentException( "Unexpected error. It is not expected this behaviour since json test files must be present.", e); From d5c23613beefc2743d489bf38c03ab81f789dbf1 Mon Sep 17 00:00:00 2001 From: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:31:20 -0300 Subject: [PATCH 110/130] [lgthinq][fix] Win file compatibility Co-authored-by: Jacob Laursen Signed-off-by: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> --- .../model/CapabilityFactoryTest.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java index 1e9297adf51c7..971680c37c364 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java @@ -38,18 +38,18 @@ class CapabilityFactoryTest { void create() throws IOException, LGThinqException { ClassLoader classLoader = JsonUtils.class.getClassLoader(); assertNotNull(classLoader); - URL fileUrl = classLoader.getResource("thinq-washer-v2-cap.json"); - assertNotNull(fileUrl); - File capFile = new File(fileUrl.getFile()); - JsonNode mapper = objectMapper.readTree(capFile); - WasherDryerCapability wpCap = (WasherDryerCapability) CapabilityFactory.getInstance().create(mapper, - WasherDryerCapability.class); - assertNotNull(wpCap); - assertEquals(40, wpCap.getCourses().size()); - assertTrue(wpCap.getRinseFeat().getValuesMapping().size() > 1); - assertTrue(wpCap.getSpinFeat().getValuesMapping().size() > 1); - assertTrue(wpCap.getSoilWash().getValuesMapping().size() > 1); - assertTrue(wpCap.getTemperatureFeat().getValuesMapping().size() > 1); - assertTrue(wpCap.hasDoorLook()); + try (InputStream inputStream = classLoader.getResourceAsStream("thinq-washer-v2-cap.json")) { + assertNotNull(inputStream); + JsonNode mapper = objectMapper.readTree(inputStream); + WasherDryerCapability wpCap = (WasherDryerCapability) CapabilityFactory.getInstance().create(mapper, + WasherDryerCapability.class); + assertNotNull(wpCap); + assertEquals(40, wpCap.getCourses().size()); + assertTrue(wpCap.getRinseFeat().getValuesMapping().size() > 1); + assertTrue(wpCap.getSpinFeat().getValuesMapping().size() > 1); + assertTrue(wpCap.getSoilWash().getValuesMapping().size() > 1); + assertTrue(wpCap.getTemperatureFeat().getValuesMapping().size() > 1); + assertTrue(wpCap.hasDoorLook()); + } } } From 6c7ec693ed64870f47fbdaa63d4999a2bd6237d1 Mon Sep 17 00:00:00 2001 From: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> Date: Fri, 23 Aug 2024 18:33:09 -0300 Subject: [PATCH 111/130] Update bundles/org.openhab.binding.lgthinq/README.md Co-authored-by: lsiepel Signed-off-by: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> --- bundles/org.openhab.binding.lgthinq/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index da15c65770ac6..e86f4a2baee54 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -1,6 +1,6 @@ # LG ThinQ Bridge & Things -This binding was developed to integrate de OpenHab framework to LG ThinQ API. +This binding was developed to integrate the LG ThinQ API into openHAB. The ThinQ Bridge is necessary to work as a hub/bridge to discovery and first configure the LG ThinQ devices related with the LG's user account. Then, the first thing is to create the LG ThinQ Bridge and then, it will discover all Things you have related in your LG Account. From 364876577c24bb373be957a437427197a4b9affe Mon Sep 17 00:00:00 2001 From: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> Date: Fri, 23 Aug 2024 19:08:39 -0300 Subject: [PATCH 112/130] [lgthinq][fix] name convention Co-authored-by: lsiepel Signed-off-by: Nemer Daud <37001239+nemerdaud@users.noreply.github.com> --- .../org.openhab.binding.lgthinq/src/main/feature/feature.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml index df53a90d37a9c..3f7a9f2280486 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml @@ -2,7 +2,7 @@ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features - + openhab-runtime-base openhab.tp-jackson mvn:org.openhab.addons.bundles/org.openhab.binding.lgthinq/${project.version} From fc245d3d657185695b05257d9c0cef5acfee484f Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Fri, 23 Aug 2024 19:27:33 -0300 Subject: [PATCH 113/130] [lgthinq][fix] Revisions Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 40 +-- .../internal/LGThinQBindingConstants.java | 5 - .../handler/LGThinQAbstractDeviceHandler.java | 16 +- .../main/resources/OH-INF/binding/binding.xml | 9 - .../OH-INF/i18n/devicefeatures.properties | 1 - .../resources/OH-INF/i18n/lgthinq.properties | 290 +++++++++++++++++- .../OH-INF/i18n/lgthinq_pt_BR.properties | 29 -- .../OH-INF/thing/air-conditioner.xml | 20 +- .../main/resources/OH-INF/thing/bridge.xml | 14 +- .../main/resources/OH-INF/thing/channels.xml | 26 +- .../resources/OH-INF/thing/dish-washer.xml | 12 +- .../src/main/resources/OH-INF/thing/dryer.xml | 24 +- .../main/resources/OH-INF/thing/fridge.xml | 4 +- .../main/resources/OH-INF/thing/heat-pump.xml | 22 +- .../resources/OH-INF/thing/washer-dryer.xml | 28 +- 15 files changed, 381 insertions(+), 159 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/binding/binding.xml delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/devicefeatures.properties delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index da15c65770ac6..22b328c5aceef 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -7,17 +7,21 @@ Then, the first thing is to create the LG ThinQ Bridge and then, it will discove ## Supported Things This binding support several devices from the LG ThinQ Devices V1 & V2 line. Se the table bellow: -| Device Name | Versions | Special Functions | Commands | Obs | -|-----------------|----------|------------------------------|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| Air Conditioner | V1 & V2 | Filter and Energy Monitoring | All features in LG App, except Wind Direction | | -| Dish Washer | V2 | None | None | Provide only some channels to follow the cycle | -| Dryer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | -| Washer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | -| Washer Machine | V1 & V2 | None | All features in LG App (including remote start) | | -| Dryer Machine | V1 & V2 | None | All features in LG App (including remote start) | | -| Refrigerator | V1 & V2 | None | All features in LG App | | -| Heat Pump | V1 & V2 | None | All features in LG App | | - +| Device ID | Device Name | Versions | Special Functions | Commands | Obs | +|-----------|-----------------|----------|------------------------------|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| 401 | Air Conditioner | V1 & V2 | Filter and Energy Monitoring | All features in LG App, except Wind Direction | | +| 204 | Dish Washer | V2 | None | None | Provide only some channels to follow the cycle | +| 222 | Dryer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | +| 221 | Washer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | +| 201 | Washer Machine | V1 & V2 | None | All features in LG App (including remote start) | | +| 202 | Dryer Machine | V1 & V2 | None | All features in LG App (including remote start) | | +| 101 | Refrigerator | V1 & V2 | None | All features in LG App | | +| 401HP | Heat Pump | V1 & V2 | None | All features in LG App | | + +## Bridge Thing + +This binding has a Bridge responsible for the discovery and registry of LG Things. The first step to create a thing, is to firstly add the LG Thinq Bridge, that will +connect the binding to your LG Account and after, the bridge can discovery all devices registered and you are able to add it to OpenHab Things. ## Discovery @@ -25,10 +29,8 @@ This binding bas auto-discovering for the supported devices ## Binding Configuration -![LG Bridge Configuration](doc/bridge-configuration.jpg) - -The binding is represented by a bridge (LG GatewayBridge) and you must configure the following parameters: - +The binding is configured through a bridge (LG GatewayBridge) and you must configure the following parameters: + | Bridge Parameter | Description | Obs | |----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| | User Language | More frequent languages used | If you choose other, you can fill Manual user language (only if your language was not pre-defined in this combo | @@ -37,15 +39,15 @@ The binding is represented by a bridge (LG GatewayBridge) and you must configure | Manual User Country | The acronym for the country (UK, US, BR, etc) | | | LG User name | The LG user's account (normally an email) | | | LG Password | The LG user's password | | -| LG Password | The LG user's password | | -| Pooling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | +| Polling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | ## Thing Configuration All the configurations are pre-defined by the discovery process. But you can customize some parameters to fine-tune the device's state polling process: + Polling period in seconds when the device is off: is the period that the binding wait until hit the LG API to get the latest device's state when the device is actually turned off -Polling period in seconds when the device is oon: is the period that the binding wait until hit the LG API to get the latest device's state when the device is actually turned on +Polling period in seconds when the device is on: is the period that the binding wait until hit the LG API to get the latest device's state when the device is actually turned on ## Channels @@ -185,7 +187,7 @@ OBS: some versions of this device can not support all the channels, depending on **Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart ThinQ way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: 1. **Internet Link** - if you OpenHab server doesn't have a good internet connection this binding will not work properly! In the same way, if the internet link goes down, your Things and Bridge going to be Offline as well, and you won't be able to control the devices though OpenHab until the link comes back. 2. **LG ThinQ App** - if you've already used the LG ThinQ App to control your devices and hold it constantly activated in your mobile phone, you may experience some instability because the App (and Binding) will try to lock the device in LG ThinQ API Server to get it's current state. In the app, you may see some information in the device informing that "The device is being used by other" (something like this) and in the OpenHab, the thing can go Offline for a while. -3. **Pooling time** - both Bridge and Thing use pooling strategy to get the current state information about the registered devices. Note that the Thing pooling time is internal and can't be changed (please, don't change in the source code) and the Bridge can be changed for something greater than 300 seconds, and it's recommended long pooling periods for the Bridge because the discovery process fetch a lot of information from the LG API Server, depending on the number of devices you have registered in your account. +3. **Polling time** - both Bridge and Thing use polling strategy to get the current state information about the registered devices. Note that the Thing polling time is internal and can't be changed (please, don't change in the source code) and the Bridge can be changed for something greater than 300 seconds, and it's recommended long polling periods for the Bridge because the discovery process fetch a lot of information from the LG API Server, depending on the number of devices you have registered in your account. About this last point, it's important to know that LG API is not Open & Public, i.e, only LG Official Partners with some agreement have access to their support and documentations. This binding was a hard (very hard actually) work to dig and reverse engineering in the LG's ThinQ API protocol. Because this, you must respect the hardcoded pool period to do not put your account in LG Blacklist. ## Be nice! diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 9f9e7cdbd3cc2..19fc178049113 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -127,11 +127,6 @@ public class LGThinQBindingConstants { // ====================== FRIDGE DEVICE CONSTANTS ============================= // CHANNEL IDS - // public static final String CHANNEL_MOD_OP_ID = "op_mode"; - // public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; - // public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; - // public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; - // public static final String CHANNEL_COOL_JET_ID = "cool_jet"; public static final Double FRIDGE_TEMPERATURE_IGNORE_VALUE = 255.0; public static final Double FREEZER_TEMPERATURE_IGNORE_VALUE = 255.0; public static final String FR_CHANNEL_FRIDGE_TEMP_ID = "fridge-temperature"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 3aa367e43e9ea..90b120cf5bc87 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -384,20 +384,20 @@ public void refreshStatus() { private void loadConfigurations() { isThingReconfigured = true; - if (getThing().getConfiguration().containsKey("polling-period-poweron-seconds")) { - pollingPeriodOnSeconds = ((BigDecimal) getThing().getConfiguration().get("polling-period-poweron-seconds")) + if (getThing().getConfiguration().containsKey("pollingPeriodPowerOnSeconds")) { + pollingPeriodOnSeconds = ((BigDecimal) getThing().getConfiguration().get("pollingPeriodPowerOnSeconds")) .intValue(); } - if (getThing().getConfiguration().containsKey("polling-period-poweroff-seconds")) { + if (getThing().getConfiguration().containsKey("pollingPeriodPowerOffSeconds")) { pollingPeriodOffSeconds = ((BigDecimal) getThing().getConfiguration() - .get("polling-period-poweroff-seconds")).intValue(); + .get("pollingPeriodPowerOffSeconds")).intValue(); } - if (getThing().getConfiguration().containsKey("polling-extra-info-period-seconds")) { + if (getThing().getConfiguration().containsKey("pollingExtraInfoPeriodSeconds")) { pollingExtraInfoPeriodSeconds = ((BigDecimal) getThing().getConfiguration() - .get("polling-extra-info-period-seconds")).intValue(); + .get("pollingExtraInfoPeriodSeconds")).intValue(); } - if (getThing().getConfiguration().containsKey("poll-extra-info-on-power-off")) { - pollExtraInfoOnPowerOff = (Boolean) getThing().getConfiguration().get("poll-extra-info-on-power-off"); + if (getThing().getConfiguration().containsKey("pollExtraInfoOnPowerOff")) { + pollExtraInfoOnPowerOff = (Boolean) getThing().getConfiguration().get("pollExtraInfoOnPowerOff"); } // if the periods are the same, I can define currentPeriod for polling right now. If not, I postpone to the nest // snapshot update diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/binding/binding.xml deleted file mode 100644 index 94c1032b3fb02..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/binding/binding.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - lgthinq Binding - This is the binding for lgthinq. - - diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/devicefeatures.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/devicefeatures.properties deleted file mode 100644 index bafc5d9a1a836..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/devicefeatures.properties +++ /dev/null @@ -1 +0,0 @@ -x=1 diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index 233bde0b5b4c4..c3b315cb837f2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -1,23 +1,292 @@ -# binding -binding.lgthinq.name = LG Thinq Binding -binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 & v2) +# add-on + +addon.lgthinq.name = LG ThinQ Binding +addon.lgthinq.description = Controlling LG ThinQ enabled devices # thing types -thing-type.lgthinq.401.label = LG Thinq Air Conditioner -thing-type.lgthinq.401.description = LG Thinq Air Conditioner V1 & V2 + +thing-type.lgthinq.101.label = LG ThinQ Fridge +thing-type.lgthinq.101.description = LG ThinQ Fridge +thing-type.lgthinq.201.label = LG ThinQ Washer +thing-type.lgthinq.201.description = LG ThinQ Washing Machine +thing-type.lgthinq.202.label = LG ThinQ Dryer +thing-type.lgthinq.202.description = LG ThinQ Dryer +thing-type.lgthinq.204.label = LG ThinQ Dish Washer +thing-type.lgthinq.204.description = LG ThinQ Dish Washer +thing-type.lgthinq.221.label = LG ThinQ Washer Tower +thing-type.lgthinq.221.description = LG ThinQ Washing Tower +thing-type.lgthinq.221.label = LG ThinQ Washer Tower +thing-type.lgthinq.221.description = LG ThinQ Washing Tower +thing-type.lgthinq.222.label = LG ThinQ Dryer Tower +thing-type.lgthinq.222.description = LG ThinQ Dryer Tower +thing-type.lgthinq.401.label = LG ThinQ Air Conditioner +thing-type.lgthinq.401.description = LG ThinQ Air Conditioner +thing-type.lgthinq.401HP.label = LG ThinQ Heat Pump +thing-type.lgthinq.401HP.description = LG ThinQ Heat Pump +thing-type.lgthinq.bridge.label = LG ThinQ GatewayBridge +thing-type.lgthinq.bridge.description = A connection to a LGThinQ Gateway + +# thing types config + +thing-type.config.lgthinq.201.group.Settings.label = Polling +thing-type.config.lgthinq.201.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.201.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.201.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.201.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.201.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.202.group.Settings.label = Polling +thing-type.config.lgthinq.202.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.202.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.202.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.202.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.202.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.204.group.Settings.label = Polling +thing-type.config.lgthinq.204.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.204.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.204.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.204.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.204.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.221.group.Settings.label = Polling +thing-type.config.lgthinq.221.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.221.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.221.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.221.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.221.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.222.group.Settings.label = Polling +thing-type.config.lgthinq.222.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.222.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.222.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.222.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.222.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.401.group.Settings.label = Polling +thing-type.config.lgthinq.401.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.401.pollExtraInfoOnPowerOff.label = Enable Extra Info Polling on powered off device +thing-type.config.lgthinq.401.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. +thing-type.config.lgthinq.401.pollingExtraInfoPeriodSeconds.label = Polling period in seconds for Extra Info +thing-type.config.lgthinq.401.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) +thing-type.config.lgthinq.401.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.401.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.401.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.401.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.401HP.group.Settings.label = Polling +thing-type.config.lgthinq.401HP.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.401HP.pollExtraInfoOnPowerOff.label = Enable Extra Info Polling on powered off device +thing-type.config.lgthinq.401HP.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. +thing-type.config.lgthinq.401HP.pollingExtraInfoPeriodSeconds.label = Polling period in seconds for Extra Info +thing-type.config.lgthinq.401HP.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) +thing-type.config.lgthinq.401HP.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.401HP.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.401HP.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.401HP.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.bridge.alternativeServer.label = Alternative Gateway Server +thing-type.config.lgthinq.bridge.alternativeServer.description = Only used for proxy/test gateway server. +thing-type.config.lgthinq.bridge.country.label = User Country (LG registry) +thing-type.config.lgthinq.bridge.country.description = The User Country registered in LG Account +thing-type.config.lgthinq.bridge.country.option.US = United States +thing-type.config.lgthinq.bridge.country.option.UK = United Kingdom +thing-type.config.lgthinq.bridge.country.option.BE = Belgium +thing-type.config.lgthinq.bridge.country.option.BR = Brazil +thing-type.config.lgthinq.bridge.country.option.IT = Italy +thing-type.config.lgthinq.bridge.country.option.LU = Luxembourg +thing-type.config.lgthinq.bridge.country.option.PL = Poland +thing-type.config.lgthinq.bridge.country.option.PT = Portugal +thing-type.config.lgthinq.bridge.country.option.DE = Germany +thing-type.config.lgthinq.bridge.country.option.DK = Denmark +thing-type.config.lgthinq.bridge.country.option.NO = Norway +thing-type.config.lgthinq.bridge.country.option.-- = Other +thing-type.config.lgthinq.bridge.language.label = User Language (LG registry) +thing-type.config.lgthinq.bridge.language.description = The User Language registered in LG Account +thing-type.config.lgthinq.bridge.language.option.en-US = American English +thing-type.config.lgthinq.bridge.language.option.nl-BE = Belgium Dutch +thing-type.config.lgthinq.bridge.language.option.en-GB = British English +thing-type.config.lgthinq.bridge.language.option.pt-BR = Brazilian Portuguese +thing-type.config.lgthinq.bridge.language.option.it-IT = Italian +thing-type.config.lgthinq.bridge.language.option.de-LU = Luxembourg German +thing-type.config.lgthinq.bridge.language.option.pl-PL = Polish +thing-type.config.lgthinq.bridge.language.option.pt-PT = Portugal Portuguese +thing-type.config.lgthinq.bridge.language.option.de-DE = German (Standard) +thing-type.config.lgthinq.bridge.language.option.da-DK = Danish +thing-type.config.lgthinq.bridge.language.option.-- = Other +thing-type.config.lgthinq.bridge.manualCountry.label = Manual User Country (LG registry) +thing-type.config.lgthinq.bridge.manualCountry.description = Fill this only if selected "Other" in the Country above +thing-type.config.lgthinq.bridge.manualLanguage.label = Manual User Language (LG registry) +thing-type.config.lgthinq.bridge.manualLanguage.description = Fill this only if selected "Other" in the Language above +thing-type.config.lgthinq.bridge.password.label = Password +thing-type.config.lgthinq.bridge.password.description = Password from LG Thinq Personal Account +thing-type.config.lgthinq.bridge.poolingIntervalSec.label = Pooling Discovery Interval in Seconds (>300) (0 disabled) +thing-type.config.lgthinq.bridge.poolingIntervalSec.description = Pooling interval to discover new devices from LG Account. +thing-type.config.lgthinq.bridge.username.label = Username +thing-type.config.lgthinq.bridge.username.description = Username from LG Thinq Personal Account + +# channel group types + +channel-group-type.lgthinq.ac-dashboard.label = Dashboard +channel-group-type.lgthinq.ac-dashboard.description = This is the Displayed Information. +channel-group-type.lgthinq.ac-extended-information.label = More Information +channel-group-type.lgthinq.ac-extended-information.description = Show more information about the device. +channel-group-type.lgthinq.dr-dashboard.label = Dashboard +channel-group-type.lgthinq.dr-dashboard.description = This is the Displayed Information. +channel-group-type.lgthinq.dr-remote-start-grp.label = Remote Start Options +channel-group-type.lgthinq.dr-remote-start-grp.description = Remote Start Actions and Options. +channel-group-type.lgthinq.dw-dashboard.label = Dashboard +channel-group-type.lgthinq.dw-dashboard.description = This is the Displayed Information. +channel-group-type.lgthinq.fr-dashboard.label = Dashboard +channel-group-type.lgthinq.fr-dashboard.description = This is the Displayed Information. +channel-group-type.lgthinq.fr-extended-information.label = More Information +channel-group-type.lgthinq.fr-extended-information.description = Show more information about the device. +channel-group-type.lgthinq.hp-dashboard.label = Dashboard +channel-group-type.lgthinq.hp-dashboard.description = This is the Displayed Information. +channel-group-type.lgthinq.hp-extended-information.label = More Information +channel-group-type.lgthinq.hp-extended-information.description = Show more information about the device. +channel-group-type.lgthinq.wm-dashboard.label = Dashboard +channel-group-type.lgthinq.wm-dashboard.description = This is the Displayed Information. +channel-group-type.lgthinq.wm-remote-start-grp.label = Remote Start Options +channel-group-type.lgthinq.wm-remote-start-grp.description = Remote Start Actions and Options. # channel types + +channel-type.lgthinq.air_clean.label = Air Clean +channel-type.lgthinq.auto_dry.label = Auto Dry +channel-type.lgthinq.cool_jet.label = Cool Jet channel-type.lgthinq.current-temperature.label = Temperature -channel-type.lgthinq.current-temperature.description = Current Temperature +channel-type.lgthinq.current-temperature.description = Current temperature. +channel-type.lgthinq.current_power.label = Current Power +channel-type.lgthinq.current_power.description = Current Power Consumption (kWh) +channel-type.lgthinq.current_watts_power.label = Current Power +channel-type.lgthinq.current_watts_power.description = Current Power Consumption (W) +channel-type.lgthinq.dryer-child-lock.label = Child Lock +channel-type.lgthinq.dryer-child-lock.description = Dryer Child Lock +channel-type.lgthinq.dryer-child-lock.state.option.CHILDLOCK_OFF = Unlocked +channel-type.lgthinq.dryer-child-lock.state.option.CHILDLOCK_ON = Locked +channel-type.lgthinq.dryer-dry-level.label = Dry Level +channel-type.lgthinq.dryer-dry-level.description = Dryer Dry +channel-type.lgthinq.dryer-error.label = Error +channel-type.lgthinq.dryer-error.description = Dryer Error +channel-type.lgthinq.dryer-error.state.option.ERROR_TE4 = ERROR_TE4 +channel-type.lgthinq.dryer-error.state.option.ERROR_CE1 = ERROR_CE1 +channel-type.lgthinq.dryer-remain-time.label = Remaining Time +channel-type.lgthinq.dryer-remain-time.description = Dryer Remaining Time +channel-type.lgthinq.dryer-remain-time.state.pattern = %1$tH:%1$tM +channel-type.lgthinq.dryer-state.label = Dryer State +channel-type.lgthinq.dryer-state.description = Dryer Operation State +channel-type.lgthinq.energy_saving.label = Energy Saving +channel-type.lgthinq.extended_info_collector.label = Enable Extended Info Collector +channel-type.lgthinq.extended_info_collector.description = This switch enable collector for energy and filter consumption (if presents) channel-type.lgthinq.fan-speed.label = Fan Speed -channel-type.lgthinq.fan-speed.description = Air Conditioner Fan Speed +channel-type.lgthinq.fan-speed.description = AC Wind Strength +channel-type.lgthinq.fan-speed.state.option.2 = Lower +channel-type.lgthinq.fan-speed.state.option.3 = Low +channel-type.lgthinq.fan-speed.state.option.4 = Medium +channel-type.lgthinq.fan-speed.state.option.5 = Fast +channel-type.lgthinq.fan-speed.state.option.6 = Faster +channel-type.lgthinq.fan-speed.state.option.8 = Auto +channel-type.lgthinq.fan_step_left_right.label = Fan HDir +channel-type.lgthinq.fan_step_left_right.description = Fan Horizontal Direction +channel-type.lgthinq.fan_step_up_down.label = Fan VDir +channel-type.lgthinq.fan_step_up_down.description = Fan Vertical Direction +channel-type.lgthinq.fr-active-saving.label = Active Saving +channel-type.lgthinq.fr-active-saving.description = Active Saving +channel-type.lgthinq.fr-eco-friendly-mode.label = Vacation +channel-type.lgthinq.fr-eco-friendly-mode.description = Vacation Mode +channel-type.lgthinq.fr-express-cool-mode.label = Express Cool +channel-type.lgthinq.fr-express-cool-mode.description = Express Cool +channel-type.lgthinq.fr-express-mode.label = Express Freeze +channel-type.lgthinq.fr-express-mode.description = Express Freeze Mode +channel-type.lgthinq.fr-ice-plus.label = Ice Plus +channel-type.lgthinq.fr-ice-plus.description = Ice Plus Feature +channel-type.lgthinq.fr-smart-saving-mode.label = Smart Saving +channel-type.lgthinq.fr-smart-saving-mode.description = Smart Saving Mode +channel-type.lgthinq.fr-smart-saving-switch.label = Smart Saving +channel-type.lgthinq.fr-smart-saving-switch.description = Smart Saving +channel-type.lgthinq.fr_fresh_air_filter.label = Fresh Air Filter +channel-type.lgthinq.fr_fresh_air_filter.description = Fresh Air Filter State. +channel-type.lgthinq.fr_water_filter.label = Water Filter +channel-type.lgthinq.fr_water_filter.description = Months passed since filter has been changed. +channel-type.lgthinq.fridge-freezer-temperature.label = Freezer Setpoint Temperature +channel-type.lgthinq.fridge-freezer-temperature.description = Freezer setpoint temperature +channel-type.lgthinq.fridge-fridge-temperature.label = Fridge Setpoint Temperature +channel-type.lgthinq.fridge-fridge-temperature.description = Fridge setpoint temperature. +channel-type.lgthinq.fridge-some-door-open.label = Door Open +channel-type.lgthinq.fridge-some-door-open.description = Door status (at least one if combined fridge/freezer) +channel-type.lgthinq.fridge-some-door-open.state.option.OPEN = Open +channel-type.lgthinq.fridge-some-door-open.state.option.CLOSE = Closed +channel-type.lgthinq.fridge-temp-unit.label = Temp. Unit +channel-type.lgthinq.fridge-temp-unit.description = Temperature Unit +channel-type.lgthinq.fridge-temp-unit.state.option.CELSIUS = C +channel-type.lgthinq.fridge-temp-unit.state.option.FAHRENHEIT = F +channel-type.lgthinq.hp-air-water-switch.label = Air/Water Switch +channel-type.lgthinq.hp-air-water-switch.description = Define the Temperature Selector based on Water/Air. +channel-type.lgthinq.hp-air-water-switch.state.option.0.0 = Air Temperature +channel-type.lgthinq.hp-air-water-switch.state.option.1.0 = Leaving Water Temperature +channel-type.lgthinq.max-temperature.label = Maximum Temperature +channel-type.lgthinq.max-temperature.description = Maximum Temperature for this mode. +channel-type.lgthinq.min-temperature.label = Minimum Temperature +channel-type.lgthinq.min-temperature.description = Minimum temperature for this mode. channel-type.lgthinq.operation-mode.label = Operation Mode -channel-type.lgthinq.operation-mode.description = Air Contirioner Operation Mode +channel-type.lgthinq.operation-mode.description = AC Operation Mode +channel-type.lgthinq.operation-mode.state.option.1 = Cool +channel-type.lgthinq.operation-mode.state.option.2 = Dry +channel-type.lgthinq.operation-mode.state.option.3 = Fan +channel-type.lgthinq.remaining_filter.label = Remaining Filter +channel-type.lgthinq.remaining_filter.description = Remaining filter without need to be replaced. +channel-type.lgthinq.rs-course.label = Course to Run +channel-type.lgthinq.rs-course.description = Course +channel-type.lgthinq.rs-rinse.label = Rinse +channel-type.lgthinq.rs-rinse.description = Rinse +channel-type.lgthinq.rs-spin.label = Spin +channel-type.lgthinq.rs-spin.description = Spin Speed +channel-type.lgthinq.rs-start-stop.label = Remote Start/Stop Switch +channel-type.lgthinq.rs-start-stop.description = Remote Start/Stop +channel-type.lgthinq.rs-temperature-level.label = Temperature Level +channel-type.lgthinq.rs-temperature-level.description = Target Temperature Level +channel-type.lgthinq.target-temperature.label = Target Temperature +channel-type.lgthinq.target-temperature.description = Target temperature. +channel-type.lgthinq.washer-course.label = Washer Course +channel-type.lgthinq.washer-course.description = Washer Course +channel-type.lgthinq.washer-course.state.option.COTTON = Cotton +channel-type.lgthinq.washer-downloaded-course.label = Washer Downloaded Course +channel-type.lgthinq.washer-downloaded-course.description = Washer Downloaded Course +channel-type.lgthinq.washer-downloaded-course.state.option.COTTON = Cotton +channel-type.lgthinq.washer-rinse.label = Rinse +channel-type.lgthinq.washer-rinse.description = Rinse +channel-type.lgthinq.washer-smart-course.label = Washer Smart Course +channel-type.lgthinq.washer-smart-course.description = Washer Smart Course +channel-type.lgthinq.washer-smart-course.state.option.COTTON = Cotton +channel-type.lgthinq.washer-spin.label = Spin +channel-type.lgthinq.washer-spin.description = Spin Speed +channel-type.lgthinq.washer-state.label = Washer State +channel-type.lgthinq.washer-state.description = Washer State Operation +channel-type.lgthinq.washerdryer-course.label = Course +channel-type.lgthinq.washerdryer-course.description = Course +channel-type.lgthinq.washerdryer-delay-time.label = Delay Time +channel-type.lgthinq.washerdryer-delay-time.description = Delay Time +channel-type.lgthinq.washerdryer-door-lock.label = Door Lock +channel-type.lgthinq.washerdryer-door-lock.description = Door Lock +channel-type.lgthinq.washerdryer-door-lock.state.option.DOOR_LOCK_ON = Locked +channel-type.lgthinq.washerdryer-door-lock.state.option.DOOR_LOCK_OFF = Unlocked +channel-type.lgthinq.washerdryer-process-state.label = Process State +channel-type.lgthinq.washerdryer-process-state.description = Process State +channel-type.lgthinq.washerdryer-remain-time.label = Remaining Time +channel-type.lgthinq.washerdryer-remain-time.description = Remaining Time +channel-type.lgthinq.washerdryer-remote-start.label = Remote Start +channel-type.lgthinq.washerdryer-remote-start.description = Remote start +channel-type.lgthinq.washerdryer-stand-by.label = Standby Mode +channel-type.lgthinq.washerdryer-stand-by.description = Standby Mode +channel-type.lgthinq.washerdryer-temp-level.label = Temperature Level +channel-type.lgthinq.washerdryer-temp-level.description = Target Temperature Level + +# binding + +binding.lgthinq.name = LG Thinq Binding +binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 & v2) + +# channel types + channel-type.lgthinq.cool-jet.label = Cool Jet channel-type.lgthinq.cool-jet.description = Cool Jet Mode +# errors -# ERRORS error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). error.toke-file-corrupted = Error Openning LGThinq Token File. Try to delete it (in data directory) to the bridge automatically recreate it. error.toke-file-access-error = Error Handling Token Configuration File. @@ -26,5 +295,6 @@ error.lgapi-communication-error = Generic Error in the LG API communication proc error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. error.lgapi-getting-devices = Error getting devices from LG API in scanner process. -# OFFLINE Statuses +# offline statuses + offline.device-disconnected = Device is disconnected. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties deleted file mode 100644 index d787bd6f204e5..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq_pt_BR.properties +++ /dev/null @@ -1,29 +0,0 @@ -# binding -binding.lgthinq.name = LG Thinq Binding -binding.lgthinq.description = Binding para integra\u00E7\u00E3o OpenHab com LG Thinq API (v1 & v2) - -# thing types -thing-type.lgthinq.401.label = LG Thinq Ar Condicionado -thing-type.lgthinq.401.description = LG Thinq Ar Condicionado V1 & V2 - -# channel types -channel-type.lgthinq.current-temperature.label = Temperatura -channel-type.lgthinq.current-temperature.description = Temperatura Atual -channel-type.lgthinq.fan-speed.label = Velocidade Ar -channel-type.lgthinq.fan-speed.description = Velocidade Ar -channel-type.lgthinq.operation-mode.label = Modo de Opera\u00E7\u00E3o -channel-type.lgthinq.operation-mode.description = Modo de Opera\u00E7\u00E3o do Ar Condicionado -channel-type.lgthinq.cool-jet.label = Jato Frio -channel-type.lgthinq.cool-jet.description = Modo Jato Frio - -# ERRORS -error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). -error.toke-file-corrupted = Error Openning LGThinq Token File. Try to delete it (in data directory) to the bridge automatically recreate it. -error.toke-file-access-error = Error Handling Token Configuration File. -error.toke-refresh = Error refreshing LGThinq Token. Try to delete it (in data directory) to the bridge automatically recreate it. -error.lgapi-communication-error = Generic Error in the LG API communication process. -error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. -error.lgapi-getting-devices = Error getting devices from LG APIevice is disconnected in scanner process. - -# OFFLINE Statuses -offline.device-disconnected = Dispositivo desconectado. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml index f1650ab164327..009267c812602 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml @@ -21,11 +21,11 @@ Settings required to optimize the polling behaviour. - false + true - - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is @@ -34,23 +34,23 @@ 10 - - + Seconds to wait to the next polling for device state (dashboard channels) 10 - - + Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) 60 - - + + If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. @@ -73,7 +73,7 @@ - + Show more information about the device. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml index e33ec88472420..ff62e95d4f665 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + A connection to a LGThinQ Gateway @@ -27,7 +27,7 @@ - + The User Country registered in LG Account @@ -45,11 +45,11 @@ - + Fill this only if selected "Other" in the Language above - + Fill this only if selected "Other" in the Country above @@ -62,12 +62,12 @@ password - - Pooling interval to discover new devices from LG Account. + + Polling interval to discover new devices from LG Account (in Seconds >300 or 0 disabled). 86400 - + Only used for proxy/test gateway server. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 19ccaaa6f9529..bff99029ef672 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -22,7 +22,7 @@ Number - + Define the Temperature Selector based on Water/Air. @@ -48,17 +48,11 @@ Switch - + This switch enable collector for energy and filter consumption (if presents) Switch - - Switch - - Temperature - - Switch @@ -99,7 +93,7 @@ Number:Temperature - + Target temperature. Temperature @@ -107,7 +101,7 @@ Number:Temperature - + Minimum temperature for this mode. Temperature @@ -115,7 +109,7 @@ Number:Temperature - + Maximum Temperature for this mode. Temperature @@ -175,7 +169,7 @@ Switch - + Remote Start/Stop @@ -256,7 +250,7 @@ String - + Washer Downloaded Course @@ -266,14 +260,14 @@ String - + Target Temperature Level Temperature String - + Target Temperature Level Temperature @@ -381,7 +375,7 @@ Number:Temperature - + Freezer setpoint temperature Temperature diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml index 44a5448033a19..a58021f24f669 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml @@ -10,7 +10,7 @@ - + LG ThinQ Dish Washer @@ -19,11 +19,11 @@ Settings required to optimize the polling behaviour. - false + true - - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is @@ -32,9 +32,9 @@ 10 - - + Seconds to wait to the next polling for device state (dashboard channels) 10 diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml index 035410a3f98b9..7722c4d23d9bf 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml @@ -10,7 +10,7 @@ - + LG ThinQ Dryer @@ -20,11 +20,11 @@ Settings required to optimize the polling behaviour. - false + true - - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is @@ -33,9 +33,9 @@ 10 - - + Seconds to wait to the next polling for device state (dashboard channels) 10 @@ -47,7 +47,7 @@ - + LG ThinQ Dryer Tower @@ -58,11 +58,11 @@ Settings required to optimize the polling behaviour. - false + true - - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is @@ -71,9 +71,9 @@ 10 - - + Seconds to wait to the next polling for device state (dashboard channels) 10 diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml index 0a9d40e4623b9..994b7e0e3e487 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml @@ -10,7 +10,7 @@ - + LG ThinQ Fridge @@ -30,7 +30,7 @@ - + Show more information about the device. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml index 5bcdc2e6875c1..b4e18c3c1e3ef 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -10,7 +10,7 @@ - + LG ThinQ Heat Pump @@ -21,11 +21,11 @@ Settings required to optimize the polling behaviour. - false + true - - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is @@ -34,23 +34,23 @@ 10 - - + Seconds to wait to the next polling for device state (dashboard channels) 10 - - + Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) 60 - - + + If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. @@ -75,7 +75,7 @@ - + Show more information about the device. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml index 8bab5a16590e6..72d7d5a1dca13 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml @@ -10,7 +10,7 @@ - + LG ThinQ Washing Machine @@ -20,11 +20,11 @@ Settings required to optimize the polling behaviour. - false + true - - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is @@ -33,9 +33,9 @@ 10 - - + Seconds to wait to the next polling for device state (dashboard channels) 10 @@ -47,8 +47,8 @@ - - LG ThinQ Washing Tower + + LGThinQ Washing Tower @@ -57,11 +57,11 @@ Settings required to optimize the polling behaviour. - false + true - - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is @@ -70,9 +70,9 @@ 10 - - + Seconds to wait to the next polling for device state (dashboard channels) 10 @@ -115,7 +115,7 @@ - + LG ThinQ Washing Tower From c2a9b9a4737833ac9cf3402393be51df7dc38566 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Fri, 23 Aug 2024 19:33:31 -0300 Subject: [PATCH 114/130] [lgthinq][fix] Code Format violations Signed-off-by: nemerdaud --- .../handler/LGThinQAbstractDeviceHandler.java | 4 ++-- .../main/resources/OH-INF/thing/air-conditioner.xml | 6 ++---- .../src/main/resources/OH-INF/thing/dish-washer.xml | 6 ++---- .../src/main/resources/OH-INF/thing/dryer.xml | 12 ++++-------- .../src/main/resources/OH-INF/thing/heat-pump.xml | 6 ++---- .../src/main/resources/OH-INF/thing/washer-dryer.xml | 12 ++++-------- .../openhab/binding/lgthinq/handler/JsonUtils.java | 3 --- .../lgservices/model/CapabilityFactoryTest.java | 3 +-- 8 files changed, 17 insertions(+), 35 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 90b120cf5bc87..61c47477b9e61 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -389,8 +389,8 @@ private void loadConfigurations() { .intValue(); } if (getThing().getConfiguration().containsKey("pollingPeriodPowerOffSeconds")) { - pollingPeriodOffSeconds = ((BigDecimal) getThing().getConfiguration() - .get("pollingPeriodPowerOffSeconds")).intValue(); + pollingPeriodOffSeconds = ((BigDecimal) getThing().getConfiguration().get("pollingPeriodPowerOffSeconds")) + .intValue(); } if (getThing().getConfiguration().containsKey("pollingExtraInfoPeriodSeconds")) { pollingExtraInfoPeriodSeconds = ((BigDecimal) getThing().getConfiguration() diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml index 009267c812602..e6763e46ed8d1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml @@ -23,8 +23,7 @@ Settings required to optimize the polling behaviour. true - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your @@ -34,8 +33,7 @@ 10 - + Seconds to wait to the next polling for device state (dashboard channels) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml index a58021f24f669..2407764b442fd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml @@ -21,8 +21,7 @@ Settings required to optimize the polling behaviour. true - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your @@ -32,8 +31,7 @@ 10 - + Seconds to wait to the next polling for device state (dashboard channels) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml index 7722c4d23d9bf..ee0c6e5cfb721 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml @@ -22,8 +22,7 @@ Settings required to optimize the polling behaviour. true - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your @@ -33,8 +32,7 @@ 10 - + Seconds to wait to the next polling for device state (dashboard channels) @@ -60,8 +58,7 @@ Settings required to optimize the polling behaviour. true - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your @@ -71,8 +68,7 @@ 10 - + Seconds to wait to the next polling for device state (dashboard channels) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml index b4e18c3c1e3ef..706ea6b025a69 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -23,8 +23,7 @@ Settings required to optimize the polling behaviour. true - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your @@ -34,8 +33,7 @@ 10 - + Seconds to wait to the next polling for device state (dashboard channels) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml index 72d7d5a1dca13..fd1c6729ff6ce 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml @@ -22,8 +22,7 @@ Settings required to optimize the polling behaviour. true - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your @@ -33,8 +32,7 @@ 10 - + Seconds to wait to the next polling for device state (dashboard channels) @@ -59,8 +57,7 @@ Settings required to optimize the polling behaviour. true - + Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your @@ -70,8 +67,7 @@ 10 - + Seconds to wait to the next polling for device state (dashboard channels) diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index a3a94d3d20bdd..572c159f7dade 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -12,12 +12,9 @@ */ package org.openhab.binding.lgthinq.handler; -import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.nio.charset.StandardCharsets; -import java.nio.file.Files; import org.eclipse.jdt.annotation.NonNullByDefault; diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java index 971680c37c364..b1134d6a2097a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java @@ -14,9 +14,8 @@ import static org.junit.jupiter.api.Assertions.*; -import java.io.File; import java.io.IOException; -import java.net.URL; +import java.io.InputStream; import org.junit.jupiter.api.Test; import org.openhab.binding.lgthinq.handler.JsonUtils; From 58e95e49167992f857ecf5fe083f84ec304f2c31 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Fri, 23 Aug 2024 19:51:34 -0300 Subject: [PATCH 115/130] [lgthinq][fix] Documentation Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 31 +++++++------------ 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index 4f9e51e92a714..0d8942cab0cae 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -31,15 +31,17 @@ This binding bas auto-discovering for the supported devices The binding is configured through a bridge (LG GatewayBridge) and you must configure the following parameters: -| Bridge Parameter | Description | Obs | -|----------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| -| User Language | More frequent languages used | If you choose other, you can fill Manual user language (only if your language was not pre-defined in this combo | -| User Country | More frequent countries used | If you choose other, you can fill Manual user country (only if your country was not pre-defined in this combo | -| Manual User Language | The acronym for the language (PT, EN, IT, etc) | | -| Manual User Country | The acronym for the country (UK, US, BR, etc) | | -| LG User name | The LG user's account (normally an email) | | -| LG Password | The LG user's password | | -| Polling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time | greater than 300 seconds | +| Bridge Parameter | Label | Description | Obs | +|--------------------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| +| language | User Language | More frequent languages used | If you choose other, you can fill Manual user language (only if your language was not pre-defined in this combo | +| country | User Country | More frequent countries used | If you choose other, you can fill Manual user country (only if your country was not pre-defined in this combo | +| manualLanguage | Manual User Language | The acronym for the language (PT, EN, IT, etc) | | +| manualCountry | Manual User Country | The acronym for the country (UK, US, BR, etc) | | +| username | LG User name | The LG user's account (normally an email) | | +| password | LG Password | The LG user's password | | +| poolingIntervalSec | Polling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time greater than 300 seconds | +| alternativeServer | Alt Gateway Server | Only used if you have some proxy to the LG API Server or for Mock Tests | | + ## Thing Configuration @@ -184,14 +186,3 @@ This Channel Group is reports useful information data for the device: OBS: some versions of this device can not support all the channels, depending on the model's capabilities. -**Important:** this binding will always interact with the LG API server to get information about the device. This is the Smart ThinQ way to work, there is no other way (like direct access) to the devices. Hence, some side effects will happen in the following situations: -1. **Internet Link** - if you OpenHab server doesn't have a good internet connection this binding will not work properly! In the same way, if the internet link goes down, your Things and Bridge going to be Offline as well, and you won't be able to control the devices though OpenHab until the link comes back. -2. **LG ThinQ App** - if you've already used the LG ThinQ App to control your devices and hold it constantly activated in your mobile phone, you may experience some instability because the App (and Binding) will try to lock the device in LG ThinQ API Server to get it's current state. In the app, you may see some information in the device informing that "The device is being used by other" (something like this) and in the OpenHab, the thing can go Offline for a while. -3. **Polling time** - both Bridge and Thing use polling strategy to get the current state information about the registered devices. Note that the Thing polling time is internal and can't be changed (please, don't change in the source code) and the Bridge can be changed for something greater than 300 seconds, and it's recommended long polling periods for the Bridge because the discovery process fetch a lot of information from the LG API Server, depending on the number of devices you have registered in your account. -About this last point, it's important to know that LG API is not Open & Public, i.e, only LG Official Partners with some agreement have access to their support and documentations. This binding was a hard (very hard actually) work to dig and reverse engineering in the LG's ThinQ API protocol. Because this, you must respect the hardcoded pool period to do not put your account in LG Blacklist. - -## Be nice! -If you like the binding, why don't you support me by buying me a coffee? -It would certainly motivate me to further improve this work or to create new others cool bindings for OpenHab ! - -[![Buy me a coffee!](https://www.buymeacoffee.com/assets/img/custom_images/black_img.png)](https://www.buymeacoffee.com/nemerdaud) From ec821bcf87b7b2b102ba2b6c8263f2463af40dee Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 26 Aug 2024 08:40:00 -0300 Subject: [PATCH 116/130] [lgthinq][fix] configurations pattern review Signed-off-by: nemerdaud --- .../internal/LGThinQBindingConstants.java | 57 +++++++++---------- .../discovery/LGThinqDiscoveryService.java | 2 +- .../handler/LGThinQBridgeHandler.java | 2 +- .../handler/LGThinQDishWasherHandler.java | 2 +- .../handler/LGThinQWasherDryerHandler.java | 4 +- .../lgthinq/lgservices/model/DeviceTypes.java | 30 +++++----- .../WasherDryerSnapshotBuilder.java | 2 +- .../resources/OH-INF/i18n/lgthinq.properties | 40 ++++++------- .../OH-INF/thing/air-conditioner.xml | 16 +++--- .../main/resources/OH-INF/thing/channels.xml | 24 ++++---- .../resources/OH-INF/thing/dish-washer.xml | 2 +- .../src/main/resources/OH-INF/thing/dryer.xml | 4 +- .../main/resources/OH-INF/thing/fridge.xml | 6 +- .../main/resources/OH-INF/thing/heat-pump.xml | 12 ++-- .../resources/OH-INF/thing/washer-dryer.xml | 27 +-------- 15 files changed, 106 insertions(+), 124 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 19fc178049113..b71170a79a8d3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -38,28 +38,27 @@ public class LGThinQBindingConstants { public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); public static final String PROPERTY_VENDOR_NAME = "LG Thinq"; public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, - String.valueOf(DeviceTypes.AIR_CONDITIONER.deviceTypeId())); + DeviceTypes.AIR_CONDITIONER.thingTypeId()); public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, - String.valueOf(DeviceTypes.WASHERDRYER_MACHINE.deviceTypeId())); + DeviceTypes.WASHERDRYER_MACHINE.thingTypeId()); public static final String WM_CHANNEL_REMOTE_START_GRP_ID = "remote-start-grp"; public static final String CHANNEL_DASHBOARD_GRP_ID = "dashboard"; public static final String CHANNEL_EXTENDED_INFO_GRP_ID = "extended-information"; public static final ThingTypeUID THING_TYPE_WASHING_TOWER = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.WASHING_TOWER.deviceTypeId()); - public static final ThingTypeUID THING_TYPE_DRYER = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.DRYER.deviceTypeId()); + DeviceTypes.WASHER_TOWER.thingTypeId()); + public static final ThingTypeUID THING_TYPE_DRYER = new ThingTypeUID(BINDING_ID, DeviceTypes.DRYER.thingTypeId()); public static final ThingTypeUID THING_TYPE_HEAT_PUMP = new ThingTypeUID(BINDING_ID, - DeviceTypes.HEAT_PUMP.deviceTypeId() + "HP"); + DeviceTypes.HEAT_PUMP.thingTypeId()); public static final ThingTypeUID THING_TYPE_DRYER_TOWER = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.DRYER_TOWER.deviceTypeId()); + DeviceTypes.DRYER_TOWER.thingTypeId()); public static final ThingTypeUID THING_TYPE_FRIDGE = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.REFRIGERATOR.deviceTypeId()); + DeviceTypes.REFRIGERATOR.thingTypeId()); public static final ThingTypeUID THING_TYPE_DISHWASHER = new ThingTypeUID(BINDING_ID, - "" + DeviceTypes.DISH_WASHER.deviceTypeId()); + DeviceTypes.DISH_WASHER.thingTypeId()); public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, THING_TYPE_WASHING_MACHINE, THING_TYPE_WASHING_TOWER, THING_TYPE_DRYER_TOWER, THING_TYPE_DRYER, THING_TYPE_FRIDGE, THING_TYPE_BRIDGE, THING_TYPE_HEAT_PUMP, THING_TYPE_DISHWASHER); @@ -113,11 +112,11 @@ public class LGThinQBindingConstants { public static final String APP_TYPE = "NUTS"; public static final String APP_VER = "3.5.1200"; - public static final String DEVICE_ID = "device_id"; - public static final String MODEL_NAME = "model_name"; - public static final String DEVICE_ALIAS = "device_alias"; - public static final String MODEL_URL_INFO = "model_url_info"; - public static final String PLATFORM_TYPE = "platform_type"; + public static final String DEVICE_ID = "device-id"; + public static final String MODEL_NAME = "model-name"; + public static final String DEVICE_ALIAS = "device-alias"; + public static final String MODEL_URL_INFO = "model-url-info"; + public static final String PLATFORM_TYPE = "platform-type"; public static final String PLATFORM_TYPE_V1 = "thinq1"; public static final String PLATFORM_TYPE_V2 = "thinq2"; static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); @@ -144,8 +143,8 @@ public class LGThinQBindingConstants { public static final String FR_CHANNEL_SMART_SAVING_MODE_V2 = "fr-smart-saving-mode"; public static final String FR_CHANNEL_SMART_SAVING_SWITCH_V1 = "fr-smart-saving-switch"; public static final String FR_CHANNEL_ACTIVE_SAVING = "fr-active-saving"; - public static final String FR_CHANNEL_FRESH_AIR_FILTER = "fr_fresh_air_filter"; - public static final String FR_CHANNEL_WATER_FILTER = "fr_water_filter"; + public static final String FR_CHANNEL_FRESH_AIR_FILTER = "fr-fresh-air-filter"; + public static final String FR_CHANNEL_WATER_FILTER = "fr-water-filter"; public static final Set CELSIUS_UNIT_VALUES = Set.of("01", "1", "C", "CELSIUS", TEMP_UNIT_CELSIUS_SYMBOL); public static final Set FAHRENHEIT_UNIT_VALUES = Set.of("02", "2", "F", "FAHRENHEIT", TEMP_UNIT_FAHRENHEIT_SYMBOL); @@ -189,23 +188,23 @@ public class LGThinQBindingConstants { // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= // CHANNEL IDS - public static final String CHANNEL_MOD_OP_ID = "op_mode"; + public static final String CHANNEL_MOD_OP_ID = "op-mode"; public static final String CHANNEL_AIR_WATER_SWITCH_ID = "hp_air_water_switch"; - public static final String CHANNEL_FAN_SPEED_ID = "fan_speed"; + public static final String CHANNEL_FAN_SPEED_ID = "fan-speed"; public static final String CHANNEL_POWER_ID = "power"; - public static final String CHANNEL_EXTENDED_INFO_COLLECTOR_ID = "extended_info_collector"; - public static final String CHANNEL_CURRENT_POWER_ID = "current_power"; - public static final String CHANNEL_REMAINING_FILTER_ID = "remaining_filter"; - public static final String CHANNEL_TARGET_TEMP_ID = "target_temperature"; + public static final String CHANNEL_EXTENDED_INFO_COLLECTOR_ID = "extra_info_collector"; + public static final String CHANNEL_CURRENT_POWER_ID = "current-power"; + public static final String CHANNEL_REMAINING_FILTER_ID = "remaining-filter"; + public static final String CHANNEL_TARGET_TEMP_ID = "target-temperature"; public static final String CHANNEL_MIN_TEMP_ID = "min_temperature"; public static final String CHANNEL_MAX_TEMP_ID = "max_temperature"; - public static final String CHANNEL_CURRENT_TEMP_ID = "current_temperature"; - public static final String CHANNEL_COOL_JET_ID = "cool_jet"; - public static final String CHANNEL_AIR_CLEAN_ID = "air_clean"; - public static final String CHANNEL_AUTO_DRY_ID = "auto_dry"; - public static final String CHANNEL_ENERGY_SAVING_ID = "energy_saving"; - public static final String CHANNEL_STEP_UP_DOWN_ID = "fan_step_up_down"; - public static final String CHANNEL_STEP_LEFT_RIGHT_ID = "fan_step_left_right"; + public static final String CHANNEL_CURRENT_TEMP_ID = "current-temperature"; + public static final String CHANNEL_COOL_JET_ID = "cool-jet"; + public static final String CHANNEL_AIR_CLEAN_ID = "air-clean"; + public static final String CHANNEL_AUTO_DRY_ID = "auto-dry"; + public static final String CHANNEL_ENERGY_SAVING_ID = "energy-saving"; + public static final String CHANNEL_STEP_UP_DOWN_ID = "fan-step-up-down"; + public static final String CHANNEL_STEP_LEFT_RIGHT_ID = "fan-step-left-right"; public static final String CAP_ACHP_OP_MODE_COOL_KEY = "@AC_MAIN_OPERATION_MODE_COOL_W"; public static final String CAP_ACHP_OP_MODE_HEAT_KEY = "@AC_MAIN_OPERATION_MODE_HEAT_W"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index 762118a06b1eb..4c94e513c2e24 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -130,7 +130,7 @@ private ThingTypeUID getThingTypeUID(LGDevice device) throws LGThinqException { case AIR_CONDITIONER -> THING_TYPE_AIR_CONDITIONER; case HEAT_PUMP -> THING_TYPE_HEAT_PUMP; case WASHERDRYER_MACHINE -> THING_TYPE_WASHING_MACHINE; - case WASHING_TOWER -> THING_TYPE_WASHING_TOWER; + case WASHER_TOWER -> THING_TYPE_WASHING_TOWER; case DRYER_TOWER -> THING_TYPE_DRYER_TOWER; case DRYER -> THING_TYPE_DRYER; case REFRIGERATOR -> THING_TYPE_FRIDGE; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index 93a85456431da..b5f6f16d24153 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -176,7 +176,7 @@ public void registryListenerThing(LGThinQAbstractDeviceHandler thing) { lGDeviceRegister.put(thing.getDeviceId(), thing); // remove device from discovery list, if exists. LGDevice device = lastDevicesDiscovered.get(thing.getDeviceId()); - if (device != null) { + if (device != null && discoveryService != null) { discoveryService.removeLgDeviceDiscovery(device); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java index f69034634ba8d..93c18c635bfe4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -138,7 +138,7 @@ protected DeviceTypes getDeviceType() { if (THING_TYPE_WASHING_MACHINE.equals(getThing().getThingTypeUID())) { return DeviceTypes.WASHERDRYER_MACHINE; } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.WASHING_TOWER; + return DeviceTypes.WASHER_TOWER; } else { throw new IllegalArgumentException( "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index b784203e6a770..3b361d4fc2e48 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -292,9 +292,9 @@ protected DeviceTypes getDeviceType() { if (THING_TYPE_WASHING_MACHINE.equals(getThing().getThingTypeUID())) { return DeviceTypes.WASHERDRYER_MACHINE; } else if (THING_TYPE_WASHING_TOWER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.WASHING_TOWER; + return DeviceTypes.WASHER_TOWER; } else if (THING_TYPE_DRYER.equals(getThing().getThingTypeUID())) { - return DeviceTypes.WASHING_TOWER; + return DeviceTypes.WASHER_TOWER; } else { throw new IllegalArgumentException( "DeviceTypeUuid [" + getThing().getThingTypeUID() + "] not expected for WashingTower/Machine"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java index a1b690dda01e5..9636393b55ea6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java @@ -18,21 +18,20 @@ * @author Nemer Daud - Initial contribution */ public enum DeviceTypes { - AIR_CONDITIONER(401, "AC", ""), - - HEAT_PUMP(401, "AC", "AWHP"), - WASHERDRYER_MACHINE(201, "WM", ""), - - WASHING_TOWER(221, "WM", ""), - DRYER(202, "DR", "Dryer"), - DRYER_TOWER(222, "DR", "Dryer"), - REFRIGERATOR(101, "REF", "Fridge"), - DISH_WASHER(204, "DW", "DishWasher"), - UNKNOWN(-1, "", ""); + AIR_CONDITIONER(401, "AC", "", "air-conditioner-401"), + HEAT_PUMP(401, "AC", "AWHP", "heatpump-401HP"), + WASHERDRYER_MACHINE(201, "WM", "", "washer-201"), + WASHER_TOWER(221, "WM", "", "washer-tower-221"), + DRYER(202, "DR", "Dryer", "dryer-202"), + DRYER_TOWER(222, "DR", "Dryer", "dryer-tower-222"), + REFRIGERATOR(101, "REF", "Fridge", "fridge-101"), + DISH_WASHER(204, "DW", "DishWasher", "dishwasher-204"), + UNKNOWN(-1, "", "", ""); private final int deviceTypeId; private final String deviceTypeAcron; private final String deviceSubModel; + private final String thingTypeId; public String deviceTypeAcron() { return deviceTypeAcron; @@ -46,6 +45,10 @@ public String deviceSubModel() { return deviceSubModel; } + public String thingTypeId() { + return thingTypeId; + } + public static DeviceTypes fromDeviceTypeId(int deviceTypeId, String deviceCode) { switch (deviceTypeId) { case 401: @@ -56,7 +59,7 @@ public static DeviceTypes fromDeviceTypeId(int deviceTypeId, String deviceCode) case 201: return WASHERDRYER_MACHINE; case 221: - return WASHING_TOWER; + return WASHER_TOWER; case 202: return DRYER; case 204: @@ -91,9 +94,10 @@ public static DeviceTypes fromDeviceTypeAcron(String deviceTypeAcron, String mod } } - DeviceTypes(int i, String n, String submodel) { + DeviceTypes(int i, String n, String submodel, String thingTypeId) { this.deviceTypeId = i; this.deviceTypeAcron = n; this.deviceSubModel = submodel; + this.thingTypeId = thingTypeId; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java index 79e3bd4986fcd..4a976d50edd1a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java @@ -54,7 +54,7 @@ protected WasherDryerSnapshot getSnapshot(Map snapMap, Capabilit DeviceTypes type = capDef.getDeviceType(); LGAPIVerion version = capDef.getDeviceVersion(); switch (type) { - case WASHING_TOWER: + case WASHER_TOWER: case WASHERDRYER_MACHINE: switch (version) { case V1_0: { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index c3b315cb837f2..f255c3d47c833 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -145,15 +145,15 @@ channel-group-type.lgthinq.wm-remote-start-grp.description = Remote Start Action # channel types -channel-type.lgthinq.air_clean.label = Air Clean -channel-type.lgthinq.auto_dry.label = Auto Dry -channel-type.lgthinq.cool_jet.label = Cool Jet +channel-type.lgthinq.air-clean.label = Air Clean +channel-type.lgthinq.auto-dry.label = Auto Dry +channel-type.lgthinq.cool-jet.label = Cool Jet channel-type.lgthinq.current-temperature.label = Temperature channel-type.lgthinq.current-temperature.description = Current temperature. -channel-type.lgthinq.current_power.label = Current Power -channel-type.lgthinq.current_power.description = Current Power Consumption (kWh) -channel-type.lgthinq.current_watts_power.label = Current Power -channel-type.lgthinq.current_watts_power.description = Current Power Consumption (W) +channel-type.lgthinq.current-power.label = Current Power +channel-type.lgthinq.current-power.description = Current Power Consumption (kWh) +channel-type.lgthinq.current-watts-power.label = Current Power +channel-type.lgthinq.current-watts-power.description = Current Power Consumption (W) channel-type.lgthinq.dryer-child-lock.label = Child Lock channel-type.lgthinq.dryer-child-lock.description = Dryer Child Lock channel-type.lgthinq.dryer-child-lock.state.option.CHILDLOCK_OFF = Unlocked @@ -169,9 +169,9 @@ channel-type.lgthinq.dryer-remain-time.description = Dryer Remaining Time channel-type.lgthinq.dryer-remain-time.state.pattern = %1$tH:%1$tM channel-type.lgthinq.dryer-state.label = Dryer State channel-type.lgthinq.dryer-state.description = Dryer Operation State -channel-type.lgthinq.energy_saving.label = Energy Saving -channel-type.lgthinq.extended_info_collector.label = Enable Extended Info Collector -channel-type.lgthinq.extended_info_collector.description = This switch enable collector for energy and filter consumption (if presents) +channel-type.lgthinq.energy-saving.label = Energy Saving +channel-type.lgthinq.extra_info_collector.label = Enable Extended Info Collector +channel-type.lgthinq.extra_info_collector.description = This switch enable collector for energy and filter consumption (if presents) channel-type.lgthinq.fan-speed.label = Fan Speed channel-type.lgthinq.fan-speed.description = AC Wind Strength channel-type.lgthinq.fan-speed.state.option.2 = Lower @@ -180,10 +180,10 @@ channel-type.lgthinq.fan-speed.state.option.4 = Medium channel-type.lgthinq.fan-speed.state.option.5 = Fast channel-type.lgthinq.fan-speed.state.option.6 = Faster channel-type.lgthinq.fan-speed.state.option.8 = Auto -channel-type.lgthinq.fan_step_left_right.label = Fan HDir -channel-type.lgthinq.fan_step_left_right.description = Fan Horizontal Direction -channel-type.lgthinq.fan_step_up_down.label = Fan VDir -channel-type.lgthinq.fan_step_up_down.description = Fan Vertical Direction +channel-type.lgthinq.fan-step-left-right.label = Fan HDir +channel-type.lgthinq.fan-step-left-right.description = Fan Horizontal Direction +channel-type.lgthinq.fan-step-up-down.label = Fan VDir +channel-type.lgthinq.fan-step-up-down.description = Fan Vertical Direction channel-type.lgthinq.fr-active-saving.label = Active Saving channel-type.lgthinq.fr-active-saving.description = Active Saving channel-type.lgthinq.fr-eco-friendly-mode.label = Vacation @@ -198,10 +198,10 @@ channel-type.lgthinq.fr-smart-saving-mode.label = Smart Saving channel-type.lgthinq.fr-smart-saving-mode.description = Smart Saving Mode channel-type.lgthinq.fr-smart-saving-switch.label = Smart Saving channel-type.lgthinq.fr-smart-saving-switch.description = Smart Saving -channel-type.lgthinq.fr_fresh_air_filter.label = Fresh Air Filter -channel-type.lgthinq.fr_fresh_air_filter.description = Fresh Air Filter State. -channel-type.lgthinq.fr_water_filter.label = Water Filter -channel-type.lgthinq.fr_water_filter.description = Months passed since filter has been changed. +channel-type.lgthinq.fr-fresh-air-filter.label = Fresh Air Filter +channel-type.lgthinq.fr-fresh-air-filter.description = Fresh Air Filter State. +channel-type.lgthinq.fr-water-filter.label = Water Filter +channel-type.lgthinq.fr-water-filter.description = Months passed since filter has been changed. channel-type.lgthinq.fridge-freezer-temperature.label = Freezer Setpoint Temperature channel-type.lgthinq.fridge-freezer-temperature.description = Freezer setpoint temperature channel-type.lgthinq.fridge-fridge-temperature.label = Fridge Setpoint Temperature @@ -227,8 +227,8 @@ channel-type.lgthinq.operation-mode.description = AC Operation Mode channel-type.lgthinq.operation-mode.state.option.1 = Cool channel-type.lgthinq.operation-mode.state.option.2 = Dry channel-type.lgthinq.operation-mode.state.option.3 = Fan -channel-type.lgthinq.remaining_filter.label = Remaining Filter -channel-type.lgthinq.remaining_filter.description = Remaining filter without need to be replaced. +channel-type.lgthinq.remaining-filter.label = Remaining Filter +channel-type.lgthinq.remaining-filter.description = Remaining filter without need to be replaced. channel-type.lgthinq.rs-course.label = Course to Run channel-type.lgthinq.rs-course.description = Course channel-type.lgthinq.rs-rinse.label = Rinse diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml index e6763e46ed8d1..a282c36665704 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + @@ -63,10 +63,10 @@ This is the Displayed Information. - - - - + + + + @@ -74,9 +74,9 @@ Show more information about the device. - - - + + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index bff99029ef672..1695c19dfe498 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -12,7 +12,7 @@ - + Number:Dimensionless Remaining filter without need to be replaced. @@ -32,51 +32,51 @@ - + String Months passed since filter has been changed. - + String Fresh Air Filter State. - + Switch This switch enable collector for energy and filter consumption (if presents) Switch - + Switch Switch - + Switch Switch - + Switch Switch - + Switch Switch - + Number Fan Vertical Direction @@ -84,7 +84,7 @@ - + Number Fan Horizontal Direction @@ -115,7 +115,7 @@ - + Number:Energy Current Power Consumption (kWh) @@ -123,7 +123,7 @@ - + Number:Power Current Power Consumption (W) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml index 2407764b442fd..3edd477ab8428 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dish-washer.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml index ee0c6e5cfb721..2e1e7441935aa 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/dryer.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + @@ -41,7 +41,7 @@ - + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml index 994b7e0e3e487..818d021bb5cca 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + @@ -33,8 +33,8 @@ Show more information about the device. - - + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml index 706ea6b025a69..776f1bc7c8310 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + @@ -62,11 +62,11 @@ This is the Displayed Information. - + - - + + @@ -76,8 +76,8 @@ Show more information about the device. - - + + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml index fd1c6729ff6ce..b86e354321f9c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/washer-dryer.xml @@ -5,7 +5,7 @@ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd"> - + @@ -40,13 +40,13 @@ - + - LGThinQ Washing Tower + LGThinQ Washer Tower @@ -106,25 +106,4 @@ - - - - - - - LG ThinQ Washing Tower - - - - - - - - - - - - - - From cd6382d25a9a143da65ba90408b241e7ed539954 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 26 Aug 2024 09:06:59 -0300 Subject: [PATCH 117/130] [lgthinq][fix] documentation Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 66 ++++++++++--------- .../OH-INF/thing/air-conditioner.xml | 2 +- .../main/resources/OH-INF/thing/heat-pump.xml | 2 +- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index 0d8942cab0cae..19319598dc202 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -46,10 +46,14 @@ The binding is configured through a bridge (LG GatewayBridge) and you must confi ## Thing Configuration -All the configurations are pre-defined by the discovery process. But you can customize some parameters to fine-tune the device's state polling process: +All the configurations are pre-defined by the discovery process. But you can customize to fine-tune the device's state polling process. See the table bellow: -Polling period in seconds when the device is off: is the period that the binding wait until hit the LG API to get the latest device's state when the device is actually turned off -Polling period in seconds when the device is on: is the period that the binding wait until hit the LG API to get the latest device's state when the device is actually turned on +| Parameter | Description | Default Value | Supported Devices | +|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-------------------------------| +| Polling when off | Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. | 10 | All | +| Polling when on | Seconds to wait to the next polling for device state (dashboard channels) | 10 | All | +| Polling Info Period | Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) | 60 | Air Conditioner and Heat Pump | +| Extra Info | If enables, extra info will be fetched in the polling process even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. | Off | Air Conditioner and Heat Pump | ## Channels @@ -58,16 +62,16 @@ LG ThinQ Air Conditioners supports the following channels #### Dashboard Channels -| channel | type | description | -|--------------------|------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -| Target Temperature | Temperature | Defines the desired target temperature for the device | -| Temperature | Temperature | Read-Only channel that indicates the current temperature informed by the device | -| Fan Speed | Number (Labeled) | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | -| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | -| Power | Switch | Define the device's current power state. | -| Cool Jet | Switch | Switch Cool Jet ON/OFF | -| Auto Dry | Switch | Switch Auto Dry ON/OFF | -| Energy Saving | Switch | Switch Energy Saving ON/OFF | +| channel | type | description | +|--------------------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| Target Temperature | Number:Temperature | Defines the desired target temperature for the device | +| Temperature | Number:Temperature | Read-Only channel that indicates the current temperature informed by the device | +| Fan Speed | Number | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | +| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | +| Power | Switch | Define the device's current power state. | +| Cool Jet | Switch | Switch Cool Jet ON/OFF | +| Auto Dry | Switch | Switch Auto Dry ON/OFF | +| Energy Saving | Switch | Switch Energy Saving ON/OFF | #### More Information Channel @@ -82,15 +86,15 @@ LG ThinQ Heat Pump supports the following channels #### Dashboard Channels -| channel | type | description | -|---------------------|------------------|-----------------------------------------------------------------------------------------------------------| -| Target Temperature | Temperature | Defines the desired target temperature for the device | -| Minimum Temperature | Temperature | Minimum temperature for the current operation mode | -| Maximum Temperature | Temperature | Maximum temperature for the current operation mode | -| Temperature | Temperature | Read-Only channel that indicates the current temperature informed by the device | -| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | -| Power | Switch | Define the device's current power state. | -| Air/Water Switch | Switch | Switch the heat pump operation between Air or Water | +| channel | type | description | +|---------------------|--------------------|-----------------------------------------------------------------------------------------------------------| +| Target Temperature | Number:Temperature | Defines the desired target temperature for the device | +| Minimum Temperature | Number:Temperature | Minimum temperature for the current operation mode | +| Maximum Temperature | Number:Temperature | Maximum temperature for the current operation mode | +| Temperature | Number:Temperature | Read-Only channel that indicates the current temperature informed by the device | +| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | +| Power | Switch | Define the device's current power state. | +| Air/Water Switch | Switch | Switch the heat pump operation between Air or Water | #### More Information Channel @@ -165,15 +169,15 @@ LG ThinQ Refrigerator supports the following channels #### Dashboard Channels -| channel | type | description | -|-------------------------------|-------------|--------------------------------------------------------------------------------| -| Door Open | Contact | Advice if the door is opened | -| Freezer Set Point Temperature | Temperature | Temperature level chosen. This channel supports commands to change temperature | -| Fridge Set Point Temperature | Temperature | Temperature level chosen. This channel supports commands to change temperature | -| Temp. Unit | String | Temperature Unit (°C/F). Supports command to change the unit | -| Express Freeze | Switch | Channel to change the express freeze function (ON/OFF/Rapid) | -| Express Cool | Switch | Channel to switch ON/OFF express cool function | -| Vacation | Switch | Channel to switch ON/OFF Vacation function (unit will work in eco mode) | +| channel | type | description | +|-------------------------------|--------------------|--------------------------------------------------------------------------------| +| Door Open | Contact | Advice if the door is opened | +| Freezer Set Point Temperature | Number:Temperature | Temperature level chosen. This channel supports commands to change temperature | +| Fridge Set Point Temperature | Number:Temperature | Temperature level chosen. This channel supports commands to change temperature | +| Temp. Unit | String | Temperature Unit (°C/F). Supports command to change the unit | +| Express Freeze | Switch | Channel to change the express freeze function (ON/OFF/Rapid) | +| Express Cool | Switch | Channel to switch ON/OFF express cool function | +| Vacation | Switch | Channel to switch ON/OFF Vacation function (unit will work in eco mode) | #### More Information diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml index a282c36665704..66a778e6e41d6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml @@ -41,7 +41,7 @@ - + Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml index 776f1bc7c8310..752565e63dbd1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -41,7 +41,7 @@ - + Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) From 305ebfadd5f56b685241d5f1b90a27c81f6a2a40 Mon Sep 17 00:00:00 2001 From: nemerdaud Date: Mon, 26 Aug 2024 10:44:45 -0300 Subject: [PATCH 118/130] [lgthinq][fix] documentation and name convention Signed-off-by: nemerdaud --- bundles/org.openhab.binding.lgthinq/README.md | 158 +++++------ .../internal/LGThinQBindingConstants.java | 2 +- .../handler/LGThinQAirConditionerHandler.java | 2 +- .../model/devices/ac/ExtendedDeviceInfo.java | 15 - .../resources/OH-INF/i18n/lgthinq.properties | 259 +++++++++++------- .../main/resources/OH-INF/thing/channels.xml | 2 +- .../main/resources/OH-INF/thing/heat-pump.xml | 8 +- 7 files changed, 252 insertions(+), 194 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index 19319598dc202..500ee8cecae0b 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -58,108 +58,110 @@ All the configurations are pre-defined by the discovery process. But you can cus ## Channels ### Air Conditioner -LG ThinQ Air Conditioners supports the following channels +LG ThinQ Air Conditioners supports the following channels (for some models, some channels couldn't be available): #### Dashboard Channels -| channel | type | description | -|--------------------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| -| Target Temperature | Number:Temperature | Defines the desired target temperature for the device | -| Temperature | Number:Temperature | Read-Only channel that indicates the current temperature informed by the device | -| Fan Speed | Number | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | -| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | -| Power | Switch | Define the device's current power state. | -| Cool Jet | Switch | Switch Cool Jet ON/OFF | -| Auto Dry | Switch | Switch Auto Dry ON/OFF | -| Energy Saving | Switch | Switch Energy Saving ON/OFF | +| channel # | channel | type | description | +|---------------------|--------------------|--------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------| +| target-temperature | Target Temperature | Number:Temperature | Defines the desired target temperature for the device | +| current-temperature | Temperature | Number:Temperature | Read-Only channel that indicates the current temperature informed by the device | +| fan-speed | Fan Speed | Number | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | +| op-mode | Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | +| power | Power | Switch | Define the device's current power state. | +| cool-jet | Cool Jet | Switch | Switch Cool Jet ON/OFF | +| auto-dry | Auto Dry | Switch | Switch Auto Dry ON/OFF | +| energy-saving | Energy Saving | Switch | Switch Energy Saving ON/OFF | +| fan-step-up-down | Fan VDir | Number | Define the fan vertical direction's mode (Off, Upper, Circular, Up, Middle Up, etc) | +| fan-step-left-right | Fan HDir | Number | Define the fan horizontal direction's mode (Off, Lefter, Left, Circular, Right, etc) | #### More Information Channel -| channel | type | description | -|--------------------------------|----------------------|-----------------------------------------------------------------------------------------------------------| -| Enable Extended Info Collector | Switch | Enable/Disable the extra information collector to update the bellow channels | -| Current Power | Number:Energy | The current power consumption | -| Remaining Filter | Number:Dimensionless | Per percentage of the filter remaining | +| channel # | channel | type | description | +|----------------------|--------------------------------|----------------------|------------------------------------------------------------------------------| +| extra-info-collector | Enable Extended Info Collector | Switch | Enable/Disable the extra information collector to update the bellow channels | +| current-power | Current Power | Number:Energy | The current power consumption in Kw/h | +| remaining-filter | Remaining Filter | Number:Dimensionless | Percentage of the remaining filter | ### Heat Pump LG ThinQ Heat Pump supports the following channels #### Dashboard Channels -| channel | type | description | -|---------------------|--------------------|-----------------------------------------------------------------------------------------------------------| -| Target Temperature | Number:Temperature | Defines the desired target temperature for the device | -| Minimum Temperature | Number:Temperature | Minimum temperature for the current operation mode | -| Maximum Temperature | Number:Temperature | Maximum temperature for the current operation mode | -| Temperature | Number:Temperature | Read-Only channel that indicates the current temperature informed by the device | -| Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | -| Power | Switch | Define the device's current power state. | -| Air/Water Switch | Switch | Switch the heat pump operation between Air or Water | +| channel # | channel | type | description | +|---------------------|---------------------|--------------------|-----------------------------------------------------------------------------------------------------------| +| target-temperature | Target Temperature | Number:Temperature | Defines the desired target temperature for the device | +| min-temperature | Minimum Temperature | Number:Temperature | Minimum temperature for the current operation mode | +| max-temperature | Maximum Temperature | Number:Temperature | Maximum temperature for the current operation mode | +| current-temperature | Temperature | Number:Temperature | Read-Only channel that indicates the current temperature informed by the device | +| op-mode | Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | +| power | Power | Switch | Define the device's current power state. | +| air-water-switch | Air/Water Switch | Switch | Switch the heat pump operation between Air or Water | #### More Information Channel -| channel | type | description | -|--------------------------------|----------------------|-----------------------------------------------------------------------------------------------------------| -| Enable Extended Info Collector | Switch | Enable/Disable the extra information collector to update the bellow channels | -| Current Power | Number:Energy | The current power consumption | +| channel # | channel | type | description | +|----------------------|--------------------------------|----------------------|------------------------------------------------------------------------------| +| extra-info-collector | Enable Extended Info Collector | Switch | Enable/Disable the extra information collector to update the bellow channels | +| current-power | Current Power | Number:Energy | The current power consumption in Kw/h | ### Washer Machine LG ThinQ Washer Machine supports the following channels #### Dashboard Channels -| channel | type | description | -|-------------------|--------|------------------------------------------------------------------------| -| Washer State | String | General State of the Washer | -| Process State | String | States of the running cycle | -| Course | String | Course set up to work | -| Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | -| Door Lock | Switch | Display if the Door is Locked. | -| Rinse | String | The Rinse set program | -| Spin | String | The Spin set option | -| Delay Time | String | Delay time programmed to start the cycle | -| Remaining Time | String | Remaining time to finish the course | -| Stand By Mode | Switch | If the Washer is in stand-by-mode | -| Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | +| channel # | channel | type | description | +|-------------------|-------------------|--------|------------------------------------------------------------------------| +| state | Washer State | String | General State of the Washer | +| process-state | Process State | String | States of the running cycle | +| course | Course | String | Course set up to work | +| temperature-level | Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | +| door-lock | Door Lock | Switch | Display if the Door is Locked. | +| rinse | Rinse | String | The Rinse set program | +| spin | Spin | String | The Spin set option | +| delay-time | Delay Time | String | Delay time programmed to start the cycle | +| remain-time | Remaining Time | String | Remaining time to finish the course | +| stand-by | Stand By Mode | Switch | If the Washer is in stand-by-mode | +| remote-start-flag | Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | #### Remote Start Option This Channel Group is only available if the Washer is configured to Remote Start -| channel | type | description | -|-------------------|--------------------|---------------------------------------------------------------------------------------------------------| -| Remote Start/Stop | Switch | Switch to control if you want to start/stop the cycle remotely | -| Course to Run | String (Selection) | The pre-programmed course (or default) is shown. You can change-it if you want before remote start | -| Temperature Level | String (Selection) | The pre-programmed temperature (or default) is shown. You can change-it if you want before remote start | -| Spin | String | The pre-programmed spin (or default) is shown. You can change-it if you want before remote start | -| Rinse | String | The pre-programmed rinse (or default) is shown. You can change-it if you want before remote start | +| channel # | channel | type | description | +|----------------------|-------------------|--------------------|---------------------------------------------------------------------------------------------------------| +| rs-start-stop | Remote Start/Stop | Switch | Switch to control if you want to start/stop the cycle remotely | +| rs-course | Course to Run | String (Selection) | The pre-programmed course (or default) is shown. You can change-it if you want before remote start | +| rs-temperature-level | Temperature Level | String (Selection) | The pre-programmed temperature (or default) is shown. You can change-it if you want before remote start | +| rs-spin | Spin | String | The pre-programmed spin (or default) is shown. You can change-it if you want before remote start | +| rs-rinse | Rinse | String | The pre-programmed rinse (or default) is shown. You can change-it if you want before remote start | ### Dryer Machine LG ThinQ Dryer Machine supports the following channels #### Dashboard Channels -| channel | type | description | -|-------------------|--------|------------------------------------------------------------------------| -| Dryer State | String | General State of the Washer | -| Process State | String | States of the running cycle | -| Course | String | Course set up to work | -| Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | -| Chiel Lock | Switch | Display if the Door is Locked. | -| Dry Level Course | String | Dry level set to work in the course | -| Delay Time | String | Delay time programmed to start the cycle | -| Remaining Time | String | Remaining time to finish the course | -| Stand By Mode | Switch | If the Washer is in stand-by-mode | -| Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | +| channel # | channel | type | description | +|-------------------|-------------------|--------|------------------------------------------------------------------------| +| state | Dryer State | String | General State of the Washer | +| process-state | Process State | String | States of the running cycle | +| course | Course | String | Course set up to work | +| temperature-level | Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | +| child-lock | Child Lock | Switch | Display if the Door is Locked. | +| dry-level | Dry Level Course | String | Dry level set to work in the course | +| delay-time | Delay Time | String | Delay time programmed to start the cycle | +| remain-time | Remaining Time | String | Remaining time to finish the course | +| stand-by | Stand By Mode | Switch | If the Washer is in stand-by-mode | +| remote-start-flag | Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | #### Remote Start Option This Channel Group is only available if the Dryer is configured to Remote Start -| channel | type | description | -|-------------------|--------------------|---------------------------------------------------------------------------------------------------------| -| Remote Start/Stop | Switch | Switch to control if you want to start/stop the cycle remotely | -| Course to Run | String (Selection) | The pre-programmed course (or default) is shown. You can change-it if you want before remote start | +| channel # | channel | type | description | +|---------------|-------------------|--------------------|---------------------------------------------------------------------------------------------------------| +| rs-start-stop | Remote Start/Stop | Switch | Switch to control if you want to start/stop the cycle remotely | +| rs-course | Course to Run | String (Selection) | The pre-programmed course (or default) is shown. You can change-it if you want before remote start | ### Dryer/Washer Tower LG ThinQ Dryer/Washer is recognized as 2 different things: Dryer & Washer machines. Thus, for this device, follow the sessions for Dryer Machine and Washer Machine @@ -169,24 +171,24 @@ LG ThinQ Refrigerator supports the following channels #### Dashboard Channels -| channel | type | description | -|-------------------------------|--------------------|--------------------------------------------------------------------------------| -| Door Open | Contact | Advice if the door is opened | -| Freezer Set Point Temperature | Number:Temperature | Temperature level chosen. This channel supports commands to change temperature | -| Fridge Set Point Temperature | Number:Temperature | Temperature level chosen. This channel supports commands to change temperature | -| Temp. Unit | String | Temperature Unit (°C/F). Supports command to change the unit | -| Express Freeze | Switch | Channel to change the express freeze function (ON/OFF/Rapid) | -| Express Cool | Switch | Channel to switch ON/OFF express cool function | -| Vacation | Switch | Channel to switch ON/OFF Vacation function (unit will work in eco mode) | +| channel # | channel | type | description | +|----------------------|-------------------------------|--------------------|--------------------------------------------------------------------------------| +| some-door-open | Door Open | Contact | Advice if the door is opened | +| freezer-temperature | Freezer Set Point Temperature | Number:Temperature | Temperature level chosen. This channel supports commands to change temperature | +| fridge-temperature | Fridge Set Point Temperature | Number:Temperature | Temperature level chosen. This channel supports commands to change temperature | +| temp-unit | Temp. Unit | String | Temperature Unit (°C/F). Supports command to change the unit | +| fr-express-mode | Express Freeze | Switch | Channel to change the express freeze function (ON/OFF/Rapid) | +| fr-express-cool-mode | Express Cool | Switch | Channel to switch ON/OFF express cool function | +| fr-eco-friendly-mode | Vacation | Switch | Channel to switch ON/OFF Vacation function (unit will work in eco mode) | #### More Information This Channel Group is reports useful information data for the device: -| channel | type | description | -|------------------|-----------|------------------------------------------------------------| -| Fresh Air Filter | String | Shows the Fresh Air filter status (OFF/AUTO/POWER/REPLACE) | -| Water Filter | String | Shows the filter's used months | +| channel # | channel | type | description | +|---------------------|------------------|-----------|------------------------------------------------------------| +| fr-fresh-air-filter | Fresh Air Filter | String | Shows the Fresh Air filter status (OFF/AUTO/POWER/REPLACE) | +| fr-water-filter | Water Filter | String | Shows the filter's used months | OBS: some versions of this device can not support all the channels, depending on the model's capabilities. diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index b71170a79a8d3..0244d70605764 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -189,7 +189,7 @@ public class LGThinQBindingConstants { // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= // CHANNEL IDS public static final String CHANNEL_MOD_OP_ID = "op-mode"; - public static final String CHANNEL_AIR_WATER_SWITCH_ID = "hp_air_water_switch"; + public static final String CHANNEL_AIR_WATER_SWITCH_ID = "air-water-switch"; public static final String CHANNEL_FAN_SPEED_ID = "fan-speed"; public static final String CHANNEL_POWER_ID = "power"; public static final String CHANNEL_EXTENDED_INFO_COLLECTOR_ID = "extra_info_collector"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 049bd9f75dc9c..5cab86ff79438 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -453,7 +453,7 @@ protected void updateExtraInfoStateChannels(Map energyStateAttri updateState(currentPowerEnergyChannelUID, UnDefType.NULL); } else if (NumberUtils.isCreatable(instantPowerConsumption)) { double ip = Double.parseDouble(instantPowerConsumption); - updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.WATT)); + updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.WATT_HOUR)); } else { updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java index 73db5c0237662..c7c99b1974f90 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java @@ -14,16 +14,12 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; -import java.util.Map; - import org.apache.commons.lang3.math.NumberUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import com.fasterxml.jackson.annotation.JsonAlias; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link ExtendedDeviceInfo} containing extended information obout the device. In @@ -77,15 +73,4 @@ public Double getInstantPower() { public void setRawInstantPower(String instantPower) { this.instantPower = instantPower; } - - public static void main(String[] args) { - ExtendedDeviceInfo info = new ExtendedDeviceInfo(); - info.setFilterHoursMax("1"); - info.setFilterHoursUsed("2"); - info.setRawInstantPower("344"); - ObjectMapper m = new ObjectMapper(); - Map values = m.convertValue(info, new TypeReference<>() { - }); - System.out.println(values); - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index f255c3d47c833..3b31bb023c81c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -5,82 +5,40 @@ addon.lgthinq.description = Controlling LG ThinQ enabled devices # thing types -thing-type.lgthinq.101.label = LG ThinQ Fridge -thing-type.lgthinq.101.description = LG ThinQ Fridge -thing-type.lgthinq.201.label = LG ThinQ Washer -thing-type.lgthinq.201.description = LG ThinQ Washing Machine -thing-type.lgthinq.202.label = LG ThinQ Dryer -thing-type.lgthinq.202.description = LG ThinQ Dryer -thing-type.lgthinq.204.label = LG ThinQ Dish Washer -thing-type.lgthinq.204.description = LG ThinQ Dish Washer -thing-type.lgthinq.221.label = LG ThinQ Washer Tower -thing-type.lgthinq.221.description = LG ThinQ Washing Tower -thing-type.lgthinq.221.label = LG ThinQ Washer Tower -thing-type.lgthinq.221.description = LG ThinQ Washing Tower -thing-type.lgthinq.222.label = LG ThinQ Dryer Tower -thing-type.lgthinq.222.description = LG ThinQ Dryer Tower -thing-type.lgthinq.401.label = LG ThinQ Air Conditioner -thing-type.lgthinq.401.description = LG ThinQ Air Conditioner -thing-type.lgthinq.401HP.label = LG ThinQ Heat Pump -thing-type.lgthinq.401HP.description = LG ThinQ Heat Pump -thing-type.lgthinq.bridge.label = LG ThinQ GatewayBridge +thing-type.lgthinq.air-conditioner-401.label = LG ThinQ Air Conditioner +thing-type.lgthinq.air-conditioner-401.description = LG ThinQ Air Conditioner +thing-type.lgthinq.bridge.label = LGThinQ GW Bridge thing-type.lgthinq.bridge.description = A connection to a LGThinQ Gateway +thing-type.lgthinq.dishwasher-204.label = LGThinQ Dish Washer +thing-type.lgthinq.dishwasher-204.description = LG ThinQ Dish Washer +thing-type.lgthinq.dryer-202.label = LGThinQ Dryer +thing-type.lgthinq.dryer-202.description = LG ThinQ Dryer +thing-type.lgthinq.dryer-tower-222.label = LGThinQ DryerTower +thing-type.lgthinq.dryer-tower-222.description = LG ThinQ Dryer Tower +thing-type.lgthinq.fridge-101.label = LGThinQ Fridge +thing-type.lgthinq.fridge-101.description = LG ThinQ Fridge +thing-type.lgthinq.heatpump-401HP.label = LGThinQ Heat Pump +thing-type.lgthinq.heatpump-401HP.description = LG ThinQ Heat Pump +thing-type.lgthinq.washer-201.label = LGThinQ Washer +thing-type.lgthinq.washer-201.description = LG ThinQ Washing Machine +thing-type.lgthinq.washer-tower-221.label = LGThinQ Washer Tower +thing-type.lgthinq.washer-tower-221.description = LGThinQ Washer Tower # thing types config -thing-type.config.lgthinq.201.group.Settings.label = Polling -thing-type.config.lgthinq.201.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.201.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.201.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.201.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.201.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.202.group.Settings.label = Polling -thing-type.config.lgthinq.202.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.202.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.202.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.202.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.202.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.204.group.Settings.label = Polling -thing-type.config.lgthinq.204.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.204.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.204.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.204.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.204.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.221.group.Settings.label = Polling -thing-type.config.lgthinq.221.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.221.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.221.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.221.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.221.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.222.group.Settings.label = Polling -thing-type.config.lgthinq.222.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.222.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.222.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.222.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.222.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.401.group.Settings.label = Polling -thing-type.config.lgthinq.401.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.401.pollExtraInfoOnPowerOff.label = Enable Extra Info Polling on powered off device -thing-type.config.lgthinq.401.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. -thing-type.config.lgthinq.401.pollingExtraInfoPeriodSeconds.label = Polling period in seconds for Extra Info -thing-type.config.lgthinq.401.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) -thing-type.config.lgthinq.401.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.401.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.401.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.401.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.401HP.group.Settings.label = Polling -thing-type.config.lgthinq.401HP.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.401HP.pollExtraInfoOnPowerOff.label = Enable Extra Info Polling on powered off device -thing-type.config.lgthinq.401HP.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. -thing-type.config.lgthinq.401HP.pollingExtraInfoPeriodSeconds.label = Polling period in seconds for Extra Info -thing-type.config.lgthinq.401HP.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) -thing-type.config.lgthinq.401HP.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.401HP.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.401HP.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.401HP.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.bridge.alternativeServer.label = Alternative Gateway Server +thing-type.config.lgthinq.air-conditioner-401.group.Settings.label = Polling +thing-type.config.lgthinq.air-conditioner-401.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.air-conditioner-401.pollExtraInfoOnPowerOff.label = Extra Info +thing-type.config.lgthinq.air-conditioner-401.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. +thing-type.config.lgthinq.air-conditioner-401.pollingExtraInfoPeriodSeconds.label = Polling Info Period +thing-type.config.lgthinq.air-conditioner-401.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) +thing-type.config.lgthinq.air-conditioner-401.pollingPeriodPowerOffSeconds.label = Polling when off +thing-type.config.lgthinq.air-conditioner-401.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.air-conditioner-401.pollingPeriodPowerOnSeconds.label = Polling when on +thing-type.config.lgthinq.air-conditioner-401.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.bridge.alternativeServer.label = Alt Gateway Server thing-type.config.lgthinq.bridge.alternativeServer.description = Only used for proxy/test gateway server. -thing-type.config.lgthinq.bridge.country.label = User Country (LG registry) +thing-type.config.lgthinq.bridge.country.label = User Country thing-type.config.lgthinq.bridge.country.description = The User Country registered in LG Account thing-type.config.lgthinq.bridge.country.option.US = United States thing-type.config.lgthinq.bridge.country.option.UK = United Kingdom @@ -107,22 +65,62 @@ thing-type.config.lgthinq.bridge.language.option.pt-PT = Portugal Portuguese thing-type.config.lgthinq.bridge.language.option.de-DE = German (Standard) thing-type.config.lgthinq.bridge.language.option.da-DK = Danish thing-type.config.lgthinq.bridge.language.option.-- = Other -thing-type.config.lgthinq.bridge.manualCountry.label = Manual User Country (LG registry) +thing-type.config.lgthinq.bridge.manualCountry.label = Manual User Country thing-type.config.lgthinq.bridge.manualCountry.description = Fill this only if selected "Other" in the Country above -thing-type.config.lgthinq.bridge.manualLanguage.label = Manual User Language (LG registry) +thing-type.config.lgthinq.bridge.manualLanguage.label = Manual User Lang. thing-type.config.lgthinq.bridge.manualLanguage.description = Fill this only if selected "Other" in the Language above thing-type.config.lgthinq.bridge.password.label = Password thing-type.config.lgthinq.bridge.password.description = Password from LG Thinq Personal Account -thing-type.config.lgthinq.bridge.poolingIntervalSec.label = Pooling Discovery Interval in Seconds (>300) (0 disabled) -thing-type.config.lgthinq.bridge.poolingIntervalSec.description = Pooling interval to discover new devices from LG Account. +thing-type.config.lgthinq.bridge.poolingIntervalSec.label = Discovery Interval +thing-type.config.lgthinq.bridge.poolingIntervalSec.description = Polling interval to discover new devices from LG Account (in Seconds >300 or 0 disabled). thing-type.config.lgthinq.bridge.username.label = Username thing-type.config.lgthinq.bridge.username.description = Username from LG Thinq Personal Account +thing-type.config.lgthinq.dishwasher-204.group.Settings.label = Polling +thing-type.config.lgthinq.dishwasher-204.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.dishwasher-204.pollingPeriodPowerOffSeconds.label = Polling when off +thing-type.config.lgthinq.dishwasher-204.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.dishwasher-204.pollingPeriodPowerOnSeconds.label = Polling when on +thing-type.config.lgthinq.dishwasher-204.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.dryer-202.group.Settings.label = Polling +thing-type.config.lgthinq.dryer-202.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.dryer-202.pollingPeriodPowerOffSeconds.label = Polling when off +thing-type.config.lgthinq.dryer-202.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.dryer-202.pollingPeriodPowerOnSeconds.label = Polling when on +thing-type.config.lgthinq.dryer-202.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.dryer-tower-222.group.Settings.label = Polling +thing-type.config.lgthinq.dryer-tower-222.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.dryer-tower-222.pollingPeriodPowerOffSeconds.label = Polling when off +thing-type.config.lgthinq.dryer-tower-222.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.dryer-tower-222.pollingPeriodPowerOnSeconds.label = Polling when on +thing-type.config.lgthinq.dryer-tower-222.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.heatpump-401HP.group.Settings.label = Polling +thing-type.config.lgthinq.heatpump-401HP.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.heatpump-401HP.pollExtraInfoOnPowerOff.label = Extra Info +thing-type.config.lgthinq.heatpump-401HP.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. +thing-type.config.lgthinq.heatpump-401HP.pollingExtraInfoPeriodSeconds.label = Polling Info Period +thing-type.config.lgthinq.heatpump-401HP.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) +thing-type.config.lgthinq.heatpump-401HP.pollingPeriodPowerOffSeconds.label = Polling when off +thing-type.config.lgthinq.heatpump-401HP.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.heatpump-401HP.pollingPeriodPowerOnSeconds.label = Polling when on +thing-type.config.lgthinq.heatpump-401HP.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.washer-201.group.Settings.label = Polling +thing-type.config.lgthinq.washer-201.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.washer-201.pollingPeriodPowerOffSeconds.label = Polling when off +thing-type.config.lgthinq.washer-201.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.washer-201.pollingPeriodPowerOnSeconds.label = Polling when on +thing-type.config.lgthinq.washer-201.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.washer-tower-221.group.Settings.label = Polling +thing-type.config.lgthinq.washer-tower-221.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.washer-tower-221.pollingPeriodPowerOffSeconds.label = Polling when off +thing-type.config.lgthinq.washer-tower-221.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.washer-tower-221.pollingPeriodPowerOnSeconds.label = Polling when on +thing-type.config.lgthinq.washer-tower-221.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) # channel group types channel-group-type.lgthinq.ac-dashboard.label = Dashboard channel-group-type.lgthinq.ac-dashboard.description = This is the Displayed Information. -channel-group-type.lgthinq.ac-extended-information.label = More Information +channel-group-type.lgthinq.ac-extended-information.label = More Info channel-group-type.lgthinq.ac-extended-information.description = Show more information about the device. channel-group-type.lgthinq.dr-dashboard.label = Dashboard channel-group-type.lgthinq.dr-dashboard.description = This is the Displayed Information. @@ -132,12 +130,12 @@ channel-group-type.lgthinq.dw-dashboard.label = Dashboard channel-group-type.lgthinq.dw-dashboard.description = This is the Displayed Information. channel-group-type.lgthinq.fr-dashboard.label = Dashboard channel-group-type.lgthinq.fr-dashboard.description = This is the Displayed Information. -channel-group-type.lgthinq.fr-extended-information.label = More Information +channel-group-type.lgthinq.fr-extended-information.label = More Info channel-group-type.lgthinq.fr-extended-information.description = Show more information about the device. channel-group-type.lgthinq.hp-dashboard.label = Dashboard channel-group-type.lgthinq.hp-dashboard.description = This is the Displayed Information. -channel-group-type.lgthinq.hp-extended-information.label = More Information -channel-group-type.lgthinq.hp-extended-information.description = Show more information about the device. +channel-group-type.lgthinq.hp-extra-information.label = More Info +channel-group-type.lgthinq.hp-extra-information.description = Show more information about the device. channel-group-type.lgthinq.wm-dashboard.label = Dashboard channel-group-type.lgthinq.wm-dashboard.description = This is the Displayed Information. channel-group-type.lgthinq.wm-remote-start-grp.label = Remote Start Options @@ -148,10 +146,10 @@ channel-group-type.lgthinq.wm-remote-start-grp.description = Remote Start Action channel-type.lgthinq.air-clean.label = Air Clean channel-type.lgthinq.auto-dry.label = Auto Dry channel-type.lgthinq.cool-jet.label = Cool Jet -channel-type.lgthinq.current-temperature.label = Temperature -channel-type.lgthinq.current-temperature.description = Current temperature. channel-type.lgthinq.current-power.label = Current Power channel-type.lgthinq.current-power.description = Current Power Consumption (kWh) +channel-type.lgthinq.current-temperature.label = Temperature +channel-type.lgthinq.current-temperature.description = Current temperature. channel-type.lgthinq.current-watts-power.label = Current Power channel-type.lgthinq.current-watts-power.description = Current Power Consumption (W) channel-type.lgthinq.dryer-child-lock.label = Child Lock @@ -170,7 +168,7 @@ channel-type.lgthinq.dryer-remain-time.state.pattern = %1$tH:%1$tM channel-type.lgthinq.dryer-state.label = Dryer State channel-type.lgthinq.dryer-state.description = Dryer Operation State channel-type.lgthinq.energy-saving.label = Energy Saving -channel-type.lgthinq.extra_info_collector.label = Enable Extended Info Collector +channel-type.lgthinq.extra_info_collector.label = Info Collector channel-type.lgthinq.extra_info_collector.description = This switch enable collector for energy and filter consumption (if presents) channel-type.lgthinq.fan-speed.label = Fan Speed channel-type.lgthinq.fan-speed.description = AC Wind Strength @@ -192,17 +190,17 @@ channel-type.lgthinq.fr-express-cool-mode.label = Express Cool channel-type.lgthinq.fr-express-cool-mode.description = Express Cool channel-type.lgthinq.fr-express-mode.label = Express Freeze channel-type.lgthinq.fr-express-mode.description = Express Freeze Mode +channel-type.lgthinq.fr-fresh-air-filter.label = Fresh Air Filter +channel-type.lgthinq.fr-fresh-air-filter.description = Fresh Air Filter State. channel-type.lgthinq.fr-ice-plus.label = Ice Plus channel-type.lgthinq.fr-ice-plus.description = Ice Plus Feature channel-type.lgthinq.fr-smart-saving-mode.label = Smart Saving channel-type.lgthinq.fr-smart-saving-mode.description = Smart Saving Mode channel-type.lgthinq.fr-smart-saving-switch.label = Smart Saving channel-type.lgthinq.fr-smart-saving-switch.description = Smart Saving -channel-type.lgthinq.fr-fresh-air-filter.label = Fresh Air Filter -channel-type.lgthinq.fr-fresh-air-filter.description = Fresh Air Filter State. channel-type.lgthinq.fr-water-filter.label = Water Filter channel-type.lgthinq.fr-water-filter.description = Months passed since filter has been changed. -channel-type.lgthinq.fridge-freezer-temperature.label = Freezer Setpoint Temperature +channel-type.lgthinq.fridge-freezer-temperature.label = Freezer Temp. channel-type.lgthinq.fridge-freezer-temperature.description = Freezer setpoint temperature channel-type.lgthinq.fridge-fridge-temperature.label = Fridge Setpoint Temperature channel-type.lgthinq.fridge-fridge-temperature.description = Fridge setpoint temperature. @@ -214,13 +212,13 @@ channel-type.lgthinq.fridge-temp-unit.label = Temp. Unit channel-type.lgthinq.fridge-temp-unit.description = Temperature Unit channel-type.lgthinq.fridge-temp-unit.state.option.CELSIUS = C channel-type.lgthinq.fridge-temp-unit.state.option.FAHRENHEIT = F -channel-type.lgthinq.hp-air-water-switch.label = Air/Water Switch +channel-type.lgthinq.hp-air-water-switch.label = Air/Water channel-type.lgthinq.hp-air-water-switch.description = Define the Temperature Selector based on Water/Air. channel-type.lgthinq.hp-air-water-switch.state.option.0.0 = Air Temperature channel-type.lgthinq.hp-air-water-switch.state.option.1.0 = Leaving Water Temperature -channel-type.lgthinq.max-temperature.label = Maximum Temperature +channel-type.lgthinq.max-temperature.label = Maximum Temp. channel-type.lgthinq.max-temperature.description = Maximum Temperature for this mode. -channel-type.lgthinq.min-temperature.label = Minimum Temperature +channel-type.lgthinq.min-temperature.label = Minimum Temp. channel-type.lgthinq.min-temperature.description = Minimum temperature for this mode. channel-type.lgthinq.operation-mode.label = Operation Mode channel-type.lgthinq.operation-mode.description = AC Operation Mode @@ -235,16 +233,16 @@ channel-type.lgthinq.rs-rinse.label = Rinse channel-type.lgthinq.rs-rinse.description = Rinse channel-type.lgthinq.rs-spin.label = Spin channel-type.lgthinq.rs-spin.description = Spin Speed -channel-type.lgthinq.rs-start-stop.label = Remote Start/Stop Switch +channel-type.lgthinq.rs-start-stop.label = Remote Start/Stop channel-type.lgthinq.rs-start-stop.description = Remote Start/Stop -channel-type.lgthinq.rs-temperature-level.label = Temperature Level +channel-type.lgthinq.rs-temperature-level.label = Temp. Level channel-type.lgthinq.rs-temperature-level.description = Target Temperature Level -channel-type.lgthinq.target-temperature.label = Target Temperature +channel-type.lgthinq.target-temperature.label = Target Temp. channel-type.lgthinq.target-temperature.description = Target temperature. channel-type.lgthinq.washer-course.label = Washer Course channel-type.lgthinq.washer-course.description = Washer Course channel-type.lgthinq.washer-course.state.option.COTTON = Cotton -channel-type.lgthinq.washer-downloaded-course.label = Washer Downloaded Course +channel-type.lgthinq.washer-downloaded-course.label = Washer Download Course channel-type.lgthinq.washer-downloaded-course.description = Washer Downloaded Course channel-type.lgthinq.washer-downloaded-course.state.option.COTTON = Cotton channel-type.lgthinq.washer-rinse.label = Rinse @@ -272,9 +270,83 @@ channel-type.lgthinq.washerdryer-remote-start.label = Remote Start channel-type.lgthinq.washerdryer-remote-start.description = Remote start channel-type.lgthinq.washerdryer-stand-by.label = Standby Mode channel-type.lgthinq.washerdryer-stand-by.description = Standby Mode -channel-type.lgthinq.washerdryer-temp-level.label = Temperature Level +channel-type.lgthinq.washerdryer-temp-level.label = Temp. Level channel-type.lgthinq.washerdryer-temp-level.description = Target Temperature Level +# thing types + +thing-type.lgthinq.101.label = LG ThinQ Fridge +thing-type.lgthinq.101.description = LG ThinQ Fridge +thing-type.lgthinq.201.label = LG ThinQ Washer +thing-type.lgthinq.201.description = LG ThinQ Washing Machine +thing-type.lgthinq.202.label = LG ThinQ Dryer +thing-type.lgthinq.202.description = LG ThinQ Dryer +thing-type.lgthinq.204.label = LG ThinQ Dish Washer +thing-type.lgthinq.204.description = LG ThinQ Dish Washer +thing-type.lgthinq.221.label = LG ThinQ Washer Tower +thing-type.lgthinq.221.description = LG ThinQ Washing Tower +thing-type.lgthinq.221.label = LG ThinQ Washer Tower +thing-type.lgthinq.221.description = LG ThinQ Washing Tower +thing-type.lgthinq.222.label = LG ThinQ Dryer Tower +thing-type.lgthinq.222.description = LG ThinQ Dryer Tower +thing-type.lgthinq.401.label = LG ThinQ Air Conditioner +thing-type.lgthinq.401.description = LG ThinQ Air Conditioner +thing-type.lgthinq.401HP.label = LG ThinQ Heat Pump +thing-type.lgthinq.401HP.description = LG ThinQ Heat Pump + +# thing types config + +thing-type.config.lgthinq.201.group.Settings.label = Polling +thing-type.config.lgthinq.201.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.201.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.201.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.201.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.201.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.202.group.Settings.label = Polling +thing-type.config.lgthinq.202.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.202.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.202.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.202.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.202.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.204.group.Settings.label = Polling +thing-type.config.lgthinq.204.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.204.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.204.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.204.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.204.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.221.group.Settings.label = Polling +thing-type.config.lgthinq.221.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.221.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.221.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.221.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.221.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.222.group.Settings.label = Polling +thing-type.config.lgthinq.222.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.222.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.222.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.222.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.222.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.401.group.Settings.label = Polling +thing-type.config.lgthinq.401.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.401.pollExtraInfoOnPowerOff.label = Enable Extra Info Polling on powered off device +thing-type.config.lgthinq.401.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. +thing-type.config.lgthinq.401.pollingExtraInfoPeriodSeconds.label = Polling period in seconds for Extra Info +thing-type.config.lgthinq.401.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) +thing-type.config.lgthinq.401.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.401.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.401.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.401.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) +thing-type.config.lgthinq.401HP.group.Settings.label = Polling +thing-type.config.lgthinq.401HP.group.Settings.description = Settings required to optimize the polling behaviour. +thing-type.config.lgthinq.401HP.pollExtraInfoOnPowerOff.label = Enable Extra Info Polling on powered off device +thing-type.config.lgthinq.401HP.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. +thing-type.config.lgthinq.401HP.pollingExtraInfoPeriodSeconds.label = Polling period in seconds for Extra Info +thing-type.config.lgthinq.401HP.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) +thing-type.config.lgthinq.401HP.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off +thing-type.config.lgthinq.401HP.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. +thing-type.config.lgthinq.401HP.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on +thing-type.config.lgthinq.401HP.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) + # binding binding.lgthinq.name = LG Thinq Binding @@ -282,7 +354,6 @@ binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 # channel types -channel-type.lgthinq.cool-jet.label = Cool Jet channel-type.lgthinq.cool-jet.description = Cool Jet Mode # errors diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 1695c19dfe498..e693b1d7c21f8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -120,7 +120,7 @@ Current Power Consumption (kWh) Energy - +
diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml index 752565e63dbd1..977f65c370fe7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -15,7 +15,7 @@ - + @@ -67,17 +67,17 @@ - + - + Show more information about the device. - + From e7d68d1dbdcf4c9679a22f82a6282e2e6fbfa58a Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sun, 3 Nov 2024 21:48:52 -0300 Subject: [PATCH 119/130] [lgthinq][fix] fix code to remove warnings in complication Signed-off-by: Nemer Daud --- bundles/org.openhab.binding.lgthinq/README.md | 9 +- bundles/org.openhab.binding.lgthinq/pom.xml | 36 +- .../internal/LGThinQBindingConstants.java | 395 ++++-------------- .../internal/LGThinQBridgeConfiguration.java | 4 +- .../internal/LGThinQHandlerFactory.java | 18 +- .../LGThinQStateDescriptionProvider.java | 5 +- .../discovery/LGThinqDiscoveryService.java | 32 +- .../BaseThingWithExtraInfoHandler.java | 7 +- .../handler/LGThinQAbstractDeviceHandler.java | 189 +++++---- .../handler/LGThinQAirConditionerHandler.java | 155 ++++--- .../internal/handler/LGThinQBridge.java | 8 +- .../handler/LGThinQBridgeHandler.java | 92 ++-- .../handler/LGThinQDishWasherHandler.java | 61 +-- .../handler/LGThinQFridgeHandler.java | 168 ++++---- .../handler/LGThinQWasherDryerHandler.java | 247 ++++++----- .../internal/model/DeviceParameter.java | 26 +- .../type/ThinqChannelGroupTypeProvider.java | 6 +- .../type/ThinqChannelTypeProvider.java | 2 +- .../type/ThinqConfigDescriptionProvider.java | 2 +- .../internal/type/ThinqThingTypeProvider.java | 2 +- .../internal/type/ThinqTypesProviderImpl.java | 20 +- .../lgthinq/internal/type/UidUtils.java | 11 +- .../lgservices/LGServicesConstants.java | 245 +++++++++++ .../lgservices/LGThinQACApiClientService.java | 6 +- .../LGThinQACApiV1ClientServiceImpl.java | 25 +- .../LGThinQACApiV2ClientServiceImpl.java | 33 +- .../LGThinQAbstractApiClientService.java | 154 ++++--- .../LGThinQAbstractApiV1ClientService.java | 53 ++- .../LGThinQAbstractApiV2ClientService.java | 21 +- .../lgservices/LGThinQApiClientService.java | 28 +- .../LGThinQApiClientServiceFactory.java | 36 +- .../lgservices/LGThinQDRApiClientService.java | 2 +- .../LGThinQDRApiV2ClientServiceImpl.java | 13 +- .../LGThinQDishWasherApiClientService.java | 6 +- ...ThinQDishWasherApiV1ClientServiceImpl.java | 21 +- ...ThinQDishWasherApiV2ClientServiceImpl.java | 15 +- .../LGThinQFridgeApiClientService.java | 2 +- .../LGThinQFridgeApiV1ClientServiceImpl.java | 46 +- .../LGThinQFridgeApiV2ClientServiceImpl.java | 15 +- .../lgservices/LGThinQWMApiClientService.java | 2 +- .../LGThinQWMApiV1ClientServiceImpl.java | 16 +- .../LGThinQWMApiV2ClientServiceImpl.java | 19 +- .../api/LGThinqCanonicalModelUtil.java | 54 +++ .../lgservices/api/LGThinqGateway.java | 151 +++++++ .../api/OauthLgEmpAuthenticator.java | 387 +++++++++++++++++ .../lgthinq/lgservices/api/RestResult.java | 39 ++ .../lgthinq/lgservices/api/RestUtils.java | 173 ++++++++ .../lgthinq/lgservices/api/TokenManager.java | 175 ++++++++ .../lgthinq/lgservices/api/TokenResult.java | 106 +++++ .../lgthinq/lgservices/api/UserInfo.java | 75 ++++ .../lgservices/api/model/GatewayResult.java | 71 ++++ .../lgservices/api/model/HeaderResult.java | 39 ++ .../errors/AccountLoginException.java | 32 ++ .../errors/LGThinqApiException.java | 52 +++ .../errors/LGThinqApiExhaustionException.java | 36 ++ ...GThinqDeviceV1MonitorExpiredException.java | 37 ++ .../LGThinqDeviceV1OfflineException.java | 38 ++ .../lgservices/errors/LGThinqException.java | 36 ++ .../errors/LGThinqGatewayException.java | 32 ++ .../errors/LGThinqUnmarshallException.java | 36 ++ .../lgservices/errors/PreLoginException.java | 32 ++ .../errors/RefreshTokenException.java | 36 ++ .../lgservices/errors/TokenException.java | 32 ++ .../lgservices/model/AbstractCapability.java | 25 +- .../model/AbstractCapabilityFactory.java | 24 +- .../model/AbstractSnapshotDefinition.java | 22 +- .../model/CapabilityDefinition.java | 4 + .../lgservices/model/CapabilityFactory.java | 7 +- .../lgservices/model/CommandDefinition.java | 15 +- .../model/DefaultSnapshotBuilder.java | 36 +- .../lgservices/model/DevicePowerState.java | 19 +- .../lgthinq/lgservices/model/DeviceTypes.java | 30 +- .../lgservices/model/FeatureDataType.java | 3 + .../lgthinq/lgservices/model/LGAPIVerion.java | 3 + .../lgthinq/lgservices/model/ModelUtils.java | 13 +- .../model/MonitoringResultFormat.java | 17 +- .../lgthinq/lgservices/model/ResultCodes.java | 4 +- .../lgservices/model/SnapshotBuilder.java | 4 +- .../model/SnapshotBuilderFactory.java | 2 +- .../model/devices/ac/ACCanonicalSnapshot.java | 2 +- .../devices/ac/ACCapabilityFactoryV1.java | 8 +- .../devices/ac/ACCapabilityFactoryV2.java | 15 +- .../model/devices/ac/ACFanSpeed.java | 28 +- .../lgservices/model/devices/ac/ACOpMode.java | 2 +- .../model/devices/ac/ACSnapshotBuilder.java | 13 +- .../ac/AbstractACCapabilityFactory.java | 42 +- .../model/devices/ac/ExtendedDeviceInfo.java | 17 +- .../model/devices/commons/washers/Utils.java | 5 +- .../washers/WasherFeatureDefinition.java | 65 +++ .../AbstractDishWasherCapabilityFactory.java | 6 +- .../DishWasherCapabilityFactoryV2.java | 59 +-- .../dishwasher/DishWasherSnapshot.java | 4 +- .../dishwasher/DishWasherSnapshotBuilder.java | 8 +- .../AbstractFridgeCapabilityFactory.java | 14 +- .../fridge/FridgeCanonicalCapability.java | 20 +- .../fridge/FridgeCanonicalSnapshot.java | 13 +- .../fridge/FridgeCapabilityFactoryV1.java | 19 +- .../fridge/FridgeCapabilityFactoryV2.java | 25 +- .../devices/fridge/FridgeSnapshotBuilder.java | 23 +- .../AbstractWasherDryerCapabilityFactory.java | 22 +- .../washerdryer/WasherDryerCapability.java | 1 - .../WasherDryerCapabilityFactoryV1.java | 35 +- .../WasherDryerCapabilityFactoryV2.java | 31 +- .../washerdryer/WasherDryerSnapshot.java | 15 +- .../WasherDryerSnapshotBuilder.java | 54 +-- .../resources/OH-INF/i18n/lgthinq.properties | 110 +---- .../OH-INF/thing/air-conditioner.xml | 2 +- .../main/resources/OH-INF/thing/channels.xml | 19 +- .../main/resources/OH-INF/thing/heat-pump.xml | 2 +- .../binding/lgthinq/handler/JsonUtils.java | 1 + .../lgthinq/handler/LGThinqBridgeTests.java | 113 ++--- .../model/CapabilityFactoryTest.java | 13 +- 112 files changed, 3390 insertions(+), 1566 deletions(-) create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGServicesConstants.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/OauthLgEmpAuthenticator.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestResult.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenResult.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/UserInfo.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/model/GatewayResult.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/model/HeaderResult.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/AccountLoginException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqApiException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqApiExhaustionException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqDeviceV1MonitorExpiredException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqDeviceV1OfflineException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqGatewayException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqUnmarshallException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/PreLoginException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/RefreshTokenException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/TokenException.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/WasherFeatureDefinition.java diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index 500ee8cecae0b..586c5bfe47dfe 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -1,10 +1,11 @@ # LG ThinQ Bridge & Things -This binding was developed to integrate the LG ThinQ API into openHAB. +This binding was developed to integrate the LG ThinQ API with openHAB. The ThinQ Bridge is necessary to work as a hub/bridge to discovery and first configure the LG ThinQ devices related with the LG's user account. Then, the first thing is to create the LG ThinQ Bridge and then, it will discover all Things you have related in your LG Account. ## Supported Things + This binding support several devices from the LG ThinQ Devices V1 & V2 line. Se the table bellow: | Device ID | Device Name | Versions | Special Functions | Commands | Obs | @@ -58,6 +59,7 @@ All the configurations are pre-defined by the discovery process. But you can cus ## Channels ### Air Conditioner + LG ThinQ Air Conditioners supports the following channels (for some models, some channels couldn't be available): #### Dashboard Channels @@ -84,6 +86,7 @@ LG ThinQ Air Conditioners supports the following channels (for some models, some | remaining-filter | Remaining Filter | Number:Dimensionless | Percentage of the remaining filter | ### Heat Pump + LG ThinQ Heat Pump supports the following channels #### Dashboard Channels @@ -106,6 +109,7 @@ LG ThinQ Heat Pump supports the following channels | current-power | Current Power | Number:Energy | The current power consumption in Kw/h | ### Washer Machine + LG ThinQ Washer Machine supports the following channels #### Dashboard Channels @@ -137,6 +141,7 @@ This Channel Group is only available if the Washer is configured to Remote Start | rs-rinse | Rinse | String | The pre-programmed rinse (or default) is shown. You can change-it if you want before remote start | ### Dryer Machine + LG ThinQ Dryer Machine supports the following channels #### Dashboard Channels @@ -164,9 +169,11 @@ This Channel Group is only available if the Dryer is configured to Remote Start | rs-course | Course to Run | String (Selection) | The pre-programmed course (or default) is shown. You can change-it if you want before remote start | ### Dryer/Washer Tower + LG ThinQ Dryer/Washer is recognized as 2 different things: Dryer & Washer machines. Thus, for this device, follow the sessions for Dryer Machine and Washer Machine ### Refrigerator + LG ThinQ Refrigerator supports the following channels #### Dashboard Channels diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml index 7bd6c4a839ee4..26f6507d7d32f 100644 --- a/bundles/org.openhab.binding.lgthinq/pom.xml +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -14,24 +14,24 @@ openHAB Add-ons :: Bundles :: LG Thinq Binding - - com.fasterxml.jackson.core - jackson-core - ${jackson.version} - compile - - - com.fasterxml.jackson.core - jackson-annotations - ${jackson.version} - compile - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - compile - + + + + + + + + + + + + + + + + + + com.github.tomakehurst wiremock-jre8 diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 0244d70605764..cd5ee2cc693b2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -12,13 +12,11 @@ */ package org.openhab.binding.lgthinq.internal; -import static java.util.Map.entry; - import java.io.File; -import java.util.Map; import java.util.Set; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.LGServicesConstants; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.core.OpenHAB; import org.openhab.core.thing.ThingTypeUID; @@ -30,33 +28,26 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class LGThinQBindingConstants { +public class LGThinQBindingConstants extends LGServicesConstants { public static final String BINDING_ID = "lgthinq"; - public static final String CONFIG_DESCRIPTION_URI_CHANNEL = "channel-type:thinq:config"; + // =============== Thing Type IDs ================== public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "bridge"); - public static final String PROPERTY_VENDOR_NAME = "LG Thinq"; public static final ThingTypeUID THING_TYPE_AIR_CONDITIONER = new ThingTypeUID(BINDING_ID, DeviceTypes.AIR_CONDITIONER.thingTypeId()); public static final ThingTypeUID THING_TYPE_WASHING_MACHINE = new ThingTypeUID(BINDING_ID, DeviceTypes.WASHERDRYER_MACHINE.thingTypeId()); - public static final String WM_CHANNEL_REMOTE_START_GRP_ID = "remote-start-grp"; - public static final String CHANNEL_DASHBOARD_GRP_ID = "dashboard"; - public static final String CHANNEL_EXTENDED_INFO_GRP_ID = "extended-information"; public static final ThingTypeUID THING_TYPE_WASHING_TOWER = new ThingTypeUID(BINDING_ID, DeviceTypes.WASHER_TOWER.thingTypeId()); public static final ThingTypeUID THING_TYPE_DRYER = new ThingTypeUID(BINDING_ID, DeviceTypes.DRYER.thingTypeId()); - public static final ThingTypeUID THING_TYPE_HEAT_PUMP = new ThingTypeUID(BINDING_ID, DeviceTypes.HEAT_PUMP.thingTypeId()); - public static final ThingTypeUID THING_TYPE_DRYER_TOWER = new ThingTypeUID(BINDING_ID, DeviceTypes.DRYER_TOWER.thingTypeId()); public static final ThingTypeUID THING_TYPE_FRIDGE = new ThingTypeUID(BINDING_ID, DeviceTypes.REFRIGERATOR.thingTypeId()); - public static final ThingTypeUID THING_TYPE_DISHWASHER = new ThingTypeUID(BINDING_ID, DeviceTypes.DISH_WASHER.thingTypeId()); public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, @@ -65,315 +56,87 @@ public class LGThinQBindingConstants { public static final Set SUPPORTED_THING_TYPES = Set.of(THING_TYPE_AIR_CONDITIONER, THING_TYPE_WASHING_MACHINE, THING_TYPE_WASHING_TOWER, THING_TYPE_DRYER, THING_TYPE_DRYER_TOWER, THING_TYPE_HEAT_PUMP); - public static final String THING_STATUS_DETAIL_DISCONNECTED = "Device is Disconnected"; + + // ======== Common Channels & Constants ======== + public static final String CHANNEL_DASHBOARD_GRP_ID = "dashboard"; + public static final String CHANNEL_EXTENDED_INFO_GRP_ID = "extended-information"; + public static final String CHANNEL_EXTENDED_INFO_COLLECTOR_ID = "extra-info-collector"; // Max number of retries trying to get the monitor (V1) until consider ERROR in the connection - public static final Integer MAX_GET_MONITOR_RETRIES = 3; + public static final int MAX_GET_MONITOR_RETRIES = 3; + public static final int DISCOVERY_SEARCH_TIMEOUT = 20; + // === Biding property info + public static final String PROP_INFO_DEVICE_ALIAS = "device-alias"; + public static final String PROP_INFO_DEVICE_ID = "device-id"; + public static final String PROP_INFO_MODEL_URL_INFO = "model-url-info"; + public static final String PROP_INFO_PLATFORM_TYPE = "platform-type"; + // === UserData Directory and File Format public static String THINQ_USER_DATA_FOLDER = OpenHAB.getUserDataFolder() + File.separator + "thinq"; public static String THINQ_CONNECTION_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinqbridge-%s.json"; public static String BASE_CAP_CONFIG_DATA_FILE = THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-cap.json"; - public static final String V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; - public static final String V2_USER_INFO = "/users/profile"; - public static final String V2_API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; - public static final String V2_CLIENT_ID = "65260af7e8e6547b51fdccf930097c51eb9885a508d3fddfa9ee6cdec22ae1bd"; - public static final String V2_SVC_PHASE = "OP"; - public static final String V2_APP_LEVEL = "PRD"; - public static final String V2_APP_OS = "LINUX"; - public static final String V2_APP_TYPE = "NUTS"; - public static final String V2_APP_VER = "3.0.1700"; - public static final String V2_SESSION_LOGIN_PATH = "/emp/v2.0/account/session/"; - public static final String V2_LS_PATH = "/service/application/dashboard"; - public static final String V2_DEVICE_CONFIG_PATH = "service/devices/"; - public static final String V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/%s"; - public static final String V1_START_MON_PATH = "rti/rtiMon"; - public static final String V1_MON_DATA_PATH = "rti/rtiResult"; - public static final String V1_CONTROL_OP = "rti/rtiControl"; - public static final String OAUTH_SEARCH_KEY_PATH = "/searchKey"; - public static final String GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; - public static final String GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + GATEWAY_SERVICE_PATH_V2; - public static final String PRE_LOGIN_PATH = "/preLogin"; - public static final String SECURITY_KEY = "nuts_securitykey"; - public static final String SVC_CODE = "SVC202"; - public static final String OAUTH_SECRET_KEY = "c053c2a6ddeb7ad97cb0eed0dcb31cf8"; - public static final String OAUTH_CLIENT_KEY = "LGAO722A02"; - public static final String DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss +0000"; - public static final String APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; - public static final String V2_EMP_SESS_PATH = "/emp/oauth2/token/empsession"; - public static final String V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com" + V2_EMP_SESS_PATH; - public static final String API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; - - // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, - // and the build id of the thinq app it can also just be a random - // string, we use the same client id used for oauth - public static final String CLIENT_ID = "LGAO221A02"; - public static final String MESSAGE_ID = "wideq"; - public static final String SVC_PHASE = "OP"; - public static final String APP_LEVEL = "PRD"; - public static final String APP_OS = "ANDROID"; - public static final String APP_TYPE = "NUTS"; - public static final String APP_VER = "3.5.1200"; - - public static final String DEVICE_ID = "device-id"; - public static final String MODEL_NAME = "model-name"; - public static final String DEVICE_ALIAS = "device-alias"; - public static final String MODEL_URL_INFO = "model-url-info"; - public static final String PLATFORM_TYPE = "platform-type"; - public static final String PLATFORM_TYPE_V1 = "thinq1"; - public static final String PLATFORM_TYPE_V2 = "thinq2"; - static final Set SUPPORTED_LG_PLATFORMS = Set.of(PLATFORM_TYPE_V1, PLATFORM_TYPE_V2); - - public static final int SEARCH_TIME = 20; - public static final int DEFAULT_ENERGY_COLLECTOR_POLLING_UPDATE_DELAY = 60; - - // ====================== FRIDGE DEVICE CONSTANTS ============================= - // CHANNEL IDS - public static final Double FRIDGE_TEMPERATURE_IGNORE_VALUE = 255.0; - public static final Double FREEZER_TEMPERATURE_IGNORE_VALUE = 255.0; - public static final String FR_CHANNEL_FRIDGE_TEMP_ID = "fridge-temperature"; - public static final String FR_CHANNEL_FREEZER_TEMP_ID = "freezer-temperature"; - public static final String FR_CHANNEL_REF_TEMP_UNIT = "temp-unit"; - public static final String TEMP_UNIT_CELSIUS = "CELSIUS"; - public static final String TEMP_UNIT_FAHRENHEIT = "FAHRENHEIT"; - public static final String TEMP_UNIT_CELSIUS_SYMBOL = "°C"; - public static final String TEMP_UNIT_FAHRENHEIT_SYMBOL = "°F"; - - public static final String FR_CHANNEL_ICE_PLUS = "fr-ice-plus"; - public static final String FR_CHANNEL_EXPRESS_FREEZE_MODE = "fr-express-mode"; - public static final String FR_CHANNEL_EXPRESS_COOL_MODE = "fr-express-cool-mode"; - public static final String FR_CHANNEL_VACATION_MODE = "fr-eco-friendly-mode"; - public static final String FR_CHANNEL_SMART_SAVING_MODE_V2 = "fr-smart-saving-mode"; - public static final String FR_CHANNEL_SMART_SAVING_SWITCH_V1 = "fr-smart-saving-switch"; - public static final String FR_CHANNEL_ACTIVE_SAVING = "fr-active-saving"; - public static final String FR_CHANNEL_FRESH_AIR_FILTER = "fr-fresh-air-filter"; - public static final String FR_CHANNEL_WATER_FILTER = "fr-water-filter"; - public static final Set CELSIUS_UNIT_VALUES = Set.of("01", "1", "C", "CELSIUS", TEMP_UNIT_CELSIUS_SYMBOL); - public static final Set FAHRENHEIT_UNIT_VALUES = Set.of("02", "2", "F", "FAHRENHEIT", - TEMP_UNIT_FAHRENHEIT_SYMBOL); - public static final Set DOOR_OPEN_FR_VALUES = Set.of("1", "01", "OPEN"); - public static final Set DOOR_CLOSE_FR_VALUES = Set.of("0", "00", "CLOSE"); - public static final String REFRIGERATOR_SNAPSHOT_NODE_V2 = "refState"; - public static final String FR_SET_CONTROL_COMMAND_NAME_V1 = "SetControl"; - public static final Map CAP_FR_SMART_SAVING_MODE = Map.of("@CP_TERM_USE_NOT_W", "Disabled", - "@RE_SMARTSAVING_MODE_NIGHT_W", "Night Mode", "@RE_SMARTSAVING_MODE_CUSTOM_W", "Custom Mode"); - public static final Map CAP_FR_ON_OFF = Map.of("@CP_OFF_EN_W", "Off", "@CP_ON_EN_W", "On"); - public static final Map CAP_FR_LABEL_ON_OFF = Map.of("OFF", "Off", "ON", "On", "IGNORE", - "Not Available"); - - public static final Map CAP_FR_LABEL_CLOSE_OPEN = Map.of("CLOSE", "Closed", "OPEN", "Open", - "IGNORE", "Not Available"); - - public static final Map CAP_FR_EXPRESS_FREEZE_MODES = Map.of("@CP_OFF_EN_W", "Express Mode Off", - "@CP_ON_EN_W", "Express Freeze On", "@RE_MAIN_SPEED_FREEZE_TERM_W", "Rapid Freeze On"); - public static final Map CAP_FR_FRESH_AIR_FILTER_MAP = Map.ofEntries(/* v1 */ entry("1", "Off"), - entry("2", "Auto Mode"), entry("3", "Power Mode"), entry("4", "Replace Filter"), - /* v2 */ entry("OFF", "Off"), entry("AUTO", "Auto Mode"), entry("POWER", "Power Mode"), - entry("REPLACE", "Replace Filter"), entry("SMART_STORAGE_POWER", "Smart Storage Power"), - entry("SMART_STORAGE_OFF", "Smart Storage Off"), entry("SMART_STORAGE_ON", "Smart Storage On"), - entry("IGNORE", "Not Available")); - - public static final Map CAP_FR_SMART_SAVING_V2_MODE = Map.of("OFF", "Off", "NIGHT_ON", "Night Mode", - "CUSTOM_ON", "Custom Mode", "SMARTGRID_DR_ON", "Demand Response", "SMARTGRID_DD_ON", "Delay Defrost", - "IGNORE", "Not Available"); - - public static final Map CAP_FR_WATER_FILTER = Map.ofEntries(entry("0_MONTH", "0 Month Used"), - entry("0", "0 Month Used"), entry("1_MONTH", "1 Month Used"), entry("1", "1 Month Used"), - entry("2_MONTH", "2 Month Used"), entry("2", "2 Month Used"), entry("3_MONTH", "3 Month Used"), - entry("3", "3 Month Used"), entry("4_MONTH", "4 Month Used"), entry("4", "4 Month Used"), - entry("5_MONTH", "5 Month Used"), entry("5", "5 Month Used"), entry("6_MONTH", "6 Month Used"), - entry("6", "6 Month Used"), entry("7_MONTH", "7 Month Used"), entry("8_MONTH", "8 Month Used"), - entry("9_MONTH", "9 Month Used"), entry("10_MONTH", "10 Month Used"), entry("11_MONTH", "11 Month Used"), - entry("12_MONTH", "12 Month Used"), entry("IGNORE", "Not Available")); - public static final String CAP_FR_WATER_FILTER_USED_POSTFIX = "Month(s) Used"; - public static final Map CAP_FR_TEMP_UNIT_V2_MAP = Map.of(TEMP_UNIT_CELSIUS, - TEMP_UNIT_CELSIUS_SYMBOL, TEMP_UNIT_FAHRENHEIT, TEMP_UNIT_FAHRENHEIT_SYMBOL); - - // ====================== AIR CONDITIONER DEVICE CONSTANTS ============================= - // CHANNEL IDS - public static final String CHANNEL_MOD_OP_ID = "op-mode"; - public static final String CHANNEL_AIR_WATER_SWITCH_ID = "air-water-switch"; - public static final String CHANNEL_FAN_SPEED_ID = "fan-speed"; - public static final String CHANNEL_POWER_ID = "power"; - public static final String CHANNEL_EXTENDED_INFO_COLLECTOR_ID = "extra_info_collector"; - public static final String CHANNEL_CURRENT_POWER_ID = "current-power"; - public static final String CHANNEL_REMAINING_FILTER_ID = "remaining-filter"; - public static final String CHANNEL_TARGET_TEMP_ID = "target-temperature"; - public static final String CHANNEL_MIN_TEMP_ID = "min_temperature"; - public static final String CHANNEL_MAX_TEMP_ID = "max_temperature"; - public static final String CHANNEL_CURRENT_TEMP_ID = "current-temperature"; - public static final String CHANNEL_COOL_JET_ID = "cool-jet"; - public static final String CHANNEL_AIR_CLEAN_ID = "air-clean"; - public static final String CHANNEL_AUTO_DRY_ID = "auto-dry"; - public static final String CHANNEL_ENERGY_SAVING_ID = "energy-saving"; - public static final String CHANNEL_STEP_UP_DOWN_ID = "fan-step-up-down"; - public static final String CHANNEL_STEP_LEFT_RIGHT_ID = "fan-step-left-right"; - - public static final String CAP_ACHP_OP_MODE_COOL_KEY = "@AC_MAIN_OPERATION_MODE_COOL_W"; - public static final String CAP_ACHP_OP_MODE_HEAT_KEY = "@AC_MAIN_OPERATION_MODE_HEAT_W"; - public static final Map CAP_AC_OP_MODE = Map.of(CAP_ACHP_OP_MODE_COOL_KEY, "Cool", - "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", CAP_ACHP_OP_MODE_HEAT_KEY, - "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", - "@AC_MAIN_OPERATION_MODE_AI_W", "AI", "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", - "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); - - public static final Map CAP_AC_STEP_UP_DOWN_MODE = Map.of("@OFF", "Off", "@1", "Upper", "@2", "Up", - "@3", "Middle Up", "@4", "Middle Down", "@5", "Down", "@6", "Far Down", "@100", "Circular"); - public static final Map CAP_AC_STEP_LEFT_RIGHT_MODE = Map.of("@OFF", "Off", "@1", "Lefter", "@2", - "Left", "@3", "Middle", "@4", "Right", "@5", "Righter", "@13", "Left to Middle", "@35", "Middle to Right", - "@100", "Circular"); - - // Sub Modes support - public static final String AC_SUB_MODE_COOL_JET = "@AC_MAIN_WIND_MODE_COOL_JET_W"; - public static final String AC_SUB_MODE_STEP_UP_DOWN = "@AC_MAIN_WIND_DIRECTION_STEP_UP_DOWN_W"; - public static final String AC_SUB_MODE_STEP_LEFT_RIGHT = "@AC_MAIN_WIND_DIRECTION_STEP_LEFT_RIGHT_W"; - - public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( - entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), - entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), - entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), - entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), - entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Auto"), - entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), - entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), - entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), - entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), - entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), - entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); - - public static final Map CAP_AC_COOL_JET = Map.of("@COOL_JET", "Cool Jet"); - public static final Double CAP_HP_AIR_SWITCH = 0.0; - public static final Double CAP_HP_WATER_SWITCH = 1.0; - // ======= RAC MODES - public static final String CAP_AC_AUTODRY = "@AUTODRY"; - public static final String CAP_AC_AUTODRY_NODE = "AutoDry"; - public static final String CAP_AC_ENERGYSAVING = "@ENERGYSAVING"; - public static final String CAP_AC_AIRCLEAN = "@AIRCLEAN"; - // ==================== - public static final String CAP_AC_COMMAND_OFF = "@OFF"; - public static final String CAP_AC_COMMAND_ON = "@ON"; - - public static final String CAP_AC_AIR_CLEAN_COMMAND_ON = "@AC_MAIN_AIRCLEAN_ON_W"; - public static final String CAP_AC_AIR_CLEAN_COMMAND_OFF = "@AC_MAIN_AIRCLEAN_OFF_W"; - - // Extended Info Attribute Constants - public static final String EXTENDED_ATTR_INSTANT_POWER = "InOutInstantPower"; - public static final String EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE = "ChangePeriod"; - public static final String EXTENDED_ATTR_FILTER_USED_TIME = "UseTime"; - - // ====================== WASHING MACHINE CONSTANTS ============================= - public static final String WM_COURSE_NOT_SELECTED_VALUE = "NOT_SELECTED"; - public static final String WM_POWER_OFF_VALUE = "POWEROFF"; - public static final String WM_SNAPSHOT_WASHER_DRYER_NODE_V2 = "washerDryer"; - public static final String WM_CHANNEL_STATE_ID = "state"; - public static final String WM_CHANNEL_PROCESS_STATE_ID = "process-state"; - public static final String WM_CHANNEL_COURSE_ID = "course"; - public static final String DR_CHANNEL_DRY_LEVEL_ID = "dry-level"; - public static final String WM_CHANNEL_SMART_COURSE_ID = "smart-course"; - public static final String WM_CHANNEL_TEMP_LEVEL_ID = "temperature-level"; - public static final String WM_CHANNEL_DOOR_LOCK_ID = "door-lock"; - public static final String DR_CHANNEL_CHILD_LOCK_ID = "child-lock"; - public static final String FR_CHANNEL_DOOR_ID = "some-door-open"; - public static final String WM_CHANNEL_RINSE_ID = "rinse"; - - public static final String WM_CHANNEL_SPIN_ID = "spin"; - - public static final String WM_CHANNEL_REMOTE_START_START_STOP = "rs-start-stop"; - - public static final String WM_CHANNEL_REMOTE_COURSE = "rs-course"; - public static final String WM_CHANNEL_REMOTE_START_RINSE = "rs-rinse"; - public static final String WM_CHANNEL_REMOTE_START_TEMP = "rs-temperature-level"; - public static final String WM_CHANNEL_REMOTE_START_SPIN = "rs-spin"; - public static final String WM_CHANNEL_REMOTE_START_ID = "remote-start-flag"; - public static final String WM_CHANNEL_STAND_BY_ID = "stand-by"; - public static final String WM_CHANNEL_REMAIN_TIME_ID = "remain-time"; - public static final String WM_CHANNEL_DELAY_TIME_ID = "delay-time"; - - public static final String WM_LOST_WASHING_STATE_VALUE = "@WM_STATE_WASHING_W"; - public static final String WM_LOST_WASHING_STATE_KEY = "WASHING"; - public static final Map CAP_WDM_STATE = Map.ofEntries(entry("@WM_STATE_POWER_OFF_W", "Off"), - entry("@WM_STATE_INITIAL_W", "Initial"), entry("@WM_STATE_PAUSE_W", "Pause"), - entry("@WM_STATE_RESERVE_W", "Reserved"), entry("@WM_STATE_DETECTING_W", "Detecting"), - entry("@WM_STATE_RUNNING_W", "Running"), entry("@WM_STATE_RINSING_W", "Rinsing"), - entry("@WM_STATE_SPINNING_W", "Spinning"), entry("@WM_STATE_COOLDOWN_W", "Cool Down"), - entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), - entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), entry("@WM_STATE_END_W", "End"), - entry("@WM_STATE_DRYING_W", "Drying"), entry("@WM_STATE_DEMO_W", "Demonstration"), - entry("@WM_STATE_ADD_DRAIN_W", "Add Drain"), entry("@WM_STATE_LOAD_DISPLAY_W", "Loading Display"), - entry("@WM_STATE_FRESHCARE_W", "Refreshing"), entry("@WM_STATE_ERROR_AUTO_OFF_W", "Error Auto Off"), - entry("@WM_STATE_FROZEN_PREVENT_INITIAL_W", "Frozen Preventing"), - entry("@FROZEN_PREVENT_PAUSE", "Frozen Preventing Paused"), - entry("@FROZEN_PREVENT_RUNNING", "Frozen Preventing Running"), entry("@AUDIBLE_DIAGNOSIS", "Diagnosing"), - entry("@WM_STATE_ERROR_W", "Error"), - // This last one is not defined in the cap file - entry(WM_LOST_WASHING_STATE_VALUE, "Washing")); - - public static final Map CAP_WDM_PROCESS_STATE = Map.ofEntries( - entry("@WM_STATE_DETECTING_W", "Detecting"), entry("@WM_STATE_STEAM_W", "Steam"), - entry("@WM_STATE_DRY_W", "Drying"), entry("@WM_STATE_COOLING_W", "Cooling"), - entry("@WM_STATE_ANTI_CREASE_W", "Anti Creasing"), entry("@WM_STATE_END_W", "End"), - entry("@WM_STATE_POWER_OFF_W", "Power Off"), entry("@WM_STATE_INITIAL_W", "Initializing"), - entry("@WM_STATE_PAUSE_W", "Paused"), entry("@WM_STATE_RESERVE_W", "Reserved"), - entry("@WM_STATE_RUNNING_W", "Running"), entry("@WM_STATE_RINSING_W", "Rising"), - entry("@WM_STATE_SPINNING_W", "@WM_STATE_DRYING_W"), entry("WM_STATE_COOLDOWN_W", "Cool Down"), - entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), - entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), entry("@WM_STATE_ERROR_W", "Error")); - - public static final Map CAP_DR_DRY_LEVEL = Map.ofEntries( - entry("@WM_DRY24_DRY_LEVEL_IRON_W", "Iron"), entry("@WM_DRY24_DRY_LEVEL_CUPBOARD_W", "Cupboard"), - entry("@WM_DRY24_DRY_LEVEL_EXTRA_W", "Extra")); - - public static final Map CAP_WM_TEMPERATURE = Map.ofEntries( - entry("@WM_TERM_NO_SELECT_W", "Not Selected"), entry("@WM_TITAN2_OPTION_TEMP_20_W", "20"), - entry("@WM_TITAN2_OPTION_TEMP_COLD_W", "Cold"), entry("@WM_TITAN2_OPTION_TEMP_30_W", "30"), - entry("@WM_TITAN2_OPTION_TEMP_40_W", "40"), entry("@WM_TITAN2_OPTION_TEMP_50_W", "50"), - entry("@WM_TITAN27_BIG_OPTION_TEMP_TAP_COLD_W", "Tap Cold"), - entry("@WM_TITAN27_BIG_OPTION_TEMP_COLD_W", "Cold"), - entry("@WM_TITAN27_BIG_OPTION_TEMP_ECO_WARM_W", "Eco Warm"), - entry("@WM_TITAN27_BIG_OPTION_TEMP_WARM_W", "Warm"), entry("@WM_TITAN27_BIG_OPTION_TEMP_HOT_W", "Hot"), - entry("@WM_TITAN27_BIG_OPTION_TEMP_EXTRA_HOT_W", "Extra Hot")); - - public static final Map CAP_WM_SPIN = Map.ofEntries(entry("@WM_TERM_NO_SELECT_W", "Not Selected"), - entry("@WM_TITAN2_OPTION_SPIN_NO_SPIN_W", "No Spin"), entry("@WM_TITAN2_OPTION_SPIN_400_W", "400"), - entry("@WM_TITAN2_OPTION_SPIN_600_W", "600"), entry("@WM_TITAN2_OPTION_SPIN_700_W", "700"), - entry("@WM_TITAN2_OPTION_SPIN_800_W", "800"), entry("@WM_TITAN2_OPTION_SPIN_900_W", "900"), - entry("@WM_TITAN2_OPTION_SPIN_1000_W", "1000"), entry("@WM_TITAN2_OPTION_SPIN_1100_W", "1100"), - entry("@WM_TITAN2_OPTION_SPIN_1200_W", "1200"), entry("@WM_TITAN2_OPTION_SPIN_1400_W", "1400"), - entry("@WM_TITAN2_OPTION_SPIN_1600_W", "1600"), entry("@WM_TITAN2_OPTION_SPIN_MAX_W", "Max Spin"), - entry("@WM_TITAN27_BIG_OPTION_SPIN_NO_SPIN_W", "Drain Only"), - entry("@WM_TITAN27_BIG_OPTION_SPIN_LOW_W", "Low"), entry("@WM_TITAN27_BIG_OPTION_SPIN_MEDIUM_W", "Medium"), - entry("@WM_TITAN27_BIG_OPTION_SPIN_HIGH_W", "High"), - entry("@WM_TITAN27_BIG_OPTION_SPIN_EXTRA_HIGH_W", "Extra High")); - - public static final Map CAP_WM_RINSE = Map.ofEntries(entry("@WM_TERM_NO_SELECT_W", "Not Selected"), - entry("@WM_TITAN2_OPTION_RINSE_NORMAL_W", "Normal"), entry("@WM_TITAN2_OPTION_RINSE_RINSE+_W", "Plus"), - entry("@WM_TITAN2_OPTION_RINSE_RINSE++_W", "Plus +"), - entry("@WM_TITAN2_OPTION_RINSE_NORMALHOLD_W", "Normal Hold"), - entry("@WM_TITAN2_OPTION_RINSE_RINSE+HOLD_W", "Plus Hold"), - entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_0_W", "Normal"), - entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_1_W", "Plus"), - entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_2_W", "Plus +"), - entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_3_W", "Plus ++")); - - // This is the dictionary os course functions translations for V2 - public static final Map> CAP_WM_DICT_V2 = Map.of("spin", CAP_WM_SPIN, "rinse", - CAP_WM_RINSE, "temp", CAP_WM_TEMPERATURE, "state", CAP_WDM_STATE); - - public static final String WM_COMMAND_REMOTE_START_V2 = "WMStart"; - // ============================================================================== - // ====================== WASHING MACHINE CONSTANTS ============================= - public static final String DW_SNAPSHOT_WASHER_DRYER_NODE_V2 = "dishwasher"; - public static final String DW_POWER_OFF_VALUE = "POWEROFF"; - public static final String DW_STATE_COMPLETE = "END"; - public static final Map CAP_DW_DOOR_STATE = Map.of("@CP_OFF_EN_W", "Close", "@CP_ON_EN_W", - "Opened"); - public static final Map CAP_DW_PROCESS_STATE = Map.ofEntries(entry("@DW_STATE_INITIAL_W", "None"), - entry("@DW_STATE_RESERVE_W", "Reserved"), entry("@DW_STATE_RUNNING_W", "Running"), - entry("@DW_STATE_RINSING_W", "Rising"), entry("@DW_STATE_DRYING_W", "Drying"), - entry("@DW_STATE_COMPLETE_W", "Complete"), entry("@DW_STATE_NIGHTDRY_W", "Night Dry"), - entry("@DW_STATE_CANCEL_W", "Cancelled")); + // ==================================================== + + /** + * ============ Air Conditioner Channels & Constant Definition ============= + */ + public static final String CHANNEL_AC_AIR_CLEAN_ID = "air-clean"; + public static final String CHANNEL_AC_AIR_WATER_SWITCH_ID = "air-water-switch"; + public static final String CHANNEL_AC_AUTO_DRY_ID = "auto-dry"; + public static final String CHANNEL_AC_COOL_JET_ID = "cool-jet"; + public static final String CHANNEL_AC_CURRENT_POWER_ID = "current-power"; + public static final String CHANNEL_AC_CURRENT_TEMP_ID = "current-temperature"; + public static final String CHANNEL_AC_ENERGY_SAVING_ID = "energy-saving"; + public static final String CHANNEL_AC_FAN_SPEED_ID = "fan-speed"; + public static final String CHANNEL_AC_MAX_TEMP_ID = "max_temperature"; + public static final String CHANNEL_AC_MIN_TEMP_ID = "min_temperature"; + public static final String CHANNEL_AC_MOD_OP_ID = "op-mode"; + public static final String CHANNEL_AC_POWER_ID = "power"; + public static final String CHANNEL_AC_REMAINING_FILTER_ID = "remaining-filter"; + public static final String CHANNEL_AC_STEP_LEFT_RIGHT_ID = "fan-step-left-right"; + public static final String CHANNEL_AC_STEP_UP_DOWN_ID = "fan-step-up-down"; + public static final String CHANNEL_AC_TARGET_TEMP_ID = "target-temperature"; + + /** + * ============ Refrigerator's Channels & Constant Definition ============= + */ + public static final String CHANNEL_RE_ACTIVE_SAVING = "fr-active-saving"; + public static final String CHANNEL_RE_DOOR_OPEN = "some-door-open"; + public static final String CHANNEL_RE_EXPRESS_COOL_MODE = "fr-express-cool-mode"; + public static final String CHANNEL_RE_EXPRESS_FREEZE_MODE = "fr-express-mode"; + public static final String CHANNEL_RE_FREEZER_TEMP_ID = "freezer-temperature"; + public static final String CHANNEL_RE_FRESH_AIR_FILTER = "fr-fresh-air-filter"; + public static final String CHANNEL_RE_FRIDGE_TEMP_ID = "fridge-temperature"; + public static final String CHANNEL_RE_ICE_PLUS = "fr-ice-plus"; + public static final String CHANNEL_RE_REF_TEMP_UNIT = "temp-unit"; + public static final String CHANNEL_RE_SMART_SAVING_MODE_V2 = "fr-smart-saving-mode"; + public static final String CHANNEL_RE_SMART_SAVING_SWITCH_V1 = "fr-smart-saving-switch"; + public static final String CHANNEL_RE_VACATION_MODE = "fr-eco-friendly-mode"; + public static final String CHANNEL_RE_WATER_FILTER = "fr-water-filter"; + + /** + * ============ Washing Machine/Dryer and DishWasher Channels & Constant Definition ============= + * DishWasher, Washing Machine and Dryer have the same channel core and features + */ + public static final String CHANNEL_DR_CHILD_LOCK_ID = "child-lock"; + public static final String CHANNEL_DR_DRY_LEVEL_ID = "dry-level"; + public static final String CHANNEL_WMD_COURSE_ID = "course"; + public static final String CHANNEL_WMD_DELAY_TIME_ID = "delay-time"; + public static final String CHANNEL_WMD_DOOR_LOCK_ID = "door-lock"; + public static final String CHANNEL_WMD_PROCESS_STATE_ID = "process-state"; + public static final String CHANNEL_WMD_REMAIN_TIME_ID = "remain-time"; + public static final String CHANNEL_WMD_REMOTE_COURSE = "rs-course"; + public static final String CHANNEL_WMD_REMOTE_START_GRP_ID = "remote-start-grp"; + public static final String CHANNEL_WMD_REMOTE_START_ID = "remote-start-flag"; + public static final String CHANNEL_WMD_REMOTE_START_START_STOP = "rs-start-stop"; + public static final String CHANNEL_WMD_RINSE_ID = "rinse"; + public static final String CHANNEL_WMD_SMART_COURSE_ID = "smart-course"; + public static final String CHANNEL_WMD_SPIN_ID = "spin"; + public static final String CHANNEL_WMD_STAND_BY_ID = "stand-by"; + public static final String CHANNEL_WMD_STATE_ID = "state"; + public static final String CHANNEL_WMD_TEMP_LEVEL_ID = "temperature-level"; + public static final String CHANNEL_WM_REMOTE_START_RINSE = "rs-rinse"; + public static final String CHANNEL_WM_REMOTE_START_SPIN = "rs-spin"; + public static final String CHANNEL_WM_REMOTE_START_TEMP = "rs-temperature-level"; - public static final Map CAP_DW_STATE = Map.ofEntries(entry("@DW_STATE_POWER_OFF_W", "Off"), - entry("@DW_STATE_INITIAL_W", "Initial"), entry("@DW_STATE_RUNNING_W", "Running"), - entry("@DW_STATE_PAUSE_W", "Paused"), entry("@DW_STATE_STANDBY_W", "Stand By"), - entry("@DW_STATE_COMPLETE_W", "Complete"), entry("@DW_STATE_POWER_FAIL_W", "Power Fail")); // ============================================================================== } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java index 578d97c1c35f8..e5d159120cb06 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java @@ -28,8 +28,8 @@ public class LGThinQBridgeConfiguration { public String password = ""; public String country = ""; public String language = ""; - public String manualCountry = ""; - public String manualLanguage = ""; + public final String manualCountry = ""; + public final String manualLanguage = ""; public Integer poolingIntervalSec = 0; public String alternativeServer = ""; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index a67ccccd1ff73..548494e0301ac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -12,13 +12,25 @@ */ package org.openhab.binding.lgthinq.internal; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_BRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_DISHWASHER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_DRYER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_DRYER_TOWER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_FRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_HEAT_PUMP; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_WASHING_MACHINE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_WASHING_TOWER; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.handler.*; +import org.openhab.binding.lgthinq.internal.handler.LGThinQAirConditionerHandler; +import org.openhab.binding.lgthinq.internal.handler.LGThinQBridgeHandler; +import org.openhab.binding.lgthinq.internal.handler.LGThinQDishWasherHandler; +import org.openhab.binding.lgthinq.internal.handler.LGThinQFridgeHandler; +import org.openhab.binding.lgthinq.internal.handler.LGThinQWasherDryerHandler; import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.core.config.core.Configuration; @@ -49,7 +61,7 @@ public class LGThinQHandlerFactory extends BaseThingHandlerFactory { private final Logger logger = LoggerFactory.getLogger(LGThinQHandlerFactory.class); - private HttpClientFactory httpClientFactory; + private final HttpClientFactory httpClientFactory; private final LGThinQStateDescriptionProvider stateDescriptionProvider; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java index 4c4f587261b05..d7bee71d7ac5e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQStateDescriptionProvider.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.internal; -import org.openhab.binding.lgthinq.internal.handler.LGThinQAbstractDeviceHandler; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.core.events.EventPublisher; import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider; import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService; @@ -23,10 +23,11 @@ import org.osgi.service.component.annotations.Reference; /** - * The {@link LGThinQAbstractDeviceHandler} is a main interface contract for all LG Thinq things + * The {@link LGThinQStateDescriptionProvider} Custom State Description Provider * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault @Component(service = { DynamicStateDescriptionProvider.class, LGThinQStateDescriptionProvider.class }) public class LGThinQStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { @Activate diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index 4c94e513c2e24..39fb483e893a0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -12,7 +12,20 @@ */ package org.openhab.binding.lgthinq.internal.discovery; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.DISCOVERY_SEARCH_TIMEOUT; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_DEVICE_ALIAS; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_DEVICE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_MODEL_URL_INFO; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_PLATFORM_TYPE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.SUPPORTED_THING_TYPES; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_DISHWASHER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_DRYER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_DRYER_TOWER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_FRIDGE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_HEAT_PUMP; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_WASHING_MACHINE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_WASHING_TOWER; import static org.openhab.core.thing.Thing.PROPERTY_MODEL_ID; import java.time.Instant; @@ -22,10 +35,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.internal.handler.LGThinQBridgeHandler; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory.LGThinQGeneralApiClientService; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.core.config.discovery.AbstractThingHandlerDiscoveryService; import org.openhab.core.config.discovery.DiscoveryResult; @@ -51,7 +64,7 @@ public class LGThinqDiscoveryService extends AbstractThingHandlerDiscoveryServic private @Nullable LGThinQGeneralApiClientService lgApiClientService; public LGThinqDiscoveryService() { - super(LGThinQBridgeHandler.class, SUPPORTED_THING_TYPES, SEARCH_TIME); + super(LGThinQBridgeHandler.class, SUPPORTED_THING_TYPES, DISCOVERY_SEARCH_TIMEOUT); } @Override @@ -94,7 +107,8 @@ public void addLgDeviceDiscovery(LGDevice device) { ThingTypeUID thingTypeUID; try { // load capability to cache and troubleshooting - lgApiClientService.loadDeviceCapability(device.getDeviceId(), device.getModelJsonUri(), false); + Objects.requireNonNull(lgApiClientService, "Unexpected null here") + .loadDeviceCapability(device.getDeviceId(), device.getModelJsonUri(), false); thingUID = getThingUID(device); thingTypeUID = getThingTypeUID(device); } catch (LGThinqException e) { @@ -104,14 +118,14 @@ public void addLgDeviceDiscovery(LGDevice device) { } Map properties = new HashMap<>(); - properties.put(DEVICE_ID, device.getDeviceId()); - properties.put(DEVICE_ALIAS, device.getAlias()); - properties.put(MODEL_URL_INFO, device.getModelJsonUri()); - properties.put(PLATFORM_TYPE, device.getPlatformType()); + properties.put(PROP_INFO_DEVICE_ID, device.getDeviceId()); + properties.put(PROP_INFO_DEVICE_ALIAS, device.getAlias()); + properties.put(PROP_INFO_MODEL_URL_INFO, device.getModelJsonUri()); + properties.put(PROP_INFO_PLATFORM_TYPE, device.getPlatformType()); properties.put(PROPERTY_MODEL_ID, modelId); DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID) - .withProperties(properties).withBridge(bridgeHandlerUID).withRepresentationProperty(DEVICE_ID) + .withProperties(properties).withBridge(bridgeHandlerUID).withRepresentationProperty(PROP_INFO_DEVICE_ID) .withLabel(device.getAlias()).build(); thingDiscovered(discoveryResult); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java index 7977edfe64926..7f1ab4972cb6c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/BaseThingWithExtraInfoHandler.java @@ -15,7 +15,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.core.thing.Thing; import org.openhab.core.thing.binding.BaseThingHandler; @@ -40,9 +40,8 @@ public BaseThingWithExtraInfoHandler(Thing thing) { * Handle must implement this method to update device's extra information collected to the respective channels. * * @param energyStateAttributes map containing the key and values collected - * @throws LGThinqException if some error occur */ - protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { + protected void updateExtraInfoStateChannels(Map energyStateAttributes) { throw new UnsupportedOperationException( "Method must be implemented in the Handle that supports energy collector. It most likely a bug"); } @@ -61,5 +60,5 @@ protected Map collectExtraInfoState() throws LGThinqException { * Reset (put in UNDEF) the channels related to extra information. Normally called when the collector stops. */ protected void resetExtraInfoChannels() { - }; + } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 61c47477b9e61..9f0012d5da093 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -12,7 +12,14 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.BINDING_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_POWER_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_EXTENDED_INFO_COLLECTOR_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.MAX_GET_MONITOR_RETRIES; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_DEVICE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_MODEL_URL_INFO; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_PLATFORM_TYPE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_PLATFORM_TYPE_V2; import java.lang.management.ManagementFactory; import java.lang.management.ThreadInfo; @@ -23,20 +30,42 @@ import java.util.Map; import java.util.Objects; import java.util.Set; -import java.util.concurrent.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.*; import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiExhaustionException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotDefinition; import org.openhab.core.items.Item; import org.openhab.core.library.types.OnOffType; -import org.openhab.core.thing.*; +import org.openhab.core.thing.Bridge; +import org.openhab.core.thing.Channel; +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.ThingStatusInfo; import org.openhab.core.thing.binding.builder.ChannelBuilder; import org.openhab.core.thing.link.ItemChannelLinkRegistry; import org.openhab.core.thing.type.ChannelKind; @@ -52,15 +81,13 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public abstract class LGThinQAbstractDeviceHandler +public abstract class LGThinQAbstractDeviceHandler<@NonNull C extends CapabilityDefinition, @NonNull S extends SnapshotDefinition> extends BaseThingWithExtraInfoHandler { private final Logger logger = LoggerFactory.getLogger(LGThinQAbstractDeviceHandler.class); protected @Nullable LGThinQBridgeHandler account; protected final String lgPlatformType; - @Nullable private S lastShot; protected final ItemChannelLinkRegistry itemChannelLinkRegistry; - private final Class snapshotClass; @Nullable protected C thinQCapability; private @Nullable Future commandExecutorQueueJob; @@ -93,18 +120,25 @@ public abstract class LGThinQAbstractDeviceHandler getSnapshotClass() { + return (Class) (Objects.requireNonNull((ParameterizedType) getClass().getGenericSuperclass(), + "Unexpected null here")).getActualTypeArguments()[1]; } + @SuppressWarnings("null") public LGThinQAbstractDeviceHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, ItemChannelLinkRegistry itemChannelLinkRegistry) { super(thing); this.itemChannelLinkRegistry = itemChannelLinkRegistry; this.stateDescriptionProvider = stateDescriptionProvider; normalizeConfigurationsAndProperties(); - lgPlatformType = String.valueOf(thing.getProperties().get(PLATFORM_TYPE)); - this.snapshotClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()) - .getActualTypeArguments()[1]; + lgPlatformType = String.valueOf(thing.getProperties().get(PROP_INFO_PLATFORM_TYPE)); + + Class snapshotClass = getSnapshotClass(); try { this.lastShot = snapshotClass.getDeclaredConstructor().newInstance(); } catch (Exception e) { @@ -112,8 +146,12 @@ public LGThinQAbstractDeviceHandler(Thing thing, LGThinQStateDescriptionProvider } } + private LGThinQBridgeHandler getAccountBridgeHandler() { + return Objects.requireNonNull(this.account, "BridgeHandler not initialized. It most likely a bug"); + } + private void normalizeConfigurationsAndProperties() { - List.of(PLATFORM_TYPE, MODEL_URL_INFO, DEVICE_ID).forEach(p -> { + List.of(PROP_INFO_PLATFORM_TYPE, PROP_INFO_MODEL_URL_INFO, PROP_INFO_DEVICE_ID).forEach(p -> { if (!thing.getProperties().containsKey(p)) { thing.setProperty(p, (String) thing.getConfiguration().get(p)); } @@ -164,16 +202,18 @@ protected final String emptyIfNull(@Nullable String value) { * @param key key to search for a value into map * @return return value related to that key in the map, or the own key if there is no correspondent. */ - protected final String keyIfValueNotFound(Map map, @NonNull String key) { + protected final String keyIfValueNotFound(Map map, String key) { return Objects.requireNonNullElse(map.get(key), key); } + @SuppressWarnings("null") protected void startCommandExecutorQueueJob() { if (commandExecutorQueueJob == null || commandExecutorQueueJob.isDone()) { commandExecutorQueueJob = getExecutorService().submit(getQueuedCommandExecutor()); } } + @SuppressWarnings("null") protected void stopCommandExecutorQueueJob() { if (commandExecutorQueueJob != null) { commandExecutorQueueJob.cancel(true); @@ -181,6 +221,7 @@ protected void stopCommandExecutorQueueJob() { commandExecutorQueueJob = null; } + @SuppressWarnings("null") protected void handleStatusChanged(ThingStatus newStatus, ThingStatusDetail statusDetail) { if (lastThingStatus != ThingStatus.ONLINE && newStatus == ThingStatus.ONLINE) { // start the thing polling @@ -208,6 +249,7 @@ protected void updateStatus(ThingStatus newStatus, ThingStatusDetail statusDetai } @Override + @SuppressWarnings("null") public void handleCommand(ChannelUID channelUID, Command command) { if (command instanceof RefreshType) { updateThingStateFromLG(); @@ -240,10 +282,8 @@ protected ExecutorService getExecutorService() { return executorService; } - public abstract void onDeviceAdded(@NonNullByDefault LGDevice device); - public String getDeviceId() { - return Objects.requireNonNullElse(getThing().getProperties().get(DEVICE_ID), "undef"); + return Objects.requireNonNullElse(getThing().getProperties().get(PROP_INFO_DEVICE_ID), "undef"); } public abstract String getDeviceAlias(); @@ -258,18 +298,6 @@ public String getDeviceId() { public abstract LGThinQApiClientService<@NonNull C, @NonNull S> getLgThinQAPIClientService(); - protected void createDynSwitchChannel(String channelName, ChannelUID chanelUuid) { - if (getCallback() == null) { - logger.error("Unexpected behaviour. Callback not ready! Can't create dynamic channels"); - } else { - // dynamic create channel - ChannelBuilder builder = getCallback().createChannelBuilder(chanelUuid, - new ChannelTypeUID(BINDING_ID, channelName)); - Channel channel = builder.withKind(ChannelKind.STATE).withAcceptedItemType("Switch").build(); - updateThing(editThing().withChannel(channel).build()); - } - } - public C getCapabilities() throws LGThinqApiException { if (thinQCapability == null) { thinQCapability = getLgThinQAPIClientService().getCapability(getDeviceId(), getDeviceUriJsonConfig(), @@ -287,7 +315,7 @@ public C getCapabilities() throws LGThinqApiException { @Nullable protected String getItemLinkedValue(ChannelUID channelUID) { Set items = itemChannelLinkRegistry.getLinkedItems(channelUID); - if (items.size() > 0) { + if (!items.isEmpty()) { for (Item i : items) { return i.getState().toString(); } @@ -332,21 +360,25 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { if (account == null) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Account not set"); } else { - account.registryListenerThing(this); - switch (bridgeStatus) { - case ONLINE: - updateStatus(ThingStatus.ONLINE); - break; - case INITIALIZING: - case UNINITIALIZED: - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); - break; - case UNKNOWN: - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); - break; - default: - updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); - break; + getAccountBridgeHandler().registryListenerThing(this); + if (bridgeStatus == null) { + updateStatus(ThingStatus.UNINITIALIZED); + } else { + switch (bridgeStatus) { + case ONLINE: + updateStatus(ThingStatus.ONLINE); + break; + case INITIALIZING: + case UNINITIALIZED: + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_UNINITIALIZED); + break; + case UNKNOWN: + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR); + break; + default: + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE); + break; + } } } } else { @@ -466,7 +498,7 @@ public void run() { protected void updateThingStateFromLG() { try { @Nullable - S shot = getSnapshotDeviceAdapter(getDeviceId(), getCapabilities()); + S shot = getSnapshotDeviceAdapter(getDeviceId()); if (shot == null) { // no data to update. Maybe, the monitor stopped, then it's going to be restarted next try. return; @@ -500,7 +532,7 @@ protected void updateThingStateFromLG() { getLogger().error("Error updating thing {}/{} from LG API. Thing goes OFFLINE until next retry: {}", getDeviceAlias(), getDeviceId(), e.getMessage(), e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); - } catch (Throwable e) { + } catch (Exception e) { getLogger().error( "System error in pooling thread (UpdateDevice) for device {}/{}. Filtering to do not stop the thread", getDeviceAlias(), getDeviceId(), e); @@ -544,10 +576,10 @@ private void handlePowerChange(@Nullable DevicePowerState previous, DevicePowerS startThingStatePolling(); } - private void updateDeviceChannelsWrapper(S snapshot) { + private void updateDeviceChannelsWrapper(S snapshot) throws LGThinqApiException { updateDeviceChannels(snapshot); // handle power changes - handlePowerChange(lastShot == null ? null : lastShot.getPowerStatus(), snapshot.getPowerStatus()); + handlePowerChange(getLastShot().getPowerStatus(), snapshot.getPowerStatus()); // after updated successfully, copy snapshot to last snapshot lastShot = snapshot; // and finish the cycle of thing reconfiguration (when thing starts or has configurations changed - if it's the @@ -555,31 +587,28 @@ private void updateDeviceChannelsWrapper(S snapshot) { isThingReconfigured = false; } - protected abstract void updateDeviceChannels(S snapshot); + protected abstract void updateDeviceChannels(S snapshot) throws LGThinqApiException; protected String translateFeatureToItemType(FeatureDataType dataType) { - switch (dataType) { - case UNDEF: - case ENUM: - return "String"; - case RANGE: - return "Dimmer"; - case BOOLEAN: - return "Switch"; - default: - throw new IllegalStateException( - String.format("Feature DataType %s not supported for this ThingHandler", dataType)); - } + return switch (dataType) { + case UNDEF, ENUM -> "String"; + case RANGE -> "Dimmer"; + case BOOLEAN -> "Switch"; + default -> throw new IllegalStateException( + String.format("Feature DataType %s not supported for this ThingHandler", dataType)); + }; } + @SuppressWarnings("null") protected void stopThingStatePolling() { - if (thingStatePollingJob != null && !thingStatePollingJob.isDone()) { + if (!(thingStatePollingJob == null || thingStatePollingJob.isDone())) { getLogger().debug("Stopping LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); thingStatePollingJob.cancel(true); } thingStatePollingJob = null; } + @SuppressWarnings("null") private void stopExtraInfoCollectorPolling() { if (extraInfoCollectorPollingJob != null && !extraInfoCollectorPollingJob.isDone()) { getLogger().debug("Stopping Energy Collector for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); @@ -589,6 +618,7 @@ private void stopExtraInfoCollectorPolling() { extraInfoCollectorPollingJob = null; } + @SuppressWarnings("null") protected void startThingStatePolling() { if (thingStatePollingJob == null || thingStatePollingJob.isDone()) { getLogger().debug("Starting LG thinq polling for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); @@ -602,6 +632,7 @@ protected void startThingStatePolling() { * Normally, the thing has a Switch Channel that enable/disable the energy collector. By default, the collector is * disabled. */ + @SuppressWarnings("null") private void startExtraInfoCollectorPolling() { if (extraInfoCollectorPollingJob == null || extraInfoCollectorPollingJob.isDone()) { getLogger().debug("Starting Energy Collector for device/alias: {}/{}", getDeviceId(), getDeviceAlias()); @@ -627,32 +658,17 @@ protected String getBridgeId() { getLogger().error("Configuration error um Thinq Thing - No Bridge defined for the thing."); return "UNKNOWN"; } else if (bridgeId.isBlank() && getBridge() != null) { - bridgeId = getBridge().getUID().getId(); + bridgeId = Objects.requireNonNull(getBridge()).getUID().getId(); } return bridgeId; } abstract protected DeviceTypes getDeviceType(); - private S handleV1OfflineException() { - try { - // As I don't know the current device status, then I reset to default values. - S shot = snapshotClass.getDeclaredConstructor().newInstance(); - shot.setPowerStatus(DevicePowerState.DV_POWER_OFF); - shot.setOnline(false); - return shot; - } catch (Exception ex) { - logger.error("Unexpected Error. The default constructor of this Snapshot wasn't found", ex); - throw new IllegalStateException("Unexpected Error. The default constructor of this Snapshot wasn't found", - ex); - } - } - @Nullable - protected S getSnapshotDeviceAdapter(String deviceId, CapabilityDefinition capDef) - throws LGThinqApiException, LGThinqApiExhaustionException { + protected S getSnapshotDeviceAdapter(String deviceId) throws LGThinqApiException, LGThinqApiExhaustionException { // analise de platform version - if (PLATFORM_TYPE_V2.equals(lgPlatformType)) { + if (LG_API_PLATFORM_TYPE_V2.equals(lgPlatformType)) { return getLgThinQAPIClientService().getDeviceData(getBridgeId(), getDeviceId(), getCapabilities()); } else { try { @@ -665,7 +681,7 @@ protected S getSnapshotDeviceAdapter(String deviceId, CapabilityDefinition capDe stopDeviceV1Monitor(deviceId); } catch (Exception ignored) { } - return handleV1OfflineException(); + return getLgThinQAPIClientService().buildDefaultOfflineSnapshot(); } catch (Exception e) { stopDeviceV1Monitor(deviceId); throw new LGThinqApiException("Error starting device monitor in LG API for the device:" + deviceId, e); @@ -723,7 +739,7 @@ protected Runnable getQueuedCommandExecutor() { try { processCommand(params); String channelUid = getSimpleChannelUID(params.channelUID); - if (CHANNEL_POWER_ID.equals(channelUid)) { + if (CHANNEL_AC_POWER_ID.equals(channelUid)) { // if processed command come from POWER channel, then force updateDeviceChannels immediatly // this is importante to analise if the poolings need to be changed in time. updateThingStateFromLG(); @@ -758,7 +774,7 @@ public void dispose() { logger.debug("Disposing Thinq Thing {}", getDeviceId()); if (account != null) { - account.unRegistryListenerThing(this); + getAccountBridgeHandler().unRegistryListenerThing(this); } stopThingStatePolling(); @@ -788,8 +804,9 @@ protected Channel createDynChannel(String channelNameAndTypeName, ChannelUID cha throw new IllegalStateException("Unexpected behaviour. Callback not ready! Can't create dynamic channels"); } else { // dynamic create channel - ChannelBuilder builder = getCallback().createChannelBuilder(channelUuid, - new ChannelTypeUID(BINDING_ID, channelNameAndTypeName)); + ChannelBuilder builder = Objects + .requireNonNull(getCallback(), "Not expected callback null here. It most likely a bug") + .createChannelBuilder(channelUuid, new ChannelTypeUID(BINDING_ID, channelNameAndTypeName)); Channel channel = builder.withKind(ChannelKind.STATE).withAcceptedItemType(itemType).build(); updateThing(editThing().withChannel(channel).build()); return channel; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 5cab86ff79438..4f54de57805b5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -12,24 +12,56 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CAP_EXTRA_ATTR_FILTER_MAX_TIME_TO_USE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CAP_EXTRA_ATTR_FILTER_USED_TIME; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CAP_EXTRA_ATTR_INSTANT_POWER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_AIR_CLEAN_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_AIR_WATER_SWITCH_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_AUTO_DRY_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_COOL_JET_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_CURRENT_POWER_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_CURRENT_TEMP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_ENERGY_SAVING_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_FAN_SPEED_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_MAX_TEMP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_MIN_TEMP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_MOD_OP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_POWER_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_REMAINING_FILTER_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_STEP_LEFT_RIGHT_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_STEP_UP_DOWN_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_TARGET_TEMP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DASHBOARD_GRP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_EXTENDED_INFO_COLLECTOR_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_EXTENDED_INFO_GRP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_DEVICE_ALIAS; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_MODEL_URL_INFO; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_AIR_CONDITIONER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_HEAT_PUMP; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_ACHP_OP_MODE_COOL_KEY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_ACHP_OP_MODE_HEAT_KEY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_FAN_SPEED; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_OP_MODE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_STEP_LEFT_RIGHT_MODE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_STEP_UP_DOWN_MODE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_HP_AIR_SWITCH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_HP_WATER_SWITCH; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Objects; -import org.apache.commons.lang3.math.NumberUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.LGThinQACApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; @@ -85,7 +117,6 @@ public class LGThinQAirConditionerHandler extends LGThinQAbstractDeviceHandler
collectExtraInfoState() throws LGThinqException { ExtendedDeviceInfo info = lgThinqACApiClientService.getExtendedDeviceInfo(getBridgeId(), getDeviceId()); return mapper.convertValue(info, new TypeReference<>() { @@ -444,29 +473,33 @@ protected Map collectExtraInfoState() throws LGThinqException { } @Override - protected void updateExtraInfoStateChannels(Map energyStateAttributes) throws LGThinqException { + protected void updateExtraInfoStateChannels(Map energyStateAttributes) { logger.debug("Calling updateExtraInfoStateChannels for device: {}", getDeviceId()); - String instantPowerConsumption = (String) energyStateAttributes.get(EXTENDED_ATTR_INSTANT_POWER); - String filterUsed = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_USED_TIME); - String filterTimelife = (String) energyStateAttributes.get(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE); + String instantPowerConsumption = (String) energyStateAttributes.get(CAP_EXTRA_ATTR_INSTANT_POWER); + String filterUsed = (String) energyStateAttributes.get(CAP_EXTRA_ATTR_FILTER_USED_TIME); + String filterTimelife = (String) energyStateAttributes.get(CAP_EXTRA_ATTR_FILTER_MAX_TIME_TO_USE); if (instantPowerConsumption == null) { updateState(currentPowerEnergyChannelUID, UnDefType.NULL); - } else if (NumberUtils.isCreatable(instantPowerConsumption)) { - double ip = Double.parseDouble(instantPowerConsumption); - updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.WATT_HOUR)); } else { - updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + try { + double ip = Double.parseDouble(instantPowerConsumption); + updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.WATT_HOUR)); + } catch (NumberFormatException e) { + updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + } } if (filterTimelife == null || filterUsed == null) { updateState(remainingFilterChannelUID, UnDefType.NULL); - } else if (NumberUtils.isCreatable(filterTimelife) && NumberUtils.isCreatable(filterUsed)) { - double used = Double.parseDouble(filterUsed); - double max = Double.parseDouble(filterTimelife); - double perc = (1 - ((double) used / max)) * 100; - updateState(remainingFilterChannelUID, new QuantityType<>(perc, Units.PERCENT)); } else { - updateState(remainingFilterChannelUID, UnDefType.UNDEF); + try { + double used = Double.parseDouble(filterUsed); + double max = Double.parseDouble(filterTimelife); + double perc = (1 - (used / max)) * 100; + updateState(remainingFilterChannelUID, new QuantityType<>(perc, Units.PERCENT)); + } catch (NumberFormatException ex) { + updateState(remainingFilterChannelUID, UnDefType.UNDEF); + } } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java index 610fa60068b61..3968123e5c077 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java @@ -14,6 +14,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotDefinition; /** * The {@link LGThinQBridge} @@ -24,7 +26,9 @@ public interface LGThinQBridge { void registerDiscoveryListener(LGThinqDiscoveryService listener); - void registryListenerThing(LGThinQAbstractDeviceHandler thing); + void registryListenerThing( + LGThinQAbstractDeviceHandler thing); - void unRegistryListenerThing(LGThinQAbstractDeviceHandler thing); + void unRegistryListenerThing( + LGThinQAbstractDeviceHandler thing); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index b5f6f16d24153..5bc28f7d1cff0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -17,24 +17,30 @@ import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Future; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantLock; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQBridgeConfiguration; -import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.discovery.LGThinqDiscoveryService; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory.LGThinQGeneralApiClientService; +import org.openhab.binding.lgthinq.lgservices.api.TokenManager; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotDefinition; import org.openhab.core.config.core.status.ConfigStatusMessage; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; @@ -55,28 +61,30 @@ @NonNullByDefault public class LGThinQBridgeHandler extends ConfigStatusBridgeHandler implements LGThinQBridge { - private Map lGDeviceRegister = new ConcurrentHashMap<>(); - private Map lastDevicesDiscovered = new ConcurrentHashMap<>(); + private final Map> lGDeviceRegister = new ConcurrentHashMap<>(); + private final Map lastDevicesDiscovered = new ConcurrentHashMap<>(); + private static final LGThinqDiscoveryService DUMMY_DISCOVERY_SERVICE = new LGThinqDiscoveryService(); static { var logger = LoggerFactory.getLogger(LGThinQBridgeHandler.class); try { File directory = new File(THINQ_USER_DATA_FOLDER); if (!directory.exists()) { - directory.mkdir(); + if (!directory.mkdir()) { + throw new LGThinqException("Can't create directory for userdata thinq"); + } } } catch (Exception e) { logger.warn("Unable to setup thinq userdata directory: {}", e.getMessage()); } } private final Logger logger = LoggerFactory.getLogger(LGThinQBridgeHandler.class); - private @Nullable LGThinQBridgeConfiguration lgthinqConfig; - private TokenManager tokenManager; - private @Nullable LGThinqDiscoveryService discoveryService; - private @Nullable LGThinQGeneralApiClientService lgApiClient; - private @Nullable Future initJob; + private LGThinQBridgeConfiguration lgthinqConfig = new LGThinQBridgeConfiguration(); + private final TokenManager tokenManager; + private LGThinqDiscoveryService discoveryService = DUMMY_DISCOVERY_SERVICE; + private final @Nullable LGThinQGeneralApiClientService lgApiClient; private @Nullable ScheduledFuture devicePollingJob; - private final @NonNull HttpClientFactory httpClientFactory; + private final HttpClientFactory httpClientFactory; public LGThinQBridgeHandler(Bridge bridge, HttpClientFactory httpClientFactory) { super(bridge); @@ -97,7 +105,7 @@ public HttpClientFactory getHttpClientFactory() { */ abstract class PollingRunnable implements Runnable { protected final String bridgeName; - protected @Nullable LGThinQBridgeConfiguration lgthinqConfig; + protected LGThinQBridgeConfiguration lgthinqConfig = new LGThinQBridgeConfiguration(); PollingRunnable(String bridgeName) { this.bridgeName = bridgeName; @@ -151,6 +159,7 @@ public void run() { try { doConnectedRun(); } catch (Exception e) { + logger.error("Error getting device list from LG account", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.lgapi-getting-devices"); } @@ -160,34 +169,38 @@ public void run() { } } - protected abstract void doConnectedRun() throws IOException, LGThinqException; + protected abstract void doConnectedRun() throws LGThinqException; } @Override public void registerDiscoveryListener(LGThinqDiscoveryService listener) { - if (discoveryService == null) { - discoveryService = listener; - } + discoveryService = listener; } @Override - public void registryListenerThing(LGThinQAbstractDeviceHandler thing) { + public void registryListenerThing( + LGThinQAbstractDeviceHandler thing) { if (lGDeviceRegister.get(thing.getDeviceId()) == null) { lGDeviceRegister.put(thing.getDeviceId(), thing); // remove device from discovery list, if exists. LGDevice device = lastDevicesDiscovered.get(thing.getDeviceId()); - if (device != null && discoveryService != null) { + if (device != null && discoveryService != DUMMY_DISCOVERY_SERVICE) { discoveryService.removeLgDeviceDiscovery(device); } } } @Override - public void unRegistryListenerThing(LGThinQAbstractDeviceHandler thing) { + public void unRegistryListenerThing( + LGThinQAbstractDeviceHandler thing) { lGDeviceRegister.remove(thing.getDeviceId()); } - private LGDevicePollingRunnable lgDevicePollingRunnable; + private final LGDevicePollingRunnable lgDevicePollingRunnable; + + private LGThinQGeneralApiClientService getLgApiClient() { + return Objects.requireNonNull(lgApiClient, "Not expected lgApiClient null. It most likely a bug"); + } class LGDevicePollingRunnable extends PollingRunnable { public LGDevicePollingRunnable(String bridgeName) { @@ -197,18 +210,18 @@ public LGDevicePollingRunnable(String bridgeName) { @Override protected void doConnectedRun() throws LGThinqException { Map lastDevicesDiscoveredCopy = new HashMap<>(lastDevicesDiscovered); - List devices = lgApiClient.listAccountDevices(bridgeName); + List devices = getLgApiClient().listAccountDevices(bridgeName); // if not registered yet, and not discovered before, then add to discovery list. devices.forEach(device -> { String deviceId = device.getDeviceId(); logger.debug("Device found: {}", deviceId); if (lGDeviceRegister.get(deviceId) == null && !lastDevicesDiscovered.containsKey(deviceId)) { logger.debug("Adding new LG Device to things registry with id:{}", deviceId); - if (discoveryService != null) { + if (discoveryService != DUMMY_DISCOVERY_SERVICE) { discoveryService.addLgDeviceDiscovery(device); } } else { - if (discoveryService != null && lGDeviceRegister.get(deviceId) != null) { + if (discoveryService != DUMMY_DISCOVERY_SERVICE && lGDeviceRegister.get(deviceId) != null) { discoveryService.removeLgDeviceDiscovery(device); } } @@ -220,18 +233,19 @@ protected void doConnectedRun() throws LGThinqException { logger.debug("LG Device '{}' removed.", deviceId); lastDevicesDiscovered.remove(deviceId); - LGThinQAbstractDeviceHandler deviceThing = lGDeviceRegister.get(deviceId); + LGThinQAbstractDeviceHandler deviceThing = lGDeviceRegister + .get(deviceId); if (deviceThing != null) { deviceThing.onDeviceRemoved(); } - if (discoveryService != null && deviceThing != null) { + if (discoveryService != DUMMY_DISCOVERY_SERVICE && deviceThing != null) { discoveryService.removeLgDeviceDiscovery(device); } }); lGDeviceRegister.values().forEach(LGThinQAbstractDeviceHandler::refreshStatus); } - }; + } @Override public Collection getConfigStatus() { @@ -257,14 +271,18 @@ public Collection getConfigStatus() { } @Override + @SuppressWarnings("null") public void handleRemoval() { - if (devicePollingJob != null) + if (devicePollingJob != null) { devicePollingJob.cancel(true); - tokenManager.cleanupTokenRegistry(getBridge().getUID().getId()); + } + tokenManager.cleanupTokenRegistry( + Objects.requireNonNull(getBridge(), "Not expected bridge null here").getUID().getId()); super.handleRemoval(); } @Override + @SuppressWarnings("null") public void dispose() { if (devicePollingJob != null) { devicePollingJob.cancel(true); @@ -305,6 +323,7 @@ public void handleConfigurationUpdate(Map configurationParameter super.handleConfigurationUpdate(configurationParameters); } + @SuppressWarnings("null") private void startLGThinqDevicePolling() { // stop current scheduler, if any if (devicePollingJob != null && !devicePollingJob.isDone()) { @@ -337,13 +356,8 @@ public void runDiscovery() { public void handleCommand(ChannelUID channelUID, Command command) { } - public boolean unregisterDiscoveryListener() { - if (discoveryService != null) { - discoveryService = null; - return true; - } - - return false; + public void unregisterDiscoveryListener() { + discoveryService = DUMMY_DISCOVERY_SERVICE; } /** diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java index 93c18c635bfe4..56cee065e59bc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -12,28 +12,41 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_POWER_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DASHBOARD_GRP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_COURSE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_DOOR_LOCK_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_PROCESS_STATE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMAIN_TIME_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_SMART_COURSE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_STATE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_DEVICE_ALIAS; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_MODEL_URL_INFO; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_WASHING_MACHINE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_WASHING_TOWER; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_DW_DOOR_STATE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_DW_PROCESS_STATE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_DW_STATE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WMD_POWER_OFF_VALUE; import java.util.ArrayList; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQDishWasherApiClientService; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.Channel; import org.openhab.core.thing.ChannelGroupUID; import org.openhab.core.thing.ChannelUID; import org.openhab.core.thing.Thing; @@ -61,7 +74,7 @@ public class LGThinQDishWasherHandler extends LGThinQAbstractDeviceHandler dynChannels = new ArrayList<>(); - // only can have remote start channel is the WM is not in sleep mode, and remote start is enabled. } @Override @@ -146,23 +156,18 @@ protected DeviceTypes getDeviceType() { } @Override - protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { + protected void processCommand(AsyncCommandParams params) { logger.error("Command {} to the channel {} not supported. Ignored.", params.command, params.channelUID); } - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - @Override public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + return emptyIfNull(getThing().getProperties().get(PROP_INFO_DEVICE_ALIAS)); } @Override public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + return emptyIfNull(getThing().getProperties().get(PROP_INFO_MODEL_URL_INFO)); } @Override @@ -175,11 +180,11 @@ public void onDeviceRemoved() { */ @Override public void onDeviceDisconnected() { - updateState(CHANNEL_POWER_ID, OnOffType.OFF); - updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); - updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); - updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); + updateState(CHANNEL_AC_POWER_ID, OnOffType.OFF); + updateState(CHANNEL_WMD_STATE_ID, new StringType(WMD_POWER_OFF_VALUE)); + updateState(CHANNEL_WMD_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(CHANNEL_WMD_SMART_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(CHANNEL_WMD_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); + updateState(CHANNEL_WMD_REMAIN_TIME_ID, new StringType("00:00")); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index b0e051d0acd15..8a26631187f13 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -12,7 +12,32 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DASHBOARD_GRP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_EXTENDED_INFO_GRP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_ACTIVE_SAVING; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_DOOR_OPEN; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_EXPRESS_COOL_MODE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_EXPRESS_FREEZE_MODE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_FREEZER_TEMP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_FRESH_AIR_FILTER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_FRIDGE_TEMP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_ICE_PLUS; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_REF_TEMP_UNIT; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_SMART_SAVING_MODE_V2; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_SMART_SAVING_SWITCH_V1; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_VACATION_MODE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_RE_WATER_FILTER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_DEVICE_ALIAS; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_MODEL_URL_INFO; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_FRESH_AIR_FILTER_MAP; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_WATER_FILTER; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_PLATFORM_TYPE_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_CELSIUS_UNIT_VALUES; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_DOOR_CLOSE_VALUES; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_DOOR_OPEN_VALUES; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_FAHRENHEIT_UNIT_VALUES; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_TEMP_UNIT_CELSIUS; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_TEMP_UNIT_FAHRENHEIT; import java.util.ArrayList; import java.util.HashMap; @@ -23,18 +48,20 @@ import javax.measure.quantity.Temperature; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQFridgeApiClientService; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; -import org.openhab.binding.lgthinq.lgservices.model.LGDevice; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; import org.openhab.core.io.net.http.HttpClientFactory; -import org.openhab.core.library.types.*; +import org.openhab.core.library.types.DecimalType; +import org.openhab.core.library.types.OnOffType; +import org.openhab.core.library.types.OpenClosedType; +import org.openhab.core.library.types.QuantityType; +import org.openhab.core.library.types.StringType; import org.openhab.core.library.unit.ImperialUnits; import org.openhab.core.library.unit.SIUnits; import org.openhab.core.thing.ChannelGroupUID; @@ -71,9 +98,9 @@ public class LGThinQFridgeHandler extends LGThinQAbstractDeviceHandler getTemperatureUnit(FridgeCanonicalSnapshot shot) { - if (!(CELSIUS_UNIT_VALUES.contains(shot.getTempUnit()) - || FAHRENHEIT_UNIT_VALUES.contains(shot.getTempUnit()))) { + if (!(RE_CELSIUS_UNIT_VALUES.contains(shot.getTempUnit()) + || RE_FAHRENHEIT_UNIT_VALUES.contains(shot.getTempUnit()))) { logger.warn( "Temperature Unit not recognized (must be Celsius or Fahrenheit). Ignoring and considering Celsius as default"); return SIUnits.CELSIUS; } - return CELSIUS_UNIT_VALUES.contains(shot.getTempUnit()) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; + return RE_CELSIUS_UNIT_VALUES.contains(shot.getTempUnit()) ? SIUnits.CELSIUS : ImperialUnits.FAHRENHEIT; } @Override @@ -153,9 +180,9 @@ protected void updateDeviceChannels(FridgeCanonicalSnapshot shot) { } private State parseDoorStatus(String doorStatus) { - if (DOOR_CLOSE_FR_VALUES.contains(doorStatus)) { + if (RE_DOOR_CLOSE_VALUES.contains(doorStatus)) { return OpenClosedType.CLOSED; - } else if (DOOR_OPEN_FR_VALUES.contains(doorStatus)) { + } else if (RE_DOOR_OPEN_VALUES.contains(doorStatus)) { return OpenClosedType.OPEN; } else { return UnDefType.UNDEF; @@ -163,7 +190,7 @@ private State parseDoorStatus(String doorStatus) { } protected Integer decodeTempValue(ChannelUID ch, Integer value) { - FridgeCapability refCap = null; + FridgeCapability refCap; try { refCap = getCapabilities(); } catch (LGThinqApiException e) { @@ -171,16 +198,8 @@ protected Integer decodeTempValue(ChannelUID ch, Integer value) { return 0; } // temperature channels are little different. First we need to get the tempUnit in the first snapshot, - Map convertionMap; - if (fridgeTempChannelUID.equals(ch)) { - convertionMap = TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFridgeTempFMap() - : refCap.getFridgeTempCMap(); - } else if (freezerTempChannelUID.equals(ch)) { - convertionMap = TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFreezerTempFMap() - : refCap.getFreezerTempCMap(); - } else { - throw new IllegalStateException("Conversion Map Channel temperature not mapped. It's most likely a bug"); - } + Map convertionMap = new HashMap<>(); + convertionMap = getConvertionMap(ch, refCap); String strValue = convertionMap.get(value.toString()); if (strValue == null) { logger.error( @@ -197,7 +216,7 @@ protected Integer decodeTempValue(ChannelUID ch, Integer value) { } protected Integer encodeTempValue(ChannelUID ch, Integer value) { - FridgeCapability refCap = null; + FridgeCapability refCap; try { refCap = getCapabilities(); } catch (LGThinqApiException e) { @@ -205,17 +224,9 @@ protected Integer encodeTempValue(ChannelUID ch, Integer value) { return 0; } // temperature channels are little different. First we need to get the tempUnit in the first snapshot, - final Map convertionMap, invertedMap; - if (fridgeTempChannelUID.equals(ch)) { - convertionMap = TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFridgeTempFMap() - : refCap.getFridgeTempCMap(); - } else if (freezerTempChannelUID.equals(ch)) { - convertionMap = TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFreezerTempFMap() - : refCap.getFreezerTempCMap(); - } else { - throw new IllegalStateException("Conversion Map Channel temperature not mapped. It's most likely a bug"); - } - invertedMap = new HashMap<>(); + final Map convertionMap = new HashMap<>(); + getConvertionMap(ch, refCap); + final Map invertedMap = new HashMap<>(); convertionMap.forEach((k, v) -> { invertedMap.put(v, k); }); @@ -233,6 +244,20 @@ protected Integer encodeTempValue(ChannelUID ch, Integer value) { } } + private Map getConvertionMap(ChannelUID ch, FridgeCapability refCap) { + Map convertionMap; + if (fridgeTempChannelUID.equals(ch)) { + convertionMap = RE_TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFridgeTempFMap() + : refCap.getFridgeTempCMap(); + } else if (freezerTempChannelUID.equals(ch)) { + convertionMap = RE_TEMP_UNIT_FAHRENHEIT.equals(tempUnit) ? refCap.getFreezerTempFMap() + : refCap.getFreezerTempCMap(); + } else { + throw new IllegalStateException("Conversion Map Channel temperature not mapped. It's most likely a bug"); + } + return convertionMap; + } + @Override public LGThinQApiClientService getLgThinQAPIClientService() { return lgThinqFridgeApiClientService; @@ -247,19 +272,14 @@ protected DeviceTypes getDeviceType() { return DeviceTypes.AIR_CONDITIONER; } - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - @Override public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + return emptyIfNull(getThing().getProperties().get(PROP_INFO_DEVICE_ALIAS)); } @Override public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + return emptyIfNull(getThing().getProperties().get(PROP_INFO_MODEL_URL_INFO)); } @Override @@ -275,12 +295,12 @@ public void onDeviceDisconnected() { @Override public void updateChannelDynStateDescription() throws LGThinqApiException { FridgeCapability cap = getCapabilities(); - manageDynChannel(icePlusChannelUID, FR_CHANNEL_ICE_PLUS, "Switch", !cap.getIcePlusMap().isEmpty()); - manageDynChannel(expressFreezeModeChannelUID, FR_CHANNEL_EXPRESS_FREEZE_MODE, "String", + manageDynChannel(icePlusChannelUID, CHANNEL_RE_ICE_PLUS, "Switch", !cap.getIcePlusMap().isEmpty()); + manageDynChannel(expressFreezeModeChannelUID, CHANNEL_RE_EXPRESS_FREEZE_MODE, "String", !cap.getExpressFreezeModeMap().isEmpty()); - manageDynChannel(expressCoolModeChannelUID, FR_CHANNEL_EXPRESS_COOL_MODE, "Switch", + manageDynChannel(expressCoolModeChannelUID, CHANNEL_RE_EXPRESS_COOL_MODE, "Switch", cap.isExpressCoolModePresent()); - manageDynChannel(vacationModeChannelUID, FR_CHANNEL_VACATION_MODE, "Switch", cap.isEcoFriendlyModePresent()); + manageDynChannel(vacationModeChannelUID, CHANNEL_RE_VACATION_MODE, "Switch", cap.isEcoFriendlyModePresent()); Unit unTemp = getTemperatureUnit(getLastShot()); if (SIUnits.CELSIUS.equals(unTemp)) { @@ -295,19 +315,20 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { loadChannelStateOption(cap.getActiveSavingMap(), activeSavingChannelUID); loadChannelStateOption(cap.getSmartSavingMap(), smartSavingModeChannelUID); loadChannelStateOption(cap.getTempUnitMap(), tempUnitUID); - loadChannelStateOption(CAP_FR_FRESH_AIR_FILTER_MAP, freshAirFilterChannelUID); - loadChannelStateOption(CAP_FR_WATER_FILTER, waterFilterChannelUID); + loadChannelStateOption(CAP_RE_FRESH_AIR_FILTER_MAP, freshAirFilterChannelUID); + loadChannelStateOption(CAP_RE_WATER_FILTER, waterFilterChannelUID); } private void loadChannelStateOption(Map cap, ChannelUID channelUID) { - loadChannelStateOption(cap, channelUID, null); + final List faOptions = new ArrayList<>(); + cap.forEach((k, v) -> faOptions.add(new StateOption(k, v))); + stateDescriptionProvider.setStateOptions(channelUID, faOptions); } private void loadChannelTempStateOption(Map cap, ChannelUID channelUID, Unit unTemp) { final List faOptions = new ArrayList<>(); cap.forEach((k, v) -> { try { - Integer vInt = Integer.valueOf(v); QuantityType t = new QuantityType<>(Integer.valueOf(v), unTemp); faOptions.add(new StateOption(t.toString(), t.toString())); } catch (NumberFormatException ex) { @@ -317,13 +338,6 @@ private void loadChannelTempStateOption(Map cap, ChannelUID chan stateDescriptionProvider.setStateOptions(channelUID, faOptions); } - private void loadChannelStateOption(Map cap, ChannelUID channelUID, - @Nullable Map decodeMap) { - final List faOptions = new ArrayList<>(); - cap.forEach((k, v) -> faOptions.add(new StateOption(k, decodeMap == null ? v : decodeMap.get(v)))); - stateDescriptionProvider.setStateOptions(channelUID, faOptions); - } - @Override protected void processCommand(AsyncCommandParams params) throws LGThinqApiException { FridgeCanonicalSnapshot lastShot = getLastShot(); @@ -332,8 +346,8 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept String simpleChannelUID; simpleChannelUID = getSimpleChannelUID(params.channelUID); switch (simpleChannelUID) { - case FR_CHANNEL_FREEZER_TEMP_ID: - case FR_CHANNEL_FRIDGE_TEMP_ID: { + case CHANNEL_RE_FREEZER_TEMP_ID: + case CHANNEL_RE_FRIDGE_TEMP_ID: { int targetTemp; if (command instanceof DecimalType) { targetTemp = ((DecimalType) command).intValue(); @@ -344,7 +358,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept break; } - if (FR_CHANNEL_FRIDGE_TEMP_ID.equals(simpleChannelUID)) { + if (CHANNEL_RE_FRIDGE_TEMP_ID.equals(simpleChannelUID)) { targetTemp = encodeTempValue(fridgeTempChannelUID, targetTemp); lgThinqFridgeApiClientService.setFridgeTemperature(getBridgeId(), getDeviceId(), getCapabilities(), targetTemp, lastShot.getTempUnit(), cmdSnap); @@ -355,7 +369,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept } break; } - case FR_CHANNEL_ICE_PLUS: { + case CHANNEL_RE_ICE_PLUS: { if (command instanceof OnOffType) { lgThinqFridgeApiClientService.setIcePlus(getBridgeId(), getDeviceId(), getCapabilities(), OnOffType.ON.equals(command), cmdSnap); @@ -364,7 +378,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept } break; } - case FR_CHANNEL_EXPRESS_FREEZE_MODE: { + case CHANNEL_RE_EXPRESS_FREEZE_MODE: { String targetExpressMode; if (command instanceof StringType) { targetExpressMode = ((StringType) command).toString(); @@ -376,7 +390,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept lgThinqFridgeApiClientService.setExpressMode(getBridgeId(), getDeviceId(), targetExpressMode); break; } - case FR_CHANNEL_EXPRESS_COOL_MODE: { + case CHANNEL_RE_EXPRESS_COOL_MODE: { if (command instanceof OnOffType) { lgThinqFridgeApiClientService.setExpressCoolMode(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); @@ -386,7 +400,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept } break; } - case FR_CHANNEL_VACATION_MODE: { + case CHANNEL_RE_VACATION_MODE: { if (command instanceof OnOffType) { lgThinqFridgeApiClientService.setEcoFriendlyMode(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index 3b361d4fc2e48..ae835be84455a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -12,23 +12,64 @@ */ package org.openhab.binding.lgthinq.internal.handler; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_POWER_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DASHBOARD_GRP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DR_CHILD_LOCK_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DR_DRY_LEVEL_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_COURSE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_DELAY_TIME_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_DOOR_LOCK_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_PROCESS_STATE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMAIN_TIME_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_COURSE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_GRP_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_START_STOP; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_RINSE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_SMART_COURSE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_SPIN_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_STAND_BY_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_STATE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_TEMP_LEVEL_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_RINSE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_SPIN; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_TEMP; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_DEVICE_ALIAS; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_MODEL_URL_INFO; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_DRYER; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_WASHING_MACHINE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_WASHING_TOWER; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_DR_DRY_LEVEL; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_WMD_PROCESS_STATE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_WMD_STATE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_WMD_TEMPERATURE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_WM_DICT_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_WM_RINSE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_WM_SPIN; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WMD_COURSE_NOT_SELECTED_VALUE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WMD_POWER_OFF_VALUE; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseFunction; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseType; @@ -37,7 +78,11 @@ import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.library.types.OnOffType; import org.openhab.core.library.types.StringType; -import org.openhab.core.thing.*; +import org.openhab.core.thing.Channel; +import org.openhab.core.thing.ChannelGroupUID; +import org.openhab.core.thing.ChannelUID; +import org.openhab.core.thing.Thing; +import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.binding.ThingHandlerCallback; import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.thing.link.ItemChannelLinkRegistry; @@ -78,10 +123,7 @@ public class LGThinQWasherDryerHandler private final List remoteStartEnabledChannels = new CopyOnWriteArrayList<>(); - private final Map> cachedBitKeyDefinitions = new HashMap<>(); - private final Logger logger = LoggerFactory.getLogger(LGThinQWasherDryerHandler.class); - @NonNullByDefault private final LGThinQWMApiClientService lgThinqWMApiClientService; public LGThinQWasherDryerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, @@ -93,23 +135,23 @@ public LGThinQWasherDryerHandler(Thing thing, LGThinQStateDescriptionProvider st this.stateDescriptionProvider = stateDescriptionProvider; lgThinqWMApiClientService = LGThinQApiClientServiceFactory.newWMApiClientService(lgPlatformType, httpClientFactory); - channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), WM_CHANNEL_REMOTE_START_GRP_ID); + channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_WMD_REMOTE_START_GRP_ID); channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); - courseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_COURSE_ID); - dryLevelChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_DRY_LEVEL_ID); - stateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STATE_ID); - processStateChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_PROCESS_STATE_ID); - remainTimeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMAIN_TIME_ID); - delayTimeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_DELAY_TIME_ID); - temperatureChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_TEMP_LEVEL_ID); - doorLockChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_DOOR_LOCK_ID); - childLockChannelUID = new ChannelUID(channelGroupDashboardUID, DR_CHANNEL_CHILD_LOCK_ID); - rinseChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_RINSE_ID); - spinChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_SPIN_ID); - standByModeChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_STAND_BY_ID); - remoteStartFlagChannelUID = new ChannelUID(channelGroupDashboardUID, WM_CHANNEL_REMOTE_START_ID); - remoteStartStopChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_START_START_STOP); - remoteStartCourseChannelUID = new ChannelUID(channelGroupRemoteStartUID, WM_CHANNEL_REMOTE_COURSE); + courseChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_COURSE_ID); + dryLevelChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_DR_DRY_LEVEL_ID); + stateChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_STATE_ID); + processStateChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_PROCESS_STATE_ID); + remainTimeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_REMAIN_TIME_ID); + delayTimeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_DELAY_TIME_ID); + temperatureChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_TEMP_LEVEL_ID); + doorLockChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_DOOR_LOCK_ID); + childLockChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_DR_CHILD_LOCK_ID); + rinseChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_RINSE_ID); + spinChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_SPIN_ID); + standByModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_STAND_BY_ID); + remoteStartFlagChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_REMOTE_START_ID); + remoteStartStopChannelUID = new ChannelUID(channelGroupRemoteStartUID, CHANNEL_WMD_REMOTE_START_START_STOP); + remoteStartCourseChannelUID = new ChannelUID(channelGroupRemoteStartUID, CHANNEL_WMD_REMOTE_COURSE); } @Override @@ -133,14 +175,14 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { List options = new ArrayList<>(); wmCap.getStateFeat().getValuesMapping() - .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_STATE, v)))); + .forEach((k, v) -> options.add(new StateOption(k, keyIfValueNotFound(CAP_WMD_STATE, v)))); stateDescriptionProvider.setStateOptions(stateChannelUID, options); loadOptionsCourse(wmCap, courseChannelUID); List optionsTemp = new ArrayList<>(); wmCap.getTemperatureFeat().getValuesMapping() - .forEach((k, v) -> optionsTemp.add(new StateOption(k, keyIfValueNotFound(CAP_WM_TEMPERATURE, v)))); + .forEach((k, v) -> optionsTemp.add(new StateOption(k, keyIfValueNotFound(CAP_WMD_TEMPERATURE, v)))); stateDescriptionProvider.setStateOptions(temperatureChannelUID, optionsTemp); List optionsDoor = new ArrayList<>(); @@ -160,7 +202,7 @@ public void updateChannelDynStateDescription() throws LGThinqApiException { List optionsPre = new ArrayList<>(); wmCap.getProcessState().getValuesMapping() - .forEach((k, v) -> optionsPre.add(new StateOption(k, keyIfValueNotFound(CAP_WDM_PROCESS_STATE, v)))); + .forEach((k, v) -> optionsPre.add(new StateOption(k, keyIfValueNotFound(CAP_WMD_PROCESS_STATE, v)))); stateDescriptionProvider.setStateOptions(processStateChannelUID, optionsPre); List optionsChildLock = new ArrayList<>(); @@ -184,22 +226,9 @@ protected Logger getLogger() { return logger; } - private ZonedDateTime getZonedDateTime(String minutesAndSeconds) { - if (minutesAndSeconds.length() != 5) { - logger.error("Washer/Disher remain/delay time is not in standard MM:SS. Value received: {}. Reset to 00:00", - minutesAndSeconds); - minutesAndSeconds = "00:00"; - } - String min = minutesAndSeconds.substring(0, 2); - String sec = minutesAndSeconds.substring(3); - - return ZonedDateTime.of(1970, 1, 1, 0, Integer.parseInt(min), Integer.parseInt(sec), 0, ZoneId.systemDefault()); - } - @Override - protected void updateDeviceChannels(WasherDryerSnapshot shot) { - WasherDryerSnapshot lastShot = getLastShot(); - updateState("dashboard#" + CHANNEL_POWER_ID, + protected void updateDeviceChannels(WasherDryerSnapshot shot) throws LGThinqApiException { + updateState("dashboard#" + CHANNEL_AC_POWER_ID, (DevicePowerState.DV_POWER_ON.equals(shot.getPowerStatus()) ? OnOffType.ON : OnOffType.OFF)); updateState(stateChannelUID, new StringType(shot.getState())); updateState(processStateChannelUID, new StringType(shot.getProcessState())); @@ -221,62 +250,59 @@ protected void updateDeviceChannels(WasherDryerSnapshot shot) { ThingHandlerCallback callback = getCallback(); if (rsStartStopChannel == null && callback != null) { // === creating channel LaunchRemote - dynChannels - .add(createDynChannel(WM_CHANNEL_REMOTE_START_START_STOP, remoteStartStopChannelUID, "Switch")); - dynChannels.add(createDynChannel(WM_CHANNEL_REMOTE_COURSE, remoteStartCourseChannelUID, "String")); + dynChannels.add( + createDynChannel(CHANNEL_WMD_REMOTE_START_START_STOP, remoteStartStopChannelUID, "Switch")); + dynChannels.add(createDynChannel(CHANNEL_WMD_REMOTE_COURSE, remoteStartCourseChannelUID, "String")); // Just enabled remote start. Then is Off updateState(remoteStartStopChannelUID, OnOffType.OFF); // === creating selectable channels for the Course (if any) - try { - WasherDryerCapability cap = getCapabilities(); - // TODO - V1 - App will always get the default course, and V2 ? - loadOptionsCourse(cap, remoteStartCourseChannelUID); - updateState(remoteStartCourseChannelUID, new StringType(cap.getDefaultCourseId())); - - CourseDefinition courseDef = cap.getCourses().get(cap.getDefaultCourseId()); - if (WM_COURSE_NOT_SELECTED_VALUE.equals(shot.getSmartCourse()) && courseDef != null) { - // only create selectable channels if the course is not a smart course. Smart courses have - // already predefined - // the functions values - for (CourseFunction f : courseDef.getFunctions()) { - if (!f.isSelectable()) { - // only for selectable features - continue; - } - // handle well know dynamic fields - FeatureDefinition fd = cap.getFeatureDefinition(f.getValue()); - ChannelUID targetChannel = null; - ChannelUID refChannel = null; - if (!FeatureDefinition.NULL_DEFINITION.equals(fd)) { - targetChannel = new ChannelUID(channelGroupRemoteStartUID, fd.getChannelId()); - refChannel = new ChannelUID(channelGroupDashboardUID, fd.getRefChannelId()); - dynChannels.add(createDynChannel(fd.getChannelId(), targetChannel, - translateFeatureToItemType(fd.getDataType()))); - if (CAP_WM_DICT_V2.containsKey(f.getValue())) { - // if the function has translation dictionary (I hope so), then the values in - // the selectable channel will be translated to something more readable - List options = new ArrayList<>(); - for (String v : f.getSelectableValues()) { - Map values = CAP_WM_DICT_V2.get(f.getValue()); - if (values != null) { - // Canonical Value is the KEY (@...) that represents a constant in the - // definition - // that can be translated to a human description - String canonicalValue = Objects - .requireNonNullElse(fd.getValuesMapping().get(v), v); - options.add(new StateOption(v, keyIfValueNotFound(values, canonicalValue))); - stateDescriptionProvider.setStateOptions(targetChannel, options); - } + WasherDryerCapability cap = getCapabilities(); + // TODO - V1 - App will always get the default course, and V2 ? + loadOptionsCourse(cap, remoteStartCourseChannelUID); + updateState(remoteStartCourseChannelUID, new StringType(cap.getDefaultCourseId())); + + CourseDefinition courseDef = cap.getCourses().get(cap.getDefaultCourseId()); + if (WMD_COURSE_NOT_SELECTED_VALUE.equals(shot.getSmartCourse()) && courseDef != null) { + // only create selectable channels if the course is not a smart course. Smart courses have + // already predefined + // the functions values + for (CourseFunction f : courseDef.getFunctions()) { + if (!f.isSelectable()) { + // only for selectable features + continue; + } + // handle well know dynamic fields + FeatureDefinition fd = cap.getFeatureDefinition(f.getValue()); + ChannelUID targetChannel; + ChannelUID refChannel; + if (!FeatureDefinition.NULL_DEFINITION.equals(fd)) { + targetChannel = new ChannelUID(channelGroupRemoteStartUID, fd.getChannelId()); + refChannel = new ChannelUID(channelGroupDashboardUID, fd.getRefChannelId()); + dynChannels.add(createDynChannel(fd.getChannelId(), targetChannel, + translateFeatureToItemType(fd.getDataType()))); + if (CAP_WM_DICT_V2.containsKey(f.getValue())) { + // if the function has translation dictionary (I hope so), then the values in + // the selectable channel will be translated to something more readable + List options = new ArrayList<>(); + for (String v : f.getSelectableValues()) { + Map values = CAP_WM_DICT_V2.get(f.getValue()); + if (values != null) { + // Canonical Value is the KEY (@...) that represents a constant in the + // definition + // that can be translated to a human description + String canonicalValue = Objects.requireNonNullElse(fd.getValuesMapping().get(v), + v); + options.add(new StateOption(v, keyIfValueNotFound(values, canonicalValue))); + stateDescriptionProvider.setStateOptions(targetChannel, options); } } - // update state with the default referenced channel - updateState(targetChannel, new StringType(getItemLinkedValue(refChannel))); } + // update state with the default referenced channel + updateState(targetChannel, new StringType(getItemLinkedValue(refChannel))); } } - } catch (LGThinqApiException e) { - throw new RuntimeException(e); } + remoteStartEnabledChannels.addAll(dynChannels); } @@ -334,7 +360,9 @@ private Map getRemoteStartData() throws LGThinqApiException { String smartCourse = lastShot.getSmartCourse(); data.put(cap.getDefaultCourseFieldName(), selectedCourse); data.put(cap.getDefaultSmartCourseFeatName(), smartCourse); - CourseType courseType = cap.getCourses().get("NOT_SELECTED".equals(smartCourse) ? selectedCourse : smartCourse) + CourseType courseType = Objects + .requireNonNull(cap.getCourses().get("NOT_SELECTED".equals(smartCourse) ? selectedCourse : smartCourse), + "NOT_SELECTED should be hardcoded. It most likely a bug") .getCourseType(); data.put("courseType", courseType.getValue()); // 3rd - replace custom selectable features with channel's ones. @@ -342,13 +370,13 @@ private Map getRemoteStartData() throws LGThinqApiException { String value = Objects.requireNonNullElse(getItemLinkedValue(c.getUID()), ""); String simpleChannelUID = getSimpleChannelUID(c.getUID().getId()); switch (simpleChannelUID) { - case WM_CHANNEL_REMOTE_START_RINSE: + case CHANNEL_WM_REMOTE_START_RINSE: data.put(cap.getRinseFeat().getName(), value); break; - case WM_CHANNEL_REMOTE_START_TEMP: + case CHANNEL_WM_REMOTE_START_TEMP: data.put(cap.getTemperatureFeat().getName(), value); break; - case WM_CHANNEL_REMOTE_START_SPIN: + case CHANNEL_WM_REMOTE_START_SPIN: data.put(cap.getSpinFeat().getName(), value); break; default: @@ -366,7 +394,7 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa String simpleChannelUID; simpleChannelUID = getSimpleChannelUID(params.channelUID); switch (simpleChannelUID) { - case WM_CHANNEL_REMOTE_START_START_STOP: { + case CHANNEL_WMD_REMOTE_START_START_STOP: { if (command instanceof OnOffType) { if (OnOffType.ON.equals(command)) { if (!lastShot.isStandBy()) { @@ -384,7 +412,7 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa } break; } - case WM_CHANNEL_STAND_BY_ID: { + case CHANNEL_WMD_STAND_BY_ID: { if (command instanceof OnOffType) { lgThinqWMApiClientService.wakeUp(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); } else { @@ -398,19 +426,14 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa } } - @Override - public void onDeviceAdded(LGDevice device) { - // TODO - handle it. Think if it's needed - } - @Override public String getDeviceAlias() { - return emptyIfNull(getThing().getProperties().get(DEVICE_ALIAS)); + return emptyIfNull(getThing().getProperties().get(PROP_INFO_DEVICE_ALIAS)); } @Override public String getDeviceUriJsonConfig() { - return emptyIfNull(getThing().getProperties().get(MODEL_URL_INFO)); + return emptyIfNull(getThing().getProperties().get(PROP_INFO_MODEL_URL_INFO)); } @Override @@ -423,12 +446,12 @@ public void onDeviceRemoved() { */ @Override public void onDeviceDisconnected() { - updateState(CHANNEL_POWER_ID, OnOffType.OFF); - updateState(WM_CHANNEL_STATE_ID, new StringType(WM_POWER_OFF_VALUE)); - updateState(WM_CHANNEL_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_SMART_COURSE_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); - updateState(WM_CHANNEL_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); - updateState(WM_CHANNEL_REMAIN_TIME_ID, new StringType("00:00")); + updateState(CHANNEL_AC_POWER_ID, OnOffType.OFF); + updateState(CHANNEL_WMD_STATE_ID, new StringType(WMD_POWER_OFF_VALUE)); + updateState(CHANNEL_WMD_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(CHANNEL_WMD_SMART_COURSE_ID, new StringType("NOT_SELECTED")); + updateState(CHANNEL_WMD_TEMP_LEVEL_ID, new StringType("NOT_SELECTED")); + updateState(CHANNEL_WMD_DOOR_LOCK_ID, new StringType("DOOR_LOCK_OFF")); + updateState(CHANNEL_WMD_REMAIN_TIME_ID, new StringType("00:00")); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java index c6e6161bfc365..0a59b00de31e5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java @@ -12,10 +12,10 @@ */ package org.openhab.binding.lgthinq.internal.model; +import java.util.ArrayList; import java.util.List; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; import org.openhab.core.config.core.ConfigDescriptionParameter.Type; import org.openhab.core.config.core.ParameterOption; @@ -32,34 +32,34 @@ public class DeviceParameter { private final String label; private final String description; private final String defaultValue; - @Nullable - private final List options; + private DeviceParameterGroup group = new DeviceParameterGroup("", ""); + private final List options = new ArrayList(); private final boolean isReadOnly; - @Nullable - DeviceParameterGroup group; public DeviceParameter(String name, Type type, String label, String description, String defaultValue, - @Nullable List options, boolean isReadOnly) { + List options, boolean isReadOnly) { this.name = name; this.type = type; this.label = label; this.description = description; this.defaultValue = defaultValue; - this.options = options; + this.options.addAll(options); this.isReadOnly = isReadOnly; } - @Nullable - public List getOptions() { - return options; - } - - @Nullable public DeviceParameterGroup getGroup() { return group; } + public void setGroup(DeviceParameterGroup group) { + this.group = group; + } + + public List getOptions() { + return options; + } + public boolean isReadOnly() { return isReadOnly; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java index 1f7aff1c33a54..952ef769785d4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java @@ -26,9 +26,7 @@ @NonNullByDefault public interface ThinqChannelGroupTypeProvider extends ChannelGroupTypeProvider { - public void addChannelGroupType(ChannelGroupType channelGroupType); + void addChannelGroupType(ChannelGroupType channelGroupType); - public void removeChannelGroupType(ChannelGroupType channelGroupType); - - public List internalGroupTypes(); + List internalGroupTypes(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java index 9ebfa9466963d..d291b21e26222 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java @@ -23,5 +23,5 @@ */ @NonNullByDefault public interface ThinqChannelTypeProvider extends ChannelTypeProvider { - public void addChannelType(final ChannelType channelType); + void addChannelType(final ChannelType channelType); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java index 2eeaf865d1bc6..e2c699faf8ddc 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java @@ -24,5 +24,5 @@ @NonNullByDefault public interface ThinqConfigDescriptionProvider extends ConfigDescriptionProvider { - public void addConfigDescription(ConfigDescription configDescription); + void addConfigDescription(ConfigDescription configDescription); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java index c36cd7690e6b8..baa2c9bbc1c75 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java @@ -24,5 +24,5 @@ @NonNullByDefault public interface ThinqThingTypeProvider extends ThingTypeProvider { - public void addThingType(ThingType thingType); + void addThingType(ThingType thingType); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java index 200d71f1cdb3f..982b14213427b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java @@ -13,7 +13,12 @@ package org.openhab.binding.lgthinq.internal.type; import java.net.URI; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -22,7 +27,13 @@ import org.openhab.core.config.core.ConfigDescriptionProvider; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.ThingTypeProvider; -import org.openhab.core.thing.type.*; +import org.openhab.core.thing.type.ChannelGroupType; +import org.openhab.core.thing.type.ChannelGroupTypeProvider; +import org.openhab.core.thing.type.ChannelGroupTypeUID; +import org.openhab.core.thing.type.ChannelType; +import org.openhab.core.thing.type.ChannelTypeProvider; +import org.openhab.core.thing.type.ChannelTypeUID; +import org.openhab.core.thing.type.ThingType; import org.osgi.service.component.annotations.Component; /** @@ -79,11 +90,6 @@ public void addChannelGroupType(ChannelGroupType channelGroupType) { channelGroupTypesByUID.put(channelGroupType.getUID(), channelGroupType); } - @Override - public void removeChannelGroupType(ChannelGroupType channelGroupType) { - channelGroupTypesByUID.remove(channelGroupType.getUID()); - } - @Override public List internalGroupTypes() { return new ArrayList<>(channelGroupTypesByUID.values()); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java index 1314ed99bd53f..6ae02eda4fc5e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java @@ -14,6 +14,9 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.BINDING_ID; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.model.ThinqChannel; import org.openhab.binding.lgthinq.internal.model.ThinqChannelGroup; import org.openhab.binding.lgthinq.internal.model.ThinqDevice; @@ -28,6 +31,7 @@ * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public class UidUtils { /** @@ -41,7 +45,10 @@ public static ThingTypeUID generateThingTypeUID(ThinqDevice device) { * Generates the ChannelTypeUID. */ public static ChannelTypeUID generateChannelTypeUID(ThinqChannel channel) { - return new ChannelTypeUID(BINDING_ID, String.format("%s_%s", channel.getDevice().getType(), channel.getName())); + return new ChannelTypeUID(BINDING_ID, + String.format("%s_%s", + Objects.requireNonNull(channel.getDevice(), "unexpected null device type here").getType(), + channel.getName())); } /** @@ -56,6 +63,6 @@ public static ChannelGroupTypeUID generateChannelGroupTypeUID(ThinqChannelGroup * Generates the ChannelUID for the given datapoint with channelNumber and datapointName. */ public static ChannelUID generateChannelUID(ThinqChannel dp, ThingUID thingUID) { - return new ChannelUID(thingUID, String.valueOf(dp.getName()), dp.getName()); + return new ChannelUID(thingUID, dp.getName(), dp.getName()); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGServicesConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGServicesConstants.java new file mode 100644 index 0000000000000..e8b6cd40f8dd2 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGServicesConstants.java @@ -0,0 +1,245 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices; + +import static java.util.Map.entry; + +import java.util.Map; +import java.util.Set; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The LGServicesConstants constants class for lg services + * + * @author Nemer Daud - Initial Contribution + */ +@NonNullByDefault +public class LGServicesConstants { + // Extended Info Attribute Constants + public static final String CAP_EXTRA_ATTR_INSTANT_POWER = "InOutInstantPower"; + public static final String CAP_EXTRA_ATTR_FILTER_MAX_TIME_TO_USE = "ChangePeriod"; + public static final String CAP_EXTRA_ATTR_FILTER_USED_TIME = "UseTime"; + public static final String LG_ROOT_TAG_V1 = "lgedmRoot"; + public static final String LG_API_V1_CONTROL_OP = "rti/rtiControl"; + // === LG API protocol constants + public static final String LG_API_API_KEY_V2 = "VGhpblEyLjAgU0VSVklDRQ=="; + public static final String LG_API_APPLICATION_KEY = "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"; + public static final String LG_API_APP_LEVEL = "PRD"; + public static final String LG_API_APP_OS = "ANDROID"; + public static final String LG_API_APP_TYPE = "NUTS"; + public static final String LG_API_APP_VER = "3.5.1200"; + // the client id is a SHA512 hash of the phone MFR,MODEL,SERIAL, + // and the build id of the thinq app it can also just be a random + // string, we use the same client id used for oauth + public static final String LG_API_CLIENT_ID = "LGAO221A02"; + public static final String LG_API_DATE_FORMAT = "EEE, dd MMM yyyy HH:mm:ss +0000"; + public static final String LG_API_GATEWAY_SERVICE_PATH_V2 = "/v1/service/application/gateway-uri"; + public static final String LG_API_GATEWAY_URL_V2 = "https://route.lgthinq.com:46030" + + LG_API_GATEWAY_SERVICE_PATH_V2; + public static final String LG_API_MESSAGE_ID = "wideq"; + public static final String LG_API_OAUTH_CLIENT_KEY = "LGAO722A02"; + public static final String LG_API_OAUTH_SEARCH_KEY_PATH = "/searchKey"; + public static final String LG_API_OAUTH_SECRET_KEY = "c053c2a6ddeb7ad97cb0eed0dcb31cf8"; + public static final String LG_API_PLATFORM_TYPE_V1 = "thinq1"; + public static final String LG_API_PLATFORM_TYPE_V2 = "thinq2"; + public static final String LG_API_PRE_LOGIN_PATH = "/preLogin"; + public static final String LG_API_SECURITY_KEY = "nuts_securitykey"; + public static final String LG_API_SVC_CODE = "SVC202"; + public static final String LG_API_SVC_PHASE = "OP"; + public static final String LG_API_V1_MON_DATA_PATH = "rti/rtiResult"; + public static final String LG_API_V1_START_MON_PATH = "rti/rtiMon"; + public static final String LG_API_V2_API_KEY = "VGhpblEyLjAgU0VSVklDRQ=="; + public static final String LG_API_V2_APP_LEVEL = "PRD"; + public static final String LG_API_V2_APP_OS = "LINUX"; + public static final String LG_API_V2_APP_TYPE = "NUTS"; + public static final String LG_API_V2_APP_VER = "3.0.1700"; + public static final String LG_API_V2_AUTH_PATH = "/oauth/1.0/oauth2/token"; + public static final String LG_API_V2_CLIENT_ID = "65260af7e8e6547b51fdccf930097c51eb9885a508d3fddfa9ee6cdec22ae1bd"; + public static final String LG_API_V2_CTRL_DEVICE_CONFIG_PATH = "service/devices/%s/%s"; + public static final String LG_API_V2_DEVICE_CONFIG_PATH = "service/devices/"; + public static final String LG_API_V2_EMP_SESS_PATH = "/emp/oauth2/token/empsession"; + public static final String LG_API_V2_EMP_SESS_URL = "https://emp-oauth.lgecloud.com" + LG_API_V2_EMP_SESS_PATH; + public static final String LG_API_V2_LS_PATH = "/service/application/dashboard"; + public static final String LG_API_V2_SESSION_LOGIN_PATH = "/emp/v2.0/account/session/"; + public static final String LG_API_V2_SVC_PHASE = "OP"; + public static final String LG_API_V2_USER_INFO = "/users/profile"; + public static final Double FREEZER_TEMPERATURE_IGNORE_VALUE = 255.0; + public static final Double FRIDGE_TEMPERATURE_IGNORE_VALUE = 255.0; + public static final String RE_TEMP_UNIT_CELSIUS = "CELSIUS"; + public static final String RE_TEMP_UNIT_CELSIUS_SYMBOL = "°C"; + public static final Set RE_CELSIUS_UNIT_VALUES = Set.of("01", "1", "C", "CELSIUS", + RE_TEMP_UNIT_CELSIUS_SYMBOL); + public static final String RE_TEMP_UNIT_FAHRENHEIT = "FAHRENHEIT"; + public static final String RE_TEMP_UNIT_FAHRENHEIT_SYMBOL = "°F"; + public static final Map CAP_RE_TEMP_UNIT_V2_MAP = Map.of(RE_TEMP_UNIT_CELSIUS, + RE_TEMP_UNIT_CELSIUS_SYMBOL, RE_TEMP_UNIT_FAHRENHEIT, RE_TEMP_UNIT_FAHRENHEIT_SYMBOL); + public static final Set RE_FAHRENHEIT_UNIT_VALUES = Set.of("02", "2", "F", "FAHRENHEIT", + RE_TEMP_UNIT_FAHRENHEIT_SYMBOL); + public static final Set RE_DOOR_OPEN_VALUES = Set.of("1", "01", "OPEN"); + public static final Set RE_DOOR_CLOSE_VALUES = Set.of("0", "00", "CLOSE"); + public static final String RE_SNAPSHOT_NODE_V2 = "refState"; + public static final String RE_SET_CONTROL_COMMAND_NAME_V1 = "SetControl"; + public static final Map CAP_RE_SMART_SAVING_MODE = Map.of("@CP_TERM_USE_NOT_W", "Disabled", + "@RE_SMARTSAVING_MODE_NIGHT_W", "Night Mode", "@RE_SMARTSAVING_MODE_CUSTOM_W", "Custom Mode"); + public static final Map CAP_RE_ON_OFF = Map.of("@CP_OFF_EN_W", "Off", "@CP_ON_EN_W", "On"); + public static final Map CAP_RE_LABEL_ON_OFF = Map.of("OFF", "Off", "ON", "On", "IGNORE", + "Not Available"); + public static final Map CAP_RE_LABEL_CLOSE_OPEN = Map.of("CLOSE", "Closed", "OPEN", "Open", + "IGNORE", "Not Available"); + public static final Map CAP_RE_EXPRESS_FREEZE_MODES = Map.of("@CP_OFF_EN_W", "Express Mode Off", + "@CP_ON_EN_W", "Express Freeze On", "@RE_MAIN_SPEED_FREEZE_TERM_W", "Rapid Freeze On"); + public static final Map CAP_RE_FRESH_AIR_FILTER_MAP = Map.ofEntries(/* v1 */ entry("1", "Off"), + entry("2", "Auto Mode"), entry("3", "Power Mode"), entry("4", "Replace Filter"), + /* v2 */ entry("OFF", "Off"), entry("AUTO", "Auto Mode"), entry("POWER", "Power Mode"), + entry("REPLACE", "Replace Filter"), entry("SMART_STORAGE_POWER", "Smart Storage Power"), + entry("SMART_STORAGE_OFF", "Smart Storage Off"), entry("SMART_STORAGE_ON", "Smart Storage On"), + entry("IGNORE", "Not Available")); + public static final Map CAP_RE_SMART_SAVING_V2_MODE = Map.of("OFF", "Off", "NIGHT_ON", "Night Mode", + "CUSTOM_ON", "Custom Mode", "SMARTGRID_DR_ON", "Demand Response", "SMARTGRID_DD_ON", "Delay Defrost", + "IGNORE", "Not Available"); + public static final Map CAP_RE_WATER_FILTER = Map.ofEntries(entry("0_MONTH", "0 Month Used"), + entry("0", "0 Month Used"), entry("1_MONTH", "1 Month Used"), entry("1", "1 Month Used"), + entry("2_MONTH", "2 Month Used"), entry("2", "2 Month Used"), entry("3_MONTH", "3 Month Used"), + entry("3", "3 Month Used"), entry("4_MONTH", "4 Month Used"), entry("4", "4 Month Used"), + entry("5_MONTH", "5 Month Used"), entry("5", "5 Month Used"), entry("6_MONTH", "6 Month Used"), + entry("6", "6 Month Used"), entry("7_MONTH", "7 Month Used"), entry("8_MONTH", "8 Month Used"), + entry("9_MONTH", "9 Month Used"), entry("10_MONTH", "10 Month Used"), entry("11_MONTH", "11 Month Used"), + entry("12_MONTH", "12 Month Used"), entry("IGNORE", "Not Available")); + public static final String CAP_RE_WATER_FILTER_USED_POSTFIX = "Month(s) Used"; + // === Device Definition/Capability Constants + public static final String CAP_ACHP_OP_MODE_COOL_KEY = "@AC_MAIN_OPERATION_MODE_COOL_W"; + public static final String CAP_ACHP_OP_MODE_HEAT_KEY = "@AC_MAIN_OPERATION_MODE_HEAT_W"; + public static final Map CAP_AC_OP_MODE = Map.of(CAP_ACHP_OP_MODE_COOL_KEY, "Cool", + "@AC_MAIN_OPERATION_MODE_DRY_W", "Dry", "@AC_MAIN_OPERATION_MODE_FAN_W", "Fan", CAP_ACHP_OP_MODE_HEAT_KEY, + "Heat", "@AC_MAIN_OPERATION_MODE_AIRCLEAN_W", "Air Clean", "@AC_MAIN_OPERATION_MODE_ACO_W", "Auto", + "@AC_MAIN_OPERATION_MODE_AI_W", "AI", "@AC_MAIN_OPERATION_MODE_ENERGY_SAVING_W", "Eco", + "@AC_MAIN_OPERATION_MODE_AROMA_W", "Aroma", "@AC_MAIN_OPERATION_MODE_ANTIBUGS_W", "Anti Bugs"); + public static final Map CAP_AC_STEP_UP_DOWN_MODE = Map.of("@OFF", "Off", "@1", "Upper", "@2", "Up", + "@3", "Middle Up", "@4", "Middle Down", "@5", "Down", "@6", "Far Down", "@100", "Circular"); + public static final Map CAP_AC_STEP_LEFT_RIGHT_MODE = Map.of("@OFF", "Off", "@1", "Lefter", "@2", + "Left", "@3", "Middle", "@4", "Right", "@5", "Righter", "@13", "Left to Middle", "@35", "Middle to Right", + "@100", "Circular"); + // Sub Modes support + public static final String CAP_AC_SUB_MODE_COOL_JET = "@AC_MAIN_WIND_MODE_COOL_JET_W"; + public static final String CAP_AC_SUB_MODE_STEP_UP_DOWN = "@AC_MAIN_WIND_DIRECTION_STEP_UP_DOWN_W"; + public static final String CAP_AC_SUB_MODE_STEP_LEFT_RIGHT = "@AC_MAIN_WIND_DIRECTION_STEP_LEFT_RIGHT_W"; + public static final Map CAP_AC_FAN_SPEED = Map.ofEntries( + entry("@AC_MAIN_WIND_STRENGTH_SLOW_W", "Slow"), entry("@AC_MAIN_WIND_STRENGTH_SLOW_LOW_W", "Slower"), + entry("@AC_MAIN_WIND_STRENGTH_LOW_W", "Low"), entry("@AC_MAIN_WIND_STRENGTH_LOW_MID_W", "Low Mid"), + entry("@AC_MAIN_WIND_STRENGTH_MID_W", "Mid"), entry("@AC_MAIN_WIND_STRENGTH_MID_HIGH_W", "Mid High"), + entry("@AC_MAIN_WIND_STRENGTH_HIGH_W", "High"), entry("@AC_MAIN_WIND_STRENGTH_POWER_W", "Power"), + entry("@AC_MAIN_WIND_STRENGTH_AUTO_W", "Auto"), entry("@AC_MAIN_WIND_STRENGTH_NATURE_W", "Auto"), + entry("@AC_MAIN_WIND_STRENGTH_LOW_RIGHT_W", "Right Low"), + entry("@AC_MAIN_WIND_STRENGTH_MID_RIGHT_W", "Right Mid"), + entry("@AC_MAIN_WIND_STRENGTH_HIGH_RIGHT_W", "Right High"), + entry("@AC_MAIN_WIND_STRENGTH_LOW_LEFT_W", "Left Low"), + entry("@AC_MAIN_WIND_STRENGTH_MID_LEFT_W", "Left Mid"), + entry("@AC_MAIN_WIND_STRENGTH_HIGH_LEFT_W", "Left High")); + public static final Map CAP_AC_COOL_JET = Map.of("@COOL_JET", "Cool Jet"); + public static final Double CAP_HP_AIR_SWITCH = 0.0; + public static final Double CAP_HP_WATER_SWITCH = 1.0; + // ======= RAC MODES + public static final String CAP_AC_AUTODRY = "@AUTODRY"; + public static final String CAP_AC_ENERGYSAVING = "@ENERGYSAVING"; + public static final String CAP_AC_AIRCLEAN = "@AIRCLEAN"; + // ==================== + public static final String CAP_AC_COMMAND_OFF = "@OFF"; + public static final String CAP_AC_COMMAND_ON = "@ON"; + public static final String CAP_AC_AIR_CLEAN_COMMAND_ON = "@AC_MAIN_AIRCLEAN_ON_W"; + public static final String CAP_AC_AIR_CLEAN_COMMAND_OFF = "@AC_MAIN_AIRCLEAN_OFF_W"; + public static final String WMD_COURSE_NOT_SELECTED_VALUE = "NOT_SELECTED"; + public static final String WMD_POWER_OFF_VALUE = "POWEROFF"; + public static final String WMD_SNAPSHOT_WASHER_DRYER_NODE_V2 = "washerDryer"; + public static final String WM_LOST_WASHING_STATE_KEY = "WASHING"; + public static final String WM_LOST_WASHING_STATE_VALUE = "@WM_STATE_WASHING_W"; + public static final Map CAP_WMD_STATE = Map.ofEntries(entry("@WM_STATE_POWER_OFF_W", "Off"), + entry("@WM_STATE_INITIAL_W", "Initial"), entry("@WM_STATE_PAUSE_W", "Pause"), + entry("@WM_STATE_RESERVE_W", "Reserved"), entry("@WM_STATE_DETECTING_W", "Detecting"), + entry("@WM_STATE_RUNNING_W", "Running"), entry("@WM_STATE_RINSING_W", "Rinsing"), + entry("@WM_STATE_SPINNING_W", "Spinning"), entry("@WM_STATE_COOLDOWN_W", "Cool Down"), + entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), + entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), entry("@WM_STATE_END_W", "End"), + entry("@WM_STATE_DRYING_W", "Drying"), entry("@WM_STATE_DEMO_W", "Demonstration"), + entry("@WM_STATE_ADD_DRAIN_W", "Add Drain"), entry("@WM_STATE_LOAD_DISPLAY_W", "Loading Display"), + entry("@WM_STATE_FRESHCARE_W", "Refreshing"), entry("@WM_STATE_ERROR_AUTO_OFF_W", "Error Auto Off"), + entry("@WM_STATE_FROZEN_PREVENT_INITIAL_W", "Frozen Preventing"), + entry("@FROZEN_PREVENT_PAUSE", "Frozen Preventing Paused"), + entry("@FROZEN_PREVENT_RUNNING", "Frozen Preventing Running"), entry("@AUDIBLE_DIAGNOSIS", "Diagnosing"), + entry("@WM_STATE_ERROR_W", "Error"), + // This last one is not defined in the cap file + entry(WM_LOST_WASHING_STATE_VALUE, "Washing")); + public static final Map CAP_WMD_PROCESS_STATE = Map.ofEntries( + entry("@WM_STATE_DETECTING_W", "Detecting"), entry("@WM_STATE_STEAM_W", "Steam"), + entry("@WM_STATE_DRY_W", "Drying"), entry("@WM_STATE_COOLING_W", "Cooling"), + entry("@WM_STATE_ANTI_CREASE_W", "Anti Creasing"), entry("@WM_STATE_END_W", "End"), + entry("@WM_STATE_POWER_OFF_W", "Power Off"), entry("@WM_STATE_INITIAL_W", "Initializing"), + entry("@WM_STATE_PAUSE_W", "Paused"), entry("@WM_STATE_RESERVE_W", "Reserved"), + entry("@WM_STATE_RUNNING_W", "Running"), entry("@WM_STATE_RINSING_W", "Rising"), + entry("@WM_STATE_SPINNING_W", "@WM_STATE_DRYING_W"), entry("WM_STATE_COOLDOWN_W", "Cool Down"), + entry("@WM_STATE_RINSEHOLD_W", "Rinse Hold"), entry("@WM_STATE_WASH_REFRESHING_W", "Refreshing"), + entry("@WM_STATE_STEAMSOFTENING_W", "Steam Softening"), entry("@WM_STATE_ERROR_W", "Error")); + public static final Map CAP_DR_DRY_LEVEL = Map.ofEntries( + entry("@WM_DRY24_DRY_LEVEL_IRON_W", "Iron"), entry("@WM_DRY24_DRY_LEVEL_CUPBOARD_W", "Cupboard"), + entry("@WM_DRY24_DRY_LEVEL_EXTRA_W", "Extra")); + public static final Map CAP_WMD_TEMPERATURE = Map.ofEntries( + entry("@WM_TERM_NO_SELECT_W", "Not Selected"), entry("@WM_TITAN2_OPTION_TEMP_20_W", "20"), + entry("@WM_TITAN2_OPTION_TEMP_COLD_W", "Cold"), entry("@WM_TITAN2_OPTION_TEMP_30_W", "30"), + entry("@WM_TITAN2_OPTION_TEMP_40_W", "40"), entry("@WM_TITAN2_OPTION_TEMP_50_W", "50"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_TAP_COLD_W", "Tap Cold"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_COLD_W", "Cold"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_ECO_WARM_W", "Eco Warm"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_WARM_W", "Warm"), entry("@WM_TITAN27_BIG_OPTION_TEMP_HOT_W", "Hot"), + entry("@WM_TITAN27_BIG_OPTION_TEMP_EXTRA_HOT_W", "Extra Hot")); + public static final Map CAP_WM_SPIN = Map.ofEntries(entry("@WM_TERM_NO_SELECT_W", "Not Selected"), + entry("@WM_TITAN2_OPTION_SPIN_NO_SPIN_W", "No Spin"), entry("@WM_TITAN2_OPTION_SPIN_400_W", "400"), + entry("@WM_TITAN2_OPTION_SPIN_600_W", "600"), entry("@WM_TITAN2_OPTION_SPIN_700_W", "700"), + entry("@WM_TITAN2_OPTION_SPIN_800_W", "800"), entry("@WM_TITAN2_OPTION_SPIN_900_W", "900"), + entry("@WM_TITAN2_OPTION_SPIN_1000_W", "1000"), entry("@WM_TITAN2_OPTION_SPIN_1100_W", "1100"), + entry("@WM_TITAN2_OPTION_SPIN_1200_W", "1200"), entry("@WM_TITAN2_OPTION_SPIN_1400_W", "1400"), + entry("@WM_TITAN2_OPTION_SPIN_1600_W", "1600"), entry("@WM_TITAN2_OPTION_SPIN_MAX_W", "Max Spin"), + entry("@WM_TITAN27_BIG_OPTION_SPIN_NO_SPIN_W", "Drain Only"), + entry("@WM_TITAN27_BIG_OPTION_SPIN_LOW_W", "Low"), entry("@WM_TITAN27_BIG_OPTION_SPIN_MEDIUM_W", "Medium"), + entry("@WM_TITAN27_BIG_OPTION_SPIN_HIGH_W", "High"), + entry("@WM_TITAN27_BIG_OPTION_SPIN_EXTRA_HIGH_W", "Extra High")); + public static final Map CAP_WM_RINSE = Map.ofEntries(entry("@WM_TERM_NO_SELECT_W", "Not Selected"), + entry("@WM_TITAN2_OPTION_RINSE_NORMAL_W", "Normal"), entry("@WM_TITAN2_OPTION_RINSE_RINSE+_W", "Plus"), + entry("@WM_TITAN2_OPTION_RINSE_RINSE++_W", "Plus +"), + entry("@WM_TITAN2_OPTION_RINSE_NORMALHOLD_W", "Normal Hold"), + entry("@WM_TITAN2_OPTION_RINSE_RINSE+HOLD_W", "Plus Hold"), + entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_0_W", "Normal"), + entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_1_W", "Plus"), + entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_2_W", "Plus +"), + entry("@WM_TITAN27_BIG_OPTION_EXTRA_RINSE_3_W", "Plus ++")); + // This is the dictionary os course functions translations for V2 + public static final Map> CAP_WM_DICT_V2 = Map.of("spin", CAP_WM_SPIN, "rinse", + CAP_WM_RINSE, "temp", CAP_WMD_TEMPERATURE, "state", CAP_WMD_STATE); + public static final String WMD_COMMAND_REMOTE_START_V2 = "WMStart"; + /** + * ============ Dish Washer's Label/Feature Translation Constants ============= + */ + public static final String DW_SNAPSHOT_WASHER_DRYER_NODE_V2 = "dishwasher"; + public static final String DW_POWER_OFF_VALUE = "POWEROFF"; + public static final String DW_STATE_COMPLETE = "END"; + public static final Map CAP_DW_DOOR_STATE = Map.of("@CP_OFF_EN_W", "Close", "@CP_ON_EN_W", + "Opened"); + public static final Map CAP_DW_PROCESS_STATE = Map.ofEntries(entry("@DW_STATE_INITIAL_W", "None"), + entry("@DW_STATE_RESERVE_W", "Reserved"), entry("@DW_STATE_RUNNING_W", "Running"), + entry("@DW_STATE_RINSING_W", "Rising"), entry("@DW_STATE_DRYING_W", "Drying"), + entry("@DW_STATE_COMPLETE_W", "Complete"), entry("@DW_STATE_NIGHTDRY_W", "Night Dry"), + entry("@DW_STATE_CANCEL_W", "Cancelled")); + public static final Map CAP_DW_STATE = Map.ofEntries(entry("@DW_STATE_POWER_OFF_W", "Off"), + entry("@DW_STATE_INITIAL_W", "Initial"), entry("@DW_STATE_RUNNING_W", "Running"), + entry("@DW_STATE_PAUSE_W", "Paused"), entry("@DW_STATE_STANDBY_W", "Stand By"), + entry("@DW_STATE_COMPLETE_W", "Complete"), entry("@DW_STATE_POWER_FAIL_W", "Power Fail")); +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java index f7283368d3fcc..a5338c3aa2eae 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java @@ -12,9 +12,8 @@ */ package org.openhab.binding.lgthinq.lgservices; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACTargetTmp; @@ -48,6 +47,5 @@ void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp new void turnEnergySavingMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; - ExtendedDeviceInfo getExtendedDeviceInfo(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException; + ExtendedDeviceInfo getExtendedDeviceInfo(String bridgeName, String deviceId) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java index 8733b9d313b54..28ace60b90d1a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV1ClientServiceImpl.java @@ -12,19 +12,18 @@ */ package org.openhab.binding.lgthinq.lgservices; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V1_CONTROL_OP; -import static org.openhab.binding.lgthinq.internal.api.LGThinqCanonicalModelUtil.LG_ROOT_TAG_V1; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V1_CONTROL_OP; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_ROOT_TAG_V1; import java.io.IOException; import java.util.Base64; import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; @@ -52,8 +51,9 @@ protected LGThinQACApiV1ClientServiceImpl(HttpClient httpClient) { } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { - // Nothing to do on V1 ACCapability here + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { + // there's no before settings to send command + return false; } /** @@ -63,12 +63,10 @@ protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String d * @param deviceId device ID for de desired V2 LG Thinq. * @param capDef * @return return map containing metamodel of settings and snapshot - * @throws LGThinqApiException if some communication error occur. */ @Override @Nullable - public ACCanonicalSnapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull CapabilityDefinition capDef) throws LGThinqApiException { + public ACCanonicalSnapshot getDeviceData(String bridgeName, String deviceId, CapabilityDefinition capDef) { throw new UnsupportedOperationException("Method not supported in V1 API device."); } @@ -90,16 +88,15 @@ private void readDataResultNodeToObject(String jsonResult, Object obj) throws IO } @Override - public ExtendedDeviceInfo getExtendedDeviceInfo(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException { + public ExtendedDeviceInfo getExtendedDeviceInfo(String bridgeName, String deviceId) throws LGThinqApiException { ExtendedDeviceInfo info = new ExtendedDeviceInfo(); try { - RestResult resp = sendCommand(bridgeName, deviceId, V1_CONTROL_OP, "Config", "Get", "", + RestResult resp = sendCommand(bridgeName, deviceId, LG_API_V1_CONTROL_OP, "Config", "Get", "", "InOutInstantPower"); handleGenericErrorResult(resp); readDataResultNodeToObject(resp.getJsonResponse(), info); - resp = sendCommand(bridgeName, deviceId, V1_CONTROL_OP, "Config", "Get", "", "Filter"); + resp = sendCommand(bridgeName, deviceId, LG_API_V1_CONTROL_OP, "Config", "Get", "", "Filter"); handleGenericErrorResult(resp); readDataResultNodeToObject(resp.getJsonResponse(), info); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java index b81a9ee83822a..7d0f08c2146bb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -14,15 +14,11 @@ import java.io.IOException; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; @@ -161,42 +157,42 @@ public void changeTargetTemperature(String bridgeName, String deviceId, ACTarget * * @param deviceId Device ID * @return Work1 to be uses to grab data during monitoring. - * @throws LGThinqApiException If some communication error occur. */ @Override - public String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { + public String startMonitor(String bridgeName, String deviceId) { throw new UnsupportedOperationException("Not supported in V2 API."); } @Override - public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { + public void stopMonitor(String bridgeName, String deviceId, String workId) { throw new UnsupportedOperationException("Not supported in V2 API."); } @Override - public @Nullable ACCanonicalSnapshot getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull String workId, DeviceTypes deviceType, @NonNull ACCapability deviceCapability) - throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException { + public @Nullable ACCanonicalSnapshot getMonitorData(String bridgeName, String deviceId, String workId, + DeviceTypes deviceType, ACCapability deviceCapability) { throw new UnsupportedOperationException("Not supported in V2 API."); } @Override - public void initializeDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + public void initializeDevice(String bridgeName, String deviceId) throws LGThinqApiException { super.initializeDevice(bridgeName, deviceId); } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException { + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { try { RestResult resp = sendCommand(bridgeName, deviceId, "control", "allEventEnable", "Set", "airState.mon.timeout", "70"); handleGenericErrorResult(resp); + if (resp.getStatusCode() == 400) { + // Access Denied. Return false to indicate user don't have access to this functionality + return false; + } } catch (Exception e) { logger.debug("Can't execute Before Update command", e); } + return true; } /** @@ -226,8 +222,7 @@ private void readDataResultNodeToObject(String jsonResult, Object obj) throws IO } @Override - public ExtendedDeviceInfo getExtendedDeviceInfo(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException { + public ExtendedDeviceInfo getExtendedDeviceInfo(String bridgeName, String deviceId) throws LGThinqApiException { ExtendedDeviceInfo info = new ExtendedDeviceInfo(); try { ObjectNode dataList = JsonNodeFactory.instance.objectNode(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index c238e397c8d93..d00c8fe744b64 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -12,7 +12,20 @@ */ package org.openhab.binding.lgthinq.lgservices; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.BASE_CAP_CONFIG_DATA_FILE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_SECURITY_KEY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_SVC_CODE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V1_MON_DATA_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V1_START_MON_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_API_KEY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_APP_LEVEL; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_APP_OS; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_APP_TYPE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_APP_VER; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_CLIENT_ID; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_DEVICE_CONFIG_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_LS_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_SVC_PHASE; import java.io.File; import java.io.IOException; @@ -20,21 +33,38 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.StandardCopyOption; -import java.util.*; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; import javax.ws.rs.core.UriBuilder; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; import org.openhab.binding.lgthinq.internal.LGThinQBindingConstants; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.*; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.api.RestUtils; +import org.openhab.binding.lgthinq.lgservices.api.TokenManager; +import org.openhab.binding.lgthinq.lgservices.api.TokenResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityFactory; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotBuilderFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -50,14 +80,15 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault +@SuppressWarnings("unchecked") public abstract class LGThinQAbstractApiClientService implements LGThinQApiClientService { private static final Logger logger = LoggerFactory.getLogger(LGThinQAbstractApiClientService.class); protected final ObjectMapper objectMapper = new ObjectMapper(); protected final TokenManager tokenManager; - protected Class capabilityClass; - protected Class snapshotClass; - protected HttpClient httpClient; + protected final Class capabilityClass; + protected final Class snapshotClass; + protected final HttpClient httpClient; protected LGThinQAbstractApiClientService(Class capabilityClass, Class snapshotClass, HttpClient httpClient) { this.httpClient = httpClient; @@ -71,18 +102,18 @@ static Map getCommonHeaders(String language, String country, Str Map headers = new HashMap<>(); headers.put("Accept", "application/json"); headers.put("Content-type", "application/json;charset=UTF-8"); - headers.put("x-api-key", V2_API_KEY); - headers.put("x-client-id", V2_CLIENT_ID); + headers.put("x-api-key", LG_API_V2_API_KEY); + headers.put("x-client-id", LG_API_V2_CLIENT_ID); headers.put("x-country-code", country); headers.put("x-language-code", language); headers.put("x-message-id", UUID.randomUUID().toString()); - headers.put("x-service-code", SVC_CODE); - headers.put("x-service-phase", V2_SVC_PHASE); - headers.put("x-thinq-app-level", V2_APP_LEVEL); - headers.put("x-thinq-app-os", V2_APP_OS); - headers.put("x-thinq-app-type", V2_APP_TYPE); - headers.put("x-thinq-app-ver", V2_APP_VER); - headers.put("x-thinq-security-key", SECURITY_KEY); + headers.put("x-service-code", LG_API_SVC_CODE); + headers.put("x-service-phase", LG_API_V2_SVC_PHASE); + headers.put("x-thinq-app-level", LG_API_V2_APP_LEVEL); + headers.put("x-thinq-app-os", LG_API_V2_APP_OS); + headers.put("x-thinq-app-type", LG_API_V2_APP_TYPE); + headers.put("x-thinq-app-ver", LG_API_V2_APP_VER); + headers.put("x-thinq-security-key", LG_API_SECURITY_KEY); if (!accessToken.isBlank()) headers.put("x-emp-token", accessToken); if (!userNumber.isBlank()) @@ -100,7 +131,7 @@ static Map getCommonHeaders(String language, String country, Str public List listAccountDevices(String bridgeName) throws LGThinqApiException { try { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(V2_LS_PATH); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()).path(LG_API_V2_LS_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); RestResult resp = RestUtils.getCall(httpClient, builder.build().toURL().toString(), headers, null); @@ -139,7 +170,7 @@ public Map getDeviceSettings(String bridgeName, String deviceId) try { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) - .path(String.format("%s/%s", V2_DEVICE_CONFIG_PATH, deviceId)); + .path(String.format("%s/%s", LG_API_V2_DEVICE_CONFIG_PATH, deviceId)); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); RestResult resp = RestUtils.getCall(httpClient, builder.build().toURL().toString(), headers, null); @@ -150,32 +181,27 @@ public Map getDeviceSettings(String bridgeName, String deviceId) } private Map handleDeviceSettingsResult(RestResult resp) throws LGThinqApiException { - return genericHandleDeviceSettingsResult(resp, logger, objectMapper); + return genericHandleDeviceSettingsResult(resp, objectMapper); } - @SuppressWarnings("unchecked") - static Map genericHandleDeviceSettingsResult(RestResult resp, Logger logger, - ObjectMapper objectMapper) throws LGThinqApiException { + static Map genericHandleDeviceSettingsResult(RestResult resp, ObjectMapper objectMapper) + throws LGThinqApiException { Map deviceSettings; - Map respMap = Collections.EMPTY_MAP; - String resultCode = "???"; + Map respMap; + String resultCode; if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { - logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", + LGThinQAbstractApiClientService.logger.warn( + "Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); return Collections.emptyMap(); } try { - if (resp.getStatusCode() == 400) { - logger.warn("Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); - return Collections.emptyMap(); - } respMap = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); resultCode = respMap.get("resultCode"); if (resultCode != null) { - logger.error( + LGThinQAbstractApiClientService.logger.error( "Error calling device settings from LG Server API. The code is: {} and The reason is: {}", resultCode, ResultCodes.fromCode(resultCode)); throw new LGThinqApiException("Error calling device settings from LG Server API."); @@ -183,7 +209,8 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo } catch (JsonProcessingException e) { // This exception doesn't matter, it's because response is not in json format. Logging raw response. } - logger.error("Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); + LGThinQAbstractApiClientService.logger.error( + "Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); throw new LGThinqApiException(String.format( "Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); @@ -193,7 +220,8 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo }); String code = Objects.requireNonNullElse((String) deviceSettings.get("resultCode"), ""); if (!ResultCodes.OK.containsResultCode(code)) { - logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, + LGThinQAbstractApiClientService.logger.error( + "LG API report error processing the request -> resultCode=[{}], message=[{}]", code, getErrorCodeMessage(code)); throw new LGThinqApiException( String.format("Status error getting device list. resultCode must be 0000, but was:%s", @@ -208,7 +236,6 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Lo "Unexpected json result asking for Device Settings. Node 'result' no present"); } - @SuppressWarnings("unchecked") private List handleListAccountDevicesResult(RestResult resp) throws LGThinqApiException { Map devicesResult; List devices; @@ -234,8 +261,8 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG String.format("Status error getting device list. resultCode must be 0000, but was:%s", devicesResult.get("resultCode"))); } - List> items = (List>) ((Map) devicesResult - .get("result")).get("item"); + List> items = (List>) ((Map) Objects + .requireNonNull(devicesResult.get("result"), "Not expected null here")).get("item"); devices = objectMapper.convertValue(items, new TypeReference<>() { }); } catch (JsonProcessingException e) { @@ -275,9 +302,10 @@ public C getCapability(String deviceId, String uri, boolean forceRecreate) throw } } - private S handleV1OfflineException() { + public S buildDefaultOfflineSnapshot() { try { // As I don't know the current device status, then I reset to default values. + @SuppressWarnings("null") S shot = snapshotClass.getDeclaredConstructor().newInstance(); shot.setPowerStatus(DevicePowerState.DV_POWER_OFF); shot.setOnline(false); @@ -289,11 +317,11 @@ private S handleV1OfflineException() { } } - public @Nullable S getMonitorData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull String workId, - DeviceTypes deviceType, @NonNull C deviceCapability) throws LGThinqApiException, - LGThinqDeviceV1MonitorExpiredException, IOException, LGThinqUnmarshallException { + public @Nullable S getMonitorData(String bridgeName, String deviceId, String workId, DeviceTypes deviceType, + C deviceCapability) throws LGThinqApiException, LGThinqDeviceV1MonitorExpiredException, IOException, + LGThinqUnmarshallException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_MON_DATA_PATH); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(LG_API_V1_MON_DATA_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); String jsonData = String.format("{\n" + " \"lgedmRoot\":{\n" + " \"workList\":[\n" + " {\n" @@ -306,11 +334,12 @@ private S handleV1OfflineException() { try { envelop = handleGenericErrorResult(resp); } catch (LGThinqDeviceV1OfflineException e) { - return handleV1OfflineException(); + return buildDefaultOfflineSnapshot(); } - if (envelop.get("workList") != null - && ((Map) envelop.get("workList")).get("returnData") != null) { - Map workList = ((Map) envelop.get("workList")); + Map workList = objectMapper + .convertValue(envelop.getOrDefault("workList", Collections.emptyMap()), new TypeReference<>() { + }); + if (workList.get("returnData") != null) { if (logger.isDebugEnabled()) { try { objectMapper.writeValue(new File(String.format( @@ -360,16 +389,16 @@ private S handleV1OfflineException() { } @Override - public void initializeDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException { + public void initializeDevice(String bridgeName, String deviceId) throws LGThinqApiException { } /** * Perform some routine before getting data device. Depending on the kind of the device, this is needed * to update or prepare some informations before go to get the data. * + * @return */ - protected abstract void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException; + protected abstract boolean beforeGetDataDevice(String bridgeName, String deviceId); /** * Get snapshot data from the device. @@ -382,11 +411,12 @@ protected abstract void beforeGetDataDevice(@NonNull String bridgeName, @NonNull */ @Override @Nullable - public S getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, @NonNull CapabilityDefinition capDef) - throws LGThinqApiException { + public S getDeviceData(String bridgeName, String deviceId, CapabilityDefinition capDef) throws LGThinqApiException { // Exec pre-conditions (normally ask for update monitoring sensors of the device - temp and power) before call // for data - beforeGetDataDevice(bridgeName, deviceId); + if (capDef.isBeforeCommandSupported() && !beforeGetDataDevice(bridgeName, deviceId)) { + capDef.setBeforeCommandSupported(false); + } Map deviceSettings = getDeviceSettings(bridgeName, deviceId); if (deviceSettings.get("snapshot") != null) { @@ -406,7 +436,7 @@ public S getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, @No } S shot = (S) SnapshotBuilderFactory.getInstance().getBuilder(snapshotClass).createFromJson(deviceSettings, capDef); - shot.setOnline((Boolean) snapMap.get("online")); + shot.setOnline((Boolean) snapMap.getOrDefault("online", Boolean.FALSE)); return shot; } return null; @@ -420,10 +450,9 @@ public S getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, @No * @throws LGThinqApiException If some communication error occur. */ @Override - public String startMonitor(String bridgeName, String deviceId) - throws LGThinqApiException, LGThinqDeviceV1OfflineException, IOException { + public String startMonitor(String bridgeName, String deviceId) throws LGThinqApiException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(LG_API_V1_START_MON_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); String workerId = UUID.randomUUID().toString(); @@ -440,10 +469,9 @@ public String startMonitor(String bridgeName, String deviceId) } @Override - public void stopMonitor(String bridgeName, String deviceId, String workId) - throws LGThinqApiException, RefreshTokenException, IOException, LGThinqDeviceV1OfflineException { + public void stopMonitor(String bridgeName, String deviceId, String workId) throws LGThinqApiException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_START_MON_PATH); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(LG_API_V1_START_MON_PATH); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); String jsonData = String.format(" { \"lgedmRoot\" : {" + "\"cmd\": \"Mon\"," + "\"cmdOpt\": \"Stop\"," diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index af40a920677a3..ca0fb30732d7c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -12,21 +12,26 @@ */ package org.openhab.binding.lgthinq.lgservices; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V1_CONTROL_OP; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V1_CONTROL_OP; -import java.util.*; +import java.util.Base64; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; import javax.ws.rs.core.UriBuilder; -import org.apache.commons.lang3.ArrayUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1OfflineException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.api.RestUtils; +import org.openhab.binding.lgthinq.lgservices.api.TokenResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1OfflineException; import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; @@ -90,11 +95,6 @@ protected RestResult sendCommand(String bridgeName, String deviceId, String cont } } }); - // String payload = String.format( - // "{\n" + " \"lgedmRoot\":{\n" + " \"cmd\": \"%s\"," + " \"cmdOpt\": \"%s\"," - // + " \"value\": {\"%s\": \"%s\"}," + " \"deviceId\": \"%s\"," - // + " \"workId\": \"%s\"," + " \"data\": \"\"" + " }\n" + "}", - // controlKey, command, keyName, value, deviceId, UUID.randomUUID()); if (extraNode != null) { payloadNode.setAll(extraNode); } @@ -111,14 +111,15 @@ protected RestResult sendCommand(String bridgeName, String deviceId, String cont protected RestResult sendCommand(String bridgeName, String deviceId, Object cmdPayload) throws Exception { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); - UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(V1_CONTROL_OP); + UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV1()).path(LG_API_V1_CONTROL_OP); Map headers = getCommonHeaders(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); - ObjectNode payloadNode = null; + ObjectNode payloadNode; if (cmdPayload instanceof ObjectNode) { payloadNode = ((ObjectNode) cmdPayload).deepCopy(); } else { - payloadNode = objectMapper.convertValue(cmdPayload, ObjectNode.class); + payloadNode = objectMapper.convertValue(cmdPayload, new TypeReference<>() { + }); } ObjectNode rootNode = JsonNodeFactory.instance.objectNode(); ObjectNode bodyNode = JsonNodeFactory.instance.objectNode(); @@ -145,8 +146,13 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp } if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { - logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), - resp.getJsonResponse()); + if (logger.isDebugEnabled()) { + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}\n {}", + resp.getStatusCode(), resp.getJsonResponse(), Thread.currentThread().getStackTrace()); + } else { + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", + resp.getStatusCode(), resp.getJsonResponse()); + } } else { logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), resp.getJsonResponse()); @@ -158,7 +164,8 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp try { metaResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); - envelope = (Map) metaResult.get("lgedmRoot"); + envelope = objectMapper.convertValue(metaResult.get("lgedmRoot"), new TypeReference<>() { + }); String code = String.valueOf(envelope.get("returnCd")); if (envelope.isEmpty()) { throw new LGThinqApiException(String.format( @@ -215,8 +222,12 @@ protected LinkedHashMap completeCommandDataNodeV1(CommandDefinit List list = objectMapper.readValue(dataStr, new TypeReference<>() { }); // convert the list of integer to a bytearray - byte[] bytes = ArrayUtils.toPrimitive(list.stream().map(Integer::byteValue).toArray(Byte[]::new)); - String str_data_encoded = new String(Base64.getEncoder().encode(bytes)); + + byte[] byteArray = new byte[list.size()]; + for (int i = 0; i < list.size(); i++) { + byteArray[i] = list.get(i).byteValue(); // Converte Integer para byte + } + String str_data_encoded = new String(Base64.getEncoder().encode(byteArray)); data.put("data", str_data_encoded); } else { data.put("data", dataStr); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java index 65ed4bb4e6a95..348d4f3b580a0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_CTRL_DEVICE_CONFIG_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_CTRL_DEVICE_CONFIG_PATH; import java.io.IOException; import java.util.Collections; @@ -23,10 +23,10 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.api.RestUtils; +import org.openhab.binding.lgthinq.lgservices.api.TokenResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; @@ -63,7 +63,7 @@ protected RestResult postCall(String bridgeName, String deviceId, String control throws LGThinqApiException, IOException { TokenResult token = tokenManager.getValidRegisteredToken(bridgeName); UriBuilder builder = UriBuilder.fromUri(token.getGatewayInfo().getApiRootV2()) - .path(String.format(V2_CTRL_DEVICE_CONFIG_PATH, deviceId, controlPath)); + .path(String.format(LG_API_V2_CTRL_DEVICE_CONFIG_PATH, deviceId, controlPath)); Map headers = getCommonV2Headers(token.getGatewayInfo().getLanguage(), token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); RestResult resp = RestUtils.postCall(httpClient, builder.build().toURL().toString(), headers, payload); @@ -100,8 +100,13 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp } if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { - logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), - resp.getJsonResponse()); + if (logger.isDebugEnabled()) { + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}\n {}", + resp.getStatusCode(), resp.getJsonResponse(), Thread.currentThread().getStackTrace()); + } else { + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", + resp.getStatusCode(), resp.getJsonResponse()); + } return Collections.emptyMap(); } else { logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index ff3482385e0d5..dfa81c4f5dbd7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -17,14 +17,17 @@ import java.util.List; import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqDeviceV1MonitorExpiredException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1MonitorExpiredException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.SnapshotDefinition; /** * The {@link LGThinQApiClientService} @@ -38,7 +41,7 @@ public interface LGThinQApiClientService getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException; - void initializeDevice(@NonNull String bridgeName, @NonNull String deviceId) throws LGThinqApiException; + void initializeDevice(String bridgeName, String deviceId) throws LGThinqApiException; /** * Retrieve actual data from device (its sensors and points states). @@ -49,8 +52,7 @@ public interface LGThinQApiClientService { @@ -77,37 +75,33 @@ private LGThinQGeneralApiClientService(HttpClient httpClient) { } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { throw new UnsupportedOperationException(); } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) - throws LGThinqApiException { + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { throw new UnsupportedOperationException(); } @Override protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, - String command, String keyName, String value) throws Exception { + String command, String keyName, String value) { throw new UnsupportedOperationException(); } @Override protected RestResult sendCommand(String bridgeName, String deviceId, String controlPath, String controlKey, - String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) - throws Exception { + String command, @Nullable String keyName, @Nullable String value, @Nullable ObjectNode extraNode) { throw new UnsupportedOperationException(); } @Override - protected Map handleGenericErrorResult(@Nullable RestResult resp) throws LGThinqApiException { + protected Map handleGenericErrorResult(@Nullable RestResult resp) { throw new UnsupportedOperationException(); } } - @NonNullByDefault private static final class GenericCapability extends AbstractCapability { } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java index e1a2dd156a559..857a3785157ba 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java @@ -13,7 +13,7 @@ package org.openhab.binding.lgthinq.lgservices; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java index 44a7bbb648060..480ba18685438 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java @@ -12,11 +12,10 @@ */ package org.openhab.binding.lgthinq.lgservices; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; @@ -36,13 +35,13 @@ protected LGThinQDRApiV2ClientServiceImpl(HttpClient httpClient) { } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { - // TODO - Analise what to do here + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { + // there's no before settings to send command + return false; } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { throw new UnsupportedOperationException("Not implemented yet."); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java index 74648814b8878..452a837bd0699 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java @@ -15,7 +15,6 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; @@ -27,8 +26,7 @@ @NonNullByDefault public interface LGThinQDishWasherApiClientService extends LGThinQApiClientService { - void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) - throws LGThinqApiException; + void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data); - void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException; + void wakeUp(String bridgeName, String deviceId, Boolean wakeUp); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java index 819aa384b8e99..dcbd4be98990f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java @@ -14,17 +14,13 @@ import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link LGThinQDishWasherApiV1ClientServiceImpl} @@ -35,38 +31,35 @@ public class LGThinQDishWasherApiV1ClientServiceImpl extends LGThinQAbstractApiV1ClientService implements LGThinQDishWasherApiClientService { - private final Logger logger = LoggerFactory.getLogger(LGThinQDishWasherApiV1ClientServiceImpl.class); protected LGThinQDishWasherApiV1ClientServiceImpl(HttpClient httpClient) { super(DishWasherCapability.class, DishWasherSnapshot.class, httpClient); } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { - // Nothing to do for V1 thinq + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { + // there's no before settings to send command + return false; } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { throw new UnsupportedOperationException("Not Supported for this device"); } @Override @Nullable - public DishWasherSnapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull CapabilityDefinition capDef) throws LGThinqApiException { + public DishWasherSnapshot getDeviceData(String bridgeName, String deviceId, CapabilityDefinition capDef) { throw new UnsupportedOperationException("Method not supported in V1 API device."); } @Override - public void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) - throws LGThinqApiException { + public void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) { throw new UnsupportedOperationException("Not implemented yet"); } @Override - public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException { + public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) { throw new UnsupportedOperationException("Not implemented yet"); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java index bce79ebf2af33..a1f90c183ab7a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java @@ -14,10 +14,8 @@ import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; @@ -37,24 +35,23 @@ protected LGThinQDishWasherApiV2ClientServiceImpl(HttpClient httpClient) { } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { - // TODO - Analise what to do here + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { + // there's no before settings to send command + return false; } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { throw new UnsupportedOperationException("Unsupported for this device"); } @Override - public void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) - throws LGThinqApiException { + public void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) { throw new UnsupportedOperationException("Not implemented yet"); } @Override - public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException { + public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) { throw new UnsupportedOperationException("Not implemented yet"); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java index 416345debfbd3..077da8a60888e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java @@ -16,7 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java index 8eed310e35873..74f1b85d6a685 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV1ClientServiceImpl.java @@ -12,16 +12,15 @@ */ package org.openhab.binding.lgthinq.lgservices; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.FR_SET_CONTROL_COMMAND_NAME_V1; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_SET_CONTROL_COMMAND_NAME_V1; import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; @@ -46,20 +45,19 @@ protected LGThinQFridgeApiV1ClientServiceImpl(HttpClient httpClient) { } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { - // Nothing to do for V1 thinq + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { + // there's no before settings to send command + return false; } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { throw new UnsupportedOperationException("Not implemented yet."); } @Override @Nullable - public FridgeCanonicalSnapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull CapabilityDefinition capDef) throws LGThinqApiException { + public FridgeCanonicalSnapshot getDeviceData(String bridgeName, String deviceId, CapabilityDefinition capDef) { throw new UnsupportedOperationException("Method not supported in V1 API device."); } @@ -67,35 +65,39 @@ public FridgeCanonicalSnapshot getDeviceData(@NonNull String bridgeName, @NonNul public void setFridgeTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) throws LGThinqApiException { - assert snapCmdData != null; - snapCmdData.put("TempRefrigerator", targetTemperatureIndex); - setControlCommand(bridgeId, deviceId, fridgeCapability, snapCmdData); + if (snapCmdData != null) { + snapCmdData.put("TempRefrigerator", targetTemperatureIndex); + setControlCommand(bridgeId, deviceId, fridgeCapability, snapCmdData); + } else { + logger.warn("Snapshot Command Data is null"); + } } @Override public void setFreezerTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) throws LGThinqApiException { - assert snapCmdData != null; - snapCmdData.put("TempFreezer", targetTemperatureIndex); - setControlCommand(bridgeId, deviceId, fridgeCapability, snapCmdData); + if (snapCmdData != null) { + snapCmdData.put("TempFreezer", targetTemperatureIndex); + setControlCommand(bridgeId, deviceId, fridgeCapability, snapCmdData); + } else { + logger.warn("Snapshot command is null"); + } } @Override - public void setExpressMode(String bridgeId, String deviceId, String expressModeIndex) throws LGThinqApiException { + public void setExpressMode(String bridgeId, String deviceId, String expressModeIndex) { throw new UnsupportedOperationException("V1 Fridge doesn't support ExpressMode feature. It mostly like a bug"); } @Override - public void setExpressCoolMode(String bridgeId, String deviceId, boolean trueOnFalseOff) - throws LGThinqApiException { + public void setExpressCoolMode(String bridgeId, String deviceId, boolean trueOnFalseOff) { throw new UnsupportedOperationException( "V1 Fridge doesn't support ExpressCoolMode feature. It mostly like a bug"); } @Override - public void setEcoFriendlyMode(String bridgeId, String deviceId, boolean trueOnFalseOff) - throws LGThinqApiException { + public void setEcoFriendlyMode(String bridgeId, String deviceId, boolean trueOnFalseOff) { throw new UnsupportedOperationException( "V1 Fridge doesn't support ExpressCoolMode feature. It mostly like a bug"); } @@ -111,7 +113,7 @@ private void setControlCommand(String bridgeId, String deviceId, FridgeCapabilit @Nullable Map snapCmdData) throws LGThinqApiException { try { CommandDefinition cmdSetControlDef = fridgeCapability.getCommandsDefinition() - .get(FR_SET_CONTROL_COMMAND_NAME_V1); + .get(RE_SET_CONTROL_COMMAND_NAME_V1); if (cmdSetControlDef == null) { logger.warn("No command definition found for set control command. Ignoring command"); return; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java index 0255eeddda0a0..d75828eaa9ef9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiV2ClientServiceImpl.java @@ -14,12 +14,11 @@ import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCanonicalSnapshot; import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; @@ -42,13 +41,13 @@ protected LGThinQFridgeApiV2ClientServiceImpl(HttpClient httpClient) { } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { - // TODO - Analise what to do here + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { + // there's no before settings to send command + return false; } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { throw new UnsupportedOperationException("Not implemented yet."); } @@ -99,7 +98,7 @@ public void setEcoFriendlyMode(String bridgeId, String deviceId, boolean trueOnF @Override public void setIcePlus(String bridgeId, String deviceId, FridgeCapability fridgeCapability, boolean trueOnFalseOff, - Map snapCmdData) throws LGThinqApiException { + Map snapCmdData) { throw new UnsupportedOperationException("V2 Fridge doesn't support IcePlus feature. It mostly like a bug"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java index 29d197c4662ee..df88d80ed7d26 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java @@ -15,7 +15,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java index d2664f14f8a0b..ca49c2a1e1b71 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV1ClientServiceImpl.java @@ -15,12 +15,11 @@ import java.util.LinkedHashMap; import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; @@ -47,20 +46,19 @@ protected LGThinQWMApiV1ClientServiceImpl(HttpClient httpClient) { } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { - // Nothing to do for V1 thinq + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { + // there's no before settings to send command + return false; } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { throw new UnsupportedOperationException("Not implemented yet."); } @Override @Nullable - public WasherDryerSnapshot getDeviceData(@NonNull String bridgeName, @NonNull String deviceId, - @NonNull CapabilityDefinition capDef) throws LGThinqApiException { + public WasherDryerSnapshot getDeviceData(String bridgeName, String deviceId, CapabilityDefinition capDef) { throw new UnsupportedOperationException("Method not supported in V1 API device."); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java index 1e09cc4c66a0d..e2798115c9e8f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java @@ -12,15 +12,14 @@ */ package org.openhab.binding.lgthinq.lgservices; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_COMMAND_REMOTE_START_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WMD_COMMAND_REMOTE_START_V2; import java.util.Map; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.RestResult; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.api.RestResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; @@ -44,13 +43,13 @@ protected LGThinQWMApiV2ClientServiceImpl(HttpClient httpClient) { } @Override - protected void beforeGetDataDevice(@NonNull String bridgeName, @NonNull String deviceId) { - // TODO - Analise what to do here + protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { + // there's no before settings to send command + return false; } @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) - throws LGThinqApiException { + public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { throw new UnsupportedOperationException("Not implemented yet."); } @@ -61,7 +60,7 @@ public void remoteStart(String bridgeName, WasherDryerCapability cap, String dev ObjectNode dataSetList = JsonNodeFactory.instance.objectNode(); ObjectNode nodeData = dataSetList.putObject("dataSetList").putObject("washerDryer"); // 1 - mount nodeData template - CommandDefinition cdStart = cap.getCommandsDefinition().get(WM_COMMAND_REMOTE_START_V2); + CommandDefinition cdStart = cap.getCommandsDefinition().get(WMD_COMMAND_REMOTE_START_V2); if (cdStart == null) { throw new LGThinqApiException( "Command WMStart doesn't defined in cap. Do the Device support Remote Start ?"); @@ -80,7 +79,7 @@ public void remoteStart(String bridgeName, WasherDryerCapability cap, String dev } } - RestResult result = sendCommand(bridgeName, deviceId, "control-sync", WM_COMMAND_REMOTE_START_V2, "Set", + RestResult result = sendCommand(bridgeName, deviceId, "control-sync", WMD_COMMAND_REMOTE_START_V2, "Set", null, null, dataSetList); handleGenericErrorResult(result); } catch (LGThinqApiException e) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java new file mode 100644 index 0000000000000..3c5dfe53c4c97 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.api.model.GatewayResult; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link LGThinqCanonicalModelUtil} class + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqCanonicalModelUtil { + public static final ObjectMapper mapper = new ObjectMapper(); + + public static GatewayResult getGatewayResult(String rawJson) throws IOException { + Map map = mapper.readValue(rawJson, new TypeReference<>() { + }); + Map content = mapper.convertValue(map, new TypeReference<>() { + }); + String resultCode = (String) map.get("resultCode"); + if (content == null) { + throw new IllegalArgumentException("Enexpected result. Gateway Content Result is null"); + } else if (resultCode == null) { + throw new IllegalArgumentException("Enexpected result. resultCode code is null"); + } + + return new GatewayResult(Objects.requireNonNull(resultCode, "Expected resultCode field in json"), "", + Objects.requireNonNull(content.get("rtiUri"), "Expected rtiUri field in json"), + Objects.requireNonNull(content.get("thinq1Uri"), "Expected thinq1Uri field in json"), + Objects.requireNonNull(content.get("thinq2Uri"), "Expected thinq2Uri field in json"), + Objects.requireNonNull(content.get("empUri"), "Expected empUri field in json"), + Objects.requireNonNull(content.get("empTermsUri"), "Expected empTermsUri field in json"), "", + Objects.requireNonNull(content.get("empSpxUri"), "Expected empSpxUri field in json")); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java new file mode 100644 index 0000000000000..f8b1ff7f758de --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java @@ -0,0 +1,151 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api; + +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_EMP_SESS_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_EMP_SESS_URL; + +import java.io.Serial; +import java.io.Serializable; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.api.model.GatewayResult; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * The {@link LGThinqGateway} hold informations about the LG Gateway + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqGateway implements Serializable { + @Serial + private static final long serialVersionUID = 202409261421L; + private String empBaseUri = ""; + private String loginBaseUri = ""; + private String apiRootV1 = ""; + private String apiRootV2 = ""; + private String authBase = ""; + private String language = ""; + private String country = ""; + private String username = ""; + private String password = ""; + private String alternativeEmpServer = ""; + private int accountVersion; + + public LGThinqGateway() { + } + + public LGThinqGateway(GatewayResult gwResult, String language, String country, String alternativeEmpServer) { + this.apiRootV2 = gwResult.getThinq2Uri(); + this.apiRootV1 = gwResult.getThinq1Uri(); + this.loginBaseUri = gwResult.getEmpSpxUri(); + this.authBase = gwResult.getEmpUri(); + this.empBaseUri = gwResult.getEmpTermsUri(); + this.language = language; + this.country = country; + this.alternativeEmpServer = alternativeEmpServer; + } + + @JsonIgnore + public String getTokenSessionEmpUrl() { + return alternativeEmpServer.isBlank() ? LG_API_V2_EMP_SESS_URL : alternativeEmpServer + LG_API_V2_EMP_SESS_PATH; + } + + public String getEmpBaseUri() { + return empBaseUri; + } + + public int getAccountVersion() { + return accountVersion; + } + + public String getApiRootV2() { + return apiRootV2; + } + + public String getAuthBase() { + return authBase; + } + + public String getLanguage() { + return language; + } + + public String getCountry() { + return country; + } + + public String getLoginBaseUri() { + return loginBaseUri; + } + + public String getApiRootV1() { + return apiRootV1; + } + + public void setEmpBaseUri(String empBaseUri) { + this.empBaseUri = empBaseUri; + } + + public void setLoginBaseUri(String loginBaseUri) { + this.loginBaseUri = loginBaseUri; + } + + public void setApiRootV1(String apiRootV1) { + this.apiRootV1 = apiRootV1; + } + + public void setApiRootV2(String apiRootV2) { + this.apiRootV2 = apiRootV2; + } + + public void setAuthBase(String authBase) { + this.authBase = authBase; + } + + public void setLanguage(String language) { + this.language = language; + } + + public void setCountry(String country) { + this.country = country; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + @Override + public String toString() { + return "LGThinqGateway{" + "empBaseUri='" + empBaseUri + '\'' + ", loginBaseUri='" + loginBaseUri + '\'' + + ", apiRootV1='" + apiRootV1 + '\'' + ", apiRootV2='" + apiRootV2 + '\'' + ", authBase='" + authBase + + '\'' + ", language='" + language + '\'' + ", country='" + country + '\'' + ", username='" + + (!username.isEmpty() ? "******" : "") + '\'' + ", password='" + + (!password.isEmpty() ? "******" : "") + '\'' + ", alternativeEmpServer='" + + alternativeEmpServer + '\'' + ", accountVersion=" + accountVersion + '}'; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/OauthLgEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/OauthLgEmpAuthenticator.java new file mode 100644 index 0000000000000..f77c073707da3 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/OauthLgEmpAuthenticator.java @@ -0,0 +1,387 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api; + +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_API_KEY_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APPLICATION_KEY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APP_LEVEL; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APP_OS; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APP_TYPE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APP_VER; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_CLIENT_ID; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_DATE_FORMAT; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_MESSAGE_ID; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_OAUTH_CLIENT_KEY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_OAUTH_SEARCH_KEY_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_OAUTH_SECRET_KEY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_PRE_LOGIN_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_SVC_CODE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_SVC_PHASE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_AUTH_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_SESSION_LOGIN_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_USER_INFO; + +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.text.SimpleDateFormat; +import java.util.AbstractMap; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.TimeZone; + +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.lgthinq.lgservices.api.model.GatewayResult; +import org.openhab.binding.lgthinq.lgservices.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link OauthLgEmpAuthenticator} main service to authenticate against LG Emp Server via Oauth + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class OauthLgEmpAuthenticator { + + private static final Logger logger = LoggerFactory.getLogger(OauthLgEmpAuthenticator.class); + private static final Map oauthSearchKeyQueryParams = new LinkedHashMap<>(); + private static final ObjectMapper objectMapper = new ObjectMapper(); + + static { + oauthSearchKeyQueryParams.put("key_name", "OAUTH_SECRETKEY"); + oauthSearchKeyQueryParams.put("sever_type", "OP"); + } + + private final HttpClient httpClient; + + public OauthLgEmpAuthenticator(HttpClient httpClient) { + this.httpClient = httpClient; + } + + record PreLoginResult(String username, String signature, String timestamp, String encryptedPwd) { + } + + record LoginAccountResult(String userIdType, String userId, String country, String loginSessionId) { + } + + private Map getGatewayRestHeader(String language, String country) { + return Map.ofEntries(new AbstractMap.SimpleEntry("Accept", "application/json"), + new AbstractMap.SimpleEntry("x-api-key", LG_API_API_KEY_V2), + new AbstractMap.SimpleEntry("x-country-code", country), + new AbstractMap.SimpleEntry("x-client-id", LG_API_CLIENT_ID), + new AbstractMap.SimpleEntry("x-language-code", language), + new AbstractMap.SimpleEntry("x-message-id", LG_API_MESSAGE_ID), + new AbstractMap.SimpleEntry("x-service-code", LG_API_SVC_CODE), + new AbstractMap.SimpleEntry("x-service-phase", LG_API_SVC_PHASE), + new AbstractMap.SimpleEntry("x-thinq-app-level", LG_API_APP_LEVEL), + new AbstractMap.SimpleEntry("x-thinq-app-os", LG_API_APP_OS), + new AbstractMap.SimpleEntry("x-thinq-app-type", LG_API_APP_TYPE), + new AbstractMap.SimpleEntry("x-thinq-app-ver", LG_API_APP_VER)); + } + + private Map getLoginHeader(LGThinqGateway gw) { + Map headers = new HashMap<>(); + headers.put("Connection", "keep-alive"); + headers.put("X-Device-Language-Type", "IETF"); + headers.put("X-Application-Key", "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"); + headers.put("X-Client-App-Key", "LGAO221A02"); + headers.put("X-Lge-Svccode", "SVC709"); + headers.put("X-Device-Type", "M01"); + headers.put("X-Device-Platform", "ADR"); + headers.put("X-Device-Publish-Flag", "Y"); + headers.put("X-Device-Country", gw.getCountry()); + headers.put("X-Device-Language", gw.getLanguage()); + headers.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); + headers.put("Access-Control-Allow-Origin", "*"); + headers.put("Accept-Encoding", "gzip, deflate, br"); + headers.put("Accept-Language", "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7"); + headers.put("Accept", "application/json"); + return headers; + } + + public LGThinqGateway discoverGatewayConfiguration(String gwUrl, String language, String country, + String alternativeEmpServer) throws IOException { + Map header = getGatewayRestHeader(language, country); + RestResult result; + result = RestUtils.getCall(httpClient, gwUrl, header, null); + + if (result.getStatusCode() != 200) { + throw new IllegalStateException( + "Expected HTTP OK return, but received result core:" + result.getJsonResponse()); + } else { + GatewayResult gwResult = LGThinqCanonicalModelUtil.getGatewayResult(result.getJsonResponse()); + ResultCodes resultCode = ResultCodes.fromCode(gwResult.getReturnedCode()); + if (ResultCodes.OK != resultCode) { + throw new IllegalStateException(String.format( + "Result from LGThinq Gateway from Authentication URL was unexpected. ResultCode: %s, with message:%s, Error Description:%s", + gwResult.getReturnedCode(), gwResult.getReturnedMessage(), resultCode.getDescription())); + } + + return new LGThinqGateway(gwResult, language, country, alternativeEmpServer); + } + } + + public PreLoginResult preLoginUser(LGThinqGateway gw, String username, String password) throws IOException { + String encPwd = RestUtils.getPreLoginEncPwd(password); + Map headers = getLoginHeader(gw); + // 1) Doing preLogin -> getting the password key + String preLoginUrl = gw.getLoginBaseUri() + LG_API_PRE_LOGIN_PATH; + Map formData = Map.of("user_auth2", encPwd, "log_param", String.format( + "login request / user_id : %s / " + "third_party : null / svc_list : SVC202,SVC710 / 3rd_service : ", + username)); + RestResult resp = RestUtils.postCall(httpClient, preLoginUrl, headers, formData); + if (resp == null) { + logger.error("Error preLogin into account. Null data returned"); + throw new IllegalStateException("Error login into account. Null data returned"); + } else if (resp.getStatusCode() != 200) { + logger.error("Error preLogin into account. The reason is:{}", resp.getJsonResponse()); + throw new IllegalStateException(String.format("Error login into account:%s", resp.getJsonResponse())); + } + + Map preLoginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + logger.debug("encrypted_pw={}, signature={}, tStamp={}", preLoginResult.get("encrypted_pw"), + preLoginResult.get("signature"), preLoginResult.get("tStamp")); + return new PreLoginResult(username, + Objects.requireNonNull(preLoginResult.get("signature"), + "Unexpected login json result. Node 'signature' not found"), + Objects.requireNonNull(preLoginResult.get("tStamp"), + "Unexpected login json result. Node 'signature' not found"), + Objects.requireNonNull(preLoginResult.get("encrypted_pw"), + "Unexpected login json result. Node 'signature' not found")); + } + + public LoginAccountResult loginUser(LGThinqGateway gw, PreLoginResult preLoginResult) throws IOException { + // 2 - Login with username and hashed password + Map headers = getLoginHeader(gw); + headers.put("X-Signature", preLoginResult.signature()); + headers.put("X-Timestamp", preLoginResult.timestamp()); + Map formData = Map.of("user_auth2", preLoginResult.encryptedPwd(), + "password_hash_prameter_flag", "Y", "svc_list", "SVC202,SVC710"); // SVC202=LG SmartHome, SVC710=EMP + // OAuth + String loginUrl = gw.getEmpBaseUri() + LG_API_V2_SESSION_LOGIN_PATH + + URLEncoder.encode(preLoginResult.username(), StandardCharsets.UTF_8); + RestResult resp = RestUtils.postCall(httpClient, loginUrl, headers, formData); + if (resp == null) { + logger.error("Error login into account. Null data returned"); + throw new IllegalStateException("Error loggin into acccount. Null data returned"); + } else if (resp.getStatusCode() != 200) { + logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); + throw new IllegalStateException(String.format("Error login into account:%s", resp.getJsonResponse())); + } + Map loginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + Map accountResult = objectMapper.convertValue(loginResult.get("account"), + new TypeReference<>() { + }); + if (accountResult == null) { + throw new IllegalStateException("Error getting account from Login"); + } + return new LoginAccountResult( + Objects.requireNonNull(accountResult.get("userIDType"), + "Unexpected account json result. 'userIDType' not found"), + Objects.requireNonNull(accountResult.get("userID"), + "Unexpected account json result. 'userID' not found"), + Objects.requireNonNull(accountResult.get("country"), + "Unexpected account json result. 'country' not found"), + Objects.requireNonNull(accountResult.get("loginSessionID"), + "Unexpected account json result. 'loginSessionID' not found")); + } + + private String getCurrentTimestamp() { + SimpleDateFormat sdf = new SimpleDateFormat(LG_API_DATE_FORMAT, Locale.US); + sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + return sdf.format(new Date()); + } + + TokenResult getToken(LGThinqGateway gw, LoginAccountResult accountResult) throws IOException { + // 3 - get secret key from emp signature + String empSearchKeyUrl = gw.getLoginBaseUri() + LG_API_OAUTH_SEARCH_KEY_PATH; + + RestResult resp = RestUtils.getCall(httpClient, empSearchKeyUrl, null, oauthSearchKeyQueryParams); + if (resp.getStatusCode() != 200) { + logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); + throw new IllegalStateException(String.format("Error loggin into acccount:%s", resp.getJsonResponse())); + } + Map secretResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + String secretKey = Objects.requireNonNull(secretResult.get("returnData"), + "Unexpected json returned. Expected 'returnData' node here"); + logger.debug("Secret found:{}", secretResult.get("returnData")); + + // 4 - get OAuth Token Key from EMP API + Map empData = new LinkedHashMap<>(); + empData.put("account_type", accountResult.userIdType()); + empData.put("client_id", LG_API_CLIENT_ID); + empData.put("country_code", accountResult.country()); + empData.put("username", accountResult.userId()); + String timestamp = getCurrentTimestamp(); + + byte[] oauthSig = RestUtils.getTokenSignature(gw.getTokenSessionEmpUrl(), secretKey, empData, timestamp); + + Map oauthEmpHeaders = getOauthEmpHeaders(accountResult, timestamp, oauthSig); + logger.debug("===> Localized timestamp used: [{}]", timestamp); + logger.debug("===> signature created: [{}]", new String(oauthSig)); + resp = RestUtils.postCall(httpClient, gw.getTokenSessionEmpUrl(), oauthEmpHeaders, empData); + return handleTokenResult(resp); + } + + private Map getOauthEmpHeaders(LoginAccountResult accountResult, String timestamp, + byte[] oauthSig) { + Map oauthEmpHeaders = new LinkedHashMap<>(); + oauthEmpHeaders.put("lgemp-x-app-key", LG_API_OAUTH_CLIENT_KEY); + oauthEmpHeaders.put("lgemp-x-date", timestamp); + oauthEmpHeaders.put("lgemp-x-session-key", accountResult.loginSessionId()); + oauthEmpHeaders.put("lgemp-x-signature", new String(oauthSig)); + oauthEmpHeaders.put("Accept", "application/json"); + oauthEmpHeaders.put("X-Device-Type", "M01"); + oauthEmpHeaders.put("X-Device-Platform", "ADR"); + oauthEmpHeaders.put("Content-Type", "application/x-www-form-urlencoded"); + oauthEmpHeaders.put("Access-Control-Allow-Origin", "*"); + oauthEmpHeaders.put("Accept-Encoding", "gzip, deflate, br"); + oauthEmpHeaders.put("Accept-Language", "en-US,en;q=0.9"); + oauthEmpHeaders.put("User-Agent", + "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.44"); + return oauthEmpHeaders; + } + + public UserInfo getUserInfo(TokenResult token) throws IOException { + UriBuilder builder = UriBuilder.fromUri(token.getOauthBackendUrl()).path(LG_API_V2_USER_INFO); + String oauthUrl = builder.build().toURL().toString(); + String timestamp = getCurrentTimestamp(); + byte[] oauthSig = RestUtils.getTokenSignature(oauthUrl, LG_API_OAUTH_SECRET_KEY, Collections.emptyMap(), + timestamp); + Map headers = Map.of("Accept", "application/json", "Authorization", + String.format("Bearer %s", token.getAccessToken()), "X-Lge-Svccode", LG_API_SVC_CODE, + "X-Application-Key", LG_API_APPLICATION_KEY, "lgemp-x-app-key", LG_API_CLIENT_ID, "X-Device-Type", + "M01", "X-Device-Platform", "ADR", "x-lge-oauth-date", timestamp, "x-lge-oauth-signature", + new String(oauthSig)); + RestResult resp = RestUtils.getCall(httpClient, oauthUrl, headers, null); + + return handleAccountInfoResult(resp); + } + + private UserInfo handleAccountInfoResult(RestResult resp) throws IOException { + Map result = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + if (resp.getStatusCode() != 200) { + logger.error("LG API returned error when trying to get user account information. The reason is:{}", + resp.getJsonResponse()); + throw new IllegalStateException( + String.format("LG API returned error when trying to get user account information. The reason is:%s", + resp.getJsonResponse())); + } else if (result.get("account") == null + || ((Map) result.getOrDefault("account", Collections.emptyMap())).get("userNo") == null) { + throw new IllegalStateException("Error retrieving the account user information from access token"); + } + Map accountInfo = objectMapper.convertValue(result.get("account"), new TypeReference<>() { + }); + + return new UserInfo( + Objects.requireNonNullElse(accountInfo.get("userNo"), + "Unexpected result. userID must be present in json result"), + Objects.requireNonNull(accountInfo.get("userID"), + "Unexpected result. userID must be present in json result"), + Objects.requireNonNull(accountInfo.get("userIDType"), + "Unexpected result. userIDType must be present in json result"), + Objects.requireNonNullElse(accountInfo.get("displayUserID"), "")); + } + + public TokenResult doRefreshToken(TokenResult currentToken) throws IOException, RefreshTokenException { + UriBuilder builder = UriBuilder.fromUri(currentToken.getOauthBackendUrl()).path(LG_API_V2_AUTH_PATH); + String oauthUrl = builder.build().toURL().toString(); + String timestamp = getCurrentTimestamp(); + + Map formData = new LinkedHashMap<>(); + formData.put("grant_type", "refresh_token"); + formData.put("refresh_token", currentToken.getRefreshToken()); + + byte[] oauthSig = RestUtils.getTokenSignature(oauthUrl, LG_API_OAUTH_SECRET_KEY, formData, timestamp); + + Map headers = Map.of("x-lge-appkey", LG_API_CLIENT_ID, "x-lge-oauth-signature", + new String(oauthSig), "x-lge-oauth-date", timestamp, "Accept", "application/json"); + + RestResult resp = RestUtils.postCall(httpClient, oauthUrl, headers, formData); + return handleRefreshTokenResult(resp, currentToken); + } + + private TokenResult handleTokenResult(@Nullable RestResult resp) throws IOException { + Map tokenResult; + if (resp == null) { + throw new IllegalStateException("Error getting oauth token. Null data returned"); + } + if (resp.getStatusCode() != 200) { + logger.error("Error getting oauth token. HTTP Status Code is:{}, The reason is:{}", resp.getStatusCode(), + resp.getJsonResponse()); + throw new IllegalStateException(String.format("Error getting oauth token:%s", resp.getJsonResponse())); + } else { + tokenResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + Integer status = (Integer) tokenResult.get("status"); + if ((status != null && !"1".equals("" + status)) || tokenResult.get("expires_in") == null) { + throw new IllegalStateException(String.format("Status error getting token:%s", tokenResult)); + } + } + + return new TokenResult( + Objects.requireNonNull((String) tokenResult.get("access_token"), + "Unexpected result. access_token must be present in json result"), + Objects.requireNonNull((String) tokenResult.get("refresh_token"), + "Unexpected result. refresh_token must be present in json result"), + Integer.parseInt(Objects.requireNonNull((String) tokenResult.get("expires_in"), + "Unexpected result. expires_in must be present in json result")), + new Date(), Objects.requireNonNull((String) tokenResult.get("oauth2_backend_url"), + "Unexpected result. oauth2_backend_url must be present in json result")); + } + + private TokenResult handleRefreshTokenResult(@Nullable RestResult resp, TokenResult currentToken) + throws IOException, RefreshTokenException { + Map tokenResult; + if (resp == null) { + logger.error("Error getting oauth token. Null data returned"); + throw new RefreshTokenException("Error getting oauth token. Null data returned"); + } + if (resp.getStatusCode() != 200) { + logger.error("Error getting oauth token. HTTP Status Code is:{}, The reason is:{}", resp.getStatusCode(), + resp.getJsonResponse()); + throw new RefreshTokenException(String.format("Error getting oauth token:%s", resp.getJsonResponse())); + } else { + tokenResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { + }); + if (tokenResult.get("access_token") == null || tokenResult.get("expires_in") == null) { + throw new RefreshTokenException(String.format("Status error get refresh token info:%s", tokenResult)); + } + } + + currentToken.setAccessToken(Objects.requireNonNull(tokenResult.get("access_token"), + "Unexpected error. Access Token must ever been provided by LG API")); + currentToken.setGeneratedTime(new Date()); + currentToken.setExpiresIn(Integer.parseInt(Objects.requireNonNull(tokenResult.get("expires_in"), + "Unexpected error. Access Token must ever been provided by LG API"))); + return currentToken; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestResult.java new file mode 100644 index 0000000000000..1ae8c9545cc14 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestResult.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link RestResult} result from rest calls + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class RestResult { + private final String jsonResponse; + private final int resultCode; + + public RestResult(String jsonResponse, int resultCode) { + this.jsonResponse = jsonResponse; + this.resultCode = resultCode; + } + + public String getJsonResponse() { + return jsonResponse; + } + + public int getStatusCode() { + return resultCode; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java new file mode 100644 index 0000000000000..80333e5b66a28 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java @@ -0,0 +1,173 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Base64; +import java.util.Map; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.ws.rs.core.UriBuilder; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +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.util.FormContentProvider; +import org.eclipse.jetty.client.util.StringContentProvider; +import org.eclipse.jetty.util.Fields; +import org.openhab.core.i18n.CommunicationException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * The {@link RestUtils} rest utilities + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class RestUtils { + + private static final Logger logger = LoggerFactory.getLogger(RestUtils.class); + private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; + + public static String getPreLoginEncPwd(String pwdToEnc) { + MessageDigest digest; + try { + digest = MessageDigest.getInstance("SHA-512"); + } catch (NoSuchAlgorithmException e) { + logger.error("Definitively, it is unexpected.", e); + throw new IllegalStateException("Unexpected error. SHA-512 algorithm must exists in JDK distribution", e); + } + digest.reset(); + digest.update(pwdToEnc.getBytes(StandardCharsets.UTF_8)); + + return String.format("%0128x", new BigInteger(1, digest.digest())); + } + + public static byte[] getOauth2Sig(String messageSign, String secret) { + byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8); + SecretKeySpec signingKey = new SecretKeySpec(secretBytes, HMAC_SHA1_ALGORITHM); + + try { + Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); + mac.init(signingKey); + return Base64.getEncoder().encode(mac.doFinal(messageSign.getBytes(StandardCharsets.UTF_8))); + } catch (NoSuchAlgorithmException e) { + logger.error("Unexpected error. SHA1 algorithm must exists in JDK distribution.", e); + throw new IllegalStateException("Unexpected error. SHA1 algorithm must exists in JDK distribution", e); + } catch (InvalidKeyException e) { + logger.error("Unexpected error.", e); + throw new IllegalStateException("Unexpected error.", e); + } + } + + public static byte[] getTokenSignature(String authUrl, String secretKey, Map empData, + String timestamp) { + UriBuilder builder = UriBuilder.fromUri(authUrl); + empData.forEach(builder::queryParam); + + URI reqUri = builder.build(); + String signUrl = !empData.isEmpty() ? reqUri.getPath() + "?" + reqUri.getRawQuery() : reqUri.getPath(); + String messageToSign = String.format("%s\n%s", signUrl, timestamp); + return getOauth2Sig(messageToSign, secretKey); + } + + public static RestResult getCall(HttpClient httpClient, String encodedUrl, @Nullable Map headers, + @Nullable Map params) { + + Request request = httpClient.newRequest(encodedUrl).method("GET"); + if (params != null) { + params.forEach(request::param); + } + if (headers != null) { + headers.forEach(request::header); + } + + if (logger.isTraceEnabled()) { + logger.trace("GET request: {}", request.getURI()); + } + try { + ContentResponse response = request.send(); + + logger.trace("GET response: {}", response.getContentAsString()); + + return new RestResult(response.getContentAsString(), response.getStatus()); + } catch (InterruptedException | TimeoutException | ExecutionException e) { + logger.error("Exception occurred during GET execution: {}", e.getMessage(), e); + throw new CommunicationException(e); + } + } + + @Nullable + public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, + String jsonData) throws IOException { + return postCall(httpClient, encodedUrl, headers, new StringContentProvider(jsonData)); + } + + @Nullable + public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, + Map formParams) throws IOException { + Fields fields = new Fields(); + formParams.forEach(fields::put); + return postCall(httpClient, encodedUrl, headers, new FormContentProvider(fields)); + } + + @Nullable + private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, + ContentProvider contentProvider) { + + try { + Request request = httpClient.newRequest(encodedUrl).method("POST").content(contentProvider).timeout(10, + TimeUnit.SECONDS); + headers.forEach(request::header); + if (logger.isTraceEnabled()) { + logger.trace("POST request: {}", request.getURI()); + } + + ContentResponse response = request.content(contentProvider).timeout(10, TimeUnit.SECONDS).send(); + + logger.trace("POST response: {}", response.getContentAsString()); + + return new RestResult(response.getContentAsString(), response.getStatus()); + } catch (TimeoutException e) { + if (logger.isDebugEnabled()) { + logger.warn("Timeout reading post call result from LG API", e); + } else { + logger.warn("Timeout reading post call result from LG API"); + } + // In SocketTimeout cases I'm considering that I have no response on time. Then, I return null data + // forcing caller to retry. + return null; + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + logger.error("InterruptedException occurred during POST execution: {}", e.getMessage(), e); + throw new CommunicationException(e); + } catch (ExecutionException e) { + logger.error("ExecutionException occurred during POST execution: {}", e.getMessage(), e); + throw new CommunicationException(e); + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java new file mode 100644 index 0000000000000..fbc7d0c248eab --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java @@ -0,0 +1,175 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api; + +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THINQ_CONNECTION_DATA_FILE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_GATEWAY_SERVICE_PATH_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_GATEWAY_URL_V2; + +import java.io.File; +import java.io.IOException; +import java.util.Calendar; +import java.util.Date; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.jetty.client.HttpClient; +import org.openhab.binding.lgthinq.lgservices.errors.AccountLoginException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqGatewayException; +import org.openhab.binding.lgthinq.lgservices.errors.PreLoginException; +import org.openhab.binding.lgthinq.lgservices.errors.RefreshTokenException; +import org.openhab.binding.lgthinq.lgservices.errors.TokenException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.fasterxml.jackson.databind.ObjectMapper; + +/** + * The {@link TokenManager} Principal facade to manage all token handles + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class TokenManager { + private static final int EXPIRICY_TOLERANCE_SEC = 60; + private static final Logger logger = LoggerFactory.getLogger(TokenManager.class); + private final OauthLgEmpAuthenticator oAuthAuthenticator; + private final ObjectMapper objectMapper = new ObjectMapper(); + private final Map tokenCached = new ConcurrentHashMap<>(); + + public TokenManager(HttpClient httpClient) { + oAuthAuthenticator = new OauthLgEmpAuthenticator(httpClient); + } + + public boolean isTokenExpired(TokenResult token) { + Calendar c = Calendar.getInstance(); + c.setTime(token.getGeneratedTime()); + c.add(Calendar.SECOND, token.getExpiresIn() - EXPIRICY_TOLERANCE_SEC); + Date expiricyDate = c.getTime(); + return expiricyDate.before(new Date()); + } + + public TokenResult refreshToken(String bridgeName, TokenResult currentToken) throws RefreshTokenException { + try { + TokenResult token = oAuthAuthenticator.doRefreshToken(currentToken); + objectMapper.writeValue(new File(getConfigDataFileName(bridgeName)), token); + return token; + } catch (IOException e) { + throw new RefreshTokenException("Error refreshing LGThinq token", e); + } + } + + private String getConfigDataFileName(String bridgeName) { + return String.format(THINQ_CONNECTION_DATA_FILE, bridgeName); + } + + public boolean isOauthTokenRegistered(String bridgeName) { + File tokenFile = new File(getConfigDataFileName(bridgeName)); + // TODO - check if the file content is valid. + return tokenFile.isFile(); + } + + private String getGatewayUrl(String alternativeGtwServer) { + return alternativeGtwServer.isBlank() ? LG_API_GATEWAY_URL_V2 + : (alternativeGtwServer + LG_API_GATEWAY_SERVICE_PATH_V2); + } + + public void oauthFirstRegistration(String bridgeName, String language, String country, String username, + String password, String alternativeGtwServer) + throws LGThinqGatewayException, PreLoginException, AccountLoginException, TokenException, IOException { + LGThinqGateway gw; + OauthLgEmpAuthenticator.PreLoginResult preLogin; + OauthLgEmpAuthenticator.LoginAccountResult accountLogin; + TokenResult token; + UserInfo userInfo; + try { + gw = oAuthAuthenticator.discoverGatewayConfiguration(getGatewayUrl(alternativeGtwServer), language, country, + alternativeGtwServer); + } catch (Exception ex) { + throw new LGThinqGatewayException( + "Error trying to discovery the LG Gateway Setting for the region informed", ex); + } + + try { + preLogin = oAuthAuthenticator.preLoginUser(gw, username, password); + } catch (Exception ex) { + logger.error("Error pre-login with gateway: {}", gw); + throw new PreLoginException("Error doing pre-login of the user in the Emp LG Server", ex); + } + try { + accountLogin = oAuthAuthenticator.loginUser(gw, preLogin); + } catch (Exception ex) { + logger.error("Error logging with gateway: {}", gw); + throw new AccountLoginException("Error doing user's account login on the Emp LG Server", ex); + } + try { + token = oAuthAuthenticator.getToken(gw, accountLogin); + } catch (Exception ex) { + logger.error("Error getting token with gateway: {}", gw); + throw new TokenException("Error getting Token", ex); + } + try { + userInfo = oAuthAuthenticator.getUserInfo(token); + token.setUserInfo(userInfo); + token.setGatewayInfo(gw); + } catch (Exception ex) { + throw new TokenException("Error getting UserInfo from Token", ex); + } + + // persist the token information generated in file + objectMapper.writeValue(new File(getConfigDataFileName(bridgeName)), token); + } + + public TokenResult getValidRegisteredToken(String bridgeName) throws IOException, RefreshTokenException { + TokenResult validToken; + TokenResult bridgeToken = tokenCached.get(bridgeName); + if (bridgeToken == null) { + bridgeToken = Objects.requireNonNull( + objectMapper.readValue(new File(getConfigDataFileName(bridgeName)), TokenResult.class), + "Unexpected. Never null here"); + } + + if (!isValidToken(bridgeToken)) { + throw new RefreshTokenException( + "Token is not valid. Try to delete token file and disable/enable bridge to restart authentication process"); + } else { + tokenCached.put(bridgeName, bridgeToken); + } + + validToken = Objects.requireNonNull(bridgeToken, "Unexpected. Never null here"); + if (isTokenExpired(validToken)) { + validToken = refreshToken(bridgeName, validToken); + } + return validToken; + } + + private boolean isValidToken(@Nullable TokenResult token) { + return token != null && !token.getAccessToken().isBlank() && token.getExpiresIn() != 0 + && !token.getOauthBackendUrl().isBlank() && !token.getRefreshToken().isBlank(); + } + + /** + * Remove the toke file registered for the bridge. Must be called only if the bridge is removed + */ + public void cleanupTokenRegistry(String bridgeName) { + File f = new File(getConfigDataFileName(bridgeName)); + if (f.isFile()) { + if (!f.delete()) { + logger.warn("Can't delete token registry file {}", f.getName()); + } + } + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenResult.java new file mode 100644 index 0000000000000..f87341f193581 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenResult.java @@ -0,0 +1,106 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link TokenResult} Hold information about token and related entities + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class TokenResult implements Serializable { + @Serial + private static final long serialVersionUID = 202409261447L; + private String accessToken = ""; + private String refreshToken = ""; + private int expiresIn; + private Date generatedTime = new Date(); + private String oauthBackendUrl = ""; + private UserInfo userInfo = new UserInfo(); + private LGThinqGateway gatewayInfo = new LGThinqGateway(); + + public TokenResult(String accessToken, String refreshToken, int expiresIn, Date generatedTime, + String ouathBackendUrl) { + this.accessToken = accessToken; + this.refreshToken = refreshToken; + this.expiresIn = expiresIn; + this.generatedTime = generatedTime; + this.oauthBackendUrl = ouathBackendUrl; + } + + // This constructor will never be called by this. It only exists because of ObjectMapper instantiation needs + public TokenResult() { + } + + public LGThinqGateway getGatewayInfo() { + return gatewayInfo; + } + + public void setGatewayInfo(LGThinqGateway gatewayInfo) { + this.gatewayInfo = gatewayInfo; + } + + public String getAccessToken() { + return accessToken; + } + + public String getRefreshToken() { + return refreshToken; + } + + public int getExpiresIn() { + return expiresIn; + } + + public Date getGeneratedTime() { + return generatedTime; + } + + public String getOauthBackendUrl() { + return oauthBackendUrl; + } + + public void setAccessToken(String accessToken) { + this.accessToken = accessToken; + } + + public void setRefreshToken(String refreshToken) { + this.refreshToken = refreshToken; + } + + public void setExpiresIn(int expiresIn) { + this.expiresIn = expiresIn; + } + + public void setGeneratedTime(Date generatedTime) { + this.generatedTime = generatedTime; + } + + public void setOauthBackendUrl(String ouathBackendUrl) { + this.oauthBackendUrl = ouathBackendUrl; + } + + public UserInfo getUserInfo() { + return userInfo; + } + + public void setUserInfo(UserInfo userInfo) { + this.userInfo = userInfo; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/UserInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/UserInfo.java new file mode 100644 index 0000000000000..24498e0c369bc --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/UserInfo.java @@ -0,0 +1,75 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api; + +import java.io.Serial; +import java.io.Serializable; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link UserInfo} User Info (registered in LG Account) + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class UserInfo implements Serializable { + @Serial + private static final long serialVersionUID = 202409261445L; + private String userNumber = ""; + private String userID = ""; + private String userIDType = ""; + private String displayUserID = ""; + + public UserInfo() { + } + + public UserInfo(String userNumber, String userID, String userIDType, String displayUserId) { + this.userNumber = userNumber; + this.userID = userID; + this.userIDType = userIDType; + this.displayUserID = displayUserId; + } + + public String getUserNumber() { + return userNumber; + } + + public void setUserNumber(String userNumber) { + this.userNumber = userNumber; + } + + public String getUserID() { + return userID; + } + + public void setUserID(String userID) { + this.userID = userID; + } + + public String getUserIDType() { + return userIDType; + } + + public void setUserIDType(String userIDType) { + this.userIDType = userIDType; + } + + public String getDisplayUserID() { + return displayUserID; + } + + public void setDisplayUserID(String displayUserID) { + this.displayUserID = displayUserID; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/model/GatewayResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/model/GatewayResult.java new file mode 100644 index 0000000000000..45a8f56fd98f9 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/model/GatewayResult.java @@ -0,0 +1,71 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link GatewayResult} class + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class GatewayResult extends HeaderResult { + private final String rtiUri; + private final String thinq1Uri; + private final String thinq2Uri; + private final String empUri; + private final String empTermsUri; + private final String oauthUri; + private final String empSpxUri; + + public GatewayResult(String resultCode, String resultMessage, String rtiUri, String thinq1Uri, String thinq2Uri, + String empUri, String empTermsUri, String oauthUri, String empSpxUri) { + super(resultCode, resultMessage); + this.rtiUri = rtiUri; + this.thinq1Uri = thinq1Uri; + this.thinq2Uri = thinq2Uri; + this.empUri = empUri; + this.empTermsUri = empTermsUri; + this.oauthUri = oauthUri; + this.empSpxUri = empSpxUri; + } + + public String getRtiUri() { + return rtiUri; + } + + public String getEmpTermsUri() { + return empTermsUri; + } + + public String getEmpSpxUri() { + return empSpxUri; + } + + public String getThinq1Uri() { + return thinq1Uri; + } + + public String getThinq2Uri() { + return thinq2Uri; + } + + public String getEmpUri() { + return empUri; + } + + public String getOauthUri() { + return oauthUri; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/model/HeaderResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/model/HeaderResult.java new file mode 100644 index 0000000000000..039106a33fd6f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/model/HeaderResult.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.api.model; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link HeaderResult} class + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class HeaderResult { + private final String returnedCode; + private final String returnedMessage; + + public HeaderResult(String returnedCode, String returnedMessage) { + this.returnedCode = returnedCode; + this.returnedMessage = returnedMessage; + } + + public String getReturnedCode() { + return returnedCode; + } + + public String getReturnedMessage() { + return returnedMessage; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/AccountLoginException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/AccountLoginException.java new file mode 100644 index 0000000000000..58448b06a7d45 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/AccountLoginException.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link AccountLoginException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class AccountLoginException extends LGThinqException { + @Serial + private static final long serialVersionUID = 202409261450L; + + public AccountLoginException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqApiException.java new file mode 100644 index 0000000000000..76d78d2d3dc0b --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqApiException.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; + +/** + * The {@link LGThinqApiException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqApiException extends LGThinqException { + @Serial + private static final long serialVersionUID = 202409261451L; + protected ResultCodes apiReasonCode = ResultCodes.UNKNOWN; + + public LGThinqApiException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqApiException(String message, Throwable cause, ResultCodes reasonCode) { + super(message, cause); + this.apiReasonCode = reasonCode; + } + + public ResultCodes getApiReasonCode() { + return apiReasonCode; + } + + public LGThinqApiException(String message) { + super(message); + } + + public LGThinqApiException(String message, ResultCodes resultCode) { + super(message); + this.apiReasonCode = resultCode; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqApiExhaustionException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqApiExhaustionException.java new file mode 100644 index 0000000000000..3d971bd33d492 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqApiExhaustionException.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqApiExhaustionException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqApiExhaustionException extends LGThinqException { + @Serial + private static final long serialVersionUID = 202409261451L; + + public LGThinqApiExhaustionException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqApiExhaustionException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqDeviceV1MonitorExpiredException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqDeviceV1MonitorExpiredException.java new file mode 100644 index 0000000000000..e22ba56f4eaae --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqDeviceV1MonitorExpiredException.java @@ -0,0 +1,37 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqDeviceV1MonitorExpiredException} - Normally caught by V1 API in monitoring device. + * After long-running moniotor, it indicates the need to refresh the monitor. + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqDeviceV1MonitorExpiredException extends LGThinqException { + @Serial + private static final long serialVersionUID = 202409261450L; + + public LGThinqDeviceV1MonitorExpiredException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqDeviceV1MonitorExpiredException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqDeviceV1OfflineException.java new file mode 100644 index 0000000000000..8bb4639b39d6f --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqDeviceV1OfflineException.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqDeviceV1OfflineException} - Normally caught by V1 API in monitoring device. + * When the device is OFFLINE (away from internet), the API doesn't return data information and this + * exception is thrown to indicate that this device is offline for monitoring + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqDeviceV1OfflineException extends LGThinqApiException { + @Serial + private static final long serialVersionUID = 202409261450L; + + public LGThinqDeviceV1OfflineException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqDeviceV1OfflineException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqException.java new file mode 100644 index 0000000000000..5f5a8fdc0ba0b --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqException.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqException} Parent Exception for all exceptions of this module + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqException extends Exception { + @Serial + private static final long serialVersionUID = 202409261450L; + + public LGThinqException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqGatewayException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqGatewayException.java new file mode 100644 index 0000000000000..803e091317e50 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqGatewayException.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqGatewayException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqGatewayException extends LGThinqException { + @Serial + private static final long serialVersionUID = 202409261450L; + + public LGThinqGatewayException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqUnmarshallException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqUnmarshallException.java new file mode 100644 index 0000000000000..7fc08f2133f7d --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqUnmarshallException.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link LGThinqUnmarshallException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class LGThinqUnmarshallException extends LGThinqException { + @Serial + private static final long serialVersionUID = 202409261450L; + + public LGThinqUnmarshallException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqUnmarshallException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/PreLoginException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/PreLoginException.java new file mode 100644 index 0000000000000..cf50e978b1644 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/PreLoginException.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PreLoginException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class PreLoginException extends LGThinqException { + @Serial + private static final long serialVersionUID = 202409261450L; + + public PreLoginException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/RefreshTokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/RefreshTokenException.java new file mode 100644 index 0000000000000..0e7c57e38e169 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/RefreshTokenException.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PreLoginException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class RefreshTokenException extends LGThinqApiException { + @Serial + private static final long serialVersionUID = 202409261450L; + + public RefreshTokenException(String message, Throwable cause) { + super(message, cause); + } + + public RefreshTokenException(String message) { + super(message); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/TokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/TokenException.java new file mode 100644 index 0000000000000..f7739df401c60 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/TokenException.java @@ -0,0 +1,32 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.errors; + +import java.io.Serial; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link PreLoginException} + * + * @author Nemer Daud - Initial contribution + */ +@NonNullByDefault +public class TokenException extends LGThinqException { + @Serial + private static final long serialVersionUID = 202409261450L; + + public TokenException(String message, Throwable cause) { + super(message, cause); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java index a344efbbd7ba6..0dfcf58dae05e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapability.java @@ -15,7 +15,12 @@ import static org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition.NULL_DEFINITION; import java.lang.reflect.ParameterizedType; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -28,12 +33,25 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault +@SuppressWarnings("unchecked") public abstract class AbstractCapability implements CapabilityDefinition { + // Define if the device supports sending setup commands before monitoring + // This is to control result 400 for some devices that doesn't support or permit setup commands before monitoring + boolean isBeforeCommandSupporter = true; + + public boolean isBeforeCommandSupported() { + return isBeforeCommandSupporter; + } + + public void setBeforeCommandSupported(boolean beforeCommandSupporter) { + isBeforeCommandSupporter = beforeCommandSupporter; + } + // default result format protected Map> featureDefinitionMap = new HashMap<>(); protected String modelName = ""; - Class realClass; + final Class realClass; @Override public String getModelName() { @@ -46,7 +64,8 @@ public void setModelName(String modelName) { } protected AbstractCapability() { - this.realClass = (Class) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0]; + this.realClass = (Class) ((ParameterizedType) Objects.requireNonNull(getClass().getGenericSuperclass())) + .getActualTypeArguments()[0]; } protected DeviceTypes deviceType = DeviceTypes.UNKNOWN; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java index 939cfed374276..7e705b9f0c4a3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java @@ -12,12 +12,18 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -33,7 +39,7 @@ */ @NonNullByDefault public abstract class AbstractCapabilityFactory { - protected ObjectMapper mapper = new ObjectMapper(); + protected final ObjectMapper mapper = new ObjectMapper(); private static final Logger logger = LoggerFactory.getLogger(AbstractCapabilityFactory.class); public T create(JsonNode rootNode) throws LGThinqException { @@ -41,7 +47,8 @@ public T create(JsonNode rootNode) throws LGThinqException { cap.setModelName(rootNode.path("Info").path("modelName").textValue()); cap.setDeviceType(ModelUtils.getDeviceType(rootNode)); cap.setDeviceVersion(ModelUtils.discoveryAPIVersion(rootNode)); - cap.setRawData(mapper.convertValue(rootNode, Map.class)); + cap.setRawData(mapper.convertValue(rootNode, new TypeReference<>() { + })); switch (cap.getDeviceVersion()) { case V1_0: // V1 has Monitoring node describing the protocol data format @@ -121,8 +128,7 @@ protected void validateMandatoryNote(JsonNode node) throws LGThinqException { } } - protected abstract Map getCommandsDefinition(JsonNode rootNode) - throws LGThinqApiException; + protected abstract Map getCommandsDefinition(JsonNode rootNode); /** * General method to parse commands for average of V1 Thinq Devices. @@ -136,7 +142,7 @@ protected Map getCommandsDefinitionV1(JsonNode rootNo JsonNode commandNode = rootNode.path("ControlWifi").path("action"); if (commandNode.isMissingNode()) { logger.warn("No commands found in the devices's definition. This is most likely a bug."); - return Collections.EMPTY_MAP; + return Collections.emptyMap(); } Map commands = new HashMap<>(); for (Iterator> it = commandNode.fields(); it.hasNext();) { @@ -150,7 +156,7 @@ protected Map getCommandsDefinitionV1(JsonNode rootNo continue; } cd.setCommand(cmdField.textValue()); - cd.setCmdOpt(thisCommandNode.path("cmdOpt").textValue()); + // cd.setCmdOpt(thisCommandNode.path("cmdOpt").textValue()); cd.setCmdOptValue(thisCommandNode.path("value").textValue()); cd.setBinary(isBinaryCommands); String strData = Objects.requireNonNullElse(thisCommandNode.path("data").textValue(), ""); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java index bc736bfa265ce..b41470f5d9f5b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractSnapshotDefinition.java @@ -29,7 +29,7 @@ @NonNullByDefault public abstract class AbstractSnapshotDefinition implements SnapshotDefinition { - protected Map otherInfo = new HashMap<>(); + protected final Map otherInfo = new HashMap<>(); @JsonAnySetter public void addOtherInfo(String propertyKey, Object value) { @@ -51,4 +51,24 @@ public Map getRawData() { public void setRawData(Map rawData) { this.rawData = rawData; } + + public static final AbstractSnapshotDefinition EMPTY_SHOT = new AbstractSnapshotDefinition() { + @Override + public DevicePowerState getPowerStatus() { + return DevicePowerState.DV_POWER_UNK; + } + + @Override + public void setPowerStatus(DevicePowerState value) { + } + + @Override + public boolean isOnline() { + return false; + } + + @Override + public void setOnline(boolean online) { + } + }; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java index 7c53ec768bc59..03888b71990b0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityDefinition.java @@ -24,6 +24,10 @@ */ @NonNullByDefault public interface CapabilityDefinition { + boolean isBeforeCommandSupported(); + + void setBeforeCommandSupported(boolean supports); + String getModelName(); void setModelName(String modelName); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java index 9c641ebb19541..43e21abb74e24 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactory.java @@ -18,7 +18,7 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapabilityFactoryV1; import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapabilityFactoryV2; import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherCapabilityFactoryV2; @@ -30,7 +30,6 @@ import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link CapabilityFactory} @@ -39,7 +38,7 @@ */ @NonNullByDefault public class CapabilityFactory { - Map>> capabilityDeviceFactories = new HashMap<>(); + final Map>> capabilityDeviceFactories = new HashMap<>(); private CapabilityFactory() { List> factories = Arrays.asList(new ACCapabilityFactoryV1(), @@ -55,7 +54,6 @@ private CapabilityFactory() { for (LGAPIVerion v : f.getSupportedAPIVersions()) { versionMap.put(v, f); } - ; capabilityDeviceFactories.put(d, versionMap); }); }); @@ -66,7 +64,6 @@ private CapabilityFactory() { instance = new CapabilityFactory(); } private static final Logger logger = LoggerFactory.getLogger(CapabilityFactory.class); - private static final ObjectMapper mapper = new ObjectMapper(); public static CapabilityFactory getInstance() { return instance; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java index a9f7b5f8c58e8..680e93f7c0373 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/CommandDefinition.java @@ -16,8 +16,6 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * The {@link CommandDefinition} @@ -26,22 +24,19 @@ */ @NonNullByDefault public class CommandDefinition { - private static final Logger logger = LoggerFactory.getLogger(CommandDefinition.class); /** * This is the command tag value that is used by the API to launch the command service */ - private String dataKey = ""; private String command = ""; private Map data = new HashMap<>(); // =========== Used only for thinq V1 commands ============= - private String cmdOpt = ""; private String cmdOptValue = ""; private boolean isBinary; // This is the template in the device definition of data that must be send to the LG API complementing the command private String dataTemplate = ""; /* - * holds the how command (in text) as defined in the node command definition. Ex: For Remote Start (WM): + * holds how command (in text) as defined in the node command definition. Ex: For Remote Start (WM): * { * "cmd":"Control", * "cmdOpt":"Operation", @@ -79,14 +74,6 @@ public void setData(Map data) { this.data = data; } - public String getCmdOpt() { - return cmdOpt; - } - - public void setCmdOpt(String cmdOpt) { - this.cmdOpt = cmdOpt; - } - public String getCmdOptValue() { return cmdOptValue; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java index cb0cefaa1ca95..b8e3290ae8054 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DefaultSnapshotBuilder.java @@ -18,12 +18,16 @@ import java.beans.PropertyDescriptor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.*; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.function.Function; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqUnmarshallException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,11 +58,12 @@ public DefaultSnapshotBuilder(Class clazz) { * Create a Snapshot result based on snapshotData collected from LG API (V1/C2) * * @param binaryData V1: decoded returnedData - * @param capDef + * @param capDef Capability Definition * @return returns Snapshot implementation based on device type provided * @throws LGThinqApiException any error. */ @Override + @SuppressWarnings("null") public S createFromBinary(String binaryData, List prot, CapabilityDefinition capDef) throws LGThinqUnmarshallException, LGThinqApiException { try { @@ -76,6 +81,7 @@ public S createFromBinary(String binaryData, List prot aliasesMethod.putIfAbsent(value, property); } if (m.isAnnotationPresent(JsonAlias.class)) { + @SuppressWarnings("null") String[] values = m.getAnnotation(JsonAlias.class).value(); for (String v : values) { aliasesMethod.putIfAbsent(v, property); @@ -144,8 +150,8 @@ public S createFromJson(String snapshotDataJson, DeviceTypes deviceType, Capabil @Override public S createFromJson(Map deviceSettings, CapabilityDefinition capDef) throws LGThinqApiException { - DeviceTypes type = getDeviceType(deviceSettings); - Map snapMap = ((Map) deviceSettings.get("snapshot")); + Map snapMap = objectMapper.convertValue(deviceSettings.get("snapshot"), new TypeReference<>() { + }); if (snapMap == null) { throw new LGThinqApiException("snapshot node not present in device monitoring result."); } @@ -163,12 +169,12 @@ protected DeviceTypes getDeviceType(Map rootMap) { } /** - * Used + * Create the map containing the bit representation of device features * - * @param key - * @param capFeatureValues - * @param cachedBitKey - * @return + * @param key raw value + * @param capFeatureValues capability features defined to the device + * @param cachedBitKey chached bitKey representation if any was done previously + * @return the bitKey - map os key features, position and options available */ private Map getBitKey(String key, final Map> capFeatureValues, final Map> cachedBitKey) { @@ -186,7 +192,9 @@ private Map getBitKey(String key, final Map> optionList = (List>) option.get("option"); + List> optionList = objectMapper.convertValue(option.get("option"), + new TypeReference<>() { + }); if (optionList == null) { continue; @@ -264,8 +272,8 @@ protected String bitValue(String key, Map snapRawValues, final C } int bitValue = Integer.parseInt(value); - int startBit = (int) bitKey.get("startbit"); - int length = (int) bitKey.get("length"); + int startBit = (int) Objects.requireNonNull(bitKey.get("startbit"), "Not expected null here"); + int length = (int) bitKey.getOrDefault("length", 0); int val = 0; for (int i = 0; i < length; i++) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java index a7780da8ede28..ae184d692e2d8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DevicePowerState.java @@ -13,6 +13,7 @@ package org.openhab.binding.lgthinq.lgservices.model; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; /** * The {@link DevicePowerState} @@ -35,18 +36,12 @@ public double getValue() { powerState = i; } - public static DevicePowerState statusOf(double value) { - switch ((int) value) { - case 0: - return DV_POWER_OFF; - case 1: - case 256: - case 257: - return DV_POWER_ON; - - default: - return DV_POWER_UNK; - } + public static DevicePowerState statusOf(@Nullable Integer value) { + return switch (value == null ? -1 : value) { + case 0 -> DV_POWER_OFF; + case 1, 256, 257 -> DV_POWER_ON; + default -> DV_POWER_UNK; + }; } public static double valueOf(DevicePowerState dps) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java index 9636393b55ea6..956034987de67 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.lgthinq.lgservices.model; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link DeviceTypes} * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public enum DeviceTypes { AIR_CONDITIONER(401, "AC", "", "air-conditioner-401"), HEAT_PUMP(401, "AC", "AWHP", "heatpump-401HP"), @@ -74,24 +77,23 @@ public static DeviceTypes fromDeviceTypeId(int deviceTypeId, String deviceCode) } public static DeviceTypes fromDeviceTypeAcron(String deviceTypeAcron, String modelType) { - switch (deviceTypeAcron) { - case "AC": + return switch (deviceTypeAcron) { + case "AC" -> { if ("AWHP".equals(modelType)) { - return HEAT_PUMP; + yield HEAT_PUMP; } - return AIR_CONDITIONER; - case "WM": + yield AIR_CONDITIONER; + } + case "WM" -> { if ("Dryer".equals(modelType)) { - return DRYER; + yield DRYER; } - return WASHERDRYER_MACHINE; - case "REF": - return REFRIGERATOR; - case "DW": - return DISH_WASHER; - default: - return UNKNOWN; - } + yield WASHERDRYER_MACHINE; + } + case "REF" -> REFRIGERATOR; + case "DW" -> DISH_WASHER; + default -> UNKNOWN; + }; } DeviceTypes(int i, String n, String submodel, String thingTypeId) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java index f70bd51650f89..fb75a74724ee1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/FeatureDataType.java @@ -12,12 +12,15 @@ */ package org.openhab.binding.lgthinq.lgservices.model; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link FeatureDataType} * Feature is the values the device has to expose its sensor attributes * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public enum FeatureDataType { ENUM, RANGE, diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java index 579f4f797e71b..15af8042c223f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/LGAPIVerion.java @@ -12,11 +12,14 @@ */ package org.openhab.binding.lgthinq.lgservices.model; +import org.eclipse.jdt.annotation.NonNullByDefault; + /** * The {@link LGAPIVerion} * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public enum LGAPIVerion { V1_0(1.0), V2_0(2.0), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java index 6518a0d1c47ea..f14d2c8cefc9d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java @@ -17,6 +17,8 @@ import java.util.Map; import java.util.Objects; +import org.eclipse.jdt.annotation.NonNullByDefault; + import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -26,14 +28,16 @@ * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public class ModelUtils { public static final ObjectMapper objectMapper = new ObjectMapper(); public static DeviceTypes getDeviceType(Map rootMap) { - Map infoMap = (Map) rootMap.get("Info"); + Map infoMap = objectMapper.convertValue(rootMap.get("Info"), new TypeReference<>() { + }); Objects.requireNonNull(infoMap, "Unexpected error. Info node not present in capability schema"); - String productType = infoMap.get("productType"); - String modelType = infoMap.get("modelType"); + String productType = infoMap.getOrDefault("productType", ""); + String modelType = infoMap.getOrDefault("modelType", ""); Objects.requireNonNull(infoMap, "Unexpected error. ProductType attribute not present in capability schema"); return fromDeviceTypeAcron(productType, modelType); } @@ -55,7 +59,8 @@ public static LGAPIVerion discoveryAPIVersion(Map rootMap) { switch (type) { case AIR_CONDITIONER: case HEAT_PUMP: - Map valueNode = (Map) rootMap.get("Value"); + Map valueNode = objectMapper.convertValue(rootMap.get("Value"), new TypeReference<>() { + }); if (valueNode.containsKey("support.airState.opMode")) { return LGAPIVerion.V2_0; } else if (valueNode.containsKey("SupportOpMode")) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java index eac43372837f1..69bde8877e6f6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/MonitoringResultFormat.java @@ -12,7 +12,6 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.NonNullByDefault; /** @@ -36,15 +35,11 @@ public String getFormat() { return format; } - public static MonitoringResultFormat getFormatOf(@NonNull String formatValue) { - switch (formatValue.toUpperCase()) { - case "BINARY(BYTE)": - return BINARY_FORMAT; - case "JSON": - return JSON_FORMAT; - default: - return UNKNOWN_FORMAT; - - } + public static MonitoringResultFormat getFormatOf(String formatValue) { + return switch (formatValue.toUpperCase()) { + case "BINARY(BYTE)" -> BINARY_FORMAT; + case "JSON" -> JSON_FORMAT; + default -> UNKNOWN_FORMAT; + }; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java index 919ebdf2c2d0c..e1a2f3265b28a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java @@ -16,6 +16,8 @@ import java.util.List; import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; @@ -26,7 +28,7 @@ * * @author Nemer Daud - Initial contribution */ - +@NonNullByDefault public enum ResultCodes { DEVICE_OFFLINE("Device Offline", "0106"), OK("Success", "0000", "0001"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java index fc47f08266c30..de395d16bcc12 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilder.java @@ -16,8 +16,8 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqUnmarshallException; /** * The {@link SnapshotBuilder} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java index cae199e527141..201f348c22392 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/SnapshotBuilderFactory.java @@ -40,7 +40,7 @@ public class SnapshotBuilderFactory { } private SnapshotBuilderFactory() { - }; + } public static SnapshotBuilderFactory getInstance() { return instance; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java index cfcd1bcf5adb3..8d45feeeb20ef 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCanonicalSnapshot.java @@ -63,7 +63,7 @@ public class ACCanonicalSnapshot extends AbstractSnapshotDefinition { @JsonIgnore public DevicePowerState getPowerStatus() { - return operation == null ? DevicePowerState.DV_POWER_UNK : DevicePowerState.statusOf(operation); + return DevicePowerState.statusOf(operation); } @JsonIgnore diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java index f0bfde402905d..d689c23b146af 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV1.java @@ -18,12 +18,9 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; @@ -34,7 +31,6 @@ */ @NonNullByDefault public class ACCapabilityFactoryV1 extends AbstractACCapabilityFactory { - private static final Logger logger = LoggerFactory.getLogger(ACCapabilityFactoryV1.class); @Override protected List getSupportedAPIVersions() { @@ -42,7 +38,7 @@ protected List getSupportedAPIVersions() { } @Override - protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + protected Map getCommandsDefinition(JsonNode rootNode) { return Collections.emptyMap(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java index b5794be455c9c..4c64ab9580f0d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACCapabilityFactoryV2.java @@ -17,12 +17,9 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; @@ -33,7 +30,6 @@ */ @NonNullByDefault public class ACCapabilityFactoryV2 extends AbstractACCapabilityFactory { - private static final Logger logger = LoggerFactory.getLogger(ACCapabilityFactoryV2.class); @Override protected List getSupportedAPIVersions() { @@ -41,7 +37,7 @@ protected List getSupportedAPIVersions() { } @Override - protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + protected Map getCommandsDefinition(JsonNode rootNode) { Map result = new HashMap<>(); JsonNode controlDeviceNode = rootNode.path("ControlDevice"); if (controlDeviceNode.isArray()) { @@ -50,15 +46,12 @@ protected Map getCommandsDefinition(JsonNode rootNode // commands variations are described separated by pipe "|" String[] commands = c.path("command").asText().split("\\|"); String dataValues = c.path("dataValue").asText(); - int i = 0; - for (String cOpt : commands) { + for (String cmd : commands) { CommandDefinition cd = new CommandDefinition(); - cd.setCommand(ctrlKey); - cd.setCmdOpt(cOpt); + cd.setCommand(cmd); cd.setCmdOptValue(dataValues.replaceAll("[{%}]", "")); cd.setRawCommand(c.toPrettyString()); result.put(ctrlKey, cd); - i++; } }); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java index baac299973d12..7a0188cc92962 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACFanSpeed.java @@ -29,29 +29,19 @@ public enum ACFanSpeed { F_AUTO(8.0), F_UNK(-1); - private final double funStrength; - ACFanSpeed(double v) { - this.funStrength = v; } public static ACFanSpeed statusOf(double value) { - switch ((int) value) { - case 2: - return F1; - case 3: - return F2; - case 4: - return F3; - case 5: - return F4; - case 6: - return F5; - case 8: - return F_AUTO; - default: - return F_UNK; - } + return switch ((int) value) { + case 2 -> F1; + case 3 -> F2; + case 4 -> F3; + case 5 -> F4; + case 6 -> F5; + case 8 -> F_AUTO; + default -> F_UNK; + }; } /** diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java index 1cd632837253c..4d356153ace4e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACOpMode.java @@ -37,7 +37,7 @@ public enum ACOpMode { } public static ACOpMode statusOf(int value) { - switch ((int) value) { + switch (value) { case 0: return COOL; case 1: diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java index 7a9a4889e16e0..c5d203a42a466 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ACSnapshotBuilder.java @@ -16,9 +16,11 @@ import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DefaultSnapshotBuilder; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringBinaryProtocol; /** * The {@link ACSnapshotBuilder} @@ -46,8 +48,9 @@ protected ACCanonicalSnapshot getSnapshot(Map snapMap, Capabilit snap = objectMapper.convertValue(snapMap, snapClass); snap.setRawData(snapMap); return snap; + default: + throw new IllegalStateException("Snapshot for device type " + capDef.getDeviceType() + + " not supported for this builder. It most likely a bug"); } - throw new IllegalStateException("Snapshot for device type " + capDef.getDeviceType() - + " not supported for this builder. It most likely a bug"); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java index fef8b31709a8f..3b14768bd240a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/AbstractACCapabilityFactory.java @@ -12,16 +12,34 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.ac; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_AIRCLEAN; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_AIR_CLEAN_COMMAND_OFF; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_AIR_CLEAN_COMMAND_ON; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_AUTODRY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_COMMAND_OFF; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_COMMAND_ON; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_COOL_JET; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_ENERGYSAVING; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_SUB_MODE_COOL_JET; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_SUB_MODE_STEP_LEFT_RIGHT; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_AC_SUB_MODE_STEP_UP_DOWN; import static org.openhab.binding.lgthinq.lgservices.model.DeviceTypes.HEAT_PUMP; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -78,7 +96,7 @@ private List extractValueOptions(JsonNode optionsNode) throws LGThinqApi private Map extractOptions(JsonNode optionsNode, boolean invertKeyValue) { if (optionsNode.isMissingNode()) { logger.warn("Error extracting options supported by the device"); - return Collections.EMPTY_MAP; + return Collections.emptyMap(); } else { Map modes = new HashMap(); optionsNode.fields().forEachRemaining(e -> { @@ -134,18 +152,17 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { acCap.setFanSpeed(fanSpeeds); // ===== get supported extra modes - boolean isSupportDryMode = false, isSupportEnergyMode = false; JsonNode supRacSubModeOps = valuesNode.path(getSupSubRacModeNodeName()).path(getOptionsMapNodeName()); if (!supRacSubModeOps.isMissingNode()) { supRacSubModeOps.fields().forEachRemaining(f -> { - if (AC_SUB_MODE_COOL_JET.equals(f.getValue().asText())) { + if (CAP_AC_SUB_MODE_COOL_JET.equals(f.getValue().asText())) { acCap.setJetModeAvailable(true); } - if (AC_SUB_MODE_STEP_UP_DOWN.equals(f.getValue().asText())) { + if (CAP_AC_SUB_MODE_STEP_UP_DOWN.equals(f.getValue().asText())) { acCap.setStepUpDownAvailable(true); } - if (AC_SUB_MODE_STEP_LEFT_RIGHT.equals(f.getValue().asText())) { + if (CAP_AC_SUB_MODE_STEP_LEFT_RIGHT.equals(f.getValue().asText())) { acCap.setStepLeftRightAvailable(true); } }); @@ -235,14 +252,11 @@ public ACCapability create(JsonNode rootNode) throws LGThinqException { JsonNode supHpAirSwitchNode = valuesNode.path(getHpAirWaterSwitchNodeName()).path(getOptionsMapNodeName()); if (!supHpAirSwitchNode.isMissingNode()) { supHpAirSwitchNode.fields().forEachRemaining(r -> { - String racOpValue = r.getValue().asText(); + r.getValue().asText(); }); } } - if (!supRACModeOps.isMissingNode()) { - - } JsonNode infoNode = rootNode.get("Info"); if (infoNode.isMissingNode()) { logger.warn("No info session defined in the cap data."); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java index c7c99b1974f90..665a280e75d6d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/ac/ExtendedDeviceInfo.java @@ -12,9 +12,10 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.ac; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_EXTRA_ATTR_FILTER_MAX_TIME_TO_USE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_EXTRA_ATTR_FILTER_USED_TIME; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_EXTRA_ATTR_INSTANT_POWER; -import org.apache.commons.lang3.math.NumberUtils; import org.eclipse.jdt.annotation.NonNullByDefault; import com.fasterxml.jackson.annotation.JsonAlias; @@ -32,7 +33,7 @@ public class ExtendedDeviceInfo { private String instantPower = ""; - @JsonProperty(EXTENDED_ATTR_FILTER_USED_TIME) + @JsonProperty(CAP_EXTRA_ATTR_FILTER_USED_TIME) @JsonAlias("airState.filterMngStates.useTime") public String getFilterHoursUsed() { return filterHoursUsed; @@ -42,7 +43,7 @@ public void setFilterHoursUsed(String filterHoursUsed) { this.filterHoursUsed = filterHoursUsed; } - @JsonProperty(EXTENDED_ATTR_FILTER_MAX_TIME_TO_USE) + @JsonProperty(CAP_EXTRA_ATTR_FILTER_MAX_TIME_TO_USE) @JsonAlias("airState.filterMngStates.maxTime") public String getFilterHoursMax() { return filterHoursMax; @@ -60,14 +61,18 @@ public void setFilterHoursMax(String filterHoursMax) { * * @return the instant total power consumption */ - @JsonProperty(EXTENDED_ATTR_INSTANT_POWER) + @JsonProperty(CAP_EXTRA_ATTR_INSTANT_POWER) @JsonAlias("airState.energy.totalCurrent") public String getRawInstantPower() { return instantPower; } public Double getInstantPower() { - return NumberUtils.isCreatable(instantPower) ? Double.parseDouble(instantPower) : 0.0; + try { + return Double.parseDouble(instantPower); + } catch (NumberFormatException e) { + return 0.0; + } } public void setRawInstantPower(String instantPower) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java index c0a9b4a8a6d72..b5275ec6d6646 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/Utils.java @@ -16,6 +16,8 @@ import java.util.List; import java.util.Map; +import org.eclipse.jdt.annotation.NonNullByDefault; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ArrayNode; @@ -24,6 +26,7 @@ * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault public class Utils { public static Map getGenericCourseDefinitions(JsonNode courseNode, CourseType type, @@ -53,7 +56,7 @@ public static Map getGenericCourseDefinitions(JsonNode if (f.isSelectable()) { List selectableValues = f.getSelectableValues(); // map values acceptable for this function - for (JsonNode v : (ArrayNode) selectableNode) { + for (JsonNode v : selectableNode) { if (v.isValueNode()) { selectableValues.add(v.textValue()); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/WasherFeatureDefinition.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/WasherFeatureDefinition.java new file mode 100644 index 0000000000000..10efd40899558 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/WasherFeatureDefinition.java @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2010-2024 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.lgthinq.lgservices.model.devices.commons.washers; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * The WasherFeatureDefinition + * + * @author nemer (nemer.daud@gmail.com) - Initial contribution + */ +@NonNullByDefault +public class WasherFeatureDefinition { + public static FeatureDefinition getBasicFeatureDefinition(String featureName, JsonNode featureNode, + @Nullable String targetChannelId, @Nullable String refChannelId) { + if (featureNode.isMissingNode()) { + return FeatureDefinition.NULL_DEFINITION; + } + FeatureDefinition fd = new FeatureDefinition(); + fd.setName(featureName); + fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); + fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); + fd.setLabel(featureName); + return fd; + } + + public static FeatureDefinition setAllValuesMapping(FeatureDefinition fd, JsonNode featureNode) { + fd.setDataType(FeatureDataType.ENUM); + JsonNode valuesMappingNode = featureNode.path("option"); + if (!valuesMappingNode.isMissingNode()) { + + Map valuesMapping = new HashMap<>(); + valuesMappingNode.fields().forEachRemaining(e -> { + // collect values as: + // + // "option":{ + // "0":"@WM_STATE_POWER_OFF_W", + // to "0" -> "@WM_STATE_POWER_OFF_W" + valuesMapping.put(e.getKey(), e.getValue().asText()); + }); + fd.setValuesMapping(valuesMapping); + } + + return fd; + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java index 2b86fdf3c9762..01c06a0077f54 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/AbstractDishWasherCapabilityFactory.java @@ -18,15 +18,13 @@ import java.util.function.BiConsumer; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseType; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; @@ -37,7 +35,6 @@ */ @NonNullByDefault public abstract class AbstractDishWasherCapabilityFactory extends AbstractCapabilityFactory { - private static final Logger logger = LoggerFactory.getLogger(AbstractDishWasherCapabilityFactory.class); protected abstract String getStateFeatureNodeName(); @@ -49,6 +46,7 @@ public abstract class AbstractDishWasherCapabilityFactory extends AbstractCapabi protected abstract String getControlConvertingRulesNodeName(); + @SuppressWarnings("unused") protected abstract MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode); @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java index 234789f1de672..59ae3531a41b4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV2.java @@ -12,13 +12,17 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import java.util.*; +import java.util.Collections; +import java.util.List; +import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.lgservices.model.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.WasherFeatureDefinition; import com.fasterxml.jackson.databind.JsonNode; @@ -29,7 +33,6 @@ */ @NonNullByDefault public class DishWasherCapabilityFactoryV2 extends AbstractDishWasherCapabilityFactory { - private static final Logger logger = LoggerFactory.getLogger(DishWasherCapabilityFactoryV2.class); @Override protected List getSupportedAPIVersions() { @@ -54,44 +57,14 @@ protected String getControlConvertingRulesNodeName() { protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, @Nullable String targetChannelId, @Nullable String refChannelId) { JsonNode featureNode = featuresNode.path(featureName); - if (featureNode.isMissingNode()) { - return FeatureDefinition.NULL_DEFINITION; - } - FeatureDefinition fd = new FeatureDefinition(); - fd.setName(featureName); - fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); - fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); - fd.setLabel(featureName); - // all features from V2 are enums - fd.setDataType(FeatureDataType.ENUM); - // surprisingly the DW V2 has the same json struct as V1 of other devices. - JsonNode optionsNode = featureNode.path("option"); - if (!optionsNode.isMissingNode()) { - - Map options = new HashMap<>(); - optionsNode.fields().forEachRemaining(e -> { - // collect values as: - // - // "State": { - // "type": "Enum", - // "default": "POWEROFF", - // "option": { - // "POWEROFF": "@DW_STATE_POWER_OFF_W", - // "INITIAL": "@DW_STATE_INITIAL_W", - // "RUNNING": "@DW_STATE_RUNNING_W", - // "PAUSE": "@DW_STATE_PAUSE_W", - // "STANDBY": "@DW_STATE_POWER_OFF_W", - // "END": "@DW_STATE_COMPLETE_W", - // "POWERFAIL": "@DW_STATE_POWER_FAIL_W" - // } - // }, - options.put(e.getKey(), e.getValue().asText()); - }); - fd.setValuesMapping(options); + FeatureDefinition fd; + if ((fd = WasherFeatureDefinition.getBasicFeatureDefinition(featureName, featureNode, targetChannelId, + refChannelId)) == FeatureDefinition.NULL_DEFINITION) { + return fd; } - - return fd; + // all features from V2 are enums + return WasherFeatureDefinition.setAllValuesMapping(fd, featureNode); } @Override @@ -109,10 +82,6 @@ protected String getSmartCourseNodeName() { return "SmartCourse"; } - private String getConfigNodeName() { - return "Config"; - } - @Override protected String getStateFeatureNodeName() { return "State"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java index 3ec618a937165..38ad6da589eb4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshot.java @@ -12,8 +12,8 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.DW_POWER_OFF_VALUE; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.DW_STATE_COMPLETE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.DW_POWER_OFF_VALUE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.DW_STATE_COMPLETE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java index ba44dfa8e6fc8..85569123f1e79 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherSnapshotBuilder.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.DW_SNAPSHOT_WASHER_DRYER_NODE_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.DW_SNAPSHOT_WASHER_DRYER_NODE_V2; import java.util.Map; import java.util.Objects; @@ -23,6 +23,8 @@ import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import com.fasterxml.jackson.core.type.TypeReference; + /** * The {@link DishWasherSnapshotBuilder} * @@ -48,8 +50,8 @@ protected DishWasherSnapshot getSnapshot(Map snapMap, Capability throw new IllegalArgumentException("Version 1.0 for DishWasher is not supported yet."); case V2_0: Map dishWasher = Objects.requireNonNull( - (Map) snapMap.get(DW_SNAPSHOT_WASHER_DRYER_NODE_V2), - "dishwasher node must be present in the snapshot"); + objectMapper.convertValue(snapMap.get(DW_SNAPSHOT_WASHER_DRYER_NODE_V2), new TypeReference<>() { + }), "dishwasher node must be present in the snapshot"); snap = objectMapper.convertValue(dishWasher, snapClass); snap.setRawData(dishWasher); return snap; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java index 2c83c27af5990..527c3b012402d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java @@ -12,15 +12,15 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_CELSIUS_SYMBOL; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.TEMP_UNIT_FAHRENHEIT_SYMBOL; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_TEMP_UNIT_CELSIUS_SYMBOL; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_TEMP_UNIT_FAHRENHEIT_SYMBOL; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.slf4j.Logger; @@ -97,17 +97,17 @@ public FridgeCapability create(JsonNode rootNode) throws LGThinqException { if (!node.path(getMonitorValueNodeName()).path(getEcoFriendlyNodeName()).isMissingNode()) { frCap.setEcoFriendlyModePresent(true); } - loadTempNode(fridgeTempCNode, frCap.getFridgeTempCMap(), TEMP_UNIT_CELSIUS_SYMBOL); + loadTempNode(fridgeTempCNode, frCap.getFridgeTempCMap(), RE_TEMP_UNIT_CELSIUS_SYMBOL); if (fridgeTempFNode.isMissingNode()) { frCap.getFridgeTempFMap().putAll(convertCelsius2Fahrenheit(frCap.getFridgeTempCMap())); } else { - loadTempNode(fridgeTempFNode, frCap.getFridgeTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + loadTempNode(fridgeTempFNode, frCap.getFridgeTempFMap(), RE_TEMP_UNIT_FAHRENHEIT_SYMBOL); } - loadTempNode(freezerTempCNode, frCap.getFreezerTempCMap(), TEMP_UNIT_CELSIUS_SYMBOL); + loadTempNode(freezerTempCNode, frCap.getFreezerTempCMap(), RE_TEMP_UNIT_CELSIUS_SYMBOL); if (freezerTempFNode.isMissingNode()) { frCap.getFreezerTempFMap().putAll(convertCelsius2Fahrenheit(frCap.getFreezerTempCMap())); } else { - loadTempNode(freezerTempFNode, frCap.getFreezerTempFMap(), TEMP_UNIT_FAHRENHEIT_SYMBOL); + loadTempNode(freezerTempFNode, frCap.getFreezerTempFMap(), RE_TEMP_UNIT_FAHRENHEIT_SYMBOL); } loadTempUnitNode(tempUnitNode, frCap.getTempUnitMap()); loadIcePlus(icePlusNode, frCap.getIcePlusMap()); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java index 639643aa31ca7..1c8263930b8be 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalCapability.java @@ -18,10 +18,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapability; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; /** * The {@link FridgeCanonicalCapability} @@ -32,23 +28,19 @@ public class FridgeCanonicalCapability extends AbstractCapability implements FridgeCapability { - private static final Logger logger = LoggerFactory.getLogger(FridgeCanonicalCapability.class); - private static final ObjectMapper mapper = new ObjectMapper(); - private final Map fridgeTempCMap = new LinkedHashMap(); private final Map fridgeTempFMap = new LinkedHashMap(); private final Map freezerTempCMap = new LinkedHashMap(); private final Map freezerTempFMap = new LinkedHashMap(); private final Map tempUnitMap = new LinkedHashMap(); - private final Map icePlusMap = new LinkedHashMap();; - private final Map freshAirFilterMap = new LinkedHashMap();; - private final Map waterFilterMap = new LinkedHashMap();; - private final Map expressFreezeModeMap = new LinkedHashMap();; - private final Map smartSavingMap = new LinkedHashMap();; - private final Map activeSavingMap = new LinkedHashMap();; + private final Map icePlusMap = new LinkedHashMap(); + private final Map freshAirFilterMap = new LinkedHashMap(); + private final Map waterFilterMap = new LinkedHashMap(); + private final Map expressFreezeModeMap = new LinkedHashMap(); + private final Map smartSavingMap = new LinkedHashMap(); + private final Map activeSavingMap = new LinkedHashMap(); private final Map atLeastOneDoorOpenMap = new LinkedHashMap<>(); private final Map commandsDefinition = new LinkedHashMap<>(); - private final Map expressCoolModeMap = new LinkedHashMap<>();; private boolean isExpressCoolModePresent = false; private boolean isEcoFriendlyModePresent = false; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java index be31b4e70e6a0..8d20b3f6221b7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCanonicalSnapshot.java @@ -12,7 +12,12 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.FREEZER_TEMPERATURE_IGNORE_VALUE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.FRIDGE_TEMPERATURE_IGNORE_VALUE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_TEMP_UNIT_CELSIUS; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_TEMP_UNIT_CELSIUS_SYMBOL; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_TEMP_UNIT_FAHRENHEIT; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_TEMP_UNIT_FAHRENHEIT_SYMBOL; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; @@ -38,7 +43,7 @@ public class FridgeCanonicalSnapshot extends AbstractFridgeSnapshot { private boolean online; private Double fridgeTemp = FRIDGE_TEMPERATURE_IGNORE_VALUE; private Double freezerTemp = FREEZER_TEMPERATURE_IGNORE_VALUE; - private String tempUnit = TEMP_UNIT_CELSIUS; // celsius as default + private String tempUnit = RE_TEMP_UNIT_CELSIUS; // celsius as default private String doorStatus = ""; private String waterFilterUsedMonth = ""; @@ -84,8 +89,8 @@ public String getTempUnit() { } private String getStrTempWithUnit(Double temp) { - return temp.intValue() + (TEMP_UNIT_CELSIUS.equals(tempUnit) ? " " + TEMP_UNIT_CELSIUS_SYMBOL - : (TEMP_UNIT_FAHRENHEIT).equals(tempUnit) ? " " + TEMP_UNIT_FAHRENHEIT_SYMBOL : ""); + return temp.intValue() + (RE_TEMP_UNIT_CELSIUS.equals(tempUnit) ? " " + RE_TEMP_UNIT_CELSIUS_SYMBOL + : (RE_TEMP_UNIT_FAHRENHEIT).equals(tempUnit) ? " " + RE_TEMP_UNIT_FAHRENHEIT_SYMBOL : ""); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index 087045461844b..09907db3796a5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -12,7 +12,10 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_FRESH_AIR_FILTER_MAP; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_ON_OFF; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_SMART_SAVING_MODE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_WATER_FILTER_USED_POSTFIX; import java.util.Collections; import java.util.List; @@ -20,12 +23,9 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; @@ -36,7 +36,6 @@ */ @NonNullByDefault public class FridgeCapabilityFactoryV1 extends AbstractFridgeCapabilityFactory { - private static final Logger logger = LoggerFactory.getLogger(FridgeCapabilityFactoryV1.class); @Override protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, @@ -46,7 +45,7 @@ protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode fe } @Override - protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + protected Map getCommandsDefinition(JsonNode rootNode) { return getCommandsDefinitionV1(rootNode); } @@ -71,12 +70,12 @@ protected void loadTempUnitNode(JsonNode tempUnitNode, Map tempU @Override protected void loadIcePlus(JsonNode icePlusNode, Map icePlusMap) { - loadGenericFeatNode(icePlusNode, icePlusMap, CAP_FR_ON_OFF); + loadGenericFeatNode(icePlusNode, icePlusMap, CAP_RE_ON_OFF); } @Override protected void loadFreshAirFilter(JsonNode freshAirFilterNode, Map freshAirFilterMap) { - loadGenericFeatNode(freshAirFilterNode, freshAirFilterMap, CAP_FR_FRESH_AIR_FILTER_MAP); + loadGenericFeatNode(freshAirFilterNode, freshAirFilterMap, CAP_RE_FRESH_AIR_FILTER_MAP); } @Override @@ -84,7 +83,7 @@ protected void loadWaterFilter(JsonNode waterFilterNode, Map wat int minValue = waterFilterNode.path("min").asInt(0); int maxValue = waterFilterNode.path("max").asInt(6); for (int i = minValue; i <= maxValue; i++) { - waterFilterMap.put(String.valueOf(i), i + CAP_FR_WATER_FILTER_USED_POSTFIX); + waterFilterMap.put(String.valueOf(i), i + CAP_RE_WATER_FILTER_USED_POSTFIX); } } @@ -95,7 +94,7 @@ protected void loadExpressFreezeMode(JsonNode expressFreezeModeNode, Map smartSavingModeMap) { - loadGenericFeatNode(smartSavingModeNode, smartSavingModeMap, CAP_FR_SMART_SAVING_MODE); + loadGenericFeatNode(smartSavingModeNode, smartSavingModeMap, CAP_RE_SMART_SAVING_MODE); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java index 8010dec93caa5..b0a19945d4256 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -12,7 +12,13 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_EXPRESS_FREEZE_MODES; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_FRESH_AIR_FILTER_MAP; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_LABEL_CLOSE_OPEN; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_LABEL_ON_OFF; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_SMART_SAVING_V2_MODE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_TEMP_UNIT_V2_MAP; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.CAP_RE_WATER_FILTER; import java.util.Collections; import java.util.List; @@ -20,7 +26,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; @@ -41,7 +46,7 @@ protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode fe } @Override - protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + protected Map getCommandsDefinition(JsonNode rootNode) { // doesn't meter command definition for V2 return Collections.emptyMap(); } @@ -75,7 +80,7 @@ protected void loadTempNode(JsonNode tempNode, Map capMap, Strin @Override protected void loadTempUnitNode(JsonNode tempUnitNode, Map tempUnitMap) { - tempUnitMap.putAll(CAP_FR_TEMP_UNIT_V2_MAP); + tempUnitMap.putAll(CAP_RE_TEMP_UNIT_V2_MAP); } @Override @@ -85,32 +90,32 @@ protected void loadIcePlus(JsonNode icePlusNode, Map icePlusMap) @Override protected void loadFreshAirFilter(JsonNode freshAirFilterNode, Map freshAirFilterMap) { - loadGenericFeatNode(freshAirFilterNode, freshAirFilterMap, CAP_FR_FRESH_AIR_FILTER_MAP); + loadGenericFeatNode(freshAirFilterNode, freshAirFilterMap, CAP_RE_FRESH_AIR_FILTER_MAP); } @Override protected void loadWaterFilter(JsonNode waterFilterNode, Map waterFilterMap) { - loadGenericFeatNode(waterFilterNode, waterFilterMap, CAP_FR_WATER_FILTER); + loadGenericFeatNode(waterFilterNode, waterFilterMap, CAP_RE_WATER_FILTER); } @Override protected void loadExpressFreezeMode(JsonNode expressFreezeModeNode, Map expressFreezeModeMap) { - loadGenericFeatNode(expressFreezeModeNode, expressFreezeModeMap, CAP_FR_EXPRESS_FREEZE_MODES); + loadGenericFeatNode(expressFreezeModeNode, expressFreezeModeMap, CAP_RE_EXPRESS_FREEZE_MODES); } @Override protected void loadSmartSavingMode(JsonNode smartSavingModeNode, Map smartSavingModeMap) { - loadGenericFeatNode(smartSavingModeNode, smartSavingModeMap, CAP_FR_SMART_SAVING_V2_MODE); + loadGenericFeatNode(smartSavingModeNode, smartSavingModeMap, CAP_RE_SMART_SAVING_V2_MODE); } @Override protected void loadActiveSaving(JsonNode activeSavingNode, Map activeSavingMap) { - loadGenericFeatNode(activeSavingNode, activeSavingMap, CAP_FR_LABEL_ON_OFF); + loadGenericFeatNode(activeSavingNode, activeSavingMap, CAP_RE_LABEL_ON_OFF); } @Override protected void loadAtLeastOneDoorOpen(JsonNode atLeastOneDoorOpenNode, Map atLeastOneDoorOpenMap) { - loadGenericFeatNode(atLeastOneDoorOpenNode, atLeastOneDoorOpenMap, CAP_FR_LABEL_CLOSE_OPEN); + loadGenericFeatNode(atLeastOneDoorOpenNode, atLeastOneDoorOpenMap, CAP_RE_LABEL_CLOSE_OPEN); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java index ae2ee08e2fc9d..132d4998b9fb6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.REFRIGERATOR_SNAPSHOT_NODE_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_SNAPSHOT_NODE_V2; import static org.openhab.binding.lgthinq.lgservices.model.DeviceTypes.REFRIGERATOR; import java.util.List; @@ -20,9 +20,13 @@ import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DefaultSnapshotBuilder; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringBinaryProtocol; + +import com.fasterxml.jackson.core.type.TypeReference; /** * The {@link FridgeSnapshotBuilder} @@ -46,17 +50,18 @@ protected FridgeCanonicalSnapshot getSnapshot(Map snapMap, Capab FridgeCanonicalSnapshot snap; if (REFRIGERATOR.equals(capDef.getDeviceType())) { switch (capDef.getDeviceVersion()) { - case V1_0: { - throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); - } + case V1_0: + throw new IllegalArgumentException("Version 1.0 for Fridge driver is not supported yet."); case V2_0: { Map refMap = Objects.requireNonNull( - (Map) snapMap.get(REFRIGERATOR_SNAPSHOT_NODE_V2), - "washerDryer node must be present in the snapshot"); + objectMapper.convertValue(snapMap.get(RE_SNAPSHOT_NODE_V2), new TypeReference<>() { + }), "washerDryer node must be present in the snapshot"); snap = objectMapper.convertValue(refMap, snapClass); snap.setRawData(snapMap); return snap; } + default: + throw new IllegalArgumentException("Version informed is not supported for the Fridge driver."); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java index dca883b165434..c1cd8063fbb95 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java @@ -12,14 +12,21 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_RINSE_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_SPIN_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_TEMP_LEVEL_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_RINSE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_SPIN; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_TEMP; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WM_LOST_WASHING_STATE_KEY; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WM_LOST_WASHING_STATE_VALUE; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.AbstractCapabilityFactory; import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; @@ -27,8 +34,6 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseDefinition; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.CourseType; import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.Utils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import com.fasterxml.jackson.databind.JsonNode; @@ -39,7 +44,6 @@ */ @NonNullByDefault public abstract class AbstractWasherDryerCapabilityFactory extends AbstractCapabilityFactory { - private static final Logger logger = LoggerFactory.getLogger(AbstractWasherDryerCapabilityFactory.class); protected abstract String getStateFeatureNodeName(); @@ -94,11 +98,11 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { wdCap.setProcessState(newFeatureDefinition(getProcessStateNodeName(), monitorValueNode)); // --- Selectable features ----- wdCap.setRinseFeat(newFeatureDefinition(getRinseFeatureNodeName(), monitorValueNode, - WM_CHANNEL_REMOTE_START_RINSE, WM_CHANNEL_RINSE_ID)); + CHANNEL_WM_REMOTE_START_RINSE, CHANNEL_WMD_RINSE_ID)); wdCap.setTemperatureFeat(newFeatureDefinition(getTemperatureFeatureNodeName(), monitorValueNode, - WM_CHANNEL_REMOTE_START_TEMP, WM_CHANNEL_TEMP_LEVEL_ID)); - wdCap.setSpinFeat(newFeatureDefinition(getSpinFeatureNodeName(), monitorValueNode, WM_CHANNEL_REMOTE_START_SPIN, - WM_CHANNEL_SPIN_ID)); + CHANNEL_WM_REMOTE_START_TEMP, CHANNEL_WMD_TEMP_LEVEL_ID)); + wdCap.setSpinFeat(newFeatureDefinition(getSpinFeatureNodeName(), monitorValueNode, CHANNEL_WM_REMOTE_START_SPIN, + CHANNEL_WMD_SPIN_ID)); // ---------------------------- wdCap.setDryLevel(newFeatureDefinition(getDryLevelNodeName(), monitorValueNode)); wdCap.setSoilWash(newFeatureDefinition(getSoilWashFeatureNodeName(), monitorValueNode)); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java index 490da35f6bada..b2203587a2f23 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapability.java @@ -48,7 +48,6 @@ public class WasherDryerCapability extends AbstractCapability commandsDefinition = new HashMap<>(); private Map courses = new LinkedHashMap<>(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java index 1696c2d4df65a..390f5e0f543ba 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV1.java @@ -12,19 +12,18 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; -import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.concurrent.atomic.AtomicReference; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.WasherFeatureDefinition; import com.fasterxml.jackson.databind.JsonNode; @@ -35,7 +34,6 @@ */ @NonNullByDefault public class WasherDryerCapabilityFactoryV1 extends AbstractWasherDryerCapabilityFactory { - private static final Logger logger = LoggerFactory.getLogger(WasherDryerCapabilityFactoryV1.class); @Override public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { @@ -108,7 +106,7 @@ protected MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode) { } @Override - protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { + protected Map getCommandsDefinition(JsonNode rootNode) { return getCommandsDefinitionV1(rootNode); } @@ -160,23 +158,7 @@ protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode fe fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); // All features from V1 are ENUMs - fd.setDataType(FeatureDataType.ENUM); - JsonNode valuesMappingNode = featureNode.path("option"); - if (!valuesMappingNode.isMissingNode()) { - - Map valuesMapping = new HashMap<>(); - valuesMappingNode.fields().forEachRemaining(e -> { - // collect values as: - // - // "option":{ - // "0":"@WM_STATE_POWER_OFF_W", - // to "0" -> "@WM_STATE_POWER_OFF_W" - valuesMapping.put(e.getKey(), e.getValue().asText()); - }); - fd.setValuesMapping(valuesMapping); - } - - return fd; + return WasherFeatureDefinition.setAllValuesMapping(fd, featureNode); } @Override @@ -217,7 +199,6 @@ protected String getCourseNodeName(JsonNode rootNode) { JsonNode refOptions = rootNode.path(getMonitorValueNodeName()).path(getConfigCourseType(rootNode)) .path("option"); if (refOptions.isArray()) { - AtomicReference courseNodeName = new AtomicReference<>(""); for (JsonNode node : refOptions) { return node.asText(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java index c93493474992f..4eb0157301f77 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerCapabilityFactoryV2.java @@ -12,12 +12,22 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; -import java.util.*; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.model.CommandDefinition; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDataType; +import org.openhab.binding.lgthinq.lgservices.model.FeatureDefinition; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringResultFormat; +import org.openhab.binding.lgthinq.lgservices.model.devices.commons.washers.WasherFeatureDefinition; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -57,14 +67,11 @@ protected boolean hasFeatInOptions(String featName, JsonNode monitoringValueNode protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, @Nullable String targetChannelId, @Nullable String refChannelId) { JsonNode featureNode = featuresNode.path(featureName); - if (featureNode.isMissingNode()) { - return FeatureDefinition.NULL_DEFINITION; + FeatureDefinition fd; + if ((fd = WasherFeatureDefinition.getBasicFeatureDefinition(featureName, featureNode, targetChannelId, + refChannelId)) == FeatureDefinition.NULL_DEFINITION) { + return fd; } - FeatureDefinition fd = new FeatureDefinition(); - fd.setName(featureName); - fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); - fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); - JsonNode labelNode = featureNode.path("label"); if (!labelNode.isMissingNode() && !labelNode.isNull()) { fd.setLabel(labelNode.asText()); @@ -197,13 +204,13 @@ protected Map getCommandsDefinition(JsonNode rootNode List escapeDataValues = Arrays.asList("course", "SmartCourse", "doorLock", "childLock"); if (commandNode.isMissingNode()) { logger.warn("No commands found in the DryerWasher definition. This is most likely a bug."); - return Collections.EMPTY_MAP; + return Collections.emptyMap(); } Map commands = new HashMap<>(); for (Iterator> it = commandNode.fields(); it.hasNext();) { Map.Entry e = it.next(); String commandName = e.getKey(); - if (commandName.equals("vtCtrl")) { + if ("vtCtrl".equals(commandName)) { // ignore command continue; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java index dcdba425e44fb..40195da03665e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshot.java @@ -12,7 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_POWER_OFF_VALUE; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WMD_POWER_OFF_VALUE; import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.lgservices.model.AbstractSnapshotDefinition; @@ -238,7 +238,7 @@ public void setChildLock(String childLock) { public void setState(String state) { this.state = state; - if (state.equals(WM_POWER_OFF_VALUE)) { + if (state.equals(WMD_POWER_OFF_VALUE)) { powerState = DevicePowerState.DV_POWER_OFF; } else { powerState = DevicePowerState.DV_POWER_ON; @@ -256,9 +256,9 @@ public String getRemoteStart() { } public void setRemoteStart(String remoteStart) { - this.remoteStart = remoteStart.contains("ON") || remoteStart.equals("1") ? "ON" - : (remoteStart.contains("OFF") || remoteStart.equals("0") ? "OFF" : remoteStart); - remoteStartEnabled = this.remoteStart.equals("ON"); + this.remoteStart = remoteStart.contains("ON") || "1".equals(remoteStart) ? "ON" + : (remoteStart.contains("OFF") || "0".equals(remoteStart) ? "OFF" : remoteStart); + remoteStartEnabled = "ON".equals(this.remoteStart); } @JsonProperty("standby") @@ -268,9 +268,8 @@ public String getStandByStatus() { } public void setStandByStatus(String standByStatus) { - this.standByStatus = standByStatus.contains("ON") || standByStatus.equals("1") ? "ON" - : (standByStatus.contains("OFF") || standByStatus.equals("0") ? "OFF" : standByStatus); - ; + this.standByStatus = standByStatus.contains("ON") || "1".equals(standByStatus) ? "ON" + : (standByStatus.contains("OFF") || "0".equals(standByStatus) ? "OFF" : standByStatus); standBy = this.standByStatus.contains("ON"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java index 4a976d50edd1a..9abbfe56373fd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/WasherDryerSnapshotBuilder.java @@ -12,16 +12,22 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.WM_SNAPSHOT_WASHER_DRYER_NODE_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WMD_SNAPSHOT_WASHER_DRYER_NODE_V2; import java.util.List; import java.util.Map; import java.util.Objects; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.internal.errors.LGThinqUnmarshallException; -import org.openhab.binding.lgthinq.lgservices.model.*; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqUnmarshallException; +import org.openhab.binding.lgthinq.lgservices.model.CapabilityDefinition; +import org.openhab.binding.lgthinq.lgservices.model.DefaultSnapshotBuilder; +import org.openhab.binding.lgthinq.lgservices.model.DeviceTypes; +import org.openhab.binding.lgthinq.lgservices.model.LGAPIVerion; +import org.openhab.binding.lgthinq.lgservices.model.MonitoringBinaryProtocol; + +import com.fasterxml.jackson.core.type.TypeReference; /** * The {@link WasherDryerSnapshotBuilder} @@ -54,42 +60,36 @@ protected WasherDryerSnapshot getSnapshot(Map snapMap, Capabilit DeviceTypes type = capDef.getDeviceType(); LGAPIVerion version = capDef.getDeviceVersion(); switch (type) { + case DRYER_TOWER: + case DRYER: case WASHER_TOWER: case WASHERDRYER_MACHINE: switch (version) { case V1_0: { - snap = objectMapper.convertValue(snapMap, snapClass); - snap.setRawData(snapMap); + if (type == DeviceTypes.DRYER || type == DeviceTypes.DRYER_TOWER) { + throw new IllegalArgumentException("Version 1.0 for Dryer is not supported yet."); + } else { + snap = objectMapper.convertValue(snapMap, snapClass); + snap.setRawData(snapMap); + } } case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), - "washerDryer node must be present in the snapshot"); + Map washerDryerMap = Objects.requireNonNull(objectMapper + .convertValue(snapMap.get(WMD_SNAPSHOT_WASHER_DRYER_NODE_V2), new TypeReference<>() { + }), "washerDryer node must be present in the snapshot"); snap = objectMapper.convertValue(washerDryerMap, snapClass); setAltCourseNodeName(capDef, snap, washerDryerMap); snap.setRawData(washerDryerMap); return snap; } + default: + throw new IllegalStateException("Snapshot for device type " + type + " and version " + version + + " are not supported for this builder. It most likely a bug"); } - case DRYER_TOWER: - case DRYER: - switch (version) { - case V1_0: { - throw new IllegalArgumentException("Version 1.0 for Washer is not supported yet."); - } - case V2_0: { - Map washerDryerMap = Objects.requireNonNull( - (Map) snapMap.get(WM_SNAPSHOT_WASHER_DRYER_NODE_V2), - "washerDryer node must be present in the snapshot"); - snap = objectMapper.convertValue(washerDryerMap, snapClass); - setAltCourseNodeName(capDef, snap, washerDryerMap); - snap.setRawData(snapMap); - return snap; - } - } + default: + throw new IllegalStateException( + "Snapshot for device type " + type + " not supported for this builder. It most likely a bug"); } - throw new IllegalStateException( - "Snapshot for device type " + type + " not supported for this builder. It most likely a bug"); } private static void setAltCourseNodeName(CapabilityDefinition capDef, WasherDryerSnapshot snap, diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index 3b31bb023c81c..e896b27fc29f5 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -168,16 +168,10 @@ channel-type.lgthinq.dryer-remain-time.state.pattern = %1$tH:%1$tM channel-type.lgthinq.dryer-state.label = Dryer State channel-type.lgthinq.dryer-state.description = Dryer Operation State channel-type.lgthinq.energy-saving.label = Energy Saving -channel-type.lgthinq.extra_info_collector.label = Info Collector -channel-type.lgthinq.extra_info_collector.description = This switch enable collector for energy and filter consumption (if presents) +channel-type.lgthinq.extra-info-collector.label = Info Collector +channel-type.lgthinq.extra-info-collector.description = This switch enable collector for energy and filter consumption (if presents) channel-type.lgthinq.fan-speed.label = Fan Speed channel-type.lgthinq.fan-speed.description = AC Wind Strength -channel-type.lgthinq.fan-speed.state.option.2 = Lower -channel-type.lgthinq.fan-speed.state.option.3 = Low -channel-type.lgthinq.fan-speed.state.option.4 = Medium -channel-type.lgthinq.fan-speed.state.option.5 = Fast -channel-type.lgthinq.fan-speed.state.option.6 = Faster -channel-type.lgthinq.fan-speed.state.option.8 = Auto channel-type.lgthinq.fan-step-left-right.label = Fan HDir channel-type.lgthinq.fan-step-left-right.description = Fan Horizontal Direction channel-type.lgthinq.fan-step-up-down.label = Fan VDir @@ -222,9 +216,6 @@ channel-type.lgthinq.min-temperature.label = Minimum Temp. channel-type.lgthinq.min-temperature.description = Minimum temperature for this mode. channel-type.lgthinq.operation-mode.label = Operation Mode channel-type.lgthinq.operation-mode.description = AC Operation Mode -channel-type.lgthinq.operation-mode.state.option.1 = Cool -channel-type.lgthinq.operation-mode.state.option.2 = Dry -channel-type.lgthinq.operation-mode.state.option.3 = Fan channel-type.lgthinq.remaining-filter.label = Remaining Filter channel-type.lgthinq.remaining-filter.description = Remaining filter without need to be replaced. channel-type.lgthinq.rs-course.label = Course to Run @@ -273,99 +264,4 @@ channel-type.lgthinq.washerdryer-stand-by.description = Standby Mode channel-type.lgthinq.washerdryer-temp-level.label = Temp. Level channel-type.lgthinq.washerdryer-temp-level.description = Target Temperature Level -# thing types - -thing-type.lgthinq.101.label = LG ThinQ Fridge -thing-type.lgthinq.101.description = LG ThinQ Fridge -thing-type.lgthinq.201.label = LG ThinQ Washer -thing-type.lgthinq.201.description = LG ThinQ Washing Machine -thing-type.lgthinq.202.label = LG ThinQ Dryer -thing-type.lgthinq.202.description = LG ThinQ Dryer -thing-type.lgthinq.204.label = LG ThinQ Dish Washer -thing-type.lgthinq.204.description = LG ThinQ Dish Washer -thing-type.lgthinq.221.label = LG ThinQ Washer Tower -thing-type.lgthinq.221.description = LG ThinQ Washing Tower -thing-type.lgthinq.221.label = LG ThinQ Washer Tower -thing-type.lgthinq.221.description = LG ThinQ Washing Tower -thing-type.lgthinq.222.label = LG ThinQ Dryer Tower -thing-type.lgthinq.222.description = LG ThinQ Dryer Tower -thing-type.lgthinq.401.label = LG ThinQ Air Conditioner -thing-type.lgthinq.401.description = LG ThinQ Air Conditioner -thing-type.lgthinq.401HP.label = LG ThinQ Heat Pump -thing-type.lgthinq.401HP.description = LG ThinQ Heat Pump - -# thing types config - -thing-type.config.lgthinq.201.group.Settings.label = Polling -thing-type.config.lgthinq.201.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.201.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.201.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.201.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.201.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.202.group.Settings.label = Polling -thing-type.config.lgthinq.202.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.202.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.202.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.202.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.202.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.204.group.Settings.label = Polling -thing-type.config.lgthinq.204.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.204.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.204.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.204.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.204.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.221.group.Settings.label = Polling -thing-type.config.lgthinq.221.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.221.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.221.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.221.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.221.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.222.group.Settings.label = Polling -thing-type.config.lgthinq.222.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.222.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.222.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.222.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.222.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.401.group.Settings.label = Polling -thing-type.config.lgthinq.401.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.401.pollExtraInfoOnPowerOff.label = Enable Extra Info Polling on powered off device -thing-type.config.lgthinq.401.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. -thing-type.config.lgthinq.401.pollingExtraInfoPeriodSeconds.label = Polling period in seconds for Extra Info -thing-type.config.lgthinq.401.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) -thing-type.config.lgthinq.401.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.401.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.401.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.401.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) -thing-type.config.lgthinq.401HP.group.Settings.label = Polling -thing-type.config.lgthinq.401HP.group.Settings.description = Settings required to optimize the polling behaviour. -thing-type.config.lgthinq.401HP.pollExtraInfoOnPowerOff.label = Enable Extra Info Polling on powered off device -thing-type.config.lgthinq.401HP.pollExtraInfoOnPowerOff.description = If enables, extra info will be fetched even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. -thing-type.config.lgthinq.401HP.pollingExtraInfoPeriodSeconds.label = Polling period in seconds for Extra Info -thing-type.config.lgthinq.401HP.pollingExtraInfoPeriodSeconds.description = Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) -thing-type.config.lgthinq.401HP.pollingPeriodPowerOffSeconds.label = Polling period in seconds when the device is off -thing-type.config.lgthinq.401HP.pollingPeriodPowerOffSeconds.description = Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. -thing-type.config.lgthinq.401HP.pollingPeriodPowerOnSeconds.label = Polling period in seconds for Device States when device is on -thing-type.config.lgthinq.401HP.pollingPeriodPowerOnSeconds.description = Seconds to wait to the next polling for device state (dashboard channels) - -# binding - -binding.lgthinq.name = LG Thinq Binding -binding.lgthinq.description = Binding to integrate OpenHab with LG Thinq API (v1 & v2) - -# channel types - -channel-type.lgthinq.cool-jet.description = Cool Jet Mode - -# errors - -error.mandotory-fields-missing = Mandatory Fields are missing (username, passoword, language and country). -error.toke-file-corrupted = Error Openning LGThinq Token File. Try to delete it (in data directory) to the bridge automatically recreate it. -error.toke-file-access-error = Error Handling Token Configuration File. -error.toke-refresh = Error refreshing LGThinq Token. Try to delete it (in data directory) to the bridge automatically recreate it. -error.lgapi-communication-error = Generic Error in the LG API communication process. -error.offline.conf-error-no-device-id = No DeviceID defined for the LG Thinq Thing. -error.lgapi-getting-devices = Error getting devices from LG API in scanner process. - -# offline statuses - -offline.device-disconnected = Device is disconnected. +error.lgapi-getting-devices = Error getting device list from the account diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml index 66a778e6e41d6..48042229c4290 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml @@ -74,7 +74,7 @@ Show more information about the device. - + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index e693b1d7c21f8..87227ca46b476 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -46,7 +46,7 @@ - + Switch This switch enable collector for energy and filter consumption (if presents) @@ -136,28 +136,11 @@ AC Wind Strength Wind - - - - - - - - - - Number AC Operation Mode - - - - - - - diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml index 977f65c370fe7..3c19bc4d09599 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -76,7 +76,7 @@ Show more information about the device. - + diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index 572c159f7dade..4e6f188744319 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -27,6 +27,7 @@ * @author Nemer Daud - Initial contribution */ @NonNullByDefault +@SuppressWarnings("null") public class JsonUtils { public static T unmashallJson(String fileName) { InputStream inputStream = JsonUtils.class.getResourceAsStream(fileName); diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java index 12a7642d7b2d0..24813b276c81b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/LGThinqBridgeTests.java @@ -12,21 +12,39 @@ */ package org.openhab.binding.lgthinq.handler; -import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.containing; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.ok; +import static com.github.tomakehurst.wiremock.client.WireMock.post; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.Mockito.*; import static org.mockito.Mockito.any; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_GATEWAY_SERVICE_PATH_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_OAUTH_SEARCH_KEY_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_PLATFORM_TYPE_V1; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_PLATFORM_TYPE_V2; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_PRE_LOGIN_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_LS_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_SESSION_LOGIN_PATH; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_USER_INFO; import java.io.File; import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; import javax.ws.rs.core.UriBuilder; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jetty.client.HttpClient; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -34,13 +52,15 @@ import org.mockito.junit.jupiter.MockitoExtension; import org.openhab.binding.lgthinq.internal.LGThinQBindingConstants; import org.openhab.binding.lgthinq.internal.LGThinQBridgeConfiguration; -import org.openhab.binding.lgthinq.internal.api.RestUtils; -import org.openhab.binding.lgthinq.internal.api.TokenManager; import org.openhab.binding.lgthinq.internal.handler.LGThinQBridgeHandler; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; +import org.openhab.binding.lgthinq.lgservices.api.RestUtils; +import org.openhab.binding.lgthinq.lgservices.api.TokenManager; import org.openhab.binding.lgthinq.lgservices.model.LGDevice; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCanonicalSnapshot; +import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ACCapability; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; import org.openhab.core.thing.ThingUID; @@ -56,13 +76,15 @@ */ @ExtendWith(MockitoExtension.class) @WireMockTest(httpPort = 8880) +@NonNullByDefault +@SuppressWarnings({ "unchecked", "null" }) class LGThinqBridgeTests { private static final Logger logger = LoggerFactory.getLogger(LGThinqBridgeTests.class); private final String fakeBridgeName = "fakeBridgeId"; - private String fakeLanguage = "pt-BR"; - private String fakeCountry = "BR"; - private String fakeUserName = "someone@some.url"; - private String fakePassword = "somepassword"; + private final String fakeLanguage = "pt-BR"; + private final String fakeCountry = "BR"; + private final String fakeUserName = "someone@some.url"; + private final String fakePassword = "somepassword"; private final String gtwResponse = JsonUtils.loadJson("gtw-response-1.json"); private final String preLoginResponse = JsonUtils.loadJson("prelogin-response-1.json"); private final String userIdType = "LGE"; @@ -80,19 +102,20 @@ class LGThinqBridgeTests { private final String sessionTokenReturned = String.format(JsonUtils.loadJson("session-token-response-1.json"), accessToken, refreshToken); - private String getCurrentTimestamp() { - SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - return sdf.format(new Date()); - } + // private String getCurrentTimestamp() { + // SimpleDateFormat sdf = new SimpleDateFormat(LG_API_DATE_FORMAT); + // sdf.setTimeZone(TimeZone.getTimeZone("UTC")); + // return sdf.format(new Date()); + // } @Test public void testDiscoveryACThings() { setupAuthenticationMock(); - LGThinQApiClientService service1 = LGThinQApiClientServiceFactory.newACApiClientService(PLATFORM_TYPE_V1, - mock(HttpClientFactory.class)); - LGThinQApiClientService service2 = LGThinQApiClientServiceFactory.newACApiClientService(PLATFORM_TYPE_V2, - mock(HttpClientFactory.class)); + // LGThinQApiClientService service1 = + // LGThinQApiClientServiceFactory.newACApiClientService(LG_API_PLATFORM_TYPE_V1, + // mock(HttpClientFactory.class)); + LGThinQApiClientService service2 = LGThinQApiClientServiceFactory + .newACApiClientService(LG_API_PLATFORM_TYPE_V2, mock(HttpClientFactory.class)); try { List devices = service2.listAccountDevices("bridgeTest"); assertEquals(devices.size(), 2); @@ -102,21 +125,20 @@ public void testDiscoveryACThings() { } private void setupAuthenticationMock() { - stubFor(get(GATEWAY_SERVICE_PATH_V2).willReturn(ok(gtwResponse))); + stubFor(get(LG_API_GATEWAY_SERVICE_PATH_V2).willReturn(ok(gtwResponse))); String preLoginPwd = RestUtils.getPreLoginEncPwd(fakePassword); - stubFor(post("/spx" + PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) + stubFor(post("/spx" + LG_API_PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) .willReturn(ok(preLoginResponse))); - URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + OAUTH_SEARCH_KEY_PATH) + URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + LG_API_OAUTH_SEARCH_KEY_PATH) .queryParam("key_name", "OAUTH_SECRETKEY").queryParam("sever_type", "OP").build(); stubFor(get(String.format("%s?%s", uri.getPath(), uri.getQuery())).willReturn(ok(oauthTokenSearchKeyReturned))); String fakeUserNameEncoded = URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8); - stubFor(post(V2_SESSION_LOGIN_PATH + fakeUserNameEncoded) + stubFor(post(LG_API_V2_SESSION_LOGIN_PATH + fakeUserNameEncoded) .withRequestBody(containing("user_auth2=SOME_DUMMY_ENC_PWD")) .withHeader("X-Signature", equalTo("SOME_DUMMY_SIGNATURE")) .withHeader("X-Timestamp", equalTo("1643236928")).willReturn(ok(loginSessionResponse))); - stubFor(get(V2_USER_INFO).willReturn(ok(userInfoReturned))); - stubFor(get("/v1" + V2_LS_PATH).willReturn(ok(dashboardListReturned))); - String currTimestamp = getCurrentTimestamp(); + stubFor(get(LG_API_V2_USER_INFO).willReturn(ok(userInfoReturned))); + stubFor(get("/v1" + LG_API_V2_LS_PATH).willReturn(ok(dashboardListReturned))); Map empData = new LinkedHashMap<>(); empData.put("account_type", userIdType); empData.put("country_code", fakeCountry); @@ -159,22 +181,21 @@ void setUp() { @Test public void testDiscoveryWMThings() { - stubFor(get(GATEWAY_SERVICE_PATH_V2).willReturn(ok(gtwResponse))); + stubFor(get(LG_API_GATEWAY_SERVICE_PATH_V2).willReturn(ok(gtwResponse))); String preLoginPwd = RestUtils.getPreLoginEncPwd(fakePassword); - stubFor(post("/spx" + PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) + stubFor(post("/spx" + LG_API_PRE_LOGIN_PATH).withRequestBody(containing("user_auth2=" + preLoginPwd)) .willReturn(ok(preLoginResponse))); - URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + OAUTH_SEARCH_KEY_PATH) + URI uri = UriBuilder.fromUri("http://localhost:8880").path("spx" + LG_API_OAUTH_SEARCH_KEY_PATH) .queryParam("key_name", "OAUTH_SECRETKEY").queryParam("sever_type", "OP").build(); stubFor(get(String.format("%s?%s", uri.getPath(), uri.getQuery())).willReturn(ok(oauthTokenSearchKeyReturned))); - stubFor(post(V2_SESSION_LOGIN_PATH + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8)) + stubFor(post(LG_API_V2_SESSION_LOGIN_PATH + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8)) .withRequestBody(containing("user_auth2=SOME_DUMMY_ENC_PWD")) .withHeader("X-Signature", equalTo("SOME_DUMMY_SIGNATURE")) .withHeader("X-Timestamp", equalTo("1643236928")).willReturn(ok(loginSessionResponse))); - stubFor(get(V2_USER_INFO).willReturn(ok(userInfoReturned))); - stubFor(get("/v1" + V2_LS_PATH).willReturn(ok(dashboardWMListReturned))); + stubFor(get(LG_API_V2_USER_INFO).willReturn(ok(userInfoReturned))); + stubFor(get("/v1" + LG_API_V2_LS_PATH).willReturn(ok(dashboardWMListReturned))); String dataCollectedWM = JsonUtils.loadJson("wm-data-result.json"); stubFor(get("/v1/service/devices/fakeDeviceId").willReturn(ok(dataCollectedWM))); - String currTimestamp = getCurrentTimestamp(); Map empData = new LinkedHashMap<>(); empData.put("account_type", userIdType); empData.put("country_code", fakeCountry); @@ -185,18 +206,19 @@ public void testDiscoveryWMThings() { .withRequestBody(containing("username=" + URLEncoder.encode(fakeUserName, StandardCharsets.UTF_8))) .withHeader("lgemp-x-session-key", equalTo(loginSessionId)).willReturn(ok(sessionTokenReturned))); - Bridge fakeThing = mock(Bridge.class); - ThingUID fakeThingUid = mock(ThingUID.class); - when(fakeThingUid.getId()).thenReturn(fakeBridgeName); - when(fakeThing.getUID()).thenReturn(fakeThingUid); - String tempDir = System.getProperty("java.io.tmpdir"); - LGThinQBindingConstants.THINQ_USER_DATA_FOLDER = "" + tempDir; + // Bridge fakeThing = mock(Bridge.class); + // ThingUID fakeThingUid = mock(ThingUID.class); + // when(fakeThingUid.getId()).thenReturn(fakeBridgeName); + // when(fakeThing.getUID()).thenReturn(fakeThingUid); + String tempDir = Objects.requireNonNull(System.getProperty("java.io.tmpdir"), + "java.io.tmpdir environment variable must be set"); + LGThinQBindingConstants.THINQ_USER_DATA_FOLDER = tempDir; LGThinQBindingConstants.THINQ_CONNECTION_DATA_FILE = tempDir + File.separator + "token.json"; LGThinQBindingConstants.BASE_CAP_CONFIG_DATA_FILE = tempDir + File.separator + "thinq-cap.json"; - LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing, mock(HttpClientFactory.class)); + // LGThinQBridgeHandler b = new LGThinQBridgeHandler(fakeThing, mock(HttpClientFactory.class)); final LGThinQWMApiClientService service2 = LGThinQApiClientServiceFactory - .newWMApiClientService(PLATFORM_TYPE_V1, mock(HttpClientFactory.class)); + .newWMApiClientService(LG_API_PLATFORM_TYPE_V1, mock(HttpClientFactory.class)); TokenManager tokenManager = new TokenManager(mock(HttpClient.class)); try { if (!tokenManager.isOauthTokenRegistered(fakeBridgeName)) { @@ -210,11 +232,4 @@ public void testDiscoveryWMThings() { logger.error("Error testing facade", e); } } - - // @Test - // void TestWakeUp() throws LGThinqApiException { - // setupAuthenticationMock(); - // LGThinQWMApiClientService service = LGThinQWMApiV1ClientServiceImpl.getInstance(); - // service.wakeUp("xxx", "yyyy", true); - // } } diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java index b1134d6a2097a..afc23d5b07a36 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/lgservices/model/CapabilityFactoryTest.java @@ -12,14 +12,17 @@ */ package org.openhab.binding.lgthinq.lgservices.model; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.IOException; import java.io.InputStream; +import org.eclipse.jdt.annotation.NonNullByDefault; import org.junit.jupiter.api.Test; import org.openhab.binding.lgthinq.handler.JsonUtils; -import org.openhab.binding.lgthinq.internal.errors.LGThinqException; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqException; import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; import com.fasterxml.jackson.databind.JsonNode; @@ -30,8 +33,9 @@ * * @author Nemer Daud - Initial contribution */ +@NonNullByDefault class CapabilityFactoryTest { - private ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper = new ObjectMapper(); @Test void create() throws IOException, LGThinqException { @@ -40,8 +44,7 @@ void create() throws IOException, LGThinqException { try (InputStream inputStream = classLoader.getResourceAsStream("thinq-washer-v2-cap.json")) { assertNotNull(inputStream); JsonNode mapper = objectMapper.readTree(inputStream); - WasherDryerCapability wpCap = (WasherDryerCapability) CapabilityFactory.getInstance().create(mapper, - WasherDryerCapability.class); + WasherDryerCapability wpCap = CapabilityFactory.getInstance().create(mapper, WasherDryerCapability.class); assertNotNull(wpCap); assertEquals(40, wpCap.getCourses().size()); assertTrue(wpCap.getRinseFeat().getValuesMapping().size() > 1); From dace432022ad9d80caf0cd07b4edbafeee8d660e Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sun, 3 Nov 2024 21:50:46 -0300 Subject: [PATCH 120/130] [lgthinq][fix] fix code to remove warnings in complication Signed-off-by: Nemer Daud --- .../api/LGThinqCanonicalModelUtil.java | 52 --- .../lgthinq/internal/api/LGThinqGateway.java | 148 ------- .../internal/api/OauthLgEmpAuthenticator.java | 408 ------------------ .../lgthinq/internal/api/RestResult.java | 39 -- .../lgthinq/internal/api/RestUtils.java | 193 --------- .../lgthinq/internal/api/TokenManager.java | 167 ------- .../lgthinq/internal/api/TokenResult.java | 105 ----- .../lgthinq/internal/api/UserInfo.java | 73 ---- .../internal/api/model/GatewayResult.java | 71 --- .../internal/api/model/HeaderResult.java | 39 -- .../errors/AccountLoginException.java | 27 -- .../internal/errors/LGThinqApiException.java | 48 --- .../errors/LGThinqApiExhaustionException.java | 31 -- ...GThinqDeviceV1MonitorExpiredException.java | 32 -- .../LGThinqDeviceV1OfflineException.java | 33 -- .../internal/errors/LGThinqException.java | 31 -- .../errors/LGThinqGatewayException.java | 27 -- .../errors/LGThinqUnmarshallException.java | 31 -- .../internal/errors/PreLoginException.java | 27 -- .../errors/RefreshTokenException.java | 31 -- .../internal/errors/TokenException.java | 27 -- .../internal/errors/UserInfoException.java | 24 -- .../internal/type/ThingModelTypeUtils.java | 269 ------------ .../DishWasherCapabilityFactoryV1.java | 115 ----- .../main/resources/OH-INF/thing/bridge.xml | 6 +- .../LGThinQWasherDryerHandlerTest.java | 30 -- 26 files changed, 4 insertions(+), 2080 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java deleted file mode 100644 index 50bf772c4be2b..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqCanonicalModelUtil.java +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api; - -import java.io.IOException; -import java.util.Map; -import java.util.Objects; - -import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * The {@link LGThinqCanonicalModelUtil} class - * - * @author Nemer Daud - Initial contribution - */ -public class LGThinqCanonicalModelUtil { - public static final ObjectMapper mapper = new ObjectMapper(); - public static final String LG_ROOT_TAG_V1 = "lgedmRoot"; - - public static GatewayResult getGatewayResult(String rawJson) throws IOException { - Map map = mapper.readValue(rawJson, new TypeReference<>() { - }); - Map content = (Map) map.get("result"); - String resultCode = (String) map.get("resultCode"); - if (content == null) { - throw new IllegalArgumentException("Enexpected result. Gateway Content Result is null"); - } else if (resultCode == null) { - throw new IllegalArgumentException("Enexpected result. resultCode code is null"); - } - - return new GatewayResult(Objects.requireNonNull(resultCode, "Expected resultCode field in json"), "", - Objects.requireNonNull(content.get("rtiUri"), "Expected rtiUri field in json"), - Objects.requireNonNull(content.get("thinq1Uri"), "Expected thinq1Uri field in json"), - Objects.requireNonNull(content.get("thinq2Uri"), "Expected thinq2Uri field in json"), - Objects.requireNonNull(content.get("empUri"), "Expected empUri field in json"), - Objects.requireNonNull(content.get("empTermsUri"), "Expected empTermsUri field in json"), "", - Objects.requireNonNull(content.get("empSpxUri"), "Expected empSpxUri field in json")); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java deleted file mode 100644 index 8e0c4b0bfe6dd..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/LGThinqGateway.java +++ /dev/null @@ -1,148 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_EMP_SESS_PATH; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.V2_EMP_SESS_URL; - -import java.io.Serializable; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; - -import com.fasterxml.jackson.annotation.JsonIgnore; - -/** - * The {@link LGThinqGateway} hold informations about the LG Gateway - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqGateway implements Serializable { - private String empBaseUri = ""; - private String loginBaseUri = ""; - private String apiRootV1 = ""; - private String apiRootV2 = ""; - private String authBase = ""; - private String language = ""; - private String country = ""; - private String username = ""; - private String password = ""; - private String alternativeEmpServer = ""; - private int accountVersion; - - public LGThinqGateway() { - } - - public LGThinqGateway(GatewayResult gwResult, String language, String country, String alternativeEmpServer) { - this.apiRootV2 = gwResult.getThinq2Uri(); - this.apiRootV1 = gwResult.getThinq1Uri(); - this.loginBaseUri = gwResult.getEmpSpxUri(); - this.authBase = gwResult.getEmpUri(); - this.empBaseUri = gwResult.getEmpTermsUri(); - this.language = language; - this.country = country; - this.alternativeEmpServer = alternativeEmpServer; - } - - @JsonIgnore - public String getTokenSessionEmpUrl() { - return alternativeEmpServer.isBlank() ? V2_EMP_SESS_URL : alternativeEmpServer + V2_EMP_SESS_PATH; - } - - public String getEmpBaseUri() { - return empBaseUri; - } - - public int getAccountVersion() { - return accountVersion; - } - - public String getApiRootV2() { - return apiRootV2; - } - - public String getAuthBase() { - return authBase; - } - - public String getLanguage() { - return language; - } - - public String getCountry() { - return country; - } - - public String getLoginBaseUri() { - return loginBaseUri; - } - - public String getApiRootV1() { - return apiRootV1; - } - - public void setEmpBaseUri(String empBaseUri) { - this.empBaseUri = empBaseUri; - } - - public void setLoginBaseUri(String loginBaseUri) { - this.loginBaseUri = loginBaseUri; - } - - public void setApiRootV1(String apiRootV1) { - this.apiRootV1 = apiRootV1; - } - - public void setApiRootV2(String apiRootV2) { - this.apiRootV2 = apiRootV2; - } - - public void setAuthBase(String authBase) { - this.authBase = authBase; - } - - public void setLanguage(String language) { - this.language = language; - } - - public void setCountry(String country) { - this.country = country; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - @Override - public String toString() { - return "LGThinqGateway{" + "empBaseUri='" + empBaseUri + '\'' + ", loginBaseUri='" + loginBaseUri + '\'' - + ", apiRootV1='" + apiRootV1 + '\'' + ", apiRootV2='" + apiRootV2 + '\'' + ", authBase='" + authBase - + '\'' + ", language='" + language + '\'' + ", country='" + country + '\'' + ", username='" - + (!username.isEmpty() ? "******" : "") + '\'' + ", password='" - + (!password.isEmpty() ? "******" : "") + '\'' + ", alternativeEmpServer='" - + alternativeEmpServer + '\'' + ", accountVersion=" + accountVersion + '}'; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java deleted file mode 100644 index 2f023d9fdfd6c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/OauthLgEmpAuthenticator.java +++ /dev/null @@ -1,408 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.text.SimpleDateFormat; -import java.util.*; - -import javax.ws.rs.core.UriBuilder; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.api.model.GatewayResult; -import org.openhab.binding.lgthinq.internal.errors.RefreshTokenException; -import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * The {@link OauthLgEmpAuthenticator} main service to authenticate against LG Emp Server via Oauth - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class OauthLgEmpAuthenticator { - - private static final Logger logger = LoggerFactory.getLogger(OauthLgEmpAuthenticator.class); - private static final Map oauthSearchKeyQueryParams = new LinkedHashMap<>(); - private static final ObjectMapper objectMapper = new ObjectMapper(); - - static { - oauthSearchKeyQueryParams.put("key_name", "OAUTH_SECRETKEY"); - oauthSearchKeyQueryParams.put("sever_type", "OP"); - } - - private HttpClient httpClient; - - public OauthLgEmpAuthenticator(HttpClient httpClient) { - this.httpClient = httpClient; - } - - static class PreLoginResult { - private final String username; - private final String signature; - private final String timestamp; - private final String encryptedPwd; - - public PreLoginResult(String username, String signature, String timestamp, String encryptedPwd) { - this.username = username; - this.signature = signature; - this.timestamp = timestamp; - this.encryptedPwd = encryptedPwd; - } - - public String getUsername() { - return username; - } - - public String getSignature() { - return signature; - } - - public String getTimestamp() { - return timestamp; - } - - public String getEncryptedPwd() { - return encryptedPwd; - } - } - - @NonNullByDefault - static class LoginAccountResult { - private final String userIdType; - private final String userId; - private final String country; - private final String loginSessionId; - - public LoginAccountResult(String userIdType, String userId, String country, String loginSessionId) { - this.userIdType = userIdType; - this.userId = userId; - this.country = country; - this.loginSessionId = loginSessionId; - } - - public String getUserIdType() { - return userIdType; - } - - public String getUserId() { - return userId; - } - - public String getCountry() { - return country; - } - - public String getLoginSessionId() { - return loginSessionId; - } - } - - private Map getGatewayRestHeader(String language, String country) { - return Map.ofEntries(new AbstractMap.SimpleEntry("Accept", "application/json"), - new AbstractMap.SimpleEntry("x-api-key", API_KEY_V2), - new AbstractMap.SimpleEntry("x-country-code", country), - new AbstractMap.SimpleEntry("x-client-id", CLIENT_ID), - new AbstractMap.SimpleEntry("x-language-code", language), - new AbstractMap.SimpleEntry("x-message-id", MESSAGE_ID), - new AbstractMap.SimpleEntry("x-service-code", SVC_CODE), - new AbstractMap.SimpleEntry("x-service-phase", SVC_PHASE), - new AbstractMap.SimpleEntry("x-thinq-app-level", APP_LEVEL), - new AbstractMap.SimpleEntry("x-thinq-app-os", APP_OS), - new AbstractMap.SimpleEntry("x-thinq-app-type", APP_TYPE), - new AbstractMap.SimpleEntry("x-thinq-app-ver", APP_VER)); - } - - private Map getLoginHeader(LGThinqGateway gw) { - Map headers = new HashMap<>(); - headers.put("Connection", "keep-alive"); - headers.put("X-Device-Language-Type", "IETF"); - headers.put("X-Application-Key", "6V1V8H2BN5P9ZQGOI5DAQ92YZBDO3EK9"); - headers.put("X-Client-App-Key", "LGAO221A02"); - headers.put("X-Lge-Svccode", "SVC709"); - headers.put("X-Device-Type", "M01"); - headers.put("X-Device-Platform", "ADR"); - headers.put("X-Device-Publish-Flag", "Y"); - headers.put("X-Device-Country", gw.getCountry()); - headers.put("X-Device-Language", gw.getLanguage()); - headers.put("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"); - headers.put("Access-Control-Allow-Origin", "*"); - headers.put("Accept-Encoding", "gzip, deflate, br"); - headers.put("Accept-Language", "en-US,en;q=0.9,pt-BR;q=0.8,pt;q=0.7"); - headers.put("Accept", "application/json"); - return headers; - } - - public LGThinqGateway discoverGatewayConfiguration(String gwUrl, String language, String country, - String alternativeEmpServer) throws IOException { - Map header = getGatewayRestHeader(language, country); - RestResult result; - result = RestUtils.getCall(httpClient, gwUrl, header, null); - - if (result.getStatusCode() != 200) { - throw new IllegalStateException( - "Expected HTTP OK return, but received result core:" + result.getJsonResponse()); - } else { - GatewayResult gwResult = LGThinqCanonicalModelUtil.getGatewayResult(result.getJsonResponse()); - ResultCodes resultCode = ResultCodes.fromCode(gwResult.getReturnedCode()); - if (ResultCodes.OK != resultCode) { - throw new IllegalStateException(String.format( - "Result from LGThinq Gateway from Authentication URL was unexpected. ResultCode: %s, with message:%s, Error Description:%s", - gwResult.getReturnedCode(), gwResult.getReturnedMessage(), resultCode.getDescription())); - } - - return new LGThinqGateway(gwResult, language, country, alternativeEmpServer); - } - } - - public PreLoginResult preLoginUser(LGThinqGateway gw, String username, String password) throws IOException { - String encPwd = RestUtils.getPreLoginEncPwd(password); - Map headers = getLoginHeader(gw); - // 1) Doing preLogin -> getting the password key - String preLoginUrl = gw.getLoginBaseUri() + PRE_LOGIN_PATH; - Map formData = Map.of("user_auth2", encPwd, "log_param", String.format( - "login request / user_id : %s / " + "third_party : null / svc_list : SVC202,SVC710 / 3rd_service : ", - username)); - RestResult resp = RestUtils.postCall(httpClient, preLoginUrl, headers, formData); - if (resp == null) { - logger.error("Error preLogin into account. Null data returned"); - throw new IllegalStateException("Error login into account. Null data returned"); - } else if (resp.getStatusCode() != 200) { - logger.error("Error preLogin into account. The reason is:{}", resp.getJsonResponse()); - throw new IllegalStateException(String.format("Error login into account:%s", resp.getJsonResponse())); - } - - Map preLoginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - logger.debug("encrypted_pw={}, signature={}, tStamp={}", preLoginResult.get("encrypted_pw"), - preLoginResult.get("signature"), preLoginResult.get("tStamp")); - return new PreLoginResult(username, - Objects.requireNonNull(preLoginResult.get("signature"), - "Unexpected login json result. Node 'signature' not found"), - Objects.requireNonNull(preLoginResult.get("tStamp"), - "Unexpected login json result. Node 'signature' not found"), - Objects.requireNonNull(preLoginResult.get("encrypted_pw"), - "Unexpected login json result. Node 'signature' not found")); - } - - public LoginAccountResult loginUser(LGThinqGateway gw, PreLoginResult preLoginResult) throws IOException { - // 2 - Login with username and hashed password - Map headers = getLoginHeader(gw); - headers.put("X-Signature", preLoginResult.getSignature()); - headers.put("X-Timestamp", preLoginResult.getTimestamp()); - Map formData = Map.of("user_auth2", "" + preLoginResult.getEncryptedPwd(), - "password_hash_prameter_flag", "Y", "svc_list", "SVC202,SVC710"); // SVC202=LG SmartHome, SVC710=EMP - // OAuth - String loginUrl = gw.getEmpBaseUri() + V2_SESSION_LOGIN_PATH - + URLEncoder.encode(preLoginResult.getUsername(), StandardCharsets.UTF_8); - RestResult resp = RestUtils.postCall(httpClient, loginUrl, headers, formData); - if (resp == null) { - logger.error("Error login into account. Null data returned"); - throw new IllegalStateException("Error loggin into acccount. Null data returned"); - } else if (resp.getStatusCode() != 200) { - logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); - throw new IllegalStateException(String.format("Error login into account:%s", resp.getJsonResponse())); - } - Map loginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - Map accountResult = (Map) loginResult.get("account"); - if (accountResult == null) { - throw new IllegalStateException("Error getting account from Login"); - } - return new LoginAccountResult( - Objects.requireNonNull((String) accountResult.get("userIDType"), - "Unexpected account json result. 'userIDType' not found"), - Objects.requireNonNull((String) accountResult.get("userID"), - "Unexpected account json result. 'userID' not found"), - Objects.requireNonNull((String) accountResult.get("country"), - "Unexpected account json result. 'country' not found"), - Objects.requireNonNull((String) accountResult.get("loginSessionID"), - "Unexpected account json result. 'loginSessionID' not found")); - } - - private String getCurrentTimestamp() { - SimpleDateFormat sdf = new SimpleDateFormat(DATE_FORMAT, Locale.US); - sdf.setTimeZone(TimeZone.getTimeZone("UTC")); - return sdf.format(new Date()); - } - - public TokenResult getToken(LGThinqGateway gw, LoginAccountResult accountResult) throws IOException { - // 3 - get secret key from emp signature - String empSearchKeyUrl = gw.getLoginBaseUri() + OAUTH_SEARCH_KEY_PATH; - - RestResult resp = RestUtils.getCall(httpClient, empSearchKeyUrl, null, oauthSearchKeyQueryParams); - if (resp.getStatusCode() != 200) { - logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); - throw new IllegalStateException(String.format("Error loggin into acccount:%s", resp.getJsonResponse())); - } - Map secretResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - @NonNull - String secretKey = Objects.requireNonNull(secretResult.get("returnData"), - "Unexpected json returned. Expected 'returnData' node here"); - logger.debug("Secret found:{}", secretResult.get("returnData")); - - // 4 - get OAuth Token Key from EMP API - Map empData = new LinkedHashMap<>(); - empData.put("account_type", accountResult.getUserIdType()); - empData.put("client_id", CLIENT_ID); - empData.put("country_code", accountResult.getCountry()); - empData.put("username", "" + accountResult.getUserId()); - String timestamp = getCurrentTimestamp(); - - byte[] oauthSig = RestUtils.getTokenSignature(gw.getTokenSessionEmpUrl(), secretKey, empData, timestamp); - - Map oauthEmpHeaders = new LinkedHashMap<>(); - oauthEmpHeaders.put("lgemp-x-app-key", OAUTH_CLIENT_KEY); - oauthEmpHeaders.put("lgemp-x-date", timestamp); - oauthEmpHeaders.put("lgemp-x-session-key", accountResult.getLoginSessionId()); - oauthEmpHeaders.put("lgemp-x-signature", new String(oauthSig)); - oauthEmpHeaders.put("Accept", "application/json"); - oauthEmpHeaders.put("X-Device-Type", "M01"); - oauthEmpHeaders.put("X-Device-Platform", "ADR"); - oauthEmpHeaders.put("Content-Type", "application/x-www-form-urlencoded"); - oauthEmpHeaders.put("Access-Control-Allow-Origin", "*"); - oauthEmpHeaders.put("Accept-Encoding", "gzip, deflate, br"); - oauthEmpHeaders.put("Accept-Language", "en-US,en;q=0.9"); - oauthEmpHeaders.put("User-Agent", - "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.63 Safari/537.36 Edg/93.0.961.44"); - logger.debug("===> Localized timestamp used: [{}]", timestamp); - logger.debug("===> signature created: [{}]", new String(oauthSig)); - resp = RestUtils.postCall(httpClient, gw.getTokenSessionEmpUrl(), oauthEmpHeaders, empData); - return handleTokenResult(resp); - } - - public UserInfo getUserInfo(TokenResult token) throws IOException { - UriBuilder builder = UriBuilder.fromUri(token.getOauthBackendUrl()).path(V2_USER_INFO); - String oauthUrl = builder.build().toURL().toString(); - String timestamp = getCurrentTimestamp(); - byte[] oauthSig = RestUtils.getTokenSignature(oauthUrl, OAUTH_SECRET_KEY, Collections.EMPTY_MAP, timestamp); - Map headers = Map.of("Accept", "application/json", "Authorization", - String.format("Bearer %s", token.getAccessToken()), "X-Lge-Svccode", SVC_CODE, "X-Application-Key", - APPLICATION_KEY, "lgemp-x-app-key", CLIENT_ID, "X-Device-Type", "M01", "X-Device-Platform", "ADR", - "x-lge-oauth-date", timestamp, "x-lge-oauth-signature", new String(oauthSig)); - RestResult resp = RestUtils.getCall(httpClient, oauthUrl, headers, null); - - return handleAccountInfoResult(resp); - } - - private UserInfo handleAccountInfoResult(RestResult resp) throws IOException { - Map result = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - if (resp.getStatusCode() != 200) { - logger.error("LG API returned error when trying to get user account information. The reason is:{}", - resp.getJsonResponse()); - throw new IllegalStateException( - String.format("LG API returned error when trying to get user account information. The reason is:%s", - resp.getJsonResponse())); - } else if (result.get("account") == null || ((Map) result.get("account")).get("userNo") == null) { - throw new IllegalStateException( - String.format("Error retrieving the account user information from access token")); - } - Map accountInfo = (Map) result.get("account"); - - return new UserInfo( - Objects.requireNonNullElse(accountInfo.get("userNo"), - "Unexpected result. userID must be present in json result"), - Objects.requireNonNull(accountInfo.get("userID"), - "Unexpected result. userID must be present in json result"), - Objects.requireNonNull(accountInfo.get("userIDType"), - "Unexpected result. userIDType must be present in json result"), - Objects.requireNonNullElse(accountInfo.get("displayUserID"), "")); - } - - public TokenResult doRefreshToken(TokenResult currentToken) throws IOException, RefreshTokenException { - UriBuilder builder = UriBuilder.fromUri(currentToken.getOauthBackendUrl()).path(V2_AUTH_PATH); - String oauthUrl = builder.build().toURL().toString(); - String timestamp = getCurrentTimestamp(); - - Map formData = new LinkedHashMap<>(); - formData.put("grant_type", "refresh_token"); - formData.put("refresh_token", currentToken.getRefreshToken()); - - byte[] oauthSig = RestUtils.getTokenSignature(oauthUrl, OAUTH_SECRET_KEY, formData, timestamp); - - Map headers = Map.of("x-lge-appkey", CLIENT_ID, "x-lge-oauth-signature", new String(oauthSig), - "x-lge-oauth-date", timestamp, "Accept", "application/json"); - - RestResult resp = RestUtils.postCall(httpClient, oauthUrl, headers, formData); - return handleRefreshTokenResult(resp, currentToken); - } - - private TokenResult handleTokenResult(@Nullable RestResult resp) throws IOException { - Map tokenResult; - if (resp == null) { - throw new IllegalStateException("Error getting oauth token. Null data returned"); - } - if (resp.getStatusCode() != 200) { - logger.error("Error getting oauth token. HTTP Status Code is:{}, The reason is:{}", resp.getStatusCode(), - resp.getJsonResponse()); - throw new IllegalStateException(String.format("Error getting oauth token:%s", resp.getJsonResponse())); - } else { - tokenResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - Integer status = (Integer) tokenResult.get("status"); - if ((status != null && !"1".equals("" + status)) || tokenResult.get("expires_in") == null) { - throw new IllegalStateException(String.format("Status error getting token:%s", tokenResult)); - } - } - - return new TokenResult( - Objects.requireNonNull((String) tokenResult.get("access_token"), - "Unexpected result. access_token must be present in json result"), - Objects.requireNonNull((String) tokenResult.get("refresh_token"), - "Unexpected result. refresh_token must be present in json result"), - Integer.parseInt(Objects.requireNonNull((String) tokenResult.get("expires_in"), - "Unexpected result. expires_in must be present in json result")), - new Date(), Objects.requireNonNull((String) tokenResult.get("oauth2_backend_url"), - "Unexpected result. oauth2_backend_url must be present in json result")); - } - - private TokenResult handleRefreshTokenResult(@Nullable RestResult resp, TokenResult currentToken) - throws IOException, RefreshTokenException { - Map tokenResult; - if (resp == null) { - logger.error("Error getting oauth token. Null data returned"); - throw new RefreshTokenException("Error getting oauth token. Null data returned"); - } - if (resp.getStatusCode() != 200) { - logger.error("Error getting oauth token. HTTP Status Code is:{}, The reason is:{}", resp.getStatusCode(), - resp.getJsonResponse()); - throw new RefreshTokenException(String.format("Error getting oauth token:%s", resp.getJsonResponse())); - } else { - tokenResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { - }); - if (tokenResult.get("access_token") == null || tokenResult.get("expires_in") == null) { - throw new RefreshTokenException(String.format("Status error get refresh token info:%s", tokenResult)); - } - } - - currentToken.setAccessToken(Objects.requireNonNull(tokenResult.get("access_token"), - "Unexpected error. Access Token must ever been provided by LG API")); - currentToken.setGeneratedTime(new Date()); - currentToken.setExpiresIn(Integer.parseInt(Objects.requireNonNull(tokenResult.get("expires_in"), - "Unexpected error. Access Token must ever been provided by LG API"))); - return currentToken; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java deleted file mode 100644 index 95cd1323e46e8..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestResult.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link RestResult} result from rest calls - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class RestResult { - private final String jsonResponse; - private final int resultCode; - - public RestResult(String jsonResponse, int resultCode) { - this.jsonResponse = jsonResponse; - this.resultCode = resultCode; - } - - public String getJsonResponse() { - return jsonResponse; - } - - public int getStatusCode() { - return resultCode; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java deleted file mode 100644 index eee210db9c5d9..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/RestUtils.java +++ /dev/null @@ -1,193 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api; - -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.math.BigInteger; -import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.security.InvalidKeyException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Base64; -import java.util.Map; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import javax.crypto.Mac; -import javax.crypto.spec.SecretKeySpec; -import javax.ws.rs.core.UriBuilder; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -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.util.FormContentProvider; -import org.eclipse.jetty.client.util.StringContentProvider; -import org.eclipse.jetty.util.Fields; -import org.openhab.core.i18n.CommunicationException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link RestUtils} rest utilities - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class RestUtils { - - private static final Logger logger = LoggerFactory.getLogger(RestUtils.class); - private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; - - public static String getPreLoginEncPwd(String pwdToEnc) { - MessageDigest digest; - try { - digest = MessageDigest.getInstance("SHA-512"); - } catch (NoSuchAlgorithmException e) { - logger.error("Definitively, it is unexpected.", e); - throw new IllegalStateException("Unexpected error. SHA-512 algorithm must exists in JDK distribution", e); - } - digest.reset(); - digest.update(pwdToEnc.getBytes(StandardCharsets.UTF_8)); - - return String.format("%0128x", new BigInteger(1, digest.digest())); - } - - public static byte[] getOauth2Sig(String messageSign, String secret) { - byte[] secretBytes = secret.getBytes(StandardCharsets.UTF_8); - SecretKeySpec signingKey = new SecretKeySpec(secretBytes, HMAC_SHA1_ALGORITHM); - - try { - Mac mac = Mac.getInstance(HMAC_SHA1_ALGORITHM); - mac.init(signingKey); - return Base64.getEncoder().encode(mac.doFinal(messageSign.getBytes(StandardCharsets.UTF_8))); - } catch (NoSuchAlgorithmException e) { - logger.error("Unexpected error. SHA1 algorithm must exists in JDK distribution.", e); - throw new IllegalStateException("Unexpected error. SHA1 algorithm must exists in JDK distribution", e); - } catch (InvalidKeyException e) { - logger.error("Unexpected error.", e); - throw new IllegalStateException("Unexpected error.", e); - } - } - - public static byte[] getTokenSignature(String authUrl, String secretKey, Map empData, - String timestamp) { - UriBuilder builder = UriBuilder.fromUri(authUrl); - empData.forEach(builder::queryParam); - - URI reqUri = builder.build(); - String signUrl = empData.size() > 0 ? reqUri.getPath() + "?" + reqUri.getRawQuery() : reqUri.getPath(); - String messageToSign = String.format("%s\n%s", signUrl, timestamp); - return getOauth2Sig(messageToSign, secretKey); - } - - public static RestResult getCall(HttpClient httpClient, String encodedUrl, @Nullable Map headers, - @Nullable Map params) throws IOException { - - Request request = httpClient.newRequest(encodedUrl).method("GET"); - if (params != null) { - params.forEach(request::param); - } - if (headers != null) { - headers.forEach(request::header); - } - - if (logger.isTraceEnabled()) { - logger.trace("GET request: {}", request.getURI()); - } - try { - ContentResponse response = request.send(); - - logger.trace("GET response: {}", response.getContentAsString()); - - return new RestResult(response.getContentAsString(), response.getStatus()); - } catch (InterruptedException | TimeoutException | ExecutionException e) { - logger.error("Exception occurred during GET execution: {}", e.getMessage(), e); - throw new CommunicationException(e); - } - } - - @Nullable - public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, - String jsonData) throws IOException { - try { - return postCall(httpClient, encodedUrl, headers, new StringContentProvider(jsonData)); - } catch (UnsupportedEncodingException e) { - logger.error( - "Unexpected error. Character encoding from json informed not supported by this platform. Payload:{}", - jsonData, e); - throw new IllegalStateException( - "Unexpected error. Character encoding from json informed not supported by this platform.", e); - } - } - - @Nullable - public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, - Map formParams) throws IOException { - Fields fields = new Fields(); - formParams.forEach(fields::put); - try { - return postCall(httpClient, encodedUrl, headers, new FormContentProvider(fields)); - } catch (UnsupportedEncodingException e) { - logger.error( - "Unexpected error. Character encoding received from Form Parameters not supported by this platform. Form Parameters:{}", - formParams, e); - throw new IllegalStateException( - "Unexpected error. Character encoding received from Form Parameters not supported by this platform.", - e); - } - } - - @Nullable - private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, - ContentProvider contentProvider) throws IOException { - - try { - Request request = httpClient.newRequest(encodedUrl).method("POST").content(contentProvider).timeout(10, - TimeUnit.SECONDS); - if (headers != null) { - headers.forEach(request::header); - } - if (logger.isTraceEnabled()) { - logger.trace("POST request: {}", request.getURI()); - } - - ContentResponse response = request.content(contentProvider).timeout(10, TimeUnit.SECONDS).send(); - - logger.trace("POST response: {}", response.getContentAsString()); - - return new RestResult(response.getContentAsString(), response.getStatus()); - } catch (TimeoutException e) { - if (logger.isDebugEnabled()) { - logger.warn("Timeout reading post call result from LG API", e); - } else { - logger.warn("Timeout reading post call result from LG API"); - } - // In SocketTimeout cases I'm considering that I have no response on time. Then, I return null data - // forcing caller to retry. - return null; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - logger.error("InterruptedException occurred during POST execution: {}", e.getMessage(), e); - throw new CommunicationException(e); - } catch (ExecutionException e) { - logger.error("ExecutionException occurred during POST execution: {}", e.getMessage(), e); - throw new CommunicationException(e); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java deleted file mode 100644 index 7346f2fb70326..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenManager.java +++ /dev/null @@ -1,167 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; - -import java.io.File; -import java.io.IOException; -import java.util.Calendar; -import java.util.Date; -import java.util.Map; -import java.util.Objects; -import java.util.concurrent.ConcurrentHashMap; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.internal.errors.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * The {@link TokenManager} Principal facade to manage all token handles - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class TokenManager { - private static final int EXPIRICY_TOLERANCE_SEC = 60; - private static final Logger logger = LoggerFactory.getLogger(TokenManager.class); - private final OauthLgEmpAuthenticator oAuthAuthenticator; - private final ObjectMapper objectMapper = new ObjectMapper(); - private final Map tokenCached = new ConcurrentHashMap<>(); - - public TokenManager(HttpClient httpClient) { - oAuthAuthenticator = new OauthLgEmpAuthenticator(httpClient); - } - - public boolean isTokenExpired(TokenResult token) { - Calendar c = Calendar.getInstance(); - c.setTime(token.getGeneratedTime()); - c.add(Calendar.SECOND, token.getExpiresIn() - EXPIRICY_TOLERANCE_SEC); - Date expiricyDate = c.getTime(); - return expiricyDate.before(new Date()); - } - - @NonNull - public TokenResult refreshToken(String bridgeName, TokenResult currentToken) throws RefreshTokenException { - try { - TokenResult token = oAuthAuthenticator.doRefreshToken(currentToken); - objectMapper.writeValue(new File(getConfigDataFileName(bridgeName)), token); - return token; - } catch (IOException e) { - throw new RefreshTokenException("Error refreshing LGThinq token", e); - } - } - - private String getConfigDataFileName(String bridgeName) { - return String.format(THINQ_CONNECTION_DATA_FILE, bridgeName); - } - - public boolean isOauthTokenRegistered(String bridgeName) { - File tokenFile = new File(getConfigDataFileName(bridgeName)); - // TODO - check if the file content is valid. - return tokenFile.isFile(); - } - - private String getGatewayUrl(String alternativeGtwServer) { - return alternativeGtwServer.isBlank() ? GATEWAY_URL_V2 : (alternativeGtwServer + GATEWAY_SERVICE_PATH_V2); - } - - public void oauthFirstRegistration(String bridgeName, String language, String country, String username, - String password, String alternativeGtwServer) - throws LGThinqGatewayException, PreLoginException, AccountLoginException, TokenException, IOException { - LGThinqGateway gw; - OauthLgEmpAuthenticator.PreLoginResult preLogin; - OauthLgEmpAuthenticator.LoginAccountResult accountLogin; - TokenResult token; - UserInfo userInfo; - try { - gw = oAuthAuthenticator.discoverGatewayConfiguration(getGatewayUrl(alternativeGtwServer), language, country, - alternativeGtwServer); - } catch (Exception ex) { - throw new LGThinqGatewayException( - "Error trying to discovery the LG Gateway Setting for the region informed", ex); - } - - try { - preLogin = oAuthAuthenticator.preLoginUser(gw, username, password); - } catch (Exception ex) { - logger.error("Error pre-login with gateway: {}", gw); - throw new PreLoginException("Error doing pre-login of the user in the Emp LG Server", ex); - } - try { - accountLogin = oAuthAuthenticator.loginUser(gw, preLogin); - } catch (Exception ex) { - logger.error("Error logging with gateway: {}", gw); - throw new AccountLoginException("Error doing user's account login on the Emp LG Server", ex); - } - try { - token = oAuthAuthenticator.getToken(gw, accountLogin); - } catch (Exception ex) { - logger.error("Error getting token with gateway: {}", gw); - throw new TokenException("Error getting Token", ex); - } - try { - userInfo = oAuthAuthenticator.getUserInfo(token); - token.setUserInfo(userInfo); - token.setGatewayInfo(gw); - } catch (Exception ex) { - throw new TokenException("Error getting UserInfo from Token", ex); - } - - // persist the token information generated in file - objectMapper.writeValue(new File(getConfigDataFileName(bridgeName)), token); - } - - public TokenResult getValidRegisteredToken(String bridgeName) throws IOException, RefreshTokenException { - @NonNull - TokenResult validToken; - TokenResult bridgeToken = tokenCached.get(bridgeName); - if (bridgeToken == null) { - bridgeToken = objectMapper.readValue(new File(getConfigDataFileName(bridgeName)), TokenResult.class); - } - - if (!isValidToken(bridgeToken)) { - throw new RefreshTokenException( - "Token is not valid. Try to delete token file and disable/enable bridge to restart authentication process"); - } else { - tokenCached.put(bridgeName, bridgeToken); - } - - validToken = Objects.requireNonNull(bridgeToken, "Unexpected. Never null here"); - if (isTokenExpired(validToken)) { - validToken = refreshToken(bridgeName, validToken); - } - return validToken; - } - - private boolean isValidToken(@Nullable TokenResult token) { - return token != null && !token.getAccessToken().isBlank() && token.getExpiresIn() != 0 - && !token.getOauthBackendUrl().isBlank() && !token.getRefreshToken().isBlank(); - } - - /** - * Remove the toke file registered for the bridge. Must be called only if the bridge is removed - */ - public void cleanupTokenRegistry(String bridgeName) { - File f = new File(getConfigDataFileName(bridgeName)); - if (f.isFile()) { - f.delete(); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java deleted file mode 100644 index 9f678b54f2b28..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/TokenResult.java +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api; - -import java.io.Serializable; -import java.util.Date; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link TokenResult} Hold information about token and related entities - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class TokenResult implements Serializable { - private String accessToken = ""; - private String refreshToken = ""; - private int expiresIn; - private Date generatedTime = new Date(); - private String oauthBackendUrl = ""; - private UserInfo userInfo = new UserInfo(); - private LGThinqGateway gatewayInfo = new LGThinqGateway(); - - public TokenResult(String accessToken, String refreshToken, int expiresIn, Date generatedTime, - String ouathBackendUrl) { - this.accessToken = accessToken; - this.refreshToken = refreshToken; - this.expiresIn = expiresIn; - this.generatedTime = generatedTime; - this.oauthBackendUrl = ouathBackendUrl; - } - - // This constructor will never be called by this. It only exists because of ObjectMapper instantiation needs - public TokenResult() { - } - - public LGThinqGateway getGatewayInfo() { - return gatewayInfo; - } - - public void setGatewayInfo(LGThinqGateway gatewayInfo) { - this.gatewayInfo = gatewayInfo; - } - - public String getAccessToken() { - return accessToken; - } - - public String getRefreshToken() { - return refreshToken; - } - - public int getExpiresIn() { - return expiresIn; - } - - public Date getGeneratedTime() { - return generatedTime; - } - - public String getOauthBackendUrl() { - return oauthBackendUrl; - } - - public void setAccessToken(String accessToken) { - this.accessToken = accessToken; - } - - @SuppressWarnings("It is implicitly used by the ObjectMapper instantiator") - public void setRefreshToken(String refreshToken) { - this.refreshToken = refreshToken; - } - - public void setExpiresIn(int expiresIn) { - this.expiresIn = expiresIn; - } - - public void setGeneratedTime(Date generatedTime) { - this.generatedTime = generatedTime; - } - - @SuppressWarnings("It is implicitly used by the ObjectMapper instantiator") - public void setOauthBackendUrl(String ouathBackendUrl) { - this.oauthBackendUrl = ouathBackendUrl; - } - - public UserInfo getUserInfo() { - return userInfo; - } - - public void setUserInfo(UserInfo userInfo) { - this.userInfo = userInfo; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java deleted file mode 100644 index e783df386503f..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/UserInfo.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api; - -import java.io.Serializable; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link UserInfo} User Info (registered in LG Account) - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class UserInfo implements Serializable { - - private String userNumber = ""; - private String userID = ""; - private String userIDType = ""; - private String displayUserID = ""; - - public UserInfo() { - } - - public UserInfo(String userNumber, String userID, String userIDType, String displayUserId) { - this.userNumber = userNumber; - this.userID = userID; - this.userIDType = userIDType; - this.displayUserID = displayUserId; - } - - public String getUserNumber() { - return userNumber; - } - - public void setUserNumber(String userNumber) { - this.userNumber = userNumber; - } - - public String getUserID() { - return userID; - } - - public void setUserID(String userID) { - this.userID = userID; - } - - public String getUserIDType() { - return userIDType; - } - - public void setUserIDType(String userIDType) { - this.userIDType = userIDType; - } - - public String getDisplayUserID() { - return displayUserID; - } - - public void setDisplayUserID(String displayUserID) { - this.displayUserID = displayUserID; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java deleted file mode 100644 index 77baec9e9393e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/GatewayResult.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link GatewayResult} class - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class GatewayResult extends HeaderResult { - private final String rtiUri; - private final String thinq1Uri; - private final String thinq2Uri; - private final String empUri; - private final String empTermsUri; - private final String oauthUri; - private final String empSpxUri; - - public GatewayResult(String resultCode, String resultMessage, String rtiUri, String thinq1Uri, String thinq2Uri, - String empUri, String empTermsUri, String oauthUri, String empSpxUri) { - super(resultCode, resultMessage); - this.rtiUri = rtiUri; - this.thinq1Uri = thinq1Uri; - this.thinq2Uri = thinq2Uri; - this.empUri = empUri; - this.empTermsUri = empTermsUri; - this.oauthUri = oauthUri; - this.empSpxUri = empSpxUri; - } - - public String getRtiUri() { - return rtiUri; - } - - public String getEmpTermsUri() { - return empTermsUri; - } - - public String getEmpSpxUri() { - return empSpxUri; - } - - public String getThinq1Uri() { - return thinq1Uri; - } - - public String getThinq2Uri() { - return thinq2Uri; - } - - public String getEmpUri() { - return empUri; - } - - public String getOauthUri() { - return oauthUri; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java deleted file mode 100644 index 6ad1f0fec3022..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/api/model/HeaderResult.java +++ /dev/null @@ -1,39 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.api.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link HeaderResult} class - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class HeaderResult { - private final String returnedCode; - private final String returnedMessage; - - public HeaderResult(String returnedCode, String returnedMessage) { - this.returnedCode = returnedCode; - this.returnedMessage = returnedMessage; - } - - public String getReturnedCode() { - return returnedCode; - } - - public String getReturnedMessage() { - return returnedMessage; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java deleted file mode 100644 index 7910c0d6351b2..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/AccountLoginException.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link AccountLoginException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class AccountLoginException extends LGThinqException { - public AccountLoginException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java deleted file mode 100644 index b24673cc9d03d..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiException.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; - -/** - * The {@link LGThinqApiException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqApiException extends LGThinqException { - protected ResultCodes apiReasonCode = ResultCodes.UNKNOWN; - - public LGThinqApiException(String message, Throwable cause) { - super(message, cause); - } - - public LGThinqApiException(String message, Throwable cause, ResultCodes reasonCode) { - super(message, cause); - this.apiReasonCode = reasonCode; - } - - public ResultCodes getApiReasonCode() { - return apiReasonCode; - } - - public LGThinqApiException(String message) { - super(message); - } - - public LGThinqApiException(String message, ResultCodes resultCode) { - super(message); - this.apiReasonCode = resultCode; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java deleted file mode 100644 index d358c22c3d3a2..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqApiExhaustionException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGThinqApiExhaustionException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqApiExhaustionException extends LGThinqException { - public LGThinqApiExhaustionException(String message, Throwable cause) { - super(message, cause); - } - - public LGThinqApiExhaustionException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java deleted file mode 100644 index e0ad5b656b541..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1MonitorExpiredException.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGThinqDeviceV1MonitorExpiredException} - Normally caught by V1 API in monitoring device. - * After long-running moniotor, it indicates the need to refresh the monitor. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqDeviceV1MonitorExpiredException extends LGThinqException { - public LGThinqDeviceV1MonitorExpiredException(String message, Throwable cause) { - super(message, cause); - } - - public LGThinqDeviceV1MonitorExpiredException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java deleted file mode 100644 index 1bdbae5a9cc73..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqDeviceV1OfflineException.java +++ /dev/null @@ -1,33 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGThinqDeviceV1OfflineException} - Normally caught by V1 API in monitoring device. - * When the device is OFFLINE (away from internet), the API doesn't return data information and this - * exception is thrown to indicate that this device is offline for monitoring - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqDeviceV1OfflineException extends LGThinqApiException { - public LGThinqDeviceV1OfflineException(String message, Throwable cause) { - super(message, cause); - } - - public LGThinqDeviceV1OfflineException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java deleted file mode 100644 index 531f602f2ae34..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGThinqException} Parent Exception for all exceptions of this module - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqException extends Exception { - public LGThinqException(String message, Throwable cause) { - super(message, cause); - } - - public LGThinqException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java deleted file mode 100644 index e2f403d906c79..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqGatewayException.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGThinqGatewayException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqGatewayException extends LGThinqException { - public LGThinqGatewayException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java deleted file mode 100644 index e6346ad1ff636..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/LGThinqUnmarshallException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link LGThinqUnmarshallException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinqUnmarshallException extends LGThinqException { - public LGThinqUnmarshallException(String message, Throwable cause) { - super(message, cause); - } - - public LGThinqUnmarshallException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java deleted file mode 100644 index f28d666548cbc..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/PreLoginException.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link PreLoginException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class PreLoginException extends LGThinqException { - public PreLoginException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java deleted file mode 100644 index 03d018234ea18..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/RefreshTokenException.java +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link PreLoginException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class RefreshTokenException extends LGThinqApiException { - public RefreshTokenException(String message, Throwable cause) { - super(message, cause); - } - - public RefreshTokenException(String message) { - super(message); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java deleted file mode 100644 index a844e87b095e6..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/TokenException.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link PreLoginException} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class TokenException extends LGThinqException { - public TokenException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java deleted file mode 100644 index a8bf5fea9d2ec..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/errors/UserInfoException.java +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.errors; - -/** - * The {@link PreLoginException} - * - * @author Nemer Daud - Initial contribution - */ -public class UserInfoException extends LGThinqException { - public UserInfoException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java deleted file mode 100644 index da36f84ed8141..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThingModelTypeUtils.java +++ /dev/null @@ -1,269 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.type; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.*; -import static org.openhab.core.thing.DefaultSystemChannelTypeProvider.SYSTEM_POWER; - -import java.math.BigDecimal; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.*; - -import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.model.*; -import org.openhab.core.config.core.*; -import org.openhab.core.library.CoreItemFactory; -import org.openhab.core.thing.Thing; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.type.*; -import org.openhab.core.types.StateDescriptionFragmentBuilder; -import org.osgi.service.component.annotations.Reference; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * The {@link ThingModelTypeUtils} class. - * - * @author Nemer Daud - Initial contribution - */ -// @Component -public class ThingModelTypeUtils { - private static final Logger logger = LoggerFactory.getLogger(ThingModelTypeUtils.class); - - private ThinqThingTypeProvider thingTypeProvider; - private ThinqChannelTypeProvider channelTypeProvider; - private ThinqChannelGroupTypeProvider channelGroupTypeProvider; - private ThinqConfigDescriptionProvider configDescriptionProvider; - private LGThinQStateDescriptionProvider stateDescriptionProvider; - - @Reference - public void setThingTypeProvider(ThinqThingTypeProvider thingTypeProvider) { - this.thingTypeProvider = thingTypeProvider; - } - - @Reference - public void setChannelTypeProvider(ThinqChannelTypeProvider channelTypeProvider) { - this.channelTypeProvider = channelTypeProvider; - } - - @Reference - public void setChannelGroupTypeProvider(ThinqChannelGroupTypeProvider channelGroupTypeProvider) { - this.channelGroupTypeProvider = channelGroupTypeProvider; - } - - @Reference - public void setConfigDescriptionProvider(ThinqConfigDescriptionProvider configDescriptionProvider) { - this.configDescriptionProvider = configDescriptionProvider; - } - - @Reference - public void setStateDescriptionProvider(LGThinQStateDescriptionProvider stateDescriptionProvider) { - this.stateDescriptionProvider = stateDescriptionProvider; - } - - protected ChannelTypeUID createDynTypeChannel(final String channelTypeId, final String channelLabel, - final String itemType, final Boolean readOnly) { - final StateDescriptionFragmentBuilder sdb = StateDescriptionFragmentBuilder.create(); - final ChannelTypeUID channelTypeUID = new ChannelTypeUID(BINDING_ID, channelTypeId + "-Type"); - String normLabel = channelLabel.replace(" ", ""); - final ChannelType channelType = ChannelTypeBuilder.state(channelTypeUID, normLabel, itemType) - .withStateDescriptionFragment(sdb.withReadOnly(readOnly).build()) - .withConfigDescriptionURI(URI.create(String.format("channel-type:lgthinq:%s-type", normLabel))).build(); - channelTypeProvider.addChannelType(channelType); - return channelTypeUID; - } - - @NonNull - private URI getConfigDescriptionURI(ThinqDevice device) { - try { - return new URI(String.format("%s:%s", "thing-type", UidUtils.generateThingTypeUID(device))); - } catch (URISyntaxException ex) { - String msg = String.format("Can't create configDescriptionURI for device type %s", device.getType()); - throw new IllegalStateException(msg, ex); - } - } - - protected ChannelGroupTypeUID createAndRegistryGroupTypeChannel(final ThinqChannelGroup channelGroup, - final List channelDefinitions) { - ChannelGroupTypeUID groupTypeUID = UidUtils.generateChannelGroupTypeUID(channelGroup); - ChannelGroupType groupType = channelGroupTypeProvider.getChannelGroupType(groupTypeUID, Locale.getDefault()); - if (groupType == null) { - - groupType = ChannelGroupTypeBuilder.instance(groupTypeUID, channelGroup.getLabel()) - .withChannelDefinitions(channelDefinitions).build(); - channelGroupTypeProvider.addChannelGroupType(groupType); - } - - return groupTypeUID; - } - - public void generate(ThinqDevice device) { - if (thingTypeProvider != null) { - ThingTypeUID thingTypeUID = UidUtils.generateThingTypeUID(device); - ThingType thingType = thingTypeProvider.getThingType(thingTypeUID, Locale.getDefault()); - if (thingType == null) { - HashMap> groupChannelsMap = new HashMap<>(); - logger.debug("Generating ThingType for device '{}' with {} channels", device.getType(), - device.getChannels().size()); - device.getChannels().forEach(c -> { - // Only generate Channel that is not dynamic. Dyn channel will be created depending on the - // thing handler decision. - if (!c.isDynamic()) { - // generate channel - ChannelTypeUID channelTypeUID = UidUtils.generateChannelTypeUID(c); - ChannelType channelType = channelTypeProvider.getChannelType(channelTypeUID, - Locale.getDefault()); - if (channelType == null) { - channelType = createChannelType(c, channelTypeUID); - channelTypeProvider.addChannelType(channelType); - } - - ChannelDefinition channelDef = new ChannelDefinitionBuilder(c.getName(), channelType.getUID()) - .build(); - groupChannelsMap.computeIfAbsent(c.getChannelGroup(), k -> new ArrayList<>()).add(channelDef); - } - }); - groupChannelsMap.forEach(this::createAndRegistryGroupTypeChannel); - } - thingType = createThingType(device, channelGroupTypeProvider.internalGroupTypes()); - thingTypeProvider.addThingType(thingType); - } - } - - private ThingType createThingType(ThinqDevice device, List groupTypes) { - String label = device.getLabel(); - String description = device.getDescription(); - - List supportedBridgeTypeUids = List.of(THING_TYPE_BRIDGE.toString()); - ThingTypeUID thingTypeUID = UidUtils.generateThingTypeUID(device); - - Map properties = new HashMap<>(); - properties.put(Thing.PROPERTY_VENDOR, PROPERTY_VENDOR_NAME); - properties.put(Thing.PROPERTY_MODEL_ID, device.getType()); - - URI configDescriptionURI = getConfigDescriptionURI(device); - if (configDescriptionProvider.getConfigDescription(configDescriptionURI, Locale.getDefault()) == null) { - generateConfigDescription(device, configDescriptionURI); - } - - List groupDefinitions = new ArrayList<>(); - for (ChannelGroupType groupType : groupTypes) { - int usPos = groupType.getUID().getId().lastIndexOf("_"); - String id = usPos == -1 ? groupType.getUID().getId() : groupType.getUID().getId().substring(usPos + 1); - groupDefinitions.add(new ChannelGroupDefinition(id, groupType.getUID())); - } - - return ThingTypeBuilder.instance(thingTypeUID, label).withSupportedBridgeTypeUIDs(supportedBridgeTypeUids) - .withDescription(description).withChannelGroupDefinitions(groupDefinitions).withProperties(properties) - .withRepresentationProperty(Thing.PROPERTY_SERIAL_NUMBER).withConfigDescriptionURI(configDescriptionURI) - .build(); - } - - public void generateConfigDescription(ThinqDevice device, URI configDescriptionURI) { - List params = new ArrayList<>(); - List groups = new ArrayList<>(); - - for (DeviceParameter param : device.getConfigParameter()) { - String groupName = null; - if (param.getGroup() != null) { - groupName = param.getGroup().getGroupName(); - groups.add(ConfigDescriptionParameterGroupBuilder.create(groupName) - .withLabel(param.getGroup().getGroupLabel()).build()); - } - ConfigDescriptionParameterBuilder builder = ConfigDescriptionParameterBuilder.create(param.getName(), - param.getType()); - builder.withLabel(param.getLabel()); - builder.withDefault(param.getDefaultValue()); - builder.withDescription(param.getDescription()); - builder.withReadOnly(param.isReadOnly()); - if (param.getOptions() != null) - builder.withOptions(param.getOptions()); - - builder.withGroupName(groupName); - params.add(builder.build()); - } - configDescriptionProvider.addConfigDescription(ConfigDescriptionBuilder.create(configDescriptionURI) - .withParameters(params).withParameterGroups(groups).build()); - } - - private ChannelType createChannelType(ThinqChannel channel, ChannelTypeUID channelTypeUID) { - /* - * - * - * - * - * - * - * - * - * - */ - DataType dataType = channel.getType(); - if (dataType.getName().equals("system.power")) { - return SYSTEM_POWER; - } else { - String itemType = dataType.getName(); - StateDescriptionFragmentBuilder stateFragment = StateDescriptionFragmentBuilder.create(); - if (channel.getUnitDisplayPattern() != null) { - stateFragment.withPattern(Objects.requireNonNull(channel.getUnitDisplayPattern())); - } - stateFragment.withReadOnly(channel.isReadOnly()); - - if (dataType.isNumeric()) { - final BigDecimal min, max; - if (CoreItemFactory.DIMMER.equals(itemType) || CoreItemFactory.ROLLERSHUTTER.equals(itemType)) { - // those types use PercentTypeConverter, so set up min and max as percent values - min = BigDecimal.ZERO; - max = new BigDecimal(100); - stateFragment.withMinimum(min).withMaximum(max); - } - } else if (dataType.isEnum() && dataType.getOptions() != null) { - stateFragment.withOptions(Objects.requireNonNull(dataType.getOptions())); - } - - String label = channel.getLabel(); - URI configUriDescriptor; - try { - configUriDescriptor = new URI(CONFIG_DESCRIPTION_URI_CHANNEL); - } catch (URISyntaxException e) { - throw new IllegalStateException( - "Error creating URI configuration for a Thinq channel. It's most likely a bug.", e); - } - final ChannelTypeBuilder channelTypeBuilder; - channelTypeBuilder = ChannelTypeBuilder.state(channelTypeUID, label, itemType) - .withStateDescriptionFragment(stateFragment.build()).isAdvanced(channel.isAdvanced()) - .withDescription(channel.getDescription()).withConfigDescriptionURI(configUriDescriptor); - String category = discoverCategory(channel); - if (category != null) { - channelTypeBuilder.withCategory(category); - } - return channelTypeBuilder.build(); - } - } - - @Nullable - private String discoverCategory(ThinqChannel c) { - switch (c.getType().getName()) { - case "washer-temp-level": - return "Temperature"; - case "washerdryer-delay-time": - case "washerdryer-remain-time": - return "Time"; - default: - return null; - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java deleted file mode 100644 index 672caea04be9b..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/dishwasher/DishWasherCapabilityFactoryV1.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.lgservices.model.devices.dishwasher; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.binding.lgthinq.internal.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.*; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.JsonNode; - -/** - * The {@link DishWasherCapabilityFactoryV1} - Not implemented - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public abstract class DishWasherCapabilityFactoryV1 extends AbstractDishWasherCapabilityFactory { - private static final Logger logger = LoggerFactory.getLogger(DishWasherCapabilityFactoryV1.class); - - @Override - protected String getStateFeatureNodeName() { - return "State"; - } - - @Override - protected String getProcessStateNodeName() { - return "PreState"; - } - - @Override - protected String getDoorLockFeatureNodeName() { - return "Door"; - } - - @Override - protected MonitoringResultFormat getMonitorDataFormat(JsonNode rootNode) { - String type = rootNode.path("Monitoring").path("type").textValue(); - return MonitoringResultFormat.getFormatOf(Objects.requireNonNullElse(type, "")); - } - - @Override - protected Map getCommandsDefinition(JsonNode rootNode) throws LGThinqApiException { - return getCommandsDefinitionV1(rootNode); - } - - @Override - protected String getNotSelectedCourseKey() { - return "0"; - } - - @Override - protected List getSupportedAPIVersions() { - return List.of(LGAPIVerion.V1_0); - } - - @Override - protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, - @Nullable String targetChannelId, @Nullable String refChannelId) { - JsonNode featureNode = featuresNode.path(featureName); - if (featureNode.isMissingNode()) { - return FeatureDefinition.NULL_DEFINITION; - } - FeatureDefinition fd = new FeatureDefinition(); - fd.setName(featureName); - fd.setLabel(featureName); - fd.setChannelId(Objects.requireNonNullElse(targetChannelId, "")); - fd.setRefChannelId(Objects.requireNonNullElse(refChannelId, "")); - // All features from V1 are ENUMs - fd.setDataType(FeatureDataType.ENUM); - JsonNode valuesMappingNode = featureNode.path("option"); - if (!valuesMappingNode.isMissingNode()) { - - Map valuesMapping = new HashMap<>(); - valuesMappingNode.fields().forEachRemaining(e -> { - // collect values as: - // - // "option":{ - // "0":"@WM_STATE_POWER_OFF_W", - // to "0" -> "@WM_STATE_POWER_OFF_W" - valuesMapping.put(e.getKey(), e.getValue().asText()); - }); - fd.setValuesMapping(valuesMapping); - } - - return fd; - } - - @Override - public DishWasherCapability getCapabilityInstance() { - return new DishWasherCapability(); - } - - @Override - protected String getMonitorValueNodeName() { - return "Value"; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml index ff62e95d4f665..21630ed22034a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml @@ -19,6 +19,7 @@ + @@ -36,6 +37,7 @@ + @@ -46,11 +48,11 @@ - Fill this only if selected "Other" in the Language above + Fill this only if selected "Other" in the Language above. Example value: de-DE - Fill this only if selected "Other" in the Country above + Fill this only if selected "Other" in the Country above. Example value: "DE" diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java deleted file mode 100644 index 22614b9ce2f18..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandlerTest.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.handler; - -import org.junit.jupiter.api.Test; -import org.openhab.core.library.types.DateTimeType; - -/** - * The LGThinQWasherDryerHandlerTest test class. - * - * @author Nemer Daud - Initial contribution - */ -class LGThinQWasherDryerHandlerTest { - - @Test - void updateDeviceChannels() { - String time = String.format("%02.0f:%02.0f", 0.00, 0.0); - DateTimeType dt = new DateTimeType(time); - } -} From 032ae447c05a05b799270a4babdc02363977be4f Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sun, 3 Nov 2024 23:35:45 -0300 Subject: [PATCH 121/130] [lgthinq][fix] fix code revision list Signed-off-by: Nemer Daud --- .../src/main/feature/feature.xml | 1 - .../internal/LGThinQBindingConstants.java | 3 ++ .../internal/LGThinQHandlerFactory.java | 2 +- .../discovery/LGThinqDiscoveryService.java | 2 +- .../handler/LGThinQAbstractDeviceHandler.java | 33 ++++++-------- .../handler/LGThinQAirConditionerHandler.java | 6 +-- .../handler/LGThinQBridgeHandler.java | 6 +-- .../handler/LGThinQDishWasherHandler.java | 2 +- .../handler/LGThinQFridgeHandler.java | 4 +- .../handler/LGThinQWasherDryerHandler.java | 4 +- .../lgthinq/internal/model/ThinqChannel.java | 6 ++- .../LGThinQACApiV2ClientServiceImpl.java | 5 --- .../LGThinQAbstractApiClientService.java | 45 ++++++++----------- .../LGThinQAbstractApiV1ClientService.java | 15 ++----- .../LGThinQAbstractApiV2ClientService.java | 8 +--- .../LGThinQWMApiV2ClientServiceImpl.java | 1 - ...java => LGThinqOauthEmpAuthenticator.java} | 37 +++++++-------- .../lgthinq/lgservices/api/RestUtils.java | 34 ++++++-------- .../lgthinq/lgservices/api/TokenManager.java | 27 +++++------ .../model/AbstractCapabilityFactory.java | 5 +-- .../AbstractFridgeCapabilityFactory.java | 3 +- .../resources/OH-INF/i18n/lgthinq.properties | 2 + .../binding/lgthinq/handler/JsonUtils.java | 14 ------ 23 files changed, 104 insertions(+), 161 deletions(-) rename bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/{OauthLgEmpAuthenticator.java => LGThinqOauthEmpAuthenticator.java} (91%) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml index d6b28b83ee2e5..7c617dee5c6f1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/feature/feature.xml @@ -4,7 +4,6 @@ openhab-runtime-base - mvn:org.openhab.addons.bundles/org.openhab.binding.lgthinq/${project.version} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index cd5ee2cc693b2..77dfac20b91ab 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -139,4 +139,7 @@ public class LGThinQBindingConstants extends LGServicesConstants { public static final String CHANNEL_WM_REMOTE_START_TEMP = "rs-temperature-level"; // ============================================================================== + // DIGEST CONSTANTS + public static final String MESSAGE_DIGEST_ALGORITHM = "SHA-512"; + public static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index 548494e0301ac..1c958820414a2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -111,7 +111,7 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } - logger.error("Thing not supported by this Factory: {}", thingTypeUID.getId()); + logger.warn("Thing not supported by this Factory: {}", thingTypeUID.getId()); return null; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index 39fb483e893a0..9a7f8c8e324cd 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -96,7 +96,7 @@ public void removeLgDeviceDiscovery(LGDevice device) { ThingUID thingUID = getThingUID(device); thingRemoved(thingUID); } catch (LGThinqException e) { - logger.error("Error getting Thing UID"); + logger.warn("Error getting Thing UID"); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 9f0012d5da093..854cecaeb5be2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -259,18 +259,21 @@ public void handleCommand(ChannelUID channelUID, Command command) { // Ensure commands are send in a pipe per device. commandBlockQueue.add(params); } catch (IllegalStateException ex) { - getLogger().error( - "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command. Above the ThreadDump to analise the stuck"); - getLogger().error("Status of the commandQueue: consumer: {}, size: {}", + getLogger().warn( + "Device's command queue reached the size limit. Probably the device is busy ou stuck. Ignoring command."); + getLogger().debug("Status of the commandQueue: consumer: {}, size: {}", commandExecutorQueueJob == null || commandExecutorQueueJob.isDone() ? "OFF" : "ON", commandBlockQueue.size()); - ThreadMXBean bean = ManagementFactory.getThreadMXBean(); - ThreadInfo[] infos = bean.dumpAllThreads(true, true); - String message = ""; - for (ThreadInfo i : infos) { - message = String.format("%s\n%s", message, i.toString()); + if (logger.isTraceEnabled()) { + // logging the thread dump to analise possible stuck thread. + ThreadMXBean bean = ManagementFactory.getThreadMXBean(); + ThreadInfo[] infos = bean.dumpAllThreads(true, true); + String message = ""; + for (ThreadInfo i : infos) { + message = String.format("%s\n%s", message, i.toString()); + } + logger.trace("{}", message); } - logger.error("{}", message); updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.COMMUNICATION_ERROR, "Device Command Queue is Busy"); } @@ -385,22 +388,14 @@ protected void initializeThing(@Nullable ThingStatus bridgeStatus) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "@text/offline.conf-error-no-device-id"); } - // finally, start command queue, regardless of the thing state, as we can still try to send commands without + // finally, start command queue, regardless of the thing state, since we can still try to send commands without // property ONLINE (the successful result from command request can put the thing in ONLINE status). startCommandExecutorQueueJob(); if (getThing().getStatus() == ThingStatus.ONLINE) { try { getLgThinQAPIClientService().initializeDevice(bridgeId, getDeviceId()); } catch (Exception e) { - if (logger.isDebugEnabled()) { - logger.error( - "Error initializing device {} from bridge {}. Is the device support pre-condition setup ?", - thingId, bridgeId, e); - } else { - logger.error( - "Error initializing device {} from bridge {}. Is the device support pre-condition setup ?", - getDeviceId(), bridgeId); - } + logger.warn("Error initializing the device {} from bridge {}.", thingId, bridgeId, e); } // force start state pooling if the device is ONLINE resetExtraInfoChannels(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 4f54de57805b5..2cbe16c5a1e9e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -229,7 +229,7 @@ protected void updateDeviceChannels(ACCanonicalSnapshot shot) { maxTempConstraint = shot.getHpWaterTempHeatMax(); } } else { - logger.error("Invalid value received by HP snapshot fo the air/water switch property: {}", + logger.warn("Invalid value received by HP snapshot for the air/water switch property: {}", shot.getHpAirWaterTempSwitch()); } updateState(minTempChannelUID, new DecimalType(BigDecimal.valueOf(minTempConstraint))); @@ -431,7 +431,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept // analise temperature constraints if (targetTemp > maxTempConstraint || targetTemp < minTempConstraint) { // values out of range - logger.error("Target Temperature: {} is out of range: {} - {}. Ignoring command", targetTemp, + logger.warn("Target Temperature: {} is out of range: {} - {}. Ignoring command", targetTemp, minTempConstraint, maxTempConstraint); break; } @@ -443,7 +443,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept break; } default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + logger.warn("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index 5bc28f7d1cff0..2ca83bc10a726 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -122,7 +122,7 @@ public void run() { try { tokenManager.getValidRegisteredToken(bridgeName); } catch (IOException e) { - logger.error("Error reading LGThinq TokenFile", e); + logger.error("Unexpected error reading LGThinq TokenFile", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "@text/error.toke-file-corrupted"); return; @@ -159,7 +159,7 @@ public void run() { try { doConnectedRun(); } catch (Exception e) { - logger.error("Error getting device list from LG account", e); + logger.error("Unexpected error getting device list from LG account", e); updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "@text/error.lgapi-getting-devices"); } @@ -317,7 +317,7 @@ public void handleConfigurationUpdate(Map configurationParameter if (f.isFile()) { // file exists. Delete it if (!f.delete()) { - logger.error("Error deleting file:{}", f.getAbsolutePath()); + logger.error("Unexpected error deleting file:{}", f.getAbsolutePath()); } } super.handleConfigurationUpdate(configurationParameters); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java index 56cee065e59bc..ee5bcb499addb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -157,7 +157,7 @@ protected DeviceTypes getDeviceType() { @Override protected void processCommand(AsyncCommandParams params) { - logger.error("Command {} to the channel {} not supported. Ignored.", params.command, params.channelUID); + logger.warn("Command {} to the channel {} not supported. Ignored.", params.command, params.channelUID); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index 8a26631187f13..567c1d255db26 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -174,7 +174,7 @@ protected void updateDeviceChannels(FridgeCanonicalSnapshot shot) { // force update states after first snapshot fetched to fit changes in temperature unit updateChannelDynStateDescription(); } catch (Exception ex) { - logger.error("Error updating dynamic state description", ex); + logger.error("Unexpected error updating dynamic state description.", ex); } } } @@ -411,7 +411,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept break; } default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + logger.warn("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index ae835be84455a..ce5f44ffbfe7c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -334,7 +334,7 @@ private Map getRemoteStartData() throws LGThinqApiException { } String selectedCourse = getItemLinkedValue(remoteStartCourseChannelUID); if (selectedCourse == null) { - logger.error("Remote Start Channel must be linked to proceed with remote start."); + logger.warn("Remote Start Channel must be linked to proceed with remote start."); return Collections.emptyMap(); } WasherDryerCapability cap = getCapabilities(); @@ -421,7 +421,7 @@ protected void processCommand(LGThinQAbstractDeviceHandler.AsyncCommandParams pa break; } default: { - logger.error("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); + logger.warn("Command {} to the channel {} not supported. Ignored.", command, params.channelUID); } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java index 8546c73857e0c..980f534e459d9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java @@ -41,10 +41,12 @@ public class ThinqChannel { @Override public boolean equals(@Nullable Object o) { - if (this == o) + if (this == o) { return true; - if (o == null || getClass() != o.getClass()) + } + if (o == null || getClass() != o.getClass()) { return false; + } ThinqChannel that = (ThinqChannel) o; return Objects.equals(name, that.name); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java index 7d0f08c2146bb..585bce9d1dd27 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiV2ClientServiceImpl.java @@ -174,11 +174,6 @@ public void stopMonitor(String bridgeName, String deviceId, String workId) { throw new UnsupportedOperationException("Not supported in V2 API."); } - @Override - public void initializeDevice(String bridgeName, String deviceId) throws LGThinqApiException { - super.initializeDevice(bridgeName, deviceId); - } - @Override protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { try { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index d00c8fe744b64..07e0ae92e6b7e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -151,7 +151,6 @@ public File loadDeviceCapability(String deviceId, String uri, boolean forceRecre } } } catch (IOException e) { - logger.error("Error reading resource from URI: {}", uri, e); throw new LGThinqApiException("Error reading IO interface", e); } return regFile; @@ -201,16 +200,13 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Ob }); resultCode = respMap.get("resultCode"); if (resultCode != null) { - LGThinQAbstractApiClientService.logger.error( - "Error calling device settings from LG Server API. The code is: {} and The reason is: {}", - resultCode, ResultCodes.fromCode(resultCode)); - throw new LGThinqApiException("Error calling device settings from LG Server API."); + throw new LGThinqApiException(String.format( + "Error calling device settings from LG Server API. The code is: %s and The reason is: %s", + resultCode, ResultCodes.fromCode(resultCode))); } } catch (JsonProcessingException e) { // This exception doesn't matter, it's because response is not in json format. Logging raw response. } - LGThinQAbstractApiClientService.logger.error( - "Error calling device settings from LG Server API. The reason is:{}", resp.getJsonResponse()); throw new LGThinqApiException(String.format( "Error calling device settings from LG Server API. The reason is:%s", resp.getJsonResponse())); @@ -220,12 +216,9 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Ob }); String code = Objects.requireNonNullElse((String) deviceSettings.get("resultCode"), ""); if (!ResultCodes.OK.containsResultCode(code)) { - LGThinQAbstractApiClientService.logger.error( - "LG API report error processing the request -> resultCode=[{}], message=[{}]", code, - getErrorCodeMessage(code)); - throw new LGThinqApiException( - String.format("Status error getting device list. resultCode must be 0000, but was:%s", - deviceSettings.get("resultCode"))); + throw new LGThinqApiException(String.format( + "LG API report error processing the request -> resultCode=[{%s], message=[%s]", code, + getErrorCodeMessage(code))); } } catch (JsonProcessingException e) { throw new IllegalStateException("Unknown error occurred deserializing json stream", e); @@ -245,21 +238,18 @@ private List handleListAccountDevicesResult(RestResult resp) throws LG resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); return Collections.emptyList(); } - logger.error("Error calling device list from LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); - throw new LGThinqApiException(String - .format("Error calling device list from LG Server API. The reason is: %s", resp.getJsonResponse())); + throw new LGThinqApiException( + String.format("Error calling device list from LG Server API. HTTP Status: %s. The reason is: %s", + resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse()))); } else { try { devicesResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); String code = Objects.requireNonNullElse((String) devicesResult.get("resultCode"), ""); if (!ResultCodes.OK.containsResultCode(code)) { - logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, - getErrorCodeMessage(code)); throw new LGThinqApiException( - String.format("Status error getting device list. resultCode must be 0000, but was:%s", - devicesResult.get("resultCode"))); + String.format("LG API report error processing the request -> resultCode=[%s], message=[%s]", + code, getErrorCodeMessage(code))); } List> items = (List>) ((Map) Objects .requireNonNull(devicesResult.get("result"), "Not expected null here")).get("item"); @@ -311,7 +301,6 @@ public S buildDefaultOfflineSnapshot() { shot.setOnline(false); return shot; } catch (Exception ex) { - logger.error("Unexpected Error. The default constructor of this Snapshot wasn't found", ex); throw new IllegalStateException("Unexpected Error. The default constructor of this Snapshot wasn't found", ex); } @@ -346,7 +335,8 @@ public S buildDefaultOfflineSnapshot() { LGThinQBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", deviceId)), workList); } catch (IOException e) { - logger.error("Error saving data trace", e); + // Only debug since datatrace is a trace data. + logger.debug("Unexpected error saving data trace", e); } } @@ -371,8 +361,8 @@ public S buildDefaultOfflineSnapshot() { shot = (S) SnapshotBuilderFactory.getInstance().getBuilder(snapshotClass).createFromBinary(monData, deviceCapability.getMonitoringBinaryProtocol(), deviceCapability); } else { - logger.error("Returned data format not supported: {}", deviceCapability.getMonitoringDataFormat()); - throw new LGThinqApiException("Returned data format not supported"); + throw new LGThinqApiException(String.format("Returned data format not supported: %s", + deviceCapability.getMonitoringDataFormat())); } shot.setOnline("E".equals(workList.get("deviceState"))); } catch (LGThinqUnmarshallException ex) { @@ -390,13 +380,14 @@ public S buildDefaultOfflineSnapshot() { @Override public void initializeDevice(String bridgeName, String deviceId) throws LGThinqApiException { + logger.debug("Initializing device [{}] from bridge [{}]", deviceId, bridgeName); } /** * Perform some routine before getting data device. Depending on the kind of the device, this is needed * to update or prepare some informations before go to get the data. * - * @return + * @return false if the device doesn't support pre-condition commands */ protected abstract boolean beforeGetDataDevice(String bridgeName, String deviceId); @@ -427,7 +418,7 @@ public S getDeviceData(String bridgeName, String deviceId, CapabilityDefinition LGThinQBindingConstants.THINQ_USER_DATA_FOLDER + File.separator + "thinq-%s-datatrace.json", deviceId)), deviceSettings); } catch (IOException e) { - logger.error("Error saving data trace", e); + logger.debug("Error saving data trace", e); } } if (snapMap == null) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index ca0fb30732d7c..fc7041b303685 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -146,16 +146,9 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp } if (resp.getStatusCode() != 200) { if (resp.getStatusCode() == 400) { - if (logger.isDebugEnabled()) { - logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}\n {}", - resp.getStatusCode(), resp.getJsonResponse(), Thread.currentThread().getStackTrace()); - } else { - logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), resp.getJsonResponse()); - } + logger.warn("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), + resp.getJsonResponse()); } else { - logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), resp.getJsonResponse()); throw new LGThinqApiException( String.format("Error returned by LG Server API. HTTP Status: %s. The reason is: %s", resp.getStatusCode(), resp.getJsonResponse())); @@ -178,8 +171,6 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp // Disconnected Device throw new LGThinqDeviceV1OfflineException("Device is offline. No data available"); } - logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, - getErrorCodeMessage(code)); throw new LGThinqApiException(String .format("Status error executing endpoint. resultCode must be 0000, but was:%s", code)); } @@ -197,7 +188,7 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp * @param cmdDef command definition with template of the payload and data (binary or not) * @param snapData snapshot data with features to be set in the device * @return return the command structure. - * @throws JsonProcessingException + * @throws JsonProcessingException - unmarshall error. */ protected Map prepareCommandV1(CommandDefinition cmdDef, Map snapData) throws JsonProcessingException { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java index 348d4f3b580a0..83273197b93ed 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -109,8 +109,6 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp } return Collections.emptyMap(); } else { - logger.error("Error returned by LG Server API. HTTP Status: {}. The reason is: {}", - resp.getStatusCode(), resp.getJsonResponse()); throw new LGThinqApiException( String.format("Error returned by LG Server API. HTTP Status: %s. The reason is: %s", resp.getStatusCode(), resp.getJsonResponse())); @@ -121,11 +119,9 @@ protected Map handleGenericErrorResult(@Nullable RestResult resp }); String code = (String) metaResult.get("resultCode"); if (!ResultCodes.OK.containsResultCode(String.valueOf(metaResult.get("resultCode")))) { - logger.error("LG API report error processing the request -> resultCode=[{}], message=[{}]", code, - getErrorCodeMessage(code)); throw new LGThinqApiException( - String.format("Status error executing endpoint. resultCode must be 0000, but was:%s", - metaResult.get("resultCode"))); + String.format("LG API report error processing the request -> resultCode=[%s], message=[%s]", + code, getErrorCodeMessage(code))); } return metaResult; } catch (JsonProcessingException e) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java index e2798115c9e8f..8444019dbb8b8 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiV2ClientServiceImpl.java @@ -44,7 +44,6 @@ protected LGThinQWMApiV2ClientServiceImpl(HttpClient httpClient) { @Override protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { - // there's no before settings to send command return false; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/OauthLgEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java similarity index 91% rename from bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/OauthLgEmpAuthenticator.java rename to bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java index f77c073707da3..c281a7526a48a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/OauthLgEmpAuthenticator.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java @@ -60,14 +60,14 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link OauthLgEmpAuthenticator} main service to authenticate against LG Emp Server via Oauth + * The {@link LGThinqOauthEmpAuthenticator} main service to authenticate against LG Emp Server via Oauth * * @author Nemer Daud - Initial contribution */ @NonNullByDefault -public class OauthLgEmpAuthenticator { +public class LGThinqOauthEmpAuthenticator { - private static final Logger logger = LoggerFactory.getLogger(OauthLgEmpAuthenticator.class); + private static final Logger logger = LoggerFactory.getLogger(LGThinqOauthEmpAuthenticator.class); private static final Map oauthSearchKeyQueryParams = new LinkedHashMap<>(); private static final ObjectMapper objectMapper = new ObjectMapper(); @@ -78,7 +78,7 @@ public class OauthLgEmpAuthenticator { private final HttpClient httpClient; - public OauthLgEmpAuthenticator(HttpClient httpClient) { + public LGThinqOauthEmpAuthenticator(HttpClient httpClient) { this.httpClient = httpClient; } @@ -155,11 +155,10 @@ public PreLoginResult preLoginUser(LGThinqGateway gw, String username, String pa username)); RestResult resp = RestUtils.postCall(httpClient, preLoginUrl, headers, formData); if (resp == null) { - logger.error("Error preLogin into account. Null data returned"); throw new IllegalStateException("Error login into account. Null data returned"); } else if (resp.getStatusCode() != 200) { - logger.error("Error preLogin into account. The reason is:{}", resp.getJsonResponse()); - throw new IllegalStateException(String.format("Error login into account:%s", resp.getJsonResponse())); + throw new IllegalStateException( + String.format("Error preLogin into account: The reason is: %s", resp.getJsonResponse())); } Map preLoginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { @@ -187,11 +186,10 @@ public LoginAccountResult loginUser(LGThinqGateway gw, PreLoginResult preLoginRe + URLEncoder.encode(preLoginResult.username(), StandardCharsets.UTF_8); RestResult resp = RestUtils.postCall(httpClient, loginUrl, headers, formData); if (resp == null) { - logger.error("Error login into account. Null data returned"); throw new IllegalStateException("Error loggin into acccount. Null data returned"); } else if (resp.getStatusCode() != 200) { - logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); - throw new IllegalStateException(String.format("Error login into account:%s", resp.getJsonResponse())); + throw new IllegalStateException( + String.format("Error login into account. The reason is: %s", resp.getJsonResponse())); } Map loginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); @@ -224,8 +222,8 @@ TokenResult getToken(LGThinqGateway gw, LoginAccountResult accountResult) throws RestResult resp = RestUtils.getCall(httpClient, empSearchKeyUrl, null, oauthSearchKeyQueryParams); if (resp.getStatusCode() != 200) { - logger.error("Error login into account. The reason is:{}", resp.getJsonResponse()); - throw new IllegalStateException(String.format("Error loggin into acccount:%s", resp.getJsonResponse())); + throw new IllegalStateException( + String.format("Error loggin into acccount. The reason is:%s", resp.getJsonResponse())); } Map secretResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); @@ -289,8 +287,6 @@ private UserInfo handleAccountInfoResult(RestResult resp) throws IOException { Map result = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); if (resp.getStatusCode() != 200) { - logger.error("LG API returned error when trying to get user account information. The reason is:{}", - resp.getJsonResponse()); throw new IllegalStateException( String.format("LG API returned error when trying to get user account information. The reason is:%s", resp.getJsonResponse())); @@ -335,9 +331,9 @@ private TokenResult handleTokenResult(@Nullable RestResult resp) throws IOExcept throw new IllegalStateException("Error getting oauth token. Null data returned"); } if (resp.getStatusCode() != 200) { - logger.error("Error getting oauth token. HTTP Status Code is:{}, The reason is:{}", resp.getStatusCode(), - resp.getJsonResponse()); - throw new IllegalStateException(String.format("Error getting oauth token:%s", resp.getJsonResponse())); + throw new IllegalStateException( + String.format("Error getting oauth token. HTTP Status Code is:%s, The reason is:%s", + resp.getStatusCode(), resp.getJsonResponse())); } else { tokenResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); @@ -362,13 +358,12 @@ private TokenResult handleRefreshTokenResult(@Nullable RestResult resp, TokenRes throws IOException, RefreshTokenException { Map tokenResult; if (resp == null) { - logger.error("Error getting oauth token. Null data returned"); throw new RefreshTokenException("Error getting oauth token. Null data returned"); } if (resp.getStatusCode() != 200) { - logger.error("Error getting oauth token. HTTP Status Code is:{}, The reason is:{}", resp.getStatusCode(), - resp.getJsonResponse()); - throw new RefreshTokenException(String.format("Error getting oauth token:%s", resp.getJsonResponse())); + throw new RefreshTokenException( + String.format("Error getting oauth token. HTTP Status Code is:%s, The reason is:%s", + resp.getStatusCode(), resp.getJsonResponse())); } else { tokenResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java index 80333e5b66a28..fceae3d96519c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java @@ -12,7 +12,9 @@ */ package org.openhab.binding.lgthinq.lgservices.api; -import java.io.IOException; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.HMAC_SHA1_ALGORITHM; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.MESSAGE_DIGEST_ALGORITHM; + import java.math.BigInteger; import java.net.URI; import java.nio.charset.StandardCharsets; @@ -51,14 +53,13 @@ public class RestUtils { private static final Logger logger = LoggerFactory.getLogger(RestUtils.class); - private static final String HMAC_SHA1_ALGORITHM = "HmacSHA1"; public static String getPreLoginEncPwd(String pwdToEnc) { MessageDigest digest; try { - digest = MessageDigest.getInstance("SHA-512"); + digest = MessageDigest.getInstance(MESSAGE_DIGEST_ALGORITHM); } catch (NoSuchAlgorithmException e) { - logger.error("Definitively, it is unexpected.", e); + logger.warn("The required algorithm is not available.", e); throw new IllegalStateException("Unexpected error. SHA-512 algorithm must exists in JDK distribution", e); } digest.reset(); @@ -76,10 +77,10 @@ public static byte[] getOauth2Sig(String messageSign, String secret) { mac.init(signingKey); return Base64.getEncoder().encode(mac.doFinal(messageSign.getBytes(StandardCharsets.UTF_8))); } catch (NoSuchAlgorithmException e) { - logger.error("Unexpected error. SHA1 algorithm must exists in JDK distribution.", e); + logger.debug("Unexpected error. SHA1 algorithm must exists in JDK distribution.", e); throw new IllegalStateException("Unexpected error. SHA1 algorithm must exists in JDK distribution", e); } catch (InvalidKeyException e) { - logger.error("Unexpected error.", e); + logger.debug("Unexpected error.", e); throw new IllegalStateException("Unexpected error.", e); } } @@ -116,20 +117,20 @@ public static RestResult getCall(HttpClient httpClient, String encodedUrl, @Null return new RestResult(response.getContentAsString(), response.getStatus()); } catch (InterruptedException | TimeoutException | ExecutionException e) { - logger.error("Exception occurred during GET execution: {}", e.getMessage(), e); + logger.debug("Exception occurred during GET execution: {}", e.getMessage(), e); throw new CommunicationException(e); } } @Nullable public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, - String jsonData) throws IOException { + String jsonData) { return postCall(httpClient, encodedUrl, headers, new StringContentProvider(jsonData)); } @Nullable public static RestResult postCall(HttpClient httpClient, String encodedUrl, Map headers, - Map formParams) throws IOException { + Map formParams) { Fields fields = new Fields(); formParams.forEach(fields::put); return postCall(httpClient, encodedUrl, headers, new FormContentProvider(fields)); @@ -143,9 +144,7 @@ private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map Request request = httpClient.newRequest(encodedUrl).method("POST").content(contentProvider).timeout(10, TimeUnit.SECONDS); headers.forEach(request::header); - if (logger.isTraceEnabled()) { - logger.trace("POST request: {}", request.getURI()); - } + logger.debug("POST request to URI: {}", request.getURI()); ContentResponse response = request.content(contentProvider).timeout(10, TimeUnit.SECONDS).send(); @@ -153,20 +152,15 @@ private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map return new RestResult(response.getContentAsString(), response.getStatus()); } catch (TimeoutException e) { - if (logger.isDebugEnabled()) { - logger.warn("Timeout reading post call result from LG API", e); - } else { - logger.warn("Timeout reading post call result from LG API"); - } - // In SocketTimeout cases I'm considering that I have no response on time. Then, I return null data + logger.warn("Timeout reading post call result from LG API", e); // In SocketTimeout cases I'm considering + // that I have no response on time. Then, I + // return null data // forcing caller to retry. return null; } catch (InterruptedException e) { Thread.currentThread().interrupt(); - logger.error("InterruptedException occurred during POST execution: {}", e.getMessage(), e); throw new CommunicationException(e); } catch (ExecutionException e) { - logger.error("ExecutionException occurred during POST execution: {}", e.getMessage(), e); throw new CommunicationException(e); } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java index fbc7d0c248eab..4d60b52424613 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java @@ -46,12 +46,12 @@ public class TokenManager { private static final int EXPIRICY_TOLERANCE_SEC = 60; private static final Logger logger = LoggerFactory.getLogger(TokenManager.class); - private final OauthLgEmpAuthenticator oAuthAuthenticator; + private final LGThinqOauthEmpAuthenticator authenticator; private final ObjectMapper objectMapper = new ObjectMapper(); private final Map tokenCached = new ConcurrentHashMap<>(); public TokenManager(HttpClient httpClient) { - oAuthAuthenticator = new OauthLgEmpAuthenticator(httpClient); + authenticator = new LGThinqOauthEmpAuthenticator(httpClient); } public boolean isTokenExpired(TokenResult token) { @@ -64,7 +64,7 @@ public boolean isTokenExpired(TokenResult token) { public TokenResult refreshToken(String bridgeName, TokenResult currentToken) throws RefreshTokenException { try { - TokenResult token = oAuthAuthenticator.doRefreshToken(currentToken); + TokenResult token = authenticator.doRefreshToken(currentToken); objectMapper.writeValue(new File(getConfigDataFileName(bridgeName)), token); return token; } catch (IOException e) { @@ -91,38 +91,35 @@ public void oauthFirstRegistration(String bridgeName, String language, String co String password, String alternativeGtwServer) throws LGThinqGatewayException, PreLoginException, AccountLoginException, TokenException, IOException { LGThinqGateway gw; - OauthLgEmpAuthenticator.PreLoginResult preLogin; - OauthLgEmpAuthenticator.LoginAccountResult accountLogin; + LGThinqOauthEmpAuthenticator.PreLoginResult preLogin; + LGThinqOauthEmpAuthenticator.LoginAccountResult accountLogin; TokenResult token; UserInfo userInfo; try { - gw = oAuthAuthenticator.discoverGatewayConfiguration(getGatewayUrl(alternativeGtwServer), language, country, + gw = authenticator.discoverGatewayConfiguration(getGatewayUrl(alternativeGtwServer), language, country, alternativeGtwServer); } catch (Exception ex) { - throw new LGThinqGatewayException( - "Error trying to discovery the LG Gateway Setting for the region informed", ex); + throw new LGThinqGatewayException("Error trying to discover the LG Gateway Setting for the region informed", + ex); } try { - preLogin = oAuthAuthenticator.preLoginUser(gw, username, password); + preLogin = authenticator.preLoginUser(gw, username, password); } catch (Exception ex) { - logger.error("Error pre-login with gateway: {}", gw); throw new PreLoginException("Error doing pre-login of the user in the Emp LG Server", ex); } try { - accountLogin = oAuthAuthenticator.loginUser(gw, preLogin); + accountLogin = authenticator.loginUser(gw, preLogin); } catch (Exception ex) { - logger.error("Error logging with gateway: {}", gw); throw new AccountLoginException("Error doing user's account login on the Emp LG Server", ex); } try { - token = oAuthAuthenticator.getToken(gw, accountLogin); + token = authenticator.getToken(gw, accountLogin); } catch (Exception ex) { - logger.error("Error getting token with gateway: {}", gw); throw new TokenException("Error getting Token", ex); } try { - userInfo = oAuthAuthenticator.getUserInfo(token); + userInfo = authenticator.getUserInfo(token); token.setUserInfo(userInfo); token.setGatewayInfo(gw); } catch (Exception ex) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java index 7e705b9f0c4a3..be0ab8a11b1ab 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/AbstractCapabilityFactory.java @@ -75,10 +75,9 @@ public T create(JsonNode rootNode) throws LGThinqException { cap.setMonitoringBinaryProtocol(protocols); } else { if (protocol.isMissingNode()) { - logger.error("protocol node is missing in the capability descriptor for a binary monitoring"); + logger.warn("protocol node is missing in the capability descriptor for a binary monitoring"); } else { - logger.error( - "protocol node is not and array in the capability descriptor for a binary monitoring "); + logger.warn("protocol node is not and array in the capability descriptor for a binary monitoring "); } } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java index 527c3b012402d..2ca058033a1ad 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java @@ -53,8 +53,7 @@ public FridgeCapability create(JsonNode rootNode) throws LGThinqException { FridgeCapability frCap = super.create(rootNode); JsonNode node = mapper.valueToTree(rootNode); if (node.isNull()) { - logger.error("Can't parse json capability for Fridge. The payload has been ignored"); - logger.debug("payload {}", rootNode); + logger.debug("Can't parse json capability for Fridge. The payload has been ignored. Payload:{}", rootNode); throw new LGThinqException("Can't parse json capability for Fridge. The payload has been ignored"); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index e896b27fc29f5..c0e0841834530 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -265,3 +265,5 @@ channel-type.lgthinq.washerdryer-temp-level.label = Temp. Level channel-type.lgthinq.washerdryer-temp-level.description = Target Temperature Level error.lgapi-getting-devices = Error getting device list from the account +error.toke-file-corrupted = LGThinq Bridge Token File corrupted +error.toke-refresh = Error refreshing LGThinq Bridge Token diff --git a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java index 4e6f188744319..1888fa569d246 100644 --- a/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/test/java/org/openhab/binding/lgthinq/handler/JsonUtils.java @@ -18,9 +18,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; - /** * The {@link JsonUtils} * @@ -29,17 +26,6 @@ @NonNullByDefault @SuppressWarnings("null") public class JsonUtils { - public static T unmashallJson(String fileName) { - InputStream inputStream = JsonUtils.class.getResourceAsStream(fileName); - try { - return new ObjectMapper().readValue(inputStream, new TypeReference<>() { - }); - } catch (IOException e) { - throw new IllegalArgumentException( - "Unexpected error. It is not expected this behaviour since json test files must be present."); - } - } - public static String loadJson(String fileName) { ClassLoader classLoader = JsonUtils.class.getClassLoader(); try (InputStream inputStream = classLoader.getResourceAsStream(fileName)) { From 7eac499e0fad2707cda2109ffdf349ffb031779e Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Sun, 3 Nov 2024 23:39:01 -0300 Subject: [PATCH 122/130] [lgthinq][fix] fix log Signed-off-by: Nemer Daud --- .../org/openhab/binding/lgthinq/lgservices/api/RestUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java index fceae3d96519c..cba66fe04a20e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/RestUtils.java @@ -144,7 +144,7 @@ private static RestResult postCall(HttpClient httpClient, String encodedUrl, Map Request request = httpClient.newRequest(encodedUrl).method("POST").content(contentProvider).timeout(10, TimeUnit.SECONDS); headers.forEach(request::header); - logger.debug("POST request to URI: {}", request.getURI()); + logger.trace("POST request to URI: {}", request.getURI()); ContentResponse response = request.content(contentProvider).timeout(10, TimeUnit.SECONDS).send(); From 8ff725d64b07f45d2592207201ab92b435f1ad57 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 6 Nov 2024 11:34:08 -0300 Subject: [PATCH 123/130] [lgthinq][fix] review fixes Signed-off-by: Nemer Daud --- bundles/org.openhab.binding.lgthinq/README.md | 125 +++++++++--------- .../internal/LGThinQBindingConstants.java | 2 +- .../internal/LGThinQBridgeConfiguration.java | 10 +- .../handler/LGThinQAirConditionerHandler.java | 30 ++--- .../internal/handler/LGThinQBridge.java | 5 + .../handler/LGThinQDishWasherHandler.java | 1 - .../handler/LGThinQFridgeHandler.java | 2 - .../handler/LGThinQWasherDryerHandler.java | 2 - .../lgthinq/lgservices/api/TokenManager.java | 1 - .../devices/commons/washers/CourseType.java | 1 - .../fridge/FridgeCapabilityFactoryV1.java | 1 - .../AbstractWasherDryerCapabilityFactory.java | 1 - .../resources/OH-INF/i18n/lgthinq.properties | 12 +- .../OH-INF/thing/air-conditioner.xml | 2 +- .../main/resources/OH-INF/thing/bridge.xml | 2 +- .../main/resources/OH-INF/thing/channels.xml | 10 +- .../main/resources/OH-INF/thing/heat-pump.xml | 2 +- 17 files changed, 101 insertions(+), 108 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/README.md b/bundles/org.openhab.binding.lgthinq/README.md index 586c5bfe47dfe..bdb41962e2ab1 100644 --- a/bundles/org.openhab.binding.lgthinq/README.md +++ b/bundles/org.openhab.binding.lgthinq/README.md @@ -1,37 +1,29 @@ -# LG ThinQ Bridge & Things +# LG ThinQ Binding This binding was developed to integrate the LG ThinQ API with openHAB. -The ThinQ Bridge is necessary to work as a hub/bridge to discovery and first configure the LG ThinQ devices related with the LG's user account. -Then, the first thing is to create the LG ThinQ Bridge and then, it will discover all Things you have related in your LG Account. ## Supported Things -This binding support several devices from the LG ThinQ Devices V1 & V2 line. Se the table bellow: +This binding support several devices from the LG ThinQ Devices V1 & V2 line. +See the table bellow: -| Device ID | Device Name | Versions | Special Functions | Commands | Obs | -|-----------|-----------------|----------|------------------------------|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| 401 | Air Conditioner | V1 & V2 | Filter and Energy Monitoring | All features in LG App, except Wind Direction | | -| 204 | Dish Washer | V2 | None | None | Provide only some channels to follow the cycle | -| 222 | Dryer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | -| 221 | Washer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | -| 201 | Washer Machine | V1 & V2 | None | All features in LG App (including remote start) | | -| 202 | Dryer Machine | V1 & V2 | None | All features in LG App (including remote start) | | -| 101 | Refrigerator | V1 & V2 | None | All features in LG App | | -| 401HP | Heat Pump | V1 & V2 | None | All features in LG App | | +| Thing ID | Device Name | Versions | Special Functions | Commands | Obs | +|---------------------|-----------------|----------|------------------------------|-------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| air-conditioner-401 | Air Conditioner | V1 & V2 | Filter and Energy Monitoring | All features in LG App, except Wind Direction | | +| dishwasher-204 | Dish Washer | V2 | None | None | Provide only some channels to follow the cycle | +| 222 | Dryer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | +| washer-tower-221 | Washer Tower | V1 & V2 | None | All features in LG App (including remote start) | LG has a WasherDryer Tower that is 2 in one device.
When this device is discovered by this binding, it's recognized as 2 separated devices Washer and Dryer | +| washer-201 | Washer Machine | V1 & V2 | None | All features in LG App (including remote start) | | +| dryer-tower-222 | Dryer Machine | V1 & V2 | None | All features in LG App (including remote start) | | +| fridge-101 | Refrigerator | V1 & V2 | None | All features in LG App | | +| heatpump-401HP | Heat Pump | V1 & V2 | None | All features in LG App | | -## Bridge Thing +## `bridge` Thing -This binding has a Bridge responsible for the discovery and registry of LG Things. The first step to create a thing, is to firstly add the LG Thinq Bridge, that will -connect the binding to your LG Account and after, the bridge can discovery all devices registered and you are able to add it to OpenHab Things. +This binding has a Bridge responsible for discovering and registering LG Things. +Thus, adding the Bridge (LGThinq GW Bridge) is the first step in configuring this Binding. +The following parameters are available to configure the Bridge and to link to your LG Account as well: -## Discovery - -This binding bas auto-discovering for the supported devices - -## Binding Configuration - -The binding is configured through a bridge (LG GatewayBridge) and you must configure the following parameters: - | Bridge Parameter | Label | Description | Obs | |--------------------|----------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------| | language | User Language | More frequent languages used | If you choose other, you can fill Manual user language (only if your language was not pre-defined in this combo | @@ -40,27 +32,32 @@ The binding is configured through a bridge (LG GatewayBridge) and you must confi | manualCountry | Manual User Country | The acronym for the country (UK, US, BR, etc) | | | username | LG User name | The LG user's account (normally an email) | | | password | LG Password | The LG user's password | | -| poolingIntervalSec | Polling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time greater than 300 seconds | +| pollingIntervalSec | Polling Discovery Interval | It the time (in seconds) that the bridge wait to try to fetch de devices registered to the user's account and, if find some new device, will show available to link. Please, choose some long time greater than 300 seconds | | alternativeServer | Alt Gateway Server | Only used if you have some proxy to the LG API Server or for Mock Tests | | +## Discovery +This Binding has auto-discovery for the supported LG Thinq devices. +Once LG Thinq Bridge has been added, LG Thinq devices linked to your account will be automatically discovered and displayed in the OpenHab Inbox. ## Thing Configuration -All the configurations are pre-defined by the discovery process. But you can customize to fine-tune the device's state polling process. See the table bellow: +All the configurations are pre-defined by the discovery process. +But you can customize to fine-tune the device's state polling process. +See the table below: -| Parameter | Description | Default Value | Supported Devices | -|---------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-------------------------------| -| Polling when off | Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. | 10 | All | -| Polling when on | Seconds to wait to the next polling for device state (dashboard channels) | 10 | All | -| Polling Info Period | Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) | 60 | Air Conditioner and Heat Pump | -| Extra Info | If enables, extra info will be fetched in the polling process even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. | Off | Air Conditioner and Heat Pump | +| Parameter | Description | Default Value | Supported Devices | +|-------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------|-------------------------------| +| pollingPeriodPowerOffSeconds | Seconds to wait to the next polling when device is off. Useful to save up i/o and cpu when your device is not working. If you use only this binding to control the device, you can put higher values here. | 10 | All | +| pollingPeriodPowerOnSeconds | Seconds to wait to the next polling for device state (dashboard channels) | 10 | All | +| pollingExtraInfoPeriodSeconds | Seconds to wait to the next polling for Device's Extra Info (energy consumption, remaining filter, etc) | 60 | Air Conditioner and Heat Pump | +| pollExtraInfoOnPowerOff | If enables, extra info will be fetched in the polling process even when the device is powered off. It's not so common, since extra info are normally changed only when the device is running. | Off | Air Conditioner and Heat Pump | ## Channels ### Air Conditioner -LG ThinQ Air Conditioners supports the following channels (for some models, some channels couldn't be available): +Most, but not all, LG ThinQ Air Conditioners support the following channels: #### Dashboard Channels @@ -70,7 +67,7 @@ LG ThinQ Air Conditioners supports the following channels (for some models, some | current-temperature | Temperature | Number:Temperature | Read-Only channel that indicates the current temperature informed by the device | | fan-speed | Fan Speed | Number | This channel let you choose the current label value for the fan speed (Low, Medium, High, Auto, etc.). These values are pre-configured in discovery time. | | op-mode | Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | -| power | Power | Switch | Define the device's current power state. | +| power | Power | Switch | Define the device's Current Power state. | | cool-jet | Cool Jet | Switch | Switch Cool Jet ON/OFF | | auto-dry | Auto Dry | Switch | Switch Auto Dry ON/OFF | | energy-saving | Energy Saving | Switch | Switch Energy Saving ON/OFF | @@ -82,7 +79,7 @@ LG ThinQ Air Conditioners supports the following channels (for some models, some | channel # | channel | type | description | |----------------------|--------------------------------|----------------------|------------------------------------------------------------------------------| | extra-info-collector | Enable Extended Info Collector | Switch | Enable/Disable the extra information collector to update the bellow channels | -| current-power | Current Power | Number:Energy | The current power consumption in Kw/h | +| current-energy | Current Energy | Number:Energy | The Current Energy consumption in Kwh | | remaining-filter | Remaining Filter | Number:Dimensionless | Percentage of the remaining filter | ### Heat Pump @@ -98,7 +95,7 @@ LG ThinQ Heat Pump supports the following channels | max-temperature | Maximum Temperature | Number:Temperature | Maximum temperature for the current operation mode | | current-temperature | Temperature | Number:Temperature | Read-Only channel that indicates the current temperature informed by the device | | op-mode | Operation Mode | Number (Labeled) | Defines device's operation mode (Fan, Cool, Dry, etc). These values are pre-configured at discovery time. | -| power | Power | Switch | Define the device's current power state. | +| power | Power | Switch | Define the device's Current Power state. | | air-water-switch | Air/Water Switch | Switch | Switch the heat pump operation between Air or Water | #### More Information Channel @@ -106,7 +103,7 @@ LG ThinQ Heat Pump supports the following channels | channel # | channel | type | description | |----------------------|--------------------------------|----------------------|------------------------------------------------------------------------------| | extra-info-collector | Enable Extended Info Collector | Switch | Enable/Disable the extra information collector to update the bellow channels | -| current-power | Current Power | Number:Energy | The current power consumption in Kw/h | +| current-energy | Current Energy | Number:Energy | The Current Energy consumption in Kwh | ### Washer Machine @@ -114,19 +111,20 @@ LG ThinQ Washer Machine supports the following channels #### Dashboard Channels -| channel # | channel | type | description | -|-------------------|-------------------|--------|------------------------------------------------------------------------| -| state | Washer State | String | General State of the Washer | -| process-state | Process State | String | States of the running cycle | -| course | Course | String | Course set up to work | -| temperature-level | Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | -| door-lock | Door Lock | Switch | Display if the Door is Locked. | -| rinse | Rinse | String | The Rinse set program | -| spin | Spin | String | The Spin set option | -| delay-time | Delay Time | String | Delay time programmed to start the cycle | -| remain-time | Remaining Time | String | Remaining time to finish the course | -| stand-by | Stand By Mode | Switch | If the Washer is in stand-by-mode | -| remote-start-flag | Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | +| channel # | channel | type | description | +|-------------------|-------------------|------------|------------------------------------------------------------------------------------------------------------| +| state | Washer State | String | General State of the Washer | +| power | Power | Switch | Define the device's Current Power state. | +| process-state | Process State | String | States of the running cycle | +| course | Course | String | Course set up to work | +| temperature-level | Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | +| door-lock | Door Lock | Switch | Display if the Door is Locked. | +| rinse | Rinse | String | The Rinse set program | +| spin | Spin | String | The Spin set option | +| delay-time | Delay Time | String | Delay time programmed to start the cycle | +| remain-time | Remaining Time | String | Remaining time to finish the course | +| stand-by | Stand By Mode | Switch | If the Washer is in stand-by-mode | +| remote-start-flag | Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | #### Remote Start Option @@ -146,18 +144,19 @@ LG ThinQ Dryer Machine supports the following channels #### Dashboard Channels -| channel # | channel | type | description | -|-------------------|-------------------|--------|------------------------------------------------------------------------| -| state | Dryer State | String | General State of the Washer | -| process-state | Process State | String | States of the running cycle | -| course | Course | String | Course set up to work | -| temperature-level | Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | -| child-lock | Child Lock | Switch | Display if the Door is Locked. | -| dry-level | Dry Level Course | String | Dry level set to work in the course | -| delay-time | Delay Time | String | Delay time programmed to start the cycle | -| remain-time | Remaining Time | String | Remaining time to finish the course | -| stand-by | Stand By Mode | Switch | If the Washer is in stand-by-mode | -| remote-start-flag | Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | +| channel # | channel | type | description | +|-------------------|-------------------|---------|------------------------------------------------------------------------| +| power | Power | Switch | Define the device's Current Power state. | +| state | Dryer State | String | General State of the Washer | +| process-state | Process State | String | States of the running cycle | +| course | Course | String | Course set up to work | +| temperature-level | Temperature Level | String | Temperature level supported by the Washer (Cold, 20, 30, 40, 50, etc.) | +| child-lock | Child Lock | Switch | Display if the Door is Locked. | +| dry-level | Dry Level Course | String | Dry level set to work in the course | +| delay-time | Delay Time | String | Delay time programmed to start the cycle | +| remain-time | Remaining Time | String | Remaining time to finish the course | +| stand-by | Stand By Mode | Switch | If the Washer is in stand-by-mode | +| remote-start-flag | Remote Start | Switch | If the Washer is in remote start mode waiting to be remotely started | #### Remote Start Option diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 77dfac20b91ab..48d672483516c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -83,7 +83,7 @@ public class LGThinQBindingConstants extends LGServicesConstants { public static final String CHANNEL_AC_AIR_WATER_SWITCH_ID = "air-water-switch"; public static final String CHANNEL_AC_AUTO_DRY_ID = "auto-dry"; public static final String CHANNEL_AC_COOL_JET_ID = "cool-jet"; - public static final String CHANNEL_AC_CURRENT_POWER_ID = "current-power"; + public static final String CHANNEL_AC_CURRENT_ENERGY_ID = "current-energy"; public static final String CHANNEL_AC_CURRENT_TEMP_ID = "current-temperature"; public static final String CHANNEL_AC_ENERGY_SAVING_ID = "energy-saving"; public static final String CHANNEL_AC_FAN_SPEED_ID = "fan-speed"; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java index e5d159120cb06..19a5c4d674d88 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java @@ -30,7 +30,7 @@ public class LGThinQBridgeConfiguration { public String language = ""; public final String manualCountry = ""; public final String manualLanguage = ""; - public Integer poolingIntervalSec = 0; + public Integer pollingIntervalSec = 0; public String alternativeServer = ""; public LGThinQBridgeConfiguration() { @@ -42,7 +42,7 @@ public LGThinQBridgeConfiguration(String username, String password, String count this.password = password; this.country = country; this.language = language; - this.poolingIntervalSec = pollingIntervalSec; + this.pollingIntervalSec = pollingIntervalSec; this.alternativeServer = alternativeServer; } @@ -69,7 +69,7 @@ public String getLanguage() { } public Integer getPoolingIntervalSec() { - return poolingIntervalSec; + return pollingIntervalSec; } public void setUsername(String username) { @@ -88,8 +88,8 @@ public void setLanguage(String language) { this.language = language; } - public void setPoolingIntervalSec(Integer poolingIntervalSec) { - this.poolingIntervalSec = poolingIntervalSec; + public void setPoolingIntervalSec(Integer pollingIntervalSec) { + this.pollingIntervalSec = pollingIntervalSec; } public String getAlternativeServer() { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 2cbe16c5a1e9e..9057fba69676b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -19,7 +19,7 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_AIR_WATER_SWITCH_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_AUTO_DRY_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_COOL_JET_ID; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_CURRENT_POWER_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_CURRENT_ENERGY_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_CURRENT_TEMP_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_ENERGY_SAVING_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_FAN_SPEED_ID; @@ -111,7 +111,7 @@ public class LGThinQAirConditionerHandler extends LGThinQAbstractDeviceHandler
collectExtraInfoState() throws LGThinqException { @Override protected void updateExtraInfoStateChannels(Map energyStateAttributes) { logger.debug("Calling updateExtraInfoStateChannels for device: {}", getDeviceId()); - String instantPowerConsumption = (String) energyStateAttributes.get(CAP_EXTRA_ATTR_INSTANT_POWER); + String instantEnergyConsumption = (String) energyStateAttributes.get(CAP_EXTRA_ATTR_INSTANT_POWER); String filterUsed = (String) energyStateAttributes.get(CAP_EXTRA_ATTR_FILTER_USED_TIME); - String filterTimelife = (String) energyStateAttributes.get(CAP_EXTRA_ATTR_FILTER_MAX_TIME_TO_USE); - if (instantPowerConsumption == null) { - updateState(currentPowerEnergyChannelUID, UnDefType.NULL); + String filterLifetime = (String) energyStateAttributes.get(CAP_EXTRA_ATTR_FILTER_MAX_TIME_TO_USE); + if (instantEnergyConsumption == null) { + updateState(currentEnergyConsumptionChannelUID, UnDefType.NULL); } else { try { - double ip = Double.parseDouble(instantPowerConsumption); - updateState(currentPowerEnergyChannelUID, new QuantityType<>(ip, Units.WATT_HOUR)); + double ip = Double.parseDouble(instantEnergyConsumption); + updateState(currentEnergyConsumptionChannelUID, new QuantityType<>(ip, Units.WATT_HOUR)); } catch (NumberFormatException e) { - updateState(currentPowerEnergyChannelUID, UnDefType.UNDEF); + updateState(currentEnergyConsumptionChannelUID, UnDefType.UNDEF); } } - if (filterTimelife == null || filterUsed == null) { + if (filterLifetime == null || filterUsed == null) { updateState(remainingFilterChannelUID, UnDefType.NULL); } else { try { double used = Double.parseDouble(filterUsed); - double max = Double.parseDouble(filterTimelife); + double max = Double.parseDouble(filterLifetime); double perc = (1 - (used / max)) * 100; updateState(remainingFilterChannelUID, new QuantityType<>(perc, Units.PERCENT)); } catch (NumberFormatException ex) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java index 3968123e5c077..526a8841bff1c 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java @@ -24,6 +24,11 @@ */ @NonNullByDefault public interface LGThinQBridge { + /** + * Register + * + * @param listener + */ void registerDiscoveryListener(LGThinqDiscoveryService listener); void registryListenerThing( diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java index ee5bcb499addb..4ecf4cfbd8e4a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -172,7 +172,6 @@ public String getDeviceUriJsonConfig() { @Override public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed } /** diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java index 567c1d255db26..34d73d5077326 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQFridgeHandler.java @@ -284,12 +284,10 @@ public String getDeviceUriJsonConfig() { @Override public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed } @Override public void onDeviceDisconnected() { - // TODO - HANDLE IT, Think if it's needed } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index ce5f44ffbfe7c..792ec5c976f24 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -257,7 +257,6 @@ protected void updateDeviceChannels(WasherDryerSnapshot shot) throws LGThinqApiE updateState(remoteStartStopChannelUID, OnOffType.OFF); // === creating selectable channels for the Course (if any) WasherDryerCapability cap = getCapabilities(); - // TODO - V1 - App will always get the default course, and V2 ? loadOptionsCourse(cap, remoteStartCourseChannelUID); updateState(remoteStartCourseChannelUID, new StringType(cap.getDefaultCourseId())); @@ -438,7 +437,6 @@ public String getDeviceUriJsonConfig() { @Override public void onDeviceRemoved() { - // TODO - HANDLE IT, Think if it's needed } /** diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java index 4d60b52424613..a09e29686dd8f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/TokenManager.java @@ -78,7 +78,6 @@ private String getConfigDataFileName(String bridgeName) { public boolean isOauthTokenRegistered(String bridgeName) { File tokenFile = new File(getConfigDataFileName(bridgeName)); - // TODO - check if the file content is valid. return tokenFile.isFile(); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseType.java index b84a14c428028..c548a63b24a32 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseType.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/commons/washers/CourseType.java @@ -21,7 +21,6 @@ */ @NonNullByDefault public enum CourseType { - // TODO - review DownloadCourse value, in remote start debugging COURSE("Course"), SMART_COURSE("SmartCourse"), DOWNLOADED_COURSE("DownloadedCourse"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java index 09907db3796a5..7290e6efb3643 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV1.java @@ -40,7 +40,6 @@ public class FridgeCapabilityFactoryV1 extends AbstractFridgeCapabilityFactory { @Override protected FeatureDefinition newFeatureDefinition(String featureName, JsonNode featuresNode, @Nullable String targetChannelId, @Nullable String refChannelId) { - // TODO - Implement feature definition return FeatureDefinition.NULL_DEFINITION; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java index c1cd8063fbb95..497b95d40c771 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java @@ -84,7 +84,6 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { Map allCourses = new HashMap<>(getCourseDefinitions(coursesNode)); allCourses.putAll(getSmartCourseDefinitions(smartCoursesNode)); - // TODO - Put Downloaded Course wdCap.setCourses(allCourses); JsonNode monitorValueNode = rootNode.path(getMonitorValueNodeName()); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index c0e0841834530..1b9f7b14b8b2b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -71,8 +71,8 @@ thing-type.config.lgthinq.bridge.manualLanguage.label = Manual User Lang. thing-type.config.lgthinq.bridge.manualLanguage.description = Fill this only if selected "Other" in the Language above thing-type.config.lgthinq.bridge.password.label = Password thing-type.config.lgthinq.bridge.password.description = Password from LG Thinq Personal Account -thing-type.config.lgthinq.bridge.poolingIntervalSec.label = Discovery Interval -thing-type.config.lgthinq.bridge.poolingIntervalSec.description = Polling interval to discover new devices from LG Account (in Seconds >300 or 0 disabled). +thing-type.config.lgthinq.bridge.pollingIntervalSec.label = Discovery Interval +thing-type.config.lgthinq.bridge.pollingIntervalSec.description = Polling interval to discover new devices from LG Account (in Seconds >300 or 0 disabled). thing-type.config.lgthinq.bridge.username.label = Username thing-type.config.lgthinq.bridge.username.description = Username from LG Thinq Personal Account thing-type.config.lgthinq.dishwasher-204.group.Settings.label = Polling @@ -146,12 +146,12 @@ channel-group-type.lgthinq.wm-remote-start-grp.description = Remote Start Action channel-type.lgthinq.air-clean.label = Air Clean channel-type.lgthinq.auto-dry.label = Auto Dry channel-type.lgthinq.cool-jet.label = Cool Jet -channel-type.lgthinq.current-power.label = Current Power -channel-type.lgthinq.current-power.description = Current Power Consumption (kWh) +channel-type.lgthinq.current-energy.label = Current Energy +channel-type.lgthinq.current-energy.description = Current Energy Consumption (kWh) channel-type.lgthinq.current-temperature.label = Temperature channel-type.lgthinq.current-temperature.description = Current temperature. -channel-type.lgthinq.current-watts-power.label = Current Power -channel-type.lgthinq.current-watts-power.description = Current Power Consumption (W) +channel-type.lgthinq.current-watts-power.label = Current Energy +channel-type.lgthinq.current-watts-power.description = Current Energy Consumption (W) channel-type.lgthinq.dryer-child-lock.label = Child Lock channel-type.lgthinq.dryer-child-lock.description = Dryer Child Lock channel-type.lgthinq.dryer-child-lock.state.option.CHILDLOCK_OFF = Unlocked diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml index 48042229c4290..02b4792ce0e6d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/air-conditioner.xml @@ -75,7 +75,7 @@ Show more information about the device. - + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml index 21630ed22034a..b8c9e0276cbd0 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/bridge.xml @@ -63,7 +63,7 @@ Password from LG Thinq Personal Account password - + Polling interval to discover new devices from LG Account (in Seconds >300 or 0 disabled). 86400 diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 87227ca46b476..3590a499c128b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -115,18 +115,18 @@ - + Number:Energy - - Current Power Consumption (kWh) + + Current Energy Consumption (kWh) Energy Number:Power - - Current Power Consumption (W) + + Current Energy Consumption (W) Energy diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml index 3c19bc4d09599..5be5176644c67 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/heat-pump.xml @@ -77,7 +77,7 @@ Show more information about the device. - + From d92f9574ee6937e3d68a917136d06c8106d02d61 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 6 Nov 2024 11:36:30 -0300 Subject: [PATCH 124/130] [lgthinq][fix] review fixes Signed-off-by: Nemer Daud --- .../src/main/resources/OH-INF/i18n/lgthinq.properties | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index 1b9f7b14b8b2b..8e1c1f08f632b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -46,6 +46,7 @@ thing-type.config.lgthinq.bridge.country.option.BE = Belgium thing-type.config.lgthinq.bridge.country.option.BR = Brazil thing-type.config.lgthinq.bridge.country.option.IT = Italy thing-type.config.lgthinq.bridge.country.option.LU = Luxembourg +thing-type.config.lgthinq.bridge.country.option.NL = Netherlands thing-type.config.lgthinq.bridge.country.option.PL = Poland thing-type.config.lgthinq.bridge.country.option.PT = Portugal thing-type.config.lgthinq.bridge.country.option.DE = Germany @@ -60,15 +61,16 @@ thing-type.config.lgthinq.bridge.language.option.en-GB = British English thing-type.config.lgthinq.bridge.language.option.pt-BR = Brazilian Portuguese thing-type.config.lgthinq.bridge.language.option.it-IT = Italian thing-type.config.lgthinq.bridge.language.option.de-LU = Luxembourg German +thing-type.config.lgthinq.bridge.language.option.nl-NL = Netherlands Dutch thing-type.config.lgthinq.bridge.language.option.pl-PL = Polish thing-type.config.lgthinq.bridge.language.option.pt-PT = Portugal Portuguese thing-type.config.lgthinq.bridge.language.option.de-DE = German (Standard) thing-type.config.lgthinq.bridge.language.option.da-DK = Danish thing-type.config.lgthinq.bridge.language.option.-- = Other thing-type.config.lgthinq.bridge.manualCountry.label = Manual User Country -thing-type.config.lgthinq.bridge.manualCountry.description = Fill this only if selected "Other" in the Country above +thing-type.config.lgthinq.bridge.manualCountry.description = Fill this only if selected "Other" in the Country above. Example value: "DE" thing-type.config.lgthinq.bridge.manualLanguage.label = Manual User Lang. -thing-type.config.lgthinq.bridge.manualLanguage.description = Fill this only if selected "Other" in the Language above +thing-type.config.lgthinq.bridge.manualLanguage.description = Fill this only if selected "Other" in the Language above. Example value: de-DE thing-type.config.lgthinq.bridge.password.label = Password thing-type.config.lgthinq.bridge.password.description = Password from LG Thinq Personal Account thing-type.config.lgthinq.bridge.pollingIntervalSec.label = Discovery Interval @@ -264,6 +266,8 @@ channel-type.lgthinq.washerdryer-stand-by.description = Standby Mode channel-type.lgthinq.washerdryer-temp-level.label = Temp. Level channel-type.lgthinq.washerdryer-temp-level.description = Target Temperature Level +# channel types + error.lgapi-getting-devices = Error getting device list from the account error.toke-file-corrupted = LGThinq Bridge Token File corrupted error.toke-refresh = Error refreshing LGThinq Bridge Token From 0ed7ca54d2608c6179ca9f9b7e9e7d67a69572ae Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 6 Nov 2024 11:40:44 -0300 Subject: [PATCH 125/130] [lgthinq][fix] review fixes Signed-off-by: Nemer Daud --- .../binding/lgthinq/internal/LGThinQBridgeConfiguration.java | 4 ++-- .../lgthinq/internal/handler/LGThinQBridgeHandler.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java index 19a5c4d674d88..e10dc98ed27a9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java @@ -68,7 +68,7 @@ public String getLanguage() { return language; } - public Integer getPoolingIntervalSec() { + public Integer getPollingIntervalSec() { return pollingIntervalSec; } @@ -88,7 +88,7 @@ public void setLanguage(String language) { this.language = language; } - public void setPoolingIntervalSec(Integer pollingIntervalSec) { + public void setPollingIntervalSec(Integer pollingIntervalSec) { this.pollingIntervalSec = pollingIntervalSec; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index 2ca83bc10a726..6a00165667896 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -330,7 +330,7 @@ private void startLGThinqDevicePolling() { devicePollingJob.cancel(true); } long poolingInterval; - int configPollingInterval = lgthinqConfig.getPoolingIntervalSec(); + int configPollingInterval = lgthinqConfig.getPollingIntervalSec(); // It's not recommended to polling for resources in LG API short intervals to do not enter in BlackList if (configPollingInterval < 300 && configPollingInterval != 0) { poolingInterval = TimeUnit.SECONDS.toSeconds(300); From c23e6f0e79b771a719341cef3ca64071447a3828 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 6 Nov 2024 11:52:46 -0300 Subject: [PATCH 126/130] [lgthinq][fix] review fixes Signed-off-by: Nemer Daud --- bundles/org.openhab.binding.lgthinq/pom.xml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/pom.xml b/bundles/org.openhab.binding.lgthinq/pom.xml index 26f6507d7d32f..285e19d71825b 100644 --- a/bundles/org.openhab.binding.lgthinq/pom.xml +++ b/bundles/org.openhab.binding.lgthinq/pom.xml @@ -14,24 +14,6 @@ openHAB Add-ons :: Bundles :: LG Thinq Binding - - - - - - - - - - - - - - - - - - com.github.tomakehurst wiremock-jre8 From 261f00eb44c844cb729d0e4b909fba7ad593b789 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 6 Nov 2024 12:11:21 -0300 Subject: [PATCH 127/130] [lgthinq][fix] review fixes Signed-off-by: Nemer Daud --- .../lgthinq/internal/LGThinQBridgeConfiguration.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java index e10dc98ed27a9..e748971376ff6 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java @@ -30,14 +30,14 @@ public class LGThinQBridgeConfiguration { public String language = ""; public final String manualCountry = ""; public final String manualLanguage = ""; - public Integer pollingIntervalSec = 0; + public int pollingIntervalSec = 0; public String alternativeServer = ""; public LGThinQBridgeConfiguration() { } public LGThinQBridgeConfiguration(String username, String password, String country, String language, - Integer pollingIntervalSec, String alternativeServer) { + int pollingIntervalSec, String alternativeServer) { this.username = username; this.password = password; this.country = country; @@ -68,7 +68,7 @@ public String getLanguage() { return language; } - public Integer getPollingIntervalSec() { + public int getPollingIntervalSec() { return pollingIntervalSec; } @@ -88,7 +88,7 @@ public void setLanguage(String language) { this.language = language; } - public void setPollingIntervalSec(Integer pollingIntervalSec) { + public void setPollingIntervalSec(int pollingIntervalSec) { this.pollingIntervalSec = pollingIntervalSec; } From 600d83eb91151f41e4f9bbf58e1a636a7978f654 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 6 Nov 2024 12:13:05 -0300 Subject: [PATCH 128/130] [lgthinq][fix] review fixes Signed-off-by: Nemer Daud --- .../lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java index 3c5dfe53c4c97..816a427daa798 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java @@ -38,9 +38,9 @@ public static GatewayResult getGatewayResult(String rawJson) throws IOException }); String resultCode = (String) map.get("resultCode"); if (content == null) { - throw new IllegalArgumentException("Enexpected result. Gateway Content Result is null"); + throw new IllegalArgumentException("Unexpected result. Gateway Content Result is null"); } else if (resultCode == null) { - throw new IllegalArgumentException("Enexpected result. resultCode code is null"); + throw new IllegalArgumentException("Unexpected result. resultCode code is null"); } return new GatewayResult(Objects.requireNonNull(resultCode, "Expected resultCode field in json"), "", From 1b570514115e8e7db5cfd8a6d16195eb17ddffc4 Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Wed, 6 Nov 2024 12:18:44 -0300 Subject: [PATCH 129/130] [lgthinq][fix] review fixes Signed-off-by: Nemer Daud --- .../lgservices/api/LGThinqGateway.java | 7 ++- .../api/LGThinqOauthEmpAuthenticator.java | 51 +++++++------------ 2 files changed, 20 insertions(+), 38 deletions(-) diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java index f8b1ff7f758de..d5df36e97d86e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java @@ -143,9 +143,8 @@ public void setPassword(String password) { public String toString() { return "LGThinqGateway{" + "empBaseUri='" + empBaseUri + '\'' + ", loginBaseUri='" + loginBaseUri + '\'' + ", apiRootV1='" + apiRootV1 + '\'' + ", apiRootV2='" + apiRootV2 + '\'' + ", authBase='" + authBase - + '\'' + ", language='" + language + '\'' + ", country='" + country + '\'' + ", username='" - + (!username.isEmpty() ? "******" : "") + '\'' + ", password='" - + (!password.isEmpty() ? "******" : "") + '\'' + ", alternativeEmpServer='" - + alternativeEmpServer + '\'' + ", accountVersion=" + accountVersion + '}'; + + '\'' + ", language='" + language + '\'' + ", country='" + country + '\'' + ", username='" + username + + '\'' + ", password='" + (!password.isEmpty() ? "******" : "") + '\'' + + ", alternativeEmpServer='" + alternativeEmpServer + '\'' + ", accountVersion=" + accountVersion + '}'; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java index c281a7526a48a..594edb75aef0b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java @@ -12,24 +12,7 @@ */ package org.openhab.binding.lgthinq.lgservices.api; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_API_KEY_V2; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APPLICATION_KEY; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APP_LEVEL; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APP_OS; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APP_TYPE; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_APP_VER; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_CLIENT_ID; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_DATE_FORMAT; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_MESSAGE_ID; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_OAUTH_CLIENT_KEY; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_OAUTH_SEARCH_KEY_PATH; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_OAUTH_SECRET_KEY; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_PRE_LOGIN_PATH; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_SVC_CODE; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_SVC_PHASE; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_AUTH_PATH; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_SESSION_LOGIN_PATH; -import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.LG_API_V2_USER_INFO; +import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.*; import java.io.IOException; import java.net.URLEncoder; @@ -90,17 +73,17 @@ record LoginAccountResult(String userIdType, String userId, String country, Stri private Map getGatewayRestHeader(String language, String country) { return Map.ofEntries(new AbstractMap.SimpleEntry("Accept", "application/json"), - new AbstractMap.SimpleEntry("x-api-key", LG_API_API_KEY_V2), - new AbstractMap.SimpleEntry("x-country-code", country), - new AbstractMap.SimpleEntry("x-client-id", LG_API_CLIENT_ID), - new AbstractMap.SimpleEntry("x-language-code", language), - new AbstractMap.SimpleEntry("x-message-id", LG_API_MESSAGE_ID), - new AbstractMap.SimpleEntry("x-service-code", LG_API_SVC_CODE), - new AbstractMap.SimpleEntry("x-service-phase", LG_API_SVC_PHASE), - new AbstractMap.SimpleEntry("x-thinq-app-level", LG_API_APP_LEVEL), - new AbstractMap.SimpleEntry("x-thinq-app-os", LG_API_APP_OS), - new AbstractMap.SimpleEntry("x-thinq-app-type", LG_API_APP_TYPE), - new AbstractMap.SimpleEntry("x-thinq-app-ver", LG_API_APP_VER)); + new AbstractMap.SimpleEntry<>("x-api-key", LG_API_API_KEY_V2), + new AbstractMap.SimpleEntry<>("x-country-code", country), + new AbstractMap.SimpleEntry<>("x-client-id", LG_API_CLIENT_ID), + new AbstractMap.SimpleEntry<>("x-language-code", language), + new AbstractMap.SimpleEntry<>("x-message-id", LG_API_MESSAGE_ID), + new AbstractMap.SimpleEntry<>("x-service-code", LG_API_SVC_CODE), + new AbstractMap.SimpleEntry<>("x-service-phase", LG_API_SVC_PHASE), + new AbstractMap.SimpleEntry<>("x-thinq-app-level", LG_API_APP_LEVEL), + new AbstractMap.SimpleEntry<>("x-thinq-app-os", LG_API_APP_OS), + new AbstractMap.SimpleEntry<>("x-thinq-app-type", LG_API_APP_TYPE), + new AbstractMap.SimpleEntry<>("x-thinq-app-ver", LG_API_APP_VER)); } private Map getLoginHeader(LGThinqGateway gw) { @@ -123,7 +106,7 @@ private Map getLoginHeader(LGThinqGateway gw) { return headers; } - public LGThinqGateway discoverGatewayConfiguration(String gwUrl, String language, String country, + LGThinqGateway discoverGatewayConfiguration(String gwUrl, String language, String country, String alternativeEmpServer) throws IOException { Map header = getGatewayRestHeader(language, country); RestResult result; @@ -145,7 +128,7 @@ public LGThinqGateway discoverGatewayConfiguration(String gwUrl, String language } } - public PreLoginResult preLoginUser(LGThinqGateway gw, String username, String password) throws IOException { + PreLoginResult preLoginUser(LGThinqGateway gw, String username, String password) throws IOException { String encPwd = RestUtils.getPreLoginEncPwd(password); Map headers = getLoginHeader(gw); // 1) Doing preLogin -> getting the password key @@ -174,7 +157,7 @@ public PreLoginResult preLoginUser(LGThinqGateway gw, String username, String pa "Unexpected login json result. Node 'signature' not found")); } - public LoginAccountResult loginUser(LGThinqGateway gw, PreLoginResult preLoginResult) throws IOException { + LoginAccountResult loginUser(LGThinqGateway gw, PreLoginResult preLoginResult) throws IOException { // 2 - Login with username and hashed password Map headers = getLoginHeader(gw); headers.put("X-Signature", preLoginResult.signature()); @@ -267,7 +250,7 @@ private Map getOauthEmpHeaders(LoginAccountResult accountResult, return oauthEmpHeaders; } - public UserInfo getUserInfo(TokenResult token) throws IOException { + UserInfo getUserInfo(TokenResult token) throws IOException { UriBuilder builder = UriBuilder.fromUri(token.getOauthBackendUrl()).path(LG_API_V2_USER_INFO); String oauthUrl = builder.build().toURL().toString(); String timestamp = getCurrentTimestamp(); @@ -307,7 +290,7 @@ private UserInfo handleAccountInfoResult(RestResult resp) throws IOException { Objects.requireNonNullElse(accountInfo.get("displayUserID"), "")); } - public TokenResult doRefreshToken(TokenResult currentToken) throws IOException, RefreshTokenException { + TokenResult doRefreshToken(TokenResult currentToken) throws IOException, RefreshTokenException { UriBuilder builder = UriBuilder.fromUri(currentToken.getOauthBackendUrl()).path(LG_API_V2_AUTH_PATH); String oauthUrl = builder.build().toURL().toString(); String timestamp = getCurrentTimestamp(); From 375aef6b082f27c47e7f66464f6020208dddce4d Mon Sep 17 00:00:00 2001 From: Nemer Daud Date: Mon, 23 Dec 2024 22:29:36 -0300 Subject: [PATCH 130/130] [lgthinq][fix] review fixes [lgthinq][fix] Refrigerator channel command handle fixes Signed-off-by: Nemer Daud --- .../internal/LGThinQBindingConstants.java | 45 +++---- .../internal/LGThinQBridgeConfiguration.java | 28 +--- .../internal/LGThinQHandlerFactory.java | 11 -- .../discovery/LGThinqDiscoveryService.java | 4 +- .../handler/LGThinQAbstractDeviceHandler.java | 9 +- .../handler/LGThinQAirConditionerHandler.java | 3 +- .../internal/handler/LGThinQBridge.java | 14 +- .../handler/LGThinQBridgeHandler.java | 4 +- .../handler/LGThinQDishWasherHandler.java | 8 +- .../handler/LGThinQFridgeHandler.java | 83 ++++++------ .../handler/LGThinQWasherDryerHandler.java | 28 ++-- .../lgthinq/internal/model/DataType.java | 56 -------- .../internal/model/DeviceParameter.java | 86 ------------ .../internal/model/DeviceParameterGroup.java | 40 ------ .../lgthinq/internal/model/ThinqChannel.java | 115 ---------------- .../internal/model/ThinqChannelGroup.java | 81 ----------- .../lgthinq/internal/model/ThinqDevice.java | 73 ---------- .../type/ThinqChannelGroupTypeProvider.java | 32 ----- .../type/ThinqChannelTypeProvider.java | 27 ---- .../type/ThinqConfigDescriptionProvider.java | 28 ---- .../internal/type/ThinqThingTypeProvider.java | 28 ---- .../internal/type/ThinqTypesProviderImpl.java | 127 ------------------ .../lgthinq/internal/type/UidUtils.java | 68 ---------- .../lgservices/LGThinQACApiClientService.java | 86 +++++++++++- .../LGThinQAbstractApiClientService.java | 10 +- .../LGThinQAbstractApiV1ClientService.java | 3 +- .../LGThinQAbstractApiV2ClientService.java | 3 +- .../lgservices/LGThinQApiClientService.java | 85 +++++++++++- .../lgservices/LGThinQDRApiClientService.java | 30 ----- .../LGThinQDRApiV2ClientServiceImpl.java | 71 ---------- .../LGThinQDishWasherApiClientService.java | 12 +- ...ThinQDishWasherApiV1ClientServiceImpl.java | 5 - ...ThinQDishWasherApiV2ClientServiceImpl.java | 5 - .../LGThinQFridgeApiClientService.java | 57 +++++++- .../lgservices/LGThinQWMApiClientService.java | 19 ++- .../api/LGThinqCanonicalModelUtil.java | 15 ++- .../lgservices/api/LGThinqGateway.java | 2 +- .../api/LGThinqOauthEmpAuthenticator.java | 12 +- .../errors/LGThinqAccessException.java | 21 +++ .../lgthinq/lgservices/model/DeviceTypes.java | 6 +- .../lgthinq/lgservices/model/ModelUtils.java | 2 +- .../lgthinq/lgservices/model/ResultCodes.java | 122 +++++------------ .../AbstractFridgeCapabilityFactory.java | 2 +- .../fridge/FridgeCapabilityFactoryV2.java | 6 +- .../devices/fridge/FridgeSnapshotBuilder.java | 4 +- .../AbstractWasherDryerCapabilityFactory.java | 14 +- .../resources/OH-INF/i18n/lgthinq.properties | 4 + .../main/resources/OH-INF/thing/channels.xml | 12 +- .../main/resources/OH-INF/thing/fridge.xml | 8 +- 49 files changed, 463 insertions(+), 1151 deletions(-) delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiClientService.java delete mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java create mode 100644 bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqAccessException.java diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java index 48d672483516c..42b091ee39fbe 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBindingConstants.java @@ -46,8 +46,7 @@ public class LGThinQBindingConstants extends LGServicesConstants { public static final ThingTypeUID THING_TYPE_DRYER_TOWER = new ThingTypeUID(BINDING_ID, DeviceTypes.DRYER_TOWER.thingTypeId()); - public static final ThingTypeUID THING_TYPE_FRIDGE = new ThingTypeUID(BINDING_ID, - DeviceTypes.REFRIGERATOR.thingTypeId()); + public static final ThingTypeUID THING_TYPE_FRIDGE = new ThingTypeUID(BINDING_ID, DeviceTypes.FRIDGE.thingTypeId()); public static final ThingTypeUID THING_TYPE_DISHWASHER = new ThingTypeUID(BINDING_ID, DeviceTypes.DISH_WASHER.thingTypeId()); public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_AIR_CONDITIONER, @@ -97,36 +96,36 @@ public class LGThinQBindingConstants extends LGServicesConstants { public static final String CHANNEL_AC_TARGET_TEMP_ID = "target-temperature"; /** - * ============ Refrigerator's Channels & Constant Definition ============= + * ============ Fridge's Channels & Constant Definition ============= */ - public static final String CHANNEL_RE_ACTIVE_SAVING = "fr-active-saving"; - public static final String CHANNEL_RE_DOOR_OPEN = "some-door-open"; - public static final String CHANNEL_RE_EXPRESS_COOL_MODE = "fr-express-cool-mode"; - public static final String CHANNEL_RE_EXPRESS_FREEZE_MODE = "fr-express-mode"; - public static final String CHANNEL_RE_FREEZER_TEMP_ID = "freezer-temperature"; - public static final String CHANNEL_RE_FRESH_AIR_FILTER = "fr-fresh-air-filter"; - public static final String CHANNEL_RE_FRIDGE_TEMP_ID = "fridge-temperature"; - public static final String CHANNEL_RE_ICE_PLUS = "fr-ice-plus"; - public static final String CHANNEL_RE_REF_TEMP_UNIT = "temp-unit"; - public static final String CHANNEL_RE_SMART_SAVING_MODE_V2 = "fr-smart-saving-mode"; - public static final String CHANNEL_RE_SMART_SAVING_SWITCH_V1 = "fr-smart-saving-switch"; - public static final String CHANNEL_RE_VACATION_MODE = "fr-eco-friendly-mode"; - public static final String CHANNEL_RE_WATER_FILTER = "fr-water-filter"; + public static final String CHANNEL_FR_ACTIVE_SAVING = "fr-active-saving"; + public static final String CHANNEL_FR_DOOR_OPEN = "fr-some-door-open"; + public static final String CHANNEL_FR_EXPRESS_COOL_MODE = "fr-express-cool-mode"; + public static final String CHANNEL_FR_EXPRESS_FREEZE_MODE = "fr-express-mode"; + public static final String CHANNEL_FR_FREEZER_TEMP_ID = "fr-freezer-temperature"; + public static final String CHANNEL_FR_FRESH_AIR_FILTER = "fr-fresh-air-filter"; + public static final String CHANNEL_FR_FRIDGE_TEMP_ID = "fr-fridge-temperature"; + public static final String CHANNEL_FR_ICE_PLUS = "fr-ice-plus"; + public static final String CHANNEL_FR_REF_TEMP_UNIT = "fr-temp-unit"; + public static final String CHANNEL_FR_SMART_SAVING_MODE_V2 = "fr-smart-saving-mode"; + public static final String CHANNEL_FR_SMART_SAVING_SWITCH_V1 = "fr-smart-saving-switch"; + public static final String CHANNEL_FR_VACATION_MODE = "fr-eco-friendly-mode"; + public static final String CHANNEL_FR_WATER_FILTER = "fr-water-filter"; /** * ============ Washing Machine/Dryer and DishWasher Channels & Constant Definition ============= * DishWasher, Washing Machine and Dryer have the same channel core and features */ - public static final String CHANNEL_DR_CHILD_LOCK_ID = "child-lock"; - public static final String CHANNEL_DR_DRY_LEVEL_ID = "dry-level"; + public static final String CHANNEL_WMD_CHILD_LOCK_ID = "child-lock"; + public static final String CHANNEL_WMD_DRY_LEVEL_ID = "dry-level"; public static final String CHANNEL_WMD_COURSE_ID = "course"; public static final String CHANNEL_WMD_DELAY_TIME_ID = "delay-time"; public static final String CHANNEL_WMD_DOOR_LOCK_ID = "door-lock"; public static final String CHANNEL_WMD_PROCESS_STATE_ID = "process-state"; public static final String CHANNEL_WMD_REMAIN_TIME_ID = "remain-time"; public static final String CHANNEL_WMD_REMOTE_COURSE = "rs-course"; - public static final String CHANNEL_WMD_REMOTE_START_GRP_ID = "remote-start-grp"; - public static final String CHANNEL_WMD_REMOTE_START_ID = "remote-start-flag"; + public static final String CHANNEL_WMD_REMOTE_START_GRP_ID = "rs-grp"; + public static final String CHANNEL_WMD_REMOTE_START_ID = "rs-flag"; public static final String CHANNEL_WMD_REMOTE_START_START_STOP = "rs-start-stop"; public static final String CHANNEL_WMD_RINSE_ID = "rinse"; public static final String CHANNEL_WMD_SMART_COURSE_ID = "smart-course"; @@ -134,9 +133,9 @@ public class LGThinQBindingConstants extends LGServicesConstants { public static final String CHANNEL_WMD_STAND_BY_ID = "stand-by"; public static final String CHANNEL_WMD_STATE_ID = "state"; public static final String CHANNEL_WMD_TEMP_LEVEL_ID = "temperature-level"; - public static final String CHANNEL_WM_REMOTE_START_RINSE = "rs-rinse"; - public static final String CHANNEL_WM_REMOTE_START_SPIN = "rs-spin"; - public static final String CHANNEL_WM_REMOTE_START_TEMP = "rs-temperature-level"; + public static final String CHANNEL_WMD_REMOTE_START_RINSE = "rs-rinse"; + public static final String CHANNEL_WMD_REMOTE_START_SPIN = "rs-spin"; + public static final String CHANNEL_WMD_REMOTE_START_TEMP = "rs-temperature-level"; // ============================================================================== // DIGEST CONSTANTS diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java index e748971376ff6..024a83bcfd0e7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQBridgeConfiguration.java @@ -28,8 +28,8 @@ public class LGThinQBridgeConfiguration { public String password = ""; public String country = ""; public String language = ""; - public final String manualCountry = ""; - public final String manualLanguage = ""; + public String manualCountry = ""; + public String manualLanguage = ""; public int pollingIntervalSec = 0; public String alternativeServer = ""; @@ -72,31 +72,7 @@ public int getPollingIntervalSec() { return pollingIntervalSec; } - public void setUsername(String username) { - this.username = username; - } - - public void setPassword(String password) { - this.password = password; - } - - public void setCountry(String country) { - this.country = country; - } - - public void setLanguage(String language) { - this.language = language; - } - - public void setPollingIntervalSec(int pollingIntervalSec) { - this.pollingIntervalSec = pollingIntervalSec; - } - public String getAlternativeServer() { return alternativeServer; } - - public void setAlternativeServer(String alternativeServer) { - this.alternativeServer = alternativeServer; - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java index 1c958820414a2..197ffc82c553a 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/LGThinQHandlerFactory.java @@ -31,8 +31,6 @@ import org.openhab.binding.lgthinq.internal.handler.LGThinQDishWasherHandler; import org.openhab.binding.lgthinq.internal.handler.LGThinQFridgeHandler; import org.openhab.binding.lgthinq.internal.handler.LGThinQWasherDryerHandler; -import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; -import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.core.config.core.Configuration; import org.openhab.core.io.net.http.HttpClientFactory; import org.openhab.core.thing.Bridge; @@ -65,12 +63,6 @@ public class LGThinQHandlerFactory extends BaseThingHandlerFactory { private final LGThinQStateDescriptionProvider stateDescriptionProvider; - @Nullable - @Reference - protected ThinqChannelTypeProvider thinqChannelProvider; - @Nullable - @Reference - protected ThinqChannelGroupTypeProvider thinqChannelGroupProvider; @Nullable @Reference protected ItemChannelLinkRegistry itemChannelLinkRegistry; @@ -97,18 +89,15 @@ public boolean supportsThingType(ThingTypeUID thingTypeUID) { return new LGThinQBridgeHandler((Bridge) thing, httpClientFactory); } else if (THING_TYPE_WASHING_MACHINE.equals(thingTypeUID) || THING_TYPE_WASHING_TOWER.equals(thingTypeUID)) { return new LGThinQWasherDryerHandler(thing, stateDescriptionProvider, - Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } else if (THING_TYPE_DRYER.equals(thingTypeUID) || THING_TYPE_DRYER_TOWER.equals(thingTypeUID)) { return new LGThinQWasherDryerHandler(thing, stateDescriptionProvider, - Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } else if (THING_TYPE_FRIDGE.equals(thingTypeUID)) { return new LGThinQFridgeHandler(thing, stateDescriptionProvider, Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } else if (THING_TYPE_DISHWASHER.equals(thingTypeUID)) { return new LGThinQDishWasherHandler(thing, stateDescriptionProvider, - Objects.requireNonNull(thinqChannelProvider), Objects.requireNonNull(thinqChannelGroupProvider), Objects.requireNonNull(itemChannelLinkRegistry), httpClientFactory); } logger.warn("Thing not supported by this Factory: {}", thingTypeUID.getId()); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java index 9a7f8c8e324cd..b7dcd1d162419 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/discovery/LGThinqDiscoveryService.java @@ -51,7 +51,7 @@ import org.slf4j.LoggerFactory; /** - * The {@link LGThinqDiscoveryService} + * The {@link LGThinqDiscoveryService} - Responsable to discovery new LG Thinq Devices for the registered Bridge * * @author Nemer Daud - Initial contribution */ @@ -147,7 +147,7 @@ private ThingTypeUID getThingTypeUID(LGDevice device) throws LGThinqException { case WASHER_TOWER -> THING_TYPE_WASHING_TOWER; case DRYER_TOWER -> THING_TYPE_DRYER_TOWER; case DRYER -> THING_TYPE_DRYER; - case REFRIGERATOR -> THING_TYPE_FRIDGE; + case FRIDGE -> THING_TYPE_FRIDGE; case DISH_WASHER -> THING_TYPE_DISHWASHER; default -> throw new LGThinqException(String.format("device type [%s] not supported", device.getDeviceType())); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java index 854cecaeb5be2..40e91caee1390 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAbstractDeviceHandler.java @@ -42,9 +42,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; -import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqAccessException; import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiExhaustionException; import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1MonitorExpiredException; @@ -114,10 +113,6 @@ public abstract class LGThinQAbstractDeviceHandler<@NonNull C extends Capability ThingStatusDetail.CONFIGURATION_ERROR); protected final LGThinQStateDescriptionProvider stateDescriptionProvider; - @Nullable - protected ThinqChannelTypeProvider thinqChannelProvider; - @Nullable - protected ThinqChannelGroupTypeProvider thinqChannelGroupProvider; protected S getLastShot() { return Objects.requireNonNull(lastShot, "LastShot shouldn't be null. It most likely a bug."); @@ -513,6 +508,8 @@ protected void updateThingStateFromLG() { updateStatus(ThingStatus.ONLINE); } + } catch (LGThinqAccessException e) { + updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage()); } catch (LGThinqApiExhaustionException e) { fetchMonitorRetries++; getLogger().warn("LG API returns null monitoring data for the thing {}/{}. No data available yet ?", diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java index 9057fba69676b..1f0786b5b45d1 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQAirConditionerHandler.java @@ -86,8 +86,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGThinQAirConditionerHandler} is responsible for handling commands, which are - * sent to one of the channels. + * The {@link LGThinQAirConditionerHandler} Handle Air Conditioner and HeatPump Things * * @author Nemer Daud - Initial contribution */ diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java index 526a8841bff1c..81f928803f5ac 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridge.java @@ -18,22 +18,32 @@ import org.openhab.binding.lgthinq.lgservices.model.SnapshotDefinition; /** - * The {@link LGThinQBridge} + * The {@link LGThinQBridge} - Specific methods for discovery integration * * @author Nemer Daud - Initial contribution */ @NonNullByDefault public interface LGThinQBridge { /** - * Register + * Register Discovery Listener * * @param listener */ void registerDiscoveryListener(LGThinqDiscoveryService listener); + /** + * Registry a device Thing to the bridge + * + * @param thing Thing to be registered. + */ void registryListenerThing( LGThinQAbstractDeviceHandler thing); + /** + * Unregistry the thing + * + * @param thing to be unregistered + */ void unRegistryListenerThing( LGThinQAbstractDeviceHandler thing); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java index 6a00165667896..6cc72119b73b2 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQBridgeHandler.java @@ -54,7 +54,8 @@ import org.slf4j.LoggerFactory; /** - * The {@link LGThinQBridgeHandler} + * The {@link LGThinQBridgeHandler} - connect to the LG Account and get information about the user and registered + * devices of that user. * * @author Nemer Daud - Initial contribution */ @@ -129,6 +130,7 @@ public void run() { } catch (RefreshTokenException e) { updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR, "@text/error.toke-refresh"); + logger.error("Error refreshing token", e); return; } } else { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java index 4ecf4cfbd8e4a..05eae198b51f7 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQDishWasherHandler.java @@ -34,8 +34,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; -import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQDishWasherApiClientService; @@ -56,8 +54,7 @@ import org.slf4j.LoggerFactory; /** - * The {@link LGThinQDishWasherHandler} is responsible for handling commands, which are - * sent to one of the channels. + * The {@link LGThinQDishWasherHandler} Handle the Dish Washer Things * * @author Nemer Daud - Initial contribution */ @@ -78,11 +75,8 @@ public class LGThinQDishWasherHandler extends LGThinQAbstractDeviceHandler getTemperatureUnit(FridgeCanonicalSnapshot shot) { @@ -198,8 +197,7 @@ protected Integer decodeTempValue(ChannelUID ch, Integer value) { return 0; } // temperature channels are little different. First we need to get the tempUnit in the first snapshot, - Map convertionMap = new HashMap<>(); - convertionMap = getConvertionMap(ch, refCap); + Map convertionMap = getConvertionMap(ch, refCap); String strValue = convertionMap.get(value.toString()); if (strValue == null) { logger.error( @@ -224,8 +222,7 @@ protected Integer encodeTempValue(ChannelUID ch, Integer value) { return 0; } // temperature channels are little different. First we need to get the tempUnit in the first snapshot, - final Map convertionMap = new HashMap<>(); - getConvertionMap(ch, refCap); + final Map convertionMap = getConvertionMap(ch, refCap); final Map invertedMap = new HashMap<>(); convertionMap.forEach((k, v) -> { invertedMap.put(v, k); @@ -293,12 +290,12 @@ public void onDeviceDisconnected() { @Override public void updateChannelDynStateDescription() throws LGThinqApiException { FridgeCapability cap = getCapabilities(); - manageDynChannel(icePlusChannelUID, CHANNEL_RE_ICE_PLUS, "Switch", !cap.getIcePlusMap().isEmpty()); - manageDynChannel(expressFreezeModeChannelUID, CHANNEL_RE_EXPRESS_FREEZE_MODE, "String", + manageDynChannel(icePlusChannelUID, CHANNEL_FR_ICE_PLUS, "Switch", !cap.getIcePlusMap().isEmpty()); + manageDynChannel(expressFreezeModeChannelUID, CHANNEL_FR_EXPRESS_FREEZE_MODE, "String", !cap.getExpressFreezeModeMap().isEmpty()); - manageDynChannel(expressCoolModeChannelUID, CHANNEL_RE_EXPRESS_COOL_MODE, "Switch", + manageDynChannel(expressCoolModeChannelUID, CHANNEL_FR_EXPRESS_COOL_MODE, "Switch", cap.isExpressCoolModePresent()); - manageDynChannel(vacationModeChannelUID, CHANNEL_RE_VACATION_MODE, "Switch", cap.isEcoFriendlyModePresent()); + manageDynChannel(vacationModeChannelUID, CHANNEL_FR_VACATION_MODE, "Switch", cap.isEcoFriendlyModePresent()); Unit unTemp = getTemperatureUnit(getLastShot()); if (SIUnits.CELSIUS.equals(unTemp)) { @@ -344,8 +341,8 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept String simpleChannelUID; simpleChannelUID = getSimpleChannelUID(params.channelUID); switch (simpleChannelUID) { - case CHANNEL_RE_FREEZER_TEMP_ID: - case CHANNEL_RE_FRIDGE_TEMP_ID: { + case CHANNEL_FR_FREEZER_TEMP_ID: + case CHANNEL_FR_FRIDGE_TEMP_ID: { int targetTemp; if (command instanceof DecimalType) { targetTemp = ((DecimalType) command).intValue(); @@ -356,7 +353,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept break; } - if (CHANNEL_RE_FRIDGE_TEMP_ID.equals(simpleChannelUID)) { + if (CHANNEL_FR_FRIDGE_TEMP_ID.equals(simpleChannelUID)) { targetTemp = encodeTempValue(fridgeTempChannelUID, targetTemp); lgThinqFridgeApiClientService.setFridgeTemperature(getBridgeId(), getDeviceId(), getCapabilities(), targetTemp, lastShot.getTempUnit(), cmdSnap); @@ -367,7 +364,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept } break; } - case CHANNEL_RE_ICE_PLUS: { + case CHANNEL_FR_ICE_PLUS: { if (command instanceof OnOffType) { lgThinqFridgeApiClientService.setIcePlus(getBridgeId(), getDeviceId(), getCapabilities(), OnOffType.ON.equals(command), cmdSnap); @@ -376,7 +373,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept } break; } - case CHANNEL_RE_EXPRESS_FREEZE_MODE: { + case CHANNEL_FR_EXPRESS_FREEZE_MODE: { String targetExpressMode; if (command instanceof StringType) { targetExpressMode = ((StringType) command).toString(); @@ -388,7 +385,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept lgThinqFridgeApiClientService.setExpressMode(getBridgeId(), getDeviceId(), targetExpressMode); break; } - case CHANNEL_RE_EXPRESS_COOL_MODE: { + case CHANNEL_FR_EXPRESS_COOL_MODE: { if (command instanceof OnOffType) { lgThinqFridgeApiClientService.setExpressCoolMode(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); @@ -398,7 +395,7 @@ protected void processCommand(AsyncCommandParams params) throws LGThinqApiExcept } break; } - case CHANNEL_RE_VACATION_MODE: { + case CHANNEL_FR_VACATION_MODE: { if (command instanceof OnOffType) { lgThinqFridgeApiClientService.setEcoFriendlyMode(getBridgeId(), getDeviceId(), OnOffType.ON.equals(command)); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java index 792ec5c976f24..86d6ea45a2282 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/handler/LGThinQWasherDryerHandler.java @@ -14,26 +14,26 @@ import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_AC_POWER_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DASHBOARD_GRP_ID; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DR_CHILD_LOCK_ID; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_DR_DRY_LEVEL_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_CHILD_LOCK_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_COURSE_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_DELAY_TIME_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_DOOR_LOCK_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_DRY_LEVEL_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_PROCESS_STATE_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMAIN_TIME_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_COURSE; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_GRP_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_ID; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_RINSE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_SPIN; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_START_STOP; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_TEMP; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_RINSE_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_SMART_COURSE_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_SPIN_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_STAND_BY_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_STATE_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_TEMP_LEVEL_ID; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_RINSE; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_SPIN; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_TEMP; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_DEVICE_ALIAS; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.PROP_INFO_MODEL_URL_INFO; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.THING_TYPE_DRYER; @@ -60,8 +60,6 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; import org.openhab.binding.lgthinq.internal.LGThinQStateDescriptionProvider; -import org.openhab.binding.lgthinq.internal.type.ThinqChannelGroupTypeProvider; -import org.openhab.binding.lgthinq.internal.type.ThinqChannelTypeProvider; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientService; import org.openhab.binding.lgthinq.lgservices.LGThinQApiClientServiceFactory; import org.openhab.binding.lgthinq.lgservices.LGThinQWMApiClientService; @@ -92,8 +90,7 @@ import org.slf4j.LoggerFactory; /** - * The {@link LGThinQWasherDryerHandler} is responsible for handling commands, which are - * sent to one of the channels. + * The {@link LGThinQWasherDryerHandler} Handle Washer/Dryer And Washer Dryer Towers things * * @author Nemer Daud - Initial contribution */ @@ -127,25 +124,22 @@ public class LGThinQWasherDryerHandler private final LGThinQWMApiClientService lgThinqWMApiClientService; public LGThinQWasherDryerHandler(Thing thing, LGThinQStateDescriptionProvider stateDescriptionProvider, - ThinqChannelTypeProvider channelTypeProvider, ThinqChannelGroupTypeProvider channelGroupTypeProvider, ItemChannelLinkRegistry itemChannelLinkRegistry, HttpClientFactory httpClientFactory) { super(thing, stateDescriptionProvider, itemChannelLinkRegistry); - this.thinqChannelGroupProvider = channelGroupTypeProvider; - this.thinqChannelProvider = channelTypeProvider; this.stateDescriptionProvider = stateDescriptionProvider; lgThinqWMApiClientService = LGThinQApiClientServiceFactory.newWMApiClientService(lgPlatformType, httpClientFactory); channelGroupRemoteStartUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_WMD_REMOTE_START_GRP_ID); channelGroupDashboardUID = new ChannelGroupUID(getThing().getUID(), CHANNEL_DASHBOARD_GRP_ID); courseChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_COURSE_ID); - dryLevelChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_DR_DRY_LEVEL_ID); + dryLevelChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_DRY_LEVEL_ID); stateChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_STATE_ID); processStateChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_PROCESS_STATE_ID); remainTimeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_REMAIN_TIME_ID); delayTimeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_DELAY_TIME_ID); temperatureChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_TEMP_LEVEL_ID); doorLockChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_DOOR_LOCK_ID); - childLockChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_DR_CHILD_LOCK_ID); + childLockChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_CHILD_LOCK_ID); rinseChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_RINSE_ID); spinChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_SPIN_ID); standByModeChannelUID = new ChannelUID(channelGroupDashboardUID, CHANNEL_WMD_STAND_BY_ID); @@ -369,13 +363,13 @@ private Map getRemoteStartData() throws LGThinqApiException { String value = Objects.requireNonNullElse(getItemLinkedValue(c.getUID()), ""); String simpleChannelUID = getSimpleChannelUID(c.getUID().getId()); switch (simpleChannelUID) { - case CHANNEL_WM_REMOTE_START_RINSE: + case CHANNEL_WMD_REMOTE_START_RINSE: data.put(cap.getRinseFeat().getName(), value); break; - case CHANNEL_WM_REMOTE_START_TEMP: + case CHANNEL_WMD_REMOTE_START_TEMP: data.put(cap.getTemperatureFeat().getName(), value); break; - case CHANNEL_WM_REMOTE_START_SPIN: + case CHANNEL_WMD_REMOTE_START_SPIN: data.put(cap.getSpinFeat().getName(), value); break; default: diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java deleted file mode 100644 index b841aff1583b3..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DataType.java +++ /dev/null @@ -1,56 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.model; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.types.StateOption; - -/** - * The {@link DataType} class. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class DataType { - private final String name; - private final boolean isNumeric; - private final boolean isEnum; - @Nullable - private final List options; - - public DataType(String name, boolean isNumeric, boolean isEnum, @Nullable List options) { - this.name = name; - this.isNumeric = isNumeric; - this.isEnum = isEnum; - this.options = options; - } - - public @Nullable List getOptions() { - return options; - } - - public String getName() { - return name; - } - - public boolean isNumeric() { - return isNumeric; - } - - public boolean isEnum() { - return isEnum; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java deleted file mode 100644 index 0a59b00de31e5..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameter.java +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.model; - -import java.util.ArrayList; -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.config.core.ConfigDescriptionParameter.Type; -import org.openhab.core.config.core.ParameterOption; - -/** - * The {@link DeviceParameter} class. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class DeviceParameter { - - private final String name; - private final Type type; - private final String label; - private final String description; - private final String defaultValue; - private DeviceParameterGroup group = new DeviceParameterGroup("", ""); - private final List options = new ArrayList(); - - private final boolean isReadOnly; - - public DeviceParameter(String name, Type type, String label, String description, String defaultValue, - List options, boolean isReadOnly) { - this.name = name; - this.type = type; - this.label = label; - this.description = description; - this.defaultValue = defaultValue; - this.options.addAll(options); - this.isReadOnly = isReadOnly; - } - - public DeviceParameterGroup getGroup() { - return group; - } - - public void setGroup(DeviceParameterGroup group) { - this.group = group; - } - - public List getOptions() { - return options; - } - - public boolean isReadOnly() { - return isReadOnly; - } - - public String getName() { - return name; - } - - public Type getType() { - return type; - } - - public String getLabel() { - return label; - } - - public String getDescription() { - return description; - } - - public String getDefaultValue() { - return defaultValue; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java deleted file mode 100644 index 882c76d00fd63..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/DeviceParameterGroup.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.model; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link DeviceParameterGroup} class. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class DeviceParameterGroup { - - private final String groupName; - private final String groupLabel; - - public DeviceParameterGroup(String groupName, String groupLabel) { - this.groupName = groupName; - this.groupLabel = groupLabel; - } - - public String getGroupName() { - return groupName; - } - - public String getGroupLabel() { - return groupLabel; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java deleted file mode 100644 index 980f534e459d9..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannel.java +++ /dev/null @@ -1,115 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.model; - -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link ThinqChannel} class. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ThinqChannel { - - @Nullable - ThinqDevice device; - private final DataType type; - @Nullable - private final String unitDisplayPattern; - private final String name; - private final String label; - private final String description; - private final boolean isDynamic; - private final boolean isReadOnly; - private final boolean isAdvanced; - @Nullable - private final ThinqChannelGroup channelGroup; - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ThinqChannel that = (ThinqChannel) o; - return Objects.equals(name, that.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } - - public ThinqChannel(DataType type, @Nullable String unitDisplayPattern, String name, String label, - String description, boolean isDynamic, boolean isReadOnly, boolean isAdvanced, - @Nullable ThinqChannelGroup channelGroup) { - this.type = type; - this.unitDisplayPattern = unitDisplayPattern; - this.name = name; - this.label = label; - this.description = description; - this.isDynamic = isDynamic; - this.isReadOnly = isReadOnly; - this.isAdvanced = isAdvanced; - this.channelGroup = channelGroup; - if (channelGroup != null && !channelGroup.getChannels().contains(this)) { - channelGroup.getChannels().add(this); - } - } - - public @Nullable ThinqChannelGroup getChannelGroup() { - return channelGroup; - } - - public boolean isAdvanced() { - return isAdvanced; - } - - public String getLabel() { - return label; - } - - public boolean isReadOnly() { - return isReadOnly; - } - - public @Nullable String getUnitDisplayPattern() { - return unitDisplayPattern; - } - - public @Nullable ThinqDevice getDevice() { - return device; - } - - public DataType getType() { - return type; - } - - public boolean isDynamic() { - return isDynamic; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java deleted file mode 100644 index fb3691348f02c..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqChannelGroup.java +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.model; - -import java.util.List; -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; - -/** - * The {@link ThinqChannelGroup} class. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ThinqChannelGroup { - private final List channels; - private ThinqDevice device; - private final String name; - private final String description; - private final String label; - - public ThinqChannelGroup(List channels, ThinqDevice device, String name, String description, - String label) { - this.channels = channels; - this.device = device; - this.name = name; - this.description = description; - this.label = label; - } - - @Override - public boolean equals(@Nullable Object o) { - if (this == o) - return true; - if (o == null || getClass() != o.getClass()) - return false; - ThinqChannelGroup that = (ThinqChannelGroup) o; - return Objects.equals(name, that.name); - } - - @Override - public int hashCode() { - return Objects.hash(name); - } - - public String getLabel() { - return label; - } - - public List getChannels() { - return channels; - } - - public ThinqDevice getDevice() { - return device; - } - - public void setDevice(ThinqDevice device) { - this.device = device; - } - - public String getName() { - return name; - } - - public String getDescription() { - return description; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java deleted file mode 100644 index c1dc92f86b7b3..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/model/ThinqDevice.java +++ /dev/null @@ -1,73 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.model; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; - -/** - * The {@link ThinqDevice} class. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class ThinqDevice { - private final String type; - private final String label; - private final String description; - - private final List channels; - private final List configParameter; - private final List groups; - - public String getType() { - return type; - } - - public String getDescription() { - return description; - } - - public List getChannels() { - return channels; - } - - public List getGroups() { - return groups; - } - - public ThinqDevice(String type, String label, String description, List channels, - List configParameter, List groups) { - this.type = type; - this.label = label; - this.description = description; - this.channels = channels; - this.configParameter = configParameter; - this.groups = groups; - this.channels.forEach(c -> { - c.device = this; - }); - this.groups.forEach(g -> { - g.setDevice(this); - }); - } - - public String getLabel() { - return label; - } - - public List getConfigParameter() { - return configParameter; - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java deleted file mode 100644 index 952ef769785d4..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelGroupTypeProvider.java +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.type; - -import java.util.List; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.thing.type.ChannelGroupType; -import org.openhab.core.thing.type.ChannelGroupTypeProvider; - -/** - * The ThinqChannelGroupTypeProvider interface. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface ThinqChannelGroupTypeProvider extends ChannelGroupTypeProvider { - - void addChannelGroupType(ChannelGroupType channelGroupType); - - List internalGroupTypes(); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java deleted file mode 100644 index d291b21e26222..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqChannelTypeProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.type; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.thing.type.ChannelType; -import org.openhab.core.thing.type.ChannelTypeProvider; - -/** - * The ThinqChannelTypeProvider interface. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface ThinqChannelTypeProvider extends ChannelTypeProvider { - void addChannelType(final ChannelType channelType); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java deleted file mode 100644 index e2c699faf8ddc..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqConfigDescriptionProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.type; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.config.core.ConfigDescription; -import org.openhab.core.config.core.ConfigDescriptionProvider; - -/** - * The ThinqConfigDescriptionProvider interface. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface ThinqConfigDescriptionProvider extends ConfigDescriptionProvider { - - void addConfigDescription(ConfigDescription configDescription); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java deleted file mode 100644 index baa2c9bbc1c75..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqThingTypeProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.type; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.core.thing.binding.ThingTypeProvider; -import org.openhab.core.thing.type.ThingType; - -/** - * The ThinqThingTypeProvider interface. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public interface ThinqThingTypeProvider extends ThingTypeProvider { - - void addThingType(ThingType thingType); -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java deleted file mode 100644 index 982b14213427b..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/ThinqTypesProviderImpl.java +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.type; - -import java.net.URI; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jdt.annotation.Nullable; -import org.openhab.core.config.core.ConfigDescription; -import org.openhab.core.config.core.ConfigDescriptionProvider; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.binding.ThingTypeProvider; -import org.openhab.core.thing.type.ChannelGroupType; -import org.openhab.core.thing.type.ChannelGroupTypeProvider; -import org.openhab.core.thing.type.ChannelGroupTypeUID; -import org.openhab.core.thing.type.ChannelType; -import org.openhab.core.thing.type.ChannelTypeProvider; -import org.openhab.core.thing.type.ChannelTypeUID; -import org.openhab.core.thing.type.ThingType; -import org.osgi.service.component.annotations.Component; - -/** - * Provider class to provide model types for custom things (not in XML). - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -@Component(service = { ThinqChannelTypeProvider.class, ChannelTypeProvider.class, ChannelGroupTypeProvider.class, - ThinqChannelGroupTypeProvider.class, ThinqConfigDescriptionProvider.class, ConfigDescriptionProvider.class, - ThinqThingTypeProvider.class, ThingTypeProvider.class }) -public class ThinqTypesProviderImpl implements ThinqChannelTypeProvider, ThinqChannelGroupTypeProvider, - ThinqConfigDescriptionProvider, ThinqThingTypeProvider { - - private final Map thingTypesByUID = new ConcurrentHashMap<>(); - private final Map channelTypesByUID = new ConcurrentHashMap<>(); - private final Map channelGroupTypesByUID = new ConcurrentHashMap<>(); - - private final Map configDescriptionsByURI = new ConcurrentHashMap<>(); - - @Override - public Collection getChannelTypes(@Nullable final Locale locale) { - return Collections.unmodifiableCollection(channelTypesByUID.values()); - } - - @Override - public @Nullable ChannelType getChannelType(final ChannelTypeUID channelTypeUID, @Nullable final Locale locale) { - return channelTypesByUID.get(channelTypeUID); - } - - /** - * Add a channel type for a user configured channel. - * - * @param channelType channelType - */ - @Override - public void addChannelType(final ChannelType channelType) { - channelTypesByUID.put(channelType.getUID(), channelType); - } - - @Override - @Nullable - public ChannelGroupType getChannelGroupType(ChannelGroupTypeUID channelGroupTypeUID, @Nullable Locale locale) { - return channelGroupTypesByUID.get(channelGroupTypeUID); - } - - @Override - public Collection getChannelGroupTypes(@Nullable Locale locale) { - return Collections.unmodifiableCollection(channelGroupTypesByUID.values()); - } - - @Override - public void addChannelGroupType(ChannelGroupType channelGroupType) { - channelGroupTypesByUID.put(channelGroupType.getUID(), channelGroupType); - } - - @Override - public List internalGroupTypes() { - return new ArrayList<>(channelGroupTypesByUID.values()); - } - - @Override - public void addConfigDescription(ConfigDescription configDescription) { - configDescriptionsByURI.put(configDescription.getUID(), configDescription); - } - - @Override - public Collection getConfigDescriptions(@Nullable Locale locale) { - return Collections.unmodifiableCollection(configDescriptionsByURI.values()); - } - - @Override - public @Nullable ConfigDescription getConfigDescription(URI uri, @Nullable Locale locale) { - return configDescriptionsByURI.get(uri); - } - - @Override - public void addThingType(ThingType thingType) { - thingTypesByUID.put(thingType.getUID(), thingType); - } - - @Override - public Collection getThingTypes(@Nullable Locale locale) { - return Collections.unmodifiableCollection(thingTypesByUID.values()); - } - - @Override - public @Nullable ThingType getThingType(ThingTypeUID thingTypeUID, @Nullable Locale locale) { - return thingTypesByUID.get(thingTypeUID); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java deleted file mode 100644 index 6ae02eda4fc5e..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/internal/type/UidUtils.java +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.internal.type; - -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.BINDING_ID; - -import java.util.Objects; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.lgthinq.internal.model.ThinqChannel; -import org.openhab.binding.lgthinq.internal.model.ThinqChannelGroup; -import org.openhab.binding.lgthinq.internal.model.ThinqDevice; -import org.openhab.core.thing.ChannelUID; -import org.openhab.core.thing.ThingTypeUID; -import org.openhab.core.thing.ThingUID; -import org.openhab.core.thing.type.ChannelGroupTypeUID; -import org.openhab.core.thing.type.ChannelTypeUID; - -/** - * Utility class for generating some UIDs. - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class UidUtils { - - /** - * Generates the ThingTypeUID for the given device. - */ - public static ThingTypeUID generateThingTypeUID(ThinqDevice device) { - return new ThingTypeUID(BINDING_ID, device.getType()); - } - - /** - * Generates the ChannelTypeUID. - */ - public static ChannelTypeUID generateChannelTypeUID(ThinqChannel channel) { - return new ChannelTypeUID(BINDING_ID, - String.format("%s_%s", - Objects.requireNonNull(channel.getDevice(), "unexpected null device type here").getType(), - channel.getName())); - } - - /** - * Generates the ChannelTypeUID for the given channel group. - */ - public static ChannelGroupTypeUID generateChannelGroupTypeUID(ThinqChannelGroup grpChannel) { - return new ChannelGroupTypeUID(BINDING_ID, - String.format("%s_%s", grpChannel.getDevice().getType(), grpChannel.getName())); - } - - /** - * Generates the ChannelUID for the given datapoint with channelNumber and datapointName. - */ - public static ChannelUID generateChannelUID(ThinqChannel dp, ThingUID thingUID) { - return new ChannelUID(thingUID, dp.getName(), dp.getName()); - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java index a5338c3aa2eae..f2c74224aba2d 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQACApiClientService.java @@ -20,32 +20,116 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.ac.ExtendedDeviceInfo; /** - * The {@link LGThinQACApiClientService} + * The {@link LGThinQACApiClientService} - Common interface to be used by the AC Handle to access LG API Services in V1 + * & v2 + * protocol versions * * @author Nemer Daud - Initial contribution */ @NonNullByDefault public interface LGThinQACApiClientService extends LGThinQApiClientService { + /** + * Change AC Operation Mode (Cool, Heat, etc.) + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param newOpMode - The new operation mode to be setup + * @throws LGThinqApiException - If some error invoking LG API. + */ void changeOperationMode(String bridgeName, String deviceId, int newOpMode) throws LGThinqApiException; + /** + * Change the AC Fan Speed. + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param newFanSpeed - new Fan Speed to be setup + * @throws LGThinqApiException - If some error invoking LG API. + */ void changeFanSpeed(String bridgeName, String deviceId, int newFanSpeed) throws LGThinqApiException; + /** + * Change the fan vertical orientation + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param currentSnap - Current data snapshot + * @param newStep - new vertical position + * @throws LGThinqApiException - If some error invoking LG API. + */ void changeStepUpDown(String bridgeName, String deviceId, ACCanonicalSnapshot currentSnap, int newStep) throws LGThinqApiException; + /** + * Change the fan horizontal orientation + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param currentSnap - Current data snapshot + * @param newStep - new horizontal position + * @throws LGThinqApiException - If some error invoking LG API. + */ void changeStepLeftRight(String bridgeName, String deviceId, ACCanonicalSnapshot currentSnap, int newStep) throws LGThinqApiException; + /** + * Change the target temperature + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param newTargetTemp - new target temperature + * @throws LGThinqApiException - If some error invoking LG API. + */ void changeTargetTemperature(String bridgeName, String deviceId, ACTargetTmp newTargetTemp) throws LGThinqApiException; + /** + * Turn On/Off the Jet Mode feature + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param modeOnOff - turn on/off + * @throws LGThinqApiException - If some error invoking LG API. + */ void turnCoolJetMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + /** + * Turn On/Off the Air Clean feature + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param modeOnOff - turn on/off + * @throws LGThinqApiException - If some error invoking LG API. + */ void turnAirCleanMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + /** + * Turn On/Off the Auto Dry feature + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param modeOnOff - turn on/off + * @throws LGThinqApiException - If some error invoking LG API. + */ void turnAutoDryMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + /** + * Turn On/Off the Energy Saving feature + * + * @param bridgeName - name of the bridge + * @param deviceId - ID of the LG Thinq Device + * @param modeOnOff - turn on/off + * @throws LGThinqApiException - If some error invoking LG API. + */ void turnEnergySavingMode(String bridgeName, String deviceId, String modeOnOff) throws LGThinqApiException; + /** + * Get Extended Device Information (Energy consumption, filter level, etc). + * + * @param bridgeName Bridge name + * @param deviceId - ID of the LG Thinq Device + * @return ExtendedDeviceInfo containing the device extended data + * @throws LGThinqApiException - If some error invoking LG API. + */ ExtendedDeviceInfo getExtendedDeviceInfo(String bridgeName, String deviceId) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java index 07e0ae92e6b7e..5cf920886d300 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiClientService.java @@ -51,6 +51,7 @@ import org.openhab.binding.lgthinq.lgservices.api.RestUtils; import org.openhab.binding.lgthinq.lgservices.api.TokenManager; import org.openhab.binding.lgthinq.lgservices.api.TokenResult; +import org.openhab.binding.lgthinq.lgservices.errors.LGThinqAccessException; import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1MonitorExpiredException; import org.openhab.binding.lgthinq.lgservices.errors.LGThinqDeviceV1OfflineException; @@ -75,7 +76,9 @@ import com.fasterxml.jackson.databind.node.ObjectNode; /** - * The {@link LGThinQACApiV1ClientServiceImpl} + * The {@link LGThinQAbstractApiClientService} - base class for all LG API client service. It's provide commons methods + * to + * communicate to the LG Cloud and exchange basic data. * * @author Nemer Daud - Initial contribution */ @@ -174,6 +177,8 @@ public Map getDeviceSettings(String bridgeName, String deviceId) token.getGatewayInfo().getCountry(), token.getAccessToken(), token.getUserInfo().getUserNumber()); RestResult resp = RestUtils.getCall(httpClient, builder.build().toURL().toString(), headers, null); return handleDeviceSettingsResult(resp); + } catch (LGThinqException e) { + throw e; } catch (Exception e) { throw new LGThinqApiException("Errors list account devices from LG Server API", e); } @@ -193,7 +198,8 @@ static Map genericHandleDeviceSettingsResult(RestResult resp, Ob LGThinQAbstractApiClientService.logger.warn( "Error calling device settings from LG Server API. HTTP Status: {}. The reason is: {}", resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse())); - return Collections.emptyMap(); + throw new LGThinqAccessException(String.format("Error calling device settings from LG Server API. HTTP Status: %d. The reason is: %s", + resp.getStatusCode(), ResultCodes.getReasonResponse(resp.getJsonResponse()))); } try { respMap = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java index fc7041b303685..125c67e658849 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV1ClientService.java @@ -46,7 +46,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; /** - * The {@link LGThinQAbstractApiV1ClientService} + * The {@link LGThinQAbstractApiV1ClientService} - Specialized abstract class that implements methods and services to + * handle LG API V1 communication and convention. * * @author Nemer Daud - Initial contribution */ diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java index 83273197b93ed..6634d4fd00121 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQAbstractApiV2ClientService.java @@ -39,7 +39,8 @@ import com.fasterxml.jackson.databind.node.ObjectNode; /** - * The {@link LGThinQAbstractApiV2ClientService} + * The {@link LGThinQAbstractApiV2ClientService} - Specialized abstract class that implements methods and services to + * * handle LG API V2 communication and convention. * * @author Nemer Daud - Initial contribution */ diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java index dfa81c4f5dbd7..c834ff69e33f3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQApiClientService.java @@ -30,15 +30,29 @@ import org.openhab.binding.lgthinq.lgservices.model.SnapshotDefinition; /** - * The {@link LGThinQApiClientService} + * The {@link LGThinQApiClientService} - defines the basic methods to manage devices in the LG Cloud * * @author Nemer Daud - Initial contribution */ @NonNullByDefault public interface LGThinQApiClientService { - + /** + * List all devices registers in the LG Account + * + * @param bridgeName bridge name + * @return return a List off all devices registered for the user account. + * @throws LGThinqApiException if some error occur accessing LG API + */ List listAccountDevices(String bridgeName) throws LGThinqApiException; + /** + * Get the LG device metadata about the settings and capabilities of the Device + * + * @param bridgeName bridge name + * @param deviceId LG Device ID + * @return A map containing all the device settings. + * @throws LGThinqApiException + */ Map getDeviceSettings(String bridgeName, String deviceId) throws LGThinqApiException; void initializeDevice(String bridgeName, String deviceId) throws LGThinqApiException; @@ -47,25 +61,88 @@ public interface LGThinQApiClientService { - void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException; - - void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException; -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java deleted file mode 100644 index 480ba18685438..0000000000000 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDRApiV2ClientServiceImpl.java +++ /dev/null @@ -1,71 +0,0 @@ -/** - * Copyright (c) 2010-2024 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.lgthinq.lgservices; - -import org.eclipse.jdt.annotation.NonNullByDefault; -import org.eclipse.jetty.client.HttpClient; -import org.openhab.binding.lgthinq.lgservices.api.RestResult; -import org.openhab.binding.lgthinq.lgservices.errors.LGThinqApiException; -import org.openhab.binding.lgthinq.lgservices.model.DevicePowerState; -import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerCapability; -import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; - -/** - * The {@link LGThinQDRApiV2ClientServiceImpl} - * - * @author Nemer Daud - Initial contribution - */ -@NonNullByDefault -public class LGThinQDRApiV2ClientServiceImpl - extends LGThinQAbstractApiV2ClientService - implements LGThinQDRApiClientService { - - protected LGThinQDRApiV2ClientServiceImpl(HttpClient httpClient) { - super(WasherDryerCapability.class, WasherDryerSnapshot.class, httpClient); - } - - @Override - protected boolean beforeGetDataDevice(String bridgeName, String deviceId) { - // there's no before settings to send command - return false; - } - - @Override - public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState newPowerState) { - throw new UnsupportedOperationException("Not implemented yet."); - } - - @Override - public void remoteStart(String bridgeName, String deviceId) throws LGThinqApiException { - try { - RestResult result = sendCommand(bridgeName, deviceId, "control-sync", "WMStart", "WMStart", "WMStart", ""); - handleGenericErrorResult(result); - } catch (LGThinqApiException e) { - throw e; - } catch (Exception e) { - throw new LGThinqApiException("Error sending remote start", e); - } - } - - @Override - public void wakeUp(String bridgeName, String deviceId) throws LGThinqApiException { - try { - RestResult result = sendCommand(bridgeName, deviceId, "control-sync", "WMWakeup", "WMWakeup", "", ""); - handleGenericErrorResult(result); - } catch (LGThinqApiException e) { - throw e; - } catch (Exception e) { - throw new LGThinqApiException("Error sending remote start", e); - } - } -} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java index 452a837bd0699..ecbbccc7c0fbb 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiClientService.java @@ -19,14 +19,20 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.dishwasher.DishWasherSnapshot; /** - * The {@link LGThinQDishWasherApiClientService} + * The {@link LGThinQDishWasherApiClientService} - implements specific methods for DishWashers * * @author Nemer Daud - Initial contribution */ @NonNullByDefault public interface LGThinQDishWasherApiClientService extends LGThinQApiClientService { + /** + * Remote start machine funcion + * + * @param bridgeName Bridge name + * @param cap Capabilities of the device + * @param deviceId LG Device ID + * @param data data to sent to remote start + */ void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data); - - void wakeUp(String bridgeName, String deviceId, Boolean wakeUp); } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java index dcbd4be98990f..791a0a2a5b7e4 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV1ClientServiceImpl.java @@ -57,9 +57,4 @@ public DishWasherSnapshot getDeviceData(String bridgeName, String deviceId, Capa public void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) { throw new UnsupportedOperationException("Not implemented yet"); } - - @Override - public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) { - throw new UnsupportedOperationException("Not implemented yet"); - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java index a1f90c183ab7a..238f6d2b7c838 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQDishWasherApiV2ClientServiceImpl.java @@ -49,9 +49,4 @@ public void turnDevicePower(String bridgeName, String deviceId, DevicePowerState public void remoteStart(String bridgeName, DishWasherCapability cap, String deviceId, Map data) { throw new UnsupportedOperationException("Not implemented yet"); } - - @Override - public void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) { - throw new UnsupportedOperationException("Not implemented yet"); - } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java index 077da8a60888e..18f826538ec98 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQFridgeApiClientService.java @@ -21,27 +21,82 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.fridge.FridgeCapability; /** - * The {@link LGThinQFridgeApiClientService} + * The {@link LGThinQFridgeApiClientService} - Interface with specific methods for Fridge Devices * * @author Nemer Daud - Initial contribution */ @NonNullByDefault public interface LGThinQFridgeApiClientService extends LGThinQApiClientService { + /** + * Set fridge temperature + * + * @param bridgeId Bridge ID + * @param deviceId LG Device ID + * @param fridgeCapability Fridge Capabilities + * @param targetTemperatureIndex target temperature + * @param tempUnit Temperature Unit + * @param snapCmdData Snapshot template for the target temperature command + * @throws LGThinqApiException If some error is reported from LG API + */ void setFridgeTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) throws LGThinqApiException; + /** + * Set fridge temperature + * + * @param bridgeId Bridge ID + * @param deviceId LG Device ID + * @param fridgeCapability Fridge Capabilities + * @param targetTemperatureIndex target temperature + * @param tempUnit Temperature Unit + * @param snapCmdData Snapshot template for the target temperature command + * @throws LGThinqApiException If some error is reported from LG API + */ void setFreezerTemperature(String bridgeId, String deviceId, FridgeCapability fridgeCapability, Integer targetTemperatureIndex, String tempUnit, @Nullable Map snapCmdData) throws LGThinqApiException; + /** + * Setup Express Mode + * + * @param bridgeId Bridge ID + * @param deviceId LG Device ID + * @param expressModeIndex Empress mode desired + * @throws LGThinqApiException If some error is reported from LG API + */ void setExpressMode(String bridgeId, String deviceId, String expressModeIndex) throws LGThinqApiException; + /** + * Set the Express Cool Mode + * + * @param bridgeId Bridge ID + * @param deviceId LG Device id + * @param trueOnFalseOff ON/OFF the Cool Mode + * @throws LGThinqApiException If some error is reported from LG API + */ void setExpressCoolMode(String bridgeId, String deviceId, boolean trueOnFalseOff) throws LGThinqApiException; + /** + * Set the Express Cool Mode + * + * @param bridgeId Bridge ID + * @param deviceId LG Device id + * @param trueOnFalseOff ON/OFF the Eco Mode + * @throws LGThinqApiException If some error is reported from LG API + */ void setEcoFriendlyMode(String bridgeId, String deviceId, boolean trueOnFalseOff) throws LGThinqApiException; + /** + * + * @param bridgeId Bridge ID + * @param deviceId LG Thinq Device ID + * @param fridgeCapability Fridge Capabilities + * @param trueOnFalseOff Set ON/OFF the ICE Plus + * @param snapCmdData Snapshot template for the ICE Plus Command + * @throws LGThinqApiException If some error is reported from LG API + */ void setIcePlus(String bridgeId, String deviceId, FridgeCapability fridgeCapability, boolean trueOnFalseOff, Map snapCmdData) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java index df88d80ed7d26..02c32aed25a55 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/LGThinQWMApiClientService.java @@ -20,14 +20,31 @@ import org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer.WasherDryerSnapshot; /** - * The {@link LGThinQWMApiClientService} + * The {@link LGThinQWMApiClientService} - Methods specifics for Washing/Drier Machines * * @author Nemer Daud - Initial contribution */ @NonNullByDefault public interface LGThinQWMApiClientService extends LGThinQApiClientService { + /** + * Control the remote start feature + * + * @param bridgeName Bridge Name + * @param cap Capabilities of the device + * @param deviceId LG Device ID + * @param data Data to control the remote start + * @throws LGThinqApiException if some error is reported from the LG API + */ void remoteStart(String bridgeName, WasherDryerCapability cap, String deviceId, Map data) throws LGThinqApiException; + /** + * Waking UP feature + * + * @param bridgeName Bridge Name + * @param deviceId LG Device Name + * @param wakeUp to Wake Up (true/false) + * @throws LGThinqApiException if some error is reported from the LG API + */ void wakeUp(String bridgeName, String deviceId, Boolean wakeUp) throws LGThinqApiException; } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java index 816a427daa798..d46624d5867f3 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqCanonicalModelUtil.java @@ -23,7 +23,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; /** - * The {@link LGThinqCanonicalModelUtil} class + * The {@link LGThinqCanonicalModelUtil} class - Utilities to help communication with LG API * * @author Nemer Daud - Initial contribution */ @@ -31,13 +31,20 @@ public class LGThinqCanonicalModelUtil { public static final ObjectMapper mapper = new ObjectMapper(); + /** + * Get structured result from the LG Authentication Gateway + * + * @param rawJson RAW Json to process + * @return Structured Object returned from the API + * @throws IOException If some error happen procession token from file. + */ public static GatewayResult getGatewayResult(String rawJson) throws IOException { Map map = mapper.readValue(rawJson, new TypeReference<>() { }); - Map content = mapper.convertValue(map, new TypeReference<>() { - }); + @SuppressWarnings("unchecked") + Map content = (Map) map.get("result"); String resultCode = (String) map.get("resultCode"); - if (content == null) { + if (content == null || content.isEmpty()) { throw new IllegalArgumentException("Unexpected result. Gateway Content Result is null"); } else if (resultCode == null) { throw new IllegalArgumentException("Unexpected result. resultCode code is null"); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java index d5df36e97d86e..ce5a2f95e4b56 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqGateway.java @@ -24,7 +24,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; /** - * The {@link LGThinqGateway} hold informations about the LG Gateway + * The {@link LGThinqGateway} hold information about the LG Gateway * * @author Nemer Daud - Initial contribution */ diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java index 594edb75aef0b..966edd2af8c4b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/api/LGThinqOauthEmpAuthenticator.java @@ -114,7 +114,8 @@ LGThinqGateway discoverGatewayConfiguration(String gwUrl, String language, Strin if (result.getStatusCode() != 200) { throw new IllegalStateException( - "Expected HTTP OK return, but received result core:" + result.getJsonResponse()); + String.format("Expected HTTP OK return, but received result core:[%s] - error message:[%s]", + result.getJsonResponse(), ResultCodes.getReasonResponse(result.getJsonResponse()))); } else { GatewayResult gwResult = LGThinqCanonicalModelUtil.getGatewayResult(result.getJsonResponse()); ResultCodes resultCode = ResultCodes.fromCode(gwResult.getReturnedCode()); @@ -176,9 +177,8 @@ LoginAccountResult loginUser(LGThinqGateway gw, PreLoginResult preLoginResult) t } Map loginResult = objectMapper.readValue(resp.getJsonResponse(), new TypeReference<>() { }); - Map accountResult = objectMapper.convertValue(loginResult.get("account"), - new TypeReference<>() { - }); + @SuppressWarnings("unchecked") + Map accountResult = (Map) loginResult.get("account"); if (accountResult == null) { throw new IllegalStateException("Error getting account from Login"); } @@ -277,8 +277,8 @@ private UserInfo handleAccountInfoResult(RestResult resp) throws IOException { || ((Map) result.getOrDefault("account", Collections.emptyMap())).get("userNo") == null) { throw new IllegalStateException("Error retrieving the account user information from access token"); } - Map accountInfo = objectMapper.convertValue(result.get("account"), new TypeReference<>() { - }); + @SuppressWarnings("unchecked") + Map accountInfo = (Map) result.getOrDefault("account", Collections.emptyMap()); return new UserInfo( Objects.requireNonNullElse(accountInfo.get("userNo"), diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqAccessException.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqAccessException.java new file mode 100644 index 0000000000000..93911b25de728 --- /dev/null +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/errors/LGThinqAccessException.java @@ -0,0 +1,21 @@ +package org.openhab.binding.lgthinq.lgservices.errors; + +import org.openhab.binding.lgthinq.lgservices.model.ResultCodes; + +public class LGThinqAccessException extends LGThinqApiException { + public LGThinqAccessException(String message, Throwable cause) { + super(message, cause); + } + + public LGThinqAccessException(String message, Throwable cause, ResultCodes reasonCode) { + super(message, cause, reasonCode); + } + + public LGThinqAccessException(String message) { + super(message); + } + + public LGThinqAccessException(String message, ResultCodes resultCode) { + super(message, resultCode); + } +} diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java index 956034987de67..66550f3f384b9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/DeviceTypes.java @@ -27,7 +27,7 @@ public enum DeviceTypes { WASHER_TOWER(221, "WM", "", "washer-tower-221"), DRYER(202, "DR", "Dryer", "dryer-202"), DRYER_TOWER(222, "DR", "Dryer", "dryer-tower-222"), - REFRIGERATOR(101, "REF", "Fridge", "fridge-101"), + FRIDGE(101, "REF", "Fridge", "fridge-101"), DISH_WASHER(204, "DW", "DishWasher", "dishwasher-204"), UNKNOWN(-1, "", "", ""); @@ -70,7 +70,7 @@ public static DeviceTypes fromDeviceTypeId(int deviceTypeId, String deviceCode) case 222: return DRYER_TOWER; case 101: - return REFRIGERATOR; + return FRIDGE; default: return UNKNOWN; } @@ -90,7 +90,7 @@ public static DeviceTypes fromDeviceTypeAcron(String deviceTypeAcron, String mod } yield WASHERDRYER_MACHINE; } - case "REF" -> REFRIGERATOR; + case "REF" -> FRIDGE; case "DW" -> DISH_WASHER; default -> UNKNOWN; }; diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java index f14d2c8cefc9d..260003518a81b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ModelUtils.java @@ -72,7 +72,7 @@ public static LGAPIVerion discoveryAPIVersion(Map rootMap) { case WASHERDRYER_MACHINE: case DRYER: - case REFRIGERATOR: + case FRIDGE: if (rootMap.containsKey("Value")) { return LGAPIVerion.V1_0; } else if (rootMap.containsKey("MonitoringValue")) { diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java index e1a2f3265b28a..2ae0e48186ae9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/ResultCodes.java @@ -102,93 +102,43 @@ public List getCodes() { } public static ResultCodes fromCode(String code) { - switch (code) { - case "0000": - case "0001": - return OK; - case "0002": - return INVALID_PAYLOAD; - case "0003": - return INVALID_HEAD; - case "0110": // Update Terms - return UPDATE_TERMS_NEEDED; - case "0004": // Duplicated Login - return LOGIN_DUPLICATED; - case "0102": // Not Logged in - case "0114": // Mismatch Login Session - return LOGIN_FAILED; - case "0100": - return GENERAL_FAILURE; - case "0116": - return INVALID_REQUEST; - case "0108": - case "0109": - return NO_INFORMATION_FOUND; - case "0115": - case "0006": - case "0009": - case "0117": - case "0014": - case "0101": - return DEVICE_MISMATCH; - case "0010": - return INVALID_CUSTOMER_DATA; - case "0112": - return LIMIT_EXCEEDED_ERROR; - case "0118": - case "0120": - return INVALID_CUSTOMER_NUMBER; - case "0121": - return NOT_REGISTERED_SMART_CARE; - case "0007": - return PORTAL_INTERWORKING_ERROR; - case "0008": - case "0013": - return DUPLICATED_DATA; - case "0005": - case "0012": - case "8001": - return NOT_SUPPORTED_CONTROL; - case "0111": - case "0103": - case "0104": - case "0106": - return DEVICE_NOT_RESPONSE; - case "0105": - return CONTROL_ERROR; - case "9001": - case "9002": - return BASE64_CODING_ERROR; - case "0107": - case "8101": - case "8102": - case "8203": - case "8204": - case "8205": - case "8206": - case "8207": - case "8900": - case "9000": - case "9003": - case "9004": - case "9005": - return LG_SERVER_ERROR; - case "9999": - return PAYLOAD_ERROR; - case "9006": - case "0011": - case "0113": - return ACCESS_DENIED; - case "9010": - return INVALID_CSR; - case "0301": - return INVALID_PUSH_TOKEN; - default: + return switch (code) { + case "0000", "0001" -> OK; + case "0002" -> INVALID_PAYLOAD; + case "0003" -> INVALID_HEAD; + case "0110" -> // Update Terms + UPDATE_TERMS_NEEDED; + case "0004" -> // Duplicated Login + LOGIN_DUPLICATED; // Not Logged in + case "0102", "0114" -> // Mismatch Login Session + LOGIN_FAILED; + case "0100" -> GENERAL_FAILURE; + case "0116" -> INVALID_REQUEST; + case "0108", "0109" -> NO_INFORMATION_FOUND; + case "0115", "0006", "0009", "0117", "0014", "0101" -> DEVICE_MISMATCH; + case "0010" -> INVALID_CUSTOMER_DATA; + case "0112" -> LIMIT_EXCEEDED_ERROR; + case "0118", "0120" -> INVALID_CUSTOMER_NUMBER; + case "0121" -> NOT_REGISTERED_SMART_CARE; + case "0007" -> PORTAL_INTERWORKING_ERROR; + case "0008", "0013" -> DUPLICATED_DATA; + case "0005", "0012", "8001" -> NOT_SUPPORTED_CONTROL; + case "0111", "0103", "0104", "0106" -> DEVICE_NOT_RESPONSE; + case "0105" -> CONTROL_ERROR; + case "9001", "9002" -> BASE64_CODING_ERROR; + case "0107", "8101", "8102", "8203", "8204", "8205", "8206", "8207", "8900", "9000", "9003", "9004", + "9005" -> + LG_SERVER_ERROR; + case "9999" -> PAYLOAD_ERROR; + case "9006", "0011", "0113" -> ACCESS_DENIED; + case "9010" -> INVALID_CSR; + case "0301" -> INVALID_PUSH_TOKEN; + default -> { if (OTHER_ERROR_CODE_RESPONSE.containsKey(code)) { - return OTHER; + yield OTHER; } - return UNKNOWN; - - } + yield UNKNOWN; + } + }; } } diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java index 2ca058033a1ad..32f09196df84e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/AbstractFridgeCapabilityFactory.java @@ -141,7 +141,7 @@ protected abstract void loadAtLeastOneDoorOpen(JsonNode atLeastOneDoorOpenNode, @Override protected List getSupportedDeviceTypes() { - return List.of(DeviceTypes.REFRIGERATOR); + return List.of(DeviceTypes.FRIDGE); } protected abstract String getMonitorValueNodeName(); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java index b0a19945d4256..d881b1474720f 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeCapabilityFactoryV2.java @@ -129,8 +129,7 @@ protected String getFridgeTempCNodeName() { } protected String getFridgeTempNodeName() { - throw new UnsupportedOperationException( - "Refrigerator Thinq2 doesn't support FridgeTemp node. It most likely a bug"); + throw new UnsupportedOperationException("Fridge Thinq2 doesn't support FridgeTemp node. It most likely a bug"); } @Override @@ -145,8 +144,7 @@ protected String getFreezerTempCNodeName() { @Override protected String getFreezerTempNodeName() { - throw new UnsupportedOperationException( - "Refrigerator Thinq2 doesn't support FreezerTemp node. It most likely a bug"); + throw new UnsupportedOperationException("Fridge Thinq2 doesn't support FreezerTemp node. It most likely a bug"); } @Override diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java index 132d4998b9fb6..51b8806a7ebc9 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/fridge/FridgeSnapshotBuilder.java @@ -13,7 +13,7 @@ package org.openhab.binding.lgthinq.lgservices.model.devices.fridge; import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.RE_SNAPSHOT_NODE_V2; -import static org.openhab.binding.lgthinq.lgservices.model.DeviceTypes.REFRIGERATOR; +import static org.openhab.binding.lgthinq.lgservices.model.DeviceTypes.FRIDGE; import java.util.List; import java.util.Map; @@ -48,7 +48,7 @@ public FridgeCanonicalSnapshot createFromBinary(String binaryData, List snapMap, CapabilityDefinition capDef) { FridgeCanonicalSnapshot snap; - if (REFRIGERATOR.equals(capDef.getDeviceType())) { + if (FRIDGE.equals(capDef.getDeviceType())) { switch (capDef.getDeviceVersion()) { case V1_0: throw new IllegalArgumentException("Version 1.0 for Fridge driver is not supported yet."); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java index 497b95d40c771..dc2dabd197c6e 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java +++ b/bundles/org.openhab.binding.lgthinq/src/main/java/org/openhab/binding/lgthinq/lgservices/model/devices/washerdryer/AbstractWasherDryerCapabilityFactory.java @@ -12,12 +12,12 @@ */ package org.openhab.binding.lgthinq.lgservices.model.devices.washerdryer; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_RINSE; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_SPIN; +import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_REMOTE_START_TEMP; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_RINSE_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_SPIN_ID; import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WMD_TEMP_LEVEL_ID; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_RINSE; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_SPIN; -import static org.openhab.binding.lgthinq.internal.LGThinQBindingConstants.CHANNEL_WM_REMOTE_START_TEMP; import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WM_LOST_WASHING_STATE_KEY; import static org.openhab.binding.lgthinq.lgservices.LGServicesConstants.WM_LOST_WASHING_STATE_VALUE; @@ -97,11 +97,11 @@ public WasherDryerCapability create(JsonNode rootNode) throws LGThinqException { wdCap.setProcessState(newFeatureDefinition(getProcessStateNodeName(), monitorValueNode)); // --- Selectable features ----- wdCap.setRinseFeat(newFeatureDefinition(getRinseFeatureNodeName(), monitorValueNode, - CHANNEL_WM_REMOTE_START_RINSE, CHANNEL_WMD_RINSE_ID)); + CHANNEL_WMD_REMOTE_START_RINSE, CHANNEL_WMD_RINSE_ID)); wdCap.setTemperatureFeat(newFeatureDefinition(getTemperatureFeatureNodeName(), monitorValueNode, - CHANNEL_WM_REMOTE_START_TEMP, CHANNEL_WMD_TEMP_LEVEL_ID)); - wdCap.setSpinFeat(newFeatureDefinition(getSpinFeatureNodeName(), monitorValueNode, CHANNEL_WM_REMOTE_START_SPIN, - CHANNEL_WMD_SPIN_ID)); + CHANNEL_WMD_REMOTE_START_TEMP, CHANNEL_WMD_TEMP_LEVEL_ID)); + wdCap.setSpinFeat(newFeatureDefinition(getSpinFeatureNodeName(), monitorValueNode, + CHANNEL_WMD_REMOTE_START_SPIN, CHANNEL_WMD_SPIN_ID)); // ---------------------------- wdCap.setDryLevel(newFeatureDefinition(getDryLevelNodeName(), monitorValueNode)); wdCap.setSoilWash(newFeatureDefinition(getSoilWashFeatureNodeName(), monitorValueNode)); diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties index 8e1c1f08f632b..8fa2789a82a05 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/i18n/lgthinq.properties @@ -271,3 +271,7 @@ channel-type.lgthinq.washerdryer-temp-level.description = Target Temperature Lev error.lgapi-getting-devices = Error getting device list from the account error.toke-file-corrupted = LGThinq Bridge Token File corrupted error.toke-refresh = Error refreshing LGThinq Bridge Token +error.toke-file-access-error = Error reading token file. +error.lgapi-communication-error = Communication Error with LG API +error.mandotory-fields-missing = Mandatory Fields Configuration Missing +offline.device-disconnected = Device is Offline diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml index 3590a499c128b..3f246f17e2453 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/channels.xml @@ -333,7 +333,7 @@ - + Contact Door status (at least one if combined fridge/freezer) @@ -344,7 +344,7 @@ - + String Temperature Unit @@ -356,20 +356,20 @@ - + Number:Temperature Freezer setpoint temperature Temperature - + - + Number:Temperature Fridge setpoint temperature. Temperature - + diff --git a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml index 818d021bb5cca..7bffe8093ec0b 100644 --- a/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml +++ b/bundles/org.openhab.binding.lgthinq/src/main/resources/OH-INF/thing/fridge.xml @@ -23,10 +23,10 @@ This is the Displayed Information. - - - - + + + +