]> git.basschouten.com Git - openhab-addons.git/blob
d6d19a300fa00b69e9665b54671b4c455ac42d0f
[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.binding.satel.internal.discovery;
14
15 import static org.openhab.binding.satel.internal.SatelBindingConstants.*;
16
17 import java.nio.charset.Charset;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.function.Function;
23 import java.util.stream.Collectors;
24 import java.util.stream.Stream;
25
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;
41
42 /**
43  * The {@link SatelDeviceDiscoveryService} searches for available Satel
44  * devices and modules connected to the API console
45  *
46  * @author Krzysztof Goworek - Initial contribution
47  *
48  */
49 @NonNullByDefault
50 public class SatelDeviceDiscoveryService extends AbstractDiscoveryService {
51
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;
56
57     private final Logger logger = LoggerFactory.getLogger(SatelDeviceDiscoveryService.class);
58
59     private SatelBridgeHandler bridgeHandler;
60     private Function<ThingTypeUID, ThingType> thingTypeProvider;
61     private volatile boolean scanStopped;
62
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;
68     }
69
70     @Override
71     protected void startScan() {
72         scanStopped = false;
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());
78             }
79         }
80         if (!scanStopped) {
81             scanForDevices(DeviceType.KEYPAD, 8);
82         }
83         if (!scanStopped) {
84             scanForDevices(DeviceType.EXPANDER, 64);
85         }
86         if (!scanStopped) {
87             scanForDevices(DeviceType.PARTITION_WITH_OBJECT, bridgeHandler.getIntegraType().getPartitions());
88         }
89         if (!scanStopped) {
90             scanForDevices(DeviceType.ZONE_WITH_PARTITION, bridgeHandler.getIntegraType().getZones());
91         }
92         if (!scanStopped) {
93             scanForDevices(DeviceType.OUTPUT, bridgeHandler.getIntegraType().getZones());
94         }
95     }
96
97     @Override
98     protected synchronized void stopScan() {
99         scanStopped = true;
100         super.stopScan();
101     }
102
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);
117                 }
118             } else if (cmd.getState() != SatelCommand.State.FAILED) {
119                 // serious failure, disconnection or so
120                 scanStopped = true;
121                 logger.error("Unexpected failure during scan for {} using {}", deviceType.name(),
122                         bridgeHandler.getThing().getUID().toString());
123             }
124         }
125         if (scanStopped) {
126             logger.debug("Scanning stopped");
127         } else {
128             logger.debug("Scanning for {} finished", deviceType.name());
129         }
130     }
131
132     private void addDevice(DeviceType deviceType, int deviceKind, int deviceId, String deviceName) {
133         ThingTypeUID thingTypeUID = getThingTypeUID(deviceType, deviceKind);
134
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());
139         } else {
140             Map<String, Object> properties = new HashMap<>();
141
142             if (THING_TYPE_SHUTTER.equals(thingTypeUID)) {
143                 properties.put(SatelThingConfig.UP_ID, deviceId);
144                 properties.put(SatelThingConfig.DOWN_ID, deviceId + 1);
145             } else {
146                 properties.put(SatelThingConfig.ID, deviceId);
147             }
148
149             addThing(thingTypeUID, String.valueOf(deviceId), deviceName, properties);
150         }
151     }
152
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);
161     }
162
163     private static @Nullable ThingTypeUID getThingTypeUID(DeviceType deviceType, int deviceKind) {
164         switch (deviceType) {
165             case OUTPUT:
166                 return (deviceKind == OUTPUT_FUNCTION_SHUTTER) ? THING_TYPE_SHUTTER : THING_TYPE_OUTPUT;
167             case PARTITION:
168             case PARTITION_WITH_OBJECT:
169                 return THING_TYPE_PARTITION;
170             case ZONE:
171             case ZONE_WITH_PARTITION:
172                 return THING_TYPE_ZONE;
173             default:
174                 return null;
175         }
176     }
177
178     private static boolean isDeviceAvailable(DeviceType deviceType, int deviceKind) {
179         switch (deviceType) {
180             case OUTPUT:
181                 return deviceKind != 0 && deviceKind != OUTPUT_FUNCTION_SHUTTER
182                         && (deviceKind != OUTPUT_FUNCTION_SHUTTER + 1);
183             case PARTITION:
184             case PARTITION_WITH_OBJECT:
185             case ZONE:
186             case ZONE_WITH_PARTITION:
187                 return true;
188             default:
189                 return false;
190         }
191     }
192
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);
198             if (c == '-') {
199                 makeUpper = true;
200             } else {
201                 result.append(makeUpper ? Character.toUpperCase(c) : c);
202                 makeUpper = false;
203             }
204         }
205         return result.toString();
206     }
207 }