added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.pjlinkdevice-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-pjlinkdevice" description="PJLinkDevice Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.pjlinkdevice/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Exception thrown whenever the thing configuration is invalid
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class ConfigurationException extends Exception {
private static final long serialVersionUID = -3319800607314286998L;
public ConfigurationException(String string) {
super(string);
}
public ConfigurationException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.binding.BaseDynamicStateDescriptionProvider;
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
import org.openhab.core.thing.type.DynamicStateDescriptionProvider;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* Dynamic provider of state options for the input selection of the PJLink device.
*
* @author Nils Schnabel - Initial contribution
*/
@Component(service = { DynamicStateDescriptionProvider.class, InputChannelStateDescriptionProvider.class })
@NonNullByDefault
public class InputChannelStateDescriptionProvider extends BaseDynamicStateDescriptionProvider {
@Reference
protected void setChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
}
protected void unsetChannelTypeI18nLocalizationService(
final ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
this.channelTypeI18nLocalizationService = null;
}
}

View File

@@ -0,0 +1,70 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link PJLinkDeviceBindingConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PJLinkDeviceBindingConstants {
private static final String BINDING_ID = "pjLinkDevice";
// List of all thing type UIDs
public static final ThingTypeUID THING_TYPE_PJLINK = new ThingTypeUID(BINDING_ID, "pjLinkDevice");
// List of all channel type IDs
public static final String CHANNEL_TYPE_POWER = "power";
public static final String CHANNEL_TYPE_INPUT = "input";
public static final String CHANNEL_TYPE_AUDIO_MUTE = "audioMute";
public static final String CHANNEL_TYPE_VIDEO_MUTE = "videoMute";
public static final String CHANNEL_TYPE_LAMP_HOURS = "lampHours";
public static final String CHANNEL_TYPE_LAMP_ACTIVE = "lampActive";
// List of all channel IDs
public static final String CHANNEL_POWER = "power";
public static final String CHANNEL_INPUT = "input";
public static final String CHANNEL_AUDIO_MUTE = "audioMute";
public static final String CHANNEL_VIDEO_MUTE = "videoMute";
public static final String CHANNEL_LAMP_1_HOURS = "lamp1Hours";
public static final String CHANNEL_LAMP_1_ACTIVE = "lamp1Active";
// List of all channel parameter names
public static final String CHANNEL_PARAMETER_LAMP_NUMBER = "lampNumber";
public static final int DEFAULT_PORT = 4352;
public static final int DEFAULT_SCAN_TIMEOUT_SECONDS = 60;
// configuration
public static final String PARAMETER_HOSTNAME = "ipAddress";
public static final String PARAMETER_PORT = "tcpPort";
public static final long DISCOVERY_RESULT_TTL_SECONDS = TimeUnit.MINUTES.toSeconds(10);
// information disclosed by device
public static final String PROPERTY_CLASS = "disclosedPjLinkClass";
public static final String PROPERTY_NAME = "disclosedName";
public static final String PROPERTY_ERROR_STATUS = "disclosedErrorStatus";
public static final String PROPERTY_LAMP_HOURS = "disclosedLampHours";
public static final String PROPERTY_OTHER_INFORMATION = "disclosedOtherInformation";
// calculated properties
public static final String PROPERTY_AUTHENTICATION_REQUIRED = "authenticationRequired";
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The {@link PJLinkDeviceConfiguration} class contains fields mapping thing configuration parameters.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PJLinkDeviceConfiguration {
public @Nullable String ipAddress;
public int tcpPort;
public @Nullable String adminPassword;
public int refreshInterval;
public boolean refreshPower;
public boolean refreshMute;
public boolean refreshInputChannel;
public boolean refreshLampState;
public int autoReconnectInterval;
}

View File

@@ -0,0 +1,447 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal;
import static org.openhab.binding.pjlinkdevice.internal.PJLinkDeviceBindingConstants.*;
import java.io.IOException;
import java.math.BigDecimal;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AuthenticationException;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
import org.openhab.binding.pjlinkdevice.internal.device.command.input.Input;
import org.openhab.binding.pjlinkdevice.internal.device.command.lampstatus.LampStatesResponse.LampState;
import org.openhab.binding.pjlinkdevice.internal.device.command.mute.MuteInstructionCommand.MuteInstructionChannel;
import org.openhab.binding.pjlinkdevice.internal.device.command.mute.MuteQueryResponse.MuteQueryResponseValue;
import org.openhab.binding.pjlinkdevice.internal.device.command.power.PowerQueryResponse.PowerQueryResponseValue;
import org.openhab.core.config.core.validation.ConfigValidationException;
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.Channel;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.type.ChannelTypeUID;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.StateOption;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link PJLinkDeviceHandler} is responsible for handling commands, which are
* sent to one of the channels.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PJLinkDeviceHandler extends BaseThingHandler {
private @Nullable PJLinkDeviceConfiguration config;
private @Nullable PJLinkDevice device;
private InputChannelStateDescriptionProvider stateDescriptionProvider;
private final Logger logger = LoggerFactory.getLogger(PJLinkDeviceHandler.class);
private @Nullable ScheduledFuture<?> refreshJob;
private @Nullable ScheduledFuture<?> setupJob;
public PJLinkDeviceHandler(Thing thing, InputChannelStateDescriptionProvider stateDescriptionProvider) {
super(thing);
this.stateDescriptionProvider = stateDescriptionProvider;
}
@Override
public void dispose() {
clearSetupJob();
clearRefreshInterval();
final PJLinkDevice device = this.device;
if (device != null) {
device.dispose();
}
this.config = null;
this.device = null;
}
public void refresh(PJLinkDeviceConfiguration config) {
PJLinkDeviceHandler.this.logger.debug("Polling device status...");
// build list of channels to be refreshed
List<String> channelNames = new ArrayList<>();
if (config.refreshPower) {
channelNames.add(CHANNEL_POWER);
}
if (config.refreshMute) {
// this updates both CHANNEL_AUDIO_MUTE and CHANNEL_VIDEO_MUTE
channelNames.add(CHANNEL_AUDIO_MUTE);
}
if (config.refreshInputChannel) {
channelNames.add(CHANNEL_INPUT);
}
if (config.refreshLampState) {
// this updates both CHANNEL_LAMP_ACTIVE and CHANNEL_LAMP_HOURS for all lamps
channelNames.add(CHANNEL_LAMP_1_HOURS);
}
// refresh all channels enabled for refreshing
for (String channelName : channelNames) {
// Do not poll if device is offline
if (PJLinkDeviceHandler.this.getThing().getStatus() != ThingStatus.ONLINE) {
PJLinkDeviceHandler.this.logger.debug("Not polling device status because device is offline");
// setup() will schedule a new refresh interval after successful reconnection, cancel this one
this.clearRefreshInterval();
return;
}
PJLinkDeviceHandler.this.handleCommand(new ChannelUID(getThing().getUID(), channelName),
RefreshType.REFRESH);
}
}
public PJLinkDevice getDevice() throws UnknownHostException, ConfigurationException {
PJLinkDevice device = this.device;
if (device == null) {
PJLinkDeviceConfiguration config = getConfiguration();
this.device = device = new PJLinkDevice(config.tcpPort, InetAddress.getByName(config.ipAddress),
config.adminPassword);
}
return device;
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
logger.trace("Received command {} on channel {}", command, channelUID.getId());
try {
PJLinkDevice device = getDevice();
String channelTypeId = getChannelTypeId(channelUID);
if (channelTypeId == null) {
logger.debug("unknown channel {}", channelUID);
return;
}
switch (channelTypeId) {
case CHANNEL_TYPE_POWER:
logger.trace("Received power command {}", command);
if (command == OnOffType.ON) {
device.powerOn();
} else if (command == OnOffType.OFF) {
device.powerOff();
} else if (command == RefreshType.REFRESH) {
updateState(PJLinkDeviceBindingConstants.CHANNEL_POWER,
PowerQueryResponseValue.POWER_ON.equals(device.getPowerStatus().getResult())
? OnOffType.ON
: OnOffType.OFF);
} else {
logger.debug("Received unknown power command {}", command);
}
break;
case CHANNEL_TYPE_INPUT:
if (command == RefreshType.REFRESH) {
StringType input = new StringType(device.getInputStatus().getResult().getValue());
updateState(PJLinkDeviceBindingConstants.CHANNEL_INPUT, input);
} else if (command instanceof StringType) {
logger.trace("Received input command {}", command);
Input input = new Input(((StringType) command).toString());
device.setInput(input);
} else {
logger.debug("Received unknown channel command {}", command);
}
break;
case CHANNEL_TYPE_AUDIO_MUTE:
case CHANNEL_TYPE_VIDEO_MUTE:
boolean isAudioMute = channelTypeId.equals(PJLinkDeviceBindingConstants.CHANNEL_TYPE_AUDIO_MUTE);
boolean isVideoMute = channelTypeId.equals(PJLinkDeviceBindingConstants.CHANNEL_TYPE_VIDEO_MUTE);
if (isVideoMute || isAudioMute) {
if (command == RefreshType.REFRESH) {
// refresh both video and audio mute, as it's one request
MuteQueryResponseValue muteStatus = device.getMuteStatus();
updateState(PJLinkDeviceBindingConstants.CHANNEL_AUDIO_MUTE,
muteStatus.isAudioMuted() ? OnOffType.ON : OnOffType.OFF);
updateState(PJLinkDeviceBindingConstants.CHANNEL_VIDEO_MUTE,
muteStatus.isVideoMuted() ? OnOffType.ON : OnOffType.OFF);
} else {
if (isAudioMute) {
logger.trace("Received audio mute command {}", command);
boolean muteOn = command == OnOffType.ON;
device.setMute(MuteInstructionChannel.AUDIO, muteOn);
}
if (isVideoMute) {
logger.trace("Received video mute command {}", command);
boolean muteOn = command == OnOffType.ON;
device.setMute(MuteInstructionChannel.VIDEO, muteOn);
}
}
} else {
logger.debug("Received unknown audio/video mute command {}", command);
}
break;
case CHANNEL_TYPE_LAMP_ACTIVE:
case CHANNEL_TYPE_LAMP_HOURS:
if (command == RefreshType.REFRESH) {
List<LampState> lampStates = device.getLampStatesCached();
// update all lamp related channels, as the response contains information about all of them
for (Channel lampChannel : thing.getChannels()) {
String lampChannelTypeId = getChannelTypeId(lampChannel.getUID());
if (lampChannelTypeId == null) {
continue;
}
boolean isLampActiveChannel = CHANNEL_TYPE_LAMP_ACTIVE.equals(lampChannelTypeId);
boolean isLampHoursChannel = CHANNEL_TYPE_LAMP_HOURS.equals(lampChannelTypeId);
if (isLampActiveChannel || isLampHoursChannel) {
int lampNumber = ((BigDecimal) lampChannel.getConfiguration()
.get(CHANNEL_PARAMETER_LAMP_NUMBER)).intValue();
try {
LampState lampState = lampStates.get(lampNumber - 1);
if (isLampActiveChannel) {
updateState(lampChannel.getUID(),
lampState.isActive() ? OnOffType.ON : OnOffType.OFF);
}
if (isLampHoursChannel) {
updateState(lampChannel.getUID(), new DecimalType(lampState.getLampHours()));
}
} catch (IndexOutOfBoundsException e) {
logger.debug("Status information for lamp {} is not available", lampNumber);
throw new ConfigurationException(
"Status information for lamp " + lampNumber + " is not available");
}
}
}
} else {
logger.debug("Received unknown lamp state command {}", command);
}
break;
default:
logger.debug("unknown channel {}", channelUID);
break;
}
logger.trace("Successfully handled command {} on channel {}", command, channelUID.getId());
handleCommunicationEstablished();
} catch (IOException | ResponseException e) {
handleCommunicationException(e);
} catch (ConfigurationException e) {
handleConfigurationException(e);
} catch (AuthenticationException e) {
handleAuthenticationException(e);
}
}
private @Nullable String getChannelTypeId(ChannelUID channelUID) {
Channel channel = thing.getChannel(channelUID);
if (channel == null) {
logger.debug("channel is null");
return null;
}
ChannelTypeUID channelTypeUID = channel.getChannelTypeUID();
if (channelTypeUID == null) {
logger.debug("channelTypeUID for channel {} is null", channel);
return null;
}
String channelTypeId = channelTypeUID.getId();
if (channelTypeId == null) {
logger.debug("channelTypeId for channelTypeUID {} is null", channelTypeUID);
return null;
}
return channelTypeId;
}
private void handleCommunicationEstablished() {
updateStatus(ThingStatus.ONLINE);
}
@Override
public void initialize() {
this.setup(0);
}
public void setup(int delay) {
this.clearSetupJob();
this.setupJob = scheduler.schedule(() -> {
try {
setupDevice();
handleCommunicationEstablished();
setupRefreshInterval();
logger.trace("device {} setup up successfully", this.getThing().getUID());
} catch (ResponseException | IOException e) {
handleCommunicationException(e);
} catch (ConfigurationException e) {
handleConfigurationException(e);
} catch (AuthenticationException e) {
handleAuthenticationException(e);
}
}, delay, TimeUnit.SECONDS);
}
protected PJLinkDeviceConfiguration getConfiguration() throws ConfigurationException {
PJLinkDeviceConfiguration config = this.config;
if (config != null) {
return config;
}
Map<String, String> validationMessages = new HashMap<>();
try {
validateConfigurationParameters(getThing().getConfiguration().getProperties());
} catch (ConfigValidationException e) {
validationMessages.putAll(e.getValidationMessages());
}
this.config = config = getConfigAs(PJLinkDeviceConfiguration.class);
int autoReconnectInterval = config.autoReconnectInterval;
if (autoReconnectInterval != 0 && autoReconnectInterval < 30) {
validationMessages.put("autoReconnectInterval", "allowed values are 0 (never) or >30");
}
if (!validationMessages.isEmpty()) {
String message = validationMessages.entrySet().stream()
.map((Map.Entry<String, String> a) -> (a.getKey() + ": " + a.getValue()))
.collect(Collectors.joining("; "));
throw new ConfigurationException(message);
}
return config;
}
private void clearSetupJob() {
final ScheduledFuture<?> setupJob = this.setupJob;
if (setupJob != null) {
setupJob.cancel(true);
this.setupJob = null;
}
}
private void clearRefreshInterval() {
final ScheduledFuture<?> refreshJob = this.refreshJob;
if (refreshJob != null) {
refreshJob.cancel(true);
this.refreshJob = null;
}
}
private void handleAuthenticationException(AuthenticationException e) {
this.clearRefreshInterval();
updateProperty(PJLinkDeviceBindingConstants.PROPERTY_AUTHENTICATION_REQUIRED, Boolean.TRUE.toString());
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
private void handleCommunicationException(Exception e) {
this.clearRefreshInterval();
PJLinkDeviceConfiguration config = this.config;
if (config != null && config.autoReconnectInterval > 0) {
this.setup(config.autoReconnectInterval);
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
private void handleConfigurationException(ConfigurationException e) {
this.clearRefreshInterval();
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
}
private void setupDevice() throws ConfigurationException, IOException, AuthenticationException, ResponseException {
PJLinkDevice device = getDevice();
device.checkAvailability();
updateDeviceProperties(device);
updateInputChannelStates(device);
}
private void setupRefreshInterval() throws ConfigurationException {
clearRefreshInterval();
PJLinkDeviceConfiguration config = PJLinkDeviceHandler.this.getConfiguration();
boolean atLeastOneChannelToBeRefreshed = config.refreshPower || config.refreshMute || config.refreshInputChannel
|| config.refreshLampState;
if (config.refreshInterval > 0 && atLeastOneChannelToBeRefreshed) {
refreshJob = scheduler.scheduleWithFixedDelay(() -> refresh(config), 0, config.refreshInterval,
TimeUnit.SECONDS);
}
}
private void updateDeviceProperties(PJLinkDevice device) throws IOException, AuthenticationException {
Map<String, String> properties = editProperties();
properties.put(PJLinkDeviceBindingConstants.PROPERTY_AUTHENTICATION_REQUIRED,
device.getAuthenticationRequired().toString());
try {
properties.put(PJLinkDeviceBindingConstants.PROPERTY_NAME, device.getName());
} catch (ResponseException e) {
logger.debug("Error retrieving property {}", PJLinkDeviceBindingConstants.PROPERTY_NAME, e);
}
try {
properties.put(Thing.PROPERTY_VENDOR, device.getManufacturer());
} catch (ResponseException e) {
logger.debug("Error retrieving property {}", Thing.PROPERTY_VENDOR, e);
}
try {
properties.put(Thing.PROPERTY_MODEL_ID, device.getModel());
} catch (ResponseException e) {
logger.debug("Error retrieving property {}", Thing.PROPERTY_MODEL_ID, e);
}
try {
properties.put(PJLinkDeviceBindingConstants.PROPERTY_CLASS, device.getPJLinkClass());
} catch (ResponseException e) {
logger.debug("Error retrieving property {}", PJLinkDeviceBindingConstants.PROPERTY_CLASS, e);
}
try {
device.getErrorStatus().forEach((k, v) -> properties
.put(PJLinkDeviceBindingConstants.PROPERTY_ERROR_STATUS + k.getCamelCaseText(), v.getText()));
} catch (ResponseException e) {
logger.debug("Error retrieving property {}", PJLinkDeviceBindingConstants.PROPERTY_ERROR_STATUS, e);
}
try {
properties.put(PJLinkDeviceBindingConstants.PROPERTY_OTHER_INFORMATION, device.getOtherInformation());
} catch (ResponseException e) {
logger.debug("Error retrieving property {}", PJLinkDeviceBindingConstants.PROPERTY_OTHER_INFORMATION, e);
}
updateProperties(properties);
}
private void updateInputChannelStates(PJLinkDevice device)
throws ResponseException, IOException, AuthenticationException {
Set<Input> inputs = device.getAvailableInputs();
List<StateOption> states = new LinkedList<>();
for (Input input : inputs) {
states.add(new StateOption(input.getPJLinkRepresentation(), input.getText()));
}
ChannelUID channelUid = new ChannelUID(getThing().getUID(), PJLinkDeviceBindingConstants.CHANNEL_INPUT);
this.stateDescriptionProvider.setStateOptions(channelUid, states);
}
}

View File

@@ -0,0 +1,62 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal;
import static org.openhab.binding.pjlinkdevice.internal.PJLinkDeviceBindingConstants.THING_TYPE_PJLINK;
import java.util.Collections;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
/**
* The {@link PJLinkDeviceHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
@Component(configurationPid = "binding.pjlinkdevice", service = { ThingHandlerFactory.class })
public class PJLinkDeviceHandlerFactory extends BaseThingHandlerFactory {
private InputChannelStateDescriptionProvider stateDescriptionProvider;
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_PJLINK);
@Activate
public PJLinkDeviceHandlerFactory(@Reference InputChannelStateDescriptionProvider provider) {
this.stateDescriptionProvider = provider;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
@Override
protected @Nullable ThingHandler createHandler(Thing thing) {
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (THING_TYPE_PJLINK.equals(thingTypeUID)) {
return new PJLinkDeviceHandler(thing, this.stateDescriptionProvider);
}
return null;
}
}

View File

@@ -0,0 +1,369 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NoRouteToHostException;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.text.MessageFormat;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pjlinkdevice.internal.device.command.AuthenticationException;
import org.openhab.binding.pjlinkdevice.internal.device.command.CachedCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
import org.openhab.binding.pjlinkdevice.internal.device.command.authentication.AuthenticationCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.errorstatus.ErrorStatusQueryCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.errorstatus.ErrorStatusQueryResponse.ErrorStatusDevicePart;
import org.openhab.binding.pjlinkdevice.internal.device.command.errorstatus.ErrorStatusQueryResponse.ErrorStatusQueryResponseState;
import org.openhab.binding.pjlinkdevice.internal.device.command.identification.IdentificationCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.input.Input;
import org.openhab.binding.pjlinkdevice.internal.device.command.input.InputInstructionCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.input.InputListQueryCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.input.InputQueryCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.input.InputQueryResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.lampstatus.LampStatesCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.lampstatus.LampStatesResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.lampstatus.LampStatesResponse.LampState;
import org.openhab.binding.pjlinkdevice.internal.device.command.mute.MuteInstructionCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.mute.MuteInstructionCommand.MuteInstructionChannel;
import org.openhab.binding.pjlinkdevice.internal.device.command.mute.MuteInstructionCommand.MuteInstructionState;
import org.openhab.binding.pjlinkdevice.internal.device.command.mute.MuteQueryCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.mute.MuteQueryResponse.MuteQueryResponseValue;
import org.openhab.binding.pjlinkdevice.internal.device.command.power.PowerInstructionCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.power.PowerQueryCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.power.PowerQueryResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents a PJLink device and takes care of managing the TCP connection, executing commands, and authentication.
*
* The central interface to get information about and set status on the device.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PJLinkDevice {
private static final int TIMEOUT = 30000;
protected int tcpPort;
protected InetAddress ipAddress;
protected @Nullable String adminPassword;
protected boolean authenticationRequired;
protected @Nullable BufferedReader reader;
protected @Nullable Socket socket;
protected int timeout = TIMEOUT;
private final Logger logger = LoggerFactory.getLogger(PJLinkDevice.class);
private String prefixForNextCommand = "";
private @Nullable Instant socketCreatedOn;
private CachedCommand<LampStatesResponse> cachedLampHoursCommand = new CachedCommand<>(new LampStatesCommand(this));
public PJLinkDevice(int tcpPort, InetAddress ipAddress, @Nullable String adminPassword, int timeout) {
this.tcpPort = tcpPort;
this.ipAddress = ipAddress;
this.adminPassword = adminPassword;
this.timeout = timeout;
}
public PJLinkDevice(int tcpPort, InetAddress ipAddress, @Nullable String adminPassword) {
this(tcpPort, ipAddress, adminPassword, TIMEOUT);
}
@Override
public String toString() {
return "PJLink " + this.ipAddress + ":" + this.tcpPort;
}
protected Socket connect() throws IOException, ResponseException, AuthenticationException {
return connect(false);
}
protected BufferedReader getReader() throws IOException, ResponseException, AuthenticationException {
BufferedReader reader = this.reader;
if (reader == null) {
this.reader = reader = new BufferedReader(new InputStreamReader(connect().getInputStream()));
}
return reader;
}
protected void closeSocket(@Nullable Socket socket) {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
// okay then, at least we tried
logger.trace("closing of socket failed", e);
}
}
this.socket = null;
this.reader = null;
}
protected Socket connect(boolean forceReconnect) throws IOException, ResponseException, AuthenticationException {
Instant now = Instant.now();
Socket socket = this.socket;
boolean connectionTooOld = false;
if (this.socketCreatedOn != null) {
long millisecondsSinceLastConnect = Duration.between(this.socketCreatedOn, now).toMillis();
// according to the PJLink specification, the device closes the connection after 30s idle (without notice),
// so to be on the safe side we do not reuse sockets older than 20s
connectionTooOld = millisecondsSinceLastConnect > 20 * 1000;
}
if (forceReconnect || connectionTooOld) {
if (socket != null) {
closeSocket(socket);
}
}
this.socketCreatedOn = now;
if (socket != null && socket.isConnected() && !socket.isClosed()) {
return socket;
}
SocketAddress socketAddress = new InetSocketAddress(ipAddress, tcpPort);
try {
this.socket = socket = new Socket();
socket.connect(socketAddress, timeout);
socket.setSoTimeout(timeout);
BufferedReader reader = getReader();
String header = reader.readLine();
if (header == null) {
throw new ResponseException("No PJLink header received from the device");
}
header = header.toUpperCase();
switch (header.substring(0, "PJLINK x".length())) {
case "PJLINK 0":
logger.debug("Authentication not needed");
this.authenticationRequired = false;
break;
case "PJLINK 1":
logger.debug("Authentication needed");
this.authenticationRequired = true;
if (this.adminPassword == null) {
closeSocket(socket);
throw new AuthenticationException("No password provided, but device requires authentication");
} else {
try {
authenticate(header.substring("PJLINK 1 ".length()));
} catch (AuthenticationException e) {
// propagate AuthenticationException
throw e;
} catch (ResponseException e) {
// maybe only the test command is broken on the device
// as long as we don't get an AuthenticationException, we'll just ignore it for now
}
}
break;
default:
logger.debug("Cannot handle introduction response {}", header);
throw new ResponseException("Invalid header: " + header);
}
return socket;
} catch (ConnectException | SocketTimeoutException | NoRouteToHostException e) {
// these exceptions indicate that there's no device at this address, just throw without logging
throw e;
} catch (IOException | ResponseException e) {
// these exceptions seem to be more interesting in the log during a scan
// This should not happen and might be a user configuration issue, we log a warning message therefore.
logger.debug("Could not create a socket connection", e);
throw e;
}
}
private void authenticate(String challenge) throws ResponseException, IOException, AuthenticationException {
new AuthenticationCommand<>(this, challenge, new PowerQueryCommand(this)).execute();
}
public PowerQueryResponse getPowerStatus() throws ResponseException, IOException, AuthenticationException {
return new PowerQueryCommand(this).execute();
}
public void addPrefixToNextCommand(String cmd) throws IOException, AuthenticationException {
this.prefixForNextCommand = cmd;
}
public static String preprocessResponse(String response) {
// some devices send leading zero bytes, see https://github.com/openhab/openhab-addons/issues/6725
return response.replaceAll("^\0*|\0*$", "");
}
public synchronized String execute(String command) throws IOException, AuthenticationException, ResponseException {
String fullCommand = this.prefixForNextCommand + command;
this.prefixForNextCommand = "";
for (int numberOfTries = 0; true; numberOfTries++) {
try {
Socket socket = connect();
socket.getOutputStream().write((fullCommand).getBytes());
socket.getOutputStream().flush();
// success, no further tries needed
break;
} catch (java.net.SocketException e) {
closeSocket(socket);
if (numberOfTries >= 2) {
// do not retry endlessly
throw e;
}
}
}
String response = null;
while ((response = getReader().readLine()) != null && preprocessResponse(response).isEmpty()) {
logger.debug("Got empty string response for request '{}' from {}, waiting for another line", response,
fullCommand.replaceAll("\r", "\\\\r"));
}
if (response == null) {
throw new ResponseException(MessageFormat.format("Response to request ''{0}'' was null",
fullCommand.replaceAll("\r", "\\\\r")));
}
if (logger.isDebugEnabled()) {
logger.debug("Got response '{}' ({}) for request '{}' from {}", response,
Arrays.toString(response.getBytes()), fullCommand.replaceAll("\r", "\\\\r"), ipAddress);
}
return preprocessResponse(response);
}
public void checkAvailability() throws IOException, AuthenticationException, ResponseException {
connect();
}
public String getName() throws IOException, ResponseException, AuthenticationException {
return new IdentificationCommand(this, IdentificationCommand.IdentificationProperty.NAME).execute().getResult();
}
public String getManufacturer() throws IOException, ResponseException, AuthenticationException {
return new IdentificationCommand(this, IdentificationCommand.IdentificationProperty.MANUFACTURER).execute()
.getResult();
}
public String getModel() throws IOException, ResponseException, AuthenticationException {
return new IdentificationCommand(this, IdentificationCommand.IdentificationProperty.MODEL).execute()
.getResult();
}
public String getFullDescription() throws AuthenticationException, ResponseException {
StringBuilder sb = new StringBuilder();
try {
sb.append(getManufacturer());
sb.append(" ");
} catch (ResponseException | IOException e) {
// okay, we'll try the other identification commands
}
try {
sb.append(getModel());
} catch (ResponseException | IOException e1) {
// okay, we'll try the other identification commands
}
try {
String name = getName();
if (!name.isEmpty()) {
sb.append(": ");
sb.append(name);
}
} catch (ResponseException | IOException e2) {
// okay, we'll try the other identification commands
}
if (sb.length() == 0) {
throw new ResponseException("None of the identification commands worked");
}
return sb.toString();
}
public String getPJLinkClass() throws IOException, AuthenticationException, ResponseException {
return new IdentificationCommand(this, IdentificationCommand.IdentificationProperty.CLASS).execute()
.getResult();
}
public void powerOn() throws ResponseException, IOException, AuthenticationException {
new PowerInstructionCommand(this, PowerInstructionCommand.PowerInstructionState.ON).execute();
}
public void powerOff() throws IOException, ResponseException, AuthenticationException {
new PowerInstructionCommand(this, PowerInstructionCommand.PowerInstructionState.OFF).execute();
}
public @Nullable String getAdminPassword() {
return this.adminPassword;
}
public Boolean getAuthenticationRequired() {
return this.authenticationRequired;
}
public InputQueryResponse getInputStatus() throws ResponseException, IOException, AuthenticationException {
return new InputQueryCommand(this).execute();
}
public void setInput(Input input) throws ResponseException, IOException, AuthenticationException {
new InputInstructionCommand(this, input).execute();
}
public MuteQueryResponseValue getMuteStatus() throws ResponseException, IOException, AuthenticationException {
return new MuteQueryCommand(this).execute().getResult();
}
public void setMute(MuteInstructionChannel channel, boolean muteOn)
throws ResponseException, IOException, AuthenticationException {
new MuteInstructionCommand(this, muteOn ? MuteInstructionState.ON : MuteInstructionState.OFF, channel)
.execute();
}
public Map<ErrorStatusDevicePart, ErrorStatusQueryResponseState> getErrorStatus()
throws ResponseException, IOException, AuthenticationException {
return new ErrorStatusQueryCommand(this).execute().getResult();
}
public List<LampState> getLampStates() throws ResponseException, IOException, AuthenticationException {
return new LampStatesCommand(this).execute().getResult();
}
public List<LampState> getLampStatesCached() throws ResponseException, IOException, AuthenticationException {
return cachedLampHoursCommand.execute().getResult();
}
public String getOtherInformation() throws ResponseException, IOException, AuthenticationException {
return new IdentificationCommand(this, IdentificationCommand.IdentificationProperty.OTHER_INFORMATION).execute()
.getResult();
}
public Set<Input> getAvailableInputs() throws ResponseException, IOException, AuthenticationException {
return new InputListQueryCommand(this).execute().getResult();
}
public void dispose() {
final Socket socket = this.socket;
if (socket != null) {
closeSocket(socket);
}
}
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
/**
* Common base class for most PJLink commands.
*
* Takes care of generating the request string, sending it to the device, authentication error checking and response
* parsing.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractCommand<RequestType extends Request, ResponseType extends Response<?>>
implements Command<ResponseType> {
private PJLinkDevice pjLinkDevice;
public AbstractCommand(PJLinkDevice pjLinkDevice) {
this.pjLinkDevice = pjLinkDevice;
}
public PJLinkDevice getDevice() {
return this.pjLinkDevice;
}
protected abstract RequestType createRequest();
protected abstract ResponseType parseResponse(String response) throws ResponseException;
@Override
public ResponseType execute() throws ResponseException, IOException, AuthenticationException {
RequestType request = createRequest();
String responseString = this.pjLinkDevice.execute(request.getRequestString() + "\r");
if ("PJLINK ERRA".equalsIgnoreCase(responseString)) {
throw new AuthenticationException("Authentication error, wrong password provided?");
}
return parseResponse(responseString);
}
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* The possible outcomes of a command which can only be acknowledged or fail.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public enum AcknowledgeResponseValue {
OK("Success", "OK");
private String text;
private String code;
private AcknowledgeResponseValue(String text, String code) {
this.text = text;
this.code = code;
}
public String getText() {
return this.text;
}
public static AcknowledgeResponseValue getValueForCode(String code) throws ResponseException {
for (AcknowledgeResponseValue result : AcknowledgeResponseValue.values()) {
if (result.code.equalsIgnoreCase(code)) {
return result;
}
}
throw new ResponseException("Cannot understand acknowledgement status: " + code);
}
}

View File

@@ -0,0 +1,33 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Exception thrown whenever authentication with the device fails.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class AuthenticationException extends Exception {
private static final long serialVersionUID = -3319800607314286998L;
public AuthenticationException(String string) {
super(string);
}
public AuthenticationException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Exception thrown whenever an error code or unexpected response is retrieved from the device.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class CacheException extends RuntimeException {
private static final long serialVersionUID = -3319800607314286998L;
public CacheException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.cache.ExpiringCache;
/**
* CachedCommand wraps any command and caches its response for a configurable period of time.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class CachedCommand<ResponseType extends Response<?>> implements Command<ResponseType> {
private Command<ResponseType> cachedCommand;
private ExpiringCache<ResponseType> cache;
public CachedCommand(Command<ResponseType> cachedCommand) {
this(cachedCommand, 1000);
}
public CachedCommand(Command<ResponseType> cachedCommand, int expiry) {
this.cachedCommand = cachedCommand;
this.cache = new ExpiringCache<>(expiry, () -> {
try {
return this.cachedCommand.execute();
} catch (ResponseException | IOException | AuthenticationException e) {
// wrap exception into RuntimeException to unwrap again later in CachedCommand.execute()
throw new CacheException(e);
}
});
}
@Override
public ResponseType execute() throws ResponseException, IOException, AuthenticationException {
ExpiringCache<ResponseType> cache = this.cache;
try {
@Nullable
ResponseType result = cache.getValue();
if (result == null) {
// this can not happen in reality, limitation of ExpiringCache
throw new ResponseException("Cached value is null");
}
return result;
} catch (CacheException e) {
// try to unwrap RuntimeException thrown in ExpiringCache
Throwable cause = e.getCause();
if (cause instanceof ResponseException) {
throw (ResponseException) cause;
}
if (cause instanceof IOException) {
throw (IOException) cause;
}
if (cause instanceof AuthenticationException) {
throw (AuthenticationException) cause;
}
throw e;
}
}
}

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Basic command interface allowing to execute the command.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public interface Command<ResponseType extends Response<?>> {
public ResponseType execute() throws ResponseException, IOException, AuthenticationException;
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import java.text.MessageFormat;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Error codes as specified in <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a>
* chapters
* 2.3. Set commands
* 2.4. Get commands
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public enum ErrorCode {
UNDEFINED_COMMAND("Undefined command", "ERR1"),
OUT_OF_PARAMETER("Out of parameter", "ERR2"),
UNAVAILABLE_TIME("Unavailable time", "ERR3"),
DEVICE_FAILURE("Projector/Display failure", "ERR4");
private String text;
private String code;
private ErrorCode(String text, String code) {
this.text = text;
this.code = code;
}
public static @Nullable ErrorCode getValueForCode(String code) {
for (ErrorCode result : ErrorCode.values()) {
if (result.code.equalsIgnoreCase(code)) {
return result;
}
}
return null;
}
public String getText() {
return this.text;
}
/**
* Checks if a the given code is an error code. Optionally, restrictCodesTo can restrict the allowed error codes
* (based on PJLink specification).
*
* @param code string to be checked for error codes
* @param restrictCodesTo list of expected error codes according to PJLink specification, can be null if all error
* codes (ERR1-ERR4) can be expected
* @throws ResponseException
*/
public static void checkForErrorStatus(String code, @Nullable Set<ErrorCode> restrictCodesTo)
throws ResponseException {
ErrorCode parsed = getValueForCode(code);
if (parsed != null && (restrictCodesTo == null || restrictCodesTo.contains(parsed))) {
throw new ResponseException(MessageFormat.format("Got error status {0} ({1})", parsed.getText(), code));
}
}
}

View File

@@ -0,0 +1,67 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import java.text.MessageFormat;
import java.util.Arrays;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* Base class for most responses that can be retrieved from the device.
*
* A prefix has to be passed in the constructor for which is checked.
*
* Subclasses have to implement parseResponseWithoutPrefix, which allows parsing without having to remove the prefix
* first.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public abstract class PrefixedResponse<ResponseType> implements Response<ResponseType> {
private String prefix;
private @Nullable Set<ErrorCode> specifiedErrors;
private ResponseType result;
public PrefixedResponse(String prefix, String response) throws ResponseException {
this(prefix, null, response);
}
public PrefixedResponse(String prefix, @Nullable Set<ErrorCode> specifiedErrors, String response)
throws ResponseException {
this.prefix = prefix;
this.specifiedErrors = specifiedErrors;
this.result = parse(response);
}
public ResponseType getResult() {
return this.result;
}
@Override
public ResponseType parse(String response) throws ResponseException {
String fullPrefix = "%1" + this.prefix;
if (!response.toUpperCase().startsWith(fullPrefix)) {
throw new ResponseException(
MessageFormat.format("Expected prefix ''{0}'' ({1}), instead got ''{2}'' ({3})", fullPrefix,
Arrays.toString(fullPrefix.getBytes()), response, Arrays.toString(response.getBytes())));
}
String result = response.substring(fullPrefix.length());
ErrorCode.checkForErrorStatus(result, this.specifiedErrors);
return parseResponseWithoutPrefix(result);
}
protected abstract ResponseType parseResponseWithoutPrefix(String responseWithoutPrefix) throws ResponseException;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Basic request interface allowing to create the request string.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public interface Request {
public String getRequestString() throws AuthenticationException;
}

View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Basic response interface allowing to parse the response string.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public interface Response<ResponseType> {
public ResponseType parse(String response) throws ResponseException;
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command;
import org.eclipse.jdt.annotation.NonNullByDefault;
/**
* Exception thrown whenever an error code or unexpected response is retrieved from the device.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class ResponseException extends Exception {
private static final long serialVersionUID = -3319800607314286998L;
public ResponseException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
public ResponseException(String message, Throwable cause) {
super(message, cause);
}
public ResponseException(String message) {
super(message);
}
public ResponseException(Throwable cause) {
super(cause);
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.authentication;
import java.io.IOException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AuthenticationException;
import org.openhab.binding.pjlinkdevice.internal.device.command.Command;
import org.openhab.binding.pjlinkdevice.internal.device.command.Response;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used to authenticate to the device after the connection established.
* As authentication can only be done in conjunction with a real command, a testCommand must be passed to authenticate.
*
* The authentication procedure is described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> chapter 5.1. Authentication
* procedure
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class AuthenticationCommand<ResponseType extends Response<?>> implements Command<ResponseType> {
private String challenge;
private Command<ResponseType> testCommand;
private PJLinkDevice device;
public AuthenticationCommand(PJLinkDevice pjLinkDevice, String challenge, Command<ResponseType> testCommand) {
this.device = pjLinkDevice;
this.challenge = challenge;
this.testCommand = testCommand;
}
@Override
public ResponseType execute() throws ResponseException, IOException, AuthenticationException {
this.device.addPrefixToNextCommand(createRequest().getRequestString());
return this.testCommand.execute();
}
protected AuthenticationRequest<ResponseType> createRequest() {
return new AuthenticationRequest<>(this);
}
public String getChallenge() {
return this.challenge;
}
public PJLinkDevice getDevice() {
return this.device;
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.authentication;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.AuthenticationException;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
import org.openhab.binding.pjlinkdevice.internal.device.command.Response;
/**
* Calculates the response matching the callenge received from the PJLink device.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class AuthenticationRequest<ResponseType extends Response<?>> implements Request {
private AuthenticationCommand<ResponseType> command;
public AuthenticationRequest(AuthenticationCommand<ResponseType> command) {
this.command = command;
}
@Override
public String getRequestString() throws AuthenticationException {
try {
MessageDigest md = MessageDigest.getInstance("MD5");
String toBeDigested = (this.command.getChallenge() + this.command.getDevice().getAdminPassword());
byte[] digest = md.digest(toBeDigested.getBytes());
BigInteger bigInt = new BigInteger(1, digest);
return bigInt.toString(16);
} catch (NoSuchAlgorithmException e) {
throw new AuthenticationException(e);
}
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.errorstatus;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for retrieving error information as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> chapter 4.7. Error status query
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class ErrorStatusQueryCommand extends AbstractCommand<ErrorStatusQueryRequest, ErrorStatusQueryResponse> {
public ErrorStatusQueryCommand(PJLinkDevice pjLinkDevice) {
super(pjLinkDevice);
}
@Override
public ErrorStatusQueryRequest createRequest() {
return new ErrorStatusQueryRequest();
}
@Override
public ErrorStatusQueryResponse parseResponse(String response) throws ResponseException {
return new ErrorStatusQueryResponse(response);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.errorstatus;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link ErrorStatusQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class ErrorStatusQueryRequest implements Request {
@Override
public String getRequestString() {
return "%1ERST ?";
}
}

View File

@@ -0,0 +1,116 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.errorstatus;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.ErrorCode;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link ErrorStatusQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class ErrorStatusQueryResponse extends
PrefixedResponse<Map<ErrorStatusQueryResponse.ErrorStatusDevicePart, ErrorStatusQueryResponse.ErrorStatusQueryResponseState>> {
public enum ErrorStatusQueryResponseState {
OK_UNKOWN("OK/no failure detection", "0"),
WARNING("Warning", "1"),
ERROR("Error", "2");
private String text;
private String code;
private ErrorStatusQueryResponseState(String text, String code) {
this.text = text;
this.code = code;
}
public String getText() {
return this.text;
}
public static ErrorStatusQueryResponseState parseString(String code) throws ResponseException {
for (ErrorStatusQueryResponseState result : ErrorStatusQueryResponseState.values()) {
if (result.code.equals(code)) {
return result;
}
}
throw new ResponseException("Cannot understand error status: " + code);
}
}
public enum ErrorStatusDevicePart {
FAN("Fan error", "FanError", 0),
LAMP("Lamp error", "LampError", 1),
TEMPERATURE("Temperature error", "TemperatureError", 2),
COVER_OPEN("Cover open error", "CoverOpenError", 3),
FILTER("Filter error", "FilterError", 4),
OTHER("Other errors", "OtherErrors", 5);
private String text;
private String camelCaseText;
private int positionInResponse;
private ErrorStatusDevicePart(String text, String camelCaseText, int positionInResponse) {
this.text = text;
this.camelCaseText = camelCaseText;
this.positionInResponse = positionInResponse;
}
public String getText() {
return this.text;
}
public String getCamelCaseText() {
return this.camelCaseText;
}
public static ErrorStatusDevicePart getDevicePartByResponsePosition(int pos) {
for (ErrorStatusDevicePart result : ErrorStatusDevicePart.values()) {
if (result.positionInResponse == pos) {
return result;
}
}
return OTHER;
}
}
private static final HashSet<ErrorCode> SPECIFIED_ERRORCODES = new HashSet<>(
Arrays.asList(ErrorCode.UNAVAILABLE_TIME, ErrorCode.DEVICE_FAILURE));
public ErrorStatusQueryResponse(String response) throws ResponseException {
super("ERST=", SPECIFIED_ERRORCODES, response);
}
@Override
protected Map<ErrorStatusDevicePart, ErrorStatusQueryResponseState> parseResponseWithoutPrefix(
String responseWithoutPrefix) throws ResponseException {
Map<ErrorStatusDevicePart, ErrorStatusQueryResponseState> result = new HashMap<>();
for (int i = 0; i < ErrorStatusDevicePart.values().length; i++) {
result.put(ErrorStatusDevicePart.getDevicePartByResponsePosition(i),
ErrorStatusQueryResponseState.parseString(responseWithoutPrefix.substring(i, i + 1)));
}
return result;
}
}

View File

@@ -0,0 +1,74 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.identification;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for retrieving device information as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> chapters:
* 4.8. Lamp number/ lighting hour query
* 4.10. Projector/Display name query
* 4.11. Manufacture name information query
* 4.12. Product name information query
* 4.13. Other information query
* 4.14. Class information query
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class IdentificationCommand extends AbstractCommand<IdentificationRequest, IdentificationResponse> {
public enum IdentificationProperty {
NAME("NAME"),
MANUFACTURER("INF1"),
MODEL("INF2"),
CLASS("CLSS"),
OTHER_INFORMATION("INFO"),
LAMP_HOURS("LAMP");
private String prefix;
private IdentificationProperty(String prefix) {
this.prefix = prefix;
}
public String getPJLinkCommandPrefix() {
return this.prefix;
}
}
private IdentificationProperty identificationProperty;
public IdentificationCommand(PJLinkDevice pjLinkDevice, IdentificationProperty identificationProperty) {
super(pjLinkDevice);
this.identificationProperty = identificationProperty;
}
public IdentificationProperty getIdentificationProperty() {
return identificationProperty;
}
@Override
protected IdentificationRequest createRequest() {
return new IdentificationRequest(this);
}
@Override
protected IdentificationResponse parseResponse(String response) throws ResponseException {
return new IdentificationResponse(this, response);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.identification;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link IdentificationCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class IdentificationRequest implements Request {
private IdentificationCommand command;
public IdentificationRequest(IdentificationCommand command) {
this.command = command;
}
@Override
public String getRequestString() {
return "%1" + this.command.getIdentificationProperty().getPJLinkCommandPrefix() + " ?";
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.identification;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link IdentificationCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class IdentificationResponse extends PrefixedResponse<String> {
public IdentificationResponse(IdentificationCommand command, String response) throws ResponseException {
super(command.getIdentificationProperty().getPJLinkCommandPrefix() + "=", response);
}
@Override
protected String parseResponseWithoutPrefix(String responseWithoutPrefix) throws ResponseException {
return responseWithoutPrefix;
}
}

View File

@@ -0,0 +1,127 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* Describes an A/V source that can be selected on the PJLink device.
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class Input {
private static final Pattern INPUT_NUMBER_PATTERN = Pattern.compile("[0-9A-Z]");
enum InputType {
RGB("RGB", '1'),
VIDEO("Video", '2'),
DIGITAL("Digital", '3'),
STORAGE("Storage", '4'),
NETWORK("Network", '5');
private String text;
private char code;
private InputType(String text, char code) {
this.text = text;
this.code = code;
}
public String getText() {
return this.text;
}
public static InputType parseString(String value) throws ResponseException {
for (InputType result : InputType.values()) {
if (result.code == value.charAt(0)) {
return result;
}
}
throw new ResponseException("Unknown input channel type: " + value);
}
}
private String value;
public Input(String value) throws ResponseException {
this.value = value;
validate();
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + value.hashCode();
return result;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Input other = (Input) obj;
if (!value.equals(other.value)) {
return false;
}
return true;
}
public InputType getInputType() throws ResponseException {
return InputType.parseString(this.value);
}
public String getInputNumber() throws ResponseException {
String inputNumber = this.value.substring(1, 2);
if (!INPUT_NUMBER_PATTERN.matcher(inputNumber).matches()) {
throw new ResponseException("Illegal channel number: " + inputNumber);
}
return inputNumber;
}
public void validate() throws ResponseException {
if (this.value.length() != 2) {
throw new ResponseException("Illegal input description: " + value);
}
// these method also validate
getInputType();
getInputNumber();
}
public String getValue() {
return this.value;
}
public String getPJLinkRepresentation() {
return this.value;
}
public String getText() throws ResponseException {
return getInputType().getText() + " " + getInputNumber();
}
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for setting the current input of the device as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> chapter 4.3. Input switch
* instruction
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputInstructionCommand extends AbstractCommand<InputInstructionRequest, InputInstructionResponse> {
private Input target;
public InputInstructionCommand(PJLinkDevice pjLinkDevice, Input target) {
super(pjLinkDevice);
this.target = target;
}
public Input getTarget() {
return target;
}
@Override
public InputInstructionRequest createRequest() {
return new InputInstructionRequest(this);
}
@Override
public InputInstructionResponse parseResponse(String response) throws ResponseException {
return new InputInstructionResponse(response);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link InputInstructionCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputInstructionRequest implements Request {
private InputInstructionCommand command;
public InputInstructionRequest(InputInstructionCommand command) {
this.command = command;
}
@Override
public String getRequestString() {
return "%1INPT " + this.command.getTarget().getPJLinkRepresentation();
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import java.util.Arrays;
import java.util.HashSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.AcknowledgeResponseValue;
import org.openhab.binding.pjlinkdevice.internal.device.command.ErrorCode;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link InputInstructionCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputInstructionResponse extends PrefixedResponse<AcknowledgeResponseValue> {
private static final HashSet<ErrorCode> SPECIFIED_ERRORCODES = new HashSet<>(
Arrays.asList(ErrorCode.OUT_OF_PARAMETER, ErrorCode.UNAVAILABLE_TIME, ErrorCode.DEVICE_FAILURE));
public InputInstructionResponse(String response) throws ResponseException {
super("INPT=", SPECIFIED_ERRORCODES, response);
}
@Override
protected AcknowledgeResponseValue parseResponseWithoutPrefix(String responseWithoutPrefix)
throws ResponseException {
return AcknowledgeResponseValue.getValueForCode(responseWithoutPrefix);
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for retrieving the list of available inputs of the device as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> chapter 4.9. Input toggling
* list query
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputListQueryCommand extends AbstractCommand<InputListQueryRequest, InputListQueryResponse> {
public InputListQueryCommand(PJLinkDevice pjLinkDevice) {
super(pjLinkDevice);
}
@Override
public InputListQueryRequest createRequest() {
return new InputListQueryRequest();
}
@Override
public InputListQueryResponse parseResponse(String response) throws ResponseException {
return new InputListQueryResponse(response);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link InputListQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputListQueryRequest implements Request {
@Override
public String getRequestString() {
return "%1INST ?";
}
}

View File

@@ -0,0 +1,48 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.ErrorCode;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link InputListQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputListQueryResponse extends PrefixedResponse<Set<Input>> {
private static final HashSet<ErrorCode> SPECIFIED_ERRORCODES = new HashSet<>(
Arrays.asList(ErrorCode.UNAVAILABLE_TIME, ErrorCode.DEVICE_FAILURE));
public InputListQueryResponse(String response) throws ResponseException {
super("INST=", SPECIFIED_ERRORCODES, response);
}
@Override
protected Set<Input> parseResponseWithoutPrefix(String responseWithoutPrefix) throws ResponseException {
Set<Input> result = new HashSet<>();
int pos = 0;
while (pos < responseWithoutPrefix.length()) {
result.add(new Input(responseWithoutPrefix.substring(pos, pos + 2)));
pos += 3;
}
return result;
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for retrieving the currently selected input of the device as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> chapter 4.4. Input switch query
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputQueryCommand extends AbstractCommand<InputQueryRequest, InputQueryResponse> {
public InputQueryCommand(PJLinkDevice pjLinkDevice) {
super(pjLinkDevice);
}
@Override
public InputQueryRequest createRequest() {
return new InputQueryRequest();
}
@Override
public InputQueryResponse parseResponse(String response) throws ResponseException {
return new InputQueryResponse(response);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link InputQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputQueryRequest implements Request {
@Override
public String getRequestString() {
return "%1INPT ?";
}
}

View File

@@ -0,0 +1,34 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.input;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link InputQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class InputQueryResponse extends PrefixedResponse<Input> {
public InputQueryResponse(String response) throws ResponseException {
super("INPT=", response);
}
@Override
protected Input parseResponseWithoutPrefix(String responseWithoutPrefix) throws ResponseException {
return new Input(responseWithoutPrefix);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.lampstatus;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for retrieving device information as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> chapters:
* 4.8. Lamp number/ lighting hour query
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class LampStatesCommand extends AbstractCommand<LampStatesRequest, LampStatesResponse> {
public LampStatesCommand(PJLinkDevice pjLinkDevice) {
super(pjLinkDevice);
}
@Override
protected LampStatesRequest createRequest() {
return new LampStatesRequest();
}
@Override
protected LampStatesResponse parseResponse(String response) throws ResponseException {
return new LampStatesResponse(response);
}
}

View File

@@ -0,0 +1,29 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.lampstatus;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link LampStatesCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class LampStatesRequest implements Request {
@Override
public String getRequestString() {
return "%1LAMP ?";
}
}

View File

@@ -0,0 +1,77 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.lampstatus;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link LampStatesCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class LampStatesResponse extends PrefixedResponse<List<LampStatesResponse.LampState>> {
static final Pattern RESPONSE_VALIDATION_PATTERN = Pattern.compile("^((\\d+) ([01]))( (\\d+) ([01]))*$");
static final Pattern RESPONSE_PARSING_PATTERN = Pattern.compile("(?<hours>\\d+) (?<active>[01])");
@NonNullByDefault
public class LampState {
private boolean active;
private int lampHours;
public LampState(boolean active, int lampHours) {
this.active = active;
this.lampHours = lampHours;
}
public int getLampHours() {
return lampHours;
}
public boolean isActive() {
return active;
}
}
public LampStatesResponse(String response) throws ResponseException {
super("LAMP=", response);
}
@Override
protected List<LampStatesResponse.LampState> parseResponseWithoutPrefix(String responseWithoutPrefix)
throws ResponseException {
// validate if response fully matches specification
if (!RESPONSE_VALIDATION_PATTERN.matcher(responseWithoutPrefix).matches()) {
throw new ResponseException(
MessageFormat.format("Lamp status response could not be parsed: ''{0}''", responseWithoutPrefix));
}
// go through individual matches for each lamp
List<LampStatesResponse.LampState> result = new ArrayList<>();
Matcher matcher = RESPONSE_PARSING_PATTERN.matcher(responseWithoutPrefix);
while (matcher.find()) {
int lampHours = Integer.parseInt(matcher.group("hours"));
boolean active = matcher.group("active").equals("1");
result.add(new LampState(active, lampHours));
}
return result;
}
}

View File

@@ -0,0 +1,87 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.mute;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for selecting audio/video mute of the device as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> 4.5. Mute instruction
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class MuteInstructionCommand extends AbstractCommand<MuteInstructionRequest, MuteInstructionResponse> {
public enum MuteInstructionState {
ON("1"),
OFF("0");
private String pjLinkRepresentation;
private MuteInstructionState(String pjLinkRepresentation) {
this.pjLinkRepresentation = pjLinkRepresentation;
}
public String getPJLinkRepresentation() {
return this.pjLinkRepresentation;
}
}
public enum MuteInstructionChannel {
VIDEO("1"),
AUDIO("2"),
AUDIO_AND_VIDEO("3");
private String pjLinkRepresentation;
private MuteInstructionChannel(String pjLinkRepresentation) {
this.pjLinkRepresentation = pjLinkRepresentation;
}
public String getPJLinkRepresentation() {
return this.pjLinkRepresentation;
}
}
private MuteInstructionState targetState;
private MuteInstructionChannel targetChannel;
public MuteInstructionCommand(PJLinkDevice pjLinkDevice, MuteInstructionState targetState,
MuteInstructionChannel targetChannel) {
super(pjLinkDevice);
this.targetState = targetState;
this.targetChannel = targetChannel;
}
public MuteInstructionState getTargetState() {
return this.targetState;
}
public MuteInstructionChannel getTargetChannel() {
return this.targetChannel;
}
@Override
public MuteInstructionRequest createRequest() {
return new MuteInstructionRequest(this);
}
@Override
public MuteInstructionResponse parseResponse(String response) throws ResponseException {
return new MuteInstructionResponse(response);
}
}

View File

@@ -0,0 +1,37 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.mute;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link MuteInstructionCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class MuteInstructionRequest implements Request {
private MuteInstructionCommand command;
public MuteInstructionRequest(MuteInstructionCommand command) {
this.command = command;
}
@Override
public String getRequestString() {
return "%1AVMT " + this.command.getTargetChannel().getPJLinkRepresentation()
+ this.command.getTargetState().getPJLinkRepresentation();
}
}

View File

@@ -0,0 +1,43 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.mute;
import java.util.Arrays;
import java.util.HashSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.AcknowledgeResponseValue;
import org.openhab.binding.pjlinkdevice.internal.device.command.ErrorCode;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link MuteInstructionCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class MuteInstructionResponse extends PrefixedResponse<AcknowledgeResponseValue> {
private static final HashSet<ErrorCode> SPECIFIED_ERRORCODES = new HashSet<>(
Arrays.asList(ErrorCode.OUT_OF_PARAMETER, ErrorCode.UNAVAILABLE_TIME, ErrorCode.DEVICE_FAILURE));
public MuteInstructionResponse(String response) throws ResponseException {
super("AVMT=", SPECIFIED_ERRORCODES, response);
}
@Override
protected AcknowledgeResponseValue parseResponseWithoutPrefix(String responseWithoutPrefix)
throws ResponseException {
return AcknowledgeResponseValue.getValueForCode(responseWithoutPrefix);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.mute;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for retrieving the current audio/video mute status of the device as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> 4.6. Mute status query
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class MuteQueryCommand extends AbstractCommand<MuteQueryRequest, MuteQueryResponse> {
public MuteQueryCommand(PJLinkDevice pjLinkDevice) {
super(pjLinkDevice);
}
@Override
public MuteQueryRequest createRequest() {
return new MuteQueryRequest();
}
@Override
public MuteQueryResponse parseResponse(String response) throws ResponseException {
return new MuteQueryResponse(response);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.mute;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link MuteQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class MuteQueryRequest implements Request {
@Override
public String getRequestString() {
return "%1AVMT ?";
}
}

View File

@@ -0,0 +1,83 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.mute;
import java.util.Arrays;
import java.util.HashSet;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.ErrorCode;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link MuteQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class MuteQueryResponse extends PrefixedResponse<MuteQueryResponse.MuteQueryResponseValue> {
public enum MuteQueryResponseValue {
OFF("Mute off", "30", false, false),
VIDEO_MUTE_ON("Video muted", "11", false, true),
AUDIO_MUTE_ON("Audio muted", "21", true, false),
AUDIO_AND_VIDEO_MUTE_ON("Audio and video muted", "31", true, true);
private String text;
private String code;
private boolean audioMuted;
private boolean videoMuted;
private MuteQueryResponseValue(String text, String code, boolean audioMuted, boolean videoMuted) {
this.text = text;
this.code = code;
this.audioMuted = audioMuted;
this.videoMuted = videoMuted;
}
public String getText() {
return this.text;
}
public static MuteQueryResponseValue parseString(String code) throws ResponseException {
for (MuteQueryResponseValue result : MuteQueryResponseValue.values()) {
if (result.code.equals(code)) {
return result;
}
}
throw new ResponseException("Cannot understand mute status: " + code);
}
public boolean isAudioMuted() {
return this.audioMuted;
}
public boolean isVideoMuted() {
return this.videoMuted;
}
}
private static final HashSet<ErrorCode> SPECIFIED_ERRORCODES = new HashSet<>(
Arrays.asList(ErrorCode.UNAVAILABLE_TIME, ErrorCode.DEVICE_FAILURE));
public MuteQueryResponse(String response) throws ResponseException {
super("AVMT=", SPECIFIED_ERRORCODES, response);
}
@Override
protected MuteQueryResponseValue parseResponseWithoutPrefix(String responseWithoutPrefix) throws ResponseException {
return MuteQueryResponseValue.parseString(responseWithoutPrefix);
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for switching the device on/off as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> 4.1. Power control instruction
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PowerInstructionCommand extends AbstractCommand<PowerInstructionRequest, PowerInstructionResponse> {
public enum PowerInstructionState {
ON("1"),
OFF("0");
private String pjLinkRepresentation;
private PowerInstructionState(String pjLinkRepresentation) {
this.pjLinkRepresentation = pjLinkRepresentation;
}
public String getPJLinkRepresentation() {
return this.pjLinkRepresentation;
}
}
private PowerInstructionState target;
public PowerInstructionCommand(PJLinkDevice pjLinkDevice, PowerInstructionState target) {
super(pjLinkDevice);
this.target = target;
}
public PowerInstructionState getTarget() {
return this.target;
}
@Override
public PowerInstructionRequest createRequest() {
return new PowerInstructionRequest(this);
}
@Override
public PowerInstructionResponse parseResponse(String response) throws ResponseException {
return new PowerInstructionResponse(response);
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link PowerInstructionCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PowerInstructionRequest implements Request {
private PowerInstructionCommand command;
public PowerInstructionRequest(PowerInstructionCommand command) {
this.command = command;
}
@Override
public String getRequestString() {
return "%1POWR " + this.command.getTarget().getPJLinkRepresentation();
}
}

View File

@@ -0,0 +1,36 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.AcknowledgeResponseValue;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link PowerInstructionCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PowerInstructionResponse extends PrefixedResponse<AcknowledgeResponseValue> {
public PowerInstructionResponse(String response) throws ResponseException {
super("POWR=", response);
}
@Override
protected AcknowledgeResponseValue parseResponseWithoutPrefix(String responseWithoutPrefix)
throws ResponseException {
return AcknowledgeResponseValue.getValueForCode(responseWithoutPrefix);
}
}

View File

@@ -0,0 +1,42 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AbstractCommand;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* This command is used for retrieving the devices power status as described in
* <a href="https://pjlink.jbmia.or.jp/english/data_cl2/PJLink_5-1.pdf">[PJLinkSpec]</a> 4.2. Power status query
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PowerQueryCommand extends AbstractCommand<PowerQueryRequest, PowerQueryResponse> {
public PowerQueryCommand(PJLinkDevice pjLinkDevice) {
super(pjLinkDevice);
}
@Override
public PowerQueryRequest createRequest() {
return new PowerQueryRequest();
}
@Override
public PowerQueryResponse parseResponse(String response) throws ResponseException {
return new PowerQueryResponse(response);
}
}

View File

@@ -0,0 +1,30 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.Request;
/**
* The request part of {@link PowerQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PowerQueryRequest implements Request {
@Override
public String getRequestString() {
return "%1POWR ?";
}
}

View File

@@ -0,0 +1,64 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.device.command.power;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.device.command.PrefixedResponse;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
/**
* The response part of {@link PowerQueryCommand}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public class PowerQueryResponse extends PrefixedResponse<PowerQueryResponse.PowerQueryResponseValue> {
public enum PowerQueryResponseValue {
STAND_BY("Stand-by", "0"),
POWER_ON("Power on", "1"),
COOLING_DOWN("Cooling down", "2"),
WARMING_UP("Warming up", "3");
private String text;
private String code;
private PowerQueryResponseValue(String text, String code) {
this.text = text;
this.code = code;
}
public String getText() {
return this.text;
}
public static PowerQueryResponseValue parseString(String code) throws ResponseException {
for (PowerQueryResponseValue result : PowerQueryResponseValue.values()) {
if (result.code.equals(code)) {
return result;
}
}
throw new ResponseException("Cannot understand power status: " + code);
}
}
public PowerQueryResponse(String response) throws ResponseException {
super("POWR=", response);
}
@Override
protected PowerQueryResponseValue parseResponseWithoutPrefix(String responseWithoutPrefix)
throws ResponseException {
return PowerQueryResponseValue.parseString(responseWithoutPrefix);
}
}

View File

@@ -0,0 +1,131 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.discovery;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.NetworkInterface;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.pjlinkdevice.internal.PJLinkDeviceBindingConstants;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovery of PJLink devices. Checks IP addresses in parallel processing.
*
* Generating IP addresses and checking them is done by the subclasses implementing
* {@link AbstractDiscoveryParticipant#generateAddressesToScan} and {@link AbstractDiscoveryParticipant#checkAddress}
*
* @author Nils Schnabel - Initial contribution
*/
@NonNullByDefault
public abstract class AbstractDiscoveryParticipant extends AbstractDiscoveryService {
protected final Logger logger = LoggerFactory.getLogger(AbstractDiscoveryParticipant.class);
private Integer scannedIPcount = 0;
private @Nullable ExecutorService executorService = null;
public AbstractDiscoveryParticipant(Set<ThingTypeUID> supportedThingTypes, int timeout,
boolean backgroundDiscoveryEnabledByDefault) throws IllegalArgumentException {
super(supportedThingTypes, timeout, backgroundDiscoveryEnabledByDefault);
}
protected ExecutorService getExecutorService() {
ExecutorService executorService = this.executorService;
if (executorService == null) {
this.executorService = executorService = Executors
.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 2);
}
return executorService;
}
@Override
protected void startScan() {
logger.trace("PJLinkProjectorDiscoveryParticipant startScan");
Set<InetAddress> addressesToScan = generateAddressesToScan();
scannedIPcount = 0;
for (InetAddress ip : addressesToScan) {
getExecutorService().execute(() -> {
Thread.currentThread().setName("Discovery thread " + ip);
checkAddress(ip, PJLinkDeviceBindingConstants.DEFAULT_PORT,
PJLinkDeviceBindingConstants.DEFAULT_SCAN_TIMEOUT_SECONDS);
synchronized (scannedIPcount) {
scannedIPcount += 1;
logger.debug("Scanned {} of {} IPs", scannedIPcount, addressesToScan.size());
if (scannedIPcount == addressesToScan.size()) {
logger.debug("Scan of {} IPs successful", scannedIPcount);
stopScan();
}
}
});
}
}
@Override
protected synchronized void stopScan() {
super.stopScan();
ExecutorService executorService = this.executorService;
if (executorService == null) {
return;
}
try {
executorService.awaitTermination(10000, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // Reset interrupt flag
}
executorService.shutdown();
}
public static ThingUID createServiceUID(String ip, int tcpPort) {
// uid must not contains dots
return new ThingUID(PJLinkDeviceBindingConstants.THING_TYPE_PJLINK,
ip.replace('.', '_') + "_" + String.valueOf(tcpPort));
}
protected abstract void checkAddress(InetAddress ip, int tcpPort, int timeout);
private Set<InetAddress> generateAddressesToScan() {
try {
Set<InetAddress> addressesToScan = new HashSet<>();
ArrayList<NetworkInterface> interfaces = java.util.Collections
.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface networkInterface : interfaces) {
if (networkInterface.isLoopback() || !networkInterface.isUp()) {
continue;
}
for (InterfaceAddress i : networkInterface.getInterfaceAddresses()) {
collectAddressesToScan(addressesToScan, i);
}
}
return addressesToScan;
} catch (SocketException e) {
logger.debug("Could not enumerate network interfaces", e);
}
return new HashSet<>();
}
protected abstract void collectAddressesToScan(Set<InetAddress> addressesToScan, InterfaceAddress i);
}

View File

@@ -0,0 +1,103 @@
/**
* Copyright (c) 2010-2020 Contributors to the openHAB project
*
* See the NOTICE file(s) distributed with this work for additional
* information.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.openhab.binding.pjlinkdevice.internal.discovery;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InterfaceAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.apache.commons.net.util.SubnetUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.pjlinkdevice.internal.PJLinkDeviceBindingConstants;
import org.openhab.binding.pjlinkdevice.internal.device.PJLinkDevice;
import org.openhab.binding.pjlinkdevice.internal.device.command.AuthenticationException;
import org.openhab.binding.pjlinkdevice.internal.device.command.ResponseException;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.DiscoveryService;
import org.osgi.service.component.annotations.Component;
/**
* Implementation of {@link AbstractDiscoveryParticipant} for IPv4 address ranges and PJLink Class 1 devices.
*
* @author Nils Schnabel - Initial contribution
*/
@Component(service = DiscoveryService.class, configurationPid = "org.openhab.binding.pjlinkdevice.internal.discovery.DiscoveryParticipantClass1")
@NonNullByDefault
public class DiscoveryParticipantClass1 extends AbstractDiscoveryParticipant {
public DiscoveryParticipantClass1() throws IllegalArgumentException {
super(Collections.singleton(PJLinkDeviceBindingConstants.THING_TYPE_PJLINK), 60, true);
logger.trace("PJLinkProjectorDiscoveryParticipant constructor");
}
@Override
protected void collectAddressesToScan(Set<InetAddress> addressesToScan, InterfaceAddress i) {
// only scan IPv4
if (!(i.getAddress() instanceof Inet4Address)) {
return;
}
// only scan Class C networks
if (i.getNetworkPrefixLength() < 24) {
return;
}
SubnetUtils utils = new SubnetUtils(i.getAddress().getHostAddress() + "/" + i.getNetworkPrefixLength());
for (String addressToScan : utils.getInfo().getAllAddresses()) {
try {
logger.debug("Add address to scan: {}", addressToScan);
addressesToScan.add(InetAddress.getByName(addressToScan));
} catch (UnknownHostException e) {
logger.debug("Unknown Host", e);
}
}
}
@Override
protected void checkAddress(InetAddress ip, int tcpPort, int timeout) {
PJLinkDevice device = new PJLinkDevice(tcpPort, ip, null, timeout);
try {
Map<String, Object> properties = new HashMap<>();
properties.put(PJLinkDeviceBindingConstants.PARAMETER_HOSTNAME, ip.getHostAddress());
properties.put(PJLinkDeviceBindingConstants.PARAMETER_PORT, tcpPort);
String description = "Unknown PJLink Device";
try {
device.checkAvailability();
try {
description = device.getFullDescription();
logger.debug("got name {}", description);
} catch (ResponseException e) {
logger.debug("Could not find a name for PJLink device", e);
// okay, no name
}
} catch (AuthenticationException e) {
properties.put(PJLinkDeviceBindingConstants.PROPERTY_AUTHENTICATION_REQUIRED, true);
}
logger.debug("Adding thing");
thingDiscovered(DiscoveryResultBuilder.create(createServiceUID(ip.getHostAddress(), tcpPort))
.withTTL(PJLinkDeviceBindingConstants.DISCOVERY_RESULT_TTL_SECONDS).withProperties(properties)
.withLabel(description).build());
logger.debug("Added thing");
} catch (ResponseException | IOException e) {
logger.debug("No PJLinkDevice here {} {}", ip, e.getStackTrace());
// no device here
}
}
}

View File

@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="pjLinkDevice" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>PJLinkDevice Binding</name>
<description>This binding can control PJLink compatible devices.
The PJLink protocol was mainly standardized for digital
projectors, but some other types of devices also use it, in
particular TV screens.</description>
<author>Nils Schnabel</author>
</binding:binding>

View File

@@ -0,0 +1,133 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="pjLinkDevice"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<thing-type id="pjLinkDevice">
<label>PJLink Device</label>
<description>A PJLink compatible device, e.g. a digital projector</description>
<channels>
<channel id="power" typeId="power"/>
<channel id="input" typeId="input"/>
<channel id="audioMute" typeId="audioMute"/>
<channel id="videoMute" typeId="videoMute"/>
<channel id="lamp1Hours" typeId="lampHours"/>
<channel id="lamp1Active" typeId="lampActive"/>
</channels>
<config-description>
<parameter-group name="basic">
<context>basic</context>
<label>Basic Settings</label>
</parameter-group>
<parameter-group name="refresh">
<label>Polling Settings</label>
<description>Use these settings to configure how the status of the PJLink device will be refreshed</description>
</parameter-group>
<parameter name="ipAddress" type="text" required="true" min="1" groupName="basic">
<context>network-address</context>
<label>IP Address</label>
<description>The address of the PJLink device to control.</description>
</parameter>
<parameter name="tcpPort" type="integer" min="1" max="65535" groupName="basic">
<default>4352</default>
<label>TCP Port</label>
<description>The TCP port of the PJLink device to control.</description>
</parameter>
<parameter name="adminPassword" type="text" groupName="basic">
<context>password</context>
<label>Password</label>
<description>The password of the PJLink device.</description>
</parameter>
<parameter name="autoReconnectInterval" type="integer" min="0" groupName="basic">
<label>Auto Reconnect Interval</label>
<description>Seconds between connection retries when connection to the PJLink device has been lost, 0 means never
retry, minimum 30s</description>
<default>60</default>
</parameter>
<parameter name="refreshInterval" type="integer" min="0" groupName="refresh">
<default>5</default>
<label>Refresh Interval (s)</label>
<description>How often to poll the device state (in seconds). A value of zero will disable polling.</description>
</parameter>
<parameter name="refreshPower" type="boolean" groupName="refresh">
<default>false</default>
<label>Poll for Power State</label>
<description>Enable polling for the power state. Only considered if the refreshInterval interval is greater than
zero.</description>
</parameter>
<parameter name="refreshMute" type="boolean" groupName="refresh">
<default>false</default>
<label>Poll for Mute State</label>
<description>Enable polling for the mute state. Only considered if the refreshInterval interval is greater than
zero.</description>
</parameter>
<parameter name="refreshInputChannel" type="boolean" groupName="refresh">
<default>false</default>
<label>Poll for Selected Input Channel</label>
<description>Enable polling for the selected input channel. Only considered if the refreshInterval interval is
greater than zero.</description>
</parameter>
<parameter name="refreshLampState" type="boolean" groupName="refresh">
<default>false</default>
<label>Poll for Lamp State</label>
<description>Enable polling for the lamp state. Only considered if the refresh interval is greater than zero.</description>
</parameter>
</config-description>
</thing-type>
<channel-type id="power">
<item-type>Switch</item-type>
<label>Power</label>
<description>Power ON/OFF the projector</description>
</channel-type>
<channel-type id="input">
<item-type>String</item-type>
<label>Input</label>
<description>Select the input signal to use</description>
</channel-type>
<channel-type id="audioMute">
<item-type>Switch</item-type>
<label>Audio Mute</label>
<description>Select the audio mute status</description>
</channel-type>
<channel-type id="videoMute">
<item-type>Switch</item-type>
<label>Video Mute</label>
<description>Select the video mute status</description>
</channel-type>
<channel-type id="lampHours">
<item-type>Number</item-type>
<label>Lamp Hours</label>
<description>How long the lamp has been in use (in hours)</description>
<state readOnly="true" pattern="%d h"/>
<config-description>
<parameter name="lampNumber" type="integer" min="1" max="8">
<label>Lamp Number</label>
<description>Show used hours for this lamp</description>
<default>1</default>
</parameter>
</config-description>
</channel-type>
<channel-type id="lampActive">
<item-type>Switch</item-type>
<label>Lamp Active</label>
<description>Is the lamp in use?</description>
<state readOnly="true"/>
<config-description>
<parameter name="lampNumber" type="integer" min="1" max="8">
<label>Lamp Number</label>
<description>Show activity for this lamp</description>
<default>1</default>
</parameter>
</config-description>
</channel-type>
</thing:thing-descriptions>