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.neeo-${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-neeo" description="NEEO Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.neeo/${project.version}</bundle>
</feature>
</features>

View File

@@ -0,0 +1,289 @@
/**
* 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.neeo.internal;
import java.io.IOException;
import java.net.URL;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.neeo.internal.models.ExecuteResult;
import org.openhab.binding.neeo.internal.models.NeeoBrain;
import org.openhab.binding.neeo.internal.models.NeeoForwardActions;
import org.openhab.binding.neeo.internal.models.NeeoRoom;
import org.openhab.binding.neeo.internal.net.HttpRequest;
import org.openhab.binding.neeo.internal.net.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* The class provides the API for communicating with a NEEO brain
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoBrainApi implements AutoCloseable {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoBrainApi.class);
/** The gson used in communications */
private final Gson gson = NeeoUtil.getGson();
/** The {@link HttpRequest} used for making requests */
private final AtomicReference<HttpRequest> request = new AtomicReference<>(new HttpRequest());
/** The IP address of the neeo brain */
private final NeeoUrlBuilder urlBuilder;
/**
* Instantiates the API using the specified IP Address
*
* @param ipAddress the non-empty ip address
*/
public NeeoBrainApi(String ipAddress) {
NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty");
this.urlBuilder = new NeeoUrlBuilder(
NeeoConstants.PROTOCOL + ipAddress + ":" + NeeoConstants.DEFAULT_BRAIN_PORT);
}
/**
* Gets the {@link NeeoBrain}
*
* @return the non-null {@link NeeoBrain}
* @throws IOException Signals that an I/O exception has occurred.
*/
public NeeoBrain getBrain() throws IOException {
final String url = urlBuilder.append(NeeoConstants.PROJECTS_HOME).toString();
final HttpRequest rqst = request.get();
final HttpResponse resp = rqst.sendGetCommand(url);
if (resp.getHttpCode() != HttpStatus.OK_200) {
throw resp.createException();
}
return gson.fromJson(resp.getContent(), NeeoBrain.class);
}
/**
* Gets the {@link NeeoRoom} from the brain for the specified room key
*
* @param roomKey the non-empty room key
* @return the {@link NeeoRoom}
* @throws IOException Signals that an I/O exception has occurred.
*/
public NeeoRoom getRoom(String roomKey) throws IOException {
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
final String url = urlBuilder.append(NeeoConstants.GET_ROOM).sub("roomkey", roomKey).toString();
final HttpRequest rqst = request.get();
final HttpResponse resp = rqst.sendGetCommand(url);
if (resp.getHttpCode() != HttpStatus.OK_200) {
throw resp.createException();
}
return gson.fromJson(resp.getContent(), NeeoRoom.class);
}
/**
* Execute the specified recipe in the specified room
*
* @param roomKey the non-empty room key
* @param recipeKey the non-empty recipe key
* @return the execute result
* @throws IOException Signals that an I/O exception has occurred.
*/
ExecuteResult executeRecipe(String roomKey, String recipeKey) throws IOException {
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
final String url = urlBuilder.append(NeeoConstants.EXECUTE_RECIPE).sub("roomkey", roomKey)
.sub("recipekey", recipeKey).toString();
final HttpRequest rqst = request.get();
final HttpResponse resp = rqst.sendGetCommand(url);
if (resp.getHttpCode() != HttpStatus.OK_200) {
throw resp.createException();
}
return gson.fromJson(resp.getContent(), ExecuteResult.class);
}
/**
* Stops the specified scenario in the specified room
*
* @param roomKey the non-empty room key
* @param scenarioKey the non-empty scenario key
* @return the execute result
* @throws IOException Signals that an I/O exception has occurred.
*/
ExecuteResult stopScenario(String roomKey, String scenarioKey) throws IOException {
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
final String url = urlBuilder.append(NeeoConstants.STOP_SCENARIO).sub("roomkey", roomKey)
.sub("scenariokey", scenarioKey).toString();
final HttpRequest rqst = request.get();
final HttpResponse resp = rqst.sendGetCommand(url);
if (resp.getHttpCode() != HttpStatus.OK_200) {
throw resp.createException();
}
return gson.fromJson(resp.getContent(), ExecuteResult.class);
}
/**
* Gets the active scenarios.
*
* @return the non-null, possibly empty list of active scenarios keys
* @throws IOException Signals that an I/O exception has occurred.
*/
public String[] getActiveScenarios() throws IOException {
final String url = urlBuilder.append(NeeoConstants.GET_ACTIVESCENARIOS).toString();
final HttpRequest rqst = request.get();
final HttpResponse resp = rqst.sendGetCommand(url);
if (resp.getHttpCode() != HttpStatus.OK_200) {
throw resp.createException();
}
return gson.fromJson(resp.getContent(), String[].class);
}
/**
* Trigger macro on a specified device in the specified room.
*
* @param roomKey the non-null room key
* @param deviceKey the non-null device key
* @param macroKey the non-null macro key
* @return the execute result
* @throws IOException Signals that an I/O exception has occurred.
*/
ExecuteResult triggerMacro(String roomKey, String deviceKey, String macroKey) throws IOException {
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
NeeoUtil.requireNotEmpty(deviceKey, "deviceKey cannot be empty");
NeeoUtil.requireNotEmpty(macroKey, "macroKey cannot be empty");
final String url = urlBuilder.append(NeeoConstants.TRIGGER_MACRO).sub("roomkey", roomKey)
.sub("devicekey", deviceKey).sub("macrokey", macroKey).toString();
final HttpRequest rqst = request.get();
final HttpResponse resp = rqst.sendGetCommand(url);
if (resp.getHttpCode() != HttpStatus.OK_200) {
throw resp.createException();
}
return gson.fromJson(resp.getContent(), ExecuteResult.class);
}
/**
* Register our API with the brain's forward actions.
*
* @param url the non-null URL to register to
* @throws IOException Signals that an I/O exception has occurred.
*/
public void registerForwardActions(URL url) throws IOException {
Objects.requireNonNull(url, "url cannot be null");
final String brainUrl = urlBuilder.append(NeeoConstants.FORWARD_ACTIONS).toString();
logger.debug("Registering forward actions {} using callback {}", brainUrl, url.toExternalForm());
final String forwardActions = gson.toJson(new NeeoForwardActions(url.getHost(), url.getPort(), url.getPath()));
final HttpRequest rqst = request.get();
final HttpResponse resp = rqst.sendPostJsonCommand(brainUrl, forwardActions);
if (resp.getHttpCode() != HttpStatus.OK_200) {
throw resp.createException();
}
}
/**
* Deregister our API with the brain's forward actions.
*
* @throws IOException Signals that an I/O exception has occurred.
*/
public void deregisterForwardActions() throws IOException {
final String brainUrl = urlBuilder.append(NeeoConstants.FORWARD_ACTIONS).toString();
logger.debug("Deregistering forward actions callback {}", brainUrl);
final HttpRequest rqst = request.get();
final HttpResponse resp = rqst.sendPostJsonCommand(brainUrl, "");
if (resp.getHttpCode() != HttpStatus.OK_200) {
throw resp.createException();
}
}
@Override
public void close() throws Exception {
NeeoUtil.close(request.getAndSet(new HttpRequest()));
}
/**
* Helper class to help build NEEO URLs
*
* @author Tim Roberts - Initial contribution
*/
private class NeeoUrlBuilder {
/** The current url */
private final String url;
/**
* Create a new class from the given URL
*
* @param url a non-null, non-empty URL
*/
NeeoUrlBuilder(final String url) {
NeeoUtil.requireNotEmpty(url, "url cannot be empty");
this.url = url;
}
/**
* Substitutes '{key}' into value from the URL and returns a new {@link NeeoUrlBuilder} with the new URL
*
* @param key a non-null, non-empty key
* @param value a non-null, non-empty key
* @return a non-null {@link NeeoUrlBuilder} with the new URL
*/
NeeoUrlBuilder sub(String key, String value) {
NeeoUtil.requireNotEmpty(key, "key cannot be empty");
NeeoUtil.requireNotEmpty(value, "value cannot be empty");
final String newUrl = url.replace("{" + key + "}", value);
return new NeeoUrlBuilder(newUrl);
}
/**
* Appends the specified value to the URL and returns the new {@link NeeoUrlBuilder} with the new URL
*
* @param value a non-null, non-empty value
* @return a non-null {@link NeeoUrlBuilder} with the new URL
*/
NeeoUrlBuilder append(String value) {
NeeoUtil.requireNotEmpty(value, "value cannot be empty");
return new NeeoUrlBuilder(url + (value.startsWith("/") ? value : ("/" + value)));
}
@Override
public String toString() {
return url;
}
}
}

View File

@@ -0,0 +1,135 @@
/**
* 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.neeo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.handler.NeeoBrainHandler;
/**
* The configuration class a neeo brain and used by {@link NeeoBrainHandler}
*
* @author Tim Roberts - initial contribution
*/
@NonNullByDefault
public class NeeoBrainConfig {
/** The ip address */
@Nullable
private String ipAddress;
/** Whether to enable forward actions */
private boolean enableForwardActions;
/** The forward actions chain (comma delimited) */
@Nullable
private String forwardChain;
/** Whether to discover empty rooms or not */
private boolean discoverEmptyRooms;
/** The check status interval (in seconds) */
private int checkStatusInterval;
/**
* Gets the ip address.
*
* @return the ip address
*/
@Nullable
public String getIpAddress() {
return ipAddress;
}
/**
* Sets the ip address.
*
* @param ipAddress the new ip address
*/
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
/**
* Determines if forward actions is enabled
*
* @return true for enabled, false otherwise
*/
public boolean isEnableForwardActions() {
return enableForwardActions;
}
/**
* Sets whether to enable forward actions
*
* @param enableForwardActions true to enable, false otherwise
*/
public void setEnableForwardActions(boolean enableForwardActions) {
this.enableForwardActions = enableForwardActions;
}
/**
* Get's the forward chain
*
* @return the forward chain
*/
@Nullable
public String getForwardChain() {
return forwardChain;
}
/**
* Sets the forward change
*
* @param forwardChain the forward chain
*/
public void setForwardChain(String forwardChain) {
this.forwardChain = forwardChain;
}
/**
* Whether empty rooms should be discovered or not
*
* @return true to discover empty rooms, false otherwise
*/
public boolean isDiscoverEmptyRooms() {
return discoverEmptyRooms;
}
/**
* Set's whether to discover empty rooms
*
* @param discoverEmptyRooms true to discover, false otherwise
*/
public void setDiscoverEmptyRooms(boolean discoverEmptyRooms) {
this.discoverEmptyRooms = discoverEmptyRooms;
}
/**
* Gets the interval (in seconds) to check the brain status
*
* @return the check status interval (negative to disable)
*/
public int getCheckStatusInterval() {
return checkStatusInterval;
}
/**
* Sets the interval (in seconds) to check the brain status
*
* @param checkStatusInterval return the check status interval (negative to disable)
*/
public void setCheckStatusInterval(int checkStatusInterval) {
this.checkStatusInterval = checkStatusInterval;
}
}

View File

@@ -0,0 +1,96 @@
/**
* 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.neeo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.type.ChannelTypeUID;
/**
* The {@link NeeoConstants} class defines common constants, which are
* used across the whole binding.
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoConstants {
/** The main binding */
public static final String BINDING_ID = "neeo";
/** The various bridge/thing UIDs */
public static final ThingTypeUID BRIDGE_TYPE_BRAIN = new ThingTypeUID(BINDING_ID, "brain");
public static final ThingTypeUID BRIDGE_TYPE_ROOM = new ThingTypeUID(BINDING_ID, "room");
public static final ThingTypeUID THING_TYPE_DEVICE = new ThingTypeUID(BINDING_ID, "device");
/** The MDNS type for the NEEO brain */
public static final String NEEO_MDNS_TYPE = "_neeo._tcp.local.";
/** Various config related */
public static final String CONFIG_IPADDRESS = "ipAddress";
public static final String CONFIG_ENABLEFORWARDACTIONS = "enableForwardActions";
public static final String CONFIG_REFRESH_POLLING = "refreshPolling";
public static final String CONFIG_DEVICEKEY = "deviceKey";
public static final String CONFIG_ROOMKEY = "roomKey";
public static final String CONFIG_EXCLUDE_THINGS = "excludeThings";
/** Brain channels */
public static final String CHANNEL_BRAIN_FOWARDACTIONS = "forwardActions";
/** The various room channel constants */
public static final String ROOM_CHANNEL_NAME = "name";
public static final String ROOM_CHANNEL_TYPE = "type";
public static final String ROOM_CHANNEL_ENABLED = "enabled";
public static final String ROOM_CHANNEL_CONFIGURED = "configured";
public static final String ROOM_CHANNEL_STATUS = "status";
public static final String ROOM_CHANNEL_CURRENTSTEP = "currentStep";
public static final String ROOM_GROUP_STATE_ID = "state";
public static final String ROOM_GROUP_SCENARIO_ID = "scenario";
public static final String ROOM_GROUP_RECIPE_ID = "recipe";
public static final ChannelTypeUID ROOM_STATE_CURRENTSTEP_UID = new ChannelTypeUID(BINDING_ID,
"room-state-currentstep");
public static final ChannelTypeUID ROOM_SCENARIO_NAME_UID = new ChannelTypeUID(BINDING_ID, "room-scenario-name");
public static final ChannelTypeUID ROOM_SCENARIO_CONFIGURED_UID = new ChannelTypeUID(BINDING_ID,
"room-scenario-configured");
public static final ChannelTypeUID ROOM_SCENARIO_STATUS_UID = new ChannelTypeUID(BINDING_ID,
"room-scenario-status");
public static final ChannelTypeUID ROOM_RECIPE_NAME_UID = new ChannelTypeUID(BINDING_ID, "room-recipe-name");
public static final ChannelTypeUID ROOM_RECIPE_TYPE_UID = new ChannelTypeUID(BINDING_ID, "room-recipe-type");
public static final ChannelTypeUID ROOM_RECIPE_ENABLED_UID = new ChannelTypeUID(BINDING_ID, "room-recipe-enabled");
public static final ChannelTypeUID ROOM_RECIPE_STATUS_UID = new ChannelTypeUID(BINDING_ID, "room-recipe-status");
/** The various device channel constants */
public static final String DEVICE_CHANNEL_STATUS = "status";
public static final String DEVICE_GROUP_MACROS_ID = "macros";
public static final ChannelTypeUID DEVICE_MACRO_STATUS_UID = new ChannelTypeUID(BINDING_ID, "device-macros-status");
public static final String WEBAPP_FORWARDACTIONS = "/neeo/binding/{brainid}/forwardactions";
/** The default port the brain listens on. */
public static final int DEFAULT_BRAIN_PORT = 3000;
public static final int DEFAULT_BRAIN_HTTP_PORT = 8080;
/** The default protocol for the brain. */
public static final String PROTOCOL = "http://";
/** The brain API constants */
private static final String NEEO_VERSION = "/v1";
public static final String FORWARD_ACTIONS = NEEO_VERSION + "/forwardactions";
public static final String PROJECTS_HOME = NEEO_VERSION + "/projects/home";
public static final String GET_ACTIVESCENARIOS = PROJECTS_HOME + "/activescenariokeys";
public static final String GET_ROOM = PROJECTS_HOME + "/rooms/{roomkey}";
public static final String EXECUTE_RECIPE = PROJECTS_HOME + "/rooms/{roomkey}/recipes/{recipekey}/execute";
public static final String STOP_SCENARIO = PROJECTS_HOME + "/rooms/{roomkey}/scenarios/{scenariokey}/poweroff";
public static final String TRIGGER_MACRO = PROJECTS_HOME
+ "/rooms/{roomkey}/devices/{devicekey}/macros/{macrokey}/trigger";
}

View File

@@ -0,0 +1,49 @@
/**
* 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.neeo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.handler.NeeoDeviceHandler;
/**
* THe configuration class for the device used by {@link NeeoDeviceHandler}
*
* @author Tim Roberts - initial contribution
*/
@NonNullByDefault
public class NeeoDeviceConfig {
/** The NEEO device key */
@Nullable
private String deviceKey;
/**
* Gets the device key
*
* @return the device key
*/
@Nullable
public String getDeviceKey() {
return deviceKey;
}
/**
* Sets the device key.
*
* @param deviceKey the new device key
*/
public void setDeviceKey(String deviceKey) {
this.deviceKey = deviceKey;
}
}

View File

