2 * Copyright (c) 2010-2021 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.imperihome.internal.processor;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.EnumMap;
18 import java.util.HashMap;
19 import java.util.LinkedList;
20 import java.util.List;
23 import org.apache.commons.lang.BooleanUtils;
24 import org.apache.commons.lang.StringUtils;
25 import org.openhab.core.items.Item;
26 import org.openhab.core.items.ItemRegistry;
27 import org.openhab.core.items.ItemRegistryChangeListener;
28 import org.openhab.core.library.types.DecimalType;
29 import org.openhab.core.library.types.HSBType;
30 import org.openhab.core.library.types.OnOffType;
31 import org.openhab.core.library.types.OpenClosedType;
32 import org.openhab.core.types.State;
33 import org.openhab.io.imperihome.internal.ImperiHomeConfig;
34 import org.openhab.io.imperihome.internal.action.ActionRegistry;
35 import org.openhab.io.imperihome.internal.model.device.AbstractDevice;
36 import org.openhab.io.imperihome.internal.model.device.AbstractNumericValueDevice;
37 import org.openhab.io.imperihome.internal.model.device.Co2SensorDevice;
38 import org.openhab.io.imperihome.internal.model.device.DeviceType;
39 import org.openhab.io.imperihome.internal.model.device.DimmerDevice;
40 import org.openhab.io.imperihome.internal.model.device.ElectricityDevice;
41 import org.openhab.io.imperihome.internal.model.device.GenericSensorDevice;
42 import org.openhab.io.imperihome.internal.model.device.HygrometryDevice;
43 import org.openhab.io.imperihome.internal.model.device.LockDevice;
44 import org.openhab.io.imperihome.internal.model.device.LuminosityDevice;
45 import org.openhab.io.imperihome.internal.model.device.MultiSwitchDevice;
46 import org.openhab.io.imperihome.internal.model.device.NoiseDevice;
47 import org.openhab.io.imperihome.internal.model.device.PressureDevice;
48 import org.openhab.io.imperihome.internal.model.device.RainDevice;
49 import org.openhab.io.imperihome.internal.model.device.RgbLightDevice;
50 import org.openhab.io.imperihome.internal.model.device.SceneDevice;
51 import org.openhab.io.imperihome.internal.model.device.ShutterDevice;
52 import org.openhab.io.imperihome.internal.model.device.SwitchDevice;
53 import org.openhab.io.imperihome.internal.model.device.TempHygroDevice;
54 import org.openhab.io.imperihome.internal.model.device.TemperatureDevice;
55 import org.openhab.io.imperihome.internal.model.device.ThermostatDevice;
56 import org.openhab.io.imperihome.internal.model.device.TrippableDevice;
57 import org.openhab.io.imperihome.internal.model.device.UvDevice;
58 import org.openhab.io.imperihome.internal.model.device.WindDevice;
59 import org.openhab.io.imperihome.internal.model.param.DeviceParam;
60 import org.openhab.io.imperihome.internal.model.param.ParamType;
61 import org.openhab.io.imperihome.internal.util.DigestUtil;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
66 * Processor of openHAB Items. Parses ISS tags and creates and registers {@link AbstractDevice} implementations where
69 * @author Pepijn de Geus - Initial contribution
71 public class ItemProcessor implements ItemRegistryChangeListener {
73 private static final String PREFIX_ISS = "iss:";
75 private final Logger logger = LoggerFactory.getLogger(ItemProcessor.class);
77 private final ItemRegistry itemRegistry;
78 private final DeviceRegistry deviceRegistry;
79 private final ActionRegistry actionRegistry;
80 private final ImperiHomeConfig config;
82 public ItemProcessor(ItemRegistry itemRegistry, DeviceRegistry deviceRegistry, ActionRegistry actionRegistry,
83 ImperiHomeConfig config) {
84 this.itemRegistry = itemRegistry;
85 this.deviceRegistry = deviceRegistry;
86 this.actionRegistry = actionRegistry;
89 allItemsChanged(Collections.emptyList());
90 itemRegistry.addRegistryChangeListener(this);
93 public void destroy() {
94 itemRegistry.removeRegistryChangeListener(this);
96 // Destroy all Devices (unregisters state listeners)
97 synchronized (deviceRegistry) {
98 for (AbstractDevice device : deviceRegistry) {
101 deviceRegistry.clear();
105 private void parseItem(Item item) {
106 Map<TagType, List<String>> issTags = getIssTags(item);
107 if (!issTags.isEmpty()) {
108 logger.debug("Found item {} with ISS tags: {}", item, issTags);
110 DeviceType deviceType = getDeviceType(item, issTags);
111 if (deviceType == null) {
112 logger.warn("Unrecognized device type for item: {}", item);
114 AbstractDevice device = getDeviceInstance(deviceType, item);
115 device.setId(getDeviceId(item));
116 device.setName(getLabel(item, issTags));
117 device.setInverted(isInverted(issTags));
118 device.setActionRegistry(actionRegistry);
120 setIcon(device, issTags);
121 setDeviceRoom(device, issTags);
122 setDeviceLinks(device, item, issTags);
123 setMapping(device, item, issTags);
124 setUnit(device, issTags);
126 device.processCustomTags(issTags);
129 logger.debug("Setting initial state of {} to {}", device, item.getState());
130 device.stateUpdated(item, item.getState());
132 logger.debug("Item parsed to device: {}", device);
133 synchronized (deviceRegistry) {
134 deviceRegistry.add(device);
140 private void setIcon(AbstractDevice device, Map<TagType, List<String>> issTags) {
141 if (!issTags.containsKey(TagType.ICON)) {
145 String icon = issTags.get(TagType.ICON).get(0);
146 if (!icon.toLowerCase().startsWith("http")) {
147 if (StringUtils.isEmpty(config.getRootUrl())) {
148 logger.error("Can't set icon; 'openhab.rootUrl' not set in configuration");
151 icon = config.getRootUrl() + "icon/" + icon;
154 device.addParam(new DeviceParam(ParamType.DEFAULT_ICON, icon));
157 private AbstractDevice getDeviceInstance(DeviceType deviceType, Item item) {
158 switch (deviceType) {
160 return new SwitchDevice(item);
162 return new DimmerDevice(item);
164 return new RgbLightDevice(item);
166 return new TemperatureDevice(item);
168 return new TempHygroDevice(item);
170 return new LuminosityDevice(item);
172 return new HygrometryDevice(item);
174 return new Co2SensorDevice(item);
176 return new ElectricityDevice(item);
178 return new SceneDevice(item);
180 return new MultiSwitchDevice(item);
182 return new GenericSensorDevice(item);
184 return new PressureDevice(item);
186 return new UvDevice(item);
188 return new NoiseDevice(item);
190 return new RainDevice(item);
192 return new WindDevice(item);
194 return new LockDevice(item);
196 return new ShutterDevice(item);
198 return new ThermostatDevice(item);
204 return new TrippableDevice(deviceType, item);
209 throw new IllegalArgumentException("Unknown device type: " + deviceType);
212 private String getLabel(Item item, Map<TagType, List<String>> issTags) {
213 if (issTags.containsKey(TagType.LABEL)) {
214 return issTags.get(TagType.LABEL).get(0);
217 if (StringUtils.isNotBlank(item.getLabel())) {
218 String label = item.getLabel().trim();
219 if (label.matches("\\[.*\\]$")) {
220 label = label.substring(0, label.indexOf('['));
225 return item.getName();
228 private boolean isInverted(Map<TagType, List<String>> issTags) {
229 return issTags.containsKey(TagType.INVERT) && BooleanUtils.toBoolean(issTags.get(TagType.INVERT).get(0));
232 private void setDeviceRoom(AbstractDevice device, Map<TagType, List<String>> issTags) {
233 String roomName = "No room";
234 if (issTags.containsKey(TagType.ROOM)) {
235 roomName = issTags.get(TagType.ROOM).get(0);
238 device.setRoom(DigestUtil.sha1(roomName));
239 device.setRoomName(roomName);
242 private void setDeviceLinks(AbstractDevice device, Item item, Map<TagType, List<String>> issTags) {
243 if (issTags.containsKey(TagType.LINK)) {
244 // Pass device registry to device for linked device lookup
245 device.setDeviceRegistry(deviceRegistry);
248 for (String link : issTags.get(TagType.LINK)) {
249 String[] parts = link.split(":");
250 if (parts.length == 2) {
251 device.addLink(parts[0].toLowerCase().trim(), parts[1].trim());
253 logger.error("Item has incorrect link format (should be 'iss:link:<type>:<item>'): {}", item);
257 // Check required links
258 for (String requiredLink : device.getType().getRequiredLinks()) {
259 if (!device.getLinks().containsKey(requiredLink)) {
260 logger.error("Item doesn't contain required link {} for {}: {}", requiredLink,
261 device.getType().getApiString(), item);
268 * Parses a mapping tag, if it exists. Format: "iss:mapping:1=Foo,2=Bar,3=Foobar".
270 private void setMapping(AbstractDevice device, Item item, Map<TagType, List<String>> issTags) {
271 if (issTags.containsKey(TagType.MAPPING)) {
272 String mapItems = issTags.get(TagType.MAPPING).get(0);
274 Map<String, String> mapping = new HashMap<>();
275 for (String mapItem : mapItems.split(",")) {
276 String[] keyVal = mapItem.split("=", 2);
277 if (keyVal.length != 2) {
278 logger.error("Invalid mapping syntax for Item {}", item);
281 mapping.put(keyVal[0].trim(), keyVal[1].trim());
284 device.setMapping(mapping);
289 * Parses the unit tag, if it exists. Format: "iss:unit:°C".
291 private void setUnit(AbstractDevice device, Map<TagType, List<String>> issTags) {
292 if (issTags.containsKey(TagType.UNIT)) {
293 if (!(device instanceof AbstractNumericValueDevice)) {
294 logger.warn("Unit tag is not supported for device {}", device);
298 ((AbstractNumericValueDevice) device).setUnit(issTags.get(TagType.UNIT).get(0));
303 * Determines the Device type for the given Item. Uses the 'type' tag first, tries to auto-detect the type if no
306 private DeviceType getDeviceType(Item item, Map<TagType, List<String>> issTags) {
307 if (issTags.containsKey(TagType.TYPE)) {
308 return DeviceType.forApiString(issTags.get(TagType.TYPE).get(0));
311 List<Class<? extends State>> acceptedDataTypes = item.getAcceptedDataTypes();
312 String name = item.getName().toLowerCase();
314 if (acceptedDataTypes.contains(DecimalType.class)) {
315 if (name.contains("tempe")) {
316 return DeviceType.TEMPERATURE;
317 } else if (name.contains("lumi")) {
318 return DeviceType.LUMINOSITY;
319 } else if (name.contains("hygro")) {
320 return DeviceType.HYGROMETRY;
321 } else if (name.contains("wind")) {
322 return DeviceType.WIND;
324 return DeviceType.GENERIC_SENSOR;
328 if (acceptedDataTypes.contains(HSBType.class)) {
329 return DeviceType.RGB_LIGHT;
332 if (acceptedDataTypes.contains(OpenClosedType.class)) {
333 return DeviceType.DOOR;
335 if (acceptedDataTypes.contains(OnOffType.class)) {
336 return DeviceType.SWITCH;
342 private Map<TagType, List<String>> getIssTags(Item item) {
343 Map<TagType, List<String>> tags = new EnumMap<>(TagType.class);
345 for (String tag : item.getTags()) {
346 if (tag.startsWith(PREFIX_ISS)) {
347 String issTag = tag.substring(PREFIX_ISS.length());
348 for (TagType tagType : TagType.values()) {
349 if (issTag.startsWith(tagType.getPrefix() + ':')) {
350 String tagValue = issTag.substring(tagType.getPrefix().length() + 1);
351 if (!tags.containsKey(tagType)) {
352 tags.put(tagType, new LinkedList<>());
353 } else if (!tagType.isMultiValue()) {
354 logger.error("Found multiple values for tag {} - only first value is used",
355 tagType.getPrefix());
357 tags.get(tagType).add(tagValue);
368 * Removes the given item for the device list.
370 * @param item Item to remove.
372 private void removeItem(Item item) {
373 removeItem(item.getName());
377 * Removes the given item for the device list.
379 * @param itemName Name of the Item to remove.
381 private void removeItem(String itemName) {
382 String deviceId = getDeviceId(itemName);
384 AbstractDevice device;
385 synchronized (deviceRegistry) {
386 device = deviceRegistry.remove(deviceId);
389 if (device != null) {
390 logger.debug("Removing Device from ISS registry for Item: {}", itemName);
396 * Generates an unique device ID for the given item.
398 * @param item Item to get device ID for.
401 public static String getDeviceId(Item item) {
402 return getDeviceId(item.getName());
406 * Generates an unique device ID for the given item name.
408 * @param itemName Item name.
411 public static String getDeviceId(String itemName) {
412 return DigestUtil.sha1(itemName);
416 public void added(Item item) {
417 logger.debug("Processing item added event");
422 public void removed(Item item) {
423 logger.debug("Processing item removed event");
428 public void updated(Item oldItem, Item newItem) {
429 logger.debug("Processing item updated event");
435 public void allItemsChanged(Collection<String> oldItems) {
436 synchronized (deviceRegistry) {
437 logger.debug("Processing allItemsChanged event");
439 for (String oldItem : oldItems) {
443 if (deviceRegistry.hasDevices()) {
444 logger.warn("There are still Devices left after processing all Items from allItemsChanged(): {}",
445 deviceRegistry.getDevices());
446 deviceRegistry.clear();
449 for (Item item : itemRegistry.getItems()) {