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();
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));
172 logger.error("Received a TIMECLOCK channel command with a non DateTimeType: {}", command);
174 } else if (id.startsWith(PrgConstants.CHANNEL_SCHEDULE)) {
175 if (command instanceof DecimalType scheduleCommand) {
176 final int schedule = scheduleCommand.intValue();
177 protocolHandler.selectSchedule(schedule);
179 logger.error("Received a SCHEDULE channel command with a non DecimalType: {}", command);
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();
188 logger.error("Unknown/Unsupported Channel id: {}", id);
193 * Method that handles the {@link RefreshType} command specifically. Calls the {@link PrgProtocolHandler} to
194 * handle the actual refresh based on the channel id.
196 * @param id a non-null, possibly empty channel id to refresh
198 private void handleRefresh(String id) {
199 if (getThing().getStatus() != ThingStatus.ONLINE) {
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();
225 * Initializes the handler. This initialization will read/validate the configuration and will attempt to connect to
226 * the switch (via {{@link #retryConnect()}.
229 public void initialize() {
230 final PrgBridgeConfig config = getPrgBridgeConfig();
232 if (config == null) {
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");
242 // Try initial connection in a scheduled task
243 this.scheduler.schedule(new Runnable() {
248 }, 1, TimeUnit.SECONDS);
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()})
256 private void connect() {
257 final PrgBridgeConfig config = getPrgBridgeConfig();
259 String response = "Server is offline - will try to reconnect later";
261 logger.info("Attempting connection ...");
264 response = protocolHandler.login(config.getUserName());
265 if (response == null) {
266 if (config != null) {
267 updateStatus(ThingStatus.ONLINE);
271 } catch (Exception e) {
272 logger.error("Exception during connection attempt", e);
275 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, response);
280 * Attempts to disconnect from the session and will optionally retry the connection attempt.
282 * @param retryConnection true to retry connection attempts after the disconnect
284 private void disconnect(boolean retryConnection) {
286 session.disconnect();
287 } catch (IOException e) {
288 // ignore - we don't care
291 if (retryConnection) {
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.
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() {
308 retryConnectionJob = null;
311 }, config.getRetryPolling(), TimeUnit.SECONDS);
314 logger.debug("RetryConnection called when a retry connection is pending - ignoring request");
319 * Simplu gets the {@link PrgBridgeConfig} from the {@link Thing} and will set the status to offline if not
322 * @return a possible null {@link PrgBridgeConfig}
324 private PrgBridgeConfig getPrgBridgeConfig() {
325 final PrgBridgeConfig config = getThing().getConfiguration().as(PrgBridgeConfig.class);
327 if (config == null) {
328 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Configuration file missing");
337 * Disposes of the handler. Will simply call {@link #disconnect(boolean)} to disconnect and NOT retry the
341 public void dispose() {