2 * Copyright (c) 2010-2023 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
7 * This program and the accompanying materials are made available under the
8 * terms of the Eclipse Public License 2.0 which is available at
9 * http://www.eclipse.org/legal/epl-2.0
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.io.hueemulation.internal.rest;
16 import java.util.stream.Collectors;
17 import java.util.stream.Stream;
19 import javax.ws.rs.Consumes;
20 import javax.ws.rs.DELETE;
21 import javax.ws.rs.GET;
22 import javax.ws.rs.POST;
23 import javax.ws.rs.PUT;
24 import javax.ws.rs.Path;
25 import javax.ws.rs.PathParam;
26 import javax.ws.rs.Produces;
27 import javax.ws.rs.core.Context;
28 import javax.ws.rs.core.MediaType;
29 import javax.ws.rs.core.Response;
30 import javax.ws.rs.core.UriInfo;
32 import org.eclipse.jdt.annotation.NonNullByDefault;
33 import org.openhab.core.common.registry.RegistryChangeListener;
34 import org.openhab.core.items.GenericItem;
35 import org.openhab.core.items.Item;
36 import org.openhab.core.items.ItemRegistry;
37 import org.openhab.core.library.CoreItemFactory;
38 import org.openhab.io.hueemulation.internal.ConfigStore;
39 import org.openhab.io.hueemulation.internal.HueEmulationService;
40 import org.openhab.io.hueemulation.internal.NetworkUtils;
41 import org.openhab.io.hueemulation.internal.dto.HueNewLights;
42 import org.openhab.io.hueemulation.internal.dto.HueSensorEntry;
43 import org.openhab.io.hueemulation.internal.dto.changerequest.HueChangeRequest;
44 import org.openhab.io.hueemulation.internal.dto.response.HueResponse;
45 import org.osgi.service.component.annotations.Activate;
46 import org.osgi.service.component.annotations.Component;
47 import org.osgi.service.component.annotations.Deactivate;
48 import org.osgi.service.component.annotations.Reference;
49 import org.osgi.service.jaxrs.whiteboard.JaxrsWhiteboardConstants;
50 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsApplicationSelect;
51 import org.osgi.service.jaxrs.whiteboard.propertytypes.JaxrsResource;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
55 import io.swagger.v3.oas.annotations.Operation;
56 import io.swagger.v3.oas.annotations.Parameter;
57 import io.swagger.v3.oas.annotations.responses.ApiResponse;
60 * Listens to the ItemRegistry and add all DecimalType, OnOffType, ContactType, DimmerType items
63 * @author David Graeff - Initial contribution
65 @Component(immediate = false, service = Sensors.class)
67 @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + HueEmulationService.REST_APP_NAME + ")")
70 @Produces(MediaType.APPLICATION_JSON)
71 @Consumes(MediaType.APPLICATION_JSON)
72 public class Sensors implements RegistryChangeListener<Item> {
73 private final Logger logger = LoggerFactory.getLogger(Sensors.class);
74 private static final Set<String> ALLOWED_ITEM_TYPES = Stream.of(CoreItemFactory.COLOR, CoreItemFactory.DIMMER,
75 CoreItemFactory.ROLLERSHUTTER, CoreItemFactory.SWITCH, CoreItemFactory.CONTACT, CoreItemFactory.NUMBER)
76 .collect(Collectors.toSet());
79 protected @NonNullByDefault({}) ConfigStore cs;
81 protected @NonNullByDefault({}) UserManagement userManagement;
83 protected @NonNullByDefault({}) ItemRegistry itemRegistry;
86 * Registers to the {@link ItemRegistry} and enumerates currently existing items.
87 * Call {@link #close(ItemRegistry)} when you are done with this object.
89 * Only call this after you have set the filter tags with {@link #setFilterTags(Set, Set, Set)}.
92 protected void activate() {
95 itemRegistry.removeRegistryChangeListener(this);
96 itemRegistry.addRegistryChangeListener(this);
98 for (Item item : itemRegistry.getItems()) {
101 logger.debug("Added as sensor: {}",
102 cs.ds.sensors.values().stream().map(l -> l.name).collect(Collectors.joining(", ")));
106 * Unregisters from the {@link ItemRegistry}.
109 protected void deactivate() {
110 itemRegistry.removeRegistryChangeListener(this);
114 public synchronized void added(Item newElement) {
115 if (!(newElement instanceof GenericItem)) {
118 GenericItem element = (GenericItem) newElement;
120 if (!ALLOWED_ITEM_TYPES.contains(element.getType())) {
124 String hueID = cs.mapItemUIDtoHueID(element);
126 HueSensorEntry sensor = new HueSensorEntry(element);
127 cs.ds.sensors.put(hueID, sensor);
131 public synchronized void removed(Item element) {
132 String hueID = cs.mapItemUIDtoHueID(element);
133 logger.debug("Remove item {}", hueID);
134 cs.ds.sensors.remove(hueID);
138 public synchronized void updated(Item oldElement, Item newElement) {
139 if (!(newElement instanceof GenericItem)) {
142 GenericItem element = (GenericItem) newElement;
144 String hueID = cs.mapItemUIDtoHueID(element);
146 HueSensorEntry sensor = new HueSensorEntry(element);
147 cs.ds.sensors.put(hueID, sensor);
151 @Path("{username}/sensors")
152 @Operation(summary = "Return all sensors", responses = { @ApiResponse(responseCode = "200", description = "OK") })
153 public Response getAllSensorsApi(@Context UriInfo uri,
154 @PathParam("username") @Parameter(description = "username") String username) {
155 if (!userManagement.authorizeUser(username)) {
156 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
158 return Response.ok(cs.gson.toJson(cs.ds.sensors)).build();
162 @Path("{username}/sensors/new")
163 @Operation(summary = "Return new sensors since last scan. Returns an empty list for openHAB as we do not cache that information.", responses = {
164 @ApiResponse(responseCode = "200", description = "OK") })
165 public Response getNewSensors(@Context UriInfo uri,
166 @PathParam("username") @Parameter(description = "username") String username) {
167 if (!userManagement.authorizeUser(username)) {
168 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
170 return Response.ok(cs.gson.toJson(new HueNewLights())).build();
174 @Path("{username}/sensors")
175 @Operation(summary = "Starts a new scan for compatible items. This is usually not necessary, because we are observing the item registry.", responses = {
176 @ApiResponse(responseCode = "200", description = "OK") })
177 public Response postNewLights(@Context UriInfo uri,
178 @PathParam("username") @Parameter(description = "username") String username) {
179 if (!userManagement.authorizeUser(username)) {
180 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
182 return NetworkUtils.singleSuccess(cs.gson, "Searching for new sensors", "/sensors");
186 @Path("{username}/sensors/{id}")
187 @Operation(summary = "Return a sensor", responses = { @ApiResponse(responseCode = "200", description = "OK") })
188 public Response getSensorApi(@Context UriInfo uri, //
189 @PathParam("username") @Parameter(description = "username") String username,
190 @PathParam("id") @Parameter(description = "sensor id") String id) {
191 if (!userManagement.authorizeUser(username)) {
192 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
194 return Response.ok(cs.gson.toJson(cs.ds.sensors.get(id))).build();
197 @SuppressWarnings({ "null", "unused" })
199 @Path("{username}/sensors/{id}/config")
200 @Operation(summary = "Return a sensor config. Always empty", responses = {
201 @ApiResponse(responseCode = "200", description = "OK") })
202 public Response getSensorConfigApi(@Context UriInfo uri, //
203 @PathParam("username") @Parameter(description = "username") String username,
204 @PathParam("id") @Parameter(description = "sensor id") String id) {
205 if (!userManagement.authorizeUser(username)) {
206 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
209 HueSensorEntry sensor = cs.ds.sensors.get(id);
210 if (sensor == null) {
211 return NetworkUtils.singleError(cs.gson, uri, HueResponse.NOT_AVAILABLE, "Sensor does not exist");
214 return Response.ok(cs.gson.toJson(sensor.config)).build();
217 @SuppressWarnings({ "null", "unused" })
219 @Path("{username}/sensors/{id}")
220 @Operation(summary = "Deletes the sensor that is represented by this id", responses = {
221 @ApiResponse(responseCode = "200", description = "The item got removed"),
222 @ApiResponse(responseCode = "403", description = "Access denied") })
223 public Response removeSensorAPI(@Context UriInfo uri,
224 @PathParam("username") @Parameter(description = "username") String username,
225 @PathParam("id") @Parameter(description = "id") String id) {
226 if (!userManagement.authorizeUser(username)) {
227 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
230 HueSensorEntry sensor = cs.ds.sensors.get(id);
231 if (sensor == null) {
232 return NetworkUtils.singleError(cs.gson, uri, HueResponse.NOT_AVAILABLE, "Sensor does not exist");
235 if (itemRegistry.remove(id) != null) {
236 return NetworkUtils.singleSuccess(cs.gson, "/sensors/" + id + " deleted.");
238 return NetworkUtils.singleError(cs.gson, uri, HueResponse.NOT_AVAILABLE, "Sensor does not exist");
242 @SuppressWarnings({ "null", "unused" })
244 @Path("{username}/sensors/{id}")
245 @Operation(summary = "Rename a sensor", responses = { @ApiResponse(responseCode = "200", description = "OK") })
246 public Response renameLightApi(@Context UriInfo uri, //
247 @PathParam("username") @Parameter(description = "username") String username,
248 @PathParam("id") @Parameter(description = "light id") String id, String body) {
249 if (!userManagement.authorizeUser(username)) {
250 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
252 HueSensorEntry sensor = cs.ds.sensors.get(id);
253 if (sensor == null) {
254 return NetworkUtils.singleError(cs.gson, uri, HueResponse.NOT_AVAILABLE, "Sensor not existing");
257 final HueChangeRequest changeRequest = cs.gson.fromJson(body, HueChangeRequest.class);
259 String name = changeRequest.name;
260 if (name == null || name.isEmpty()) {
261 return NetworkUtils.singleError(cs.gson, uri, HueResponse.INVALID_JSON, "Invalid request: No name set");
264 sensor.item.setLabel(name);
265 itemRegistry.update(sensor.item);
267 return NetworkUtils.singleSuccess(cs.gson, name, "/sensors/" + id + "/name");
271 @Path("{username}/sensors/{id}/state")
272 @Operation(summary = "Set sensor state", responses = { @ApiResponse(responseCode = "200", description = "OK") })
273 public Response setSensorStateApi(@Context UriInfo uri, //
274 @PathParam("username") @Parameter(description = "username") String username,
275 @PathParam("id") @Parameter(description = "sensor id") String id, String body) {
276 if (!userManagement.authorizeUser(username)) {
277 return NetworkUtils.singleError(cs.gson, uri, HueResponse.UNAUTHORIZED, "Not Authorized");
280 return NetworkUtils.singleError(cs.gson, uri, HueResponse.SENSOR_NOT_CLIP_SENSOR,
281 "Invalid request: Not a clip sensor");