@@ -0,0 +1,151 @@
/**
* 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.neeo.internal;
import java.io.IOException;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.neeo.internal.models.NeeoDevice;
import org.openhab.binding.neeo.internal.models.NeeoDevices;
import org.openhab.binding.neeo.internal.models.NeeoMacro;
import org.openhab.binding.neeo.internal.models.NeeoRoom;
import org.openhab.core.library.types.OnOffType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This protocol class for a Neeo Device
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoDeviceProtocol {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoDeviceProtocol.class);
/** The {@link NeeoHandlerCallback} */
private final NeeoHandlerCallback callback;
/** The room key */
private final String roomKey;
/** The device key */
private final String deviceKey;
/** The {@link NeeoDevice} in the room */
private final NeeoDevice neeoDevice;
/**
* Instantiates a new neeo device protocol.
*
* @param callback the non-null callback
* @param roomKey the non-empty room key
* @param deviceKey the non-empty device key
* @throws IOException Signals that an I/O exception has occurred.
*/
public NeeoDeviceProtocol(NeeoHandlerCallback callback, String roomKey, String deviceKey) throws IOException {
Objects.requireNonNull(callback, "callback cannot be null");
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
NeeoUtil.requireNotEmpty(deviceKey, "deviceKey cannot be empty");
this.roomKey = roomKey;
this.callback = callback;
this.deviceKey = deviceKey;
final NeeoBrainApi api = callback.getApi();
if (api == null) {
throw new IllegalArgumentException("NeeoBrainApi cannot be null");
}
final NeeoRoom neeoRoom = api.getRoom(roomKey);
final NeeoDevices devices = neeoRoom.getDevices();
final NeeoDevice device = devices.getDevice(deviceKey);
if (device == null) {
throw new IllegalArgumentException(
"Device (" + deviceKey + ") was not found in the NEEO Brain for room (" + roomKey + ")");
}
neeoDevice = device;
}
/**
* Returns the callback being used
*
* @return the non-null callback
*/
public NeeoHandlerCallback getCallback() {
return callback;
}
/**
* Refresh the macro status.
*
* @param macroKey the non-null macro key
*/
public void refreshMacroStatus(String macroKey) {
NeeoUtil.requireNotEmpty(macroKey, "macroKey cannot be empty");
final NeeoMacro macro = neeoDevice.getMacros().getMacro(macroKey);
if (macro != null) {
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.DEVICE_GROUP_MACROS_ID,
NeeoConstants.DEVICE_CHANNEL_STATUS, macroKey), OnOffType.OFF);
}
}
/**
* Sets the macro status. If the status is true, the macro will be triggered. If false, nothing occurs
*
* @param macroKey the non-null macro key
* @param start whether to start (true) or stop (false) the macro
*/
public void setMacroStatus(String macroKey, boolean start) {
NeeoUtil.requireNotEmpty(macroKey, "macroKey cannot be empty");
final NeeoBrainApi api = callback.getApi();
if (api == null) {
logger.debug("API is null [likely bridge is offline]");
} else {
try {
if (start) {
api.triggerMacro(roomKey, deviceKey, macroKey);
// NEEO macros are not what we generally think of for macros
// Trigger a NEEO macro is simply asking the brain to send an IR pulse
// for whatever the macro is linked up to (POWER ON would send the IR
// pulse for the specified device). Because of this, the execution of the
// macro will never take more than 100ms to complete. Since we get no
// feedback from the brain whether the macro has executed or completed
// AND it's impossible to tell if any macro is executing or not (no equivalent
// API to poll for), we simply refresh the status back to OFF after 500ms
callback.scheduleTask(() -> {
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.DEVICE_GROUP_MACROS_ID,
NeeoConstants.DEVICE_CHANNEL_STATUS, macroKey), OnOffType.OFF);
}, 500);
}
} catch (IOException e) {
// Some macros have issues executing on the NEEO Brain (depends on the firmware)
// and IO exception will be thrown if the macro encounters an issue
// (mostly it depends on the state of the brain - if it's starting up or in the process
// of executing a long scenario - the macro will likely timeout or simply throw an exception)
// Because of this, we simply log the error versus taking the binding offline
logger.warn(
"Exception occurred during execution of a macro (may need to update the brain firmware): {}",
e.getMessage(), e);
// callback.statusChanged(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
}

View File

@@ -0,0 +1,82 @@
/**
* 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.neeo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.State;
/**
*
* This interface is used to provide a callback mechanism between a @link {@link ThingHandler} and the assoicated
* protocol.
* This is necessary since the status and state of a bridge/thing is private and the protocol handler cannot access it
* directly.
*
* @author Tim Roberts - initial contribution
*
*/
@NonNullByDefault
public interface NeeoHandlerCallback {
/**
* Callback to the bridge/thing to update the status of the bridge/thing.
*
* @param status a non-null {@link ThingStatus}
* @param detail a non-null {@link ThingStatusDetail}
* @param msg a possibly null, possibly empty message
*/
void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg);
/**
* Callback to the bridge/thing to update the state of a channel in the bridge/thing.
*
* @param channelId the non-null, non-empty channel id
* @param state the new non-null {@State}
*/
void stateChanged(String channelId, State state);
/**
* Callback to set a property for the thing.
*
* @param propertyName a non-null, non-empty property name
* @param propertyValue a non-null, possibly empty property value
*/
void setProperty(String propertyName, String propertyValue);
/**
* Schedule a task to be executed in the future
*
* @param task the non-null task
* @param milliSeconds the milliseconds (>0)
*/
void scheduleTask(Runnable task, long milliSeconds);
/**
* Callback to trigger an event
*
* @param channelID a non-null, non-empty channel id
* @param event a possibly null, possibly empty event
*/
void triggerEvent(String channelID, String event);
/**
* Callback to retrieve the current {@link NeeoBrainApi}
*
* @return a possibly null {@link NeeoBrainApi}
*/
@Nullable
NeeoBrainApi getApi();
}

View File

@@ -0,0 +1,91 @@
/**
* 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.neeo.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.handler.NeeoRoomHandler;
/**
* THe configuration class for the room used by {@link NeeoRoomHandler}
*
* @author Tim Roberts - initial contribution
*/
@NonNullByDefault
public class NeeoRoomConfig {
/** The NEEO room key */
@Nullable
private String roomKey;
/** The refresh polling (in seconds) */
private int refreshPolling;
/** Whether to exclude things */
private boolean excludeThings;
/**
* Gets the room key
*
* @return the room key
*/
@Nullable
public String getRoomKey() {
return roomKey;
}
/**
* Sets the room key.
*
* @param roomKey the new room key
*/
public void setRoomKey(String roomKey) {
this.roomKey = roomKey;
}
/**
* Gets the refresh polling (in seconds)
*
* @return the refresh polling
*/
public int getRefreshPolling() {
return refreshPolling;
}
/**
* Set's the refresh polling
*
* @param refreshPolling the refresh polling
*/
public void setRefreshPolling(int refreshPolling) {
this.refreshPolling = refreshPolling;
}
/**
* Whether to exclude things or not
*
* @return true to exclude, false otherwise
*/
public boolean isExcludeThings() {
return excludeThings;
}
/**
* Sets whether to exclude things
*
* @param excludeThings true to exclude, false otherwise
*/
public void setExcludeThings(boolean excludeThings) {
this.excludeThings = excludeThings;
}
}

View File

@@ -0,0 +1,391 @@
/**
* 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.neeo.internal;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.models.ExecuteResult;
import org.openhab.binding.neeo.internal.models.ExecuteStep;
import org.openhab.binding.neeo.internal.models.NeeoAction;
import org.openhab.binding.neeo.internal.models.NeeoRecipe;
import org.openhab.binding.neeo.internal.models.NeeoRecipes;
import org.openhab.binding.neeo.internal.models.NeeoRoom;
import org.openhab.binding.neeo.internal.models.NeeoScenario;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This protocol class for a Neeo Room
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRoomProtocol {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoRoomProtocol.class);
/** The {@link NeeoHandlerCallback} */
private final NeeoHandlerCallback callback;
/** The room key */
private final String roomKey;
/** The {@link NeeoRoom} */
private final NeeoRoom neeoRoom;
/** The currently active scenarios */
private final AtomicReference<String[]> activeScenarios = new AtomicReference<>(new String[0]);
/**
* Instantiates a new neeo room protocol.
*
* @param callback the non-null callback
* @param roomKey the non-empty room key
* @throws IOException Signals that an I/O exception has occurred.
*/
public NeeoRoomProtocol(NeeoHandlerCallback callback, String roomKey) throws IOException {
Objects.requireNonNull(callback, "callback cannot be null");
NeeoUtil.requireNotEmpty(roomKey, "roomKey cannot be empty");
this.callback = callback;
this.roomKey = roomKey;
final NeeoBrainApi api = callback.getApi();
if (api == null) {
throw new IllegalArgumentException("NeeoBrainApi cannot be null");
}
neeoRoom = api.getRoom(roomKey);
}
/**
* Returns the callback being used
*
* @return the non-null callback
*/
public NeeoHandlerCallback getCallback() {
return callback;
}
/**
* Processes the action if it applies to this room
*
* @param action a non-null action to process
*/
public void processAction(NeeoAction action) {
Objects.requireNonNull(action, "action cannot be null");
final NeeoRecipes recipes = neeoRoom.getRecipes();
final boolean launch = StringUtils.equalsIgnoreCase(NeeoRecipe.LAUNCH, action.getAction());
final boolean poweroff = StringUtils.equalsIgnoreCase(NeeoRecipe.POWEROFF, action.getAction());
// Can't be both true but if both false - it's neither one
if (launch == poweroff) {
return;
}
final String recipeName = action.getRecipe();
final NeeoRecipe recipe = recipeName == null ? null : recipes.getRecipeByName(recipeName);
final String scenarioKey = recipe == null ? null : recipe.getScenarioKey();
if (scenarioKey != null && StringUtils.isNotEmpty(scenarioKey)) {
processScenarioChange(scenarioKey, launch);
} else {
logger.debug("Could not find a recipe named '{}' for the action {}", recipeName, action);
}
}
/**
* Processes a change to the scenario (whether it's been launched or not)
*
* @param scenarioKey a non-null, non-empty scenario key
* @param launch true if the scenario was launched, false otherwise
*/
private void processScenarioChange(String scenarioKey, boolean launch) {
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
final String[] activeScenarios = this.activeScenarios.get();
final int idx = ArrayUtils.indexOf(activeScenarios, scenarioKey);
// already set that way
if ((idx < 0 && !launch) || (idx >= 0 && launch)) {
return;
}
final String[] newScenarios = idx >= 0 ? (String[]) ArrayUtils.remove(activeScenarios, idx)
: (String[]) ArrayUtils.add(activeScenarios, scenarioKey);
this.activeScenarios.set(newScenarios);
refreshScenarioStatus(scenarioKey);
}
/**
* Refresh state of the room - currently only refreshes the active scenarios via {@link #refreshActiveScenarios()}
*/
public void refreshState() {
refreshActiveScenarios();
}
/**
* Refresh the recipe name
*
* @param recipeKey the non-empty recipe key
*/
public void refreshRecipeName(String recipeKey) {
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
if (recipe != null) {
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_NAME, recipeKey), new StringType(recipe.getName()));
}
}
/**
* Refresh the recipe type
*
* @param recipeKey the non-empty recipe key
*/
public void refreshRecipeType(String recipeKey) {
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
if (recipe != null) {
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_TYPE, recipeKey), new StringType(recipe.getType()));
}
}
/**
* Refresh whether the recipe is enabled
*
* @param recipeKey the non-null recipe key
*/
public void refreshRecipeEnabled(String recipeKey) {
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
if (recipe != null) {
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_ENABLED, recipeKey), recipe.isEnabled() ? OnOffType.ON : OnOffType.OFF);
}
}
/**
* Refresh the recipe status.
*
* @param recipeKey the non-null recipe key
*/
public void refreshRecipeStatus(String recipeKey) {
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
if (recipe != null) {
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_STATUS, recipeKey), OnOffType.OFF);
}
}
/**
* Refresh the scenario name.
*
* @param scenarioKey the non-null scenario key
*/
public void refreshScenarioName(String scenarioKey) {
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
final NeeoScenario scenario = neeoRoom.getScenarios().getScenario(scenarioKey);
if (scenario != null) {
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_NAME, scenarioKey), new StringType(scenario.getName()));
}
}
/**
* Refresh whether the scenario is configured.
*
* @param scenarioKey the non-null scenario key
*/
public void refreshScenarioConfigured(String scenarioKey) {
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
final NeeoScenario scenario = neeoRoom.getScenarios().getScenario(scenarioKey);
if (scenario != null) {
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_SCENARIO_ID,
NeeoConstants.ROOM_CHANNEL_ENABLED, scenarioKey),
scenario.isConfigured() ? OnOffType.ON : OnOffType.OFF);
}
}
/**
* Refresh the scenario status.
*
* @param scenarioKey the non-null scenario key
*/
public void refreshScenarioStatus(String scenarioKey) {
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
final NeeoScenario scenario = neeoRoom.getScenarios().getScenario(scenarioKey);
if (scenario != null) {
final String[] active = activeScenarios.get();
final boolean isActive = ArrayUtils.contains(active, scenarioKey);
callback.stateChanged(UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_SCENARIO_ID,
NeeoConstants.ROOM_CHANNEL_STATUS, scenarioKey), isActive ? OnOffType.ON : OnOffType.OFF);
}
}
/**
* Refresh active scenarios
*/
private void refreshActiveScenarios() {
final NeeoBrainApi api = callback.getApi();
if (api == null) {
logger.debug("API is null [likely bridge is offline]");
} else {
try {
final String[] activeScenarios = api.getActiveScenarios();
final String[] oldScenarios = this.activeScenarios.getAndSet(activeScenarios);
if (!ArrayUtils.isEquals(activeScenarios, oldScenarios)) {
for (String scenario : activeScenarios) {
refreshScenarioStatus(scenario);
}
for (String oldScenario : oldScenarios) {
if (!ArrayUtils.contains(activeScenarios, oldScenario)) {
refreshScenarioStatus(oldScenario);
}
}
}
} catch (IOException e) {
logger.debug("Exception requesting active scenarios: {}", e.getMessage(), e);
// callback.statusChanged(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
}
}
}
/**
* Sends the trigger for the current step
*
* @param step a possibly null, possibly empty step to send
*/
private void sendCurrentStepTrigger(@Nullable String step) {
callback.triggerEvent(
UidUtils.createChannelId(NeeoConstants.ROOM_GROUP_STATE_ID, NeeoConstants.ROOM_CHANNEL_CURRENTSTEP),
step == null || StringUtils.isEmpty(step) ? "" : step);
}
/**
* Starts the given recipe key
*
* @param recipeKey the non-null recipe key
*/
public void startRecipe(String recipeKey) {
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
final NeeoBrainApi api = callback.getApi();
if (api == null) {
logger.debug("API is null [likely bridge is offline] - cannot start recipe: {}", recipeKey);
} else {
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipe(recipeKey);
final String scenarioKey = recipe == null ? null : recipe.getScenarioKey();
if (recipe != null) {
if (recipe.isEnabled()) {
final boolean isLaunch = StringUtils.equalsIgnoreCase(NeeoRecipe.LAUNCH, recipe.getType());
try {
if (isLaunch || scenarioKey == null || StringUtils.isEmpty(scenarioKey)) {
handleExecuteResult(scenarioKey, recipeKey, true, api.executeRecipe(roomKey, recipeKey));
} else {
handleExecuteResult(scenarioKey, recipeKey, false, api.stopScenario(roomKey, scenarioKey));
}
} catch (IOException e) {
logger.debug("Exception occurred during execution: {}", e.getMessage(), e);
}
} else {
logger.debug("recipe for key {} was not enabled, cannot start or stop", recipeKey);
}
} else {
logger.debug("recipe key {} was not found", recipeKey);
}
}
}
/**
* Sets the scenario status.
*
* @param scenarioKey the non-null scenario key
* @param start whether to start (true) or stop (false) the scenario
*/
public void setScenarioStatus(String scenarioKey, boolean start) {
NeeoUtil.requireNotEmpty(scenarioKey, "scenarioKey cannot be empty");
final NeeoRecipe recipe = neeoRoom.getRecipes().getRecipeByScenarioKey(scenarioKey,
start ? NeeoRecipe.LAUNCH : NeeoRecipe.POWEROFF);
final String recipeKey = recipe == null ? null : recipe.getKey();
if (recipe != null && recipeKey != null && StringUtils.isNotEmpty(recipeKey)) {
if (recipe.isEnabled()) {
startRecipe(recipeKey);
} else {
logger.debug("Recipe ({}) found for scenario {} but was not enabled", recipe.getKey(), scenarioKey);
}
} else {
logger.debug("No recipe found for scenario {} to start ({})", scenarioKey, start);
}
}
/**
* Handle the {@link ExecuteResult} from a call
*
* @param scenarioKey the possibly null scenario key being changed
* @param recipeKey the non-null recipe key being used
* @param launch whether the recipe launches the scenario (true) or not (false)
* @param result the non-null result (null will do nothing)
*/
private void handleExecuteResult(@Nullable String scenarioKey, String recipeKey, boolean launch,
ExecuteResult result) {
Objects.requireNonNull(result, "result cannot be empty");
NeeoUtil.requireNotEmpty(recipeKey, "recipeKey cannot be empty");
int nextStep = 0;
if (scenarioKey != null && StringUtils.isNotEmpty(scenarioKey)) {
callback.scheduleTask(() -> {
processScenarioChange(scenarioKey, launch);
}, 1);
}
for (final ExecuteStep step : result.getSteps()) {
callback.scheduleTask(() -> {
sendCurrentStepTrigger(step.getText());
}, nextStep);
nextStep += step.getDuration();
}
callback.scheduleTask(() -> {
sendCurrentStepTrigger(null);
refreshRecipeStatus(recipeKey);
}, nextStep);
}
}

