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.solarwatt.internal.discovery;
15 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_PROPERTIES_GUID;
16 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_TYPE_BATTERYCONVERTER;
17 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_TYPE_EVSTATION;
18 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_TYPE_GRIDFLOW;
19 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_TYPE_INVERTER;
20 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_TYPE_LOCATION;
21 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_TYPE_POWERMETER;
22 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_TYPE_PVPLANT;
23 import static org.openhab.binding.solarwatt.internal.SolarwattBindingConstants.THING_TYPE_SMARTHEATER;
25 import java.util.HashMap;
27 import java.util.concurrent.ScheduledFuture;
28 import java.util.concurrent.TimeUnit;
30 import org.eclipse.jdt.annotation.NonNullByDefault;
31 import org.eclipse.jdt.annotation.Nullable;
32 import org.openhab.binding.solarwatt.internal.domain.model.BatteryConverter;
33 import org.openhab.binding.solarwatt.internal.domain.model.Device;
34 import org.openhab.binding.solarwatt.internal.domain.model.EVStation;
35 import org.openhab.binding.solarwatt.internal.domain.model.GridFlow;
36 import org.openhab.binding.solarwatt.internal.domain.model.Inverter;
37 import org.openhab.binding.solarwatt.internal.domain.model.Location;
38 import org.openhab.binding.solarwatt.internal.domain.model.PVPlant;
39 import org.openhab.binding.solarwatt.internal.domain.model.PowerMeter;
40 import org.openhab.binding.solarwatt.internal.domain.model.SmartHeater;
41 import org.openhab.binding.solarwatt.internal.handler.EnergyManagerHandler;
42 import org.openhab.core.config.discovery.AbstractDiscoveryService;
43 import org.openhab.core.config.discovery.DiscoveryResult;
44 import org.openhab.core.config.discovery.DiscoveryResultBuilder;
45 import org.openhab.core.config.discovery.DiscoveryService;
46 import org.openhab.core.thing.ThingStatus;
47 import org.openhab.core.thing.ThingTypeUID;
48 import org.openhab.core.thing.ThingUID;
49 import org.openhab.core.thing.binding.ThingHandler;
50 import org.openhab.core.thing.binding.ThingHandlerService;
51 import org.slf4j.Logger;
52 import org.slf4j.LoggerFactory;
55 * Discovery service to discover devices attached to the energy manager.
57 * @author Sven Carstens - Initial contribution
60 public class SolarwattDevicesDiscoveryService extends AbstractDiscoveryService
61 implements ThingHandlerService, DiscoveryService {
63 private static final int TIMEOUT_SECONDS = 20;
65 private final Logger logger = LoggerFactory.getLogger(SolarwattDevicesDiscoveryService.class);
66 private @Nullable EnergyManagerHandler energyManagerHandler;
69 * Job which will do the background scanning
71 private final EnergymanagerScan scanningRunnable;
74 * Schedule for scanning
76 private @Nullable ScheduledFuture<?> scanningJob;
78 public SolarwattDevicesDiscoveryService() {
79 super(TIMEOUT_SECONDS);
80 this.scanningRunnable = new EnergymanagerScan();
86 public void setThingHandler(final @Nullable ThingHandler handler) {
87 if (handler instanceof EnergyManagerHandler energyManagerHandler) {
88 this.energyManagerHandler = energyManagerHandler;
93 public @Nullable ThingHandler getThingHandler() {
94 return this.energyManagerHandler;
98 public void deactivate() {
99 this.stopBackgroundDiscovery();
103 protected void startBackgroundDiscovery() {
104 ScheduledFuture<?> localScanningJob = this.scanningJob;
105 if (localScanningJob == null || localScanningJob.isCancelled()) {
106 this.scanningJob = this.scheduler.scheduleWithFixedDelay(this.scanningRunnable, 5, 5 * 60,
112 protected void stopBackgroundDiscovery() {
113 ScheduledFuture<?> localScanningJob = this.scanningJob;
114 if (localScanningJob != null && !localScanningJob.isCancelled()) {
115 localScanningJob.cancel(true);
116 this.scanningJob = null;
121 protected synchronized void startScan() {
122 this.removeOlderResults(this.getTimestampOfLastScan());
123 final EnergyManagerHandler localEnergyManagerHandler = this.energyManagerHandler;
125 if (localEnergyManagerHandler == null
126 || localEnergyManagerHandler.getThing().getStatus() != ThingStatus.ONLINE) {
127 this.logger.warn("Energymanager handler not available: {}", localEnergyManagerHandler);
130 this.scanForDeviceThings();
134 * Scans for device things.
136 * Walks through the list of devices and adds discovery results for the supported devices.
138 private void scanForDeviceThings() {
139 EnergyManagerHandler localEnergyManagerHandler = this.energyManagerHandler;
140 if (localEnergyManagerHandler != null) {
141 final Map<String, Device> devices = localEnergyManagerHandler.getDevices();
143 final ThingUID bridgeUID = localEnergyManagerHandler.getThing().getUID();
145 if (devices == null) {
146 this.logger.warn("No device data for solarwatt devices in discovery for energy manager {}.", bridgeUID);
148 devices.forEach((key, entry) -> {
149 this.logger.debug("scanForDeviceThings: {}-{}", entry.getClass(), entry.getGuid());
150 if (entry instanceof BatteryConverter) {
151 this.discover(bridgeUID, entry, THING_TYPE_BATTERYCONVERTER);
152 } else if (entry instanceof Inverter) {
153 this.discover(bridgeUID, entry, THING_TYPE_INVERTER);
154 } else if (entry instanceof PowerMeter) {
155 this.discover(bridgeUID, entry, THING_TYPE_POWERMETER);
156 } else if (entry instanceof EVStation) {
157 this.discover(bridgeUID, entry, THING_TYPE_EVSTATION);
158 } else if (entry instanceof Location) {
159 this.discover(bridgeUID, entry, THING_TYPE_LOCATION);
160 } else if (entry instanceof PVPlant) {
161 this.discover(bridgeUID, entry, THING_TYPE_PVPLANT);
162 } else if (entry instanceof GridFlow) {
163 this.discover(bridgeUID, entry, THING_TYPE_GRIDFLOW);
164 } else if (entry instanceof SmartHeater) {
165 this.discover(bridgeUID, entry, THING_TYPE_SMARTHEATER);
167 this.logger.debug("Found unhandled device");
175 * Create a discovery result and add to result.
177 * @param bridgeID to which this device belongs
178 * @param entry describing the device
179 * @param typeUID for matching thing
181 private void discover(final ThingUID bridgeID, final Device entry, final ThingTypeUID typeUID) {
182 final ThingUID thingUID = new ThingUID(typeUID, bridgeID, this.rewriteGuid(entry.getGuid()));
183 final Map<String, Object> properties = new HashMap<>(5);
185 properties.put(THING_PROPERTIES_GUID, entry.getGuid());
186 final DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withBridge(bridgeID)
187 .withRepresentationProperty(THING_PROPERTIES_GUID).withProperties(properties)
188 .withLabel("Solarwatt " + entry.getLabel()).build();
189 this.thingDiscovered(discoveryResult);
193 * Rewrite energy manager guids to be acceptable to openhab.
195 * @param emGuid from energy manager
196 * @return guid for openhab
198 private String rewriteGuid(String emGuid) {
199 return emGuid.replace(":", "-");
202 public class EnergymanagerScan implements Runnable {
205 SolarwattDevicesDiscoveryService.this.startScan();