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.lgtvserial.internal.handler;
15 import java.io.IOException;
16 import java.math.BigDecimal;
17 import java.util.Collections;
18 import java.util.HashMap;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
23 import org.openhab.binding.lgtvserial.internal.protocol.serial.LGSerialCommand;
24 import org.openhab.binding.lgtvserial.internal.protocol.serial.LGSerialCommunicator;
25 import org.openhab.binding.lgtvserial.internal.protocol.serial.LGSerialResponse;
26 import org.openhab.binding.lgtvserial.internal.protocol.serial.LGSerialResponseListener;
27 import org.openhab.binding.lgtvserial.internal.protocol.serial.SerialCommunicatorFactory;
28 import org.openhab.binding.lgtvserial.internal.protocol.serial.commands.CommandFactory;
29 import org.openhab.core.io.transport.serial.PortInUseException;
30 import org.openhab.core.io.transport.serial.SerialPortIdentifier;
31 import org.openhab.core.io.transport.serial.SerialPortManager;
32 import org.openhab.core.io.transport.serial.UnsupportedCommOperationException;
33 import org.openhab.core.thing.ChannelUID;
34 import org.openhab.core.thing.Thing;
35 import org.openhab.core.thing.ThingStatus;
36 import org.openhab.core.thing.ThingStatusDetail;
37 import org.openhab.core.thing.binding.BaseThingHandler;
38 import org.openhab.core.types.Command;
39 import org.openhab.core.types.RefreshType;
40 import org.openhab.core.types.State;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
45 * The {@link LgTvSerialHandler} contains all the logic of this simple binding. It
46 * is responsible for handling commands and sending them to the serial port.
48 * @author Marius Bjoernstad - Initial contribution
49 * @author Richard Lavoie - Major rework to add many more channels and support daisy chaining
51 public class LgTvSerialHandler extends BaseThingHandler {
54 * Interval at which to send update refresh commands.
56 private static final int EVENT_REFRESH_INTERVAL = 120;
61 private final Logger logger = LoggerFactory.getLogger(LgTvSerialHandler.class);
64 * Serial communicator factory used to retrieve the communicator for a given port.
66 private final SerialCommunicatorFactory factory;
69 * Serial port manager used to get serial port identifiers.
71 private final SerialPortManager serialPortManager;
74 * Communicator used to send commands to the TV(s).
76 private LGSerialCommunicator communicator;
79 * List of linked items used for the refresh polling.
81 private Map<ChannelUID, LGSerialCommand> channelCommands = Collections.synchronizedMap(new HashMap<>());
86 private LGSerialResponseListener responseListener;
89 * Polling updater job.
91 private ScheduledFuture<?> updateJob;
94 * Create the LG TV hander.
96 * @param thing Thing associated to this handler
97 * @param factory Factory to retrieve a communicator for a given port
99 public LgTvSerialHandler(Thing thing, SerialCommunicatorFactory factory, SerialPortManager serialPortManager) {
101 this.factory = factory;
102 this.serialPortManager = serialPortManager;
106 public synchronized void initialize() {
107 String portName = (String) getThing().getConfiguration().get("port");
108 BigDecimal setIdParam = (BigDecimal) getThing().getConfiguration().get("setId");
110 if (setIdParam != null) {
111 setId = setIdParam.intValue();
113 final int set = setId;
114 responseListener = new LGSerialResponseListener() {
116 public int getSetID() {
121 public void onSuccess(ChannelUID channel, LGSerialResponse response) {
122 State state = response.getState();
123 logger.debug("Updating channel {} with value {}", channel, state);
124 updateState(channel, state);
128 public void onFailure(ChannelUID channel, LGSerialResponse response) {
129 logger.debug("Received error response for channel {}: {}", channel, response.getState());
133 if (portName != null) {
134 SerialPortIdentifier serialPortIdentifier = serialPortManager.getIdentifier(portName);
135 if (serialPortIdentifier == null) {
136 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
137 "Serial port does not exist: " + portName);
142 communicator = factory.getInstance(serialPortIdentifier);
143 } catch (IOException e) {
144 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
146 } catch (PortInUseException e) {
147 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
148 "Serial port already used: " + portName);
150 } catch (UnsupportedCommOperationException e) {
151 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
152 "Unsupported operation on '" + portName + "': " + e.getMessage());
156 if (communicator != null) {
157 communicator.register(responseListener);
159 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
160 "Failed to connect to serial port " + portName);
161 logger.debug("Failed to connect to serial port {}", portName);
165 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port name not configured");
166 logger.debug("Serial port name not configured");
170 if (updateJob == null || updateJob.isCancelled()) {
171 updateJob = scheduler.scheduleWithFixedDelay(eventRunnable, 0, EVENT_REFRESH_INTERVAL, TimeUnit.SECONDS);
173 // trigger REFRESH commands for all linked Channels to start polling
174 getThing().getChannels().forEach(channel -> {
175 if (isLinked(channel.getUID())) {
176 channelLinked(channel.getUID());
180 updateStatus(ThingStatus.ONLINE);
184 public synchronized void dispose() {
185 if (updateJob != null && !updateJob.isCancelled()) {
186 updateJob.cancel(true);
189 if (communicator != null) {
190 communicator.unregister(responseListener);
195 public void channelLinked(ChannelUID channelUID) {
196 LGSerialCommand command = CommandFactory.createCommandFor(channelUID, responseListener);
197 if (command == null) {
199 "A command could not be found for channel name '{}'. Please create an issue on the openhab project for the lgtvserial binding. ",
203 this.channelCommands.put(channelUID, command);
204 handleCommand(channelUID, RefreshType.REFRESH);
208 public void channelUnlinked(ChannelUID channelUID) {
209 this.channelCommands.remove(channelUID);
213 public void handleCommand(ChannelUID channelUID, Command command) {
215 if (command instanceof RefreshType) {
216 channelCommands.get(channelUID).execute(channelUID, communicator, null);
218 channelCommands.get(channelUID).execute(channelUID, communicator, command);
220 } catch (IOException e) {
221 logger.warn("Serial port write error", e);
225 private Runnable eventRunnable = () -> {
226 synchronized (channelCommands) {
227 for (Map.Entry<ChannelUID, LGSerialCommand> entry : channelCommands.entrySet()) {
228 if (Thread.currentThread().isInterrupted()) {
229 logger.debug("Thread interrupted, stopping");
233 entry.getValue().execute(entry.getKey(), communicator, null);
234 } catch (IOException e) {
235 logger.warn("An error occured while sending an update command for {}: {}", entry.getKey(),