]> git.basschouten.com Git - openhab-addons.git/blob
a20e41e7a2f2fd51fc7ad1f2beef79fda3390074
[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
166         } else if (id.equals(PrgConstants.CHANNEL_ZONERAISESTOP)) {
167             protocolHandler.setZoneRaiseStop();
168
169         } else if (id.equals(PrgConstants.CHANNEL_TIMECLOCK)) {
170             if (command instanceof DateTimeType) {
171                 final ZonedDateTime zdt = ((DateTimeType) command).getZonedDateTime();
172                 protocolHandler.setTime(GregorianCalendar.from(zdt));
173             } else {
174                 logger.error("Received a TIMECLOCK channel command with a non DateTimeType: {}", command);
175             }
176         } else if (id.startsWith(PrgConstants.CHANNEL_SCHEDULE)) {
177             if (command instanceof DecimalType) {
178                 final int schedule = ((DecimalType) command).intValue();
179                 protocolHandler.selectSchedule(schedule);
180             } else {
181                 logger.error("Received a SCHEDULE channel command with a non DecimalType: {}", command);
182             }
183
184         } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCESTART)) {
185             protocolHandler.startSuperSequence();
186
187         } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCEPAUSE)) {
188             protocolHandler.pauseSuperSequence();
189         } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCERESUME)) {
190             protocolHandler.resumeSuperSequence();
191
192         } else {
193             logger.error("Unknown/Unsupported Channel id: {}", id);
194         }
195     }
196
197     /**
198      * Method that handles the {@link RefreshType} command specifically. Calls the {@link PrgProtocolHandler} to
199      * handle the actual refresh based on the channel id.
200      *
201      * @param id a non-null, possibly empty channel id to refresh
202      */
203     private void handleRefresh(String id) {
204         if (getThing().getStatus() != ThingStatus.ONLINE) {
205             return;
206         }
207
208         if (id.equals(PrgConstants.CHANNEL_TIMECLOCK)) {
209             protocolHandler.refreshTime();
210
211         } else if (id.equals(PrgConstants.CHANNEL_SCHEDULE)) {
212             protocolHandler.refreshSchedule();
213
214         } else if (id.equals(PrgConstants.CHANNEL_SUNRISE)) {
215             protocolHandler.refreshSunriseSunset();
216
217         } else if (id.equals(PrgConstants.CHANNEL_SUNSET)) {
218             protocolHandler.refreshSunriseSunset();
219
220         } else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCESTATUS)) {
221             protocolHandler.reportSuperSequenceStatus();
222         } else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTSTEP)) {
223             protocolHandler.reportSuperSequenceStatus();
224         } else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTMIN)) {
225             protocolHandler.reportSuperSequenceStatus();
226         } else if (id.equals(PrgConstants.CHANNEL_SUPERSEQUENCENEXTSEC)) {
227             protocolHandler.reportSuperSequenceStatus();
228         }
229     }
230
231     /**
232      * {@inheritDoc}
233      *
234      * Initializes the handler. This initialization will read/validate the configuration and will attempt to connect to
235      * the switch (via {{@link #retryConnect()}.
236      */
237     @Override
238     public void initialize() {
239         final PrgBridgeConfig config = getPrgBridgeConfig();
240
241         if (config == null) {
242             return;
243         }
244
245         if (config.getIpAddress() == null || config.getIpAddress().trim().length() == 0) {
246             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
247                     "IP Address/Host Name of GRX-PRG/GRX-CI-PRG is missing from configuration");
248             return;
249         }
250
251         // Try initial connection in a scheduled task
252         this.scheduler.schedule(new Runnable() {
253             @Override
254             public void run() {
255                 connect();
256             }
257         }, 1, TimeUnit.SECONDS);
258     }
259
260     /**
261      * Attempts to connect to the PRG unit. If successfully connect, the {@link PrgProtocolHandler#login()} will be
262      * called to log into the unit. If a connection cannot be established (or login failed), the connection attempt will
263      * be retried later (via {@link #retryConnect()})
264      */
265     private void connect() {
266         final PrgBridgeConfig config = getPrgBridgeConfig();
267
268         String response = "Server is offline - will try to reconnect later";
269         try {
270             logger.info("Attempting connection ...");
271             session.connect();
272
273             response = protocolHandler.login(config.getUserName());
274             if (response == null) {
275                 if (config != null) {
276                     updateStatus(ThingStatus.ONLINE);
277                     return;
278                 }
279             }
280
281         } catch (Exception e) {
282             logger.error("Exception during connection attempt", e);
283         }
284
285         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, response);
286         retryConnect();
287     }
288
289     /**
290      * Attempts to disconnect from the session and will optionally retry the connection attempt.
291      *
292      * @param retryConnection true to retry connection attempts after the disconnect
293      */
294     private void disconnect(boolean retryConnection) {
295         try {
296             session.disconnect();
297         } catch (IOException e) {
298             // ignore - we don't care
299         }
300
301         if (retryConnection) {
302             retryConnect();
303         }
304     }
305
306     /**
307      * Retries the connection attempt - schedules a job in {@link PrgBridgeConfig#getRetryPolling()} seconds to
308      * call the {@link #connect()} method. If a retry attempt is pending, the request is ignored.
309      */
310     private void retryConnect() {
311         if (retryConnectionJob == null) {
312             final PrgBridgeConfig config = getPrgBridgeConfig();
313             if (config != null) {
314                 logger.info("Will try to reconnect in {} seconds", config.getRetryPolling());
315                 retryConnectionJob = this.scheduler.schedule(new Runnable() {
316                     @Override
317                     public void run() {
318                         retryConnectionJob = null;
319                         connect();
320                     }
321                 }, config.getRetryPolling(), TimeUnit.SECONDS);
322             }
323         } else {
324             logger.debug("RetryConnection called when a retry connection is pending - ignoring request");
325         }
326     }
327
328     /**
329      * Simplu gets the {@link PrgBridgeConfig} from the {@link Thing} and will set the status to offline if not
330      * found.
331      *
332      * @return a possible null {@link PrgBridgeConfig}
333      */
334     private PrgBridgeConfig getPrgBridgeConfig() {
335         final PrgBridgeConfig config = getThing().getConfiguration().as(PrgBridgeConfig.class);
336
337         if (config == null) {
338             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
339         }
340
341         return config;
342     }
343
344     /**
345      * {@inheritDoc}
346      *
347      * Disposes of the handler. Will simply call {@link #disconnect(boolean)} to disconnect and NOT retry the
348      * connection
349      */
350     @Override
351     public void dispose() {
352         disconnect(false);
353     }
354 }