2 * Copyright (c) 2010-2021 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.omnilink.internal.handler;
15 import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
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;
23 import java.util.Optional;
24 import java.util.concurrent.ScheduledFuture;
25 import java.util.concurrent.TimeUnit;
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;
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;
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.
87 * @author Craig Hamilton - Initial contribution
88 * @author Ethan Dye - openHAB3 rewrite
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;
102 public OmnilinkBridgeHandler(Bridge bridge) {
107 public Collection<Class<? extends ThingHandlerService>> getServices() {
108 return Collections.singleton(OmnilinkDiscoveryService.class);
111 public void sendOmnilinkCommand(final int message, final int param1, final int param2)
112 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
114 getOmniConnection().controllerCommand(message, param1, param2);
115 } catch (IOException | OmniNotConnectedException e) {
116 setOfflineAndReconnect(e.getMessage());
117 throw new BridgeOfflineException(e);
121 public SecurityCodeValidation reqSecurityCodeValidation(int area, int digit1, int digit2, int digit3, int digit4)
122 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
124 return getOmniConnection().reqSecurityCodeValidation(area, digit1, digit2, digit3, digit4);
125 } catch (IOException | OmniNotConnectedException e) {
126 setOfflineAndReconnect(e.getMessage());
127 throw new BridgeOfflineException(e);
131 public void activateKeypadEmergency(int area, int emergencyType)
132 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
134 getOmniConnection().activateKeypadEmergency(area, emergencyType);
135 } catch (IOException | OmniNotConnectedException e) {
136 setOfflineAndReconnect(e.getMessage());
137 throw new BridgeOfflineException(e);
141 public SystemInformation reqSystemInformation()
142 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
144 return getOmniConnection().reqSystemInformation();
145 } catch (IOException | OmniNotConnectedException e) {
146 setOfflineAndReconnect(e.getMessage());
147 throw new BridgeOfflineException(e);
151 public SystemFormats reqSystemFormats()
152 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
154 return getOmniConnection().reqSystemFormats();
155 } catch (IOException | OmniNotConnectedException e) {
156 setOfflineAndReconnect(e.getMessage());
157 throw new BridgeOfflineException(e);
161 private SystemFeatures reqSystemFeatures()
162 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
164 return getOmniConnection().reqSystemFeatures();
165 } catch (IOException | OmniNotConnectedException e) {
166 setOfflineAndReconnect(e.getMessage());
167 throw new BridgeOfflineException(e);
172 public void handleCommand(ChannelUID channelUID, Command command) {
173 logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
175 if (command instanceof RefreshType) {
180 switch (channelUID.getId()) {
181 case CHANNEL_SYSTEMDATE:
182 if (command instanceof DateTimeType) {
183 ZonedDateTime zdt = ((DateTimeType) command).getZonedDateTime();
184 boolean inDaylightSavings = zdt.getZone().getRules().isDaylightSavings(zdt.toInstant());
186 getOmniConnection().setTimeCommand(zdt.getYear() - 2000, zdt.getMonthValue(),
187 zdt.getDayOfMonth(), zdt.getDayOfWeek().getValue(), zdt.getHour(), zdt.getMinute(),
189 } catch (IOException | OmniNotConnectedException | OmniInvalidResponseException
190 | OmniUnknownMessageTypeException e) {
191 logger.debug("Could not send Set Time command to OmniLink Controller: {}", e.getMessage());
194 logger.debug("Invalid command: {}, must be DateTimeType", command);
197 case CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER:
198 if (command instanceof StringType) {
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());
208 logger.debug("Invalid command: {}, must be StringType", command);
211 case CHANNEL_CONSOLE_BEEP:
212 if (command instanceof DecimalType) {
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());
221 logger.debug("Invalid command: {}, must be DecimalType", command);
225 logger.warn("Unknown channel for Bridge thing: {}", channelUID);
229 private void makeOmnilinkConnection() {
230 final Connection connection = omniConnection;
231 if (connection != null && connection.connected()) {
235 logger.debug("Attempting to connect to controller!");
237 OmnilinkBridgeConfig config = getConfigAs(OmnilinkBridgeConfig.class);
239 this.omniConnection = new Connection(config.getIpAddress(), config.getPort(),
240 config.getKey1() + ":" + config.getKey2());
243 * HAI only supports one audio player - cycle through features until we find a feature that is an audio
246 audioPlayer = reqSystemFeatures().getFeatures().stream()
247 .map(featureCode -> AudioPlayer.getAudioPlayerForFeatureCode(featureCode))
248 .filter(Optional::isPresent).findFirst().orElse(Optional.empty());
250 systemType = SystemType.getType(reqSystemInformation().getModel());
252 if (config.getLogPollingInterval() > 0) {
253 startEventPolling(config.getLogPollingInterval());
256 final Connection connectionNew = omniConnection;
257 if (connectionNew != null) {
258 connectionNew.enableNotifications();
259 connectionNew.addNotificationListener(OmnilinkBridgeHandler.this);
260 connectionNew.addDisconnectListener(this);
263 updateStatus(ThingStatus.ONLINE);
264 cancelReconnectJob(false);
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();
272 final String causeMessage = cause.getMessage();
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!");
278 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, causeMessage);
281 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
283 } catch (Exception e) {
284 setOfflineAndReconnect(e.getMessage());
285 logger.debug("Error connecting to OmniLink Controller: {}", e.getMessage());
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();
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();
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();
314 logger.debug("Received status update for Area: {}, status: {}", areaNumber, areaStatus);
315 systemType.ifPresent(t -> {
316 Optional<Thing> theThing = Optional.empty();
319 theThing = getChildThing(THING_TYPE_LUMINA_AREA, areaNumber);
322 theThing = getChildThing(THING_TYPE_OMNI_AREA, areaNumber);
325 theThing.map(Thing::getHandler)
326 .ifPresent(theHandler -> ((AbstractAreaHandler) theHandler).handleStatus(areaStatus));
328 } else if (status instanceof ExtendedAccessControlReaderLockStatus) {
329 ExtendedAccessControlReaderLockStatus lockStatus = (ExtendedAccessControlReaderLockStatus) status;
330 int lockNumber = lockStatus.getNumber();
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();
340 logger.debug("Received status update for Thermostat: {}, status: {}", thermostatNumber,
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();
349 logger.debug("Received status update for Audio Zone: {}, status: {}", audioZoneNumber,
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();
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,
364 tempThing.map(Thing::getHandler).ifPresent(
365 theHandler -> ((TempSensorHandler) theHandler).handleStatus(auxSensorStatus));
367 if (humidityThing.isPresent()) {
368 logger.debug("Received status update for Humidity Sensor: {}, status: {}", auxSensorNumber,
370 humidityThing.map(Thing::getHandler).ifPresent(
371 theHandler -> ((HumiditySensorHandler) theHandler).handleStatus(auxSensorStatus));
374 logger.debug("Received Object Status Notification that was not processed: {}", objectStatus);
378 logger.debug("Received null Object Status Notification!");
383 public void systemEventNotification(@Nullable SystemEvent event) {
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_", ""));
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_", ""));
401 ChannelUID batteryChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_BATTERY_EVENT);
402 triggerChannel(batteryChannel, event.getType().toString().replaceAll("^BATTERY_", ""));
406 ChannelUID dcmChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_DCM_EVENT);
407 triggerChannel(dcmChannel, event.getType().toString().replaceAll("^DCM_", ""));
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_", ""));
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)));
427 Optional<Thing> buttonThing = getChildThing(THING_TYPE_BUTTON,
428 ((ButtonEvent) event).getButtonNumber());
429 buttonThing.map(Thing::getHandler)
430 .ifPresent(theHandler -> ((ButtonHandler) theHandler).buttonActivated());
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));
441 UPBLinkEvent linkEvent = (UPBLinkEvent) event;
442 UPBLinkEvent.Command command = linkEvent.getLinkCommand();
443 int link = linkEvent.getLinkNumber();
444 handleUPBLink(link, command);
446 case ALC_UPB_RADIORA_STARLITE_SWITCH_PRESS:
447 SwitchPressEvent switchPressEvent = (SwitchPressEvent) event;
448 int unitNumber = switchPressEvent.getUnitNumber();
450 Optional<Thing> unitThing = getUnitThing(unitNumber);
451 unitThing.map(Thing::getHandler).ifPresent(
452 theHandler -> ((UnitHandler) theHandler).handleSwitchPressEvent(switchPressEvent));
455 logger.warn("Ignoring System Event Notification of type: {}", event.getType());
458 logger.debug("Received null System Event Notification!");
462 private void handleUPBLink(int link, UPBLinkEvent.Command command) {
463 final ChannelUID activateChannel;
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);
470 logger.debug("Received unsupported UPB link event: {}", command);
473 triggerChannel(activateChannel, Integer.toString(link));
477 public void notConnectedEvent(@Nullable Exception e) {
479 logger.debug("Received an OmniLink Controller not connected event: {}", e.getMessage());
480 setOfflineAndReconnect(e.getMessage());
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_SYSTEMDATE, new DateTimeType(dateString));
498 public Message reqObjectProperties(int objectType, int objectNum, int direction, int filter1, int filter2,
499 int filter3) throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
501 return getOmniConnection().reqObjectProperties(objectType, objectNum, direction, filter1, filter2, filter3);
502 } catch (OmniNotConnectedException | IOException e) {
503 setOfflineAndReconnect(e.getMessage());
504 throw new BridgeOfflineException(e);
508 public Message requestAudioSourceStatus(final int source, final int position)
509 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
511 return getOmniConnection().reqAudioSourceStatus(source, position);
512 } catch (OmniNotConnectedException | IOException e) {
513 setOfflineAndReconnect(e.getMessage());
514 throw new BridgeOfflineException(e);
518 public ObjectStatus requestObjectStatus(final int objType, final int startObject, final int endObject,
520 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
522 return getOmniConnection().reqObjectStatus(objType, startObject, endObject, extended);
523 } catch (OmniNotConnectedException | IOException e) {
524 setOfflineAndReconnect(e.getMessage());
525 throw new BridgeOfflineException(e);
529 public Optional<TemperatureFormat> getTemperatureFormat() {
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();
538 public void updateChannels() {
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());
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();
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)
567 private Optional<Thing> getUnitThing(int unitId) {
568 return SUPPORTED_UNIT_TYPES_UIDS.stream().map(uid -> getChildThing(uid, unitId)).flatMap(Optional::stream)
572 public Optional<AudioPlayer> getAudioPlayer() {
576 public Message readEventRecord(int eventNumber, int direction)
577 throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
579 return getOmniConnection().readEventRecord(eventNumber, direction);
580 } catch (OmniNotConnectedException | IOException e) {
581 setOfflineAndReconnect(e.getMessage());
582 throw new BridgeOfflineException(e);
586 private void updateBridgeProperties() {
588 SystemInformation systemInformation = reqSystemInformation();
589 Map<String, String> properties = editProperties();
590 properties.put(THING_PROPERTIES_MODEL_NUMBER, Integer.toString(systemInformation.getModel()));
591 properties.put(THING_PROPERTIES_MAJOR_VERSION, Integer.toString(systemInformation.getMajor()));
592 properties.put(THING_PROPERTIES_MINOR_VERSION, Integer.toString(systemInformation.getMinor()));
593 properties.put(THING_PROPERTIES_REVISION, Integer.toString(systemInformation.getRevision()));
594 properties.put(THING_PROPERTIES_PHONE_NUMBER, systemInformation.getPhone());
595 updateProperties(properties);
596 } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
597 logger.debug("Could not request system information from OmniLink Controller: {}", e.getMessage());
602 public void initialize() {
603 scheduleReconnectJob();
606 private void scheduleReconnectJob() {
607 ScheduledFuture<?> currentReconnectJob = connectJob;
608 if (currentReconnectJob == null || currentReconnectJob.isDone()) {
609 connectJob = super.scheduler.scheduleWithFixedDelay(this::makeOmnilinkConnection, 0, autoReconnectPeriod,
614 private void cancelReconnectJob(boolean kill) {
615 ScheduledFuture<?> currentReconnectJob = connectJob;
616 if (currentReconnectJob != null) {
617 currentReconnectJob.cancel(kill);
621 private void setOfflineAndReconnect(@Nullable String message) {
622 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
623 cancelEventPolling();
624 final Connection connection = omniConnection;
625 if (connection != null) {
626 connection.removeDisconnectListener(this);
628 scheduleReconnectJob();
631 private void startEventPolling(int interval) {
632 ScheduledFuture<?> eventPollingJobFuture = eventPollingJob;
633 if (eventPollingJobFuture == null || eventPollingJobFuture.isDone()) {
635 eventPollingJob = super.scheduler.scheduleWithFixedDelay(this::pollEvents, 0, interval, TimeUnit.SECONDS);
639 private void cancelEventPolling() {
640 ScheduledFuture<?> eventPollingJobFuture = eventPollingJob;
641 if (eventPollingJobFuture != null) {
642 eventPollingJobFuture.cancel(true);
646 private void pollEvents() {
647 // On first run, direction is -1 (most recent event), after its 1 for the next log message
651 logger.trace("Polling for event log messages.");
652 int direction = eventLogNumber == 0 ? -1 : 1;
653 message = readEventRecord(eventLogNumber, direction);
654 if (message.getMessageType() == Message.MESG_TYPE_EVENT_LOG_DATA) {
655 EventLogData logData = (EventLogData) message;
656 logger.debug("Processing event log message number: {}", logData.getEventNumber());
657 eventLogNumber = logData.getEventNumber();
658 String json = gson.toJson(logData);
659 logger.debug("Receieved event log message: {}", json);
660 updateState(CHANNEL_EVENT_LOG, new StringType(json));
662 } while (message.getMessageType() != Message.MESG_TYPE_END_OF_DATA);
664 } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
665 logger.debug("Exception recieved while polling for event log messages: {}", e.getMessage());
669 private Connection getOmniConnection() throws OmniNotConnectedException {
670 final Connection connection = omniConnection;
671 if (connection != null) {
674 throw new OmniNotConnectedException("Connection not yet established!");