mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[anthem] Add channel, refactor parser, add tests (#14720)
* Add channel and refactor parser Signed-off-by: Mark Hilbush <mark@hilbush.com>
This commit is contained in:
parent
891da2c944
commit
6ebfd84bdd
@ -30,22 +30,24 @@ The Anthem AV processor supports the following channels (some zones/channels are
|
||||
|
||||
| Channel | Type | Description |
|
||||
|-------------------------|---------|--------------|
|
||||
| *Main Zone* | | |
|
||||
| 1#power | Switch | Power the zone on or off |
|
||||
| 1#volume | Dimmer | Increase or decrease the volume level |
|
||||
| 1#volumeDB | Number | The actual volume setting |
|
||||
| 1#mute | Switch | Mute the volume |
|
||||
| 1#activeInput | Number | The currently active input source |
|
||||
| *General* | | |
|
||||
| general#command | String | Send a custom command |
|
||||
| *Main Zone* | | |
|
||||
| 1#power | Switch | Power the zone on or off |
|
||||
| 1#volume | Dimmer | Increase or decrease the volume level |
|
||||
| 1#volumeDB | Number | The actual volume setting |
|
||||
| 1#mute | Switch | Mute the volume |
|
||||
| 1#activeInput | Number | The currently active input source |
|
||||
| 1#activeInputShortName | String | Short friendly name of the active input |
|
||||
| 1#activeInputLongName | String | Long friendly name of the active input |
|
||||
| *Zone 2* | | |
|
||||
| 2#power | Switch | Power the zone on or off |
|
||||
| 2#volume | Dimmer | Increase or decrease the volume level |
|
||||
| 2#volumeDB | Number | The actual volume setting |
|
||||
| 2#mute | Switch | Mute the volume |
|
||||
| 2#activeInput | Number | The currently active input source |
|
||||
| 1#activeInputLongName | String | Long friendly name of the active input |
|
||||
| *Zone 2* | | |
|
||||
| 2#power | Switch | Power the zone on or off |
|
||||
| 2#volume | Dimmer | Increase or decrease the volume level |
|
||||
| 2#volumeDB | Number | The actual volume setting |
|
||||
| 2#mute | Switch | Mute the volume |
|
||||
| 2#activeInput | Number | The currently active input source |
|
||||
| 2#activeInputShortName | String | Short friendly name of the active input |
|
||||
| 2#activeInputLongName | String | Long friendly name of the active input |
|
||||
| 2#activeInputLongName | String | Long friendly name of the active input |
|
||||
|
||||
|
||||
## Full Example
|
||||
@ -59,6 +61,8 @@ Thing anthem:anthem:mediaroom "Anthem AVM 60" [ host="192.168.1.100" ]
|
||||
### Items
|
||||
|
||||
```
|
||||
String Anthem_Command "Command [%s]" { channel="anthem:anthem:mediaroom:general#command" }
|
||||
|
||||
Switch Anthem_Z1_Power "Zone 1 Power [%s]" { channel="anthem:anthem:mediaroom:1#power" }
|
||||
Dimmer Anthem_Z1_Volume "Zone 1 Volume [%s]" { channel="anthem:anthem:mediaroom:1#volume" }
|
||||
Number Anthem_Z1_Volume_DB "Zone 1 Volume dB [%.0f]" { channel="anthem:anthem:mediaroom:1#volumeDB" }
|
||||
|
@ -32,6 +32,9 @@ public class AnthemBindingConstants {
|
||||
// List of all Thing Type UIDs
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_ANTHEM);
|
||||
|
||||
// Channel groups
|
||||
public static final String CHANNEL_GROUP_GENERAL = "general";
|
||||
|
||||
// Channel Ids
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_VOLUME = "volume";
|
||||
@ -40,6 +43,7 @@ public class AnthemBindingConstants {
|
||||
public static final String CHANNEL_ACTIVE_INPUT = "activeInput";
|
||||
public static final String CHANNEL_ACTIVE_INPUT_SHORT_NAME = "activeInputShortName";
|
||||
public static final String CHANNEL_ACTIVE_INPUT_LONG_NAME = "activeInputLongName";
|
||||
public static final String CHANNEL_COMMAND = "command";
|
||||
|
||||
// Connection-related configuration parameters
|
||||
public static final int DEFAULT_PORT = 14999;
|
||||
@ -47,4 +51,8 @@ public class AnthemBindingConstants {
|
||||
public static final int DEFAULT_COMMAND_DELAY_MSEC = 100;
|
||||
|
||||
public static final char COMMAND_TERMINATION_CHAR = ';';
|
||||
|
||||
public static final String PROPERTY_REGION = "region";
|
||||
public static final String PROPERTY_SOFTWARE_BUILD_DATE = "softwareBuildDate";
|
||||
public static final String PROPERTY_NUM_AVAILABLE_INPUTS = "numAvailableInputs";
|
||||
}
|
||||
|
@ -116,6 +116,10 @@ public class AnthemCommand {
|
||||
return new AnthemCommand("IDN?");
|
||||
}
|
||||
|
||||
public static AnthemCommand customCommand(String customCommand) {
|
||||
return new AnthemCommand(customCommand);
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command + COMMAND_TERMINATOR;
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -35,7 +35,7 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AnthemCommandParser {
|
||||
private static final Pattern NUM_AVAILABLE_INPUTS_PATTERN = Pattern.compile("ICN([0-9])");
|
||||
private static final Pattern NUM_AVAILABLE_INPUTS_PATTERN = Pattern.compile("ICN([0-9]{1,2})");
|
||||
private static final Pattern INPUT_SHORT_NAME_PATTERN = Pattern.compile("ISN([0-9][0-9])(\\p{ASCII}*)");
|
||||
private static final Pattern INPUT_LONG_NAME_PATTERN = Pattern.compile("ILN([0-9][0-9])(\\p{ASCII}*)");
|
||||
private static final Pattern POWER_PATTERN = Pattern.compile("Z([0-9])POW([01])");
|
||||
@ -45,39 +45,27 @@ public class AnthemCommandParser {
|
||||
|
||||
private Logger logger = LoggerFactory.getLogger(AnthemCommandParser.class);
|
||||
|
||||
private AnthemHandler handler;
|
||||
private Map<String, String> inputShortNamesMap = new HashMap<>();
|
||||
private Map<String, String> inputLongNamesMap = new HashMap<>();
|
||||
|
||||
private Map<Integer, String> inputShortNamesMap = new HashMap<>();
|
||||
private Map<Integer, String> inputLongNamesMap = new HashMap<>();
|
||||
|
||||
private int numAvailableInputs;
|
||||
|
||||
public AnthemCommandParser(AnthemHandler anthemHandler) {
|
||||
this.handler = anthemHandler;
|
||||
}
|
||||
|
||||
public int getNumAvailableInputs() {
|
||||
return numAvailableInputs;
|
||||
}
|
||||
|
||||
public void parseMessage(String command) {
|
||||
public @Nullable AnthemUpdate parseCommand(String command) {
|
||||
if (!isValidCommand(command)) {
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
// Strip off the termination char and any whitespace
|
||||
String cmd = command.substring(0, command.indexOf(COMMAND_TERMINATION_CHAR)).trim();
|
||||
|
||||
// Zone command
|
||||
if (cmd.startsWith("Z")) {
|
||||
parseZoneCommand(cmd);
|
||||
return parseZoneCommand(cmd);
|
||||
}
|
||||
// Information command
|
||||
else if (cmd.startsWith("ID")) {
|
||||
parseInformationCommand(cmd);
|
||||
return parseInformationCommand(cmd);
|
||||
}
|
||||
// Number of inputs
|
||||
else if (cmd.startsWith("ICN")) {
|
||||
parseNumberOfAvailableInputsCommand(cmd);
|
||||
return parseNumberOfAvailableInputsCommand(cmd);
|
||||
}
|
||||
// Input short name
|
||||
else if (cmd.startsWith("ISN")) {
|
||||
@ -95,6 +83,15 @@ public class AnthemCommandParser {
|
||||
else {
|
||||
logger.trace("Command parser doesn't know how to handle command: '{}'", cmd);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public @Nullable String getInputShortName(String input) {
|
||||
return inputShortNamesMap.get(input);
|
||||
}
|
||||
|
||||
public @Nullable String getInputLongName(String input) {
|
||||
return inputLongNamesMap.get(input);
|
||||
}
|
||||
|
||||
private boolean isValidCommand(String command) {
|
||||
@ -106,45 +103,47 @@ public class AnthemCommandParser {
|
||||
return true;
|
||||
}
|
||||
|
||||
private void parseZoneCommand(String command) {
|
||||
private @Nullable AnthemUpdate parseZoneCommand(String command) {
|
||||
// Power update
|
||||
if (command.contains("POW")) {
|
||||
parsePower(command);
|
||||
return parsePower(command);
|
||||
}
|
||||
// Volume update
|
||||
else if (command.contains("VOL")) {
|
||||
parseVolume(command);
|
||||
return parseVolume(command);
|
||||
}
|
||||
// Mute update
|
||||
else if (command.contains("MUT")) {
|
||||
parseMute(command);
|
||||
return parseMute(command);
|
||||
}
|
||||
// Active input
|
||||
else if (command.contains("INP")) {
|
||||
parseActiveInput(command);
|
||||
return parseActiveInput(command);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseInformationCommand(String command) {
|
||||
private @Nullable AnthemUpdate parseInformationCommand(String command) {
|
||||
String value = command.substring(3, command.length());
|
||||
AnthemUpdate update = null;
|
||||
switch (command.substring(2, 3)) {
|
||||
case "M":
|
||||
handler.setModel(value);
|
||||
update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_MODEL_ID, value);
|
||||
break;
|
||||
case "R":
|
||||
handler.setRegion(value);
|
||||
update = AnthemUpdate.createPropertyUpdate(PROPERTY_REGION, value);
|
||||
break;
|
||||
case "S":
|
||||
handler.setSoftwareVersion(value);
|
||||
update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_FIRMWARE_VERSION, value);
|
||||
break;
|
||||
case "B":
|
||||
handler.setSoftwareBuildDate(value);
|
||||
update = AnthemUpdate.createPropertyUpdate(PROPERTY_SOFTWARE_BUILD_DATE, value);
|
||||
break;
|
||||
case "H":
|
||||
handler.setHardwareVersion(value);
|
||||
update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_HARDWARE_VERSION, value);
|
||||
break;
|
||||
case "N":
|
||||
handler.setMacAddress(value);
|
||||
update = AnthemUpdate.createPropertyUpdate(Thing.PROPERTY_MAC_ADDRESS, value);
|
||||
break;
|
||||
case "Q":
|
||||
// Ignore
|
||||
@ -153,21 +152,20 @@ public class AnthemCommandParser {
|
||||
logger.debug("Unknown info type");
|
||||
break;
|
||||
}
|
||||
return update;
|
||||
}
|
||||
|
||||
private void parseNumberOfAvailableInputsCommand(String command) {
|
||||
private @Nullable AnthemUpdate parseNumberOfAvailableInputsCommand(String command) {
|
||||
Matcher matcher = NUM_AVAILABLE_INPUTS_PATTERN.matcher(command);
|
||||
if (matcher != null) {
|
||||
try {
|
||||
matcher.find();
|
||||
String numAvailableInputsStr = matcher.group(1);
|
||||
DecimalType numAvailableInputs = DecimalType.valueOf(numAvailableInputsStr);
|
||||
handler.setNumAvailableInputs(numAvailableInputs.intValue());
|
||||
this.numAvailableInputs = numAvailableInputs.intValue();
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
|
||||
return AnthemUpdate.createPropertyUpdate(PROPERTY_NUM_AVAILABLE_INPUTS, matcher.group(1));
|
||||
} catch (IndexOutOfBoundsException | IllegalStateException e) {
|
||||
logger.debug("Parsing exception on command: {}", command, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseInputShortNameCommand(String command) {
|
||||
@ -182,11 +180,11 @@ public class AnthemCommandParser {
|
||||
logger.info("Command was not processed successfully by the device: '{}'", command);
|
||||
}
|
||||
|
||||
private void parseInputName(String command, @Nullable Matcher matcher, Map<Integer, String> map) {
|
||||
private void parseInputName(String command, @Nullable Matcher matcher, Map<String, String> map) {
|
||||
if (matcher != null) {
|
||||
try {
|
||||
matcher.find();
|
||||
int input = Integer.parseInt(matcher.group(1));
|
||||
String input = matcher.group(1);
|
||||
String inputName = matcher.group(2);
|
||||
map.putIfAbsent(input, inputName);
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
|
||||
@ -195,69 +193,65 @@ public class AnthemCommandParser {
|
||||
}
|
||||
}
|
||||
|
||||
private void parsePower(String command) {
|
||||
private @Nullable AnthemUpdate parsePower(String command) {
|
||||
Matcher mmatcher = POWER_PATTERN.matcher(command);
|
||||
if (mmatcher != null) {
|
||||
try {
|
||||
mmatcher.find();
|
||||
String zone = mmatcher.group(1);
|
||||
String power = mmatcher.group(2);
|
||||
handler.updateChannelState(zone, CHANNEL_POWER, "1".equals(power) ? OnOffType.ON : OnOffType.OFF);
|
||||
handler.checkPowerStatusChange(zone, power);
|
||||
return AnthemUpdate.createStateUpdate(zone, CHANNEL_POWER,
|
||||
"1".equals(power) ? OnOffType.ON : OnOffType.OFF);
|
||||
} catch (IndexOutOfBoundsException | IllegalStateException e) {
|
||||
logger.debug("Parsing exception on command: {}", command, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseVolume(String command) {
|
||||
private @Nullable AnthemUpdate parseVolume(String command) {
|
||||
Matcher matcher = VOLUME_PATTERN.matcher(command);
|
||||
if (matcher != null) {
|
||||
try {
|
||||
matcher.find();
|
||||
String zone = matcher.group(1);
|
||||
String volume = matcher.group(2);
|
||||
handler.updateChannelState(zone, CHANNEL_VOLUME_DB, DecimalType.valueOf(volume));
|
||||
return AnthemUpdate.createStateUpdate(zone, CHANNEL_VOLUME_DB, DecimalType.valueOf(volume));
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
|
||||
logger.debug("Parsing exception on command: {}", command, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseMute(String command) {
|
||||
private @Nullable AnthemUpdate parseMute(String command) {
|
||||
Matcher matcher = MUTE_PATTERN.matcher(command);
|
||||
if (matcher != null) {
|
||||
try {
|
||||
matcher.find();
|
||||
String zone = matcher.group(1);
|
||||
String mute = matcher.group(2);
|
||||
handler.updateChannelState(zone, CHANNEL_MUTE, "1".equals(mute) ? OnOffType.ON : OnOffType.OFF);
|
||||
return AnthemUpdate.createStateUpdate(zone, CHANNEL_MUTE,
|
||||
"1".equals(mute) ? OnOffType.ON : OnOffType.OFF);
|
||||
} catch (IndexOutOfBoundsException | IllegalStateException e) {
|
||||
logger.debug("Parsing exception on command: {}", command, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void parseActiveInput(String command) {
|
||||
private @Nullable AnthemUpdate parseActiveInput(String command) {
|
||||
Matcher matcher = ACTIVE_INPUT_PATTERN.matcher(command);
|
||||
if (matcher != null) {
|
||||
try {
|
||||
matcher.find();
|
||||
String zone = matcher.group(1);
|
||||
DecimalType activeInput = DecimalType.valueOf(matcher.group(2));
|
||||
handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT, activeInput);
|
||||
String name;
|
||||
name = inputShortNamesMap.get(activeInput.intValue());
|
||||
if (name != null) {
|
||||
handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT_SHORT_NAME, new StringType(name));
|
||||
}
|
||||
name = inputShortNamesMap.get(activeInput.intValue());
|
||||
if (name != null) {
|
||||
handler.updateChannelState(zone, CHANNEL_ACTIVE_INPUT_LONG_NAME, new StringType(name));
|
||||
}
|
||||
return AnthemUpdate.createStateUpdate(zone, CHANNEL_ACTIVE_INPUT, activeInput);
|
||||
} catch (NumberFormatException | IndexOutOfBoundsException | IllegalStateException e) {
|
||||
logger.debug("Parsing exception on command: {}", command, e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ import org.openhab.binding.anthem.internal.AnthemConfiguration;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.IncreaseDecreaseType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.StringType;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
@ -64,7 +65,7 @@ public class AnthemHandler extends BaseThingHandler {
|
||||
private @Nullable BufferedWriter writer;
|
||||
private @Nullable BufferedReader reader;
|
||||
|
||||
private AnthemCommandParser messageParser;
|
||||
private AnthemCommandParser commandParser;
|
||||
|
||||
private final BlockingQueue<AnthemCommand> sendQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
@ -83,7 +84,7 @@ public class AnthemHandler extends BaseThingHandler {
|
||||
|
||||
public AnthemHandler(Thing thing) {
|
||||
super(thing);
|
||||
messageParser = new AnthemCommandParser(this);
|
||||
commandParser = new AnthemCommandParser();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -120,6 +121,28 @@ public class AnthemHandler extends BaseThingHandler {
|
||||
if (groupId == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (CHANNEL_GROUP_GENERAL.equals(groupId)) {
|
||||
handleGeneralCommand(channelUID, command);
|
||||
} else {
|
||||
handleZoneCommand(groupId, channelUID, command);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleGeneralCommand(ChannelUID channelUID, Command command) {
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
case CHANNEL_COMMAND:
|
||||
if (command instanceof StringType) {
|
||||
sendCommand(AnthemCommand.customCommand(command.toString()));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Received general command '{}' for unhandled channel '{}'", command, channelUID.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleZoneCommand(String groupId, ChannelUID channelUID, Command command) {
|
||||
Zone zone = Zone.fromValue(groupId);
|
||||
|
||||
switch (channelUID.getIdWithoutGroup()) {
|
||||
@ -162,71 +185,11 @@ public class AnthemHandler extends BaseThingHandler {
|
||||
}
|
||||
break;
|
||||
default:
|
||||
logger.debug("Received command '{}' for unhandled channel '{}'", command, channelUID.getId());
|
||||
logger.debug("Received zone command '{}' for unhandled channel '{}'", command, channelUID.getId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void setModel(String model) {
|
||||
updateProperty("Model", model);
|
||||
}
|
||||
|
||||
public void setRegion(String region) {
|
||||
updateProperty("Region", region);
|
||||
}
|
||||
|
||||
public void setSoftwareVersion(String version) {
|
||||
updateProperty("Software Version", version);
|
||||
}
|
||||
|
||||
public void setSoftwareBuildDate(String date) {
|
||||
updateProperty("Software Build Date", date);
|
||||
}
|
||||
|
||||
public void setHardwareVersion(String version) {
|
||||
updateProperty("Hardware Version", version);
|
||||
}
|
||||
|
||||
public void setMacAddress(String mac) {
|
||||
updateProperty("Mac Address", mac);
|
||||
}
|
||||
|
||||
public void updateChannelState(String zone, String channelId, State state) {
|
||||
updateState(zone + "#" + channelId, state);
|
||||
}
|
||||
|
||||
public void checkPowerStatusChange(String zone, String power) {
|
||||
// Zone 1
|
||||
if (Zone.MAIN.equals(Zone.fromValue(zone))) {
|
||||
boolean newZone1PowerState = "1".equals(power) ? true : false;
|
||||
if (!zone1PreviousPowerState && newZone1PowerState) {
|
||||
// Power turned on for main zone.
|
||||
// This will cause the main zone channel states to be updated
|
||||
scheduler.submit(() -> queryAdditionalInformation(Zone.MAIN));
|
||||
}
|
||||
zone1PreviousPowerState = newZone1PowerState;
|
||||
}
|
||||
// Zone 2
|
||||
else if (Zone.ZONE2.equals(Zone.fromValue(zone))) {
|
||||
boolean newZone2PowerState = "1".equals(power) ? true : false;
|
||||
if (!zone2PreviousPowerState && newZone2PowerState) {
|
||||
// Power turned on for zone 2.
|
||||
// This will cause zone 2 channel states to be updated
|
||||
scheduler.submit(() -> queryAdditionalInformation(Zone.ZONE2));
|
||||
}
|
||||
zone2PreviousPowerState = newZone2PowerState;
|
||||
}
|
||||
}
|
||||
|
||||
public void setNumAvailableInputs(int numInputs) {
|
||||
// Request the names for all the inputs
|
||||
for (int input = 1; input <= numInputs; input++) {
|
||||
sendCommand(AnthemCommand.queryInputShortName(input));
|
||||
sendCommand(AnthemCommand.queryInputLongName(input));
|
||||
}
|
||||
updateProperty("Number of Inputs", String.valueOf(numInputs));
|
||||
}
|
||||
|
||||
private void queryAdditionalInformation(Zone zone) {
|
||||
// Request information about the device
|
||||
sendCommand(AnthemCommand.queryNumAvailableInputs());
|
||||
@ -418,7 +381,10 @@ public class AnthemHandler extends BaseThingHandler {
|
||||
if (c == COMMAND_TERMINATION_CHAR) {
|
||||
command = sbReader.toString();
|
||||
logger.debug("Reader thread sending command to parser: {}", command);
|
||||
messageParser.parseMessage(command);
|
||||
AnthemUpdate update = commandParser.parseCommand(command);
|
||||
if (update != null) {
|
||||
processUpdate(update);
|
||||
}
|
||||
sbReader.setLength(0);
|
||||
}
|
||||
}
|
||||
@ -434,4 +400,88 @@ public class AnthemHandler extends BaseThingHandler {
|
||||
logger.debug("Reader thread exiting");
|
||||
}
|
||||
}
|
||||
|
||||
private void processUpdate(AnthemUpdate update) {
|
||||
// State update
|
||||
if (update.isStateUpdate()) {
|
||||
StateUpdate stateUpdate = update.getStateUpdate();
|
||||
updateState(stateUpdate.getGroupId() + ChannelUID.CHANNEL_GROUP_SEPARATOR + stateUpdate.getChannelId(),
|
||||
stateUpdate.getState());
|
||||
postProcess(stateUpdate);
|
||||
}
|
||||
// Property update
|
||||
else if (update.isPropertyUpdate()) {
|
||||
PropertyUpdate propertyUpdate = update.getPropertyUpdate();
|
||||
updateProperty(propertyUpdate.getName(), propertyUpdate.getValue());
|
||||
postProcess(propertyUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
private void postProcess(StateUpdate stateUpdate) {
|
||||
switch (stateUpdate.getChannelId()) {
|
||||
case CHANNEL_POWER:
|
||||
checkPowerStatusChange(stateUpdate);
|
||||
break;
|
||||
case CHANNEL_ACTIVE_INPUT:
|
||||
updateInputNameChannels(stateUpdate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkPowerStatusChange(StateUpdate stateUpdate) {
|
||||
String zone = stateUpdate.getGroupId();
|
||||
State power = stateUpdate.getState();
|
||||
// Zone 1
|
||||
if (Zone.MAIN.equals(Zone.fromValue(zone))) {
|
||||
boolean newZone1PowerState = (power instanceof OnOffType && power == OnOffType.ON) ? true : false;
|
||||
if (!zone1PreviousPowerState && newZone1PowerState) {
|
||||
// Power turned on for main zone.
|
||||
// This will cause the main zone channel states to be updated
|
||||
scheduler.submit(() -> queryAdditionalInformation(Zone.MAIN));
|
||||
}
|
||||
zone1PreviousPowerState = newZone1PowerState;
|
||||
}
|
||||
// Zone 2
|
||||
else if (Zone.ZONE2.equals(Zone.fromValue(zone))) {
|
||||
boolean newZone2PowerState = (power instanceof OnOffType && power == OnOffType.ON) ? true : false;
|
||||
if (!zone2PreviousPowerState && newZone2PowerState) {
|
||||
// Power turned on for zone 2.
|
||||
// This will cause zone 2 channel states to be updated
|
||||
scheduler.submit(() -> queryAdditionalInformation(Zone.ZONE2));
|
||||
}
|
||||
zone2PreviousPowerState = newZone2PowerState;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateInputNameChannels(StateUpdate stateUpdate) {
|
||||
State state = stateUpdate.getState();
|
||||
String groupId = stateUpdate.getGroupId();
|
||||
if (state instanceof StringType) {
|
||||
updateState(groupId + ChannelUID.CHANNEL_GROUP_SEPARATOR + CHANNEL_ACTIVE_INPUT_SHORT_NAME,
|
||||
new StringType(commandParser.getInputShortName(state.toString())));
|
||||
updateState(groupId + ChannelUID.CHANNEL_GROUP_SEPARATOR + CHANNEL_ACTIVE_INPUT_LONG_NAME,
|
||||
new StringType(commandParser.getInputLongName(state.toString())));
|
||||
}
|
||||
}
|
||||
|
||||
private void postProcess(PropertyUpdate propertyUpdate) {
|
||||
switch (propertyUpdate.getName()) {
|
||||
case PROPERTY_NUM_AVAILABLE_INPUTS:
|
||||
queryAllInputNames(propertyUpdate);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void queryAllInputNames(PropertyUpdate propertyUpdate) {
|
||||
try {
|
||||
int numInputs = Integer.parseInt(propertyUpdate.getValue());
|
||||
for (int input = 1; input <= numInputs; input++) {
|
||||
sendCommand(AnthemCommand.queryInputShortName(input));
|
||||
sendCommand(AnthemCommand.queryInputLongName(input));
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Unable to convert property '{}' to integer: {}", propertyUpdate.getName(),
|
||||
propertyUpdate.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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.anthem.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link AnthemUpdate} class represents the result of parsing the response from
|
||||
* an Anthem processor.
|
||||
*
|
||||
* @author Mark Hilbush - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AnthemUpdate {
|
||||
private Object updateObject;
|
||||
|
||||
public AnthemUpdate(StateUpdate stateUpdate) {
|
||||
this.updateObject = stateUpdate;
|
||||
}
|
||||
|
||||
public AnthemUpdate(PropertyUpdate propertyUpdate) {
|
||||
this.updateObject = propertyUpdate;
|
||||
}
|
||||
|
||||
public static AnthemUpdate createStateUpdate(String groupId, String channelId, State state) {
|
||||
return new AnthemUpdate(new StateUpdate(groupId, channelId, state));
|
||||
}
|
||||
|
||||
public static AnthemUpdate createPropertyUpdate(String name, String value) {
|
||||
return new AnthemUpdate(new PropertyUpdate(name, value));
|
||||
}
|
||||
|
||||
public boolean isStateUpdate() {
|
||||
return updateObject instanceof StateUpdate;
|
||||
}
|
||||
|
||||
public boolean isPropertyUpdate() {
|
||||
return updateObject instanceof PropertyUpdate;
|
||||
}
|
||||
|
||||
public StateUpdate getStateUpdate() {
|
||||
if (updateObject instanceof StateUpdate stateUpdate) {
|
||||
return stateUpdate;
|
||||
}
|
||||
throw new IllegalStateException("Update object is not a state update");
|
||||
}
|
||||
|
||||
public PropertyUpdate getPropertyUpdate() {
|
||||
if (updateObject instanceof PropertyUpdate propertyUpdate) {
|
||||
return propertyUpdate;
|
||||
}
|
||||
throw new IllegalStateException("Update object is not a property update");
|
||||
}
|
||||
}
|
@ -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.anthem.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link PropertyUpdate} class represents a property that need to be set
|
||||
* or updated on the Anthem thing.
|
||||
*
|
||||
* @author Mark Hilbush - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class PropertyUpdate {
|
||||
private String name;
|
||||
private String value;
|
||||
|
||||
public PropertyUpdate(String name, String value) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* 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.anthem.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.types.State;
|
||||
|
||||
/**
|
||||
* The {@link StateUpdate} class represents a state that needs to be updated
|
||||
* on an Anthem thing channel.
|
||||
*
|
||||
* @author Mark Hilbush - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class StateUpdate {
|
||||
private String groupId;
|
||||
private String channelId;
|
||||
private State state;
|
||||
|
||||
public StateUpdate(String groupId, String channelId, State state) {
|
||||
this.groupId = groupId;
|
||||
this.channelId = channelId;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public String getGroupId() {
|
||||
return groupId;
|
||||
}
|
||||
|
||||
public String getChannelId() {
|
||||
return channelId;
|
||||
}
|
||||
|
||||
public State getState() {
|
||||
return state;
|
||||
}
|
||||
}
|
@ -25,6 +25,8 @@ thing-type.config.anthem.anthem.reconnectIntervalMinutes.description = The time
|
||||
|
||||
# channel group types
|
||||
|
||||
channel-group-type.anthem.general.label = General Control
|
||||
channel-group-type.anthem.general.description = General channels for this AVR
|
||||
channel-group-type.anthem.zone.label = Zone Control
|
||||
channel-group-type.anthem.zone.description = Channels for a zone of this processor
|
||||
|
||||
@ -36,6 +38,8 @@ channel-type.anthem.activeInputLongName.label = Active Input Long Name
|
||||
channel-type.anthem.activeInputLongName.description = Long friendly name of the active input source
|
||||
channel-type.anthem.activeInputShortName.label = Active Input Short Name
|
||||
channel-type.anthem.activeInputShortName.description = Short friendly name of the active input source
|
||||
channel-type.anthem.command.label = Command
|
||||
channel-type.anthem.command.description = Send a custom command to the processor
|
||||
channel-type.anthem.volumeDB.label = Volume dB
|
||||
channel-type.anthem.volumeDB.description = Set the volume level dB between -90 and 0
|
||||
|
||||
|
@ -9,6 +9,7 @@
|
||||
<description>Thing for Anthem AV processor</description>
|
||||
|
||||
<channel-groups>
|
||||
<channel-group id="general" typeId="general"/>
|
||||
<channel-group id="1" typeId="zone">
|
||||
<label>Main Zone</label>
|
||||
<description>Controls zone 1 (the main zone) of the processor</description>
|
||||
@ -50,6 +51,14 @@
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<channel-group-type id="general">
|
||||
<label>General Control</label>
|
||||
<description>General channels for this AVR</description>
|
||||
<channels>
|
||||
<channel id="command" typeId="command"/>
|
||||
</channels>
|
||||
</channel-group-type>
|
||||
|
||||
<channel-group-type id="zone">
|
||||
<label>Zone Control</label>
|
||||
<description>Channels for a zone of this processor</description>
|
||||
@ -93,4 +102,11 @@
|
||||
<state readOnly="true"></state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="command">
|
||||
<item-type>String</item-type>
|
||||
<label>Command</label>
|
||||
<description>Send a custom command to the processor</description>
|
||||
<state readOnly="false"></state>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
||||
|
@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes" ?>
|
||||
<update:update-descriptions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:update="https://openhab.org/schemas/update-description/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/update-description/v1.0.0 https://openhab.org/schemas/update-description-1.0.0.xsd">
|
||||
|
||||
<thing-type uid="anthem:anthem">
|
||||
|
||||
<instruction-set targetVersion="1">
|
||||
<add-channel id="command" groupIds="general">
|
||||
<type>anthem:command</type>
|
||||
</add-channel>
|
||||
</instruction-set>
|
||||
|
||||
</thing-type>
|
||||
|
||||
</update:update-descriptions>
|
@ -0,0 +1,182 @@
|
||||
/**
|
||||
* 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.anthem.internal.handler;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.openhab.binding.anthem.internal.AnthemBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
|
||||
/**
|
||||
* The {@link AnthemCommandParserTest} is responsible for testing the functionality
|
||||
* of the Anthem command parser.
|
||||
*
|
||||
* @author Mark Hilbush - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AnthemCommandParserTest {
|
||||
|
||||
AnthemCommandParser parser = new AnthemCommandParser();
|
||||
|
||||
@Test
|
||||
public void testInvalidCommands() {
|
||||
@Nullable
|
||||
AnthemUpdate update;
|
||||
|
||||
update = parser.parseCommand("BOGUS_COMMAND;");
|
||||
assertEquals(null, update);
|
||||
|
||||
update = parser.parseCommand("UNTERMINATED_COMMAND");
|
||||
assertEquals(null, update);
|
||||
|
||||
update = parser.parseCommand("Z1POW0");
|
||||
assertEquals(null, update);
|
||||
|
||||
update = parser.parseCommand("X");
|
||||
assertEquals(null, update);
|
||||
|
||||
update = parser.parseCommand("Y;");
|
||||
assertEquals(null, update);
|
||||
|
||||
update = parser.parseCommand("Z1POW67;");
|
||||
assertEquals(null, update);
|
||||
|
||||
update = parser.parseCommand("POW0;");
|
||||
assertEquals(null, update);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPowerCommands() {
|
||||
@Nullable
|
||||
AnthemUpdate update;
|
||||
|
||||
update = parser.parseCommand("Z1POW1;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertTrue(update.isStateUpdate());
|
||||
assertFalse(update.isPropertyUpdate());
|
||||
assertEquals("1", update.getStateUpdate().getGroupId());
|
||||
assertEquals(CHANNEL_POWER, update.getStateUpdate().getChannelId());
|
||||
assertEquals(OnOffType.ON, update.getStateUpdate().getState());
|
||||
}
|
||||
|
||||
update = parser.parseCommand("Z2POW0;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertEquals("2", update.getStateUpdate().getGroupId());
|
||||
assertEquals(CHANNEL_POWER, update.getStateUpdate().getChannelId());
|
||||
assertEquals(OnOffType.OFF, update.getStateUpdate().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testVolumeCommands() {
|
||||
@Nullable
|
||||
AnthemUpdate update;
|
||||
|
||||
update = parser.parseCommand("Z1VOL55;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertEquals("1", update.getStateUpdate().getGroupId());
|
||||
assertEquals(CHANNEL_VOLUME_DB, update.getStateUpdate().getChannelId());
|
||||
assertEquals(new DecimalType(55), update.getStateUpdate().getState());
|
||||
}
|
||||
|
||||
update = parser.parseCommand("Z2VOL99;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertEquals("2", update.getStateUpdate().getGroupId());
|
||||
assertEquals(CHANNEL_VOLUME_DB, update.getStateUpdate().getChannelId());
|
||||
assertEquals(new DecimalType(99), update.getStateUpdate().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMuteCommands() {
|
||||
@Nullable
|
||||
AnthemUpdate update;
|
||||
|
||||
update = parser.parseCommand("Z1MUT1;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertEquals("1", update.getStateUpdate().getGroupId());
|
||||
assertEquals(CHANNEL_MUTE, update.getStateUpdate().getChannelId());
|
||||
assertEquals(OnOffType.ON, update.getStateUpdate().getState());
|
||||
}
|
||||
|
||||
update = parser.parseCommand("Z2MUT0;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertTrue(update.isStateUpdate());
|
||||
assertEquals("2", update.getStateUpdate().getGroupId());
|
||||
assertEquals(CHANNEL_MUTE, update.getStateUpdate().getChannelId());
|
||||
assertEquals(OnOffType.OFF, update.getStateUpdate().getState());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNumInputsCommand() {
|
||||
@Nullable
|
||||
AnthemUpdate update;
|
||||
|
||||
update = parser.parseCommand("ICN8;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertTrue(update.isPropertyUpdate());
|
||||
assertEquals(PROPERTY_NUM_AVAILABLE_INPUTS, update.getPropertyUpdate().getName());
|
||||
assertEquals("8", update.getPropertyUpdate().getValue());
|
||||
}
|
||||
|
||||
update = parser.parseCommand("ICN15;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertTrue(update.isPropertyUpdate());
|
||||
assertEquals(PROPERTY_NUM_AVAILABLE_INPUTS, update.getPropertyUpdate().getName());
|
||||
assertEquals("15", update.getPropertyUpdate().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRegionProperty() {
|
||||
@Nullable
|
||||
AnthemUpdate update;
|
||||
|
||||
update = parser.parseCommand("IDRUS;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertTrue(update.isPropertyUpdate());
|
||||
assertFalse(update.isStateUpdate());
|
||||
assertEquals(PROPERTY_REGION, update.getPropertyUpdate().getName());
|
||||
assertEquals("US", update.getPropertyUpdate().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSoftwareVersionProperty() {
|
||||
@Nullable
|
||||
AnthemUpdate update;
|
||||
|
||||
update = parser.parseCommand("IDS1.2.3.4;");
|
||||
assertNotEquals(null, update);
|
||||
if (update != null) {
|
||||
assertTrue(update.isPropertyUpdate());
|
||||
assertEquals(Thing.PROPERTY_FIRMWARE_VERSION, update.getPropertyUpdate().getName());
|
||||
assertEquals("1.2.3.4", update.getPropertyUpdate().getValue());
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user