[rotel] Set model and firmware properties (#13240)
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>
This commit is contained in:
parent
9b128c2f7d
commit
2f808bc8dd
@ -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";
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -12,7 +12,11 @@
|
||||
*/
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user