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.binding.satel.internal.discovery;
15 import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
17 import java.nio.charset.Charset;
18 import java.util.Collections;
19 import java.util.HashMap;
22 import java.util.function.Function;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
26 import org.eclipse.jdt.annotation.NonNullByDefault;
27 import org.eclipse.jdt.annotation.Nullable;
28 import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand;
29 import org.openhab.binding.satel.internal.command.ReadDeviceInfoCommand.DeviceType;
30 import org.openhab.binding.satel.internal.command.SatelCommand;
31 import org.openhab.binding.satel.internal.config.SatelThingConfig;
32 import org.openhab.binding.satel.internal.handler.SatelBridgeHandler;
33 import org.openhab.core.config.discovery.AbstractDiscoveryService;
34 import org.openhab.core.config.discovery.DiscoveryResult;
35 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
36 import org.openhab.core.thing.ThingTypeUID;
37 import org.openhab.core.thing.ThingUID;
38 import org.openhab.core.thing.type.ThingType;
39 import org.slf4j.Logger;
40 import org.slf4j.LoggerFactory;
43 * The {@link SatelDeviceDiscoveryService} searches for available Satel
44 * devices and modules connected to the API console
46 * @author Krzysztof Goworek - Initial contribution
50 public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
52 private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Stream
53 .of(DEVICE_THING_TYPES_UIDS, VIRTUAL_THING_TYPES_UIDS).flatMap(uids -> uids.stream())
54 .collect(Collectors.toSet());
55 private static final int OUTPUT_FUNCTION_SHUTTER = 105;
57 private final Logger logger = LoggerFactory.getLogger(SatelDeviceDiscoveryService.class);
59 private SatelBridgeHandler bridgeHandler;
60 private Function<ThingTypeUID, ThingType> thingTypeProvider;
61 private volatile boolean scanStopped;
63 public SatelDeviceDiscoveryService(SatelBridgeHandler bridgeHandler,
64 Function<ThingTypeUID, ThingType> thingTypeProvider) {
65 super(SUPPORTED_THING_TYPES, 60, true);
66 this.bridgeHandler = bridgeHandler;
67 this.thingTypeProvider = thingTypeProvider;
71 protected void startScan() {
73 if (bridgeHandler.isInitialized()) {
74 // add virtual things by default
75 for (ThingTypeUID thingTypeUID : VIRTUAL_THING_TYPES_UIDS) {
76 ThingType thingType = thingTypeProvider.apply(thingTypeUID);
77 addThing(thingTypeUID, null, thingType.getLabel(), Collections.emptyMap());
81 scanForDevices(DeviceType.KEYPAD, 8);
84 scanForDevices(DeviceType.EXPANDER, 64);
87 scanForDevices(DeviceType.PARTITION_WITH_OBJECT, bridgeHandler.getIntegraType().getPartitions());
90 scanForDevices(DeviceType.ZONE_WITH_PARTITION, bridgeHandler.getIntegraType().getZones());
93 scanForDevices(DeviceType.OUTPUT, bridgeHandler.getIntegraType().getZones());
98 protected synchronized void stopScan() {
103 private void scanForDevices(DeviceType deviceType, int maxNumber) {
104 logger.debug("Scanning for {} started", deviceType.name());
105 final Charset encoding = bridgeHandler.getEncoding();
106 for (int i = 1; i <= maxNumber && !scanStopped; ++i) {
107 ReadDeviceInfoCommand cmd = new ReadDeviceInfoCommand(deviceType, i);
108 cmd.ignoreResponseError();
109 if (bridgeHandler.sendCommand(cmd, false)) {
110 String name = cmd.getName(encoding);
111 int deviceKind = cmd.getDeviceKind();
112 int info = cmd.getAdditionalInfo();
113 logger.debug("Found device: type={}, id={}, name={}, kind/function={}, info={}", deviceType.name(), i,
114 name, deviceKind, info);
115 if (isDeviceAvailable(deviceType, deviceKind)) {
116 addDevice(deviceType, deviceKind, i, name);
118 } else if (cmd.getState() != SatelCommand.State.FAILED) {
119 // serious failure, disconnection or so
121 logger.error("Unexpected failure during scan for {} using {}", deviceType.name(),
122 bridgeHandler.getThing().getUID().toString());
126 logger.debug("Scanning stopped");
128 logger.debug("Scanning for {} finished", deviceType.name());
132 private void addDevice(DeviceType deviceType, int deviceKind, int deviceId, String deviceName) {
133 ThingTypeUID thingTypeUID = getThingTypeUID(deviceType, deviceKind);
135 if (thingTypeUID == null) {
136 logger.warn("Unknown device found: type={}, kind={}, name={}", deviceType.name(), deviceKind, deviceName);
137 } else if (!getSupportedThingTypes().contains(thingTypeUID)) {
138 logger.warn("Unsupported device: {}", thingTypeUID.toString());
140 Map<String, Object> properties = new HashMap<>();
142 if (THING_TYPE_SHUTTER.equals(thingTypeUID)) {
143 properties.put(SatelThingConfig.UP_ID, deviceId);
144 properties.put(SatelThingConfig.DOWN_ID, deviceId + 1);
146 properties.put(SatelThingConfig.ID, deviceId);
149 addThing(thingTypeUID, String.valueOf(deviceId), deviceName, properties);
153 private void addThing(ThingTypeUID thingTypeUID, @Nullable String deviceId, String label,
154 Map<String, Object> properties) {
155 final ThingUID bridgeUID = bridgeHandler.getThing().getUID();
156 final ThingUID thingUID = new ThingUID(thingTypeUID, bridgeUID,
157 deviceId == null ? toCamelCase(thingTypeUID.getId()) : deviceId);
158 final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withThingType(thingTypeUID)
159 .withBridge(bridgeUID).withLabel(label).withProperties(properties).build();
160 thingDiscovered(discoveryResult);
163 private static @Nullable ThingTypeUID getThingTypeUID(DeviceType deviceType, int deviceKind) {
164 switch (deviceType) {
166 return (deviceKind == OUTPUT_FUNCTION_SHUTTER) ? THING_TYPE_SHUTTER : THING_TYPE_OUTPUT;
168 case PARTITION_WITH_OBJECT:
169 return THING_TYPE_PARTITION;
171 case ZONE_WITH_PARTITION:
172 return THING_TYPE_ZONE;
178 private static boolean isDeviceAvailable(DeviceType deviceType, int deviceKind) {
179 switch (deviceType) {
181 return deviceKind != 0 && deviceKind != OUTPUT_FUNCTION_SHUTTER
182 && (deviceKind != OUTPUT_FUNCTION_SHUTTER + 1);
184 case PARTITION_WITH_OBJECT:
186 case ZONE_WITH_PARTITION:
193 private static String toCamelCase(String s) {
194 StringBuilder result = new StringBuilder();
195 boolean makeUpper = true;
196 for (int i = 0; i < s.length(); ++i) {
197 char c = s.charAt(i);
201 result.append(makeUpper ? Character.toUpperCase(c) : c);
205 return result.toString();