2 * Copyright (c) 2010-2024 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.pilight.internal.handler;
15 import java.util.Collection;
16 import java.util.List;
18 import java.util.concurrent.ExecutorService;
19 import java.util.concurrent.Executors;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.eclipse.jdt.annotation.NonNullByDefault;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.openhab.binding.pilight.internal.IPilightCallback;
26 import org.openhab.binding.pilight.internal.PilightBridgeConfiguration;
27 import org.openhab.binding.pilight.internal.PilightConnector;
28 import org.openhab.binding.pilight.internal.discovery.PilightDeviceDiscoveryService;
29 import org.openhab.binding.pilight.internal.dto.Action;
30 import org.openhab.binding.pilight.internal.dto.Config;
31 import org.openhab.binding.pilight.internal.dto.DeviceType;
32 import org.openhab.binding.pilight.internal.dto.Status;
33 import org.openhab.binding.pilight.internal.dto.Version;
34 import org.openhab.core.common.NamedThreadFactory;
35 import org.openhab.core.thing.Bridge;
36 import org.openhab.core.thing.ChannelUID;
37 import org.openhab.core.thing.Thing;
38 import org.openhab.core.thing.ThingStatus;
39 import org.openhab.core.thing.ThingStatusDetail;
40 import org.openhab.core.thing.binding.BaseBridgeHandler;
41 import org.openhab.core.thing.binding.ThingHandler;
42 import org.openhab.core.thing.binding.ThingHandlerService;
43 import org.openhab.core.types.Command;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
48 * The {@link PilightBridgeHandler} is responsible dispatching commands for the child
49 * things to the Pilight daemon and sending status updates to the child things.
51 * @author Stefan Röllin - Initial contribution
52 * @author Niklas Dörfler - Port pilight binding to openHAB 3 + add device discovery
55 public class PilightBridgeHandler extends BaseBridgeHandler {
57 private static final int REFRESH_CONFIG_MSEC = 500;
59 private final Logger logger = LoggerFactory.getLogger(PilightBridgeHandler.class);
61 private @Nullable PilightConnector connector = null;
63 private @Nullable ScheduledFuture<?> refreshJob = null;
65 private @Nullable PilightDeviceDiscoveryService discoveryService = null;
67 private @Nullable ExecutorService connectorExecutor = null;
69 public PilightBridgeHandler(Bridge bridge) {
74 public void handleCommand(ChannelUID channelUID, Command command) {
75 logger.debug("Pilight Bridge is read-only and does not handle commands.");
79 public void initialize() {
80 PilightBridgeConfiguration config = getConfigAs(PilightBridgeConfiguration.class);
82 final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService;
83 PilightConnector connector = new PilightConnector(config, new IPilightCallback() {
85 public void updateThingStatus(ThingStatus status, ThingStatusDetail statusDetail,
86 @Nullable String description) {
87 updateStatus(status, statusDetail, description);
88 if (status == ThingStatus.ONLINE) {
89 refreshConfigAndStatus();
94 public void statusReceived(List<Status> allStatus) {
95 for (Status status : allStatus) {
96 processStatus(status);
99 if (discoveryService != null) {
100 discoveryService.setStatus(allStatus);
105 public void configReceived(Config config) {
106 processConfig(config);
110 public void versionReceived(Version version) {
111 getThing().setProperty(Thing.PROPERTY_FIRMWARE_VERSION, version.getVersion());
115 updateStatus(ThingStatus.UNKNOWN);
117 ExecutorService connectorExecutor = Executors
118 .newSingleThreadExecutor(new NamedThreadFactory(getThing().getUID().getAsString(), true));
119 connectorExecutor.execute(connector);
120 this.connectorExecutor = connectorExecutor;
121 this.connector = connector;
125 public void dispose() {
126 final @Nullable ScheduledFuture<?> future = this.refreshJob;
127 if (future != null) {
131 final @Nullable PilightConnector connector = this.connector;
132 if (connector != null) {
134 this.connector = null;
137 final @Nullable ExecutorService connectorExecutor = this.connectorExecutor;
138 if (connectorExecutor != null) {
139 connectorExecutor.shutdown();
140 this.connectorExecutor = null;
145 * Is background discovery for this bridge enabled?
147 * @return background discovery
149 public boolean isBackgroundDiscoveryEnabled() {
150 return getConfigAs(PilightBridgeConfiguration.class).getBackgroundDiscovery();
154 * send action to pilight daemon
156 * @param action action to send
158 public void sendAction(Action action) {
159 final @Nullable PilightConnector connector = this.connector;
160 if (connector != null) {
161 connector.sendAction(action);
166 * refresh config and status by requesting config and all values from pilight daemon
168 public synchronized void refreshConfigAndStatus() {
169 if (thing.getStatus() == ThingStatus.ONLINE) {
170 final @Nullable ScheduledFuture<?> refreshJob = this.refreshJob;
171 if (refreshJob == null || refreshJob.isCancelled() || refreshJob.isDone()) {
172 logger.debug("schedule refresh of config and status");
173 this.refreshJob = scheduler.schedule(this::doRefreshConfigAndStatus, REFRESH_CONFIG_MSEC,
174 TimeUnit.MILLISECONDS);
177 logger.warn("Bridge is not online - ignoring refresh of config and status.");
181 private void doRefreshConfigAndStatus() {
182 final @Nullable PilightConnector connector = this.connector;
183 if (connector != null) {
184 // the config is required for dimmers to get the minimum and maximum dim levels
185 connector.refreshConfig();
186 connector.refreshStatus();
191 * Processes a status update received from pilight
193 * @param status The new Status
195 private void processStatus(Status status) {
196 final Integer type = status.getType();
197 logger.trace("processStatus device '{}' type {}", status.getDevices().get(0), type);
199 if (!DeviceType.SERVER.equals(type)) {
200 for (Thing thing : getThing().getThings()) {
201 final @Nullable ThingHandler handler = thing.getHandler();
202 if (handler instanceof PilightBaseHandler baseHandler) {
203 baseHandler.updateFromStatusIfMatches(status);
210 public Collection<Class<? extends ThingHandlerService>> getServices() {
211 return Set.of(PilightDeviceDiscoveryService.class);
215 * Register discovery service to this bridge instance.
217 public boolean registerDiscoveryListener(PilightDeviceDiscoveryService listener) {
218 if (discoveryService == null) {
219 discoveryService = listener;
226 * Unregister discovery service from this bridge instance.
228 public boolean unregisterDiscoveryListener() {
229 if (discoveryService != null) {
230 discoveryService = null;
238 * Processes a config received from pilight
240 * @param config The new config
242 private void processConfig(Config config) {
243 for (Thing thing : getThing().getThings()) {
244 final @Nullable ThingHandler handler = thing.getHandler();
245 if (handler instanceof PilightBaseHandler baseHandler) {
246 baseHandler.updateFromConfigIfMatches(config);
250 final @Nullable PilightDeviceDiscoveryService discoveryService = this.discoveryService;
251 if (discoveryService != null) {
252 discoveryService.setConfig(config);