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