View File

@@ -0,0 +1,112 @@
/**
* 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.neeo.internal;
import java.util.Objects;
import java.util.concurrent.Future;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.models.NeeoDevices;
import org.openhab.binding.neeo.internal.models.NeeoDevicesDeserializer;
import org.openhab.binding.neeo.internal.models.NeeoMacros;
import org.openhab.binding.neeo.internal.models.NeeoMacrosDeserializer;
import org.openhab.binding.neeo.internal.models.NeeoRecipes;
import org.openhab.binding.neeo.internal.models.NeeoRecipesDeserializer;
import org.openhab.binding.neeo.internal.models.NeeoRooms;
import org.openhab.binding.neeo.internal.models.NeeoRoomsDeserializer;
import org.openhab.binding.neeo.internal.models.NeeoScenarios;
import org.openhab.binding.neeo.internal.models.NeeoScenariosDeserializer;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
/**
* Various utility functions used by the NEEO binding
*
* @author Tim Roberts - initial contribution
*/
@NonNullByDefault
public class NeeoUtil {
/**
* Builds and returns a {@link Gson}. The gson has adapters registered for {@link NeeoRooms}, {@link NeeoRecipes}
* and {@link NeeoScenarios}
*
* @return a non-null {@link Gson} to use
*/
public static Gson getGson() {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.registerTypeAdapter(NeeoRooms.class, new NeeoRoomsDeserializer());
gsonBuilder.registerTypeAdapter(NeeoRecipes.class, new NeeoRecipesDeserializer());
gsonBuilder.registerTypeAdapter(NeeoScenarios.class, new NeeoScenariosDeserializer());
gsonBuilder.registerTypeAdapter(NeeoDevices.class, new NeeoDevicesDeserializer());
gsonBuilder.registerTypeAdapter(NeeoMacros.class, new NeeoMacrosDeserializer());
return gsonBuilder.create();
}
/**
* Utility function to close a {@link AutoCloseable} and log any exception thrown.
*
* @param closeable a possibly null {@link AutoCloseable}. If null, no action is done.
*/
public static void close(@Nullable AutoCloseable closeable) {
if (closeable != null) {
try {
closeable.close();
} catch (Exception e) {
LoggerFactory.getLogger(NeeoUtil.class).debug("Exception closing: {}", e.getMessage(), e);
}
}
}
/**
* Checks whether the current thread has been interrupted and throws {@link InterruptedException} if it's been
* interrupted
*
* @throws InterruptedException the interrupted exception
*/
public static void checkInterrupt() throws InterruptedException {
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException("thread interrupted");
}
}
/**
* Cancels the specified {@link Future}
*
* @param future a possibly null future. If null, no action is done
*/
public static void cancel(@Nullable Future<?> future) {
if (future != null) {
future.cancel(true);
}
}
/**
* Require the specified value to be a non-null, non-empty string
*
* @param value the value to check
* @param msg the msg to use when throwing an exception
* @throws NullPointerException if value is null
* @throws IllegalArgumentException if value is an empty string
*/
public static void requireNotEmpty(@Nullable String value, String msg) {
Objects.requireNonNull(value, msg);
if (StringUtils.isEmpty(value)) {
throw new IllegalArgumentException(msg);
}
}
}

View File

@@ -0,0 +1,134 @@
/**
* 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.neeo.internal;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.models.NeeoDevice;
import org.openhab.binding.neeo.internal.models.NeeoDeviceDetails;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingUID;
/**
* Utility class for generating some UIDs.
*
* @author Tim Roberts - Initial Contribution
*/
@NonNullByDefault
public class UidUtils {
/** The delimiter to separate 'parts' of an UID */
private static final char DELIMITER = '-';
/**
* Determines if the specified device is an openhab thing or not. The determination is made if the adapter name for
* the device is a valid {@link ThingUID}
*
* @param device a possibly null device
* @return true if a thing, false otherwise
*/
public static boolean isThing(NeeoDevice device) {
final NeeoDeviceDetails details = device.getDetails();
if (details == null) {
return false;
}
final String adapterName = details.getAdapterName();
if (adapterName == null) {
return false;
}
try {
new ThingUID(adapterName);
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
/**
* Parses the channel id/key in the {@link ChannelUID} and will return a non-null, non-empty string array
* representing the parts. The first element will always be the channelID itself. If there is a second element, the
* second element will the the channel key
*
* @param uid the non-null channel uid
* @return the non-null, non empty (only 1 or 2 element) list of parts
*/
public static String[] parseChannelId(ChannelUID uid) {
Objects.requireNonNull(uid, "uid cannot be null");
final String channelId = uid.getIdWithoutGroup();
final int idx = channelId.indexOf(DELIMITER);
if (idx < 0 || idx == channelId.length() - 1) {
return new String[] { channelId };
}
return new String[] { channelId.substring(0, idx), channelId.substring(idx + 1) };
}
/**
* Creates the channel id from the group/channel
*
* @param groupId the not empty group id
* @param channelId the not empty channel id
* @return the full channel id
*/
public static String createChannelId(String groupId, String channelId) {
NeeoUtil.requireNotEmpty(groupId, "groupId cannot be empty");
NeeoUtil.requireNotEmpty(channelId, "channelId cannot be empty");
return createChannelId(groupId, channelId, null);
}
/**
* Creates the channel id from the group, channel id, and key
*
* @param groupId the possibly empty/null group id
* @param channelId the not empty channel id
* @param channelKey the possibly empty/null channel key
* @return the full channel id
*/
public static String createChannelId(@Nullable String groupId, String channelId, @Nullable String channelKey) {
NeeoUtil.requireNotEmpty(channelId, "channelId cannot be empty");
return (StringUtils.isEmpty(groupId) ? "" : (groupId + "#"))
+ (StringUtils.isEmpty(channelKey) ? channelId : (channelId + DELIMITER + channelKey));
}
/**
* Creates the {@link ChannelUID} for a give thingUID, group ID and channel ID
*
* @param thingUid a non-null thing UID
* @param groupId a non-null, non-empty group ID
* @param channelId a non-null, non-empty channel ID
* @return a non-null {@link ChannelUID}
*/
public static ChannelUID createChannelUID(ThingUID thingUid, String groupId, String channelId) {
return createChannelUID(thingUid, groupId, channelId, null);
}
/**
* Creates the {@link ChannelUID} for a give thingUID, group ID, channel ID and channel key
*
* @param thingUid a non-null thing UID
* @param groupId a non-null, non-empty group ID
* @param channelId a non-null, non-empty channel ID
* @param channelKey a potentially null, potentially empty channel KEY
* @return a non-null {@link ChannelUID}
*/
public static ChannelUID createChannelUID(ThingUID thingUid, String groupId, String channelId,
@Nullable String channelKey) {
return new ChannelUID(thingUid, groupId,
channelId + (StringUtils.isEmpty(channelKey) ? "" : (DELIMITER + channelKey)));
}
}

View File

@@ -0,0 +1,161 @@
/**
* 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.neeo.internal.discovery;
import static org.openhab.binding.neeo.internal.NeeoConstants.BRIDGE_TYPE_BRAIN;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import javax.jmdns.ServiceInfo;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of {@link MDNSDiscoveryParticipant} that will discover NEEO brain(s).
*
* @author Tim Roberts - initial contribution
*/
@NonNullByDefault
@Component(immediate = true)
public class NeeoBrainDiscovery implements MDNSDiscoveryParticipant {
/** The logger */
private Logger logger = LoggerFactory.getLogger(NeeoBrainDiscovery.class);
@Override
public Set<@Nullable ThingTypeUID> getSupportedThingTypeUIDs() {
return Collections.singleton(BRIDGE_TYPE_BRAIN);
}
@Override
public String getServiceType() {
return NeeoConstants.NEEO_MDNS_TYPE;
}
@Nullable
@Override
public DiscoveryResult createResult(@Nullable ServiceInfo service) {
if (service == null) {
return null;
}
final ThingUID uid = getThingUID(service);
if (uid == null) {
return null;
}
logger.debug("createResult is evaluating: {}", service);
final Map<String, Object> properties = new HashMap<>(2);
final InetAddress ip = getIpAddress(service);
if (ip == null) {
logger.debug("Application not 'neeo' in MDNS serviceinfo: {}", service);
return null;
}
final String inetAddress = ip.getHostAddress();
final String id = uid.getId();
final String label = service.getName() + " (" + id + ")";
properties.put(NeeoConstants.CONFIG_IPADDRESS, inetAddress);
properties.put(NeeoConstants.CONFIG_ENABLEFORWARDACTIONS, true);
logger.debug("Adding NEEO Brain to inbox: {} at {}", id, inetAddress);
return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build();
}
@Nullable
@Override
public ThingUID getThingUID(@Nullable ServiceInfo service) {
if (service == null) {
return null;
}
logger.debug("getThingUID is evaluating: {}", service);
if (!StringUtils.equals("neeo", service.getApplication())) {
logger.debug("Application not 'neeo' in MDNS serviceinfo: {}", service);
return null;
}
if (getIpAddress(service) == null) {
logger.debug("No IP address found in MDNS serviceinfo: {}", service);
return null;
}
String model = service.getPropertyString("hon"); // model
if (model == null) {
final String server = service.getServer(); // NEEO-xxxxx.local.
if (server != null) {
final int idx = server.indexOf(".");
if (idx >= 0) {
model = server.substring(0, idx);
}
}
}
if (model == null || model.length() <= 5 || !model.toLowerCase().startsWith("neeo")) {
logger.debug("No 'hon' found in MDNS serviceinfo: {}", service);
return null;
}
final String id = model.substring(5);
logger.debug("NEEO Brain Found: {}", id);
return new ThingUID(BRIDGE_TYPE_BRAIN, id);
}
/**
* Gets the ip address found in the {@link ServiceInfo}
*
* @param service a non-null service
* @return the ip address of the service or null if none found.
*/
@Nullable
private InetAddress getIpAddress(ServiceInfo service) {
Objects.requireNonNull(service, "service cannot be null");
for (String addr : service.getHostAddresses()) {
try {
return InetAddress.getByName(addr);
} catch (UnknownHostException e) {
// ignore
}
}
for (InetAddress addr : service.getInet4Addresses()) {
return addr;
}
// Fallback for Inet6addresses
for (InetAddress addr : service.getInet6Addresses()) {
return addr;
}
return null;
}
}

View File

@@ -0,0 +1,129 @@
/**
* 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.neeo.internal.discovery;
import java.io.IOException;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.neeo.internal.NeeoBrainApi;
import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.binding.neeo.internal.NeeoRoomConfig;
import org.openhab.binding.neeo.internal.UidUtils;
import org.openhab.binding.neeo.internal.handler.NeeoRoomHandler;
import org.openhab.binding.neeo.internal.models.NeeoDevice;
import org.openhab.binding.neeo.internal.models.NeeoRoom;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of {@link AbstractDiscoveryService} that will discover the devices in a NEEO room;
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoDeviceDiscoveryService extends AbstractDiscoveryService {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoDeviceDiscoveryService.class);
/** The device thing type we support */
private static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections
.singleton(NeeoConstants.THING_TYPE_DEVICE);
/** The timeout (in seconds) for searching the room */
private static final int SEARCH_TIME = 10;
/** The room handler to search */
private final NeeoRoomHandler roomHandler;
/**
* Constructs the discovery service from the room handler
*
* @param roomHandler a non-null room handler
*/
public NeeoDeviceDiscoveryService(NeeoRoomHandler roomHandler) {
super(DISCOVERABLE_THING_TYPES_UIDS, SEARCH_TIME);
Objects.requireNonNull(roomHandler, "roomHandler cannot be null");
this.roomHandler = roomHandler;
}
@Override
protected void startScan() {
final Bridge roomBridge = roomHandler.getThing();
final ThingUID roomUid = roomBridge.getUID();
final String brainId = roomHandler.getNeeoBrainId();
if (brainId == null || StringUtils.isEmpty(brainId)) {
logger.debug("Unknown brain ID for roomHandler: {}", roomHandler);
return;
}
final NeeoBrainApi api = roomHandler.getNeeoBrainApi();
if (api == null) {
logger.debug("Brain API was not available for {} - skipping", brainId);
return;
}
final NeeoRoomConfig config = roomBridge.getConfiguration().as(NeeoRoomConfig.class);
final String roomKey = config.getRoomKey();
if (roomKey == null || StringUtils.isEmpty(roomKey)) {
logger.debug("RoomKey wasn't configured for {} - skipping", brainId);
return;
}
try {
final NeeoRoom room = api.getRoom(roomKey);
final NeeoDevice[] devices = room.getDevices().getDevices();
if (devices.length == 0) {
logger.debug("Room {} found - but there were no devices - skipping", room.getName());
return;
}
logger.debug("Room {} found, scanning {} devices in it", room.getName(), devices.length);
for (NeeoDevice device : devices) {
final String deviceKey = device.getKey();
if (deviceKey == null || StringUtils.isEmpty(deviceKey)) {
logger.debug("Device key wasn't found for device: {}", device);
continue;
}
if (config.isExcludeThings() && UidUtils.isThing(device)) {
logger.debug("Found openHAB thing but ignoring per configuration: {}", device);
continue;
}
logger.debug("Device #{} found - {}", deviceKey, device.getName());
final ThingUID thingUID = new ThingUID(NeeoConstants.THING_TYPE_DEVICE, roomUid, deviceKey);
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withProperty(NeeoConstants.CONFIG_DEVICEKEY, deviceKey).withBridge(roomUid)
.withLabel(device.getName() + " (NEEO " + brainId + ")").build();
thingDiscovered(discoveryResult);
}
} catch (IOException e) {
logger.debug("IOException occurred getting brain info ({}): {}", brainId, e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,118 @@
/**
* 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.neeo.internal.discovery;
import java.io.IOException;
import java.util.Collections;
import java.util.Objects;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.neeo.internal.NeeoBrainApi;
import org.openhab.binding.neeo.internal.NeeoBrainConfig;
import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.binding.neeo.internal.handler.NeeoBrainHandler;
import org.openhab.binding.neeo.internal.models.NeeoBrain;
import org.openhab.binding.neeo.internal.models.NeeoRoom;
import org.openhab.core.config.discovery.AbstractDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryResult;
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Implementation of {@link AbstractDiscoveryService} that will discover the rooms in a NEEO brain;
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRoomDiscoveryService extends AbstractDiscoveryService {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoRoomDiscoveryService.class);
/** The room bridge type we support */
private static final Set<ThingTypeUID> DISCOVERABLE_THING_TYPES_UIDS = Collections
.singleton(NeeoConstants.BRIDGE_TYPE_ROOM);
/** The timeout (in seconds) for searching the brain */
private static final int SEARCH_TIME = 10;
/** The brain handler that we will use */
private final NeeoBrainHandler brainHandler;
/**
* Constructs the discover service from the brain handler
*
* @param brainHandler a non-null brain handler
*/
public NeeoRoomDiscoveryService(NeeoBrainHandler brainHandler) {
super(DISCOVERABLE_THING_TYPES_UIDS, SEARCH_TIME);
Objects.requireNonNull(brainHandler, "brainHandler cannot be null");
this.brainHandler = brainHandler;
}
@Override
protected void startScan() {
final String brainId = brainHandler.getNeeoBrainId();
final Bridge brainBridge = brainHandler.getThing();
final ThingUID brainUid = brainBridge.getUID();
final NeeoBrainApi api = brainHandler.getNeeoBrainApi();
if (api == null) {
logger.debug("Brain API was not available for {} - skipping", brainId);
return;
}
try {
final NeeoBrain brain = api.getBrain();
final NeeoBrainConfig config = brainBridge.getConfiguration().as(NeeoBrainConfig.class);
final NeeoRoom[] rooms = brain.getRooms().getRooms();
if (rooms.length == 0) {
logger.debug("Brain {} ({}) found - but there were no rooms - skipping", brain.getName(), brainId);
return;
}
logger.debug("Brain {} ({}) found, scanning {} rooms in it", brain.getName(), brainId, rooms.length);
for (NeeoRoom room : rooms) {
final String roomKey = room.getKey();
if (roomKey == null || StringUtils.isEmpty(roomKey)) {
logger.debug("Room didn't have a room key: {}", room);
continue;
}
if (room.getDevices().getDevices().length == 0 && room.getRecipes().getRecipes().length == 0
&& !config.isDiscoverEmptyRooms()) {
logger.debug("Room {} ({}) found but has no devices or recipes, ignoring - {}", roomKey, brainId,
room.getName());
continue;
}
final ThingUID thingUID = new ThingUID(NeeoConstants.BRIDGE_TYPE_ROOM, brainUid, roomKey);
final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
.withProperty(NeeoConstants.CONFIG_ROOMKEY, roomKey)
.withProperty(NeeoConstants.CONFIG_EXCLUDE_THINGS, true).withBridge(brainUid)
.withLabel(room.getName() + " (NEEO " + brainId + ")").build();
thingDiscovered(discoveryResult);
}
} catch (IOException e) {
logger.debug("IOException occurred getting brain info ({}): {}", brainId, e.getMessage(), e);
}
}
}

View File

@@ -0,0 +1,189 @@
/**
* 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.neeo.internal.handler;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.binding.neeo.internal.UidUtils;
import org.openhab.binding.neeo.internal.models.NeeoDevice;
import org.openhab.binding.neeo.internal.models.NeeoMacro;
import org.openhab.binding.neeo.internal.models.NeeoRecipe;
import org.openhab.binding.neeo.internal.models.NeeoRecipes;
import org.openhab.binding.neeo.internal.models.NeeoRoom;
import org.openhab.binding.neeo.internal.models.NeeoScenario;
import org.openhab.binding.neeo.internal.models.NeeoScenarios;
import org.openhab.core.thing.Channel;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.builder.ChannelBuilder;
/**
* Utility class for generating channels
*
* @author Tim Roberts - Initial Contribution
*/
@NonNullByDefault
class ChannelUtils {
/**
* Generates a list of {@link Channel} s for a specific device. This implementation will generate a channel for each
* macro found on the device.
*
* @param thingUid a non-null thingUID
* @param device a non-null device
* @return a non-null but possibly empty list of {@link Channel} s
*/
static List<Channel> generateChannels(ThingUID thingUid, NeeoDevice device) {
Objects.requireNonNull(thingUid, "thingUid cannot be null");
Objects.requireNonNull(device, "device cannot be null");
final List<Channel> channels = new ArrayList<>();
for (NeeoMacro macro : device.getMacros().getMacros()) {
final String key = macro.getKey();
if (key != null && StringUtils.isNotEmpty(key)) {
final String label = StringUtils.isEmpty(macro.getName()) ? macro.getLabel() : macro.getName();
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.DEVICE_GROUP_MACROS_ID,
NeeoConstants.DEVICE_CHANNEL_STATUS, key), "Switch")
.withLabel(label == null || StringUtils.isEmpty(label) ? key : label)
.withType(NeeoConstants.DEVICE_MACRO_STATUS_UID).build());
}
}
return channels;
}
/**
* Generates a list of {@link Channel} s for a specific room. This implementation will generate multiple channels
* for each scenario and recipe in the room (in addition to the current step channel)
*
* @param thingUid a non-null thingUID
* @param room a non-null room
* @return a non-null but possibly empty list of {@link Channel} s
*/
static List<Channel> generateChannels(ThingUID thingUid, NeeoRoom room) {
Objects.requireNonNull(thingUid, "thingUid cannot be null");
Objects.requireNonNull(room, "room cannot be null");
final List<Channel> channels = new ArrayList<>();
channels.addAll(generateStateChannels(thingUid));
channels.addAll(generateScenarioChannels(thingUid, room.getScenarios()));
channels.addAll(generateRecipeChannels(thingUid, room.getRecipes()));
return channels;
}
/**
* Helper method to generate state channels (the current step)
*
* @param thingUid a non-null thingUID
* @return a non-null but possibly empty list of {@link Channel} s
*/
private static List<Channel> generateStateChannels(ThingUID thingUid) {
Objects.requireNonNull(thingUid, "thingUid cannot be null");
final List<Channel> channels = new ArrayList<>();
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_STATE_ID,
NeeoConstants.ROOM_CHANNEL_CURRENTSTEP), "String")
.withType(NeeoConstants.ROOM_STATE_CURRENTSTEP_UID).build());
return channels;
}
/**
* Helper method to generate scenario channels
*
* @param thingUid a non-null thingUID
* @param scenarios the non-null scenarios
* @return a non-null but possibly empty list of {@link Channel} s
*/
private static List<Channel> generateScenarioChannels(ThingUID thingUid, NeeoScenarios scenarios) {
Objects.requireNonNull(thingUid, "thingUid cannot be null");
Objects.requireNonNull(scenarios, "scenarios cannot be null");
final List<Channel> channels = new ArrayList<>();
for (NeeoScenario scenario : scenarios.getScenarios()) {
final String key = scenario.getKey();
if (key != null && StringUtils.isNotEmpty(key)) {
final String scenarioLabel = StringUtils.isEmpty(scenario.getName()) ? null : scenario.getName();
final String nameLabel = (scenarioLabel == null || StringUtils.isEmpty(scenarioLabel) ? key
: scenarioLabel) + " Name";
final String configuredLabel = (scenarioLabel == null || StringUtils.isEmpty(scenarioLabel) ? key
: scenarioLabel) + " Configured";
final String statusLabel = (scenarioLabel == null || StringUtils.isEmpty(scenarioLabel) ? key
: scenarioLabel) + " Status";
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_SCENARIO_ID,
NeeoConstants.ROOM_CHANNEL_NAME, key), "String")
.withLabel(nameLabel).withType(NeeoConstants.ROOM_SCENARIO_NAME_UID).build());
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_SCENARIO_ID,
NeeoConstants.ROOM_CHANNEL_CONFIGURED, key), "Switch")
.withLabel(configuredLabel).withType(NeeoConstants.ROOM_SCENARIO_CONFIGURED_UID).build());
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_SCENARIO_ID,
NeeoConstants.ROOM_CHANNEL_STATUS, key), "Switch")
.withLabel(statusLabel).withType(NeeoConstants.ROOM_SCENARIO_STATUS_UID).build());
}
}
return channels;
}
/**
* Helper method to generate recipe channels
*
* @param thingUid a non-null thingUID
* @param recipes the non-null recipes
* @return a non-null but possibly empty list of {@link Channel} s
*/
private static List<Channel> generateRecipeChannels(ThingUID thingUid, NeeoRecipes recipes) {
Objects.requireNonNull(thingUid, "thingUid cannot be null");
Objects.requireNonNull(recipes, "recipes cannot be null");
final List<Channel> channels = new ArrayList<>();
for (NeeoRecipe recipe : recipes.getRecipes()) {
final String key = recipe.getKey();
if (key != null && StringUtils.isNotEmpty(key)) {
final String recipeLabel = StringUtils.isEmpty(recipe.getName()) ? null : recipe.getName();
final String nameLabel = (recipeLabel == null || StringUtils.isEmpty(recipeLabel) ? key : recipeLabel)
+ " Name (" + recipe.getType() + ")";
final String typeLabel = (recipeLabel == null || StringUtils.isEmpty(recipeLabel) ? key : recipeLabel)
+ " Type (" + recipe.getType() + ")";
final String enabledLabel = (recipeLabel == null || StringUtils.isEmpty(recipeLabel) ? key
: recipeLabel) + " Enabled (" + recipe.getType() + ")";
final String statusLabel = (recipeLabel == null || StringUtils.isEmpty(recipeLabel) ? key : recipeLabel)
+ " Status (" + recipe.getType() + ")";
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_NAME, key), "String")
.withLabel(nameLabel).withType(NeeoConstants.ROOM_RECIPE_NAME_UID).build());
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_TYPE, key), "String")
.withLabel(enabledLabel).withType(NeeoConstants.ROOM_RECIPE_TYPE_UID).build());
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_ENABLED, key), "Switch")
.withLabel(typeLabel).withType(NeeoConstants.ROOM_RECIPE_ENABLED_UID).build());
channels.add(ChannelBuilder
.create(UidUtils.createChannelUID(thingUid, NeeoConstants.ROOM_GROUP_RECIPE_ID,
NeeoConstants.ROOM_CHANNEL_STATUS, key), "Switch")
.withLabel(statusLabel).withType(NeeoConstants.ROOM_RECIPE_STATUS_UID).build());
}
}
return channels;
}
}

