]> git.basschouten.com Git - openhab-addons.git/blob
206a5d60231a604987c97a9c5feeaec4fd18d123
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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.omnilink.internal.handler;
14
15 import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
16
17 import java.io.IOException;
18 import java.net.UnknownHostException;
19 import java.time.ZonedDateTime;
20 import java.util.Collection;
21 import java.util.Collections;
22 import java.util.Map;
23 import java.util.Optional;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
26
27 import org.eclipse.jdt.annotation.NonNullByDefault;
28 import org.eclipse.jdt.annotation.Nullable;
29 import org.openhab.binding.omnilink.internal.AudioPlayer;
30 import org.openhab.binding.omnilink.internal.SystemType;
31 import org.openhab.binding.omnilink.internal.TemperatureFormat;
32 import org.openhab.binding.omnilink.internal.config.OmnilinkBridgeConfig;
33 import org.openhab.binding.omnilink.internal.discovery.OmnilinkDiscoveryService;
34 import org.openhab.binding.omnilink.internal.exceptions.BridgeOfflineException;
35 import org.openhab.core.library.types.DateTimeType;
36 import org.openhab.core.library.types.DecimalType;
37 import org.openhab.core.library.types.StringType;
38 import org.openhab.core.thing.Bridge;
39 import org.openhab.core.thing.ChannelUID;
40 import org.openhab.core.thing.Thing;
41 import org.openhab.core.thing.ThingStatus;
42 import org.openhab.core.thing.ThingStatusDetail;
43 import org.openhab.core.thing.ThingTypeUID;
44 import org.openhab.core.thing.binding.BaseBridgeHandler;
45 import org.openhab.core.thing.binding.ThingHandlerService;
46 import org.openhab.core.types.Command;
47 import org.openhab.core.types.RefreshType;
48 import org.openhab.core.types.UnDefType;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
51
52 import com.digitaldan.jomnilinkII.Connection;
53 import com.digitaldan.jomnilinkII.DisconnectListener;
54 import com.digitaldan.jomnilinkII.Message;
55 import com.digitaldan.jomnilinkII.MessageTypes.CommandMessage;
56 import com.digitaldan.jomnilinkII.MessageTypes.EventLogData;
57 import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
58 import com.digitaldan.jomnilinkII.MessageTypes.SecurityCodeValidation;
59 import com.digitaldan.jomnilinkII.MessageTypes.SystemFeatures;
60 import com.digitaldan.jomnilinkII.MessageTypes.SystemFormats;
61 import com.digitaldan.jomnilinkII.MessageTypes.SystemInformation;
62 import com.digitaldan.jomnilinkII.MessageTypes.SystemStatus;
63 import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAccessControlReaderLockStatus;
64 import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAreaStatus;
65 import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAudioZoneStatus;
66 import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAuxSensorStatus;
67 import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedThermostatStatus;
68 import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedUnitStatus;
69 import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedZoneStatus;
70 import com.digitaldan.jomnilinkII.MessageTypes.statuses.Status;
71 import com.digitaldan.jomnilinkII.MessageTypes.systemevents.AllOnOffEvent;
72 import com.digitaldan.jomnilinkII.MessageTypes.systemevents.ButtonEvent;
73 import com.digitaldan.jomnilinkII.MessageTypes.systemevents.SwitchPressEvent;
74 import com.digitaldan.jomnilinkII.MessageTypes.systemevents.SystemEvent;
75 import com.digitaldan.jomnilinkII.MessageTypes.systemevents.UPBLinkEvent;
76 import com.digitaldan.jomnilinkII.NotificationListener;
77 import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
78 import com.digitaldan.jomnilinkII.OmniNotConnectedException;
79 import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
80 import com.google.gson.Gson;
81
82 /**
83  * The {@link OmnilinkBridgeHandler} defines some methods that are used to
84  * interface with an OmniLink Controller. This by extension also defines the
85  * OmniLink bridge that openHAB will be able to pick up and interface with.
86  *
87  * @author Craig Hamilton - Initial contribution
88  * @author Ethan Dye - openHAB3 rewrite
89  */
90 @NonNullByDefault
91 public class OmnilinkBridgeHandler extends BaseBridgeHandler implements NotificationListener, DisconnectListener {
92     private final Logger logger = LoggerFactory.getLogger(OmnilinkBridgeHandler.class);
93     private @Nullable Connection omniConnection = null;
94     private @Nullable ScheduledFuture<?> connectJob;
95     private @Nullable ScheduledFuture<?> eventPollingJob;
96     private final int autoReconnectPeriod = 60;
97     private Optional<AudioPlayer> audioPlayer = Optional.empty();
98     private Optional<SystemType> systemType = Optional.empty();
99     private final Gson gson = new Gson();
100     private int eventLogNumber = 0;
101
102     public OmnilinkBridgeHandler(Bridge bridge) {
103         super(bridge);
104     }
105
106     @Override
107     public Collection<Class<? extends ThingHandlerService>> getServices() {
108         return Collections.singleton(OmnilinkDiscoveryService.class);
109     }
110
111     public void sendOmnilinkCommand(final int message, final int param1, final int param2)
112             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
113         try {
114             getOmniConnection().controllerCommand(message, param1, param2);
115         } catch (IOException | OmniNotConnectedException e) {
116             setOfflineAndReconnect(e.getMessage());
117             throw new BridgeOfflineException(e);
118         }
119     }
120
121     public SecurityCodeValidation reqSecurityCodeValidation(int area, int digit1, int digit2, int digit3, int digit4)
122             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
123         try {
124             return getOmniConnection().reqSecurityCodeValidation(area, digit1, digit2, digit3, digit4);
125         } catch (IOException | OmniNotConnectedException e) {
126             setOfflineAndReconnect(e.getMessage());
127             throw new BridgeOfflineException(e);
128         }
129     }
130
131     public void activateKeypadEmergency(int area, int emergencyType)
132             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
133         try {
134             getOmniConnection().activateKeypadEmergency(area, emergencyType);
135         } catch (IOException | OmniNotConnectedException e) {
136             setOfflineAndReconnect(e.getMessage());
137             throw new BridgeOfflineException(e);
138         }
139     }
140
141     public SystemInformation reqSystemInformation()
142             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
143         try {
144             return getOmniConnection().reqSystemInformation();
145         } catch (IOException | OmniNotConnectedException e) {
146             setOfflineAndReconnect(e.getMessage());
147             throw new BridgeOfflineException(e);
148         }
149     }
150
151     public SystemFormats reqSystemFormats()
152             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
153         try {
154             return getOmniConnection().reqSystemFormats();
155         } catch (IOException | OmniNotConnectedException e) {
156             setOfflineAndReconnect(e.getMessage());
157             throw new BridgeOfflineException(e);
158         }
159     }
160
161     private SystemFeatures reqSystemFeatures()
162             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
163         try {
164             return getOmniConnection().reqSystemFeatures();
165         } catch (IOException | OmniNotConnectedException e) {
166             setOfflineAndReconnect(e.getMessage());
167             throw new BridgeOfflineException(e);
168         }
169     }
170
171     @Override
172     public void handleCommand(ChannelUID channelUID, Command command) {
173         logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
174
175         if (command instanceof RefreshType) {
176             updateChannels();
177             return;
178         }
179
180         switch (channelUID.getId()) {
181             case CHANNEL_SYSTEM_DATE:
182                 if (command instanceof DateTimeType) {
183                     ZonedDateTime zdt = ((DateTimeType) command).getZonedDateTime();
184                     boolean inDaylightSavings = zdt.getZone().getRules().isDaylightSavings(zdt.toInstant());
185                     try {
186                         getOmniConnection().setTimeCommand(zdt.getYear() - 2000, zdt.getMonthValue(),
187                                 zdt.getDayOfMonth(), zdt.getDayOfWeek().getValue(), zdt.getHour(), zdt.getMinute(),
188                                 inDaylightSavings);
189                     } catch (IOException | OmniNotConnectedException | OmniInvalidResponseException
190                             | OmniUnknownMessageTypeException e) {
191                         logger.debug("Could not send Set Time command to OmniLink Controller: {}", e.getMessage());
192                     }
193                 } else {
194                     logger.debug("Invalid command: {}, must be DateTimeType", command);
195                 }
196                 break;
197             case CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER:
198                 if (command instanceof StringType) {
199                     try {
200                         sendOmnilinkCommand(CommandMessage.CMD_CONSOLE_ENABLE_DISABLE_BEEPER,
201                                 ((StringType) command).equals(StringType.valueOf("OFF")) ? 0 : 1, 0);
202                         updateState(CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER, UnDefType.UNDEF);
203                     } catch (NumberFormatException | OmniInvalidResponseException | OmniUnknownMessageTypeException
204                             | BridgeOfflineException e) {
205                         logger.debug("Could not send Console command to OmniLink Controller: {}", e.getMessage());
206                     }
207                 } else {
208                     logger.debug("Invalid command: {}, must be StringType", command);
209                 }
210                 break;
211             case CHANNEL_CONSOLE_BEEP:
212                 if (command instanceof DecimalType) {
213                     try {
214                         sendOmnilinkCommand(CommandMessage.CMD_CONSOLE_BEEP, ((DecimalType) command).intValue(), 0);
215                         updateState(CHANNEL_CONSOLE_BEEP, UnDefType.UNDEF);
216                     } catch (NumberFormatException | OmniInvalidResponseException | OmniUnknownMessageTypeException
217                             | BridgeOfflineException e) {
218                         logger.debug("Could not send Console command to OmniLink Controller: {}", e.getMessage());
219                     }
220                 } else {
221                     logger.debug("Invalid command: {}, must be DecimalType", command);
222                 }
223                 break;
224             default:
225                 logger.warn("Unknown channel for Bridge thing: {}", channelUID);
226         }
227     }
228
229     private void makeOmnilinkConnection() {
230         final Connection connection = omniConnection;
231         if (connection != null && connection.connected()) {
232             return;
233         }
234
235         logger.debug("Attempting to connect to controller!");
236         try {
237             OmnilinkBridgeConfig config = getConfigAs(OmnilinkBridgeConfig.class);
238
239             this.omniConnection = new Connection(config.getIpAddress(), config.getPort(),
240                     config.getKey1() + ":" + config.getKey2());
241
242             /*
243              * HAI only supports one audio player - cycle through features until we find a feature that is an audio
244              * player.
245              */
246             audioPlayer = reqSystemFeatures().getFeatures().stream()
247                     .map(featureCode -> AudioPlayer.getAudioPlayerForFeatureCode(featureCode))
248                     .filter(Optional::isPresent).findFirst().orElse(Optional.empty());
249
250             systemType = SystemType.getType(reqSystemInformation().getModel());
251
252             if (config.getLogPollingInterval() > 0) {
253                 startEventPolling(config.getLogPollingInterval());
254             }
255
256             final Connection connectionNew = omniConnection;
257             if (connectionNew != null) {
258                 connectionNew.enableNotifications();
259                 connectionNew.addNotificationListener(OmnilinkBridgeHandler.this);
260                 connectionNew.addDisconnectListener(this);
261             }
262
263             updateStatus(ThingStatus.ONLINE);
264             cancelReconnectJob(false);
265             updateChannels();
266             updateBridgeProperties();
267         } catch (UnknownHostException e) {
268             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
269         } catch (IOException e) {
270             final Throwable cause = e.getCause();
271             if (cause != null) {
272                 final String causeMessage = cause.getMessage();
273
274                 if (causeMessage != null && causeMessage.contains("Connection timed out")) {
275                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
276                             "IP Address probably incorrect, timed out creating connection!");
277                 } else {
278                     updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, causeMessage);
279                 }
280             } else {
281                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
282             }
283         } catch (Exception e) {
284             setOfflineAndReconnect(e.getMessage());
285             logger.debug("Error connecting to OmniLink Controller: {}", e.getMessage());
286         }
287     }
288
289     @Override
290     public void objectStatusNotification(@Nullable ObjectStatus objectStatus) {
291         if (objectStatus != null) {
292             Status[] statuses = objectStatus.getStatuses();
293             for (Status status : statuses) {
294                 if (status instanceof ExtendedUnitStatus) {
295                     ExtendedUnitStatus unitStatus = (ExtendedUnitStatus) status;
296                     int unitNumber = unitStatus.getNumber();
297
298                     logger.debug("Received status update for Unit: {}, status: {}", unitNumber, unitStatus);
299                     Optional<Thing> theThing = getUnitThing(unitNumber);
300                     theThing.map(Thing::getHandler)
301                             .ifPresent(theHandler -> ((UnitHandler) theHandler).handleStatus(unitStatus));
302                 } else if (status instanceof ExtendedZoneStatus) {
303                     ExtendedZoneStatus zoneStatus = (ExtendedZoneStatus) status;
304                     int zoneNumber = zoneStatus.getNumber();
305
306                     logger.debug("Received status update for Zone: {}, status: {}", zoneNumber, zoneStatus);
307                     Optional<Thing> theThing = getChildThing(THING_TYPE_ZONE, zoneNumber);
308                     theThing.map(Thing::getHandler)
309                             .ifPresent(theHandler -> ((ZoneHandler) theHandler).handleStatus(zoneStatus));
310                 } else if (status instanceof ExtendedAreaStatus) {
311                     ExtendedAreaStatus areaStatus = (ExtendedAreaStatus) status;
312                     int areaNumber = areaStatus.getNumber();
313
314                     logger.debug("Received status update for Area: {}, status: {}", areaNumber, areaStatus);
315                     systemType.ifPresent(t -> {
316                         Optional<Thing> theThing = Optional.empty();
317                         switch (t) {
318                             case LUMINA:
319                                 theThing = getChildThing(THING_TYPE_LUMINA_AREA, areaNumber);
320                                 break;
321                             case OMNI:
322                                 theThing = getChildThing(THING_TYPE_OMNI_AREA, areaNumber);
323                                 break;
324                         }
325                         theThing.map(Thing::getHandler)
326                                 .ifPresent(theHandler -> ((AbstractAreaHandler) theHandler).handleStatus(areaStatus));
327                     });
328                 } else if (status instanceof ExtendedAccessControlReaderLockStatus) {
329                     ExtendedAccessControlReaderLockStatus lockStatus = (ExtendedAccessControlReaderLockStatus) status;
330                     int lockNumber = lockStatus.getNumber();
331
332                     logger.debug("Received status update for Lock: {}, status: {}", lockNumber, lockStatus);
333                     Optional<Thing> theThing = getChildThing(THING_TYPE_LOCK, lockNumber);
334                     theThing.map(Thing::getHandler)
335                             .ifPresent(theHandler -> ((LockHandler) theHandler).handleStatus(lockStatus));
336                 } else if (status instanceof ExtendedThermostatStatus) {
337                     ExtendedThermostatStatus thermostatStatus = (ExtendedThermostatStatus) status;
338                     int thermostatNumber = thermostatStatus.getNumber();
339
340                     logger.debug("Received status update for Thermostat: {}, status: {}", thermostatNumber,
341                             thermostatStatus);
342                     Optional<Thing> theThing = getChildThing(THING_TYPE_THERMOSTAT, thermostatNumber);
343                     theThing.map(Thing::getHandler)
344                             .ifPresent(theHandler -> ((ThermostatHandler) theHandler).handleStatus(thermostatStatus));
345                 } else if (status instanceof ExtendedAudioZoneStatus) {
346                     ExtendedAudioZoneStatus audioZoneStatus = (ExtendedAudioZoneStatus) status;
347                     int audioZoneNumber = audioZoneStatus.getNumber();
348
349                     logger.debug("Received status update for Audio Zone: {}, status: {}", audioZoneNumber,
350                             audioZoneStatus);
351                     Optional<Thing> theThing = getChildThing(THING_TYPE_AUDIO_ZONE, audioZoneNumber);
352                     theThing.map(Thing::getHandler)
353                             .ifPresent(theHandler -> ((AudioZoneHandler) theHandler).handleStatus(audioZoneStatus));
354                 } else if (status instanceof ExtendedAuxSensorStatus) {
355                     ExtendedAuxSensorStatus auxSensorStatus = (ExtendedAuxSensorStatus) status;
356                     int auxSensorNumber = auxSensorStatus.getNumber();
357
358                     // Aux Sensors can be either temperature or humidity, need to check both.
359                     Optional<Thing> tempThing = getChildThing(THING_TYPE_TEMP_SENSOR, auxSensorNumber);
360                     Optional<Thing> humidityThing = getChildThing(THING_TYPE_HUMIDITY_SENSOR, auxSensorNumber);
361                     if (tempThing.isPresent()) {
362                         logger.debug("Received status update for Temperature Sensor: {}, status: {}", auxSensorNumber,
363                                 auxSensorStatus);
364                         tempThing.map(Thing::getHandler).ifPresent(
365                                 theHandler -> ((TempSensorHandler) theHandler).handleStatus(auxSensorStatus));
366                     }
367                     if (humidityThing.isPresent()) {
368                         logger.debug("Received status update for Humidity Sensor: {}, status: {}", auxSensorNumber,
369                                 auxSensorStatus);
370                         humidityThing.map(Thing::getHandler).ifPresent(
371                                 theHandler -> ((HumiditySensorHandler) theHandler).handleStatus(auxSensorStatus));
372                     }
373                 } else {
374                     logger.debug("Received Object Status Notification that was not processed: {}", objectStatus);
375                 }
376             }
377         } else {
378             logger.debug("Received null Object Status Notification!");
379         }
380     }
381
382     @Override
383     public void systemEventNotification(@Nullable SystemEvent event) {
384         if (event != null) {
385             logger.debug("Received System Event Notification of type: {}", event.getType());
386             switch (event.getType()) {
387                 case PHONE_LINE_DEAD:
388                 case PHONE_LINE_OFF_HOOK:
389                 case PHONE_LINE_ON_HOOK:
390                 case PHONE_LINE_RING:
391                     ChannelUID channel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_PHONE_LINE_EVENT);
392                     triggerChannel(channel, event.getType().toString().replaceAll("^PHONE_LINE_", ""));
393                     break;
394                 case AC_POWER_OFF:
395                 case AC_POWER_RESTORED:
396                     ChannelUID acChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_AC_POWER_EVENT);
397                     triggerChannel(acChannel, event.getType().toString().replaceAll("^AC_POWER_", ""));
398                     break;
399                 case BATTERY_LOW:
400                 case BATTERY_OK:
401                     ChannelUID batteryChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_BATTERY_EVENT);
402                     triggerChannel(batteryChannel, event.getType().toString().replaceAll("^BATTERY_", ""));
403                     break;
404                 case DCM_OK:
405                 case DCM_TROUBLE:
406                     ChannelUID dcmChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_DCM_EVENT);
407                     triggerChannel(dcmChannel, event.getType().toString().replaceAll("^DCM_", ""));
408                     break;
409                 case ENERGY_COST_CRITICAL:
410                 case ENERGY_COST_HIGH:
411                 case ENERGY_COST_LOW:
412                 case ENERGY_COST_MID:
413                     ChannelUID energyChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_ENERGY_COST_EVENT);
414                     triggerChannel(energyChannel, event.getType().toString().replaceAll("^ENERGY_COST_", ""));
415                     break;
416                 case CAMERA_1_TRIGGER:
417                 case CAMERA_2_TRIGGER:
418                 case CAMERA_3_TRIGGER:
419                 case CAMERA_4_TRIGGER:
420                 case CAMERA_5_TRIGGER:
421                 case CAMERA_6_TRIGGER:
422                     ChannelUID cameraChannel = new ChannelUID(getThing().getUID(),
423                             TRIGGER_CHANNEL_CAMERA_TRIGGER_EVENT);
424                     triggerChannel(cameraChannel, String.valueOf(event.getType().toString().charAt(8)));
425                     break;
426                 case BUTTON:
427                     Optional<Thing> buttonThing = getChildThing(THING_TYPE_BUTTON,
428                             ((ButtonEvent) event).getButtonNumber());
429                     buttonThing.map(Thing::getHandler)
430                             .ifPresent(theHandler -> ((ButtonHandler) theHandler).buttonActivated());
431                     break;
432                 case ALL_ON_OFF:
433                     Optional<Thing> areaThing = getChildThing(THING_TYPE_OMNI_AREA, ((AllOnOffEvent) event).getArea());
434                     if (areaThing.isPresent()) {
435                         logger.debug("Thing for allOnOff event: {}", areaThing.get().getUID());
436                         areaThing.map(Thing::getHandler).ifPresent(theHandler -> ((AbstractAreaHandler) theHandler)
437                                 .handleAllOnOffEvent((AllOnOffEvent) event));
438                     }
439                     break;
440                 case UPB_LINK:
441                     UPBLinkEvent linkEvent = (UPBLinkEvent) event;
442                     UPBLinkEvent.Command command = linkEvent.getLinkCommand();
443                     int link = linkEvent.getLinkNumber();
444                     handleUPBLink(link, command);
445                     break;
446                 case ALC_UPB_RADIORA_STARLITE_SWITCH_PRESS:
447                     SwitchPressEvent switchPressEvent = (SwitchPressEvent) event;
448                     int unitNumber = switchPressEvent.getUnitNumber();
449
450                     Optional<Thing> unitThing = getUnitThing(unitNumber);
451                     unitThing.map(Thing::getHandler).ifPresent(
452                             theHandler -> ((UnitHandler) theHandler).handleSwitchPressEvent(switchPressEvent));
453                     break;
454                 default:
455                     logger.warn("Ignoring System Event Notification of type: {}", event.getType());
456             }
457         } else {
458             logger.debug("Received null System Event Notification!");
459         }
460     }
461
462     private void handleUPBLink(int link, UPBLinkEvent.Command command) {
463         final ChannelUID activateChannel;
464
465         if (command == UPBLinkEvent.Command.ACTIVATED) {
466             activateChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_UPB_LINK_ACTIVATED_EVENT);
467         } else if (command == UPBLinkEvent.Command.DEACTIVATED) {
468             activateChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_UPB_LINK_DEACTIVATED_EVENT);
469         } else {
470             logger.debug("Received unsupported UPB link event: {}", command);
471             return;
472         }
473         triggerChannel(activateChannel, Integer.toString(link));
474     }
475
476     @Override
477     public void notConnectedEvent(@Nullable Exception e) {
478         if (e != null) {
479             logger.debug("Received an OmniLink Controller not connected event: {}", e.getMessage());
480             setOfflineAndReconnect(e.getMessage());
481         }
482     }
483
484     private void getSystemStatus() throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
485             OmniUnknownMessageTypeException {
486         SystemStatus status = getOmniConnection().reqSystemStatus();
487         logger.debug("Received system status: {}", status);
488         // Let's update system time
489         String dateString = new StringBuilder().append(2000 + status.getYear()).append("-")
490                 .append(String.format("%02d", status.getMonth())).append("-")
491                 .append(String.format("%02d", status.getDay())).append("T")
492                 .append(String.format("%02d", status.getHour())).append(":")
493                 .append(String.format("%02d", status.getMinute())).append(":")
494                 .append(String.format("%02d", status.getSecond())).toString();
495         updateState(CHANNEL_SYSTEM_DATE, new DateTimeType(dateString));
496     }
497
498     public Message reqObjectProperties(int objectType, int objectNum, int direction, int filter1, int filter2,
499             int filter3) throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
500         try {
501             return getOmniConnection().reqObjectProperties(objectType, objectNum, direction, filter1, filter2, filter3);
502         } catch (OmniNotConnectedException | IOException e) {
503             setOfflineAndReconnect(e.getMessage());
504             throw new BridgeOfflineException(e);
505         }
506     }
507
508     public Message requestAudioSourceStatus(final int source, final int position)
509             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
510         try {
511             return getOmniConnection().reqAudioSourceStatus(source, position);
512         } catch (OmniNotConnectedException | IOException e) {
513             setOfflineAndReconnect(e.getMessage());
514             throw new BridgeOfflineException(e);
515         }
516     }
517
518     public ObjectStatus requestObjectStatus(final int objType, final int startObject, final int endObject,
519             boolean extended)
520             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
521         try {
522             return getOmniConnection().reqObjectStatus(objType, startObject, endObject, extended);
523         } catch (OmniNotConnectedException | IOException e) {
524             setOfflineAndReconnect(e.getMessage());
525             throw new BridgeOfflineException(e);
526         }
527     }
528
529     public Optional<TemperatureFormat> getTemperatureFormat() {
530         try {
531             return Optional.of(TemperatureFormat.valueOf(reqSystemFormats().getTempFormat()));
532         } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
533             logger.debug("Could not request temperature format from controller: {}", e.getMessage());
534             return Optional.empty();
535         }
536     }
537
538     public void updateChannels() {
539         try {
540             getSystemStatus();
541             updateState(CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER, UnDefType.UNDEF);
542             updateState(CHANNEL_CONSOLE_BEEP, UnDefType.UNDEF);
543         } catch (IOException | OmniNotConnectedException | OmniInvalidResponseException
544                 | OmniUnknownMessageTypeException e) {
545             logger.warn("Unable to update bridge channels: {}", e.getMessage());
546         }
547     }
548
549     @Override
550     public void dispose() {
551         cancelReconnectJob(true);
552         cancelEventPolling();
553         final Connection connection = omniConnection;
554         if (connection != null) {
555             connection.removeDisconnectListener(this);
556             connection.disconnect();
557         }
558     }
559
560     private Optional<Thing> getChildThing(ThingTypeUID type, int number) {
561         Bridge bridge = getThing();
562         return bridge.getThings().stream().filter(t -> t.getThingTypeUID().equals(type))
563                 .filter(t -> ((Number) t.getConfiguration().get(THING_PROPERTIES_NUMBER)).intValue() == number)
564                 .findFirst();
565     }
566
567     private Optional<Thing> getUnitThing(int unitId) {
568         return SUPPORTED_UNIT_TYPES_UIDS.stream().map(uid -> getChildThing(uid, unitId)).flatMap(Optional::stream)
569                 .findFirst();
570     }
571
572     public Optional<AudioPlayer> getAudioPlayer() {
573         return audioPlayer;
574     }
575
576     public Message readEventRecord(int eventNumber, int direction)
577             throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
578         try {
579             return getOmniConnection().readEventRecord(eventNumber, direction);
580         } catch (OmniNotConnectedException | IOException e) {
581             setOfflineAndReconnect(e.getMessage());
582             throw new BridgeOfflineException(e);
583         }
584     }
585
586     private void updateBridgeProperties() {
587         try {
588             SystemInformation systemInformation = reqSystemInformation();
589             Map<String, String> properties = editProperties();
590             properties.put(Thing.PROPERTY_MODEL_ID, Integer.toString(systemInformation.getModel()));
591             properties.put(Thing.PROPERTY_FIRMWARE_VERSION,
592                     Integer.toString(systemInformation.getMajor()) + "."
593                             + Integer.toString(systemInformation.getMinor()) + "."
594                             + Integer.toString(systemInformation.getRevision()));
595             properties.put(THING_PROPERTIES_PHONE_NUMBER, systemInformation.getPhone());
596             updateProperties(properties);
597         } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
598             logger.debug("Could not request system information from OmniLink Controller: {}", e.getMessage());
599         }
600     }
601
602     @Override
603     public void initialize() {
604         scheduleReconnectJob();
605     }
606
607     private void scheduleReconnectJob() {
608         ScheduledFuture<?> currentReconnectJob = connectJob;
609         if (currentReconnectJob == null || currentReconnectJob.isDone()) {
610             connectJob = super.scheduler.scheduleWithFixedDelay(this::makeOmnilinkConnection, 0, autoReconnectPeriod,
611                     TimeUnit.SECONDS);
612         }
613     }
614
615     private void cancelReconnectJob(boolean kill) {
616         ScheduledFuture<?> currentReconnectJob = connectJob;
617         if (currentReconnectJob != null) {
618             currentReconnectJob.cancel(kill);
619         }
620     }
621
622     private void setOfflineAndReconnect(@Nullable String message) {
623         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
624         cancelEventPolling();
625         final Connection connection = omniConnection;
626         if (connection != null) {
627             connection.removeDisconnectListener(this);
628         }
629         scheduleReconnectJob();
630     }
631
632     private void startEventPolling(int interval) {
633         ScheduledFuture<?> eventPollingJobFuture = eventPollingJob;
634         if (eventPollingJobFuture == null || eventPollingJobFuture.isDone()) {
635             eventLogNumber = 0;
636             eventPollingJob = super.scheduler.scheduleWithFixedDelay(this::pollEvents, 0, interval, TimeUnit.SECONDS);
637         }
638     }
639
640     private void cancelEventPolling() {
641         ScheduledFuture<?> eventPollingJobFuture = eventPollingJob;
642         if (eventPollingJobFuture != null) {
643             eventPollingJobFuture.cancel(true);
644         }
645     }
646
647     private void pollEvents() {
648         // On first run, direction is -1 (most recent event), after its 1 for the next log message
649         try {
650             Message message;
651             do {
652                 logger.trace("Polling for event log messages.");
653                 int direction = eventLogNumber == 0 ? -1 : 1;
654                 message = readEventRecord(eventLogNumber, direction);
655                 if (message.getMessageType() == Message.MESG_TYPE_EVENT_LOG_DATA) {
656                     EventLogData logData = (EventLogData) message;
657                     logger.debug("Processing event log message number: {}", logData.getEventNumber());
658                     eventLogNumber = logData.getEventNumber();
659                     String json = gson.toJson(logData);
660                     logger.debug("Receieved event log message: {}", json);
661                     updateState(CHANNEL_EVENT_LOG, new StringType(json));
662                 }
663             } while (message.getMessageType() != Message.MESG_TYPE_END_OF_DATA);
664
665         } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
666             logger.debug("Exception recieved while polling for event log messages: {}", e.getMessage());
667         }
668     }
669
670     private Connection getOmniConnection() throws OmniNotConnectedException {
671         final Connection connection = omniConnection;
672         if (connection != null) {
673             return connection;
674         } else {
675             throw new OmniNotConnectedException("Connection not yet established!");
676         }
677     }
678 }