]> git.basschouten.com Git - openhab-addons.git/blob
638a3fd86ea939082be12b8fb28630b200514ce8
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.io.hueemulation.internal.rest;
14
15 import java.util.Set;
16 import java.util.stream.Collectors;
17 import java.util.stream.Stream;
18
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;
31
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;
54
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;
58
59 /**
60  * Listens to the ItemRegistry and add all DecimalType, OnOffType, ContactType, DimmerType items
61  * as sensors.
62  *
63  * @author David Graeff - Initial contribution
64  */
65 @Component(immediate = false, service = Sensors.class)
66 @JaxrsResource
67 @JaxrsApplicationSelect("(" + JaxrsWhiteboardConstants.JAX_RS_NAME + "=" + HueEmulationService.REST_APP_NAME + ")")
68 @NonNullByDefault
69 @Path("")
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());
77
78     @Reference
79     protected @NonNullByDefault({}) ConfigStore cs;
80     @Reference
81     protected @NonNullByDefault({}) UserManagement userManagement;
82     @Reference
83     protected @NonNullByDefault({}) ItemRegistry itemRegistry;
84
85     /**
86      * Registers to the {@link ItemRegistry} and enumerates currently existing items.
87      * Call {@link #close(ItemRegistry)} when you are done with this object.
88      *
89      * Only call this after you have set the filter tags with {@link #setFilterTags(Set, Set, Set)}.
90      */
91     @Activate
92     protected void activate() {
93         cs.ds.resetSensors();
94
95         itemRegistry.removeRegistryChangeListener(this);
96         itemRegistry.addRegistryChangeListener(this);
97
98         for (Item item : itemRegistry.getItems()) {
99             added(item);
100         }
101         logger.debug("Added as sensor: {}",
102                 cs.ds.sensors.values().stream().map(l -> l.name).collect(Collectors.joining(", ")));
103     }
104
105     /**
106      * Unregisters from the {@link ItemRegistry}.
107      */
108     @Deactivate
109     protected void deactivate() {
110         itemRegistry.removeRegistryChangeListener(this);
111     }
112
113     @Override
114     public synchronized void added(Item newElement) {
115         if (!(newElement instanceof GenericItem)) {
116             return;
117         }
118         GenericItem element = (GenericItem) newElement;
119
120         if (!ALLOWED_ITEM_TYPES.contains(element.getType())) {
121             return;
122         }
123
124         String hueID = cs.mapItemUIDtoHueID(element);
125
126         HueSensorEntry sensor = new HueSensorEntry(element);
127         cs.ds.sensors.put(hueID, sensor);
128     }
129
130     @Override
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);
135     }
136
137     @Override
138     public synchronized void updated(Item oldElement, Item newElement) {
139         if (!(newElement instanceof GenericItem)) {
140             return;
141         }
142         GenericItem element = (GenericItem) newElement;
143
144         String hueID = cs.mapItemUIDtoHueID(element);
145
146         HueSensorEntry sensor = new HueSensorEntry(element);
147         cs.ds.sensors.put(hueID, sensor);
148     }
149
150     @GET
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");
157         }
158         return Response.ok(cs.gson.toJson(cs.ds.sensors)).build();
159     }
160
161     @GET
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");
169         }
170         return Response.ok(cs.gson.toJson(new HueNewLights())).build();
171     }
172
173     @POST
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");
181         }
182         return NetworkUtils.singleSuccess(cs.gson, "Searching for new sensors", "/sensors");
183     }
184
185     @GET
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");
193         }
194         return Response.ok(cs.gson.toJson(cs.ds.sensors.get(id))).build();
195     }
196
197     @SuppressWarnings({ "null", "unused" })
198     @GET
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");
207         }
208
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");
212         }
213
214         return Response.ok(cs.gson.toJson(sensor.config)).build();
215     }
216
217     @SuppressWarnings({ "null", "unused" })
218     @DELETE
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");
228         }
229
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");
233         }
234
235         if (itemRegistry.remove(id) != null) {
236             return NetworkUtils.singleSuccess(cs.gson, "/sensors/" + id + " deleted.");
237         } else {
238             return NetworkUtils.singleError(cs.gson, uri, HueResponse.NOT_AVAILABLE, "Sensor does not exist");
239         }
240     }
241
242     @SuppressWarnings({ "null", "unused" })
243     @PUT
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");
251         }
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");
255         }
256
257         final HueChangeRequest changeRequest = cs.gson.fromJson(body, HueChangeRequest.class);
258
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");
262         }
263
264         sensor.item.setLabel(name);
265         itemRegistry.update(sensor.item);
266
267         return NetworkUtils.singleSuccess(cs.gson, name, "/sensors/" + id + "/name");
268     }
269
270     @PUT
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");
278         }
279
280         return NetworkUtils.singleError(cs.gson, uri, HueResponse.SENSOR_NOT_CLIP_SENSOR,
281                 "Invalid request: Not a clip sensor");
282     }
283 }