View File

@@ -0,0 +1,391 @@
/**
* 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.neeo.internal.handler;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.servlet.ServletException;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.NeeoBrainApi;
import org.openhab.binding.neeo.internal.NeeoBrainConfig;
import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.binding.neeo.internal.NeeoUtil;
import org.openhab.binding.neeo.internal.models.NeeoAction;
import org.openhab.binding.neeo.internal.models.NeeoBrain;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
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.BaseBridgeHandler;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.types.Command;
import org.osgi.service.http.HttpService;
import org.osgi.service.http.NamespaceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
/**
* A subclass of {@link BaseBridgeHandler} is responsible for handling commands and discovery for a
* {@link NeeoBrain}
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoBrainHandler extends BaseBridgeHandler {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoBrainHandler.class);
/** The {@link HttpService} to register callbacks */
private final HttpService httpService;
/** The {@link NetworkAddressService} to use */
private final NetworkAddressService networkAddressService;
/** GSON implementation - only used to deserialize {@link NeeoAction} */
private final Gson gson = new Gson();
/** The port the HTTP service is listening on */
private final int servicePort;
/**
* The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
*/
private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>();
/** The check status task (not-null when connecting, null otherwise) */
private final AtomicReference<@Nullable Future<?>> checkStatus = new AtomicReference<>();
/** The lock that protected multi-threaded access to the state variables */
private final ReadWriteLock stateLock = new ReentrantReadWriteLock();
/** The {@link NeeoBrainApi} (null until set by {@link #initializationTask}) */
@Nullable
private NeeoBrainApi neeoBrainApi;
/** The path to the forward action servlet - will be null if not enabled */
@Nullable
private String servletPath;
/** The servlet for forward actions - will be null if not enabled */
@Nullable
private NeeoForwardActionsServlet forwardActionServlet;
/**
* Instantiates a new neeo brain handler from the {@link Bridge}, service port, {@link HttpService} and
* {@link NetworkAddressService}.
*
* @param bridge the non-null {@link Bridge}
* @param servicePort the service port the http service is listening on
* @param httpService the non-null {@link HttpService}
* @param networkAddressService the non-null {@link NetworkAddressService}
*/
NeeoBrainHandler(Bridge bridge, int servicePort, HttpService httpService,
NetworkAddressService networkAddressService) {
super(bridge);
Objects.requireNonNull(bridge, "bridge cannot be null");
Objects.requireNonNull(httpService, "httpService cannot be null");
Objects.requireNonNull(networkAddressService, "networkAddressService cannot be null");
this.servicePort = servicePort;
this.httpService = httpService;
this.networkAddressService = networkAddressService;
}
/**
* Handles any {@Commands} sent - this bridge has no commands and does nothing
*
* @see
* org.openhab.core.thing.binding.ThingHandler#handleCommand(org.openhab.core.thing.ChannelUID,
* org.openhab.core.types.Command)
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
}
/**
* Simply cancels any existing initialization tasks and schedules a new task
*
* @see org.openhab.core.thing.binding.BaseThingHandler#initialize()
*/
@Override
public void initialize() {
NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
initializeTask();
})));
}
/**
* Initializes the bridge by connecting to the configuration ip address and parsing the results. Properties will be
* set and the thing will go online.
*/
private void initializeTask() {
final Lock writerLock = stateLock.writeLock();
writerLock.lock();
try {
NeeoUtil.checkInterrupt();
final NeeoBrainConfig config = getBrainConfig();
final String ipAddress = config.getIpAddress();
if (ipAddress == null || StringUtils.isEmpty(ipAddress)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Brain IP Address must be specified");
return;
}
final NeeoBrainApi api = new NeeoBrainApi(ipAddress);
final NeeoBrain brain = api.getBrain();
final String brainId = getNeeoBrainId();
NeeoUtil.checkInterrupt();
neeoBrainApi = api;
final Map<String, String> properties = new HashMap<>();
addProperty(properties, "Name", brain.getName());
addProperty(properties, "Version", brain.getVersion());
addProperty(properties, "Label", brain.getLabel());
addProperty(properties, "Is Configured", String.valueOf(brain.isConfigured()));
addProperty(properties, "Key", brain.getKey());
addProperty(properties, "AirKey", brain.getAirkey());
addProperty(properties, "Last Change", String.valueOf(brain.getLastChange()));
updateProperties(properties);
if (config.isEnableForwardActions()) {
NeeoUtil.checkInterrupt();
forwardActionServlet = new NeeoForwardActionsServlet(scheduler,
new NeeoForwardActionsServlet.Callback() {
@Override
public void post(String json) {
triggerChannel(NeeoConstants.CHANNEL_BRAIN_FOWARDACTIONS, json);
final NeeoAction action = gson.fromJson(json, NeeoAction.class);
for (final Thing child : getThing().getThings()) {
final ThingHandler th = child.getHandler();
if (th instanceof NeeoRoomHandler) {
((NeeoRoomHandler) th).processAction(action);
}
}
}
}, config.getForwardChain());
NeeoUtil.checkInterrupt();
try {
servletPath = NeeoConstants.WEBAPP_FORWARDACTIONS.replace("{brainid}", brainId);
httpService.registerServlet(servletPath, forwardActionServlet, new Hashtable<>(),
httpService.createDefaultHttpContext());
final URL callbackURL = createCallbackUrl(brainId, config);
if (callbackURL == null) {
logger.debug(
"Unable to create a callback URL because there is no primary address specified (please set the primary address in the configuration)");
} else {
final URL url = new URL(callbackURL, servletPath);
api.registerForwardActions(url);
}
} catch (NamespaceException | ServletException e) {
logger.debug("Error registering forward actions to {}: {}", servletPath, e.getMessage(), e);
}
}
NeeoUtil.checkInterrupt();
updateStatus(ThingStatus.ONLINE);
NeeoUtil.checkInterrupt();
if (config.getCheckStatusInterval() > 0) {
NeeoUtil.cancel(checkStatus.getAndSet(scheduler.scheduleWithFixedDelay(() -> {
try {
NeeoUtil.checkInterrupt();
checkStatus(ipAddress);
} catch (InterruptedException e) {
// do nothing - we were interrupted and should stop
}
}, config.getCheckStatusInterval(), config.getCheckStatusInterval(), TimeUnit.SECONDS)));
}
} catch (IOException e) {
logger.debug("Exception occurred connecting to brain: {}", e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Exception occurred connecting to brain: " + e.getMessage());
} catch (InterruptedException e) {
logger.debug("Initializtion was interrupted", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
"Initialization was interrupted");
} finally {
writerLock.unlock();
}
}
/**
* Helper method to add a property to the properties map if the value is not null
*
* @param properties a non-null properties map
* @param key a non-null, non-empty key
* @param value a possibly null, possibly empty key
*/
private void addProperty(Map<String, String> properties, String key, @Nullable String value) {
Objects.requireNonNull(properties, "properties cannot be null");
NeeoUtil.requireNotEmpty(key, "key cannot be empty");
if (value != null && StringUtils.isNotEmpty(value)) {
properties.put(key, value);
}
}
/**
* Gets the {@link NeeoBrainApi} used by this bridge
*
* @return a possibly null {@link NeeoBrainApi}
*/
@Nullable
public NeeoBrainApi getNeeoBrainApi() {
final Lock readerLock = stateLock.readLock();
readerLock.lock();
try {
return neeoBrainApi;
} finally {
readerLock.unlock();
}
}
/**
* Gets the brain id used by this bridge
*
* @return a non-null, non-empty brain id
*/
public String getNeeoBrainId() {
return getThing().getUID().getId();
}
/**
* Helper method to get the {@link NeeoBrainConfig}
*
* @return the {@link NeeoBrainConfig}
*/
private NeeoBrainConfig getBrainConfig() {
return getConfigAs(NeeoBrainConfig.class);
}
/**
* Checks the status of the brain via a quick socket connection. If the status is unavailable and we are
* {@link ThingStatus#ONLINE}, then we go {@link ThingStatus#OFFLINE}. If the status is available and we are
* {@link ThingStatus#OFFLINE}, we go {@link ThingStatus#ONLINE}.
*
* @param ipAddress a non-null, non-empty IP address
*/
private void checkStatus(String ipAddress) {
NeeoUtil.requireNotEmpty(ipAddress, "ipAddress cannot be empty");
try {
try (Socket soc = new Socket()) {
soc.connect(new InetSocketAddress(ipAddress, NeeoConstants.DEFAULT_BRAIN_PORT), 5000);
}
logger.debug("Checking connectivity to {}:{} - successful", ipAddress, NeeoConstants.DEFAULT_BRAIN_PORT);
if (getThing().getStatus() != ThingStatus.ONLINE) {
updateStatus(ThingStatus.ONLINE);
}
} catch (IOException e) {
if (getThing().getStatus() == ThingStatus.ONLINE) {
logger.debug("Checking connectivity to {}:{} - unsuccessful - going offline: {}", ipAddress,
NeeoConstants.DEFAULT_BRAIN_PORT, e.getMessage(), e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Exception occurred connecting to brain: " + e.getMessage());
} else {
logger.debug("Checking connectivity to {}:{} - unsuccessful - still offline", ipAddress,
NeeoConstants.DEFAULT_BRAIN_PORT);
}
}
}
/**
* Disposes of the bridge by closing/removing the {@link #neeoBrainApi} and canceling/removing any pending
* {@link #initializeTask()}
*/
@Override
public void dispose() {
final Lock writerLock = stateLock.writeLock();
writerLock.lock();
try {
final NeeoBrainApi api = neeoBrainApi;
neeoBrainApi = null;
NeeoUtil.cancel(initializationTask.getAndSet(null));
NeeoUtil.cancel(checkStatus.getAndSet(null));
if (forwardActionServlet != null) {
forwardActionServlet = null;
if (api != null) {
try {
api.deregisterForwardActions();
} catch (IOException e) {
logger.debug("IOException occurred deregistering the forward actions: {}", e.getMessage(), e);
}
}
if (servletPath != null) {
httpService.unregister(servletPath);
servletPath = null;
}
}
NeeoUtil.close(api);
} finally {
writerLock.unlock();
}
}
/**
* Creates the URL the brain should callback. Note: if there is multiple interfaces, we try to prefer the one on the
* same subnet as the brain
*
* @param brainId the non-null, non-empty brain identifier
* @param config the non-null brain configuration
* @return the callback URL
* @throws MalformedURLException if the URL is malformed
*/
@Nullable
private URL createCallbackUrl(String brainId, NeeoBrainConfig config) throws MalformedURLException {
NeeoUtil.requireNotEmpty(brainId, "brainId cannot be empty");
Objects.requireNonNull(config, "config cannot be null");
final String ipAddress = networkAddressService.getPrimaryIpv4HostAddress();
if (ipAddress == null) {
logger.debug("No network interface could be found.");
return null;
}
return new URL("http://" + ipAddress + ":" + servicePort);
}
}

View File

@@ -0,0 +1,375 @@
/**
* 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.neeo.internal.handler;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.NeeoBrainApi;
import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.binding.neeo.internal.NeeoDeviceConfig;
import org.openhab.binding.neeo.internal.NeeoDeviceProtocol;
import org.openhab.binding.neeo.internal.NeeoHandlerCallback;
import org.openhab.binding.neeo.internal.NeeoRoomConfig;
import org.openhab.binding.neeo.internal.NeeoUtil;
import org.openhab.binding.neeo.internal.UidUtils;
import org.openhab.binding.neeo.internal.models.NeeoDevice;
import org.openhab.binding.neeo.internal.models.NeeoDeviceDetails;
import org.openhab.binding.neeo.internal.models.NeeoDeviceDetailsTiming;
import org.openhab.binding.neeo.internal.models.NeeoRoom;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
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.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* An extension of {@link BaseThingHandler} that is responsible for handling commands for a device
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoDeviceHandler extends BaseThingHandler {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoDeviceHandler.class);
/**
* The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
*/
private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>(null);
/**
* The refresh task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
*/
private final AtomicReference<@Nullable ScheduledFuture<?>> refreshTask = new AtomicReference<>(null);
/** The {@link NeeoDeviceProtocol} (null until set by {@link #initializationTask}) */
private final AtomicReference<@Nullable NeeoDeviceProtocol> deviceProtocol = new AtomicReference<>();
/**
* Instantiates a new neeo device handler.
*
* @param thing the non-null thing
*/
NeeoDeviceHandler(Thing thing) {
super(thing);
Objects.requireNonNull(thing, "thing cannot be null");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Objects.requireNonNull(channelUID, "channelUID cannot be null");
Objects.requireNonNull(command, "command cannot be null");
final NeeoDeviceProtocol protocol = deviceProtocol.get();
if (protocol == null) {
logger.debug("Protocol is null - ignoring update: {}", channelUID);
return;
}
final String[] channelIds = UidUtils.parseChannelId(channelUID);
if (channelIds.length == 0) {
logger.debug("Bad group declaration: {}", channelUID);
return;
}
final String localGroupId = channelUID.getGroupId();
final String groupId = localGroupId == null || StringUtils.isEmpty(localGroupId) ? "" : localGroupId;
final String channelId = channelIds[0];
final String channelKey = channelIds.length > 1 ? channelIds[1] : "";
if (StringUtils.isEmpty(groupId)) {
logger.debug("GroupID for channel is null - ignoring command: {}", channelUID);
return;
}
if (command instanceof RefreshType) {
refreshChannel(protocol, groupId, channelId, channelKey);
} else {
switch (groupId) {
case NeeoConstants.DEVICE_GROUP_MACROS_ID:
switch (channelId) {
case NeeoConstants.DEVICE_CHANNEL_STATUS:
if (command instanceof OnOffType) {
protocol.setMacroStatus(channelKey, command == OnOffType.ON);
}
break;
default:
logger.debug("Unknown channel to set: {}", channelUID);
break;
}
break;
default:
logger.debug("Unknown group to set: {}", channelUID);
break;
}
}
}
/**
* Refresh the specified channel section, key and id using the specified protocol
*
* @param protocol a non-null protocol to use
* @param groupId the non-empty groupId
* @param channelId the non-empty channel id
* @param channelKey the non-empty channel key
*/
private void refreshChannel(NeeoDeviceProtocol protocol, String groupId, String channelId, String channelKey) {
Objects.requireNonNull(protocol, "protocol cannot be null");
NeeoUtil.requireNotEmpty(groupId, "groupId must not be empty");
NeeoUtil.requireNotEmpty(channelId, "channelId must not be empty");
NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
switch (groupId) {
case NeeoConstants.DEVICE_GROUP_MACROS_ID:
switch (channelId) {
case NeeoConstants.DEVICE_CHANNEL_STATUS:
protocol.refreshMacroStatus(channelKey);
break;
}
break;
}
}
@Override
public void initialize() {
NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
initializeTask();
})));
}
/**
* Initializes the task be creating the {@link NeeoDeviceProtocol}, going online and then scheduling the refresh
* task.
*/
private void initializeTask() {
final NeeoDeviceConfig config = getConfigAs(NeeoDeviceConfig.class);
final String roomKey = getRoomKey();
if (roomKey == null || StringUtils.isEmpty(roomKey)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Room key (from the parent room bridge) was not found");
return;
}
final String deviceKey = config.getDeviceKey();
if (deviceKey == null || StringUtils.isEmpty(deviceKey)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Device key was not found or empty");
return;
}
try {
NeeoUtil.checkInterrupt();
final NeeoBrainApi brainApi = getNeeoBrainApi();
if (brainApi == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Cannot find the NEEO Brain API");
return;
}
final NeeoRoom room = brainApi.getRoom(roomKey);
final NeeoDevice device = room.getDevices().getDevice(deviceKey);
if (device == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Device (" + config.getDeviceKey() + ") was not found in room (" + roomKey + ")");
return;
}
final ThingUID thingUid = getThing().getUID();
final Map<String, String> properties = new HashMap<>();
final NeeoDeviceDetails details = device.getDetails();
if (details != null) {
/** The following properties have matches in org.openhab.io.neeo.OpenHabToDeviceConverter.java */
addProperty(properties, "Source Name", details.getSourceName());
addProperty(properties, "Adapter Name", details.getAdapterName());
addProperty(properties, "Type", details.getType());
addProperty(properties, "Manufacturer", details.getManufacturer());
addProperty(properties, "Name", details.getName());
final NeeoDeviceDetailsTiming timing = details.getTiming();
if (timing != null) {
properties.put("Standby Command Delay", toString(timing.getStandbyCommandDelay()));
properties.put("Source Switch Delay", toString(timing.getSourceSwitchDelay()));
properties.put("Shutdown Delay", toString(timing.getShutdownDelay()));
}
properties.put("Device Capabilities", StringUtils.join(details.getDeviceCapabilities(), ','));
}
final ThingBuilder thingBuilder = editThing();
thingBuilder.withLabel(device.getName() + " (NEEO " + brainApi.getBrain().getKey() + ")")
.withProperties(properties).withChannels(ChannelUtils.generateChannels(thingUid, device));
updateThing(thingBuilder.build());
NeeoUtil.checkInterrupt();
final NeeoDeviceProtocol protocol = new NeeoDeviceProtocol(new NeeoHandlerCallback() {
@Override
public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
updateStatus(status, detail, msg);
}
@Override
public void stateChanged(String channelId, State state) {
updateState(channelId, state);
}
@Override
public void setProperty(String propertyName, String propertyValue) {
getThing().setProperty(propertyName, propertyValue);
}
@Override
public void scheduleTask(Runnable task, long milliSeconds) {
scheduler.schedule(task, milliSeconds, TimeUnit.MILLISECONDS);
}
@Override
public void triggerEvent(String channelID, String event) {
triggerChannel(channelID, event);
}
@Nullable
@Override
public NeeoBrainApi getApi() {
return getNeeoBrainApi();
}
}, roomKey, deviceKey);
deviceProtocol.getAndSet(protocol);
NeeoUtil.checkInterrupt();
updateStatus(ThingStatus.ONLINE);
} catch (IOException e) {
logger.debug("IOException during initialization", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Room " + roomKey + " couldn't be found");
} catch (InterruptedException e) {
logger.debug("Initialization was interrupted", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
"Initialization was interrupted");
}
}
/**
* Helper method to add a property to the properties map if the value is not null
*
* @param properties a non-null properties map
* @param key a non-null, non-empty key
* @param value a possibly null, possibly empty key
*/
private void addProperty(Map<String, String> properties, String key, @Nullable String value) {
Objects.requireNonNull(properties, "properties cannot be null");
NeeoUtil.requireNotEmpty(key, "key cannot be empty");
if (value != null && StringUtils.isNotEmpty(value)) {
properties.put(key, value);
}
}
/**
* Helper method to get the room key from the parent bridge (which should be a room)
*
* @return a non-null, non-empty room key if found, null if not found
*/
@Nullable
private String getRoomKey() {
final Bridge bridge = getBridge();
if (bridge != null) {
final BridgeHandler handler = bridge.getHandler();
if (handler instanceof NeeoRoomHandler) {
return handler.getThing().getConfiguration().as(NeeoRoomConfig.class).getRoomKey();
}
}
return null;
}
/**
* Helper method to simply create a string from an integer
*
* @param i the integer
* @return the resulting string representation
*/
private static String toString(@Nullable Integer i) {
if (i == null) {
return "";
}
return i.toString();
}
/**
* Helper method to return the {@link NeeoRoomHandler} associated with this handler
*
* @return a possibly null {@link NeeoRoomHandler}
*/
@Nullable
private NeeoRoomHandler getRoomHandler() {
final Bridge parent = getBridge();
if (parent != null) {
final BridgeHandler handler = parent.getHandler();
if (handler instanceof NeeoRoomHandler) {
return ((NeeoRoomHandler) handler);
}
}
return null;
}
/**
* Returns the {@link NeeoBrainApi} associated with this handler.
*
* @return the {@link NeeoBrainApi} or null if not found
*/
@Nullable
private NeeoBrainApi getNeeoBrainApi() {
final NeeoRoomHandler handler = getRoomHandler();
return handler == null ? null : handler.getNeeoBrainApi();
}
/**
* Returns the brain ID associated with this handler.
*
* @return the brain ID or null if not found
*/
@Nullable
public String getNeeoBrainId() {
final NeeoRoomHandler handler = getRoomHandler();
return handler == null ? null : handler.getNeeoBrainId();
}
@Override
public void dispose() {
NeeoUtil.cancel(initializationTask.getAndSet(null));
NeeoUtil.cancel(refreshTask.getAndSet(null));
deviceProtocol.getAndSet(null);
}
}

