]> git.basschouten.com Git - openhab-addons.git/blob
276657a147013770310c07a2a1d9eefc80e5c31a
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2021 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
174         updateStatus(ThingStatus.ONLINE);
175     }
176
177     @Override
178     public synchronized void dispose() {
179         if (updateJob != null && !updateJob.isCancelled()) {
180             updateJob.cancel(true);
181             updateJob = null;
182         }
183         if (communicator != null) {
184             communicator.unregister(responseListener);
185         }
186     }
187
188     @Override
189     public void channelLinked(ChannelUID channelUID) {
190         LGSerialCommand command = CommandFactory.createCommandFor(channelUID, responseListener);
191         if (command == null) {
192             logger.warn(
193                     "A command could not be found for channel name '{}'. Please create an issue on the openhab project for the lgtvserial binding. ",
194                     channelUID.getId());
195             return;
196         }
197         this.channelCommands.put(channelUID, command);
198         handleCommand(channelUID, RefreshType.REFRESH);
199     }
200
201     @Override
202     public void channelUnlinked(ChannelUID channelUID) {
203         this.channelCommands.remove(channelUID);
204     }
205
206     @Override
207     public void handleCommand(ChannelUID channelUID, Command command) {
208         try {
209             if (command instanceof RefreshType) {
210                 channelCommands.get(channelUID).execute(channelUID, communicator, null);
211             } else {
212                 channelCommands.get(channelUID).execute(channelUID, communicator, command);
213             }
214         } catch (IOException e) {
215             logger.warn("Serial port write error", e);
216         }
217     }
218
219     private Runnable eventRunnable = () -> {
220         synchronized (channelCommands) {
221             for (Map.Entry<ChannelUID, LGSerialCommand> entry : channelCommands.entrySet()) {
222                 if (Thread.currentThread().isInterrupted()) {
223                     logger.debug("Thread interrupted, stopping");
224                     break;
225                 }
226                 try {
227                     entry.getValue().execute(entry.getKey(), communicator, null);
228                 } catch (IOException e) {
229                     logger.warn("An error occured while sending an update command for {}: {}", entry.getKey(),
230                             e.getMessage());
231                 }
232             }
233         }
234     };
235 }