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.haywardomnilogic.internal.discovery;
15 import static org.openhab.binding.haywardomnilogic.internal.HaywardBindingConstants.THING_TYPES_UIDS;
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.HashMap;
20 import java.util.List;
22 import java.util.function.BiConsumer;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.haywardomnilogic.internal.HaywardBindingConstants;
27 import org.openhab.binding.haywardomnilogic.internal.HaywardException;
28 import org.openhab.binding.haywardomnilogic.internal.HaywardTypeToRequest;
29 import org.openhab.binding.haywardomnilogic.internal.handler.HaywardBridgeHandler;
30 import org.openhab.core.config.discovery.AbstractDiscoveryService;
31 import org.openhab.core.config.discovery.DiscoveryResult;
32 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
33 import org.openhab.core.config.discovery.DiscoveryService;
34 import org.openhab.core.thing.ThingTypeUID;
35 import org.openhab.core.thing.ThingUID;
36 import org.openhab.core.thing.binding.ThingHandler;
37 import org.openhab.core.thing.binding.ThingHandlerService;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
42 * Sets up the discovery results and details
44 * @author Matt Myers - Initial contribution
48 public class HaywardDiscoveryService extends AbstractDiscoveryService implements DiscoveryService, ThingHandlerService {
49 private final Logger logger = LoggerFactory.getLogger(HaywardDiscoveryService.class);
50 private @Nullable HaywardBridgeHandler discoveryBridgehandler;
52 public HaywardDiscoveryService() {
53 super(THING_TYPES_UIDS, 0, false);
57 public void activate() {
62 public void deactivate() {
67 protected void startScan() {
68 HaywardBridgeHandler bridgehandler = discoveryBridgehandler;
70 if (bridgehandler != null) {
71 String xmlResults = bridgehandler.getMspConfig();
72 mspConfigDiscovery(xmlResults);
74 } catch (HaywardException e) {
75 logger.warn("Exception during discovery scan: {}", e.getMessage());
76 } catch (InterruptedException e) {
81 public synchronized void mspConfigDiscovery(String xmlResponse) {
82 List<String> systemIDs = new ArrayList<>();
83 List<String> names = new ArrayList<>();
84 Map<String, Object> backyardProperties = new HashMap<>();
85 Map<String, Object> bowProperties = new HashMap<>();
86 HaywardBridgeHandler bridgehandler = discoveryBridgehandler;
88 if (bridgehandler == null) {
93 names = bridgehandler.evaluateXPath("//Backyard/Name/text()", xmlResponse);
95 for (int i = 0; i < names.size(); i++) {
96 backyardProperties.put(HaywardBindingConstants.PROPERTY_TYPE, HaywardTypeToRequest.BACKYARD);
97 backyardProperties.put(HaywardBindingConstants.PROPERTY_SYSTEM_ID, bridgehandler.account.mspSystemID);
99 onDeviceDiscovered(HaywardBindingConstants.THING_TYPE_BACKYARD, names.get(i), backyardProperties);
102 // Find Bodies of Water
103 systemIDs = bridgehandler.evaluateXPath("//Body-of-water/System-Id/text()", xmlResponse);
104 names = bridgehandler.evaluateXPath("//Body-of-water/Name/text()", xmlResponse);
106 final List<String> bowProperty1 = bridgehandler.evaluateXPath("//Body-of-water/Type/text()", xmlResponse);
107 final List<String> bowProperty2 = bridgehandler.evaluateXPath("//Body-of-water/Shared-Type/text()",
109 final List<String> bowProperty3 = bridgehandler.evaluateXPath("//Body-of-water/Shared-Priority/text()",
111 final List<String> bowProperty4 = bridgehandler
112 .evaluateXPath("//Body-of-water/Shared-Equipment-System-ID/text()", xmlResponse);
113 final List<String> bowProperty5 = bridgehandler.evaluateXPath("//Body-of-water/Supports-Spillover/text()",
115 final List<String> bowProperty6 = bridgehandler.evaluateXPath("//Body-of-water/Size-In-Gallons/text()",
118 for (int i = 0; i < systemIDs.size(); i++) {
119 bowProperties.put(HaywardBindingConstants.PROPERTY_TYPE, HaywardTypeToRequest.BOW);
120 bowProperties.put(HaywardBindingConstants.PROPERTY_SYSTEM_ID, systemIDs.get(i));
121 bowProperties.put(HaywardBindingConstants.PROPERTY_BOW_TYPE, bowProperty1.get(i));
122 bowProperties.put(HaywardBindingConstants.PROPERTY_BOW_SHAREDTYPE, bowProperty2.get(i));
123 bowProperties.put(HaywardBindingConstants.PROPERTY_BOW_SHAREDPRIORITY, bowProperty3.get(i));
124 bowProperties.put(HaywardBindingConstants.PROPERTY_BOW_SHAREDEQUIPID, bowProperty4.get(i));
125 bowProperties.put(HaywardBindingConstants.PROPERTY_BOW_SUPPORTSSPILLOVER, bowProperty5.get(i));
126 bowProperties.put(HaywardBindingConstants.PROPERTY_BOW_SIZEINGALLONS, bowProperty6.get(i));
128 onDeviceDiscovered(HaywardBindingConstants.THING_TYPE_BOW, names.get(i), bowProperties);
132 final List<String> chlorinatorProperty1 = bridgehandler
133 .evaluateXPath("//Body-of-water/Chlorinator/Shared-Type/text()", xmlResponse);
134 final List<String> chlorinatorProperty2 = bridgehandler.evaluateXPath("//Body-of-water/Chlorinator/Mode/text()",
136 final List<String> chlorinatorProperty3 = bridgehandler
137 .evaluateXPath("//Body-of-water/Chlorinator/Cell-Type/text()", xmlResponse);
138 final List<String> chlorinatorProperty4 = bridgehandler
139 .evaluateXPath("//Body-of-water/Chlorinator/Dispenser-Type/text()", xmlResponse);
141 discoverDevices(bridgehandler, xmlResponse, "Chlorinator", HaywardTypeToRequest.CHLORINATOR,
142 HaywardBindingConstants.THING_TYPE_CHLORINATOR, (props, i) -> {
143 props.put(HaywardBindingConstants.PROPERTY_CHLORINATOR_SHAREDTYPE, chlorinatorProperty1.get(i));
144 props.put(HaywardBindingConstants.PROPERTY_CHLORINATOR_MODE, chlorinatorProperty2.get(i));
145 props.put(HaywardBindingConstants.PROPERTY_CHLORINATOR_CELLTYPE, chlorinatorProperty3.get(i));
146 props.put(HaywardBindingConstants.PROPERTY_CHLORINATOR_DISPENSERTYPE, chlorinatorProperty4.get(i));
149 // Find ColorLogic Lights
150 final List<String> colorLogicProperty1 = bridgehandler.evaluateXPath("//Backyard//ColorLogic-Light/Type/text()",
153 discoverDevices(bridgehandler, xmlResponse, "ColorLogic-Light", HaywardTypeToRequest.COLORLOGIC,
154 HaywardBindingConstants.THING_TYPE_COLORLOGIC, (props, i) -> {
155 props.put(HaywardBindingConstants.PROPERTY_COLORLOGIC_TYPE, colorLogicProperty1.get(i));
159 final List<String> filterProperty1 = bridgehandler.evaluateXPath("//Body-of-water/Filter/Shared-Type/text()",
161 final List<String> filterProperty2 = bridgehandler.evaluateXPath("//Body-of-water/Filter/Filter-Type/text()",
163 final List<String> filterProperty3 = bridgehandler
164 .evaluateXPath("//Body-of-water/Filter/Priming-Enabled/text()", xmlResponse);
165 final List<String> filterProperty4 = bridgehandler.evaluateXPath("//Body-of-water/Filter/Min-Pump-Speed/text()",
167 final List<String> filterProperty5 = bridgehandler.evaluateXPath("//Body-of-water/Filter/Max-Pump-Speed/text()",
169 final List<String> filterProperty6 = bridgehandler.evaluateXPath("//Body-of-water/Filter/Min-Pump-RPM/text()",
171 final List<String> filterProperty7 = bridgehandler.evaluateXPath("//Body-of-water/Filter/Max-Pump-RPM/text()",
173 final List<String> filterProperty8 = bridgehandler
174 .evaluateXPath("//Body-of-water/Filter/Vsp-Low-Pump-Speed/text()", xmlResponse);
175 final List<String> filterProperty9 = bridgehandler
176 .evaluateXPath("//Body-of-water/Filter/Vsp-Medium-Pump-Speed/text()", xmlResponse);
177 final List<String> filterProperty10 = bridgehandler
178 .evaluateXPath("//Body-of-water/Filter/Vsp-High-Pump-Speed/text()", xmlResponse);
179 final List<String> filterProperty11 = bridgehandler
180 .evaluateXPath("//Body-of-water/Filter/Vsp-Custom-Pump-Speed/text()", xmlResponse);
181 final List<String> filterProperty12 = bridgehandler
182 .evaluateXPath("//Body-of-water/Filter/Freeze-Protect-Override-Interval/text()", xmlResponse);
184 discoverDevices(bridgehandler, xmlResponse, "Filter", HaywardTypeToRequest.FILTER,
185 HaywardBindingConstants.THING_TYPE_FILTER, (props, i) -> {
186 props.put(HaywardBindingConstants.PROPERTY_FILTER_SHAREDTYPE, filterProperty1.get(i));
187 props.put(HaywardBindingConstants.PROPERTY_FILTER_FILTERTYPE, filterProperty2.get(i));
188 props.put(HaywardBindingConstants.PROPERTY_FILTER_PRIMINGENABLED, filterProperty3.get(i));
189 props.put(HaywardBindingConstants.PROPERTY_FILTER_MINSPEED, filterProperty4.get(i));
190 props.put(HaywardBindingConstants.PROPERTY_FILTER_MAXSPEED, filterProperty5.get(i));
191 props.put(HaywardBindingConstants.PROPERTY_FILTER_MINRPM, filterProperty6.get(i));
192 props.put(HaywardBindingConstants.PROPERTY_FILTER_MAXRPM, filterProperty7.get(i));
193 props.put(HaywardBindingConstants.PROPERTY_FILTER_LOWSPEED, filterProperty8.get(i));
194 props.put(HaywardBindingConstants.PROPERTY_FILTER_MEDSPEED, filterProperty9.get(i));
195 props.put(HaywardBindingConstants.PROPERTY_FILTER_HIGHSPEED, filterProperty10.get(i));
196 props.put(HaywardBindingConstants.PROPERTY_FILTER_CUSTOMSPEED, filterProperty11.get(i));
197 props.put(HaywardBindingConstants.PROPERTY_FILTER_FREEZEPROTECTOVERRIDEINTERVAL,
198 filterProperty12.get(i));
202 final List<String> heaterProperty1 = bridgehandler
203 .evaluateXPath("//Body-of-water/Heater/Operation/Heater-Equipment/Type/text()", xmlResponse);
204 final List<String> heaterProperty2 = bridgehandler
205 .evaluateXPath("//Body-of-water/Heater/Operation/Heater-Equipment/Heater-Type/text()", xmlResponse);
206 final List<String> heaterProperty3 = bridgehandler.evaluateXPath(
207 "//Body-of-water/Heater/Operation/Heater-Equipment/Shared-Equipment-System-ID/text()", xmlResponse);
209 discoverDevices(bridgehandler, xmlResponse, "Heater-Equipment", HaywardTypeToRequest.HEATER,
210 HaywardBindingConstants.THING_TYPE_HEATER, (props, i) -> {
211 props.put(HaywardBindingConstants.PROPERTY_HEATER_TYPE, heaterProperty1.get(i));
212 props.put(HaywardBindingConstants.PROPERTY_HEATER_HEATERTYPE, heaterProperty2.get(i));
213 props.put(HaywardBindingConstants.PROPERTY_HEATER_SHAREDEQUIPID, heaterProperty3.get(i));
217 final List<String> pumpProperty1 = bridgehandler.evaluateXPath("//Body-of-water/Pump/Type/text()", xmlResponse);
218 final List<String> pumpProperty2 = bridgehandler.evaluateXPath("//Body-of-water/Pump/Function/text()",
220 final List<String> pumpProperty3 = bridgehandler.evaluateXPath("//Body-of-water/Pump/Priming-Enabled/text()",
222 final List<String> pumpProperty4 = bridgehandler.evaluateXPath("//Body-of-water/Pump/Min-Pump-Speed/text()",
224 final List<String> pumpProperty5 = bridgehandler.evaluateXPath("//Body-of-water/Pump/Max-Pump-Speed/text()",
226 final List<String> pumpProperty6 = bridgehandler.evaluateXPath("//Body-of-water/Pump/Min-Pump-RPM/text()",
228 final List<String> pumpProperty7 = bridgehandler.evaluateXPath("//Body-of-water/Pump/Max-Pump-RPM/text()",
230 final List<String> pumpProperty8 = bridgehandler.evaluateXPath("//Body-of-water/Pump/Vsp-Low-Pump-Speed/text()",
232 final List<String> pumpProperty9 = bridgehandler
233 .evaluateXPath("//Body-of-water/Pump/Vsp-Medium-Pump-Speed/text()", xmlResponse);
234 final List<String> pumpProperty10 = bridgehandler
235 .evaluateXPath("//Body-of-water/Pump/Vsp-High-Pump-Speed/text()", xmlResponse);
236 final List<String> pumpProperty11 = bridgehandler
237 .evaluateXPath("//Body-of-water/Pump/Vsp-Custom-Pump-Speed/text()", xmlResponse);
239 discoverDevices(bridgehandler, xmlResponse, "Pump", HaywardTypeToRequest.PUMP,
240 HaywardBindingConstants.THING_TYPE_PUMP, (props, i) -> {
241 props.put(HaywardBindingConstants.PROPERTY_PUMP_TYPE, pumpProperty1.get(i));
242 props.put(HaywardBindingConstants.PROPERTY_PUMP_FUNCTION, pumpProperty2.get(i));
243 props.put(HaywardBindingConstants.PROPERTY_PUMP_PRIMINGENABLED, pumpProperty3.get(i));
244 props.put(HaywardBindingConstants.PROPERTY_PUMP_MINSPEED, pumpProperty4.get(i));
245 props.put(HaywardBindingConstants.PROPERTY_PUMP_MAXSPEED, pumpProperty5.get(i));
246 props.put(HaywardBindingConstants.PROPERTY_PUMP_MINRPM, pumpProperty6.get(i));
247 props.put(HaywardBindingConstants.PROPERTY_PUMP_MAXRPM, pumpProperty7.get(i));
248 props.put(HaywardBindingConstants.PROPERTY_PUMP_LOWSPEED, pumpProperty8.get(i));
249 props.put(HaywardBindingConstants.PROPERTY_PUMP_MEDSPEED, pumpProperty9.get(i));
250 props.put(HaywardBindingConstants.PROPERTY_PUMP_HIGHSPEED, pumpProperty10.get(i));
251 props.put(HaywardBindingConstants.PROPERTY_PUMP_CUSTOMSPEED, pumpProperty11.get(i));
255 final List<String> relayProperty1 = bridgehandler.evaluateXPath("//Backyard//Relay/Type/text()", xmlResponse);
256 final List<String> relayProperty2 = bridgehandler.evaluateXPath("//Backyard//Relay/Function/text()",
259 discoverDevices(bridgehandler, xmlResponse, "Relay", HaywardTypeToRequest.RELAY,
260 HaywardBindingConstants.THING_TYPE_RELAY, (props, i) -> {
261 props.put(HaywardBindingConstants.PROPERTY_RELAY_TYPE, relayProperty1.get(i));
262 props.put(HaywardBindingConstants.PROPERTY_RELAY_FUNCTION, relayProperty2.get(i));
265 // Find Virtual Heaters
266 final List<String> virtualHeaterProperty1 = bridgehandler
267 .evaluateXPath("//Body-of-water/Heater/Shared-Type/text()", xmlResponse);
268 final List<String> virtualHeaterProperty2 = bridgehandler
269 .evaluateXPath("//Body-of-water/Heater/Min-Settable-Water-Temp/text()", xmlResponse);
270 final List<String> virtualHeaterProperty3 = bridgehandler
271 .evaluateXPath("//Body-of-water/Heater/Max-Settable-Water-Temp/text()", xmlResponse);
272 final List<String> virtualHeaterProperty4 = bridgehandler
273 .evaluateXPath("//Body-of-water/Heater/Max-Water-Temp/text()", xmlResponse);
275 discoverDevices(bridgehandler, xmlResponse, "Heater", HaywardTypeToRequest.VIRTUALHEATER,
276 HaywardBindingConstants.THING_TYPE_VIRTUALHEATER, (props, i) -> {
277 props.put(HaywardBindingConstants.PROPERTY_VIRTUALHEATER_SHAREDTYPE, virtualHeaterProperty1.get(i));
278 props.put(HaywardBindingConstants.PROPERTY_VIRTUALHEATER_MINSETTABLEWATERTEMP,
279 virtualHeaterProperty2.get(i));
280 props.put(HaywardBindingConstants.PROPERTY_VIRTUALHEATER_MAXSETTABLEWATERTEMP,
281 virtualHeaterProperty3.get(i));
282 props.put(HaywardBindingConstants.PROPERTY_VIRTUALHEATER_MAXWATERTEMP,
283 virtualHeaterProperty4.get(i));
287 private void discoverDevices(HaywardBridgeHandler bridgehandler, String xmlResponse, String xmlSearchTerm,
288 HaywardTypeToRequest type, ThingTypeUID thingType,
289 @Nullable BiConsumer<Map<String, Object>, Integer> additionalPropertyConsumer) {
290 List<String> systemIDs = bridgehandler.evaluateXPath("//Backyard//" + xmlSearchTerm + "/System-Id/text()",
294 // Set Virtual Heater Name
295 if (HaywardBindingConstants.THING_TYPE_VIRTUALHEATER.equals(thingType)) {
296 names = new ArrayList<>(systemIDs);
297 Collections.fill(names, "Heater");
299 names = bridgehandler.evaluateXPath("//Backyard//" + xmlSearchTerm + "/Name/text()", xmlResponse);
302 for (int i = 0; i < systemIDs.size(); i++) {
303 // get Body of Water for each item
304 List<String> bowID = bridgehandler.evaluateXPath(
305 "//*[System-Id=" + systemIDs.get(i) + "]/ancestor::Body-of-water/System-Id/text()", xmlResponse);
306 List<String> bowName = bridgehandler.evaluateXPath(
307 "//*[System-Id=" + systemIDs.get(i) + "]/ancestor::Body-of-water/Name/text()", xmlResponse);
309 Map<String, Object> properties = new HashMap<>();
310 properties.put(HaywardBindingConstants.PROPERTY_TYPE, type);
311 properties.put(HaywardBindingConstants.PROPERTY_SYSTEM_ID, systemIDs.get(i));
313 if (!bowID.isEmpty()) {
314 properties.put(HaywardBindingConstants.PROPERTY_BOWID, bowID.get(0));
316 // Set BOWID = 0 for backyard items
317 properties.put(HaywardBindingConstants.PROPERTY_BOWID, "0");
320 if (!bowName.isEmpty()) {
321 properties.put(HaywardBindingConstants.PROPERTY_BOWNAME, bowName.get(0));
323 // Set BOWNAME = Backyard for backyard items
324 properties.put(HaywardBindingConstants.PROPERTY_BOWNAME, "Backyard");
327 if (additionalPropertyConsumer != null) {
328 additionalPropertyConsumer.accept(properties, i);
331 onDeviceDiscovered(thingType, names.get(i), properties);
335 public void onDeviceDiscovered(ThingTypeUID thingType, String label, Map<String, Object> properties) {
336 HaywardBridgeHandler bridgehandler = discoveryBridgehandler;
337 String systemID = (String) properties.get(HaywardBindingConstants.PROPERTY_SYSTEM_ID);
338 if (bridgehandler != null) {
339 if (systemID != null) {
340 ThingUID thingUID = new ThingUID(thingType, bridgehandler.getThing().getUID(), systemID);
341 DiscoveryResult result = DiscoveryResultBuilder.create(thingUID)
342 .withBridge(bridgehandler.getThing().getUID())
343 .withRepresentationProperty(HaywardBindingConstants.PROPERTY_SYSTEM_ID)
344 .withLabel("Hayward " + label).withProperties(properties).build();
345 thingDiscovered(result);
351 public void setThingHandler(@Nullable ThingHandler handler) {
352 if (handler instanceof HaywardBridgeHandler) {
353 this.discoveryBridgehandler = (HaywardBridgeHandler) handler;
358 public @Nullable ThingHandler getThingHandler() {
359 return discoveryBridgehandler;