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.lutron.internal.grxprg;
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;
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;
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}).
39 * @author Tim Roberts - Initial contribution
41 public class PrgBridgeHandler extends BaseBridgeHandler {
43 private Logger logger = LoggerFactory.getLogger(PrgBridgeHandler.class);
46 * The {@link PrgProtocolHandler} that handles the actual protocol. Will never be null
48 private PrgProtocolHandler protocolHandler;
51 * The {@link SocketSession} to the physical devices. Will never be null
53 private SocketSession session;
56 * The retry connection event. Null if not retrying.
58 private ScheduledFuture<?> retryConnectionJob;
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.
64 * @param bridge a non-null {@link Bridge} the handler is for
66 public PrgBridgeHandler(Bridge bridge) {
70 throw new IllegalArgumentException("thing cannot be null");
73 final PrgBridgeConfig config = getPrgBridgeConfig();
74 session = new SocketSession(getThing().getUID().getAsString(), config.getIpAddress(), 23);
76 protocolHandler = new PrgProtocolHandler(session, new PrgHandlerCallback() {
78 public void stateChanged(String channelId, State state) {
79 updateState(channelId, state);
83 public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
84 updateStatus(status, detail, msg);
86 if (status != ThingStatus.ONLINE) {
92 public void stateChanged(int controlUnit, String channelId, State state) {
93 getGrafikEyeHandler(controlUnit).stateChanged(channelId, state);
97 public boolean isShade(int controlUnit, int zone) {
98 return getGrafikEyeHandler(controlUnit).isShade(zone);
104 * Internal method to retrieve the {@link PrgProtocolHandler} used by the bridge
106 * @return a non-null protocol handler to use
108 PrgProtocolHandler getProtocolHandler() {
109 return protocolHandler;
113 * Helper method used to retrieve the {@link GrafikEyeHandler} for a given control unit number. If not found, an
114 * IllegalArgumentException will be thrown.
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.
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) {
128 logger.warn("Should not be a non-GrafikEyeHandler as a thing to this bridge - ignoring: {}", thing);
132 throw new IllegalArgumentException("Could not find a GrafikEyeHandler for control unit : " + controlUnit);
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
145 public void handleCommand(ChannelUID channelUID, Command command) {
146 if (command instanceof RefreshType) {
147 handleRefresh(channelUID.getId());
151 // if (getThing().getStatus() != ThingStatus.ONLINE) {
152 // // Ignore any command if not online
156 String id = channelUID.getId();
159 logger.warn("Called with a null channel id - ignoring");
163 if (id.equals(PrgConstants.CHANNEL_ZONELOWERSTOP)) {
164 protocolHandler.setZoneLowerStop();
166 } else if (id.equals(PrgConstants.CHANNEL_ZONERAISESTOP)) {
167 protocolHandler.setZoneRaiseStop();
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));
174 logger.error("Received a TIMECLOCK channel command with a non DateTimeType: {}", command);
176 } else if (id.startsWith(PrgConstants.CHANNEL_SCHEDULE)) {
177 if (command instanceof DecimalType) {
178 final int schedule = ((DecimalType) command).intValue();
179 protocolHandler.selectSchedule(schedule);
181 logger.error("Received a SCHEDULE channel command with a non DecimalType: {}", command);
184 } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCESTART)) {
185 protocolHandler.startSuperSequence();
187 } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCEPAUSE)) {
188 protocolHandler.pauseSuperSequence();
189 } else if (id.startsWith(PrgConstants.CHANNEL_SUPERSEQUENCERESUME)) {
190 protocolHandler.resumeSuperSequence();
193 logger.error("Unknown/Unsupported Channel id: {}", id);
198 * Method that handles the {@link RefreshType} command specifically. Calls the {@link PrgProtocolHandler} to
199 * handle the actual refresh based on the channel id.
201 * @param id a non-null, possibly empty channel id to refresh
203 private void handleRefresh(String id) {
204 if (getThing().getStatus() != ThingStatus.ONLINE) {
208 if (id.equals(PrgConstants.CHANNEL_TIMECLOCK)) {
209 protocolHandler.refreshTime();
211 } else if (id.equals(PrgConstants.CHANNEL_SCHEDULE)) {
212 protocolHandler.refreshSchedule();
214 } else if (id.equals(PrgConstants.CHANNEL_SUNRISE)) {
215 protocolHandler.refreshSunriseSunset();
217 } else if (id.equals(PrgConstants.CHANNEL_SUNSET)) {
218 protocolHandler.refreshSunriseSunset();
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();
234 * Initializes the handler. This initialization will read/validate the configuration and will attempt to connect to
235 * the switch (via {{@link #retryConnect()}.
238 public void initialize() {
239 final PrgBridgeConfig config = getPrgBridgeConfig();
241 if (config == null) {
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");
251 // Try initial connection in a scheduled task
252 this.scheduler.schedule(new Runnable() {
257 }, 1, TimeUnit.SECONDS);
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()})
265 private void connect() {
266 final PrgBridgeConfig config = getPrgBridgeConfig();
268 String response = "Server is offline - will try to reconnect later";
270 logger.info("Attempting connection ...");
273 response = protocolHandler.login(config.getUserName());
274 if (response == null) {
275 if (config != null) {
276 updateStatus(ThingStatus.ONLINE);
281 } catch (Exception e) {
282 logger.error("Exception during connection attempt", e);
285 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, response);
290 * Attempts to disconnect from the session and will optionally retry the connection attempt.
292 * @param retryConnection true to retry connection attempts after the disconnect
294 private void disconnect(boolean retryConnection) {
296 session.disconnect();
297 } catch (IOException e) {
298 // ignore - we don't care
301 if (retryConnection) {
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.
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() {
318 retryConnectionJob = null;
321 }, config.getRetryPolling(), TimeUnit.SECONDS);
324 logger.debug("RetryConnection called when a retry connection is pending - ignoring request");
329 * Simplu gets the {@link PrgBridgeConfig} from the {@link Thing} and will set the status to offline if not
332 * @return a possible null {@link PrgBridgeConfig}
334 private PrgBridgeConfig getPrgBridgeConfig() {
335 final PrgBridgeConfig config = getThing().getConfiguration().as(PrgBridgeConfig.class);
337 if (config == null) {
338 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
347 * Disposes of the handler. Will simply call {@link #disconnect(boolean)} to disconnect and NOT retry the
351 public void dispose() {