View File

@@ -0,0 +1,115 @@
/**
* 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.neeo.internal.handler;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ScheduledExecutorService;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.jetty.http.HttpStatus;
import org.openhab.binding.neeo.internal.net.HttpRequest;
import org.openhab.binding.neeo.internal.net.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This servlet handles the forward actions events from the NEEO Brain. The forward actions will be posted to the
* callback and then will be forwarded on to any URLs lised in {@link #forwardChain}
*
* @author Tim Roberts - Initial contribution
*
*/
@NonNullByDefault
@SuppressWarnings("serial")
public class NeeoForwardActionsServlet extends HttpServlet {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoForwardActionsServlet.class);
/** The event publisher */
private final Callback callback;
/** The forwarding chain */
@Nullable
private final String forwardChain;
/** The scheduler to use to schedule recipe execution */
private final ScheduledExecutorService scheduler;
/**
* Creates the servlet the will process foward action events from the NEEO brain.
*
* @param scheduler a non-null {@link ScheduledExecutorService} to schedule forward actions
* @param callback a non-null {@link Callback}
* @param forwardChain a possibly null, possibly empty forwarding chain
*/
NeeoForwardActionsServlet(ScheduledExecutorService scheduler, Callback callback, @Nullable String forwardChain) {
super();
Objects.requireNonNull(scheduler, "scheduler cannot be null");
Objects.requireNonNull(callback, "callback cannot be null");
this.scheduler = scheduler;
this.callback = callback;
this.forwardChain = forwardChain;
}
/**
* Processes the post action from the NEEO brain. Simply get's the specified json and then forwards it on (if
* needed)
*
* @param req the non-null request
* @param resp the non-null response
*/
@Override
protected void doPost(@Nullable HttpServletRequest req, @Nullable HttpServletResponse resp)
throws ServletException, IOException {
Objects.requireNonNull(req, "req cannot be null");
Objects.requireNonNull(resp, "resp cannot be null");
final String json = IOUtils.toString(req.getReader());
logger.debug("handleForwardActions {}", json);
callback.post(json);
final String fc = forwardChain;
if (fc != null && StringUtils.isNotEmpty(fc)) {
scheduler.execute(() -> {
try (final HttpRequest request = new HttpRequest()) {
for (final String forwardUrl : fc.split(",")) {
if (StringUtils.isNotEmpty(forwardUrl)) {
final HttpResponse httpResponse = request.sendPostJsonCommand(forwardUrl, json);
if (httpResponse.getHttpCode() != HttpStatus.OK_200) {
logger.debug("Cannot forward event {} to {}: {}", json, forwardUrl,
httpResponse.getHttpCode());
}
}
}
}
});
}
}
interface Callback {
void post(String json);
}
}

View File

