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
14 package org.openhab.binding.pilight.internal.handler;
16 import java.util.Collection;
17 import java.util.List;
19 import java.util.concurrent.ExecutorService;
20 import java.util.concurrent.Executors;
21 import java.util.concurrent.ScheduledFuture;
22 import java.util.concurrent.TimeUnit;
24 import org.eclipse.jdt.annotation.NonNullByDefault;
25 import org.eclipse.jdt.annotation.Nullable;
26 import org.openhab.binding.pilight.internal.IPilightCallback;
27 import org.openhab.binding.pilight.internal.PilightBridgeConfiguration;
28 import org.openhab.binding.pilight.internal.PilightConnector;
29 import org.openhab.binding.pilight.internal.discovery.PilightDeviceDiscoveryService;
30 import org.openhab.binding.pilight.internal.dto.Action;
31 import org.openhab.binding.pilight.internal.dto.Config;
32 import org.openhab.binding.pilight.internal.dto.DeviceType;
33 import org.openhab.binding.pilight.internal.dto.Status;
34 import org.openhab.binding.pilight.internal.dto.Version;
35 import org.openhab.core.common.NamedThreadFactory;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.binding.BaseBridgeHandler;
42 import org.openhab.core.thing.binding.ThingHandler;
43 import org.openhab.core.thing.binding.ThingHandlerService;
44 import org.openhab.core.types.Command;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * The {@link PilightBridgeHandler} is responsible dispatching commands for the child
50 * things to the Pilight daemon and sending status updates to the child things.
52 * @author Stefan Röllin - Initial contribution
53 * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
56 public class PilightBridgeHandler extends BaseBridgeHandler {
58 private static final int REFRESH_CONFIG_MSEC = 500;
60 private final Logger logger = LoggerFactory.getLogger(PilightBridgeHandler.class);
62 private @Nullable PilightConnector connector = null;
64 private @Nullable ScheduledFuture<?> refreshJob = null;
66 private @Nullable PilightDeviceDiscoveryService discoveryService = null;
68 private @Nullable ExecutorService connectorExecutor = null;
70 public PilightBridgeHandler(Bridge bridge) {
75 public void handleCommand(ChannelUID channelUID, Command command) {
76 logger.debug("Pilight Bridge is read-only and does not handle commands.");
80 public void initialize() {
81 PilightBridgeConfiguration config = getConfigAs(PilightBridgeConfiguration.class);
83 final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService;
84 PilightConnector connector = new PilightConnector(config, new IPilightCallback() {
86 public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail,
87 @Nullable String description) {
88 updateStatus(status, statusDetail, description);
89 if (status == ThingStatus.ONLINE) {
90 refreshConfigAndStatus();
95 public void statusReceived(List<Status> allStatus) {
96 for (Status status : allStatus) {
97 processStatus(status);
100 if (discoveryService != null) {
101 discoveryService.setStatus(allStatus);
106 public void configReceived(Config config) {
107 processConfig(config);
111 public void versionReceived(Version version) {
112 getThing().setProperty(Thing.PROPERTY_FIRMWARE_VERSION, version.getVersion());
116 updateStatus(ThingStatus.UNKNOWN);
118 ExecutorService connectorExecutor = Executors
119 .newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true));
120 connectorExecutor.execute(connector);
121 this.connectorExecutor = connectorExecutor;
122 this.connector = connector;
126 public void dispose() {
127 final @Nullable ScheduledFuture<?> future = this.refreshJob;
128 if (future != null) {
132 final @Nullable PilightConnector connector = this.connector;
133 if (connector != null) {
135 this.connector = null;
138 final @Nullable ExecutorService connectorExecutor = this.connectorExecutor;
139 if (connectorExecutor != null) {
140 connectorExecutor.shutdown();
141 this.connectorExecutor = null;
146 * Is background discovery for this bridge enabled?
148 * @return background discovery
150 public boolean isBackgroundDiscoveryEnabled() {
151 return getConfigAs(PilightBridgeConfiguration.class).getBackgroundDiscovery();
155 * send action to pilight daemon
157 * @param action action to send
159 public void sendAction(Action action) {
160 final @Nullable PilightConnector connector = this.connector;
161 if (connector != null) {
162 connector.sendAction(action);
167 * refresh config and status by requesting config and all values from pilight daemon
169 public synchronized void refreshConfigAndStatus() {
170 if (thing.getStatus() == ThingStatus.ONLINE) {
171 final @Nullable ScheduledFuture<?> refreshJob = this.refreshJob;
172 if (refreshJob == null || refreshJob.isCancelled() || refreshJob.isDone()) {
173 logger.debug("schedule refresh of config and status");
174 this.refreshJob = scheduler.schedule(this::doRefreshConfigAndStatus, REFRESH_CONFIG_MSEC,
175 TimeUnit.MILLISECONDS);
178 logger.warn("Bridge is not online - ignoring refresh of config and status.");
182 private void doRefreshConfigAndStatus() {
183 final @Nullable PilightConnector connector = this.connector;
184 if (connector != null) {
185 // the config is required for dimmers to get the minimum and maximum dim levels
186 connector.refreshConfig();
187 connector.refreshStatus();
192 * Processes a status update received from pilight
194 * @param status The new Status
196 private void processStatus(Status status) {
197 final Integer type = status.getType();
198 logger.trace("processStatus device '{}' type {}", status.getDevices().get(0), type);
200 if (!DeviceType.SERVER.equals(type)) {
201 for (Thing thing : getThing().getThings()) {
202 final @Nullable ThingHandler handler = thing.getHandler();
203 if (handler instanceof PilightBaseHandler baseHandler) {
204 baseHandler.updateFromStatusIfMatches(status);
211 public Collection<Class<? extends ThingHandlerService>> getServices() {
212 return Set.of(PilightDeviceDiscoveryService.class);
216 * Register discovery service to this bridge instance.
218 public boolean registerDiscoveryListener(PilightDeviceDiscoveryService listener) {
219 if (discoveryService == null) {
220 discoveryService = listener;
227 * Unregister discovery service from this bridge instance.
229 public boolean unregisterDiscoveryListener() {
230 if (discoveryService != null) {
231 discoveryService = null;
239 * Processes a config received from pilight
241 * @param config The new config
243 private void processConfig(Config config) {
244 for (Thing thing : getThing().getThings()) {
245 final @Nullable ThingHandler handler = thing.getHandler();
246 if (handler instanceof PilightBaseHandler baseHandler) {
247 baseHandler.updateFromConfigIfMatches(config);
251 final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService;
252 if (discoveryService != null) {
253 discoveryService.setConfig(config);