]> git.basschouten.com Git - openhab-addons.git/blob
a7b903eeb88f4380ccc8a33e74162d3723222105
[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.lutron.internal.grxprg;
14
15 import java.io.IOException;
16 import java.time.ZonedDateTime;
17 import java.util.GregorianCalendar;
18 import java.util.concurrent.ScheduledFuture;
19 import java.util.concurrent.TimeUnit;
20
21 import org.openhab.core.library.types.DateTimeType;
22 import org.openhab.core.library.types.DecimalType;
23 import org.openhab.core.thing.Bridge;
24 import org.openhab.core.thing.ChannelUID;
25 import org.openhab.core.thing.Thing;
26 import org.openhab.core.thing.ThingStatus;
27 import org.openhab.core.thing.ThingStatusDetail;
28 import org.openhab.core.thing.binding.BaseBridgeHandler;
29 import org.openhab.core.types.Command;
30 import org.openhab.core.types.RefreshType;
31 import org.openhab.core.types.State;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * The {@link PrgBridgeHandler} is responsible for handling all bridge interactions. This includes management of the
37  * connection and processing of any commands (thru the {@link PrgProtocolHandler}).
38  *
39  * @author Tim Roberts - Initial contribution
40  */
41 public class PrgBridgeHandler extends BaseBridgeHandler {
42
43     private Logger logger = LoggerFactory.getLogger(PrgBridgeHandler.class);
44
45     /**
46      * The {@link PrgProtocolHandler} that handles the actual protocol. Will never be null
47      */
48     private PrgProtocolHandler protocolHandler;
49
50     /**
51      * The {@link SocketSession} to the physical devices. Will never be null
52      */
53     private SocketSession session;
54
55     /**
56      * The retry connection event. Null if not retrying.
57      */
58     private ScheduledFuture<?> retryConnectionJob;
59
60     /**
61      * Constructs the handler from the {@link Bridge}. Simply calls the super constructor with the {@link Bridge},
62      * creates the session (unconnected) and the protocol handler.
63      *
64      * @param bridge a non-null {@link Bridge} the handler is for
65      */
66     public PrgBridgeHandler(Bridge bridge) {
67         super(bridge);
68
69         if (bridge == null) {
70             throw new IllegalArgumentException("thing cannot be null");
71         }
72
73         final PrgBridgeConfig config = getPrgBridgeConfig();
74         session = new SocketSession(getThing().getUID().getAsString(), config.getIpAddress(), 23);
75
76         protocolHandler = new PrgProtocolHandler(session, new PrgHandlerCallback() {
77             @Override
78             public void stateChanged(String channelId, State state) {
79                 updateState(channelId, state);
80             }
81
82             @Override
83             public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
84                 updateStatus(status, detail, msg);
85
86                 if (status != ThingStatus.ONLINE) {
87                     disconnect(true);
88                 }
89             }
90
91             @Override
92             public void stateChanged(int controlUnit, String channelId, State state) {
93                 getGrafikEyeHandler(controlUnit).stateChanged(channelId, state);
94             }
95
96             @Override
97             public boolean isShade(int controlUnit, int zone) {
98                 return getGrafikEyeHandler(controlUnit).isShade(zone);
99             }
100         });
101     }
102
103     /**
104      * Internal method to retrieve the {@link PrgProtocolHandler} used by the bridge
105      *
106      * @return a non-null protocol handler to use
107      */
108     PrgProtocolHandler getProtocolHandler() {
109         return protocolHandler;
110     }
111
112     /**
113      * Helper method used to retrieve the {@link GrafikEyeHandler} for a given control unit number. If not found, an
114      * IllegalArgumentException will be thrown.
115      *
116      * @param controlUnit a control number to retrieve
117      * @return a non-null {@link GrafikEyeHandler}
118      * @throws IllegalArgumentException if the {@link GrafikEyeHandler} for the given controlUnit was not found.
119      */
120     private GrafikEyeHandler getGrafikEyeHandler(int controlUnit) {
121         for (Thing thing : getThing().getThings()) {
122             if (thing.getHandler() instanceof GrafikEyeHandler) {
123                 final GrafikEyeHandler handler = (GrafikEyeHandler) thing.getHandler();
124                 if (handler.getControlUnit() == controlUnit) {
125                     return handler;
126                 }
127             } else {
128                 logger.warn("Should not be a non-GrafikEyeHandler as a thing to this bridge - ignoring: {}", thing);
129             }
130         }
131
132         throw new IllegalArgumentException("Could not find a GrafikEyeHandler for control unit : " + controlUnit);
133     }
134
135     /**
136      * {@inheritDoc}
137      *
138      * Handles commands to specific channels. This implementation will offload much of its work to the
139      * {@link PrgProtocolHandler}. Basically we validate the type of command for the channel then call the
140      * {@link PrgProtocolHandler} to handle the actual protocol. Special use case is the {@link RefreshType}
141      * where we call {{@link #handleRefresh(String)} to handle a refresh of the specific channel (which in turn calls
142      * {@link PrgProtocolHandler} to handle the actual refresh
143      */
144     @Override
145     public void handleCommand(ChannelUID channelUID, Command command) {
146         if (command instanceof RefreshType) {
147             handleRefresh(channelUID.getId());
148             return;
149         }
150
151         // if (getThing().getStatus() != ThingStatus.ONLINE) {
152         // // Ignore any command if not online
153         // return;
154         // }
155
156         String id = channelUID.getId();
157
158         if (id == null) {
159             logger.warn("Called with a null channel id - ignoring");
160             return;
161         }
162
163         if (id.equals(PrgConstants.CHANNEL_ZONELOWERSTOP)) {
164             protocolHandler.setZoneLowerStop();
165         } else if (id.equals(PrgConstants.CHANNEL_ZONERAISESTOP)) {
166             protocolHandler.setZoneRaiseStop();
167         } else if (id.equals(PrgConstants.CHANNEL_TIMECLOCK)) {
168             if (command instanceof DateTimeType dateTime) {
169                 final ZonedDateTime zdt = dateTime.getZonedDateTime();
170                 protocolHandler.setTime(GregorianCalendar.from(zdt));
171             } else {
172                 logger.error("Received a TIMECLOCK channel command with a non DateTimeType: {}", command);
173             }
174         } else if (id.startsWith(PrgConstants.CHANNEL_SCHEDULE)) {
175             if (command instanceof DecimalType scheduleCommand) {
176                 final int schedule = scheduleCommand.intValue();
177                 protocolHandler.selectSchedule(schedule);
178             } else {
179                 logger.error("Received a SCHEDULE channel command with a non DecimalType: {}", command);
180             }
181         } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCESTART)) {
182             protocolHandler.startSuperSequence();
183         } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCEPAUSE)) {
184             protocolHandler.pauseSuperSequence();
185         } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCERESUME)) {
186             protocolHandler.resumeSuperSequence();
187         } else {
188             logger.error("Unknown/Unsupported Channel id: {}", id);
189         }
190     }
191
192     /**
193      * Method that handles the {@link RefreshType} command specifically. Calls the {@link PrgProtocolHandler} to
194      * handle the actual refresh based on the channel id.
195      *
196      * @param id a non-null, possibly empty channel id to refresh
197      */
198     private void handleRefresh(String id) {
199         if (getThing().getStatus() != ThingStatus.ONLINE) {
200             return;
201         }
202
203         if (id.equals(PrgConstants.CHANNEL_TIMECLOCK)) {
204             protocolHandler.refreshTime();
205         } else if (id.equals(PrgConstants.CHANNEL_SCHEDULE)) {
206             protocolHandler.refreshSchedule();
207         } else if (id.equals(PrgConstants.CHANNEL_SUNRISE)) {
208             protocolHandler.refreshSunriseSunset();
209         } else if (id.equals(PrgConstants.CHANNEL_SUNSET)) {
210             protocolHandler.refreshSunriseSunset();
211         } else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCESTATUS)) {
212             protocolHandler.reportSuperSequenceStatus();
213         } else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTSTEP)) {
214             protocolHandler.reportSuperSequenceStatus();
215         } else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTMIN)) {
216             protocolHandler.reportSuperSequenceStatus();
217         } else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTSEC)) {
218             protocolHandler.reportSuperSequenceStatus();
219         }
220     }
221
222     /**
223      * {@inheritDoc}
224      *
225      * Initializes the handler. This initialization will read/validate the configuration and will attempt to connect to
226      * the switch (via {{@link #retryConnect()}.
227      */
228     @Override
229     public void initialize() {
230         final PrgBridgeConfig config = getPrgBridgeConfig();
231
232         if (config == null) {
233             return;
234         }
235
236         if (config.getIpAddress() == null || config.getIpAddress().trim().length() == 0) {
237             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
238                     "IP Address/Host Name of GRX-PRG/GRX-CI-PRG is missing from configuration");
239             return;
240         }
241
242         // Try initial connection in a scheduled task
243         this.scheduler.schedule(new Runnable() {
244             @Override
245             public void run() {
246                 connect();
247             }
248         }, 1, TimeUnit.SECONDS);
249     }
250
251     /**
252      * Attempts to connect to the PRG unit. If successfully connect, the {@link PrgProtocolHandler#login()} will be
253      * called to log into the unit. If a connection cannot be established (or login failed), the connection attempt will
254      * be retried later (via {@link #retryConnect()})
255      */
256     private void connect() {
257         final PrgBridgeConfig config = getPrgBridgeConfig();
258
259         String response = "Server is offline - will try to reconnect later";
260         try {
261             logger.info("Attempting connection ...");
262             session.connect();
263
264             response = protocolHandler.login(config.getUserName());
265             if (response == null) {
266                 if (config != null) {
267                     updateStatus(ThingStatus.ONLINE);
268                     return;
269                 }
270             }
271         } catch (Exception e) {
272             logger.error("Exception during connection attempt", e);
273         }
274
275         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, response);
276         retryConnect();
277     }
278
279     /**
280      * Attempts to disconnect from the session and will optionally retry the connection attempt.
281      *
282      * @param retryConnection true to retry connection attempts after the disconnect
283      */
284     private void disconnect(boolean retryConnection) {
285         try {
286             session.disconnect();
287         } catch (IOException e) {
288             // ignore - we don't care
289         }
290
291         if (retryConnection) {
292             retryConnect();
293         }
294     }
295
296     /**
297      * Retries the connection attempt - schedules a job in {@link PrgBridgeConfig#getRetryPolling()} seconds to
298      * call the {@link #connect()} method. If a retry attempt is pending, the request is ignored.
299      */
300     private void retryConnect() {
301         if (retryConnectionJob == null) {
302             final PrgBridgeConfig config = getPrgBridgeConfig();
303             if (config != null) {
304                 logger.info("Will try to reconnect in {} seconds", config.getRetryPolling());
305                 retryConnectionJob = this.scheduler.schedule(new Runnable() {
306                     @Override
307                     public void run() {
308                         retryConnectionJob = null;
309                         connect();
310                     }
311                 }, config.getRetryPolling(), TimeUnit.SECONDS);
312             }
313         } else {
314             logger.debug("RetryConnection called when a retry connection is pending - ignoring request");
315         }
316     }
317
318     /**
319      * Simplu gets the {@link PrgBridgeConfig} from the {@link Thing} and will set the status to offline if not
320      * found.
321      *
322      * @return a possible null {@link PrgBridgeConfig}
323      */
324     private PrgBridgeConfig getPrgBridgeConfig() {
325         final PrgBridgeConfig config = getThing().getConfiguration().as(PrgBridgeConfig.class);
326
327         if (config == null) {
328             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
329         }
330
331         return config;
332     }
333
334     /**
335      * {@inheritDoc}
336      *
337      * Disposes of the handler. Will simply call {@link #disconnect(boolean)} to disconnect and NOT retry the
338      * connection
339      */
340     @Override
341     public void dispose() {
342         disconnect(false);
343     }
344 }