@@ -0,0 +1,170 @@
/**
* 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.neeo.internal.handler;
import java.util.Hashtable;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.binding.neeo.internal.discovery.NeeoDeviceDiscoveryService;
import org.openhab.binding.neeo.internal.discovery.NeeoRoomDiscoveryService;
import org.openhab.core.config.discovery.DiscoveryService;
import org.openhab.core.net.HttpServiceUtil;
import org.openhab.core.net.NetworkAddressService;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.http.HttpService;
/**
* The {@link NeeoHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.neeo")
public class NeeoHandlerFactory extends BaseThingHandlerFactory {
/** The {@link HttpService} used to register callbacks */
@NonNullByDefault({})
private HttpService httpService;
/** The {@link NetworkAddressService} used for ip lookup */
@NonNullByDefault({})
private NetworkAddressService networkAddressService;
/** The discovery services created by this class (one per room and one for each device) */
private final ConcurrentMap<ThingUID, ServiceRegistration<?>> discoveryServiceRegs = new ConcurrentHashMap<>();
/**
* Sets the {@link HttpService}.
*
* @param httpService the non-null {@link HttpService} to use
*/
@Reference
protected void setHttpService(HttpService httpService) {
Objects.requireNonNull(httpService, "httpService cannot be null");
this.httpService = httpService;
}
/**
* Unsets the {@link HttpService}
*
* @param httpService the {@link HttpService} (not used in this implementation)
*/
protected void unsetHttpService(HttpService httpService) {
this.httpService = null;
}
/**
* Sets the {@link NetworkAddressService}.
*
* @param networkAddressService the non-null {@link NetworkAddressService} to use
*/
@Reference
protected void setNetworkAddressService(NetworkAddressService networkAddressService) {
Objects.requireNonNull(networkAddressService, "networkAddressService cannot be null");
this.networkAddressService = networkAddressService;
}
/**
* Unsets the {@link NetworkAddressService}
*
* @param networkAddressService the {@link NetworkAddressService} (not used in this implementation)
*/
protected void unsetNetworkAddressService(NetworkAddressService networkAddressService) {
this.networkAddressService = null;
}
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
Objects.requireNonNull(thingTypeUID, "thingTypeUID cannot be null");
return NeeoConstants.BINDING_ID.equals(thingTypeUID.getBindingId());
}
@Nullable
@Override
protected ThingHandler createHandler(Thing thing) {
Objects.requireNonNull(thing, "thing cannot be null");
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(NeeoConstants.BRIDGE_TYPE_BRAIN)) {
final HttpService localHttpService = httpService;
final NetworkAddressService localNetworkAddressService = networkAddressService;
Objects.requireNonNull(localHttpService, "HttpService cannot be null");
Objects.requireNonNull(localNetworkAddressService, "networkAddressService cannot be null");
final int port = HttpServiceUtil.getHttpServicePort(this.bundleContext);
final NeeoBrainHandler handler = new NeeoBrainHandler((Bridge) thing,
port < 0 ? NeeoConstants.DEFAULT_BRAIN_HTTP_PORT : port, localHttpService,
localNetworkAddressService);
registerRoomDiscoveryService(handler);
return handler;
} else if (thingTypeUID.equals(NeeoConstants.BRIDGE_TYPE_ROOM)) {
final NeeoRoomHandler handler = new NeeoRoomHandler((Bridge) thing);
registerDeviceDiscoveryService(handler);
return handler;
} else if (thingTypeUID.equals(NeeoConstants.THING_TYPE_DEVICE)) {
return new NeeoDeviceHandler(thing);
}
return null;
}
/**
* Helper method to register the room discovery service
*
* @param handler a non-null brain handler
*/
private void registerRoomDiscoveryService(NeeoBrainHandler handler) {
Objects.requireNonNull(handler, "handler cannot be null");
final NeeoRoomDiscoveryService discoveryService = new NeeoRoomDiscoveryService(handler);
this.discoveryServiceRegs.put(handler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
/**
* Helper method to register the device discovery service
*
* @param handler a non-null room handler
*/
private void registerDeviceDiscoveryService(NeeoRoomHandler handler) {
Objects.requireNonNull(handler, "handler cannot be null");
final NeeoDeviceDiscoveryService discoveryService = new NeeoDeviceDiscoveryService(handler);
this.discoveryServiceRegs.put(handler.getThing().getUID(),
bundleContext.registerService(DiscoveryService.class.getName(), discoveryService, new Hashtable<>()));
}
@Override
protected void removeHandler(ThingHandler thingHandler) {
final ServiceRegistration<?> serviceReg = this.discoveryServiceRegs.remove(thingHandler.getThing().getUID());
if (serviceReg != null) {
serviceReg.unregister();
}
}
}

View File

@@ -0,0 +1,356 @@
/**
* 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.neeo.internal.handler;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.openhab.binding.neeo.internal.NeeoBrainApi;
import org.openhab.binding.neeo.internal.NeeoConstants;
import org.openhab.binding.neeo.internal.NeeoHandlerCallback;
import org.openhab.binding.neeo.internal.NeeoRoomConfig;
import org.openhab.binding.neeo.internal.NeeoRoomProtocol;
import org.openhab.binding.neeo.internal.NeeoUtil;
import org.openhab.binding.neeo.internal.UidUtils;
import org.openhab.binding.neeo.internal.models.NeeoAction;
import org.openhab.binding.neeo.internal.models.NeeoRoom;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.thing.Bridge;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.thing.ThingUID;
import org.openhab.core.thing.binding.BaseBridgeHandler;
import org.openhab.core.thing.binding.BridgeHandler;
import org.openhab.core.thing.binding.builder.ThingBuilder;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A subclass of {@link BaseBridgeHandler} that is responsible for handling commands for a room
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRoomHandler extends BaseBridgeHandler {
/** The logger */
private final Logger logger = LoggerFactory.getLogger(NeeoRoomHandler.class);
/**
* The initialization task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
*/
private final AtomicReference<@Nullable Future<?>> initializationTask = new AtomicReference<>();
/**
* The refresh task (null until set by {@link #initializeTask()} and set back to null in {@link #dispose()}
*/
private final AtomicReference<@Nullable Future<?>> refreshTask = new AtomicReference<>();
/** The {@link NeeoRoomProtocol} (null until set by {@link #initializationTask}) */
private final AtomicReference<@Nullable NeeoRoomProtocol> roomProtocol = new AtomicReference<>();
/**
* Instantiates a new neeo room handler.
*
* @param bridge the non-null bridge
*/
NeeoRoomHandler(Bridge bridge) {
super(bridge);
Objects.requireNonNull(bridge, "bridge cannot be null");
}
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
Objects.requireNonNull(channelUID, "channelUID cannot be null");
Objects.requireNonNull(command, "command cannot be null");
final NeeoRoomProtocol protocol = roomProtocol.get();
if (protocol == null) {
logger.debug("Protocol is null - ignoring update: {}", channelUID);
return;
}
final String[] channelIds = UidUtils.parseChannelId(channelUID);
if (channelIds.length == 0) {
logger.debug("Bad group declaration: {}", channelUID);
return;
}
final String localGroupId = channelUID.getGroupId();
final String groupId = localGroupId == null || StringUtils.isEmpty(localGroupId) ? "" : localGroupId;
final String channelId = channelIds[0];
final String channelKey = channelIds.length > 1 ? channelIds[1] : "";
if (command instanceof RefreshType) {
refreshChannel(protocol, groupId, channelKey, channelId);
} else {
switch (groupId) {
case NeeoConstants.ROOM_GROUP_RECIPE_ID:
switch (channelId) {
case NeeoConstants.ROOM_CHANNEL_STATUS:
// Ignore OFF status updates
if (command == OnOffType.ON) {
protocol.startRecipe(channelKey);
}
break;
}
break;
case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
switch (channelId) {
case NeeoConstants.ROOM_CHANNEL_STATUS:
if (command instanceof OnOffType) {
protocol.setScenarioStatus(channelKey, command == OnOffType.ON);
}
break;
}
break;
default:
logger.debug("Unknown channel to set: {}", channelUID);
break;
}
}
}
/**
* Refresh the specified channel section, key and id using the specified protocol
*
* @param protocol a non-null protocol to use
* @param groupId the non-empty channel section
* @param channelKey the non-empty channel key
* @param channelId the non-empty channel id
*/
private void refreshChannel(NeeoRoomProtocol protocol, String groupId, String channelKey, String channelId) {
Objects.requireNonNull(protocol, "protocol cannot be null");
NeeoUtil.requireNotEmpty(groupId, "groupId must not be empty");
NeeoUtil.requireNotEmpty(channelId, "channelId must not be empty");
switch (groupId) {
case NeeoConstants.ROOM_GROUP_RECIPE_ID:
NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
switch (channelId) {
case NeeoConstants.ROOM_CHANNEL_NAME:
protocol.refreshRecipeName(channelKey);
break;
case NeeoConstants.ROOM_CHANNEL_TYPE:
protocol.refreshRecipeType(channelKey);
break;
case NeeoConstants.ROOM_CHANNEL_ENABLED:
protocol.refreshRecipeEnabled(channelKey);
break;
case NeeoConstants.ROOM_CHANNEL_STATUS:
protocol.refreshRecipeStatus(channelKey);
break;
}
break;
case NeeoConstants.ROOM_GROUP_SCENARIO_ID:
NeeoUtil.requireNotEmpty(channelKey, "channelKey must not be empty");
switch (channelId) {
case NeeoConstants.ROOM_CHANNEL_NAME:
protocol.refreshScenarioName(channelKey);
break;
case NeeoConstants.ROOM_CHANNEL_CONFIGURED:
protocol.refreshScenarioConfigured(channelKey);
break;
case NeeoConstants.ROOM_CHANNEL_STATUS:
protocol.refreshScenarioStatus(channelKey);
break;
}
break;
}
}
@Override
public void initialize() {
NeeoUtil.cancel(initializationTask.getAndSet(scheduler.submit(() -> {
initializeTask();
})));
}
/**
* Initializes the task be creating the {@link NeeoRoomProtocol}, going online and then scheduling the refresh task.
*/
private void initializeTask() {
final NeeoRoomConfig config = getConfigAs(NeeoRoomConfig.class);
final String roomKey = config.getRoomKey();
if (roomKey == null || StringUtils.isEmpty(roomKey)) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"Room key (from the parent room bridge) was not found");
return;
}
try {
NeeoUtil.checkInterrupt();
final NeeoBrainApi brainApi = getNeeoBrainApi();
if (brainApi == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE, "Cannot find the NEEO Brain API");
return;
}
final NeeoRoom room = brainApi.getRoom(roomKey);
final ThingUID thingUid = getThing().getUID();
final Map<String, String> properties = new HashMap<>();
properties.put("Key", roomKey);
final ThingBuilder thingBuilder = editThing();
thingBuilder.withLabel(room.getName() + " (NEEO " + brainApi.getBrain().getKey() + ")")
.withProperties(properties).withChannels(ChannelUtils.generateChannels(thingUid, room));
updateThing(thingBuilder.build());
NeeoUtil.checkInterrupt();
final NeeoRoomProtocol protocol = new NeeoRoomProtocol(new NeeoHandlerCallback() {
@Override
public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
updateStatus(status, detail, msg);
}
@Override
public void stateChanged(String channelId, State state) {
updateState(channelId, state);
}
@Override
public void setProperty(String propertyName, String propertyValue) {
getThing().setProperty(propertyName, propertyValue);
}
@Override
public void scheduleTask(Runnable task, long milliSeconds) {
scheduler.schedule(task, milliSeconds, TimeUnit.MILLISECONDS);
}
@Override
public void triggerEvent(String channelID, String event) {
triggerChannel(channelID, event);
}
@Nullable
@Override
public NeeoBrainApi getApi() {
return getNeeoBrainApi();
}
}, roomKey);
roomProtocol.getAndSet(protocol);
NeeoUtil.checkInterrupt();
updateStatus(ThingStatus.ONLINE);
if (config.getRefreshPolling() > 0) {
NeeoUtil.checkInterrupt();
NeeoUtil.cancel(refreshTask.getAndSet(scheduler.scheduleWithFixedDelay(() -> {
try {
refreshState();
} catch (InterruptedException e) {
logger.debug("Refresh State was interrupted", e);
}
}, 0, config.getRefreshPolling(), TimeUnit.SECONDS)));
}
} catch (IOException e) {
logger.debug("IOException during initialization", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Room " + config.getRoomKey() + " couldn't be found");
} catch (InterruptedException e) {
logger.debug("Initialization was interrupted", e);
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_INITIALIZING_ERROR,
"Initialization was interrupted");
}
}
/**
* Processes the action if it applies to this room
*
* @param action a non-null action to process
*/
void processAction(NeeoAction action) {
Objects.requireNonNull(action, "action cannot be null");
final NeeoRoomProtocol protocol = roomProtocol.get();
if (protocol != null) {
protocol.processAction(action);
}
}
/**
* Refreshes the state of the room by calling {@link NeeoRoomProtocol#refreshState()}
*
* @throws InterruptedException if the call is interrupted
*/
private void refreshState() throws InterruptedException {
NeeoUtil.checkInterrupt();
final NeeoRoomProtocol protocol = roomProtocol.get();
if (protocol != null) {
NeeoUtil.checkInterrupt();
protocol.refreshState();
}
}
/**
* Helper method to return the {@link NeeoBrainHandler} associated with this handler
*
* @return a possibly null {@link NeeoBrainHandler}
*/
@Nullable
private NeeoBrainHandler getBrainHandler() {
final Bridge parent = getBridge();
if (parent != null) {
final BridgeHandler handler = parent.getHandler();
if (handler instanceof NeeoBrainHandler) {
return ((NeeoBrainHandler) handler);
}
}
return null;
}
/**
* Returns the {@link NeeoBrainApi} associated with this handler.
*
* @return the {@link NeeoBrainApi} or null if not found
*/
@Nullable
public NeeoBrainApi getNeeoBrainApi() {
final NeeoBrainHandler handler = getBrainHandler();
return handler == null ? null : handler.getNeeoBrainApi();
}
/**
* Returns the brain ID associated with this handler.
*
* @return the brain ID or null if not found
*/
@Nullable
public String getNeeoBrainId() {
final NeeoBrainHandler handler = getBrainHandler();
return handler == null ? null : handler.getNeeoBrainId();
}
@Override
public void dispose() {
NeeoUtil.cancel(initializationTask.getAndSet(null));
NeeoUtil.cancel(refreshTask.getAndSet(null));
roomProtocol.getAndSet(null);
}
}

View File

@@ -0,0 +1,58 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an error response (serialize/deserialize json use only)
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class ErrorResponse {
/** The error */
@Nullable
private String error;
/** The message */
@Nullable
private String message;
/**
* Gets the error.
*
* @return the error
*/
@Nullable
public String getError() {
return error;
}
/**
* Gets the message.
*
* @return the message
*/
@Nullable
public String getMessage() {
return message;
}
@Override
public String toString() {
return "ErrorResponse [error=" + error + ", message=" + message + "]";
}
}

View File

@@ -0,0 +1,98 @@
/**
* 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.neeo.internal.models;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an execute result (serialize/deserialize json use only)
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class ExecuteResult {
/** The estimated duration */
private int estimatedDuration;
/** The name */
@Nullable
private String name;
/** The start time */
private long startTime;
/** The steps */
private ExecuteStep @Nullable [] steps;
/** The type */
@Nullable
private String type;
/**
* Gets the estimated duration.
*
* @return the estimated duration
*/
public int getEstimatedDuration() {
return estimatedDuration;
}
/**
* Gets the name.
*
* @return the name
*/
@Nullable
public String getName() {
return name;
}
/**
* Gets the start time.
*
* @return the start time
*/
public long getStartTime() {
return startTime;
}
/**
* Gets the steps.
*
* @return the steps
*/
public ExecuteStep[] getSteps() {
final ExecuteStep @Nullable [] localSteps = steps;
return localSteps == null ? new ExecuteStep[0] : localSteps;
}
/**
* Gets the type.
*
* @return the type
*/
@Nullable
public String getType() {
return type;
}
@Override
public String toString() {
return "ExecuteResult [estimatedDuration=" + estimatedDuration + ", name=" + name + ", startTime=" + startTime
+ ", steps=" + Arrays.toString(steps) + ", type=" + type + "]";
}
}

View File

@@ -0,0 +1,56 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an execute setp (serialize/deserialize json use only)
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class ExecuteStep {
/** The duration of the step */
private int duration;
/** The text describing the step */
@Nullable
private String text;
/**
* Gets the duration of the step
*
* @return the duration
*/
public int getDuration() {
return duration;
}
/**
* Gets the text describing the step
*
* @return the text
*/
@Nullable
public String getText() {
return text;
}
@Override
public String toString() {
return "ExecuteStep [duration=" + duration + ", text=" + text + "]";
}
}

View File

@@ -0,0 +1,104 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.annotations.SerializedName;
/**
* The model representing an forward actions result (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*
*/
@NonNullByDefault
public class NeeoAction {
/** The action - can be null */
@Nullable
private String action;
/** The action parameter - generally null */
@Nullable
@SerializedName("actionparameter")
private String actionParameter;
/** The recipe name - only valid on launch of recipe */
@Nullable
private String recipe;
/** The device name - usually filled */
@Nullable
private String device;
/** The room name - usually filled */
@Nullable
private String room;
/**
* Returns the action
*
* @return a possibly null, possibly empty action
*/
@Nullable
public String getAction() {
return action;
}
/**
* Returns the action parameter
*
* @return a possibly null, possibly empty action parameter
*/
@Nullable
public String getActionParameter() {
return actionParameter;
}
/**
* Returns the recipe name
*
* @return a possibly null, possibly empty recipe name
*/
@Nullable
public String getRecipe() {
return recipe;
}
/**
* Returns the device name
*
* @return a possibly null, possibly empty device name
*/
@Nullable
public String getDevice() {
return device;
}
/**
* Returns the room name
*
* @return a possibly null, possibly room name
*/
@Nullable
public String getRoom() {
return room;
}
@Override
public String toString() {
return "NeeoAction [action=" + action + ", actionParameter=" + actionParameter + ", recipe=" + recipe
+ ", device=" + device + ", room=" + room + "]";
}
}

View File

@@ -0,0 +1,139 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an Neeo Brain(serialize/deserialize json use only)
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoBrain {
/** The brain name */
@Nullable
private String name;
/** The version of the brain */
@Nullable
private String version;
/** The brain's label */
@Nullable
private String label;
/** Whether the brain has been configured */
private boolean configured;
/** The brain key */
@Nullable
private String key;
/** ?? The brain airkey ?? */
@Nullable
private String airkey;
/** Last time the brain was changed */
private long lastchange;
/** The rooms in the brain */
@Nullable
private NeeoRooms rooms;
/**
* Gets the brain name
*
* @return the name
*/
@Nullable
public String getName() {
return name;
}
/**
* Gets the version of the brain
*
* @return the version
*/
@Nullable
public String getVersion() {
return version;
}
/**
* Gets the brain's label
*
* @return the label
*/
@Nullable
public String getLabel() {
return label;
}
/**
* Checks if the brain is configured
*
* @return true, if is configured
*/
public boolean isConfigured() {
return configured;
}
/**
* Gets the brain key
*
* @return the key
*/
@Nullable
public String getKey() {
return key;
}
/**
* Gets the brain's airkey
*
* @return the airkey
*/
@Nullable
public String getAirkey() {
return airkey;
}
/**
* Gets the last time the brain was changed
*
* @return the lastchange
*/
public long getLastChange() {
return lastchange;
}
/**
* Gets the rooms in the brain
*
* @return the rooms
*/
public NeeoRooms getRooms() {
final NeeoRooms localRooms = rooms;
return localRooms == null ? new NeeoRooms(new NeeoRoom[0]) : localRooms;
}
@Override
public String toString() {
return "NeeoBrain [name=" + name + ", version=" + version + ", label=" + label + ", configured=" + configured
+ ", key=" + key + ", airkey=" + airkey + ", lastchange=" + lastchange + ", rooms=" + rooms + "]";
}
}

View File

