]> git.basschouten.com Git - openhab-addons.git/blob
de223cde9963c0081128f058daf70fa54287a2ea
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2023 Contributors to the openHAB project
3  *
4  * See the NOTICE file(s) distributed with this work for additional
5  * information.
6  *
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
10  *
11  * SPDX-License-Identifier: EPL-2.0
12  */
13 package org.openhab.binding.lgtvserial.internal.handler;
14
15 import java.io.IOException;
16 import java.math.BigDecimal;
17 import java.util.Collections;
18 import java.util.HashMap;
19 import java.util.Map;
20 import java.util.concurrent.ScheduledFuture;
21 import java.util.concurrent.TimeUnit;
22
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;
43
44 /**
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.
47  *
48  * @author Marius Bjoernstad - Initial contribution
49  * @author Richard Lavoie - Major rework to add many more channels and support daisy chaining
50  */
51 public class LgTvSerialHandler extends BaseThingHandler {
52
53     /**
54      * Interval at which to send update refresh commands.
55      */
56     private static final int EVENT_REFRESH_INTERVAL = 120;
57
58     /**
59      * Logger.
60      */
61     private final Logger logger = LoggerFactory.getLogger(LgTvSerialHandler.class);
62
63     /**
64      * Serial communicator factory used to retrieve the communicator for a given port.
65      */
66     private final SerialCommunicatorFactory factory;
67
68     /**
69      * Serial port manager used to get serial port identifiers.
70      */
71     private final SerialPortManager serialPortManager;
72
73     /**
74      * Communicator used to send commands to the TV(s).
75      */
76     private LGSerialCommunicator communicator;
77
78     /**
79      * List of linked items used for the refresh polling.
80      */
81     private Map<ChannelUID, LGSerialCommand> channelCommands = Collections.synchronizedMap(new HashMap<>());
82
83     /**
84      * Set ID of this TV.
85      */
86     private LGSerialResponseListener responseListener;
87
88     /**
89      * Polling updater job.
90      */
91     private ScheduledFuture<?> updateJob;
92
93     /**
94      * Create the LG TV hander.
95      *
96      * @param thing Thing associated to this handler
97      * @param factory Factory to retrieve a communicator for a given port
98      */
99     public LgTvSerialHandler(Thing thing, SerialCommunicatorFactory factory, SerialPortManager serialPortManager) {
100         super(thing);
101         this.factory = factory;
102         this.serialPortManager = serialPortManager;
103     }
104
105     @Override
106     public synchronized void initialize() {
107         String portName = (String) getThing().getConfiguration().get("port");
108         BigDecimal setIdParam = (BigDecimal) getThing().getConfiguration().get("setId");
109         int setId = 1;
110         if (setIdParam != null) {
111             setId = setIdParam.intValue();
112         }
113         final int set = setId;
114         responseListener = new LGSerialResponseListener() {
115             @Override
116             public int getSetID() {
117                 return set;
118             }
119
120             @Override
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);
125             }
126
127             @Override
128             public void onFailure(ChannelUID channel, LGSerialResponse response) {
129                 logger.debug("Received error response for channel {}: {}", channel, response.getState());
130             }
131         };
132
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);
138                 return;
139             }
140
141             try {
142                 communicator = factory.getInstance(serialPortIdentifier);
143             } catch (IOException e) {
144                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
145                 return;
146             } catch (PortInUseException e) {
147                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
148                         "Serial port already used: " + portName);
149                 return;
150             } catch (UnsupportedCommOperationException e) {
151                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
152                         "Unsupported operation on '" + portName + "': " + e.getMessage());
153                 return;
154             }
155
156             if (communicator != null) {
157                 communicator.register(responseListener);
158             } else {
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);
162                 return;
163             }
164         } else {
165             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Serial port name not configured");
166             logger.debug("Serial port name not configured");
167             return;
168         }
169
170         if (updateJob == null || updateJob.isCancelled()) {
171             updateJob = scheduler.scheduleWithFixedDelay(eventRunnable, 0, EVENT_REFRESH_INTERVAL, TimeUnit.SECONDS);
172         }
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());
177             }
178         });
179
180         updateStatus(ThingStatus.ONLINE);
181     }
182
183     @Override
184     public synchronized void dispose() {
185         if (updateJob != null && !updateJob.isCancelled()) {
186             updateJob.cancel(true);
187             updateJob = null;
188         }
189         if (communicator != null) {
190             communicator.unregister(responseListener);
191         }
192     }
193
194     @Override
195     public void channelLinked(ChannelUID channelUID) {
196         LGSerialCommand command = CommandFactory.createCommandFor(channelUID, responseListener);
197         if (command == null) {
198             logger.warn(
199                     "A command could not be found for channel name '{}'. Please create an issue on the openhab project for the lgtvserial binding. ",
200                     channelUID.getId());
201             return;
202         }
203         this.channelCommands.put(channelUID, command);
204         handleCommand(channelUID, RefreshType.REFRESH);
205     }
206
207     @Override
208     public void channelUnlinked(ChannelUID channelUID) {
209         this.channelCommands.remove(channelUID);
210     }
211
212     @Override
213     public void handleCommand(ChannelUID channelUID, Command command) {
214         try {
215             if (command instanceof RefreshType) {
216                 channelCommands.get(channelUID).execute(channelUID, communicator, null);
217             } else {
218                 channelCommands.get(channelUID).execute(channelUID, communicator, command);
219             }
220         } catch (IOException e) {
221             logger.warn("Serial port write error", e);
222         }
223     }
224
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");
230                     break;
231                 }
232                 try {
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(),
236                             e.getMessage());
237                 }
238             }
239         }
240     };
241 }