]> git.basschouten.com Git - openhab-addons.git/commitdiff
[rotel] Set model and firmware properties (#13240)
authorlolodomo <lg.hc@free.fr>
Fri, 12 Aug 2022 14:51:54 +0000 (16:51 +0200)
committerGitHub <noreply@github.com>
Fri, 12 Aug 2022 14:51:54 +0000 (16:51 +0200)
For all models providing these information and relying on ASCII protocol.
The binding now supports reading of status message containing variable length content.

Signed-off-by: Laurent Garnier <lg.hc@free.fr>
bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/RotelBindingConstants.java
bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelCommand.java
bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/communication/RotelSimuConnector.java
bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/handler/RotelHandler.java
bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAbstractAsciiProtocolHandler.java
bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV1ProtocolHandler.java
bundles/org.openhab.binding.rotel/src/main/java/org/openhab/binding/rotel/internal/protocol/ascii/RotelAsciiV2ProtocolHandler.java

index 80b180a22540fcade127710ad36c16b6b556c053..5f4a7e3f84037249397dd1cd34ec8624272a8b6d 100644 (file)
@@ -263,9 +263,30 @@ public class RotelBindingConstants {
     public static final String KEY_TONE_MAX = "tone_max";
     public static final String KEY1_PLAY_STATUS = "play_status";
     public static final String KEY2_PLAY_STATUS = "status";
+    public static final String KEY_DISC_NAME = "disc_name";
+    public static final String KEY_DISC_TYPE = "disc_type";
     public static final String KEY_TRACK = "track";
+    public static final String KEY_TRACK_NAME = "track_name";
+    public static final String KEY_TIME = "time";
     public static final String KEY_RANDOM = "rnd";
     public static final String KEY_REPEAT = "rpt";
+    public static final String KEY_PRESET_FM = "preset_fm";
+    public static final String KEY_FM_PRESET = "fm_preset_";
+    public static final String KEY_FM_ALL_PRESET = "fm_allpreset_";
+    public static final String KEY_FM = "fm";
+    public static final String KEY_FM_MONO = "fm_mono";
+    public static final String KEY_FM_RDS = "fm_rds";
+    public static final String KEY_FM_FREQ = "fm_freq";
+    public static final String KEY_PRESET_DAB = "preset_dab";
+    public static final String KEY_DAB_PRESET = "dab_preset_";
+    public static final String KEY_DAB_ALL_PRESET = "dab_allpreset_";
+    public static final String KEY_DAB = "dab";
+    public static final String KEY_DAB_STATION = "dab_station";
+    public static final String KEY_PRESET_IRADIO = "preset_iradio";
+    public static final String KEY_IRADIO_PRESET = "iradio_preset_";
+    public static final String KEY_IRADIO_ALL_PRESET = "iradio_allpreset_";
+    public static final String KEY_CURRENT_STATION = "current_station";
+    public static final String KEY_SIGNAL_STRENGTH = "signal_strength";
     public static final String KEY_DIMMER = "dimmer";
     public static final String KEY_FREQ = "freq";
     public static final String KEY_FREQ_ZONE1 = "freq_zone1";
@@ -291,8 +312,16 @@ public class RotelBindingConstants {
     public static final String KEY_CEILING_REAR_RIGHT_LEVEL = "ceiling_rear_right";
     public static final String KEY_CEILING_REAR_LEFT_LEVEL = "ceiling_rear_left";
     public static final String KEY_PCUSB_CLASS = "pcusb_class";
+    public static final String KEY_PRODUCT_TYPE = "product_type";
     public static final String KEY_MODEL = "model";
+    public static final String KEY_PRODUCT_VERSION = "product_version";
     public static final String KEY_VERSION = "version";
+    public static final String KEY_TC_VERSION = "tc_version";
+    public static final String KEY_DISPLAY = "display";
+    public static final String KEY_DISPLAY1 = "display1";
+    public static final String KEY_DISPLAY2 = "display2";
+    public static final String KEY_DISPLAY3 = "display3";
+    public static final String KEY_DISPLAY4 = "display4";
     // Output keys only used by the HEX protocol
     public static final String KEY_LINE1 = "line1";
     public static final String KEY_LINE2 = "line2";
index 7907eb1f34a9379a14e5dccfc27f2510c143eb4a..d3bb3dc679c521f2aa0838e2af5bd00ea32a3d26 100644 (file)
@@ -496,8 +496,8 @@ public enum RotelCommand {
     HDMI_TV_MODE("HDMI TV Mode", PRIMARY_CMD, (byte) 0x79),
     ROOM_EQ_TOGGLE("Temporary Room EQ Toggle", PRIMARY_CMD, (byte) 0x67),
     SPEAKER_SETTING_TOGGLE("Speaker Level Setting Toggle", PRIMARY_CMD, (byte) 0xA1),
-    MODEL("Request the model number", null, "model?"),
-    VERSION("Request the main CPU software version", null, "version?");
+    MODEL("Request the model number", "get_product_type", "model?"),
+    VERSION("Request the main CPU software version", "get_product_version", "version?");
 
     public static final List<RotelCommand> DSP_CMDS_SET1 = List.of(DSP_TOGGLE, PROLOGIC_TOGGLE, DOLBY_TOGGLE,
             PLII_PANORAMA_TOGGLE, PLII_DIMENSION_UP, PLII_DIMENSION_DOWN, PLII_CENTER_WIDTH_UP, PLII_CENTER_WIDTH_DOWN,
index 80172e4c87a8eff29ea0b38d5b2f500a42d7690b..b57e8da340a491849cf97a7ef03521b11c60a5ee 100644 (file)
@@ -45,6 +45,7 @@ public class RotelSimuConnector extends RotelConnector {
 
     private static final int STEP_TONE_LEVEL = 1;
     private static final double STEP_DECIBEL = 0.5;
+    private static final String FIRMWARE = "V1.1.8";
 
     private final Logger logger = LoggerFactory.getLogger(RotelSimuConnector.class);
 
@@ -175,6 +176,7 @@ public class RotelSimuConnector extends RotelConnector {
         String textLine1Right = buildVolumeLine1RightResponse();
         String textLine2 = "";
         String textAscii = "";
+        boolean variableLength = false;
         boolean accepted = true;
         boolean resetZone = true;
         int numZone = 0;
@@ -1062,10 +1064,22 @@ public class RotelSimuConnector extends RotelConnector {
                     textAscii = buildAsciiResponse(KEY_PCUSB_CLASS, pcUsbClass);
                     break;
                 case MODEL:
-                    textAscii = buildAsciiResponse(KEY_MODEL, model.getName());
+                    if (protocol == RotelProtocol.ASCII_V1) {
+                        variableLength = true;
+                        textAscii = buildAsciiResponse(KEY_PRODUCT_TYPE,
+                                String.format("%d,%s", model.getName().length(), model.getName()));
+                    } else {
+                        textAscii = buildAsciiResponse(KEY_MODEL, model.getName());
+                    }
                     break;
                 case VERSION:
-                    textAscii = buildAsciiResponse(KEY_VERSION, "1.00");
+                    if (protocol == RotelProtocol.ASCII_V1) {
+                        variableLength = true;
+                        textAscii = buildAsciiResponse(KEY_PRODUCT_VERSION,
+                                String.format("%d,%s", FIRMWARE.length(), FIRMWARE));
+                    } else {
+                        textAscii = buildAsciiResponse(KEY_VERSION, FIRMWARE);
+                    }
                     break;
                 default:
                     accepted = false;
@@ -1186,7 +1200,14 @@ public class RotelSimuConnector extends RotelConnector {
                 idxInFeedbackMsg = 0;
             }
         } else {
-            String command = textAscii + (protocol == RotelProtocol.ASCII_V1 ? "!" : "$");
+            String command = textAscii;
+            if (protocol == RotelProtocol.ASCII_V1 && !variableLength) {
+                command += "!";
+            } else if (protocol == RotelProtocol.ASCII_V2 && !variableLength) {
+                command += "$";
+            } else if (protocol == RotelProtocol.ASCII_V2 && variableLength) {
+                command += "$$";
+            }
             synchronized (lock) {
                 feedbackMsg = command.getBytes(StandardCharsets.US_ASCII);
                 idxInFeedbackMsg = 0;
index d71a6c0830688975575a5e90f108190957328c9e..f0bd7b02d1e253bd8760ca9f5cb8451baeb5cd8f 100644 (file)
@@ -1696,9 +1696,11 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL
                 case KEY_PCUSB_CLASS:
                     logger.debug("PC-USB Audio Class is set to {}", value);
                     break;
+                case KEY_PRODUCT_TYPE:
                 case KEY_MODEL:
                     getThing().setProperty(Thing.PROPERTY_MODEL_ID, value);
                     break;
+                case KEY_PRODUCT_VERSION:
                 case KEY_VERSION:
                     getThing().setProperty(Thing.PROPERTY_FIRMWARE_VERSION, value);
                     break;
@@ -1944,6 +1946,13 @@ public class RotelHandler extends BaseThingHandler implements RotelMessageEventL
                                 sendCommand(RotelCommand.SPEAKER);
                                 Thread.sleep(SLEEP_INTV);
                             }
+                            if (model != RotelModel.RAP1580 && model != RotelModel.RSP1576
+                                    && model != RotelModel.RSP1582) {
+                                sendCommand(RotelCommand.MODEL);
+                                Thread.sleep(SLEEP_INTV);
+                                sendCommand(RotelCommand.VERSION);
+                                Thread.sleep(SLEEP_INTV);
+                            }
                             break;
                         case ASCII_V2:
                             sendCommand(RotelCommand.UPDATE_AUTO);
index 71cd0f8d9c3f0a02f9e0109e99691af21e780a53..30399f74f46a66b10cb24ece1de759b3a4d75afe 100644 (file)
@@ -56,10 +56,10 @@ public abstract class RotelAbstractAsciiProtocolHandler extends RotelAbstractPro
     /** Empty table of special characters */
     public static final byte[][] NO_SPECIAL_CHARACTERS = {};
 
+    private static final int MAX_SIZE_RESPONSE = 128;
+
     private final Logger logger = LoggerFactory.getLogger(RotelAbstractAsciiProtocolHandler.class);
 
-    private final char terminatingChar;
-    private final int size;
     private final byte[] dataBuffer;
 
     private int index;
@@ -68,31 +68,31 @@ public abstract class RotelAbstractAsciiProtocolHandler extends RotelAbstractPro
      * Constructor
      *
      * @param model the Rotel model in use
-     * @param protocol the protocol to be used
      */
-    public RotelAbstractAsciiProtocolHandler(RotelModel model, char terminatingChar) {
+    public RotelAbstractAsciiProtocolHandler(RotelModel model) {
         super(model);
-        this.terminatingChar = terminatingChar;
-        this.size = 64;
-        this.dataBuffer = new byte[size];
+        this.dataBuffer = new byte[MAX_SIZE_RESPONSE];
         this.index = 0;
     }
 
-    @Override
-    public void handleIncomingData(byte[] inDataBuffer, int length) {
-        for (int i = 0; i < length; i++) {
-            if (index < size) {
-                dataBuffer[index++] = inDataBuffer[i];
-            }
-            if (inDataBuffer[i] == terminatingChar) {
-                if (index >= size) {
-                    dataBuffer[index - 1] = (byte) terminatingChar;
-                }
-                byte[] msg = Arrays.copyOf(dataBuffer, index);
-                handleIncomingMessage(msg);
-                index = 0;
-            }
+    protected boolean fillDataBuffer(byte data) {
+        if (index < MAX_SIZE_RESPONSE) {
+            dataBuffer[index++] = data;
+            return true;
         }
+        return false;
+    }
+
+    protected byte[] getDataBuffer() {
+        return Arrays.copyOf(dataBuffer, index);
+    }
+
+    protected void resetDataBuffer() {
+        index = 0;
+    }
+
+    protected int getRemainingSizeInDataBuffer() {
+        return MAX_SIZE_RESPONSE - index;
     }
 
     /**
@@ -109,12 +109,6 @@ public abstract class RotelAbstractAsciiProtocolHandler extends RotelAbstractPro
             logger.debug("Unexpected message length: {}", responseMessage.length);
             throw new RotelException("Unexpected message length");
         }
-
-        if (responseMessage[responseMessage.length - 1] != '!' && responseMessage[responseMessage.length - 1] != '$') {
-            logger.debug("Unexpected ending character in response: {}",
-                    Integer.toHexString(responseMessage[responseMessage.length - 1] & 0x000000FF));
-            throw new RotelException("Unexpected ending character in response");
-        }
     }
 
     /**
@@ -133,18 +127,19 @@ public abstract class RotelAbstractAsciiProtocolHandler extends RotelAbstractPro
             }
         }
 
-        String value = new String(message, 0, message.length - 1, StandardCharsets.US_ASCII);
+        String value = new String(message, 0, message.length, StandardCharsets.US_ASCII);
         logger.debug("handleValidAsciiMessage: chars *{}*", value);
         value = value.trim();
         if (value.isEmpty()) {
             return;
         }
         try {
-            String[] splittedValue = value.split("=");
-            if (splittedValue.length != 2) {
+            int idxSeparator = value.indexOf("=");
+            if (idxSeparator < 0) {
                 logger.debug("handleValidAsciiMessage: ignored message {}", value);
             } else {
-                dispatchKeyValue(splittedValue[0].trim().toLowerCase(), splittedValue[1]);
+                dispatchKeyValue(value.substring(0, idxSeparator).trim().toLowerCase(),
+                        value.substring(idxSeparator + 1));
             }
         } catch (PatternSyntaxException e) {
             logger.debug("handleValidAsciiMessage: ignored message {}", value);
index e1b2ea274cda17ed7b20e04e878ec7db058e81be..565e4f827800cc3a3a5fb51fe11cd3571bf3d9d0 100644 (file)
  */
 package org.openhab.binding.rotel.internal.protocol.ascii;
 
+import static org.openhab.binding.rotel.internal.RotelBindingConstants.*;
+
 import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -33,15 +37,26 @@ public class RotelAsciiV1ProtocolHandler extends RotelAbstractAsciiProtocolHandl
 
     private static final char CHAR_END_RESPONSE = '!';
 
+    private static final Set<String> KEYSET1 = Set.of(KEY_DISPLAY, KEY_DISPLAY1, KEY_DISPLAY2, KEY_DISPLAY3,
+            KEY_DISPLAY4, KEY_PRODUCT_TYPE, KEY_PRODUCT_VERSION, KEY_TC_VERSION, KEY_TRACK);
+    private static final Set<String> KEYSET2 = Set.of(KEY_FM_PRESET, KEY_FM_ALL_PRESET, KEY_DAB_PRESET,
+            KEY_DAB_ALL_PRESET, KEY_IRADIO_PRESET, KEY_IRADIO_ALL_PRESET);
+
     private final Logger logger = LoggerFactory.getLogger(RotelAsciiV1ProtocolHandler.class);
 
+    private final byte[] lengthBuffer = new byte[8];
+    private boolean searchKey = true;
+    private boolean searchLength;
+    private int valueLength;
+    private int indexLengthBuffer;
+
     /**
      * Constructor
      *
      * @param model the Rotel model in use
      */
     public RotelAsciiV1ProtocolHandler(RotelModel model) {
-        super(model, CHAR_END_RESPONSE);
+        super(model);
     }
 
     @Override
@@ -97,4 +112,61 @@ public class RotelAsciiV1ProtocolHandler extends RotelAbstractAsciiProtocolHandl
         logger.debug("Command \"{}\" => {}", cmd, messageStr);
         return message;
     }
+
+    @Override
+    public void handleIncomingData(byte[] inDataBuffer, int length) {
+        for (int i = 0; i < length; i++) {
+            boolean end = false;
+            if (searchKey && inDataBuffer[i] == '=') {
+                // End of key reading, check if the value is a fixed or variable length
+                searchKey = false;
+                byte[] dataKey = getDataBuffer();
+                String key = new String(dataKey, 0, dataKey.length, StandardCharsets.US_ASCII).trim();
+                searchLength = isVariableLengthApplicable(key);
+                indexLengthBuffer = 0;
+                valueLength = 0;
+                logger.trace("handleIncomingData: key = *{}* {}", key, searchLength ? "variable" : "fixed");
+                fillDataBuffer(inDataBuffer[i]);
+            } else if (searchKey) {
+                // Reading key
+                fillDataBuffer(inDataBuffer[i]);
+            } else if (searchLength && inDataBuffer[i] == ',') {
+                // End of value length reading
+                searchLength = false;
+                byte[] lengthData = Arrays.copyOf(lengthBuffer, indexLengthBuffer);
+                String lengthStr = new String(lengthData, 0, lengthData.length, StandardCharsets.US_ASCII);
+                valueLength = Integer.parseInt(lengthStr);
+                logger.trace("handleIncomingData: valueLength = {}", valueLength);
+                if (getRemainingSizeInDataBuffer() < valueLength) {
+                    logger.warn(
+                            "handleIncomingData: the size of the internal buffer is too small, reponse will be truncated");
+                }
+                end = valueLength == 0;
+            } else if (searchLength) {
+                // Reading value length
+                lengthBuffer[indexLengthBuffer++] = inDataBuffer[i];
+            } else if (valueLength > 0) {
+                // Reading value (variable length)
+                fillDataBuffer(inDataBuffer[i]);
+                valueLength--;
+                end = valueLength == 0;
+            } else if (inDataBuffer[i] == CHAR_END_RESPONSE) {
+                // End of value reading
+                end = true;
+            } else {
+                // Reading value (fixed length)
+                fillDataBuffer(inDataBuffer[i]);
+            }
+            if (end) {
+                handleIncomingMessage(getDataBuffer());
+                resetDataBuffer();
+                searchKey = true;
+                searchLength = false;
+            }
+        }
+    }
+
+    private boolean isVariableLengthApplicable(String key) {
+        return KEYSET1.contains(key) || KEYSET2.stream().filter(k -> key.startsWith(k)).count() > 0;
+    }
 }
index 5c2b2cf75115c1a0c5a25140420c71f4ab6dccb1..8b9ca22a5c5da2cd00f133a0cc94fbf1f2ac9397 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.rotel.internal.protocol.ascii;
 import static org.openhab.binding.rotel.internal.RotelBindingConstants.*;
 
 import java.nio.charset.StandardCharsets;
+import java.util.Set;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
@@ -35,15 +36,22 @@ public class RotelAsciiV2ProtocolHandler extends RotelAbstractAsciiProtocolHandl
 
     private static final char CHAR_END_RESPONSE = '$';
 
+    private static final Set<String> KEYSET = Set.of(KEY_DISC_NAME, KEY_DISC_TYPE, KEY_TRACK_NAME, KEY_TIME, KEY_FM_RDS,
+            KEY_DAB_STATION);
+
     private final Logger logger = LoggerFactory.getLogger(RotelAsciiV2ProtocolHandler.class);
 
+    private boolean searchKey = true;
+    private boolean variableLength;
+    private boolean prevIsEndCharacter;
+
     /**
      * Constructor
      *
      * @param model the Rotel model in use
      */
     public RotelAsciiV2ProtocolHandler(RotelModel model) {
-        super(model, CHAR_END_RESPONSE);
+        super(model);
     }
 
     @Override
@@ -116,6 +124,44 @@ public class RotelAsciiV2ProtocolHandler extends RotelAbstractAsciiProtocolHandl
         return message;
     }
 
+    @Override
+    public void handleIncomingData(byte[] inDataBuffer, int length) {
+        for (int i = 0; i < length; i++) {
+            boolean end = false;
+            if (searchKey && inDataBuffer[i] == '=') {
+                // End of key reading, check if the value is a fixed or variable length
+                searchKey = false;
+                byte[] dataKey = getDataBuffer();
+                String key = new String(dataKey, 0, dataKey.length, StandardCharsets.US_ASCII).trim();
+                variableLength = KEYSET.contains(key);
+                logger.trace("handleIncomingData: key = *{}* {}", key, variableLength ? "variable" : "fixed");
+                fillDataBuffer(inDataBuffer[i]);
+            } else if (searchKey) {
+                // Reading key
+                fillDataBuffer(inDataBuffer[i]);
+            } else if (inDataBuffer[i] == CHAR_END_RESPONSE) {
+                end = !variableLength || prevIsEndCharacter;
+            } else {
+                if (prevIsEndCharacter) {
+                    // End character inside a variable length value
+                    fillDataBuffer((byte) CHAR_END_RESPONSE);
+                }
+                // Reading value
+                fillDataBuffer(inDataBuffer[i]);
+            }
+            if (end) {
+                // End of value reading
+                handleIncomingMessage(getDataBuffer());
+                resetDataBuffer();
+                searchKey = true;
+                variableLength = false;
+                prevIsEndCharacter = false;
+            } else {
+                prevIsEndCharacter = inDataBuffer[i] == CHAR_END_RESPONSE;
+            }
+        }
+    }
+
     @Override
     protected void dispatchKeyValue(String key, String value) {
         // For distribution amplifiers, we need to split certain values to get the value for each zone