@@ -0,0 +1,129 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an NEEO Device (serialize/deserialize json use only)
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoDevice {
/** The device name */
@Nullable
private String name;
/** The associated room name */
@Nullable
private String roomName;
/** The associated room key */
@Nullable
private String roomKey;
/** The adapter device id */
@Nullable
private String adapterDeviceId;
/** The device key */
@Nullable
private String key;
/** The macros for the device */
@Nullable
private NeeoMacros macros;
/** The device details */
@Nullable
private NeeoDeviceDetails details;
/**
* Gets the device name
*
* @return the name
*/
@Nullable
public String getName() {
return name;
}
/**
* Gets the associated room name
*
* @return the room name
*/
@Nullable
public String getRoomName() {
return roomName;
}
/**
* Gets the associated room key
*
* @return the room key
*/
@Nullable
public String getRoomKey() {
return roomKey;
}
/**
* Gets the adapter device id
*
* @return the adapter device id
*/
@Nullable
public String getAdapterDeviceId() {
return adapterDeviceId;
}
/**
* Gets the macro key
*
* @return the key
*/
@Nullable
public String getKey() {
return key;
}
/**
* Gets the macros for the device
*
* @return the macros
*/
public NeeoMacros getMacros() {
final NeeoMacros localMacros = macros;
return localMacros == null ? new NeeoMacros(new NeeoMacro[0]) : localMacros;
}
/**
* Gets the details for the device
*
* @return the details
*/
@Nullable
public NeeoDeviceDetails getDetails() {
return details;
}
@Override
public String toString() {
return "NeeoDevice [name=" + name + ", roomName=" + roomName + ", roomKey=" + roomKey + ", adapterDeviceId="
+ adapterDeviceId + ", key=" + key + ", macros=" + macros + ", details=" + details + "]";
}
}

View File

@@ -0,0 +1,130 @@
/**
* 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.neeo.internal.models;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an Neeo Device Details (serialize/deserialize json use only)
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoDeviceDetails {
/** The source name (neeo-deviceadapter or sdk name) */
@Nullable
private String sourceName;
/** The adapter name (name given by source) */
@Nullable
private String adapterName;
/** The NEEO type */
@Nullable
private String type;
/** The manufacture */
@Nullable
private String manufacturer;
/** The name of the device given by source */
@Nullable
private String name;
/** The timings of the device */
@Nullable
private NeeoDeviceDetailsTiming timing;
/** The device capabilities */
private String @Nullable [] deviceCapabilities;
/**
* The device source name
*
* @return the device source name
*/
@Nullable
public String getSourceName() {
return sourceName;
}
/**
* The device adapter name (given by the source)
*
* @return the adapter name
*/
@Nullable
public String getAdapterName() {
return adapterName;
}
/**
* The NEEO device type
*
* @return the NEEO device type
*/
@Nullable
public String getType() {
return type;
}
/**
* The manufacturer of the device
*
* @return the manufacturer
*/
@Nullable
public String getManufacturer() {
return manufacturer;
}
/**
* The name of the device (given by the source)
*
* @return the device name
*/
@Nullable
public String getName() {
return name;
}
/**
* The device timing
*
* @return the timings
*/
@Nullable
public NeeoDeviceDetailsTiming getTiming() {
return timing;
}
/**
* The device capabilities
*
* @return the capabilities
*/
public String[] getDeviceCapabilities() {
final String[] localCapabilities = deviceCapabilities;
return localCapabilities == null ? new String[0] : localCapabilities;
}
@Override
public String toString() {
return "NeeoDeviceDetails [sourceName=" + sourceName + ", adapterName=" + adapterName + ", type=" + type
+ ", manufacturer=" + manufacturer + ", name=" + name + ", timing=" + timing + ", deviceCapabilities="
+ StringUtils.join(deviceCapabilities, ',') + "]";
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an Neeo Device Details Timings (serialize/deserialize json use only)
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoDeviceDetailsTiming {
/** Standby delay in ms (time to turn on device) */
@Nullable
private Integer standbyCommandDelay;
/** Source switch in ms (time to switch inputs) */
@Nullable
private Integer sourceSwitchDelay;
/** Shutdown delay in ms (time to shutdown device) */
@Nullable
private Integer shutdownDelay;
/**
* The time (in ms) to turn on device. May be null if not supported
*
* @return a possibly null time to turn on device
*/
@Nullable
public Integer getStandbyCommandDelay() {
return standbyCommandDelay;
}
/**
* The time (in ms) to switch inputs. May be null if not supported
*
* @return a possibly null time to switch inputs
*/
@Nullable
public Integer getSourceSwitchDelay() {
return sourceSwitchDelay;
}
/**
* The time (in ms) to shutdown device. May be null if not supported
*
* @return a possibly null time to shut down device
*/
@Nullable
public Integer getShutdownDelay() {
return shutdownDelay;
}
@Override
public String toString() {
return "NeeoDeviceDetailsTiming [standbyCommandDelay=" + standbyCommandDelay + ", sourceSwitchDelay="
+ sourceSwitchDelay + ", shutdownDelay=" + shutdownDelay + "]";
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.neeo.internal.models;
import java.util.Arrays;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing Neeo Devices (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoDevices {
/** The devices. */
private final NeeoDevice @Nullable [] devices;
/**
* Instantiates a new neeo devices.
*
* @param devices the devices
*/
NeeoDevices(NeeoDevice[] devices) {
Objects.requireNonNull(devices, "devices cannot be null");
this.devices = devices;
}
/**
* Gets the devices.
*
* @return the devices
*/
public NeeoDevice[] getDevices() {
final NeeoDevice[] localDevices = devices;
return localDevices == null ? new NeeoDevice[0] : localDevices;
}
/**
* Gets the device.
*
* @param key the key
* @return the device
*/
@Nullable
public NeeoDevice getDevice(String key) {
for (NeeoDevice device : getDevices()) {
if (StringUtils.equalsIgnoreCase(key, device.getKey())) {
return device;
}
}
return null;
}
@Override
public String toString() {
return "NeeoDevice [devices=" + Arrays.toString(devices) + "]";
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.neeo.internal.models;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoDevices} class
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoDevicesDeserializer implements JsonDeserializer<@Nullable NeeoDevices> {
@Nullable
@Override
public NeeoDevices deserialize(@Nullable JsonElement jsonElement, @Nullable Type type,
@Nullable JsonDeserializationContext context) throws JsonParseException {
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
Objects.requireNonNull(context, "context cannot be null");
if (jsonElement instanceof JsonObject) {
final List<NeeoDevice> scenarios = new ArrayList<>();
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
final NeeoDevice device = context.deserialize(entry.getValue(), NeeoDevice.class);
scenarios.add(device);
}
return new NeeoDevices(scenarios.toArray(new NeeoDevice[0]));
}
return null;
}
}

View File

@@ -0,0 +1,82 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an forward actions request (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoForwardActions {
/** The host to forward actions to */
@Nullable
private final String host;
/** The port to use */
private final int port;
/** The path the actions should go to */
@Nullable
private final String path;
/**
* Creates the forward actions from the given parms
*
* @param host the host name
* @param port the port
* @param path the path
*/
public NeeoForwardActions(String host, int port, String path) {
this.host = host;
this.port = port;
this.path = path;
}
/**
* Returns the host name to forward actions to
*
* @return the hostname
*/
@Nullable
public String getHost() {
return host;
}
/**
* Returns the port number
*
* @return the port number
*/
public int getPort() {
return port;
}
/**
* Returns the path to use
*
* @return the path
*/
@Nullable
public String getPath() {
return path;
}
@Override
public String toString() {
return "NeeoForwardActions [host=" + host + ", port=" + port + ", path=" + path + "]";
}
}

View File

@@ -0,0 +1,115 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an Neeo Macro (serialize/deserialize json use only)
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoMacro {
/** The macro key */
@Nullable
private String key;
/** The component type */
@Nullable
private String componentType;
/** The macro name */
@Nullable
private String name;
/** The macro label */
@Nullable
private String label;
/** The associated device name */
@Nullable
private String deviceName;
/** The associated room name */
@Nullable
private String roomName;
/**
* Gets the macro key
*
* @return the key
*/
@Nullable
public String getKey() {
return key;
}
/**
* Gets the component type
*
* @return the component type
*/
@Nullable
public String getComponentType() {
return componentType;
}
/**
* Gets the macro name
*
* @return the name
*/
@Nullable
public String getName() {
return name;
}
/**
* Gets the macro label
*
* @return the label
*/
@Nullable
public String getLabel() {
return label;
}
/**
* Gets the associated device name
*
* @return the device name
*/
@Nullable
public String getDeviceName() {
return deviceName;
}
/**
* Gets the associated room name
*
* @return the room name
*/
@Nullable
public String getRoomName() {
return roomName;
}
@Override
public String toString() {
return "NeeoMacro [key=" + key + ", componentType=" + componentType + ", name=" + name + ", label=" + label
+ ", deviceName=" + deviceName + ", roomName=" + roomName + "]";
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.neeo.internal.models;
import java.util.Arrays;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing Neeo Macros (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoMacros {
/** The macros. */
private final NeeoMacro @Nullable [] macros;
/**
* Instantiates a new neeo macros.
*
* @param macros the macros
*/
NeeoMacros(NeeoMacro[] macros) {
Objects.requireNonNull(macros, "macros cannot be null");
this.macros = macros;
}
/**
* Gets the macros.
*
* @return the macros
*/
public NeeoMacro[] getMacros() {
final NeeoMacro @Nullable [] localMacros = macros;
return localMacros == null ? new NeeoMacro[0] : localMacros;
}
/**
* Gets the macro.
*
* @param key the key
* @return the macro
*/
@Nullable
public NeeoMacro getMacro(String key) {
for (NeeoMacro macro : getMacros()) {
if (StringUtils.equalsIgnoreCase(key, macro.getKey())) {
return macro;
}
}
return null;
}
@Override
public String toString() {
return "NeeoMacro [macros=" + Arrays.toString(macros) + "]";
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.neeo.internal.models;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoMacros} class
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoMacrosDeserializer implements JsonDeserializer<@Nullable NeeoMacros> {
@Nullable
@Override
public NeeoMacros deserialize(@Nullable JsonElement jsonElement, @Nullable Type type,
@Nullable JsonDeserializationContext context) throws JsonParseException {
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
Objects.requireNonNull(context, "context cannot be null");
if (jsonElement instanceof JsonObject) {
final List<NeeoMacro> scenarios = new ArrayList<>();
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
final NeeoMacro macro = context.deserialize(entry.getValue(), NeeoMacro.class);
scenarios.add(macro);
}
return new NeeoMacros(scenarios.toArray(new NeeoMacro[0]));
}
return null;
}
}

View File

@@ -0,0 +1,175 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an Neeo Recipe (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRecipe {
/** Recipe type for launching the recipe */
public static final String LAUNCH = "launch";
/** Recipe type for powering off the recipe */
public static final String POWEROFF = "poweroff";
/** The recipe key */
@Nullable
private String key;
/** The type of recipe (generally launch/poweroff) */
@Nullable
private String type;
/** The name of the recipe */
@Nullable
private String name;
/** Whether the recipe is enabled */
private boolean enabled;
/** ?? whether the recipe is dirty ?? */
private boolean dirty;
// May be used in the future...
// private NeeoStep[] steps;
// private NeeoCondition[] conditions;
// private NeeoTrigger trigger;
/** The associated room key */
@Nullable
private String roomKey;
/** The associated room name. */
@Nullable
private String roomName;
/** The scenario key recipe is linked to */
@Nullable
private String scenarioKey;
/** Whether the recipe is hidden or not */
private boolean isHiddenRecipe;
/** ?? whether this is a custom recipe ?? */
private boolean isCustom;
/**
* Gets the recipe key
*
* @return the key
*/
@Nullable
public String getKey() {
return key;
}
/**
* Gets the recipe type
*
* @return the type
*/
@Nullable
public String getType() {
return type;
}
/**
* Gets the recipe name
*
* @return the name
*/
@Nullable
public String getName() {
return name;
}
/**
* Checks if the recipe is enabled
*
* @return true, if is enabled
*/
public boolean isEnabled() {
return enabled;
}
/**
* Checks if the recipe is dirty
*
* @return true, if is dirty
*/
public boolean isDirty() {
return dirty;
}
/**
* Gets the associated room key.
*
* @return the room key
*/
@Nullable
public String getRoomKey() {
return roomKey;
}
/**
* Gets the associated room name.
*
* @return the room name
*/
@Nullable
public String getRoomName() {
return roomName;
}
/**
* Gets the associated scenario key.
*
* @return the scenario key
*/
@Nullable
public String getScenarioKey() {
return scenarioKey;
}
/**
* Checks if the recipe is hidden
*
* @return true, if is hidden recipe
*/
public boolean isHiddenRecipe() {
return isHiddenRecipe;
}
/**
* Checks if its a custom recipe
*
* @return true, if is custom
*/
public boolean isCustom() {
return isCustom;
}
@Override
public String toString() {
return "NeeoRecipe [key=" + key + ", type=" + type + ", name=" + name + ", enabled=" + enabled + ", dirty="
+ dirty + ", roomKey=" + roomKey + ", roomName=" + roomName + ", scenarioKey=" + scenarioKey
+ ", isHiddenRecipe=" + isHiddenRecipe + ", isCustom=" + isCustom + "]";
}
}

View File

@@ -0,0 +1,119 @@
/**
* 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.neeo.internal.models;
import java.util.Arrays;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing Neeo Recipes (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRecipes {
/** The recipes. */
private NeeoRecipe @Nullable [] recipes;
/**
* Creates the recipes from the given recipes
*
* @param recipes the recipes
*/
NeeoRecipes(NeeoRecipe[] recipes) {
Objects.requireNonNull(recipes, "recipes cannot be null");
this.recipes = recipes;
}
/**
* Gets the recipes.
*
* @return the recipes
*/
public NeeoRecipe[] getRecipes() {
final NeeoRecipe[] localRecipes = recipes;
return localRecipes == null ? new NeeoRecipe[0] : localRecipes;
}
/**
* Gets the recipe by key
*
* @param key the key
* @return the recipe or null if none found
*/
@Nullable
public NeeoRecipe getRecipe(String key) {
if (recipes == null || StringUtils.isEmpty(key)) {
return null;
}
for (NeeoRecipe recipe : getRecipes()) {
if (StringUtils.equalsIgnoreCase(key, recipe.getKey())) {
return recipe;
}
}
return null;
}
/**
* Gets the recipe by a scenario key and recipe type
*
* @param key the key
* @param type the recipe type
* @return the recipe or null if none found
*/
@Nullable
public NeeoRecipe getRecipeByScenarioKey(String key, String type) {
if (recipes == null || StringUtils.isEmpty(key)) {
return null;
}
for (NeeoRecipe recipe : getRecipes()) {
if (StringUtils.equalsIgnoreCase(key, recipe.getScenarioKey())
&& StringUtils.equalsIgnoreCase(type, recipe.getType())) {
return recipe;
}
}
return null;
}
/**
* Gets the recipe by name
*
* @param name the recipe name
* @return the recipe or null if none found
*/
@Nullable
public NeeoRecipe getRecipeByName(String name) {
if (recipes == null || StringUtils.isEmpty(name)) {
return null;
}
for (NeeoRecipe recipe : getRecipes()) {
if (StringUtils.equalsIgnoreCase(name, recipe.getName())) {
return recipe;
}
}
return null;
}
@Override
public String toString() {
return "NeeoRecipes [recipes=" + Arrays.toString(recipes) + "]";
}
}

View File

@@ -0,0 +1,55 @@
/**
* 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.neeo.internal.models;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoRecipes} class
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRecipesDeserializer implements JsonDeserializer<@Nullable NeeoRecipes> {
@Nullable
@Override
public NeeoRecipes deserialize(@Nullable JsonElement jsonElement, @Nullable Type type,
@Nullable JsonDeserializationContext context) throws JsonParseException {
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
Objects.requireNonNull(context, "context cannot be null");
if (jsonElement instanceof JsonObject) {
final List<NeeoRecipe> recipes = new ArrayList<>();
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
final NeeoRecipe recipe = context.deserialize(entry.getValue(), NeeoRecipe.class);
recipes.add(recipe);
}
return new NeeoRecipes(recipes.toArray(new NeeoRecipe[0]));
}
return null;
}
}

View File

@@ -0,0 +1,101 @@
/**
* 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.neeo.internal.models;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an Neeo Room (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRoom {
/** The room name */
@Nullable
private String name;
/** The room key */
@Nullable
private String key;
/** The devices in the room */
@Nullable
private NeeoDevices devices;
/** The scenarios in the room */
@Nullable
private NeeoScenarios scenarios;
/** The recipes in the room */
@Nullable
private NeeoRecipes recipes;
/**
* Gets the room name
*
* @return the name
*/
@Nullable
public String getName() {
return name;
}
/**
* Gets the room key
*
* @return the key
*/
@Nullable
public String getKey() {
return key;
}
/**
* Gets the recipes in the room
*
* @return the recipes
*/
public NeeoRecipes getRecipes() {
final NeeoRecipes localRecipes = recipes;
return localRecipes == null ? new NeeoRecipes(new NeeoRecipe[0]) : localRecipes;
}
/**
* Gets the devices in the room
*
* @return the devices
*/
public NeeoDevices getDevices() {
final NeeoDevices localDevices = devices;
return localDevices == null ? new NeeoDevices(new NeeoDevice[0]) : localDevices;
}
/**
* Gets the scenarios in the room
*
* @return the scenarios
*/
public NeeoScenarios getScenarios() {
final NeeoScenarios localScenarios = scenarios;
return localScenarios == null ? new NeeoScenarios(new NeeoScenario[0]) : localScenarios;
}
@Override
public String toString() {
return "NeeoRoom [name=" + name + ", key=" + key + ", scenarios=" + scenarios + ", devices=" + devices
+ ", recipes=" + recipes + "]";
}
}

View File

@@ -0,0 +1,72 @@
/**
* 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.neeo.internal.models;
import java.util.Arrays;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing Neeo Rooms (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRooms {
/** The rooms. */
private final NeeoRoom @Nullable [] rooms;
/**
* Instantiates a new neeo rooms.
*
* @param rooms the rooms
*/
NeeoRooms(NeeoRoom[] rooms) {
Objects.requireNonNull(rooms, "rooms cannot be null");
this.rooms = rooms;
}
/**
* Gets the rooms.
*
* @return the rooms
*/
public NeeoRoom[] getRooms() {
final NeeoRoom @Nullable [] localRooms = rooms;
return localRooms == null ? new NeeoRoom[0] : localRooms;
}
/**
* Gets the room.
*
* @param key the key
* @return the room
*/
NeeoRoom getRoom(String key) {
for (NeeoRoom room : getRooms()) {
if (StringUtils.equalsIgnoreCase(key, room.getKey())) {
return room;
}
}
return new NeeoRoom();
}
@Override
public String toString() {
return "NeeoRooms [rooms=" + Arrays.toString(rooms) + "]";
}
}

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.neeo.internal.models;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoRooms} class
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoRoomsDeserializer implements JsonDeserializer<@Nullable NeeoRooms> {
@Nullable
@Override
public NeeoRooms deserialize(@Nullable JsonElement jsonElement, @Nullable Type type,
@Nullable JsonDeserializationContext context) throws JsonParseException {
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
Objects.requireNonNull(context, "context cannot be null");
if (jsonElement instanceof JsonObject) {
final List<NeeoRoom> recipes = new ArrayList<>();
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
final NeeoRoom room = context.deserialize(entry.getValue(), NeeoRoom.class);
recipes.add(room);
}
return new NeeoRooms(recipes.toArray(new NeeoRoom[0]));
}
return null;
}
}

View File

@@ -0,0 +1,149 @@
/**
* 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.neeo.internal.models;
import java.util.Arrays;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing an Neeo Scenario (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoScenario {
/** The scenario name */
@Nullable
private String name;
/** The main device key */
@Nullable
private String mainDeviceKey;
/** The volume device key */
@Nullable
private String volumeDeviceKey;
/** The scenario key */
@Nullable
private String key;
/** Whether the scenario is pending configuration */
private boolean configured;
/** The associated room key */
@Nullable
private String roomKey;
/** The associated room name */
@Nullable
private String roomName;
/** The devices that make up the scenario */
private String @Nullable [] devices;
// may be used in the future
// private final NeeoShortcut[] shortcuts;
// @Nullable private String[] deviceInputMacroNames;
// private final NeeoCapability[] capabilities;
/**
* Gets the scenario name
*
* @return the name
*/
@Nullable
public String getName() {
return name;
}
/**
* Gets the main device key
*
* @return the main device key
*/
@Nullable
public String getMainDeviceKey() {
return mainDeviceKey;
}
/**
* Gets the volume device key
*
* @return the volume device key
*/
@Nullable
public String getVolumeDeviceKey() {
return volumeDeviceKey;
}
/**
* Gets the scenario key
*
* @return the key
*/
@Nullable
public String getKey() {
return key;
}
/**
* Checks if the scenario is pending configuration
*
* @return true, if is configured
*/
public boolean isConfigured() {
return configured;
}
/**
* Gets the associated room key
*
* @return the room key
*/
@Nullable
public String getRoomKey() {
return roomKey;
}
/**
* Gets the associated room name
*
* @return the room name
*/
@Nullable
public String getRoomName() {
return roomName;
}
/**
* Gets the devices
*
* @return the devices
*/
@Nullable
public String[] getDevices() {
final String @Nullable [] localDevices = devices;
return localDevices == null ? new String[0] : localDevices;
}
@Override
public String toString() {
return "NeeoScenario [name=" + name + ", mainDeviceKey=" + mainDeviceKey + ", volumeDeviceKey="
+ volumeDeviceKey + ", key=" + key + ", configured=" + configured + ", roomKey=" + roomKey
+ ", roomName=" + roomName + ", devices=" + Arrays.toString(devices) + "]";
}
}

View File

@@ -0,0 +1,73 @@
/**
* 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.neeo.internal.models;
import java.util.Arrays;
import java.util.Objects;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model representing Neeo Scenarios (serialize/deserialize json use only).
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoScenarios {
/** The scenarios. */
private final NeeoScenario @Nullable [] scenarios;
/**
* Instantiates a new neeo scenarios.
*
* @param scenarios the scenarios
*/
NeeoScenarios(NeeoScenario[] scenarios) {
Objects.requireNonNull(scenarios, "scenarios cannot be null");
this.scenarios = scenarios;
}
/**
* Gets the scenarios.
*
* @return the scenarios
*/
public NeeoScenario[] getScenarios() {
final NeeoScenario @Nullable [] localScenarios = scenarios;
return localScenarios == null ? new NeeoScenario[0] : localScenarios;
}
/**
* Gets the scenario.
*
* @param key the key
* @return the scenario
*/
@Nullable
public NeeoScenario getScenario(String key) {
for (NeeoScenario scenario : getScenarios()) {
if (StringUtils.equalsIgnoreCase(key, scenario.getKey())) {
return scenario;
}
}
return null;
}
@Override
public String toString() {
return "NeeoScenarios [scenarios=" + Arrays.toString(scenarios) + "]";
}
}

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.neeo.internal.models;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
/**
* The implementation of {@link JsonDeserializer} to deserialize a {@link NeeoScenarios} class
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class NeeoScenariosDeserializer implements JsonDeserializer<@Nullable NeeoScenarios> {
@Nullable
@Override
public NeeoScenarios deserialize(@Nullable JsonElement jsonElement, @Nullable Type jtype,
@Nullable JsonDeserializationContext context) throws JsonParseException {
Objects.requireNonNull(jsonElement, "jsonElement cannot be null");
Objects.requireNonNull(context, "context cannot be null");
if (jsonElement instanceof JsonObject) {
final List<NeeoScenario> scenarios = new ArrayList<>();
for (Map.Entry<String, JsonElement> entry : ((JsonObject) jsonElement).entrySet()) {
final NeeoScenario scenario = context.deserialize(entry.getValue(), NeeoScenario.class);
scenarios.add(scenario);
}
return new NeeoScenarios(scenarios.toArray(new NeeoScenario[0]));
}
return null;
}
}

View File

@@ -0,0 +1,113 @@
/**
* 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.neeo.internal.net;
import java.io.IOException;
import java.util.Objects;
import javax.ws.rs.ProcessingException;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation.Builder;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jetty.http.HttpStatus;
import org.glassfish.jersey.filter.LoggingFilter;
import org.openhab.binding.neeo.internal.NeeoUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This class represents an HTTP session with a client
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class HttpRequest implements AutoCloseable {
/** the logger */
private final Logger logger = LoggerFactory.getLogger(HttpRequest.class);
/** The client to use */
private final Client client;
/**
* Instantiates a new request
*/
public HttpRequest() {
client = ClientBuilder.newClient();
if (logger.isDebugEnabled()) {
client.register(new LoggingFilter(new Slf4LoggingAdapter(logger), true));
}
}
/**
* Send a get command to the specified uri
*
* @param uri the non-null uri
* @return the {@link HttpResponse}
*/
public HttpResponse sendGetCommand(String uri) {
NeeoUtil.requireNotEmpty(uri, "uri cannot be empty");
try {
final Builder request = client.target(uri).request();
final Response content = request.get();
try {
return new HttpResponse(content);
} finally {
content.close();
}
} catch (IOException | IllegalStateException e) {
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage());
}
}
/**
* Send post JSON command using the body
*
* @param uri the non empty uri
* @param body the non-null, possibly empty body
* @return the {@link HttpResponse}
*/
public HttpResponse sendPostJsonCommand(String uri, String body) {
NeeoUtil.requireNotEmpty(uri, "uri cannot be empty");
Objects.requireNonNull(body, "body cannot be null");
try {
final Builder request = client.target(uri).request(MediaType.APPLICATION_JSON);
final Response content = request.post(Entity.entity(body, MediaType.APPLICATION_JSON));
try {
return new HttpResponse(content);
} finally {
content.close();
}
// IllegalArgumentException/ProcessingException catches issues with the URI being invalid
// as well
} catch (IOException | IllegalStateException | IllegalArgumentException | ProcessingException e) {
return new HttpResponse(HttpStatus.SERVICE_UNAVAILABLE_503, e.getMessage());
}
}
@Override
public void close() {
client.close();
}
}

