]> git.basschouten.com Git - openhab-addons.git/commitdiff
[gree] Use GCM encryption when bind fails (#17398)
authorZhivka Dimova <zhivka.dimova@myforest.net>
Mon, 7 Oct 2024 18:22:14 +0000 (20:22 +0200)
committerGitHub <noreply@github.com>
Mon, 7 Oct 2024 18:22:14 +0000 (20:22 +0200)
* [gree]: use GCM encryption when binding fails

Signed-off-by: Zhivka Dimova <zhivka.dimova@myforest.net>
bundles/org.openhab.binding.gree/README.md
bundles/org.openhab.binding.gree/src/main/java/org/openhab/binding/gree/internal/GreeBindingConstants.java
bundles/org.openhab.binding.gree/src/main/java/org/openhab/binding/gree/internal/GreeConfiguration.java
bundles/org.openhab.binding.gree/src/main/java/org/openhab/binding/gree/internal/GreeCryptoUtil.java
bundles/org.openhab.binding.gree/src/main/java/org/openhab/binding/gree/internal/GreeException.java
bundles/org.openhab.binding.gree/src/main/java/org/openhab/binding/gree/internal/discovery/GreeDeviceFinder.java
bundles/org.openhab.binding.gree/src/main/java/org/openhab/binding/gree/internal/discovery/GreeDiscoveryService.java
bundles/org.openhab.binding.gree/src/main/java/org/openhab/binding/gree/internal/handler/GreeAirDevice.java
bundles/org.openhab.binding.gree/src/main/java/org/openhab/binding/gree/internal/handler/GreeHandler.java
bundles/org.openhab.binding.gree/src/main/resources/OH-INF/i18n/gree.properties
bundles/org.openhab.binding.gree/src/main/resources/OH-INF/thing/thing-types.xml

index aa000c7365bcbeb979bea04889feaa189c50e98c..58b120334dde033cdd375d6916869b343bb5b87a 100644 (file)
@@ -19,15 +19,18 @@ No binding configuration is required.
 
 ## Thing Configuration
 
-| Channel Name             | Type       | Description                                                                                   |
-|--------------------------|------------|-----------------------------------------------------------------------------------------------|
-| ipAddress                | IP Address | IP address of the unit.                                                                       |
-| broadcastAddress         | IP Address | Broadcast address being used for discovery, usually derived from the IP interface address.    |
-| refresh                  | Integer    | Refresh interval in seconds for polling the device status.                                    |
-| currentTemperatureOffset | Decimal    | Offset in Celsius for the current temperature value received from the device.                 |
+| Channel Name             | Type            | Description                                                                                   |
+|--------------------------|-----------------|-----------------------------------------------------------------------------------------------|
+| ipAddress                | IP Address      | IP address of the unit.                                                                       |
+| broadcastAddress         | IP Address      | Broadcast address being used for discovery, usually derived from the IP interface address.    |
+| refresh                  | Integer         | Refresh interval in seconds for polling the device status.                                    |
+| currentTemperatureOffset | Decimal         | Offset in Celsius for the current temperature value received from the device.                 |
+| encryptionType           | EncryptionTypes | Encryption type (ECB or GCM) used for communicating with the AC device                        |
 
 The Air Conditioner's IP address is mandatory, all other parameters are optional.
 If the broadcast is not set (default) it will be derived from openHAB's network setting (Check Network Settings in the openHAB UI).
+The binding tries to automatically detect the encryption type when communicating with the AC.
+If this fails, you might need need to set the encryption type manually.
 Only change this if you have a good reason to.
 
 ## Channels
@@ -64,7 +67,7 @@ When changing mode, the air conditioner will be turned on unless "off" is select
 ### Things
 
 ```java
-Thing gree:airconditioner:a1234561 [ ipAddress="192.168.1.111", refresh=2 ]
+Thing gree:airconditioner:a1234561 [ ipAddress="192.168.1.111", refresh=2, encryptionType="ECB" ]
 ```
 
 ### Items
index 94e47edee08d419134d27d451e1d123af4dfbc1b..7538abd4e1ad7e1e3d6dfae499c265eb5a3fba23 100644 (file)
  */
 package org.openhab.binding.gree.internal;
 
+import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.core.thing.ThingTypeUID;
@@ -39,6 +43,8 @@ public class GreeBindingConstants {
     public static final String PROPERTY_IP = "ipAddress";
     public static final String PROPERTY_BROADCAST = "broadcastAddress";
 
+    public static final String PROPERTY_ENCRYPTION_TYPE = "encryptionType";
+
     // List of all Channel ids
     public static final String POWER_CHANNEL = "power";
     public static final String MODE_CHANNEL = "mode";
@@ -174,4 +180,17 @@ public class GreeBindingConstants {
      *      for more details.
      */
     public static final double INTERNAL_TEMP_SENSOR_OFFSET = -40.0;
+
+    public enum EncryptionTypes {
+        UNKNOWN,
+        ECB,
+        GCM;
+
+        private static final Map<String, EncryptionTypes> MAP = Stream.of(EncryptionTypes.values())
+                .collect(Collectors.toMap(Enum::name, Function.identity()));
+
+        public static EncryptionTypes of(final String name) {
+            return MAP.getOrDefault(name, UNKNOWN);
+        }
+    };
 }
index 1e8f397aae89900d6766f52bc18f5f473004a29c..05afdd1589528732f9b68a372ad49ccb53c96769 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.gree.internal;
 
+import static org.openhab.binding.gree.internal.GreeBindingConstants.*;
+
 import java.math.BigDecimal;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
@@ -32,10 +34,11 @@ public class GreeConfiguration {
      * of the temperature sensor.
      */
     public BigDecimal currentTemperatureOffset = new BigDecimal(0.0);
+    public EncryptionTypes encryptionType = EncryptionTypes.UNKNOWN;
 
     @Override
     public String toString() {
         return "Config: ipAddress=" + ipAddress + ", broadcastAddress=" + broadcastAddress + ", refresh=" + refresh
-                + ", currentTemperatureOffset=" + currentTemperatureOffset;
+                + ", currentTemperatureOffset=" + currentTemperatureOffset + ", encryptionType=" + encryptionType;
     }
 }
index f51678788bcb057521bbcfba2721c7db372d0eaf..aebc2fa5fca645f9083511fb4807efd5ba46adce 100644 (file)
@@ -12,6 +12,8 @@
  */
 package org.openhab.binding.gree.internal;
 
+import static org.openhab.binding.gree.internal.GreeBindingConstants.EncryptionTypes;
+
 import java.nio.charset.StandardCharsets;
 import java.security.InvalidAlgorithmParameterException;
 import java.security.InvalidKeyException;
@@ -45,11 +47,6 @@ public class GreeCryptoUtil {
     private static final String GCM_ADD = "qualcomm-test";
     private static final int TAG_LENGTH = 16;
 
-    public enum EncryptionTypes {
-        ECB,
-        GCM
-    };
-
     public static byte[] getAESGeneralKeyByteArray() {
         return AES_KEY.getBytes(StandardCharsets.UTF_8);
     }
@@ -86,6 +83,10 @@ public class GreeCryptoUtil {
     }
 
     public static <T extends GreeBaseDTO> String decrypt(T response, EncryptionTypes encType) throws GreeException {
+        if (encType == EncryptionTypes.UNKNOWN) {
+            encType = getEncryptionType(response);
+        }
+
         if (encType == EncryptionTypes.GCM) {
             return decrypt(getGCMGeneralKeyByteArray(), response, encType);
         } else {
@@ -95,6 +96,10 @@ public class GreeCryptoUtil {
 
     public static <T extends GreeBaseDTO> String decrypt(byte[] keyarray, T response, EncryptionTypes encType)
             throws GreeException {
+        if (encType == EncryptionTypes.UNKNOWN) {
+            encType = getEncryptionType(response);
+        }
+
         if (encType == EncryptionTypes.GCM) {
             return decryptGCMPack(keyarray, response.pack, response.tag);
         } else {
index cd824dc735e7ca5b2149e4d4a512c79992cbf52b..94441be8aa016523cfa12b107b7657be51e60513 100644 (file)
@@ -106,7 +106,7 @@ public class GreeException extends Exception {
 
     private Class<?> getCauseClass() {
         Throwable cause = getCause();
-        if (getCause() != null) {
+        if (cause != null) {
             return cause.getClass();
         }
         return GreeException.class;
index bc277d7034e61748e8d89a69744af93aa4f96290..a0b50f72a9a5417baf81e23e38af92760451c99f 100644 (file)
@@ -61,7 +61,8 @@ public class GreeDeviceFinder {
     public GreeDeviceFinder() {
     }
 
-    public void scan(DatagramSocket clientSocket, String broadcastAddress, boolean scanNetwork) throws GreeException {
+    public void scan(DatagramSocket clientSocket, String broadcastAddress, boolean scanNetwork,
+            EncryptionTypes encryptionTypeConfig) throws GreeException {
         InetAddress ipAddress;
         try {
             ipAddress = InetAddress.getByName(broadcastAddress);
@@ -103,7 +104,8 @@ public class GreeDeviceFinder {
                     }
 
                     // Decrypt message - a GreeException is thrown when something went wrong
-                    String decryptedMsg = scanResponseGson.decryptedPack = GreeCryptoUtil.decrypt(scanResponseGson);
+                    String decryptedMsg = scanResponseGson.decryptedPack = GreeCryptoUtil.decrypt(scanResponseGson,
+                            encryptionTypeConfig);
 
                     logger.debug("Response received from address {}: {}", remoteAddress.getHostAddress(), decryptedMsg);
 
index 7551154d938c8f88b27a3506a21cd23ab025aff9..eff9b2d360bb9005aa4c46d5ef42c9ee11268e1d 100644 (file)
@@ -87,7 +87,7 @@ public class GreeDiscoveryService extends AbstractDiscoveryService {
     @Override
     protected void startScan() {
         try (DatagramSocket clientSocket = new DatagramSocket()) {
-            deviceFinder.scan(clientSocket, broadcastAddress, true);
+            deviceFinder.scan(clientSocket, broadcastAddress, true, EncryptionTypes.UNKNOWN);
 
             int count = deviceFinder.getScannedDeviceCount();
             logger.debug("{}", messages.get("discovery.result", count));
@@ -112,6 +112,7 @@ public class GreeDiscoveryService extends AbstractDiscoveryService {
             properties.put(Thing.PROPERTY_MAC_ADDRESS, device.getId());
             properties.put(PROPERTY_IP, ipAddress);
             properties.put(PROPERTY_BROADCAST, broadcastAddress);
+            properties.put(PROPERTY_ENCRYPTION_TYPE, device.getEncryptionType());
             ThingUID thingUID = new ThingUID(THING_TYPE_GREEAIRCON, device.getId());
             DiscoveryResult result = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
                     .withRepresentationProperty(Thing.PROPERTY_MAC_ADDRESS).withLabel(device.getName()).build();
index 2a7db2a5c59193f79da32eee70493b0648ea3eff..950b682b5c8cc763696e5d60d31ae707b77bb748 100644 (file)
@@ -64,7 +64,7 @@ public class GreeAirDevice {
     private final InetAddress ipAddress;
     private int port = 0;
     private String encKey = "";
-    private GreeCryptoUtil.EncryptionTypes encType = GreeCryptoUtil.EncryptionTypes.ECB;
+    private EncryptionTypes encType = EncryptionTypes.UNKNOWN;
     private Optional<GreeScanResponseDTO> scanResponseGson = Optional.empty();
     private Optional<GreeStatusResponseDTO> statusResponseGson = Optional.empty();
     private Optional<GreeStatusResponsePackDTO> prevStatusResponsePackGson = Optional.empty();
@@ -148,7 +148,7 @@ public class GreeAirDevice {
         }
     }
 
-    public void bindWithDevice(DatagramSocket clientSocket) throws GreeException {
+    public void bindWithDevice(DatagramSocket clientSocket, EncryptionTypes encryptionTypeConfig) throws GreeException {
         try {
             // Prep the Binding Request pack
             GreeBindRequestPackDTO bindReqPackGson = new GreeBindRequestPackDTO();
@@ -158,6 +158,7 @@ public class GreeAirDevice {
             String bindReqPackStr = GSON.toJson(bindReqPackGson);
 
             // Encrypt and send the Binding Request pack
+            setEncryptionType(encryptionTypeConfig);
             String[] encryptedBindReqData = GreeCryptoUtil.encrypt(GreeCryptoUtil.getGeneralKeyByteArray(encType),
                     bindReqPackStr, encType);
             DatagramPacket sendPacket = createPackRequest(1, encryptedBindReqData);
@@ -174,7 +175,12 @@ public class GreeAirDevice {
             // save the outcome
             isBound = true;
         } catch (IOException | JsonSyntaxException e) {
-            throw new GreeException("Unable to bind to device", e);
+            if (encType != EncryptionTypes.GCM) {
+                logger.debug("Unable to bind to device - changing the encryption mode to GCM and trying again", e);
+                bindWithDevice(clientSocket, EncryptionTypes.GCM);
+            } else {
+                throw new GreeException("Unable to bind to device", e);
+            }
         }
     }
 
@@ -460,7 +466,7 @@ public class GreeAirDevice {
         request.uid = 0;
         request.tcid = getId();
         request.pack = data[0];
-        if (encType == GreeCryptoUtil.EncryptionTypes.GCM) {
+        if (encType == EncryptionTypes.GCM) {
             if (data.length > 1) {
                 request.tag = data[1];
             } else {
@@ -519,6 +525,24 @@ public class GreeAirDevice {
         return isBound;
     }
 
+    public void setEncryptionType(EncryptionTypes value) {
+        if (value == EncryptionTypes.UNKNOWN) {
+            logger.debug("Trying to set encryption type to 'UNKNOWN' for device: {}, current value: {}", getName(),
+                    encType);
+            if (encType == EncryptionTypes.UNKNOWN) {
+                logger.debug("Falling back to 'ECB' for device: {}", getName());
+                encType = EncryptionTypes.ECB;
+            }
+        } else {
+            logger.debug("Change encryption type for device: {}, from : {}, to: {}", getName(), encType, value);
+            encType = value;
+        }
+    }
+
+    public EncryptionTypes getEncryptionType() {
+        return encType;
+    }
+
     public byte[] getKey() {
         return encKey.getBytes(StandardCharsets.UTF_8);
     }
@@ -528,7 +552,12 @@ public class GreeAirDevice {
     }
 
     public String getName() {
-        return scanResponseGson.isPresent() ? scanResponseGson.get().packJson.name : "";
+        if (scanResponseGson.isPresent()) {
+            String name = scanResponseGson.get().packJson.name;
+            return name.trim().isEmpty() ? getId() : name;
+        }
+
+        return "";
     }
 
     public String getVendor() {
index 9b0203b9b885d6a287fd02d4a0d5211e191e52ed..b0428fa775e8c2c77e79e26088b5f59dd64bfdf2 100644 (file)
@@ -108,12 +108,12 @@ public class GreeHandler extends BaseThingHandler {
                 clientSocket.get().setSoTimeout(DATAGRAM_SOCKET_TIMEOUT);
             }
             // Find the GREE device
-            deviceFinder.scan(clientSocket.get(), config.ipAddress, false);
+            deviceFinder.scan(clientSocket.get(), config.ipAddress, false, config.encryptionType);
             GreeAirDevice newDevice = deviceFinder.getDeviceByIPAddress(config.ipAddress);
             if (newDevice != null) {
                 // Ok, our device responded, now let's Bind with it
                 device = newDevice;
-                device.bindWithDevice(clientSocket.get());
+                device.bindWithDevice(clientSocket.get(), config.encryptionType);
                 if (device.getIsBound()) {
                     updateStatus(ThingStatus.ONLINE);
                     return;
@@ -138,7 +138,7 @@ public class GreeHandler extends BaseThingHandler {
     @Override
     public void handleCommand(ChannelUID channelUID, Command command) {
         if (command instanceof RefreshType) {
-            // The thing is updated by the scheduled automatic refresh so do nothing here.
+            initializeThing();
         } else {
             logger.debug("{}: Issue command {} to channe {}", thingId, command, channelUID.getIdWithoutGroup());
             String channelId = channelUID.getIdWithoutGroup();
@@ -377,8 +377,9 @@ public class GreeHandler extends BaseThingHandler {
                 }
             } catch (GreeException e) {
                 String subcode = "";
-                if (e.getCause() != null) {
-                    subcode = " (" + e.getCause().getMessage() + ")";
+                Throwable cause = e.getCause();
+                if (cause != null) {
+                    subcode = " (" + cause.getMessage() + ")";
                 }
                 String message = messages.get("update.exception", e.getMessageString() + subcode);
                 if (getThing().getStatus() == ThingStatus.OFFLINE) {
index cdc2bcab1e9c7b0e3a9aa90b210c96c6ba9efeba..ccae40ffa3479e8e8445625bedd12f308d4c1043 100644 (file)
@@ -1,12 +1,15 @@
-# GREE Binding
+# add-on
+
 addon.gree.name = GREE Binding
-addon.gree.description = This binding integrates the GREE series of air conditioners
+addon.gree.description = This is the binding for GREE air conditioners.
 
 # thing types
+
 thing-type.gree.airconditioner.label = Air Conditioner
 thing-type.gree.airconditioner.description = A GREE Air Conditioner with WiFi Module
 
 # thing type config description
+
 thing-type.config.gree.airconditioner.ipAddress.label = IP Address
 thing-type.config.gree.airconditioner.ipAddress.description = IP Address of the GREE unit.
 thing-type.config.gree.airconditioner.broadcastAddress.label = Subnet Broadcast Address
@@ -15,8 +18,13 @@ thing-type.config.gree.airconditioner.refresh.label = Refresh Interval
 thing-type.config.gree.airconditioner.refresh.description = Interval to query an update from the device.
 thing-type.config.gree.airconditioner.currentTemperatureOffset.label = Offset for Current Temperature
 thing-type.config.gree.airconditioner.currentTemperatureOffset.description = The offset in Celsius for the current temperature value received from the device.
+thing-type.config.gree.airconditioner.encryptionType.label = Encryption type
+thing-type.config.gree.airconditioner.encryptionType.description = The encryption type used for encrypting the data send to the AC device.
+thing-type.config.gree.airconditioner.encryptionType.state.option.ECB = ECB
+thing-type.config.gree.airconditioner.encryptionType.state.option.GCM = GCM
 
 # channel types
+
 channel-type.gree.power.label = Power
 channel-type.gree.power.description = Turn power on/off
 channel-type.gree.mode.label = Unit Mode
@@ -70,7 +78,7 @@ channel-type.gree.swingupdown.option.11 = Swing Upmost
 channel-type.gree.swingleftright.label = Horizontal Swing Mode
 channel-type.gree.swingleftright.description = Sets the horizontal swing action on the Air Conditioner: 0=OFF, 1=Full Swing, 2=Left, 3=Mid-Left, 4=Mid, 5=Mid-Right, 6=Right
 channel-type.gree.swingleftright.option.0 = OFF
-channel-type.gree.swingleftright.option.1 = Full Swing 
+channel-type.gree.swingleftright.option.1 = Full Swing
 channel-type.gree.swingleftright.option.2 = Left
 channel-type.gree.swingleftright.option.3 = Mid-Left
 channel-type.gree.swingleftright.option.4 = Mid
@@ -83,7 +91,8 @@ channel-type.gree.light.description = Enable/disable the front display on the Ai
 channel-type.gree.health.label = Health Mode
 channel-type.gree.health.description = Set on/off the Air Conditioner's Health function if applicable to the Air Conditioner model.
 
-# User Messages
+# user messages
+
 message.thinginit.failed = Unable to connect to air conditioner
 message.thinginit.invconf = Invalid configuration data
 message.thinginit.exception = Thing initialization failed: {0}
@@ -92,5 +101,5 @@ message.command.exception = Unable to execute command {0} for channel {1}
 message.update.exception = Unable to perform auto-update: {0}
 message.channel.exception = Unable to update channel {0} with {1}
 message.discovery.result = {0} units discovered.
-message.discovery.newunit = Device {0} discovered at {1}, MAC={2} 
-message.discovery.exception = Device Discovery failed: {0} 
+message.discovery.newunit = Device {0} discovered at {1}, MAC={2}
+message.discovery.exception = Device Discovery failed: {0}
index 51cf508f7f438678f127725b6f0b801be564e82a..8fc6b30be9bdc074ace2c4567502c2d7c20d01cd 100644 (file)
                                <unitLabel>Degrees Celsius</unitLabel>
                                <advanced>true</advanced>
                        </parameter>
+                       <parameter name="encryptionType" type="text">
+                               <options>
+                                       <option value="ECB">@text/thing-type.config.gree.airconditioner.encryptionType.state.option.ECB</option>
+                                       <option value="GCM">@text/thing-type.config.gree.airconditioner.encryptionType.state.option.GCM</option>
+                               </options>
+                               <advanced>true</advanced>
+                       </parameter>
                </config-description>
 
        </thing-type>