View File

@@ -0,0 +1,120 @@
/**
* 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.neeo.internal.net;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import javax.ws.rs.core.Response;
import org.apache.commons.io.IOUtils;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* This class represents an {@link HttpRequest} response
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class HttpResponse {
/** The http status */
private final int httpStatus;
/** The http reason */
private final String httpReason;
/** The http headers */
private final Map<String, String> headers = new HashMap<>();
/** The contents as a raw byte array */
private final byte @Nullable [] contents;
/**
* Instantiates a new http response from the {@link Response}.
*
* @param response the non-null response
* @throws IOException Signals that an I/O exception has occurred.
*/
HttpResponse(Response response) throws IOException {
Objects.requireNonNull(response, "response cannot be null");
httpStatus = response.getStatus();
httpReason = response.getStatusInfo().getReasonPhrase();
if (response.hasEntity()) {
InputStream is = response.readEntity(InputStream.class);
contents = IOUtils.toByteArray(is);
} else {
contents = null;
}
for (String key : response.getHeaders().keySet()) {
headers.put(key, response.getHeaderString(key));
}
}
/**
* Instantiates a new http response.
*
* @param httpCode the http code
* @param msg the msg
*/
HttpResponse(int httpCode, String msg) {
httpStatus = httpCode;
httpReason = msg;
contents = null;
}
/**
* Gets the HTTP status code.
*
* @return the HTTP status code
*/
public int getHttpCode() {
return httpStatus;
}
/**
* Gets the content.
*
* @return the content
*/
public String getContent() {
final byte[] localContents = contents;
if (localContents == null || localContents.length == 0) {
return "";
}
return new String(localContents, StandardCharsets.UTF_8);
}
/**
* Creates an {@link IOException} from the {@link #httpReason}
*
* @return the IO exception
*/
public IOException createException() {
return new IOException(httpReason);
}
@Override
public String toString() {
return getHttpCode() + " (" + (contents == null ? ("http reason: " + httpReason) : getContent()) + ")";
}
}

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.neeo.internal.net;
import java.util.Objects;
import java.util.logging.LogRecord;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.slf4j.Logger;
/**
* Logging adapter to use for Slf4j
*
* @author Tim Roberts - Initial Contribution
*/
@NonNullByDefault
class Slf4LoggingAdapter extends java.util.logging.Logger {
/** The logger. */
private final Logger logger;
/**
* Creates the logging adapter from the given logger
*
* @param logger a non-null logger to use
*/
protected Slf4LoggingAdapter(Logger logger) {
super("jersey", null);
Objects.requireNonNull(logger, "logger cannot be null");
this.logger = logger;
}
@Override
public void log(@Nullable LogRecord record) {
logger.debug("{}", record == null ? "" : record.getMessage());
}
}

View File

@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="neeo" 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>Neeo Binding</name>
<description>This binding allows you to discover NEEO Brain(s), discover the Rooms within and the Devices within those
Rooms. The binding then will expose that information to openHAB and allow you to execute Recipes/Scenarios and Macros
on the Devices</description>
<author>Tim Roberts</author>
</binding:binding>

View File

@@ -0,0 +1,184 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="neeo"
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">
<!-- The brain bridge -->
<bridge-type id="brain">
<label>Neeo Brain</label>
<description>The NEEO Brain</description>
<channels>
<channel id="forwardActions" typeId="brain-forwardactions"/>
</channels>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<context>network-address</context>
<label>IP or Host Name</label>
<description>The IP or host name of the NEEO Brain</description>
</parameter>
<parameter name="discoverEmptyRooms" type="boolean">
<label>Discover Empty Rooms</label>
<description>Whether to discover rooms with no devices or not</description>
<default>false</default>
</parameter>
<parameter name="enableForwardActions" type="boolean">
<label>Enable Forward Actions</label>
<description>Whether to have the NEEO Brain forward actions to openHAB</description>
<default>true</default>
</parameter>
<parameter name="forwardChain" type="text">
<label>Forward Chaining</label>
<description>Comma delimited list of URLs to forward NEEO brain actions to</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="checkStatusInterval" type="integer">
<label>Check Status Interval</label>
<description>The interval (in seconds) to check the status of the brain (specify &lt;=0 to disable)</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<bridge-type id="room">
<supported-bridge-type-refs>
<bridge-type-ref id="brain"/>
</supported-bridge-type-refs>
<label>Neeo Room</label>
<description>Neeo Room</description>
<channel-groups>
<channel-group id="state" typeId="room-state"/>
<channel-group id="scenario" typeId="room-scenario"/>
<channel-group id="recipe" typeId="room-recipe"/>
</channel-groups>
<config-description>
<parameter name="roomKey" type="text" required="true">
<label>Room Key</label>
<description>Unique key of the room</description>
</parameter>
<parameter name="excludeThings" type="boolean">
<label>Exclude Things</label>
<description>Exclude openHAB things (from NEEO transport)</description>
<default>true</default>
<advanced>true</advanced>
</parameter>
<parameter name="refreshPolling" type="integer">
<label>Refresh Polling</label>
<description>The time (in seconds) to refresh state (&lt;= 0 to disable)</description>
<default>120</default>
<advanced>true</advanced>
</parameter>
</config-description>
</bridge-type>
<thing-type id="device">
<supported-bridge-type-refs>
<bridge-type-ref id="room"/>
</supported-bridge-type-refs>
<label>Neeo Device</label>
<description>Neeo Device</description>
<channel-groups>
<channel-group id="macros" typeId="device-macros"/>
</channel-groups>
<config-description>
<parameter name="deviceKey" type="text" required="true">
<label>Device Key</label>
<description>Unique key of the device</description>
</parameter>
</config-description>
</thing-type>
<!-- Channel types for the NEEO Brain -->
<channel-type id="brain-forwardactions">
<kind>trigger</kind>
<label>Forward Actions</label>
<description>The forward Actions</description>
<event>
</event>
</channel-type>
<!-- Channel Types for the NEEO Room -->
<channel-group-type id="room-state">
<label>Room State</label>
<description>The room's state</description>
<channels>
<channel id="currentStep" typeId="room-state-currentstep"/>
</channels>
</channel-group-type>
<channel-type id="room-state-currentstep">
<kind>trigger</kind>
<label>Current Step</label>
<description>The current step executing</description>
<event>
</event>
</channel-type>
<channel-group-type id="room-recipe">
<label>Recipes</label>
<description>The room recipes</description>
<channels>
<channel id="name" typeId="room-recipe-name"/>
<channel id="type" typeId="room-recipe-type"/>
<channel id="enabled" typeId="room-recipe-enabled"/>
<channel id="status" typeId="room-recipe-status"/>
</channels>
</channel-group-type>
<channel-type id="room-recipe-name" advanced="true">
<item-type>String</item-type>
<label>Label Dynamically Generated</label>
<description>The recipe name</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="room-recipe-type" advanced="true">
<item-type>String</item-type>
<label>Label Dynamically Generated</label>
<description>The type of recipe</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="room-recipe-enabled" advanced="true">
<item-type>Switch</item-type>
<label>Label Dynamically Generated</label>
<description>Whether the recipe is enabled or not.</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="room-recipe-status">
<item-type>Switch</item-type>
<label>Label Dynamically Generated</label>
<description>Send ON to execute the recipe</description>
</channel-type>
<channel-group-type id="room-scenario">
<label>Scenarios</label>
<description>The room scenarios</description>
<channels>
<channel id="name" typeId="room-scenario-name"/>
<channel id="configured" typeId="room-scenario-configured"/>
<channel id="status" typeId="room-scenario-status"/>
</channels>
</channel-group-type>
<channel-type id="room-scenario-name" advanced="true">
<item-type>String</item-type>
<label>Label Dynamically Generated</label>
<description>The scenario name</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="room-scenario-configured" advanced="true">
<item-type>Switch</item-type>
<label>Label Dynamically Generated</label>
<description>Whether the scenario is configured or not</description>
<state readOnly="true"/>
</channel-type>
<channel-type id="room-scenario-status">
<item-type>Switch</item-type>
<label>Label Dynamically Generated</label>
<description>Whether the scenario is running or not (send ON to turn on the scenario, OFF to turn off the scenario)</description>
</channel-type>
<channel-group-type id="device-macros">
<label>Macros</label>
<description>Executable macros</description>
<channels>
<channel id="status" typeId="device-macros-status"/>
</channels>
</channel-group-type>
<channel-type id="device-macros-status">
<item-type>Switch</item-type>
<label>Label Dynamically Generated</label>
<description>Send ON to trigger the macro</description>
</channel-type>
</thing:thing-descriptions>