diff --git a/CODEOWNERS b/CODEOWNERS
index 868649530d6..9cb676a5c0f 100644
--- a/CODEOWNERS
+++ b/CODEOWNERS
@@ -185,6 +185,7 @@
/bundles/org.openhab.binding.oceanic/ @kgoderis
/bundles/org.openhab.binding.ojelectronics/ @EvilPingu
/bundles/org.openhab.binding.omnikinverter/ @hansbogert
+/bundles/org.openhab.binding.omnilink/ @ecdye
/bundles/org.openhab.binding.onebusaway/ @sdwilsh
/bundles/org.openhab.binding.onewire/ @J-N-K
/bundles/org.openhab.binding.onewiregpio/ @aogorek
diff --git a/bom/openhab-addons/pom.xml b/bom/openhab-addons/pom.xml
index 3c733884952..cdc57aa16ed 100644
--- a/bom/openhab-addons/pom.xml
+++ b/bom/openhab-addons/pom.xml
@@ -911,6 +911,11 @@
org.openhab.binding.omnikinverter
${project.version}
+
+ org.openhab.addons.bundles
+ org.openhab.binding.omnilink
+ ${project.version}
+
org.openhab.addons.bundles
org.openhab.binding.onebusaway
diff --git a/bundles/org.openhab.binding.omnilink/NOTICE b/bundles/org.openhab.binding.omnilink/NOTICE
new file mode 100644
index 00000000000..88908f5b965
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/NOTICE
@@ -0,0 +1,20 @@
+This content is produced and maintained by the openHAB project.
+
+* Project home: https://www.openhab.org
+
+== Declared Project Licenses
+
+This program and the accompanying materials are made available under the terms
+of the Eclipse Public License 2.0 which is available at
+https://www.eclipse.org/legal/epl-2.0/.
+
+== Source Code
+
+https://github.com/openhab/openhab-addons
+
+== Third-party Content
+
+jomnilink
+* License: EPL-2.0
+* Project: https://github.com/digitaldan/jomnilink
+* Source: https://github.com/digitaldan/jomnilink
diff --git a/bundles/org.openhab.binding.omnilink/README.md b/bundles/org.openhab.binding.omnilink/README.md
new file mode 100644
index 00000000000..8a2d454da05
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/README.md
@@ -0,0 +1,321 @@
+# HAI/Leviton OmniLink Binding
+
+This binding integrates the [OmniPro and Lumina](http://www.leviton.com/en/products/security-automation/automation-av-controllers/omni-security-systems) line of home automation systems.
+At its core the OmniPro is a hardware board that provides security and access features.
+It connects to many other devices through serial ports or wired contacts and exposes them through a single TCP based API.
+
+## Supported Things
+
+The OmniPro/Lumina controller acts as a "bridge" for accessing other connected devices.
+
+
+| Omni type | Hardware Type | Things |
+|:---------------------------|:-------------------------------------------------|:----------------------------------|
+| Controller | Omni (Pro II, IIe, LTe), Lumina | `controller` (omni, lumina) |
+| Lights | Built-in, UPB, HLC | `unit`, `dimmable`, `upb`, `room` |
+| Thermostats | Omnistat, Omnistat2 | `thermostat` |
+| Temperature Sensors | 31A00-1/31A00-7 | `temp_sensor` |
+| Humidity Sensors | 31A00-2 | `humidity_sensor` |
+| Zones | Built-in/Hardwire, GE Wireless | `zone` |
+| Audio Zones/Sources | HAI Hi-Fi, Russound, NuVo, Xantech, Speakercraft | `audio_zone`, `audio_source` |
+| Consoles | HAI Omni Console, HAI Lumina Console | `console` |
+| Areas | Built-in | `area`, `lumina_area` |
+| Buttons | Built-in | `button` |
+| Flags | Built-in | `flag` |
+| Output | Built-in/Hardwire | `output` |
+| Access Control Reader Lock | Leviton Access Control Reader | `lock` |
+
+
+
+## Discovery
+
+### Controller
+
+Omni and Lumina controllers must be manually added using the IP and port of the controller as well as the 2 encryption keys required for network access.
+
+### Devices
+
+Once a connection can be established to a controller, all connected devices will be automatically discovered and added to the inbox.
+
+## Thing Configuration
+
+An Omni or Lumina controller requires the IP address (`ipAddress`), optional port (`port` defaults to 4369), and 2 encryption keys (`key1`, `key2`).
+The hexadecimal pairs in the encryption keys are typically delimited using a colon`:`, but dashes `-`, spaces ` ` or no delimiter may be used.
+
+In the thing file, this looks like:
+
+```
+Bridge omnilink:controller:home [ ipAddress="127.0.0.1", port=4369, key1="XXXXXXXXXXXXXXXX", key2="XXXXXXXXXXXXXXXX" ] {
+ // Add your things here
+}
+```
+
+The devices are identified by the device number that the OmniLink bridge assigns to them, see the [Full Example](#full-example) section below for a manual configuration example.
+
+## Channels
+
+The devices support some of the following channels:
+
+| Channel Type ID | Item Type | Description | Thing types supporting this channel |
+|-----------------------------|----------------------|--------------------------------------------------------------------------------------|-----------------------------------------------------|
+| `activate_keypad_emergency` | Number | Activate a burglary, fire, or auxiliary keypad emergency alarm on Omni based models. | `area` |
+| `alarm_burglary` | Switch | Indicates if a burglary alarm is active. | `area` |
+| `alarm_fire` | Switch | Indicates if a fire alarm is active. | `area` |
+| `alarm_gas` | Switch | Indicates if a gas alarm is active. | `area` |
+| `alarm_auxiliary` | Switch | Indicates if a auxiliary alarm is active. | `area` |
+| `alarm_freeze` | Switch | Indicates if a freeze alarm is active. | `area` |
+| `alarm_water` | Switch | Indicates if a water alarm is active. | `area` |
+| `alarm_duress` | Switch | Indicates if a duress alarm is active. | `area` |
+| `alarm_temperature` | Switch | Indicates if a temperature alarm is active. | `area` |
+| `mode` | Number | Represents the area security mode. | `area`, `lumina_area` |
+| `disarm` | String | Send a 4 digit user code to disarm the system. | `area` |
+| `day` | String | Send a 4 digit user code to arm the system to day. | `area` |
+| `night` | String | Send a 4 digit user code to arm the system to night. | `area` |
+| `away` | String | Send a 4 digit user code to arm the system to away. | `area` |
+| `vacation` | String | Send a 4 digit user code to arm the system to vacation. | `area` |
+| `day_instant` | String | Send a 4 digit user code to arm the system to day instant. | `area` |
+| `night_delayed` | String | Send a 4 digit user code to arm the system to night delayed. | `area` |
+| `home` | String | Send a 4 digit user code to set the system to home. | `lumina_area` |
+| `sleep` | String | Send a 4 digit user code to set the system to sleep. | `lumina_area` |
+| `away` | String | Send a 4 digit user code to set the system to away. | `lumina_area` |
+| `vacation` | String | Send a 4 digit user code to set the system to vacation. | `lumina_area` |
+| `party` | String | Send a 4 digit user code to set the system to party. | `lumina_area` |
+| `special` | String | Send a 4 digit user code to set the system to special. | `lumina_area` |
+| `source_text_{1,2,3,4,5,6}` | String | A line of metadata from this audio source. | `audio_source` |
+| `polling` | Switch | Enable or disable polling of this audio source. | `audio_source` |
+| `zone_power` | Switch | Power status of this audio zone. | `audio_zone` |
+| `zone_mute` | Switch | Mute status of this audio zone. | `audio_zone` |
+| `zone_volume` | Dimmer | Volume level of this audio zone. | `audio_zone` |
+| `zone_source` | Number | Source for this audio zone. | `audio_zone` |
+| `zone_control` | Player | Control the audio zone, e.g. start/stop/next/previous. | `audio_zone` |
+| `sysdate` | DateTime | Set controller date/time. | `controller` |
+| `last_log` | String | Last log message on the controller, represented in JSON. | `controller` |
+| `enable_disable_beeper` | Switch | Enable/Disable the beeper for this/all console(s). | `controller`, `console` |
+| `beep` | Switch | Send a beep command to this/all console(s). | `controller`, `console` |
+| `press` | Switch | Sends a button event to the controller. | `button` |
+| `low_setpoint` | Number | The current low setpoint for this humidity/temperature sensor. | `temp_sensor`, `humidity_sensor` |
+| `high_setpoint` | Number | The current high setpoint for this humidity/temperature sensor. | `temp_sensor`, `humidity_sensor` |
+| `temperature` | Number:Temperature | The current temperature at this thermostat/temperature sensor. | `thermostat`, `temp_sensor` |
+| `humidity` | Number:Dimensionless | The current relative humidity at this thermostat/humidity sensor. | `thermostat`, `humidity_sensor` |
+| `freeze_alarm` | Contact | Closed when freeze alarm is triggered by this thermostat. | `thermostat` |
+| `comm_failure` | Contact | Closed during a communications failure with this thermostat. | `thermostat` |
+| `outdoor_temperature` | Number:Temperature | The current outdoor temperature detected by this thermostat. | `thermostat` |
+| `heat_setpoint` | Number:Temperature | The current low/heating setpoint of this thermostat. | `thermostat` |
+| `cool_setpoint` | Number:Temperature | The current high/cooling setpoint of this thermostat. | `thermostat` |
+| `humidify_setpoint` | Number:Dimensionless | The current low/humidify setpoint for this thermostat. | `thermostat` |
+| `dehumidify_setpoint` | Number:Dimensionless | The current high/dehumidify setpoint for this thermostat. | `thermostat` |
+| `system_mode` | Number | The current system mode of this thermostat. | `thermostat` |
+| `fan_mode` | Number | The current fan mode of this thermostat. | `thermostat` |
+| `hold_status` | Number | The current hold status of this thermostat. | `thermostat` |
+| `status` | Number | The current numeric status of this thermostat. | `thermostat` |
+| `level` | Dimmer | Increase/Decrease the level of this unit/dimmable unit/UPB unit. | `unit`, `dimmable`, `upb` |
+| `switch` | Switch | Turn this unit/dimmable unit/flag/output/room on/off. | `unit`, `dimmable`, `upb`, `flag`, `output`, `room` |
+| `on_for_seconds` | Number | Turn on this unit for a specified number of seconds. | `unit`, `dimmable`, `upb`, `flag`, `output` |
+| `off_for_seconds` | Number | Turn off this unit for a specified number of seconds. | `unit`, `dimmable`, `upb`, `flag`, `output` |
+| `on_for_minutes` | Number | Turn on this unit for a specified number of minutes. | `unit`, `dimmable`, `upb`, `flag`, `output` |
+| `off_for_minutes` | Number | Turn off this unit for a specified number of minutes. | `unit`, `dimmable`, `upb`, `flag`, `output` |
+| `on_for_hours` | Number | Turn on this unit for a specified number of hours. | `unit`, `dimmable`, `upb`, `flag`, `output` |
+| `off_for_hours` | Number | Turn off this unit for a specified number of hours. | `unit`, `dimmable`, `upb`, `flag`, `output` |
+| `upb_status` | String | Send a UPB status request message for this UPB unit to the controller. | `upb` |
+| `value` | Number | Numeric value of this flag. | `flag` |
+| `scene_{a,b,c,d}` | Switch | Turn this scene on/off. | `room` |
+| `state` | Number | The current state of this room. | `room` |
+| `contact` | Contact | Contact state information of this zone. | `zone` |
+| `current_condition` | Number | Current condition of this zone. | `zone` |
+| `latched_alarm_status` | Number | Latched alarm status of this zone. | `zone` |
+| `arming_status` | Number | Arming status of this zone. | `zone` |
+| `bypass` | String | Send a 4 digit user code to bypass this zone. | `zone` |
+| `restore` | String | Send a 4 digit user code to restore this zone. | `zone` |
+
+
+### Trigger Channels
+
+The devices support some of the following trigger channels:
+
+| Channel Type ID | Description | Thing types supporting this channel |
+|-------------------------------|--------------------------------------------------------------------------------------|-------------------------------------|
+| `all_on_off_event` | Event sent when an all on/off event occurs. | `area`, `lumina_area` |
+| `phone_line_event` | Event sent when the phone line changes state. | `controller` |
+| `ac_power_event` | Event sent when AC trouble conditions are detected. | `controller` |
+| `battery_event` | Event sent when battery trouble conditions are detected. | `controller` |
+| `dcm_event` | Event sent when digital communicator trouble conditions are detected. | `controller` |
+| `energy_cost_event` | Event sent when the cost of energy changes. | `controller` |
+| `camera_trigger_event` | Event sent when a camera trigger is detected. | `controller` |
+| `upb_link_activated_event` | Event sent when a UPB link is activated. | `controller` |
+| `upb_link_deactivated_event` | Event sent when a UPB link is deactivated. | `controller` |
+| `activated_event` | Event sent when a button is activated. | `button` |
+| `switch_press_event` | Event sent when an ALC, UPB, Radio RA, or Starlite switch is pressed. | `dimmable`, `upb` |
+
+
+## Full Example
+
+### Example `omnilink.things`
+
+```
+Bridge omnilink:controller:home [ ipAddress="127.0.0.1", port=4369, key1="XXXXXXXXXXXXXXXX", key2="XXXXXXXXXXXXXXXX" ] {
+ Thing area MainArea "Main Area" @ "Home" [ number=1 ]
+ Thing upb UpKitTable "Table Lights" @ "Upstairs Kitchen" [ number=4 ]
+ Thing upb UpOfcDesk "Desk Lights" @ "Upstairs Office" [ number=10 ]
+ Thing thermostat UpstrsThermo "Upstairs Temperature" @ "Upstairs Entry" [ number=1 ]
+ Thing zone FrontDoor "Front Door" @ "Upstairs Entry" [ number=2 ]
+ Thing zone GarageDoor "Garage Door" @ "Laundry Room" [ number=3 ]
+ Thing zone BackDoor "Back Door" @ "Upstairs Kitchen" [ number=4 ]
+ Thing zone OneCarGarageDo "One Car Garage" @ "Garage" [ number=5 ]
+ Thing zone TwoCarGarageDo "Two Car Garage" @ "Garage" [ number=6 ]
+ Thing zone BsmtBackDoor "Back Door" @ "Basement Workout Room" [ number=8 ]
+ Thing zone MBRDeckDoor "Deck Door" @ "Master Bedroom" [ number=9 ]
+ Thing zone MBRMotion "Motion" @ "Master Bedroom" [ number=10 ]
+ Thing zone PorchDoor "Porch Door" @ "Upstairs Office" [ number=11 ]
+ Thing zone UpOffMotion "Motion" @ "Upstairs Office" [ number=12 ]
+ Thing zone UpLivMotion "Motion" @ "Upstairs Living Room" [ number=13 ]
+ Thing zone BsmtWORMotion "Motion" @ "Basement Workout Room" [ number=14 ]
+ Thing zone GarageMotion "Motion" @ "Garage" [ number=15 ]
+ Thing console UpstrsConsole "Console" @ "Laundry Room" [ number=1 ]
+ Thing button MainButton "Button" @ "Home" [ number=1 ]
+}
+```
+
+### Example `omnilink.items`
+
+```
+/*
+ * Alarms / Areas
+ */
+Group:Switch:OR(ON, OFF) Alarms "All Alarms [%s]"
+String AlarmMode "Alarm [%s]" {channel="omnilink:area:home:MainArea:mode" [profile="transform:MAP", function="area-modes.map", sourceFormat="%s"]}
+Switch AlarmBurglary "Burglary Alarm [%s]" (Alarms) {channel="omnilink:area:home:MainArea:alarm_burglary"}
+Switch AlarmFire "Fire Alarm [%s]" (Alarms) {channel="omnilink:area:home:MainArea:alarm_fire"}
+Switch alarm_gas "Gas Alarm [%s]" (Alarms) {channel="omnilink:area:home:MainArea:alarm_gas"}
+Switch AlarmAuxiliary "Auxiliary Alarm [%s]" (Alarms) {channel="omnilink:area:home:MainArea:alarm_auxiliary"}
+Switch AlarmFreeze "Freeze Alarm [%s]" (Alarms) {channel="omnilink:area:home:MainArea:alarm_freeze"}
+Switch AlarmWater "Water Alarm [%s]" (Alarms) {channel="omnilink:area:home:MainArea:alarm_water"}
+Switch AlarmDuress "Duress Alarm [%s]" (Alarms) {channel="omnilink:area:home:MainArea:alarm_duress"}
+Switch AlarmTemperature "Temperature Alarm [%s]" (Alarms) {channel="omnilink:area:home:MainArea:alarm_temperature"}
+Number AlarmModeDisarm {channel="omnilink:area:home:MainArea:disarm"}
+Number AlarmModeDay {channel="omnilink:area:home:MainArea:day"}
+Number AlarmModeNight {channel="omnilink:area:home:MainArea:night"}
+Number AlarmModeAway {channel="omnilink:area:home:MainArea:away"}
+Number AlarmModeVacation {channel="omnilink:area:home:MainArea:vacation"}
+Number AlarmModeDayInstant {channel="omnilink:area:home:MainArea:day_instant"}
+Number AlarmModeNightDelayed {channel="omnilink:area:home:MainArea:night_delayed"}
+
+/*
+ * Lights
+ */
+Switch UpKitTable "Table Lights [%s]" {channel="omnilink:upb:home:UpKitTable:level"}
+Dimmer UpOfcDesk "Desk Lights [%d]" {channel="omnilink:upb:home:UpOfcDesk:level"}
+
+/*
+ * Thermostat
+ */
+Group UpstrsThermo "Upstairs Thermostat"
+Number:Temperature UpstrsThermo_Temp "Temperature [%.1f %unit%]" (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:temperature"}
+Number UpstrsThermo_Status "Status [MAP(therm-status.map):%s]" (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:status"}
+Number UpstrsThermo_System "System Mode [MAP(therm-tempmode.map):%s]" (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:system_mode"}
+Number UpstrsThermo_Fan "Fan Mode [MAP(therm-fanmode.map):%s]" (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:fan_mode"}
+Number UpstrsThermo_Hold "Hold Mode [MAP(therm-holdmode.map):%s]" (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:hold_mode"}
+Number UpstrsThermo_HeatPoint "System HeatPoint [%d]" (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:heat_setpoint"}
+Number UpstrsThermo_CoolPoint "System CoolPoint [%d]" (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:cool_setpoint"}
+
+/*
+ * Motion and Doors
+ */
+Group:Contact:OR(OPEN, CLOSED) Doors "All Doors [%s]"
+Contact FrontDoor "Front Door" (Doors) {channel="omnilink:zone:home:FrontDoor:contact"}
+Contact GarageDoor "Garage Door" (Doors) {channel="omnilink:zone:home:GarageDoor:contact"}
+Contact BackDoor "Back Door" (Doors) {channel="omnilink:zone:home:BackDoor:contact"}
+Contact BsmtBackDoor "Back Door" (Doors) {channel="omnilink:zone:home:BsmtBackDoor:contact"}
+Contact MBRDeckDoor "Deck Door" (Doors) {channel="omnilink:zone:home:MBRDeckDoor:contact"}
+Contact PorchDoor "Porch Door" (Doors) {channel="omnilink:zone:home:PorchDoor:contact"}
+
+Group:Contact:OR(OPEN, CLOSED) GarageDoors "All Garage Doors [%s]"
+Contact TwoCarGarageDo "Two Car Garage Door" (GarageDoors) {channel="omnilink:zone:home:TwoCarGarageDo:contact"}
+Contact OneCarGarageDo "One Car Garage Door" (GarageDoors) {channel="omnilink:zone:home:OneCarGarageDo:contact"}
+
+Group:Contact:OR(OPEN, CLOSED) Motion "All Motion Sensors [%s]"
+Contact MBRMotion "Motion" (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:MBRMotion:contact"}
+Contact UpOffMotion "Motion" (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:UpOffMotion:contact"}
+Contact UpLivMotion "Motion" (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:UpLivMotion:contact"}
+Contact BsmtWORMotion "Motion" (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:BsmtWORMotion:contact"}
+Contact GarageMotion "Motion" (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:GarageMotion:contact"}
+
+/*
+ * Console
+ */
+String UpstrsConsole_Beeper "Enable/Disable Beeper [%s]" {channel="omnilink:console:home:UpstrsConsole:enable_disable_beeper"}
+Number UpstrsConsole_Beep "Beep Console" {channel="omnilink:console:home:UpstrsConsole:beep"}
+
+/*
+ * Button
+ */
+Switch MainButton "Toggle button [%s]" {channel="omnilink:button:home:MainButton:press"}
+
+/*
+ * Other OmniPro items
+ */
+DateTime OmniProTime "Last Time Update [%1$ta %1$tR]" {channel="omnilink:controller:home:sysdate"}
+```
+
+### Example `therm-status.map`
+
+```
+0=Idle
+1=Heating
+2=Cooling
+```
+
+### Example `therm-tempmode.map`
+
+```
+0=Off
+1=Heat
+2=Cool
+3=Auto
+5=Emergency heat
+```
+
+### Example `therm-fanmode.map`
+
+```
+0=Auto
+1=On
+2=Cycle
+```
+
+### Example `therm-holdmode.map`
+
+```
+0=Off
+1=Hold
+2=Vacation hold
+```
+
+### Example `area-modes.map`
+
+```
+0=Off
+1=Day
+2=Night
+3=Away
+4=Vacation
+5=Day instant
+6=Night delayed
+9=Arming day
+10=Arming night
+11=Arming away
+12=Arming vacation
+13=Arming day instant
+14=Arming night delay
+=Unknown
+```
+
+### Example `omnilink.rules`
+
+```
+rule "Update OmniPro Time"
+when
+ Time cron "0 0 0/1 1/1 * ? *"
+then
+ OmniProTime.sendCommand( new DateTimeType() )
+end
+```
diff --git a/bundles/org.openhab.binding.omnilink/pom.xml b/bundles/org.openhab.binding.omnilink/pom.xml
new file mode 100644
index 00000000000..ee0e2767fb4
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/pom.xml
@@ -0,0 +1,26 @@
+
+
+
+ 4.0.0
+
+
+ org.openhab.addons.bundles
+ org.openhab.addons.reactor.bundles
+ 3.1.0-SNAPSHOT
+
+
+ org.openhab.binding.omnilink
+
+ openHAB Add-ons :: Bundles :: OmniLink Binding
+
+
+
+ com.github.digitaldan
+ jomnilink
+ 1.4.0
+ compile
+
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/feature/feature.xml b/bundles/org.openhab.binding.omnilink/src/main/feature/feature.xml
new file mode 100644
index 00000000000..250b3b36b5f
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/feature/feature.xml
@@ -0,0 +1,23 @@
+
+
+
+ mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features
+
+
+ openhab-runtime-base
+ mvn:org.openhab.addons.bundles/org.openhab.binding.omnilink/${project.version}
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/AreaAlarm.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/AreaAlarm.java
new file mode 100644
index 00000000000..6b8bcd27821
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/AreaAlarm.java
@@ -0,0 +1,57 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.math.BigInteger;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link AreaAlarm} class defines the different types of alarms supported
+ * by the OmniLink Protocol.
+ *
+ * @author Craig Hamilton - Initial contribution
+ */
+@NonNullByDefault
+public enum AreaAlarm {
+ BURGLARY(CHANNEL_AREA_ALARM_BURGLARY, 0),
+ FIRE(CHANNEL_AREA_ALARM_FIRE, 1),
+ GAS(CHANNEL_AREA_ALARM_GAS, 2),
+ AUXILIARY(CHANNEL_AREA_ALARM_AUXILIARY, 3),
+ FREEZE(CHANNEL_AREA_ALARM_FREEZE, 4),
+ WATER(CHANNEL_AREA_ALARM_WATER, 5),
+ DURESS(CHANNEL_AREA_ALARM_DURESS, 6),
+ TEMPERATURE(CHANNEL_AREA_ALARM_TEMPERATURE, 7);
+
+ private final String channelUID;
+ private final int bit;
+
+ AreaAlarm(String channelUID, int bit) {
+ this.channelUID = channelUID;
+ this.bit = bit;
+ }
+
+ public boolean isSet(BigInteger alarmBits) {
+ return alarmBits.testBit(bit);
+ }
+
+ public boolean isSet(int alarmBits) {
+ return isSet(BigInteger.valueOf(alarmBits));
+ }
+
+ public String getChannelUID() {
+ return channelUID;
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/AudioPlayer.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/AudioPlayer.java
new file mode 100644
index 00000000000..e53467c5ec1
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/AudioPlayer.java
@@ -0,0 +1,76 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link AudioPlayer} defines some methods that are used to
+ * interface with an OmniLink Audio Player.
+ *
+ * @author Brian O'Connell - Initial contribution
+ */
+@NonNullByDefault
+public enum AudioPlayer {
+ NUVO(1, 6, 8, 7, 9, 10),
+ NUVO_GRAND_ESSENTIA_SIMPLESE(2, 6, 8, 7, 9, 10),
+ NUVO_GRAND_GRAND_CONCERTO(3, 6, 6, 6, 9, 10),
+ RUSSOUND(4, 6, 8, 7, 11, 12),
+ XANTECH(6, 13, 15, 14, 16, 17),
+ SPEAKERCRAFT(7, 45, 44, 46, 42, 43),
+ PROFICIENT(8, 45, 44, 46, 42, 43);
+
+ private final int featureCode;
+ private final int playCommand;
+ private final int pauseCommand;
+ private final int stopCommand;
+ private final int previousCommand;
+ private final int nextCommand;
+
+ AudioPlayer(int featureCode, int playCommand, int pauseCommand, int stopCommand, int previousCommand,
+ int nextCommand) {
+ this.featureCode = featureCode;
+ this.playCommand = playCommand;
+ this.pauseCommand = pauseCommand;
+ this.stopCommand = stopCommand;
+ this.previousCommand = previousCommand;
+ this.nextCommand = nextCommand;
+ }
+
+ public int getPlayCommand() {
+ return playCommand;
+ }
+
+ public int getPauseCommand() {
+ return pauseCommand;
+ }
+
+ public int getStopCommand() {
+ return stopCommand;
+ }
+
+ public int getPreviousCommand() {
+ return previousCommand;
+ }
+
+ public int getNextCommand() {
+ return nextCommand;
+ }
+
+ public static Optional getAudioPlayerForFeatureCode(int featureCode) {
+ return Arrays.stream(values()).filter(v -> v.featureCode == featureCode).findAny();
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/OmnilinkBindingConstants.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/OmnilinkBindingConstants.java
new file mode 100644
index 00000000000..05843027dbc
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/OmnilinkBindingConstants.java
@@ -0,0 +1,188 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal;
+
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.ThingTypeUID;
+
+/**
+ * The {@link OmnilinkBindingConstants} class defines common constants, which are
+ * used across the whole binding.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class OmnilinkBindingConstants {
+
+ public static final String BINDING_ID = "omnilink";
+
+ // List of all Thing Type UIDs
+ public static final ThingTypeUID THING_TYPE_BRIDGE = new ThingTypeUID(BINDING_ID, "controller");
+ public static final ThingTypeUID THING_TYPE_OMNI_AREA = new ThingTypeUID(BINDING_ID, "area");
+ public static final ThingTypeUID THING_TYPE_LUMINA_AREA = new ThingTypeUID(BINDING_ID, "lumina_area");
+ public static final ThingTypeUID THING_TYPE_ZONE = new ThingTypeUID(BINDING_ID, "zone");
+ public static final ThingTypeUID THING_TYPE_LOCK = new ThingTypeUID(BINDING_ID, "lock");
+ public static final ThingTypeUID THING_TYPE_UNIT_UPB = new ThingTypeUID(BINDING_ID, "upb");
+ public static final ThingTypeUID THING_TYPE_UNIT = new ThingTypeUID(BINDING_ID, "unit");
+ public static final ThingTypeUID THING_TYPE_DIMMABLE = new ThingTypeUID(BINDING_ID, "dimmable");
+ public static final ThingTypeUID THING_TYPE_FLAG = new ThingTypeUID(BINDING_ID, "flag");
+ public static final ThingTypeUID THING_TYPE_OUTPUT = new ThingTypeUID(BINDING_ID, "output");
+ public static final ThingTypeUID THING_TYPE_ROOM = new ThingTypeUID(BINDING_ID, "room");
+ public static final ThingTypeUID THING_TYPE_BUTTON = new ThingTypeUID(BINDING_ID, "button");
+ public static final ThingTypeUID THING_TYPE_THERMOSTAT = new ThingTypeUID(BINDING_ID, "thermostat");
+ public static final ThingTypeUID THING_TYPE_AUDIO_ZONE = new ThingTypeUID(BINDING_ID, "audio_zone");
+ public static final ThingTypeUID THING_TYPE_AUDIO_SOURCE = new ThingTypeUID(BINDING_ID, "audio_source");
+ public static final ThingTypeUID THING_TYPE_CONSOLE = new ThingTypeUID(BINDING_ID, "console");
+ public static final ThingTypeUID THING_TYPE_TEMP_SENSOR = new ThingTypeUID(BINDING_ID, "temp_sensor");
+ public static final ThingTypeUID THING_TYPE_HUMIDITY_SENSOR = new ThingTypeUID(BINDING_ID, "humidity_sensor");
+
+ // List of all Channel ids
+
+ // zones
+ public static final String CHANNEL_ZONE_CONTACT = "contact";
+ public static final String CHANNEL_ZONE_CURRENT_CONDITION = "current_condition";
+ public static final String CHANNEL_ZONE_LATCHED_ALARM_STATUS = "latched_alarm_status";
+ public static final String CHANNEL_ZONE_ARMING_STATUS = "arming_status";
+ public static final String CHANNEL_ZONE_BYPASS = "bypass";
+ public static final String CHANNEL_ZONE_RESTORE = "restore";
+
+ // areas
+ public static final String CHANNEL_AREA_MODE = "mode";
+ public static final String CHANNEL_AREA_ACTIVATE_KEYPAD_EMERGENCY = "activate_keypad_emergency";
+ public static final String CHANNEL_AREA_ALARM_BURGLARY = "alarm_burglary";
+ public static final String CHANNEL_AREA_ALARM_FIRE = "alarm_fire";
+ public static final String CHANNEL_AREA_ALARM_GAS = "alarm_gas";
+ public static final String CHANNEL_AREA_ALARM_AUXILIARY = "alarm_auxiliary";
+ public static final String CHANNEL_AREA_ALARM_FREEZE = "alarm_freeze";
+ public static final String CHANNEL_AREA_ALARM_WATER = "alarm_water";
+ public static final String CHANNEL_AREA_ALARM_DURESS = "alarm_duress";
+ public static final String CHANNEL_AREA_ALARM_TEMPERATURE = "alarm_temperature";
+
+ public static final String CHANNEL_AREA_SECURITY_MODE_DISARM = "disarm";
+ public static final String CHANNEL_AREA_SECURITY_MODE_DAY = "day";
+ public static final String CHANNEL_AREA_SECURITY_MODE_NIGHT = "night";
+ public static final String CHANNEL_AREA_SECURITY_MODE_AWAY = "away";
+ public static final String CHANNEL_AREA_SECURITY_MODE_VACATION = "vacation";
+ public static final String CHANNEL_AREA_SECURITY_MODE_DAY_INSTANT = "day_instant";
+ public static final String CHANNEL_AREA_SECURITY_MODE_NIGHT_DELAYED = "night_delayed";
+
+ public static final String CHANNEL_AREA_SECURITY_MODE_HOME = "home";
+ public static final String CHANNEL_AREA_SECURITY_MODE_SLEEP = "sleep";
+ public static final String CHANNEL_AREA_SECURITY_MODE_PARTY = "party";
+ public static final String CHANNEL_AREA_SECURITY_MODE_SPECIAL = "special";
+
+ // units
+ public static final String CHANNEL_UNIT_LEVEL = "level";
+ public static final String CHANNEL_UNIT_SWITCH = "switch";
+ public static final String CHANNEL_UNIT_ON_FOR_SECONDS = "on_for_seconds";
+ public static final String CHANNEL_UNIT_ON_FOR_MINUTES = "on_for_minutes";
+ public static final String CHANNEL_UNIT_ON_FOR_HOURS = "on_for_hours";
+ public static final String CHANNEL_UNIT_OFF_FOR_SECONDS = "off_for_seconds";
+ public static final String CHANNEL_UNIT_OFF_FOR_MINUTES = "off_for_minutes";
+ public static final String CHANNEL_UNIT_OFF_FOR_HOURS = "off_for_hours";
+ public static final String CHANNEL_FLAG_VALUE = "value";
+ public static final String CHANNEL_FLAG_SWITCH = "switch";
+ public static final String CHANNEL_UPB_STATUS = "upb_status";
+
+ public static final String CHANNEL_ROOM_SWITCH = "switch";
+ public static final String CHANNEL_ROOM_SCENE_A = "scene_a";
+ public static final String CHANNEL_ROOM_SCENE_B = "scene_b";
+ public static final String CHANNEL_ROOM_SCENE_C = "scene_c";
+ public static final String CHANNEL_ROOM_SCENE_D = "scene_d";
+ public static final String CHANNEL_ROOM_STATE = "state";
+
+ public static final String CHANNEL_SYSTEMDATE = "sysdate";
+ public static final String CHANNEL_EVENT_LOG = "last_log";
+
+ // buttons
+ public static final String CHANNEL_BUTTON_PRESS = "press";
+
+ // locks
+ public static final String CHANNEL_LOCK_SWITCH = "switch";
+
+ // thermostats
+ public static final String CHANNEL_THERMO_FREEZE_ALARM = "freeze_alarm";
+ public static final String CHANNEL_THERMO_COMM_FAILURE = "comm_failure";
+ public static final String CHANNEL_THERMO_STATUS = "status";
+ public static final String CHANNEL_THERMO_CURRENT_TEMP = "temperature";
+ public static final String CHANNEL_THERMO_OUTDOOR_TEMP = "outdoor_temperature";
+ public static final String CHANNEL_THERMO_HUMIDITY = "humidity";
+ public static final String CHANNEL_THERMO_HUMIDIFY_SETPOINT = "humidify_setpoint";
+ public static final String CHANNEL_THERMO_DEHUMIDIFY_SETPOINT = "dehumidify_setpoint";
+ public static final String CHANNEL_THERMO_SYSTEM_MODE = "system_mode";
+ public static final String CHANNEL_THERMO_FAN_MODE = "fan_mode";
+ public static final String CHANNEL_THERMO_HOLD_STATUS = "hold_status";
+ public static final String CHANNEL_THERMO_COOL_SETPOINT = "cool_setpoint";
+ public static final String CHANNEL_THERMO_HEAT_SETPOINT = "heat_setpoint";
+
+ // temp / humidity sensors
+ public static final String CHANNEL_AUX_TEMP = "temperature";
+ public static final String CHANNEL_AUX_HUMIDITY = "humidity";
+ public static final String CHANNEL_AUX_LOW_SETPOINT = "low_setpoint";
+ public static final String CHANNEL_AUX_HIGH_SETPOINT = "high_setpoint";
+
+ // consoles
+ public static final String CHANNEL_CONSOLE_BEEP = "beep";
+ public static final String CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER = "enable_disable_beeper";
+
+ // audio zones
+ public static final String CHANNEL_AUDIO_ZONE_POWER = "zone_power";
+ public static final String CHANNEL_AUDIO_ZONE_MUTE = "zone_mute";
+ public static final String CHANNEL_AUDIO_ZONE_VOLUME = "zone_volume";
+ public static final String CHANNEL_AUDIO_ZONE_SOURCE = "zone_source";
+ public static final String CHANNEL_AUDIO_ZONE_CONTROL = "zone_control";
+
+ // audio sources
+ public static final String CHANNEL_AUDIO_SOURCE_TEXT1 = "source_text_1";
+ public static final String CHANNEL_AUDIO_SOURCE_TEXT2 = "source_text_2";
+ public static final String CHANNEL_AUDIO_SOURCE_TEXT3 = "source_text_3";
+ public static final String CHANNEL_AUDIO_SOURCE_TEXT4 = "source_text_4";
+ public static final String CHANNEL_AUDIO_SOURCE_TEXT5 = "source_text_5";
+ public static final String CHANNEL_AUDIO_SOURCE_TEXT6 = "source_text_6";
+ public static final String CHANNEL_AUDIO_SOURCE_POLLING = "polling";
+
+ // trigger channels
+ public static final String TRIGGER_CHANNEL_BUTTON_ACTIVATED_EVENT = "activated_event";
+ public static final String TRIGGER_CHANNEL_PHONE_LINE_EVENT = "phone_line_event";
+ public static final String TRIGGER_CHANNEL_AC_POWER_EVENT = "ac_power_event";
+ public static final String TRIGGER_CHANNEL_BATTERY_EVENT = "battery_event";
+ public static final String TRIGGER_CHANNEL_DCM_EVENT = "dcm_event";
+ public static final String TRIGGER_CHANNEL_ENERGY_COST_EVENT = "energy_cost_event";
+ public static final String TRIGGER_CHANNEL_CAMERA_TRIGGER_EVENT = "camera_trigger_event";
+ public static final String TRIGGER_CHANNEL_ACCESS_CONTROL_READER_EVENT = "access_control_reader_event";
+ public static final String TRIGGER_CHANNEL_AREA_ALL_ON_OFF_EVENT = "all_on_off_Event";
+ public static final String TRIGGER_CHANNEL_ZONE_STATE_EVENT = "zone_state_Event";
+ public static final String TRIGGER_CHANNEL_SWITCH_PRESS_EVENT = "switch_press_event";
+ public static final String TRIGGER_CHANNEL_UPB_LINK_ACTIVATED_EVENT = "upb_link_activated_event";
+ public static final String TRIGGER_CHANNEL_UPB_LINK_DEACTIVATED_EVENT = "upb_link_deactivated_event";
+
+ // thing configuration and properties keys
+ public static final String THING_PROPERTIES_NAME = "name";
+ public static final String THING_PROPERTIES_NUMBER = "number";
+ public static final String THING_PROPERTIES_AREA = "area";
+ public static final String THING_PROPERTIES_AUTO_START = "autostart";
+ public static final String THING_PROPERTIES_MODEL_NUMBER = "modelNumber";
+ public static final String THING_PROPERTIES_MAJOR_VERSION = "majorVersion";
+ public static final String THING_PROPERTIES_MINOR_VERSION = "minorVersion";
+ public static final String THING_PROPERTIES_REVISION = "revision";
+ public static final String THING_PROPERTIES_PHONE_NUMBER = "phoneNumber";
+
+ public static final Set SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_OMNI_AREA,
+ THING_TYPE_LUMINA_AREA, THING_TYPE_ZONE, THING_TYPE_BRIDGE, THING_TYPE_FLAG, THING_TYPE_ROOM,
+ THING_TYPE_BUTTON, THING_TYPE_UNIT_UPB, THING_TYPE_THERMOSTAT, THING_TYPE_CONSOLE, THING_TYPE_AUDIO_ZONE,
+ THING_TYPE_AUDIO_SOURCE, THING_TYPE_TEMP_SENSOR, THING_TYPE_HUMIDITY_SENSOR, THING_TYPE_LOCK,
+ THING_TYPE_OUTPUT, THING_TYPE_UNIT, THING_TYPE_DIMMABLE);
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/OmnilinkHandlerFactory.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/OmnilinkHandlerFactory.java
new file mode 100644
index 00000000000..0c88a08343f
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/OmnilinkHandlerFactory.java
@@ -0,0 +1,105 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.handler.AudioSourceHandler;
+import org.openhab.binding.omnilink.internal.handler.AudioZoneHandler;
+import org.openhab.binding.omnilink.internal.handler.ButtonHandler;
+import org.openhab.binding.omnilink.internal.handler.ConsoleHandler;
+import org.openhab.binding.omnilink.internal.handler.HumiditySensorHandler;
+import org.openhab.binding.omnilink.internal.handler.LockHandler;
+import org.openhab.binding.omnilink.internal.handler.LuminaAreaHandler;
+import org.openhab.binding.omnilink.internal.handler.OmniAreaHandler;
+import org.openhab.binding.omnilink.internal.handler.OmnilinkBridgeHandler;
+import org.openhab.binding.omnilink.internal.handler.TempSensorHandler;
+import org.openhab.binding.omnilink.internal.handler.ThermostatHandler;
+import org.openhab.binding.omnilink.internal.handler.UnitHandler;
+import org.openhab.binding.omnilink.internal.handler.ZoneHandler;
+import org.openhab.binding.omnilink.internal.handler.units.DimmableUnitHandler;
+import org.openhab.binding.omnilink.internal.handler.units.FlagHandler;
+import org.openhab.binding.omnilink.internal.handler.units.OutputHandler;
+import org.openhab.binding.omnilink.internal.handler.units.UpbRoomHandler;
+import org.openhab.binding.omnilink.internal.handler.units.dimmable.UpbUnitHandler;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseThingHandlerFactory;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerFactory;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * The {@link OmnilinkHandlerFactory} is responsible for creating things and thing
+ * handlers.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+@Component(service = ThingHandlerFactory.class, configurationPid = "binding.omnilink")
+public class OmnilinkHandlerFactory extends BaseThingHandlerFactory {
+
+ @Override
+ public boolean supportsThingType(ThingTypeUID thingTypeUID) {
+ return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
+ }
+
+ @Override
+ protected @Nullable ThingHandler createHandler(Thing thing) {
+ ThingTypeUID thingTypeUID = thing.getThingTypeUID();
+
+ if (thingTypeUID.equals(THING_TYPE_AUDIO_SOURCE)) {
+ return new AudioSourceHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_AUDIO_ZONE)) {
+ return new AudioZoneHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_BRIDGE)) {
+ return new OmnilinkBridgeHandler((Bridge) thing);
+ } else if (thingTypeUID.equals(THING_TYPE_BUTTON)) {
+ return new ButtonHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_CONSOLE)) {
+ return new ConsoleHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_DIMMABLE)) {
+ return new DimmableUnitHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_FLAG)) {
+ return new FlagHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_HUMIDITY_SENSOR)) {
+ return new HumiditySensorHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_LOCK)) {
+ return new LockHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_LUMINA_AREA)) {
+ return new LuminaAreaHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_OMNI_AREA)) {
+ return new OmniAreaHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_OUTPUT)) {
+ return new OutputHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_ROOM)) {
+ return new UpbRoomHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_TEMP_SENSOR)) {
+ return new TempSensorHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_THERMOSTAT)) {
+ return new ThermostatHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_UNIT)) {
+ return new UnitHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_UNIT_UPB)) {
+ return new UpbUnitHandler(thing);
+ } else if (thingTypeUID.equals(THING_TYPE_ZONE)) {
+ return new ZoneHandler(thing);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/SystemType.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/SystemType.java
new file mode 100644
index 00000000000..5ac9cf644df
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/SystemType.java
@@ -0,0 +1,41 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal;
+
+import java.util.Arrays;
+import java.util.Set;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link SystemType} enum defines the two supported system types which can
+ * interface with the binding
+ *
+ * @author Craig Hamilton - Initial contribution
+ */
+@NonNullByDefault
+public enum SystemType {
+ OMNI(16, 30, 38),
+ LUMINA(36, 37);
+
+ private final Set modelNumbers;
+
+ SystemType(Integer... modelNumbers) {
+ this.modelNumbers = Set.of(modelNumbers);
+ }
+
+ public static SystemType getType(int modelNumber) {
+ return Arrays.stream(values()).filter(s -> s.modelNumbers.contains(modelNumber)).findFirst()
+ .orElseThrow(IllegalArgumentException::new);
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/config/OmnilinkBridgeConfig.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/config/OmnilinkBridgeConfig.java
new file mode 100644
index 00000000000..8eaeec9e689
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/config/OmnilinkBridgeConfig.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.config;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * The {@link OmnilinkBridgeConfig} sets the authentication settings of the
+ * OmniLink Controller that will allow for proper communication.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class OmnilinkBridgeConfig {
+
+ private @Nullable String key1;
+ private @Nullable String key2;
+ private @Nullable String ipAddress;
+ private int port;
+ private int logPollingInterval;
+
+ public int getLogPollingInterval() {
+ return logPollingInterval;
+ }
+
+ public void setLogPollingInterval(int logPollingInterval) {
+ this.logPollingInterval = logPollingInterval;
+ }
+
+ public @Nullable String getKey1() {
+ return key1;
+ }
+
+ public void setKey1(String key1) {
+ this.key1 = key1;
+ }
+
+ public @Nullable String getKey2() {
+ return key2;
+ }
+
+ public void setKey2(String key2) {
+ this.key2 = key2;
+ }
+
+ public @Nullable String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public int getPort() {
+ return port;
+ }
+
+ public void setPort(int port) {
+ this.port = port;
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/ObjectPropertyRequest.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/ObjectPropertyRequest.java
new file mode 100644
index 00000000000..7cf1dd8a2a6
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/ObjectPropertyRequest.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.discovery;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.omnilink.internal.handler.BridgeOfflineException;
+import org.openhab.binding.omnilink.internal.handler.OmnilinkBridgeHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectProperties;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * @author Craig Hamilton - Initial contribution
+ *
+ * @param
+ */
+@NonNullByDefault
+public class ObjectPropertyRequest implements Iterable {
+ private final Logger logger = LoggerFactory.getLogger(ObjectPropertyRequest.class);
+
+ public static > Builder builder(
+ OmnilinkBridgeHandler bridgeHandler, U request, int objectNumber, int offset) {
+ return new Builder<>(bridgeHandler, request, objectNumber, offset);
+ }
+
+ private final OmnilinkBridgeHandler bridgeHandler;
+ private final ObjectPropertyRequests request;
+ private final int objectNumber;
+ private final int filter1;
+ private final int filter2;
+ private final int filter3;
+ private final int offset;
+
+ private ObjectPropertyRequest(OmnilinkBridgeHandler bridgeHandler, ObjectPropertyRequests request,
+ int objectNumber, int filter1, int filter2, int filter3, int offset) {
+ this.bridgeHandler = bridgeHandler;
+ this.request = request;
+ this.objectNumber = objectNumber;
+ this.filter1 = filter1;
+ this.filter2 = filter2;
+ this.filter3 = filter3;
+ this.offset = offset;
+ }
+
+ @Override
+ public Iterator iterator() {
+ List messages = new ArrayList();
+ int currentObjectNumber = objectNumber;
+
+ while (true) {
+ try {
+ Message message = bridgeHandler.reqObjectProperties(request.getPropertyRequest(), currentObjectNumber,
+ offset, filter1, filter2, filter3);
+ if (message.getMessageType() == Message.MESG_TYPE_OBJ_PROP) {
+ ObjectProperties objectProperties = (ObjectProperties) message;
+ messages.add(request.getResponseType().cast(objectProperties));
+ if (offset == 0) {
+ break;
+ } else if (offset == 1) {
+ currentObjectNumber++;
+ } else if (offset == -1) {
+ currentObjectNumber--;
+ }
+ } else {
+ break;
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.warn("Error retrieving object properties: {}", e.getMessage());
+ }
+ }
+ return messages.iterator();
+ }
+
+ public static class Builder {
+ private final OmnilinkBridgeHandler bridgeHandler;
+ private final ObjectPropertyRequests request;
+ private final int objectNumber;
+ private final int offset;
+ private int filter1 = ObjectProperties.FILTER_1_NONE;
+ private int filter2 = ObjectProperties.FILTER_2_NONE;
+ private int filter3 = ObjectProperties.FILTER_3_NONE;
+
+ private Builder(OmnilinkBridgeHandler bridgeHandler, ObjectPropertyRequests request, int objectNumber,
+ int offset) {
+ this.bridgeHandler = bridgeHandler;
+ this.request = request;
+ this.objectNumber = objectNumber;
+ this.offset = offset;
+ }
+
+ public Builder selectNamed() {
+ this.filter1 = ObjectProperties.FILTER_1_NAMED;
+ return this;
+ }
+
+ public Builder areaFilter(int area) {
+ this.filter2 = area;
+ return this;
+ }
+
+ public Builder selectAnyLoad() {
+ this.filter3 = ObjectProperties.FILTER_3_ANY_LOAD;
+ return this;
+ }
+
+ public ObjectPropertyRequest build() {
+ return new ObjectPropertyRequest(bridgeHandler, request, objectNumber, filter1, filter2, filter3,
+ offset);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/ObjectPropertyRequests.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/ObjectPropertyRequests.java
new file mode 100644
index 00000000000..38aa3fcac07
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/ObjectPropertyRequests.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.discovery;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AccessControlReaderProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AudioSourceProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AudioZoneProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AuxSensorProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ButtonProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ThermostatProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.UnitProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ZoneProperties;
+
+/**
+ * @author Craig Hamilton - Initial contribution
+ *
+ * @param
+ */
+@NonNullByDefault
+public class ObjectPropertyRequests {
+
+ public static final ObjectPropertyRequests THERMOSTAT = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_THERMO, ThermostatProperties.class);
+
+ public static final ObjectPropertyRequests BUTTONS = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_BUTTON, ButtonProperties.class);
+
+ public static final ObjectPropertyRequests AREA = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_AREA, AreaProperties.class);
+
+ public static final ObjectPropertyRequests ZONE = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_ZONE, ZoneProperties.class);
+
+ public static final ObjectPropertyRequests UNIT = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_UNIT, UnitProperties.class);
+
+ public static final ObjectPropertyRequests AUDIO_ZONE = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_AUDIO_ZONE, AudioZoneProperties.class);
+
+ public static final ObjectPropertyRequests AUDIO_SOURCE = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_AUDIO_SOURCE, AudioSourceProperties.class);
+
+ public static final ObjectPropertyRequests AUX_SENSORS = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_AUX_SENSOR, AuxSensorProperties.class);
+
+ public static final ObjectPropertyRequests LOCK = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_CONTROL_READER, AccessControlReaderProperties.class);
+
+ private final int propertyRequest;
+ private final Class responseType;
+
+ private ObjectPropertyRequests(int propertyRequest, Class type) {
+ this.propertyRequest = propertyRequest;
+ this.responseType = type;
+ }
+
+ public int getPropertyRequest() {
+ return propertyRequest;
+ }
+
+ public Class getResponseType() {
+ return responseType;
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/OmnilinkDiscoveryService.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/OmnilinkDiscoveryService.java
new file mode 100644
index 00000000000..86ceadf7c69
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/discovery/OmnilinkDiscoveryService.java
@@ -0,0 +1,542 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.discovery;
+
+import static com.digitaldan.jomnilinkII.MessageTypes.properties.AuxSensorProperties.*;
+import static com.digitaldan.jomnilinkII.MessageTypes.properties.UnitProperties.*;
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.SystemType;
+import org.openhab.binding.omnilink.internal.handler.BridgeOfflineException;
+import org.openhab.binding.omnilink.internal.handler.OmnilinkBridgeHandler;
+import org.openhab.core.config.discovery.AbstractDiscoveryService;
+import org.openhab.core.config.discovery.DiscoveryResult;
+import org.openhab.core.config.discovery.DiscoveryResultBuilder;
+import org.openhab.core.config.discovery.DiscoveryService;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.binding.ThingHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.MessageTypes.SystemInformation;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AccessControlReaderProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AudioSourceProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AudioZoneProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AuxSensorProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ButtonProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ThermostatProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.UnitProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ZoneProperties;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link OmnilinkDiscoveryService} creates things based on the configured bridge.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class OmnilinkDiscoveryService extends AbstractDiscoveryService
+ implements DiscoveryService, ThingHandlerService {
+ private final Logger logger = LoggerFactory.getLogger(OmnilinkDiscoveryService.class);
+ private static final int DISCOVER_TIMEOUT_SECONDS = 30;
+ private @Nullable OmnilinkBridgeHandler bridgeHandler;
+ private @Nullable SystemType systemType;
+ private @Nullable List areas;
+
+ /**
+ * Creates an OmnilinkDiscoveryService.
+ */
+ public OmnilinkDiscoveryService() {
+ super(SUPPORTED_THING_TYPES_UIDS, DISCOVER_TIMEOUT_SECONDS, false);
+ }
+
+ @Override
+ public void setThingHandler(@Nullable ThingHandler handler) {
+ if (handler instanceof OmnilinkBridgeHandler) {
+ bridgeHandler = (OmnilinkBridgeHandler) handler;
+ }
+ }
+
+ @Override
+ public @Nullable ThingHandler getThingHandler() {
+ return bridgeHandler;
+ }
+
+ @Override
+ public void activate() {
+ }
+
+ @Override
+ public void deactivate() {
+ }
+
+ @Override
+ protected synchronized void startScan() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ logger.debug("Starting scan");
+ try {
+ SystemInformation systemInformation = handler.reqSystemInformation();
+ this.systemType = SystemType.getType(systemInformation.getModel());
+ this.areas = discoverAreas();
+ discoverUnits();
+ discoverZones();
+ discoverButtons();
+ discoverThermostats();
+ discoverAudioZones();
+ discoverAudioSources();
+ discoverTempSensors();
+ discoverHumiditySensors();
+ discoverLocks();
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received error during discovery: {}", e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ protected synchronized void stopScan() {
+ super.stopScan();
+ removeOlderResults(getTimestampOfLastScan());
+ }
+
+ /**
+ * Calculate the area filter the a supplied area
+ *
+ * @param area Area to calculate filter for.
+ * @return Calculated Bit Filter for the supplied area. Bit 0 is area 1, bit 2 is area 2 and so on.
+ */
+ private static int bitFilterForArea(AreaProperties areaProperties) {
+ return BigInteger.ZERO.setBit(areaProperties.getNumber() - 1).intValue();
+ }
+
+ /**
+ * Discovers OmniLink buttons
+ */
+ private void discoverButtons() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+ final List areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.BUTTONS, 0, 1).selectNamed().areaFilter(areaFilter)
+ .build();
+
+ for (ButtonProperties buttonProperties : objectPropertyRequest) {
+ String thingName = buttonProperties.getName();
+ String thingID = Integer.toString(buttonProperties.getNumber());
+
+ Map properties = new HashMap<>();
+ properties.put(THING_PROPERTIES_NAME, thingName);
+ properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_BUTTON, bridgeUID, thingID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
+ .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
+ .withLabel(thingName).build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Discovers OmniLink locks
+ */
+ private void discoverLocks() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.LOCK, 0, 1).selectNamed().build();
+
+ for (AccessControlReaderProperties lockProperties : objectPropertyRequest) {
+ String thingName = lockProperties.getName();
+ String thingID = Integer.toString(lockProperties.getNumber());
+
+ Map properties = Map.of(THING_PROPERTIES_NAME, thingName);
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_LOCK, bridgeUID, thingID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID).withLabel(thingName)
+ .build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+
+ /**
+ * Discovers OmniLink audio zones
+ */
+ private void discoverAudioZones() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.AUDIO_ZONE, 0, 1).selectNamed().build();
+
+ for (AudioZoneProperties audioZoneProperties : objectPropertyRequest) {
+ String thingName = audioZoneProperties.getName();
+ String thingID = Integer.toString(audioZoneProperties.getNumber());
+
+ Map properties = Map.of(THING_PROPERTIES_NAME, thingName);
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_AUDIO_ZONE, bridgeUID, thingID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID).withLabel(thingName)
+ .build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+
+ /**
+ * Discovers OmniLink audio sources
+ */
+ private void discoverAudioSources() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.AUDIO_SOURCE, 0, 1).selectNamed().build();
+
+ for (AudioSourceProperties audioSourceProperties : objectPropertyRequest) {
+ String thingName = audioSourceProperties.getName();
+ String thingID = Integer.toString(audioSourceProperties.getNumber());
+
+ Map properties = Map.of(THING_PROPERTIES_NAME, thingName);
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_AUDIO_SOURCE, bridgeUID, thingID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID).withLabel(thingName)
+ .build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+
+ /**
+ * Discovers OmniLink temperature sensors
+ */
+ private void discoverTempSensors() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+ final List areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.AUX_SENSORS, 0, 1).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (AuxSensorProperties auxSensorProperties : objectPropertyRequest) {
+ if (auxSensorProperties.getSensorType() != SENSOR_TYPE_PROGRAMMABLE_ENERGY_SAVER_MODULE
+ && auxSensorProperties.getSensorType() != SENSOR_TYPE_HUMIDITY) {
+ String thingName = auxSensorProperties.getName();
+ String thingID = Integer.toString(auxSensorProperties.getNumber());
+
+ Map properties = new HashMap<>();
+ properties.put(THING_PROPERTIES_NAME, thingName);
+ properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_TEMP_SENSOR, bridgeUID, thingID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
+ .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
+ .withLabel(thingName).build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Discovers OmniLink humidity sensors
+ */
+ private void discoverHumiditySensors() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+ final List areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.AUX_SENSORS, 0, 1).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (AuxSensorProperties auxSensorProperties : objectPropertyRequest) {
+ if (auxSensorProperties.getSensorType() == SENSOR_TYPE_HUMIDITY) {
+ String thingName = auxSensorProperties.getName();
+ String thingID = Integer.toString(auxSensorProperties.getNumber());
+
+ Map properties = new HashMap<>();
+ properties.put(THING_PROPERTIES_NAME, thingName);
+ properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_HUMIDITY_SENSOR, bridgeUID, thingID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
+ .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
+ .withLabel(thingName).build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Discovers OmniLink thermostats
+ */
+ private void discoverThermostats() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+ final List areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.THERMOSTAT, 0, 1).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (ThermostatProperties thermostatProperties : objectPropertyRequest) {
+ String thingName = thermostatProperties.getName();
+ String thingID = Integer.toString(thermostatProperties.getNumber());
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_THERMOSTAT, bridgeUID, thingID);
+
+ Map properties = new HashMap<>();
+ properties.put(THING_PROPERTIES_NAME, thingName);
+ properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
+ .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
+ .withLabel(thingName).build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Discovers OmniLink areas
+ */
+ private @Nullable List discoverAreas() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+ List areas = new LinkedList<>();
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.AREA, 0, 1).build();
+
+ for (AreaProperties areaProperties : objectPropertyRequest) {
+ int thingNumber = areaProperties.getNumber();
+ String thingName = areaProperties.getName();
+ String thingID = Integer.toString(thingNumber);
+ ThingUID thingUID = null;
+
+ /*
+ * It seems that for simple OmniLink Controller configurations there
+ * is only 1 area, without a name. So if there is no name for the
+ * first area, we will call that Main Area. If other area's name is
+ * blank, we will not create a thing.
+ */
+ if (thingNumber == 1 && "".equals(thingName)) {
+ thingName = "Main Area";
+ } else if ("".equals(thingName)) {
+ break;
+ }
+
+ Map properties = Map.of(THING_PROPERTIES_NAME, thingName);
+
+ final SystemType systemType = this.systemType;
+ if (systemType != null) {
+ switch (systemType) {
+ case LUMINA:
+ thingUID = new ThingUID(THING_TYPE_LUMINA_AREA, bridgeUID, thingID);
+ break;
+ case OMNI:
+ thingUID = new ThingUID(THING_TYPE_OMNI_AREA, bridgeUID, thingID);
+ break;
+ default:
+ throw new IllegalStateException("Unknown System Type");
+ }
+ }
+
+ if (thingUID != null) {
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID).withProperties(properties)
+ .withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
+ .withLabel(thingName).build();
+ thingDiscovered(discoveryResult);
+ }
+
+ areas.add(areaProperties);
+ }
+ return areas;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Discovers OmniLink supported units
+ */
+ private void discoverUnits() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+ final List areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.UNIT, 0, 1).selectNamed().areaFilter(areaFilter)
+ .selectAnyLoad().build();
+
+ for (UnitProperties unitProperties : objectPropertyRequest) {
+ int thingType = unitProperties.getUnitType();
+ String thingName = unitProperties.getName();
+ String thingID = Integer.toString(unitProperties.getNumber());
+ ThingUID thingUID = null;
+
+ Map properties = new HashMap<>();
+ properties.put(THING_PROPERTIES_NAME, thingName);
+ properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
+
+ switch (thingType) {
+ case UNIT_TYPE_HLC_ROOM:
+ case UNIT_TYPE_VIZIARF_ROOM:
+ thingUID = new ThingUID(THING_TYPE_ROOM, bridgeUID, thingID);
+ break;
+ case UNIT_TYPE_FLAG:
+ thingUID = new ThingUID(THING_TYPE_FLAG, bridgeUID, thingID);
+ break;
+ case UNIT_TYPE_OUTPUT:
+ thingUID = new ThingUID(THING_TYPE_OUTPUT, bridgeUID, thingID);
+ break;
+ case UNIT_TYPE_UPB:
+ case UNIT_TYPE_HLC_LOAD:
+ thingUID = new ThingUID(THING_TYPE_UNIT_UPB, bridgeUID, thingID);
+ break;
+ case UNIT_TYPE_CENTRALITE:
+ case UNIT_TYPE_RADIORA:
+ case UNIT_TYPE_VIZIARF_LOAD:
+ case UNIT_TYPE_COMPOSE:
+ thingUID = new ThingUID(THING_TYPE_DIMMABLE, bridgeUID, thingID);
+ break;
+ default:
+ thingUID = new ThingUID(THING_TYPE_UNIT, bridgeUID, thingID);
+ logger.debug("Generic unit type: {}", thingType);
+ break;
+ }
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
+ .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
+ .withLabel(thingName).build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Generates zone items
+ */
+ private void discoverZones() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+ final List areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(handler, ObjectPropertyRequests.ZONE, 0, 1).selectNamed().areaFilter(areaFilter)
+ .build();
+
+ for (ZoneProperties zoneProperties : objectPropertyRequest) {
+ if (zoneProperties.getZoneType() <= SENSOR_TYPE_PROGRAMMABLE_ENERGY_SAVER_MODULE) {
+ String thingName = zoneProperties.getName();
+ String thingID = Integer.toString(zoneProperties.getNumber());
+
+ Map properties = new HashMap<>();
+ properties.put(THING_PROPERTIES_NAME, thingName);
+ properties.put(THING_PROPERTIES_AREA, areaProperties.getNumber());
+
+ ThingUID thingUID = new ThingUID(THING_TYPE_ZONE, bridgeUID, thingID);
+
+ DiscoveryResult discoveryResult = DiscoveryResultBuilder.create(thingUID)
+ .withProperties(properties).withProperty(THING_PROPERTIES_NUMBER, thingID)
+ .withRepresentationProperty(THING_PROPERTIES_NUMBER).withBridge(bridgeUID)
+ .withLabel(thingName).build();
+ thingDiscovered(discoveryResult);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractAreaHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractAreaHandler.java
new file mode 100644
index 00000000000..d0e6db0e43a
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractAreaHandler.java
@@ -0,0 +1,240 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.math.BigInteger;
+import java.util.EnumSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.omnilink.internal.AreaAlarm;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.SecurityCodeValidation;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAreaStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.systemevents.AllOnOffEvent;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link AbstractAreaHandler} defines some methods that can be used across
+ * the many different areas defined in an OmniLink Controller.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public abstract class AbstractAreaHandler extends AbstractOmnilinkStatusHandler {
+ private final Logger logger = LoggerFactory.getLogger(AbstractAreaHandler.class);
+ private final int thingID = getThingNumber();
+
+ public AbstractAreaHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+
+ super.initialize();
+ if (bridgeHandler != null) {
+ updateAreaProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Area!");
+ }
+ }
+
+ private void updateAreaProperties(OmnilinkBridgeHandler bridgeHandler) {
+ final List areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ String thingName = areaProperties.getName();
+ if (areaProperties.getNumber() == 1 && "".equals(thingName)) {
+ thingName = "Main Area";
+ }
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, thingName);
+ updateProperties(properties);
+ }
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_AREA_ACTIVATE_KEYPAD_EMERGENCY:
+ handleKeypadEmergency(channelUID, command);
+ break;
+ default:
+ handleSecurityMode(channelUID, command);
+ break;
+ }
+ }
+
+ private void handleSecurityMode(ChannelUID channelUID, Command command) {
+ int mode = getMode(channelUID);
+
+ if (!(command instanceof StringType)) {
+ logger.debug("Invalid command: {}, must be StringType", command);
+ return;
+ }
+
+ logger.debug("Received mode: {}, on area: {}", mode, thingID);
+
+ char[] code = command.toFullString().toCharArray();
+ if (code.length != 4) {
+ logger.warn("Invalid code length, code must be 4 digits");
+ } else {
+ // mode, codeNum, areaNum
+ try {
+ final OmnilinkBridgeHandler bridge = getOmnilinkBridgeHandler();
+ if (bridge != null) {
+ SecurityCodeValidation codeValidation = bridge.reqSecurityCodeValidation(thingID,
+ Character.getNumericValue(code[0]), Character.getNumericValue(code[1]),
+ Character.getNumericValue(code[2]), Character.getNumericValue(code[3]));
+ /*
+ * 0 Invalid code
+ * 1 Master
+ * 2 Manager
+ * 3 User
+ */
+ logger.debug("User code number: {}, level: {}", codeValidation.getCodeNumber(),
+ codeValidation.getAuthorityLevel());
+
+ /*
+ * Valid user code number are 1-99, 251 is duress code, 0 means code does not exist
+ */
+ if ((codeValidation.getCodeNumber() > 0 && codeValidation.getCodeNumber() <= 99)
+ && codeValidation.getAuthorityLevel() > 0) {
+ sendOmnilinkCommand(mode, codeValidation.getCodeNumber(), thingID);
+ } else {
+ logger.warn("System reported an invalid code");
+ }
+ } else {
+ logger.debug("Received null bridge while sending area command!");
+ }
+ } catch (OmniInvalidResponseException e) {
+ logger.debug("Could not arm area: {}, are all zones closed?", e.getMessage());
+ } catch (OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Could not send area command: {}", e.getMessage());
+ }
+ }
+ // This is a send only channel, so don't store the user code
+ updateState(channelUID, UnDefType.UNDEF);
+ }
+
+ /**
+ * Get the specific mode for the OmniLink type
+ *
+ * @param channelUID Channel that maps to a mode
+ * @return OmniLink representation of mode.
+ */
+ protected abstract int getMode(ChannelUID channelUID);
+
+ /**
+ * Get the set of alarms supported by this area handler.
+ *
+ * @return Set of alarms for this handler.
+ */
+ protected abstract EnumSet getAlarms();
+
+ private void handleKeypadEmergency(ChannelUID channelUID, Command command) {
+ if (command instanceof DecimalType) {
+ try {
+ final OmnilinkBridgeHandler bridge = getOmnilinkBridgeHandler();
+ if (bridge != null) {
+ bridge.activateKeypadEmergency(thingID, ((DecimalType) command).intValue());
+ } else {
+ logger.debug("Received null bridge while sending Keypad Emergency command!");
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while sending command to OmniLink Controller: {}", e.getMessage());
+ }
+ } else {
+ logger.debug("Invalid command: {}, must be DecimalType", command);
+ }
+ }
+
+ @Override
+ public void updateChannels(ExtendedAreaStatus status) {
+ logger.debug("Handle area event: mode: {}, alarms: {}, entryTimer: {}, exitTimer: {}", status.getMode(),
+ status.getAlarms(), status.getEntryTimer(), status.getExitTimer());
+
+ /*
+ * According to the specification, if the 3rd bit is set on a area mode, then that mode is in a delayed state.
+ * Unfortunately, this is not the case, but we can fix that by looking to see if the exit timer
+ * is set and do this manually.
+ */
+ int mode = status.getExitTimer() > 0 ? status.getMode() | 1 << 3 : status.getMode();
+ updateState(new ChannelUID(thing.getUID(), CHANNEL_AREA_MODE), new DecimalType(mode));
+
+ /*
+ * Alarm status is actually 8 status, packed into each bit, so we loop through to see if a bit is set, note that
+ * this means you can have multiple alarms set at once
+ */
+ BigInteger alarmBits = BigInteger.valueOf(status.getAlarms());
+ for (AreaAlarm alarm : getAlarms()) {
+ OnOffType alarmState = OnOffType.from(alarm.isSet(alarmBits));
+ updateState(new ChannelUID(thing.getUID(), alarm.getChannelUID()), alarmState);
+ }
+ }
+
+ public void handleAllOnOffEvent(AllOnOffEvent event) {
+ ChannelUID activateChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_AREA_ALL_ON_OFF_EVENT);
+ triggerChannel(activateChannel, event.isOn() ? "ON" : "OFF");
+ }
+
+ @Override
+ protected Optional retrieveStatus() {
+ try {
+ final OmnilinkBridgeHandler bridge = getOmnilinkBridgeHandler();
+ if (bridge != null) {
+ ObjectStatus objStatus = bridge.requestObjectStatus(Message.OBJ_TYPE_AREA, thingID, thingID, true);
+ return Optional.of((ExtendedAreaStatus) objStatus.getStatuses()[0]);
+ } else {
+ logger.debug("Received null bridge while updating Area status!");
+ return Optional.empty();
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while refreshing Area status: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractOmnilinkHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractOmnilinkHandler.java
new file mode 100644
index 00000000000..f17f716a1cd
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractOmnilinkHandler.java
@@ -0,0 +1,122 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.math.BigInteger;
+import java.util.LinkedList;
+import java.util.List;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.binding.BaseThingHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link AbstractOmnilinkHandler} defines some methods that can be used across
+ * the many different things exposed by the OmniLink protocol
+ *
+ * @author Brian O'Connell - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public abstract class AbstractOmnilinkHandler extends BaseThingHandler {
+ private final Logger logger = LoggerFactory.getLogger(AbstractOmnilinkHandler.class);
+
+ public AbstractOmnilinkHandler(Thing thing) {
+ super(thing);
+ }
+
+ public @Nullable OmnilinkBridgeHandler getOmnilinkBridgeHandler() {
+ Bridge bridge = getBridge();
+ if (bridge != null) {
+ return (OmnilinkBridgeHandler) bridge.getHandler();
+ } else {
+ return null;
+ }
+ }
+
+ protected void sendOmnilinkCommand(int message, int param1, int param2) {
+ try {
+ final OmnilinkBridgeHandler bridge = getOmnilinkBridgeHandler();
+ if (bridge != null) {
+ bridge.sendOmnilinkCommand(message, param1, param2);
+ } else {
+ logger.debug("Received null bridge while sending OmniLink command!");
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Could not send command to OmniLink Controller: {}", e.getMessage());
+ }
+ }
+
+ /**
+ * Calculate the area filter the a supplied area
+ *
+ * @param area Area to calculate filter for.
+ * @return Calculated Bit Filter for the supplied area. Bit 0 is area 1, bit 2 is area 2 and so on.
+ */
+ protected static int bitFilterForArea(AreaProperties areaProperties) {
+ return BigInteger.ZERO.setBit(areaProperties.getNumber() - 1).intValue();
+ }
+
+ protected @Nullable List getAreaProperties() {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ List areas = new LinkedList<>();
+
+ if (bridgeHandler != null) {
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AREA, 0, 1).build();
+
+ for (AreaProperties areaProperties : objectPropertyRequest) {
+ String thingName = areaProperties.getName();
+ if (areaProperties.getNumber() == 1 && "".equals(thingName)) {
+ areas.add(areaProperties);
+ break;
+ } else if ("".equals(thingName)) {
+ break;
+ } else {
+ areas.add(areaProperties);
+ }
+ }
+ }
+ return areas;
+ }
+
+ /**
+ * Gets the configured number for a thing.
+ *
+ * @return Configured number for a thing.
+ */
+ protected int getThingNumber() {
+ return ((Number) getThing().getConfiguration().get(THING_PROPERTIES_NUMBER)).intValue();
+ }
+
+ /**
+ * Gets the configured area number for a thing.
+ *
+ * @return Configured area number for a thing.
+ */
+ protected int getAreaNumber() {
+ return ((Number) getThing().getConfiguration().get(THING_PROPERTIES_AREA)).intValue();
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractOmnilinkStatusHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractOmnilinkStatusHandler.java
new file mode 100644
index 00000000000..01e5290a623
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AbstractOmnilinkStatusHandler.java
@@ -0,0 +1,83 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.Status;
+
+/**
+ * The {@link AbstractOmnilinkStatusHandler} defines some methods that can be used across
+ * the many different units exposed by the OmniLink protocol to retrive updated status information.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public abstract class AbstractOmnilinkStatusHandler extends AbstractOmnilinkHandler {
+ public AbstractOmnilinkStatusHandler(Thing thing) {
+ super(thing);
+ }
+
+ private volatile Optional status = Optional.empty();
+
+ @Override
+ public void initialize() {
+ updateHandlerStatus();
+ }
+
+ /**
+ * Attempt to retrieve an updated status for this handler type.
+ *
+ * @return Optional with updated status if possible, empty optional otherwise.
+ */
+ protected abstract Optional retrieveStatus();
+
+ /**
+ * Update channels associated with handler
+ *
+ * @param t Status object to update channels with
+ */
+ protected abstract void updateChannels(T t);
+
+ /**
+ * Process a status update for this handler. This will dispatch updateChannels where appropriate.
+ *
+ * @param t Status to process.
+ */
+ public void handleStatus(T t) {
+ this.status = Optional.of(t);
+ updateChannels(t);
+ }
+
+ @Override
+ public void channelLinked(ChannelUID channelUID) {
+ status.ifPresent(this::updateChannels);
+ }
+
+ private void updateHandlerStatus() {
+ Bridge bridge = getBridge();
+ if (bridge != null && bridge.getStatus() == ThingStatus.ONLINE) {
+ updateStatus(ThingStatus.ONLINE);
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AudioSourceHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AudioSourceHandler.java
new file mode 100644
index 00000000000..9846edf7d12
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AudioSourceHandler.java
@@ -0,0 +1,182 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.Map;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.AudioSourceStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AudioSourceProperties;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link AudioSourceHandler} defines some methods that are used to
+ * interface with an OmniLink Audio Source. This by extension also defines the
+ * Audio Source thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Brian O'Connell - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class AudioSourceHandler extends AbstractOmnilinkHandler {
+ private final Logger logger = LoggerFactory.getLogger(AudioSourceHandler.class);
+ private final int POLL_DELAY_SECONDS = 5;
+ private final int thingID = getThingNumber();
+ private @Nullable ScheduledFuture> scheduledPolling = null;
+ public @Nullable String number;
+
+ public AudioSourceHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateStatus(ThingStatus.ONLINE);
+ if (((Boolean) getThing().getConfiguration().get(THING_PROPERTIES_AUTO_START)).booleanValue()) {
+ logger.debug("Autostart enabled, scheduling polling for Audio Source: {}", thingID);
+ schedulePolling();
+ } else {
+ logger.debug("Autostart disabled, not scheduling polling for Audio Source: {}", thingID);
+ cancelPolling();
+ }
+ updateAudioSourceProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Audio Source!");
+ }
+ }
+
+ private void updateAudioSourceProperties(OmnilinkBridgeHandler bridgeHandler) {
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AUDIO_SOURCE, thingID, 0).selectNamed().build();
+
+ for (AudioSourceProperties audioSourceProperties : objectPropertyRequest) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, audioSourceProperties.getName());
+ updateProperties(properties);
+ }
+ }
+
+ @Override
+ public synchronized void dispose() {
+ cancelPolling();
+ super.dispose();
+ }
+
+ private synchronized void cancelPolling() {
+ final ScheduledFuture> scheduledPolling = this.scheduledPolling;
+ if (scheduledPolling != null) {
+ logger.debug("Cancelling polling for Audio Source: {}", thingID);
+ scheduledPolling.cancel(false);
+ }
+ }
+
+ private synchronized void schedulePolling() {
+ cancelPolling();
+ logger.debug("Scheduling polling for Audio Source: {}", thingID);
+ scheduledPolling = super.scheduler.scheduleWithFixedDelay(this::pollAudioSource, 0, POLL_DELAY_SECONDS,
+ TimeUnit.SECONDS);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+ final ScheduledFuture> scheduledPolling = this.scheduledPolling;
+
+ switch (channelUID.getId()) {
+ case CHANNEL_AUDIO_SOURCE_POLLING:
+ if (command instanceof RefreshType) {
+ updateState(CHANNEL_AUDIO_SOURCE_POLLING,
+ OnOffType.from((scheduledPolling != null && !scheduledPolling.isDone())));
+ } else if (command instanceof OnOffType) {
+ handlePolling(channelUID, (OnOffType) command);
+ } else {
+ logger.debug("Invalid command: {}, must be RefreshType or OnOffType", command);
+ }
+ break;
+ default:
+ logger.warn("Unknown channel for Audio Source thing: {}", channelUID);
+ }
+ }
+
+ private void handlePolling(ChannelUID channelUID, OnOffType command) {
+ logger.debug("handlePolling called for channel: {}, command: {}", channelUID, command);
+ if (OnOffType.ON.equals(command)) {
+ schedulePolling();
+ } else {
+ cancelPolling();
+ }
+ }
+
+ public void pollAudioSource() {
+ try {
+ final OmnilinkBridgeHandler bridge = getOmnilinkBridgeHandler();
+ if (bridge != null) {
+ Message message;
+ int position = 0;
+ while ((message = bridge.requestAudioSourceStatus(thingID, position))
+ .getMessageType() == Message.MESG_TYPE_AUDIO_SOURCE_STATUS) {
+ logger.trace("Polling for Audio Source statuses on thing: {}", thingID);
+ AudioSourceStatus audioSourceStatus = (AudioSourceStatus) message;
+ position = audioSourceStatus.getPosition();
+ switch (position) {
+ case 1:
+ updateState(CHANNEL_AUDIO_SOURCE_TEXT1, new StringType(audioSourceStatus.getSourceData()));
+ break;
+ case 2:
+ updateState(CHANNEL_AUDIO_SOURCE_TEXT2, new StringType(audioSourceStatus.getSourceData()));
+ break;
+ case 3:
+ updateState(CHANNEL_AUDIO_SOURCE_TEXT3, new StringType(audioSourceStatus.getSourceData()));
+ break;
+ case 4:
+ updateState(CHANNEL_AUDIO_SOURCE_TEXT4, new StringType(audioSourceStatus.getSourceData()));
+ break;
+ case 5:
+ updateState(CHANNEL_AUDIO_SOURCE_TEXT5, new StringType(audioSourceStatus.getSourceData()));
+ break;
+ case 6:
+ updateState(CHANNEL_AUDIO_SOURCE_TEXT6, new StringType(audioSourceStatus.getSourceData()));
+ break;
+ }
+ }
+ } else {
+ logger.debug("Received null bridge while polling Audio Source statuses!");
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Exception recieved while polling for Audio Source statuses: {}", e.getMessage());
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AudioZoneHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AudioZoneHandler.java
new file mode 100644
index 00000000000..d6b7be70505
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/AudioZoneHandler.java
@@ -0,0 +1,208 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.AudioPlayer;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.NextPreviousType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.library.types.PlayPauseType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AudioZoneProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAudioZoneStatus;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link AudioZoneHandler} defines some methods that are used to
+ * interface with an OmniLink Audio Zone. This by extension also defines the
+ * Audio Zone thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class AudioZoneHandler extends AbstractOmnilinkStatusHandler {
+ private final Logger logger = LoggerFactory.getLogger(AudioZoneHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public AudioZoneHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateAudioZoneProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Audio Zone!");
+ }
+ }
+
+ private void updateAudioZoneProperties(OmnilinkBridgeHandler bridgeHandler) {
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AUDIO_ZONE, thingID, 0).selectNamed().build();
+
+ for (AudioZoneProperties audioZoneProperties : objectPropertyRequest) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, audioZoneProperties.getName());
+ updateProperties(properties);
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_AUDIO_ZONE_POWER:
+ if (command instanceof OnOffType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_AUDIO_ZONE_SET_ON_MUTE.getNumber(),
+ OnOffType.OFF.equals(command) ? 0 : 1, thingID);
+ } else {
+ logger.debug("Invalid command: {}, must be OnOffType", command);
+ }
+ break;
+ case CHANNEL_AUDIO_ZONE_MUTE:
+ if (command instanceof OnOffType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_AUDIO_ZONE_SET_ON_MUTE.getNumber(),
+ OnOffType.OFF.equals(command) ? 2 : 3, thingID);
+ } else {
+ logger.debug("Invalid command: {}, must be OnOffType", command);
+ }
+ break;
+ case CHANNEL_AUDIO_ZONE_VOLUME:
+ if (command instanceof PercentType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_AUDIO_ZONE_SET_VOLUME.getNumber(),
+ ((PercentType) command).intValue(), thingID);
+ } else {
+ logger.debug("Invalid command: {}, must be PercentType", command);
+ }
+ break;
+ case CHANNEL_AUDIO_ZONE_SOURCE:
+ if (command instanceof DecimalType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_AUDIO_ZONE_SET_SOURCE.getNumber(),
+ ((DecimalType) command).intValue(), thingID);
+ } else {
+ logger.debug("Invalid command: {}, must be DecimalType", command);
+ }
+ break;
+ case CHANNEL_AUDIO_ZONE_CONTROL:
+ if (command instanceof PlayPauseType) {
+ handlePlayPauseCommand(channelUID, (PlayPauseType) command);
+ } else if (command instanceof NextPreviousType) {
+ handleNextPreviousCommand(channelUID, (NextPreviousType) command);
+ } else {
+ logger.debug("Invalid command: {}, must be PlayPauseType or NextPreviousType", command);
+ }
+ break;
+ default:
+ logger.warn("Unknown channel for Audio Zone thing: {}", channelUID);
+ }
+ }
+
+ private void handlePlayPauseCommand(ChannelUID channelUID, PlayPauseType command) {
+ logger.debug("handlePlayPauseCommand called for channel: {}, command: {}", channelUID, command);
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+
+ if (bridgeHandler != null) {
+ Optional audioPlayer = bridgeHandler.getAudioPlayer();
+ if (audioPlayer.isPresent()) {
+ AudioPlayer player = audioPlayer.get();
+ sendOmnilinkCommand(OmniLinkCmd.CMD_AUDIO_ZONE_SET_SOURCE.getNumber(),
+ PlayPauseType.PLAY.equals(command) ? player.getPlayCommand() : player.getPauseCommand(),
+ thingID);
+ } else {
+ logger.warn("No Audio Player was detected!");
+ }
+ } else {
+ logger.debug("Received null bridge while sending Audio Zone command!");
+ }
+ }
+
+ private void handleNextPreviousCommand(ChannelUID channelUID, NextPreviousType command) {
+ logger.debug("handleNextPreviousCommand called for channel: {}, command: {}", channelUID, command);
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+
+ if (bridgeHandler != null) {
+ Optional audioPlayer = bridgeHandler.getAudioPlayer();
+ if (audioPlayer.isPresent()) {
+ AudioPlayer player = audioPlayer.get();
+ sendOmnilinkCommand(OmniLinkCmd.CMD_AUDIO_ZONE_SET_SOURCE.getNumber(),
+ NextPreviousType.NEXT.equals(command) ? player.getNextCommand() : player.getPreviousCommand(),
+ thingID);
+ } else {
+ logger.warn("Audio Player could not be found!");
+ }
+ } else {
+ logger.debug("Received null bridge while sending Audio Zone command!");
+ }
+ }
+
+ @Override
+ public void updateChannels(ExtendedAudioZoneStatus status) {
+ logger.debug("updateChannels called for Audio Zone status: {}", status);
+ updateState(CHANNEL_AUDIO_ZONE_POWER, OnOffType.from(status.isPower()));
+ updateState(CHANNEL_AUDIO_ZONE_MUTE, OnOffType.from(status.isMute()));
+ updateState(CHANNEL_AUDIO_ZONE_VOLUME, new PercentType(status.getVolume()));
+ updateState(CHANNEL_AUDIO_ZONE_SOURCE, new DecimalType(status.getSource()));
+ }
+
+ @Override
+ protected Optional retrieveStatus() {
+ try {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ ObjectStatus objStatus = bridgeHandler.requestObjectStatus(Message.OBJ_TYPE_AUDIO_ZONE, thingID,
+ thingID, true);
+ return Optional.of((ExtendedAudioZoneStatus) objStatus.getStatuses()[0]);
+ } else {
+ logger.debug("Received null bridge while updating Audio Zone status!");
+ return Optional.empty();
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while refreshing Audio Zone status: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/BridgeOfflineException.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/BridgeOfflineException.java
new file mode 100644
index 00000000000..891d4c4a699
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/BridgeOfflineException.java
@@ -0,0 +1,30 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * The {@link BridgeOfflineException} defines an exception for when the OmniLink
+ * Bridge is offline or unavailable.
+ *
+ * @author Craig Hamilton - Initial contribution
+ */
+@NonNullByDefault
+public class BridgeOfflineException extends Exception {
+ private static final long serialVersionUID = -9081729691518514097L;
+
+ public BridgeOfflineException(Exception e) {
+ super(e);
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ButtonHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ButtonHandler.java
new file mode 100644
index 00000000000..a288fa00a88
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ButtonHandler.java
@@ -0,0 +1,119 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.List;
+import java.util.Map;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ButtonProperties;
+
+/**
+ * The {@link ButtonHandler} defines some methods that are used to
+ * interface with an OmniLink Button. This by extension also defines the
+ * Button thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class ButtonHandler extends AbstractOmnilinkHandler {
+ private final Logger logger = LoggerFactory.getLogger(ButtonHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public ButtonHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateStatus(ThingStatus.ONLINE);
+ updateChannels();
+ updateButtonProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Button!");
+ }
+ }
+
+ private void updateButtonProperties(OmnilinkBridgeHandler bridgeHandler) {
+ final List areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.BUTTONS, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (ButtonProperties buttonProperties : objectPropertyRequest) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, buttonProperties.getName());
+ properties.put(THING_PROPERTIES_AREA, Integer.toString(areaProperties.getNumber()));
+ updateProperties(properties);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ updateChannels();
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_BUTTON_PRESS:
+ if (command instanceof OnOffType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_BUTTON.getNumber(), 0, thingID);
+ updateChannels();
+ } else {
+ logger.debug("Invalid command: {}, must be OnOffType", command);
+ }
+ break;
+ default:
+ logger.warn("Unknown channel for Button thing: {}", channelUID);
+ }
+ }
+
+ public void buttonActivated() {
+ ChannelUID activateChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_BUTTON_ACTIVATED_EVENT);
+ triggerChannel(activateChannel);
+ }
+
+ public void updateChannels() {
+ updateState(CHANNEL_BUTTON_PRESS, OnOffType.OFF);
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ConsoleHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ConsoleHandler.java
new file mode 100644
index 00000000000..70340edb48b
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ConsoleHandler.java
@@ -0,0 +1,94 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link ConsoleHandler} defines some methods that are used to
+ * interface with an OmniLink Console. This by extension also defines the
+ * Console thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class ConsoleHandler extends AbstractOmnilinkHandler {
+ private final Logger logger = LoggerFactory.getLogger(ConsoleHandler.class);
+ private final int thingID = getThingNumber();
+
+ public ConsoleHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ if (getOmnilinkBridgeHandler() != null) {
+ updateStatus(ThingStatus.ONLINE);
+ updateChannels();
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Console!");
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ updateChannels();
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER:
+ if (command instanceof StringType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_CONSOLE_ENABLE_DISABLE_BEEPER.getNumber(),
+ ((StringType) command).equals(StringType.valueOf("OFF")) ? 0 : 1, thingID);
+ } else {
+ logger.debug("Invalid command: {}, must be StringType", command);
+ }
+ break;
+ case CHANNEL_CONSOLE_BEEP:
+ if (command instanceof DecimalType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_CONSOLE_BEEP.getNumber(), ((DecimalType) command).intValue(),
+ thingID);
+ } else {
+ logger.debug("Invalid command: {}, must be DecimalType", command);
+ }
+ break;
+ default:
+ logger.warn("Unknown channel for Console thing: {}", channelUID);
+ }
+ updateChannels();
+ }
+
+ public void updateChannels() {
+ updateState(CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER, UnDefType.UNDEF);
+ updateState(CHANNEL_CONSOLE_BEEP, UnDefType.UNDEF);
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/HumiditySensorHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/HumiditySensorHandler.java
new file mode 100644
index 00000000000..6536175ca71
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/HumiditySensorHandler.java
@@ -0,0 +1,157 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.measure.quantity.Dimensionless;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AuxSensorProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAuxSensorStatus;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link HumiditySensorHandler} defines some methods that are used to
+ * interface with an OmniLink Humidity Sensor. This by extension also defines
+ * the Humidity Sensor thing that openHAB will be able to pick up and interface
+ * with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class HumiditySensorHandler extends AbstractOmnilinkStatusHandler {
+ private final Logger logger = LoggerFactory.getLogger(HumiditySensorHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public HumiditySensorHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateHumiditySensorProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Humidity Sensor!");
+ }
+ }
+
+ private void updateHumiditySensorProperties(OmnilinkBridgeHandler bridgeHandler) {
+ final List areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AUX_SENSORS, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (AuxSensorProperties auxSensorProperties : objectPropertyRequest) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, auxSensorProperties.getName());
+ properties.put(THING_PROPERTIES_AREA, Integer.toString(areaProperties.getNumber()));
+ updateProperties(properties);
+ }
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ if (!(command instanceof QuantityType)) {
+ logger.debug("Invalid command: {}, must be QuantityType", command);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_AUX_LOW_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HEAT_LOW_POINT.getNumber(),
+ TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_AUX_HIGH_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(),
+ TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).intValue()),
+ thingID);
+ break;
+ default:
+ logger.warn("Unknown channel for Humdity Sensor thing: {}", channelUID);
+ }
+ }
+
+ @Override
+ public void updateChannels(ExtendedAuxSensorStatus status) {
+ logger.debug("updateChannels called for Humidity Sensor status: {}", status);
+ updateState(CHANNEL_AUX_HUMIDITY,
+ new QuantityType<>(TemperatureFormat.FAHRENHEIT.omniToFormat(status.getTemperature()), Units.PERCENT));
+ updateState(CHANNEL_AUX_LOW_SETPOINT,
+ new QuantityType<>(TemperatureFormat.FAHRENHEIT.omniToFormat(status.getHeatSetpoint()), Units.PERCENT));
+ updateState(CHANNEL_AUX_HIGH_SETPOINT,
+ new QuantityType<>(TemperatureFormat.FAHRENHEIT.omniToFormat(status.getCoolSetpoint()), Units.PERCENT));
+ }
+
+ @Override
+ protected Optional retrieveStatus() {
+ try {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ ObjectStatus objStatus = bridgeHandler.requestObjectStatus(Message.OBJ_TYPE_AUX_SENSOR, thingID,
+ thingID, true);
+ return Optional.of((ExtendedAuxSensorStatus) objStatus.getStatuses()[0]);
+ } else {
+ logger.debug("Received null bridge while updating Humidity Sensor status!");
+ return Optional.empty();
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while refreshing Humidity Sensor status: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/LockHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/LockHandler.java
new file mode 100644
index 00000000000..92a20ec8e6d
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/LockHandler.java
@@ -0,0 +1,129 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AccessControlReaderProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAccessControlReaderLockStatus;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link LockHandler} defines some methods that are used to
+ * interface with an OmniLink Lock. This by extension also defines the
+ * Lock thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Brian O'Connell - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class LockHandler extends AbstractOmnilinkStatusHandler {
+ private final Logger logger = LoggerFactory.getLogger(LockHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public LockHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateLockProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Lock!");
+ }
+ }
+
+ private void updateLockProperties(OmnilinkBridgeHandler bridgeHandler) {
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.LOCK, thingID, 0).selectNamed().build();
+
+ for (AccessControlReaderProperties lockProperties : objectPropertyRequest) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, lockProperties.getName());
+ updateProperties(properties);
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_LOCK_SWITCH:
+ if (command instanceof OnOffType) {
+ sendOmnilinkCommand(OnOffType.OFF.equals(command) ? OmniLinkCmd.CMD_UNLOCK_DOOR.getNumber()
+ : OmniLinkCmd.CMD_LOCK_DOOR.getNumber(), 0, thingID);
+ } else {
+ logger.debug("Invalid command {}, must be OnOffType", command);
+ }
+ break;
+ default:
+ logger.warn("Unknown channel for Lock thing: {}", channelUID);
+ }
+ }
+
+ @Override
+ public void updateChannels(ExtendedAccessControlReaderLockStatus status) {
+ logger.debug("updateChannels called for Lock status: {}", status);
+ updateState(CHANNEL_LOCK_SWITCH, OnOffType.from(status.isLocked()));
+ }
+
+ @Override
+ protected Optional retrieveStatus() {
+ try {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ ObjectStatus objStatus = bridgeHandler.requestObjectStatus(Message.OBJ_TYPE_CONTROL_LOCK, thingID,
+ thingID, true);
+ return Optional.of((ExtendedAccessControlReaderLockStatus) objStatus.getStatuses()[0]);
+ } else {
+ logger.debug("Received null bridge while updating Lock status!");
+ return Optional.empty();
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while refreshing Lock status: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/LuminaAreaHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/LuminaAreaHandler.java
new file mode 100644
index 00000000000..e560e0939e4
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/LuminaAreaHandler.java
@@ -0,0 +1,67 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.AreaAlarm.*;
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.EnumSet;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.AreaAlarm;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+
+/**
+ * The {@link LuminaAreaHandler} defines some methods that are used to
+ * interface with an OmniLink Lumina Area. This by extension also defines the
+ * Lumina Area thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class LuminaAreaHandler extends AbstractAreaHandler {
+ private static final EnumSet LUMINA_ALARMS = EnumSet.of(FREEZE, WATER, TEMPERATURE);
+ public @Nullable String number;
+
+ public LuminaAreaHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected int getMode(ChannelUID channelUID) {
+ switch (channelUID.getId()) {
+ case CHANNEL_AREA_SECURITY_MODE_HOME:
+ return OmniLinkCmd.CMD_SECURITY_LUMINA_HOME_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_SLEEP:
+ return OmniLinkCmd.CMD_SECURITY_LUMINA_SLEEP_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_AWAY:
+ return OmniLinkCmd.CMD_SECURITY_LUMINA_AWAY_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_VACATION:
+ return OmniLinkCmd.CMD_SECURITY_LUMINA_VACATION_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_PARTY:
+ return OmniLinkCmd.CMD_SECURITY_LUMINA_PARTY_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_SPECIAL:
+ return OmniLinkCmd.CMD_SECURITY_LUMINA_SPECIAL_MODE.getNumber();
+ default:
+ throw new IllegalStateException("Unknown channel for area thing " + channelUID);
+ }
+ }
+
+ @Override
+ protected EnumSet getAlarms() {
+ return LUMINA_ALARMS;
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmniAreaHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmniAreaHandler.java
new file mode 100644
index 00000000000..7424d8d981e
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmniAreaHandler.java
@@ -0,0 +1,70 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.AreaAlarm.*;
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.EnumSet;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.AreaAlarm;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+
+/**
+ * The {@link OmniAreaHandler} defines some methods that are used to
+ * interface with an OmniLink OmniPro Area. This by extension also defines the
+ * OmniPro Area thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class OmniAreaHandler extends AbstractAreaHandler {
+ private static final EnumSet OMNI_ALARMS = EnumSet.of(BURGLARY, FIRE, GAS, AUXILIARY, FREEZE, WATER,
+ DURESS, TEMPERATURE);
+ public @Nullable String number;
+
+ public OmniAreaHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ protected int getMode(ChannelUID channelUID) {
+ switch (channelUID.getId()) {
+ case CHANNEL_AREA_SECURITY_MODE_DISARM:
+ return OmniLinkCmd.CMD_SECURITY_OMNI_DISARM.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_DAY:
+ return OmniLinkCmd.CMD_SECURITY_OMNI_DAY_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_NIGHT:
+ return OmniLinkCmd.CMD_SECURITY_OMNI_NIGHT_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_AWAY:
+ return OmniLinkCmd.CMD_SECURITY_OMNI_AWAY_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_VACATION:
+ return OmniLinkCmd.CMD_SECURITY_OMNI_VACATION_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_DAY_INSTANT:
+ return OmniLinkCmd.CMD_SECURITY_OMNI_DAY_INSTANCE_MODE.getNumber();
+ case CHANNEL_AREA_SECURITY_MODE_NIGHT_DELAYED:
+ return OmniLinkCmd.CMD_SECURITY_OMNI_NIGHT_DELAYED_MODE.getNumber();
+ default:
+ throw new IllegalStateException("Unknown channel for area thing " + channelUID);
+ }
+ }
+
+ @Override
+ protected EnumSet getAlarms() {
+ return OMNI_ALARMS;
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmniLinkCmd.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmniLinkCmd.java
new file mode 100644
index 00000000000..20e3f7cba1f
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmniLinkCmd.java
@@ -0,0 +1,199 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+
+/**
+ * OmniLink commands
+ *
+ * @author Dan Cunningham - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ * @since 1.5.0
+ */
+@NonNullByDefault
+public enum OmniLinkCmd {
+ CMD_UNIT_OFF(0),
+ CMD_UNIT_ON(1),
+ CMD_UNIT_AREA_ALL_OFF(2),
+ CMD_UNIT_AREA_ALL_ON(3),
+ CMD_UNIT_PERCENT(9),
+ CMD_UNIT_LO9_LEVEL_HIGH7(101),
+ CMD_UNIT_DECREMENT_COUNTER(10),
+ CMD_UNIT_INCREMENT_COUNTER(11),
+ CMD_UNIT_SET_COUNTER(12),
+ CMD_UNIT_LO9_RAMP_HIGH7(13),
+ CMD_UNIT_LIGHTOLIER(14),
+ CMD_UNIT_UPB_REQ_STATUS(15),
+ CMD_UNIT_UNIT_DIM_STEP_1(17),
+ CMD_UNIT_UNIT_DIM_STEP_2(18),
+ CMD_UNIT_UNIT_DIM_STEP_3(19),
+ CMD_UNIT_UNIT_DIM_STEP_4(20),
+ CMD_UNIT_UNIT_DIM_STEP_5(21),
+ CMD_UNIT_UNIT_DIM_STEP_6(22),
+ CMD_UNIT_UNIT_DIM_STEP_7(23),
+ CMD_UNIT_UNIT_DIM_STEP_8(24),
+ CMD_UNIT_UNIT_DIM_STEP_9(25),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_1(33),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_2(34),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_3(35),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_4(36),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_5(37),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_6(38),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_7(39),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_8(40),
+ CMD_UNIT_UNIT_BRIGHTEN_STEP_9(41),
+ CMD_UNIT_UPB_LO9_BLINK_HIGH7(26),
+ CMD_UNIT_UPB_STOP_BLINK(27),
+ CMD_UNIT_UPB_LINK_OFF(28),
+ CMD_UNIT_UPB_LINK_ON(29),
+ CMD_UNIT_UPB_LINK_SET(30),
+ CMD_UNIT_CENTRALITE_SCENE_OFF(42),
+ CMD_UNIT_CENTRALITE_SCENE_ON(43),
+ CMD_UNIT_UPB_LED_OFF(44),
+ CMD_UNIT_UPB_LED_ON(45),
+ CMD_UNIT_RADIORA_PHANTOM_BUTTON_OFF(46),
+ CMD_UNIT_RADIORA_PHANTM_BUTTON_ON(46),
+ CMD_UNIT_LEVITON_SCENE_OFF(60),
+ CMD_UNIT_LEVITON_SCENE_ON(61),
+ CMD_UNIT_LEVITON_SCENE_SET(62),
+
+ CMD_SECURITY_OMNI_DISARM(48),
+ CMD_SECURITY_OMNI_DAY_MODE(49),
+ CMD_SECURITY_OMNI_NIGHT_MODE(50),
+ CMD_SECURITY_OMNI_AWAY_MODE(51),
+ CMD_SECURITY_OMNI_VACATION_MODE(52),
+ CMD_SECURITY_OMNI_DAY_INSTANCE_MODE(53),
+ CMD_SECURITY_OMNI_NIGHT_DELAYED_MODE(54),
+ CMD_SECURITY_BYPASS_ZONE(4),
+ CMD_SECURITY_RESTORE_ZONE(5),
+ CMD_SECURITY_RESTORE_ALL_ZONES(6),
+ CMD_SECURITY_LUMINA_HOME_MODE(49),
+ CMD_SECURITY_LUMINA_SLEEP_MODE(50),
+ CMD_SECURITY_LUMINA_AWAY_MODE(51),
+ CMD_SECURITY_LUMINA_VACATION_MODE(52),
+ CMD_SECURITY_LUMINA_PARTY_MODE(53),
+ CMD_SECURITY_LUMINA_SPECIAL_MODE(54),
+
+ CMD_BUTTON(7),
+
+ CMD_ENERGY_SAVER_OFF(64),
+ CMD_ENERGY_SAVER_ON(65),
+
+ CMD_THERMO_SET_HEAT_LOW_POINT(66),
+ CMD_THERMO_SET_COOL_HIGH_POINT(67),
+ CMD_THERMO_SET_SYSTEM_MODE(68),
+ CMD_THERMO_SET_FAN_MODE(69),
+ CMD_THERMO_SET_HOLD_MODE(70),
+ CMD_THERMO_RAISE_LOWER_HEAT(71),
+ CMD_THERMO_RAISE_LOWER_COOL(72),
+ CMD_THERMO_SET_HUMDIFY_POINT(73),
+ CMD_THERMO_SET_DEHUMIDIFY_POINT(74),
+
+ CMD_MESSAGE_SHOW_MESSAGE_WITH_BEEP_AND_LED(80),
+ CMD_MESSAGE_SHOW_MESSAGE_WITH_BEEP_OR_LED(86),
+ CMD_MESSAGE_LOG_MESSAGE(81),
+ CMD_MESSAGE_CLEAR_MESSAGE(82),
+ CMD_MESSAGE_SAY_MESSAGE(83),
+ CMD_MESSAGE_PHONE_AND_SAY_MESSAGE(84),
+ CMD_MESSAGE_SEND_MESSAGE_TO_SERIAL_PORT(85),
+
+ CMD_CONSOLE_ENABLE_DISABLE_BEEPER(102),
+ CMD_CONSOLE_BEEP(103),
+
+ CMD_LOCK_DOOR(105),
+ CMD_UNLOCK_DOOR(106),
+
+ CMD_AUDIO_ZONE_SET_ON_MUTE(112),
+ CMD_AUDIO_ZONE_SET_VOLUME(113),
+ CMD_AUDIO_ZONE_SET_SOURCE(114),
+ CMD_AUDIO_ZONE_SELECT_KEY(115),
+
+ SENSOR_UNIT_POWER(1001),
+ SENSOR_UNIT_LEVEL(1002),
+ SENSOR_UNIT_DISPLAY(1003),
+ SENSOR_THERMO_HEAT_POINTC(2001),
+ SENSOR_THERMO_HEAT_POINTF(2002),
+ SENSOR_THERMO_COOL_POINTC(2003),
+ SENSOR_THERMO_COOL_POINTF(2004),
+ SENSOR_THERMO_SYSTEM_MODE(2005),
+ SENSOR_THERMO_FAN_MODE(2006),
+ SENSOR_THERMO_HOLD_MODE(2007),
+ SENSOR_THERMO_TEMPC(2006),
+ SENSOR_THERMO_TEMPF(2007),
+ SENSOR_ZONE_STATUS_CURRENT(3001),
+ SENSOR_ZONE_STATUS_LATCHED(3002),
+ SENSOR_ZONE_STATUS_ARMING(3003),
+ SENSOR_AREA_STATUS_MODE(4001),
+ SENSOR_AREA_STATUS_ALARM(4002),
+ SENSOR_AREA_STATUS_EXIT_DELAY(4003),
+ SENSOR_AREA_STATUS_ENTRY_DELAY(4003),
+ SENSOR_AREA_EXIT_TIMER(4004),
+ SENSOR_AREA_ENTRY_TIMER(4005),
+ SENSOR_AUX_STATUS(5001),
+ SENSOR_AUX_CURRENTC(5002),
+ SENSOR_AUX_CURRENTF(5003),
+ SENSOR_AUX_LOWC(5004),
+ SENSOR_AUX_LOWF(5005),
+ SENSOR_AUX_HIGHC(5006),
+ SENSOR_AUX_HIGHF(5007),
+ SENSOR_AUDIOZONE_POWER(6001),
+ SENSOR_AUDIOZONE_SOURCE(6002),
+ SENSOR_AUDIOZONE_VOLUME(6003),
+ SENSOR_AUDIOZONE_MUTE(6004),
+ SENSOR_AUDIOZONE_TEXT(6005),
+ SENSOR_AUDIOZONE_TEXT_FIELD1(6006),
+ SENSOR_AUDIOZONE_TEXT_FIELD2(6007),
+ SENSOR_AUDIOZONE_TEXT_FIELD3(6008),
+ SENSOR_AUDIOSOURCE_TEXT(7001),
+ SENSOR_AUDIOSOURCE_TEXT_FIELD1(7002),
+ SENSOR_AUDIOSOURCE_TEXT_FIELD2(7003),
+ SENSOR_AUDIOSOURCE_TEXT_FIELD3(7004);
+
+ private int number;
+
+ OmniLinkCmd(int number) {
+ this.number = number;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ public static @Nullable OmniLinkCmd getCommand(String name) {
+ for (OmniLinkCmd command : OmniLinkCmd.values()) {
+ if (name.equals(command.toString())) {
+ return command;
+ }
+ }
+ return null;
+ }
+
+ public static @Nullable OmniLinkCmd getCommand(int ordinal) {
+ OmniLinkCmd[] values = OmniLinkCmd.values();
+ if (ordinal < values.length) {
+ return values[ordinal];
+ } else {
+ return null;
+ }
+ }
+
+ public static String toList() {
+ StringBuilder sb = new StringBuilder();
+ for (OmniLinkCmd command : OmniLinkCmd.values()) {
+ sb.append(command.toString()).append(",");
+ }
+ return sb.toString();
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmnilinkBridgeHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmnilinkBridgeHandler.java
new file mode 100644
index 00000000000..7cc1056688d
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/OmnilinkBridgeHandler.java
@@ -0,0 +1,696 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.io.IOException;
+import java.net.UnknownHostException;
+import java.time.ZonedDateTime;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.AudioPlayer;
+import org.openhab.binding.omnilink.internal.SystemType;
+import org.openhab.binding.omnilink.internal.config.OmnilinkBridgeConfig;
+import org.openhab.binding.omnilink.internal.discovery.OmnilinkDiscoveryService;
+import org.openhab.core.library.types.DateTimeType;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.Bridge;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.binding.BaseBridgeHandler;
+import org.openhab.core.thing.binding.ThingHandlerService;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Connection;
+import com.digitaldan.jomnilinkII.DisconnectListener;
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.EventLogData;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.SecurityCodeValidation;
+import com.digitaldan.jomnilinkII.MessageTypes.SystemFeatures;
+import com.digitaldan.jomnilinkII.MessageTypes.SystemFormats;
+import com.digitaldan.jomnilinkII.MessageTypes.SystemInformation;
+import com.digitaldan.jomnilinkII.MessageTypes.SystemStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAccessControlReaderLockStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAreaStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAudioZoneStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAuxSensorStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedThermostatStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedUnitStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedZoneStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.Status;
+import com.digitaldan.jomnilinkII.MessageTypes.systemevents.AllOnOffEvent;
+import com.digitaldan.jomnilinkII.MessageTypes.systemevents.ButtonEvent;
+import com.digitaldan.jomnilinkII.MessageTypes.systemevents.SwitchPressEvent;
+import com.digitaldan.jomnilinkII.MessageTypes.systemevents.SystemEvent;
+import com.digitaldan.jomnilinkII.MessageTypes.systemevents.UPBLinkEvent;
+import com.digitaldan.jomnilinkII.NotificationListener;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniNotConnectedException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+import com.google.gson.Gson;
+
+/**
+ * The {@link OmnilinkBridgeHandler} defines some methods that are used to
+ * interface with an OmniLink Controller. This by extension also defines the
+ * OmniLink bridge that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class OmnilinkBridgeHandler extends BaseBridgeHandler implements NotificationListener, DisconnectListener {
+ private final Logger logger = LoggerFactory.getLogger(OmnilinkBridgeHandler.class);
+ private @Nullable Connection omniConnection = null;
+ private @Nullable ScheduledFuture> connectJob;
+ private @Nullable ScheduledFuture> eventPollingJob;
+ private final int autoReconnectPeriod = 60;
+ private Optional audioPlayer = Optional.empty();
+ private @Nullable SystemType systemType = null;
+ private final Gson gson = new Gson();
+ private int eventLogNumber = 0;
+
+ public OmnilinkBridgeHandler(Bridge bridge) {
+ super(bridge);
+ }
+
+ @Override
+ public Collection> getServices() {
+ return Collections.singleton(OmnilinkDiscoveryService.class);
+ }
+
+ public void sendOmnilinkCommand(final int message, final int param1, final int param2)
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ getOmniConnection().controllerCommand(message, param1, param2);
+ } catch (IOException | OmniNotConnectedException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ public SecurityCodeValidation reqSecurityCodeValidation(int area, int digit1, int digit2, int digit3, int digit4)
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ return getOmniConnection().reqSecurityCodeValidation(area, digit1, digit2, digit3, digit4);
+ } catch (IOException | OmniNotConnectedException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ public void activateKeypadEmergency(int area, int emergencyType)
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ getOmniConnection().activateKeypadEmergency(area, emergencyType);
+ } catch (IOException | OmniNotConnectedException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ public SystemInformation reqSystemInformation()
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ return getOmniConnection().reqSystemInformation();
+ } catch (IOException | OmniNotConnectedException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ public SystemFormats reqSystemFormats()
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ return getOmniConnection().reqSystemFormats();
+ } catch (IOException | OmniNotConnectedException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ private SystemFeatures reqSystemFeatures()
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ return getOmniConnection().reqSystemFeatures();
+ } catch (IOException | OmniNotConnectedException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ updateChannels();
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_SYSTEMDATE:
+ if (command instanceof DateTimeType) {
+ ZonedDateTime zdt = ((DateTimeType) command).getZonedDateTime();
+ boolean inDaylightSavings = zdt.getZone().getRules().isDaylightSavings(zdt.toInstant());
+ try {
+ getOmniConnection().setTimeCommand(zdt.getYear() - 2000, zdt.getMonthValue(),
+ zdt.getDayOfMonth(), zdt.getDayOfWeek().getValue(), zdt.getHour(), zdt.getMinute(),
+ inDaylightSavings);
+ } catch (IOException | OmniNotConnectedException | OmniInvalidResponseException
+ | OmniUnknownMessageTypeException e) {
+ logger.debug("Could not send Set Time command to OmniLink Controller: {}", e.getMessage());
+ }
+ } else {
+ logger.debug("Invalid command: {}, must be DateTimeType", command);
+ }
+ break;
+ case CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER:
+ if (command instanceof StringType) {
+ try {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_CONSOLE_ENABLE_DISABLE_BEEPER.getNumber(),
+ ((StringType) command).equals(StringType.valueOf("OFF")) ? 0 : 1, 0);
+ updateState(CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER, UnDefType.UNDEF);
+ } catch (NumberFormatException | OmniInvalidResponseException | OmniUnknownMessageTypeException
+ | BridgeOfflineException e) {
+ logger.debug("Could not send Console command to OmniLink Controller: {}", e.getMessage());
+ }
+ } else {
+ logger.debug("Invalid command: {}, must be StringType", command);
+ }
+ break;
+ case CHANNEL_CONSOLE_BEEP:
+ if (command instanceof DecimalType) {
+ try {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_CONSOLE_BEEP.getNumber(),
+ ((DecimalType) command).intValue(), 0);
+ updateState(CHANNEL_CONSOLE_BEEP, UnDefType.UNDEF);
+ } catch (NumberFormatException | OmniInvalidResponseException | OmniUnknownMessageTypeException
+ | BridgeOfflineException e) {
+ logger.debug("Could not send Console command to OmniLink Controller: {}", e.getMessage());
+ }
+ } else {
+ logger.debug("Invalid command: {}, must be DecimalType", command);
+ }
+ break;
+ default:
+ logger.warn("Unknown channel for Bridge thing: {}", channelUID);
+ }
+ }
+
+ private void makeOmnilinkConnection() {
+ final Connection connection = omniConnection;
+ if (connection != null && connection.connected()) {
+ return;
+ }
+
+ logger.debug("Attempting to connect to controller!");
+ try {
+ OmnilinkBridgeConfig config = getConfigAs(OmnilinkBridgeConfig.class);
+
+ this.omniConnection = new Connection(config.getIpAddress(), config.getPort(),
+ config.getKey1() + ":" + config.getKey2());
+
+ /*
+ * HAI only supports one audio player - cycle through features until we find a feature that is an audio
+ * player.
+ */
+ audioPlayer = reqSystemFeatures().getFeatures().stream()
+ .map(featureCode -> AudioPlayer.getAudioPlayerForFeatureCode(featureCode))
+ .filter(Optional::isPresent).findFirst().orElse(Optional.empty());
+
+ systemType = SystemType.getType(reqSystemInformation().getModel());
+
+ if (config.getLogPollingInterval() > 0) {
+ startEventPolling(config.getLogPollingInterval());
+ }
+
+ final Connection connectionNew = omniConnection;
+ if (connectionNew != null) {
+ connectionNew.enableNotifications();
+ connectionNew.addNotificationListener(OmnilinkBridgeHandler.this);
+ connectionNew.addDisconnectListener(this);
+ }
+
+ updateStatus(ThingStatus.ONLINE);
+ cancelReconnectJob(false);
+ updateChannels();
+ updateBridgeProperties();
+ } catch (UnknownHostException e) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ } catch (IOException e) {
+ final Throwable cause = e.getCause();
+ if (cause != null) {
+ final String causeMessage = cause.getMessage();
+
+ if (causeMessage != null && causeMessage.contains("Connection timed out")) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
+ "IP Address probably incorrect, timed out creating connection!");
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, causeMessage);
+ }
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
+ }
+ } catch (Exception e) {
+ setOfflineAndReconnect(e.getMessage());
+ logger.debug("Error connecting to OmniLink Controller: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public void objectStatusNotification(@Nullable ObjectStatus objectStatus) {
+ if (objectStatus != null) {
+ Status[] statuses = objectStatus.getStatuses();
+ for (Status status : statuses) {
+ if (status instanceof ExtendedUnitStatus) {
+ ExtendedUnitStatus unitStatus = (ExtendedUnitStatus) status;
+ int unitNumber = unitStatus.getNumber();
+
+ logger.debug("Received status update for Unit: {}, status: {}", unitNumber, unitStatus);
+ Optional theThing = getUnitThing(unitNumber);
+ theThing.map(Thing::getHandler)
+ .ifPresent(theHandler -> ((UnitHandler) theHandler).handleStatus(unitStatus));
+ } else if (status instanceof ExtendedZoneStatus) {
+ ExtendedZoneStatus zoneStatus = (ExtendedZoneStatus) status;
+ int zoneNumber = zoneStatus.getNumber();
+
+ logger.debug("Received status update for Zone: {}, status: {}", zoneNumber, zoneStatus);
+ Optional theThing = getChildThing(THING_TYPE_ZONE, zoneNumber);
+ theThing.map(Thing::getHandler)
+ .ifPresent(theHandler -> ((ZoneHandler) theHandler).handleStatus(zoneStatus));
+ } else if (status instanceof ExtendedAreaStatus) {
+ final SystemType systemType = this.systemType;
+ ExtendedAreaStatus areaStatus = (ExtendedAreaStatus) status;
+ int areaNumber = areaStatus.getNumber();
+
+ if (systemType != null) {
+ logger.debug("Received status update for Area: {}, status: {}", areaNumber, areaStatus);
+ Optional theThing;
+ switch (systemType) {
+ case OMNI:
+ theThing = getChildThing(THING_TYPE_OMNI_AREA, areaNumber);
+ break;
+ case LUMINA:
+ theThing = getChildThing(THING_TYPE_LUMINA_AREA, areaNumber);
+ break;
+ default:
+ theThing = Optional.empty();
+ }
+ theThing.map(Thing::getHandler)
+ .ifPresent(theHandler -> ((AbstractAreaHandler) theHandler).handleStatus(areaStatus));
+ } else {
+ logger.debug("Received null System Type!");
+ }
+ } else if (status instanceof ExtendedAccessControlReaderLockStatus) {
+ ExtendedAccessControlReaderLockStatus lockStatus = (ExtendedAccessControlReaderLockStatus) status;
+ int lockNumber = lockStatus.getNumber();
+
+ logger.debug("Received status update for Lock: {}, status: {}", lockNumber, lockStatus);
+ Optional theThing = getChildThing(THING_TYPE_LOCK, lockNumber);
+ theThing.map(Thing::getHandler)
+ .ifPresent(theHandler -> ((LockHandler) theHandler).handleStatus(lockStatus));
+ } else if (status instanceof ExtendedThermostatStatus) {
+ ExtendedThermostatStatus thermostatStatus = (ExtendedThermostatStatus) status;
+ int thermostatNumber = thermostatStatus.getNumber();
+
+ logger.debug("Received status update for Thermostat: {}, status: {}", thermostatNumber,
+ thermostatStatus);
+ Optional theThing = getChildThing(THING_TYPE_THERMOSTAT, thermostatNumber);
+ theThing.map(Thing::getHandler)
+ .ifPresent(theHandler -> ((ThermostatHandler) theHandler).handleStatus(thermostatStatus));
+ } else if (status instanceof ExtendedAudioZoneStatus) {
+ ExtendedAudioZoneStatus audioZoneStatus = (ExtendedAudioZoneStatus) status;
+ int audioZoneNumber = audioZoneStatus.getNumber();
+
+ logger.debug("Received status update for Audio Zone: {}, status: {}", audioZoneNumber,
+ audioZoneStatus);
+ Optional theThing = getChildThing(THING_TYPE_AUDIO_ZONE, audioZoneNumber);
+ theThing.map(Thing::getHandler)
+ .ifPresent(theHandler -> ((AudioZoneHandler) theHandler).handleStatus(audioZoneStatus));
+ } else if (status instanceof ExtendedAuxSensorStatus) {
+ ExtendedAuxSensorStatus auxSensorStatus = (ExtendedAuxSensorStatus) status;
+ int auxSensorNumber = auxSensorStatus.getNumber();
+
+ // Aux Sensors can be either temperature or humidity, need to check both.
+ Optional tempThing = getChildThing(THING_TYPE_TEMP_SENSOR, auxSensorNumber);
+ Optional humidityThing = getChildThing(THING_TYPE_HUMIDITY_SENSOR, auxSensorNumber);
+ if (tempThing.isPresent()) {
+ logger.debug("Received status update for Temperature Sensor: {}, status: {}", auxSensorNumber,
+ auxSensorStatus);
+ tempThing.map(Thing::getHandler).ifPresent(
+ theHandler -> ((TempSensorHandler) theHandler).handleStatus(auxSensorStatus));
+ }
+ if (humidityThing.isPresent()) {
+ logger.debug("Received status update for Humidity Sensor: {}, status: {}", auxSensorNumber,
+ auxSensorStatus);
+ humidityThing.map(Thing::getHandler).ifPresent(
+ theHandler -> ((HumiditySensorHandler) theHandler).handleStatus(auxSensorStatus));
+ }
+ } else {
+ logger.debug("Received Object Status Notification that was not processed: {}", objectStatus);
+ }
+ }
+ } else {
+ logger.debug("Received null Object Status Notification!");
+ }
+ }
+
+ @Override
+ public void systemEventNotification(@Nullable SystemEvent event) {
+ if (event != null) {
+ logger.debug("Received System Event Notification of type: {}", event.getType());
+ switch (event.getType()) {
+ case PHONE_LINE_DEAD:
+ case PHONE_LINE_OFF_HOOK:
+ case PHONE_LINE_ON_HOOK:
+ case PHONE_LINE_RING:
+ ChannelUID channel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_PHONE_LINE_EVENT);
+ triggerChannel(channel, event.getType().toString().replaceAll("^PHONE_LINE_", ""));
+ break;
+ case AC_POWER_OFF:
+ case AC_POWER_RESTORED:
+ ChannelUID acChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_AC_POWER_EVENT);
+ triggerChannel(acChannel, event.getType().toString().replaceAll("^AC_POWER_", ""));
+ break;
+ case BATTERY_LOW:
+ case BATTERY_OK:
+ ChannelUID batteryChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_BATTERY_EVENT);
+ triggerChannel(batteryChannel, event.getType().toString().replaceAll("^BATTERY_", ""));
+ break;
+ case DCM_OK:
+ case DCM_TROUBLE:
+ ChannelUID dcmChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_DCM_EVENT);
+ triggerChannel(dcmChannel, event.getType().toString().replaceAll("^DCM_", ""));
+ break;
+ case ENERGY_COST_CRITICAL:
+ case ENERGY_COST_HIGH:
+ case ENERGY_COST_LOW:
+ case ENERGY_COST_MID:
+ ChannelUID energyChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_ENERGY_COST_EVENT);
+ triggerChannel(energyChannel, event.getType().toString().replaceAll("^ENERGY_COST_", ""));
+ break;
+ case CAMERA_1_TRIGGER:
+ case CAMERA_2_TRIGGER:
+ case CAMERA_3_TRIGGER:
+ case CAMERA_4_TRIGGER:
+ case CAMERA_5_TRIGGER:
+ case CAMERA_6_TRIGGER:
+ ChannelUID cameraChannel = new ChannelUID(getThing().getUID(),
+ TRIGGER_CHANNEL_CAMERA_TRIGGER_EVENT);
+ triggerChannel(cameraChannel, String.valueOf(event.getType().toString().charAt(8)));
+ break;
+ case BUTTON:
+ Optional buttonThing = getChildThing(THING_TYPE_BUTTON,
+ ((ButtonEvent) event).getButtonNumber());
+ buttonThing.map(Thing::getHandler)
+ .ifPresent(theHandler -> ((ButtonHandler) theHandler).buttonActivated());
+ break;
+ case ALL_ON_OFF:
+ Optional areaThing = getChildThing(THING_TYPE_OMNI_AREA, ((AllOnOffEvent) event).getArea());
+ if (areaThing.isPresent()) {
+ logger.debug("Thing for allOnOff event: {}", areaThing.get().getUID());
+ areaThing.map(Thing::getHandler).ifPresent(theHandler -> ((AbstractAreaHandler) theHandler)
+ .handleAllOnOffEvent((AllOnOffEvent) event));
+ }
+ break;
+ case UPB_LINK:
+ UPBLinkEvent linkEvent = (UPBLinkEvent) event;
+ UPBLinkEvent.Command command = linkEvent.getLinkCommand();
+ int link = linkEvent.getLinkNumber();
+ handleUPBLink(link, command);
+ break;
+ case ALC_UPB_RADIORA_STARLITE_SWITCH_PRESS:
+ SwitchPressEvent switchPressEvent = (SwitchPressEvent) event;
+ int unitNumber = switchPressEvent.getUnitNumber();
+
+ Optional unitThing = getUnitThing(unitNumber);
+ unitThing.map(Thing::getHandler).ifPresent(
+ theHandler -> ((UnitHandler) theHandler).handleSwitchPressEvent(switchPressEvent));
+ break;
+ default:
+ logger.warn("Ignoring System Event Notification of type: {}", event.getType());
+ }
+ } else {
+ logger.debug("Received null System Event Notification!");
+ }
+ }
+
+ private void handleUPBLink(int link, UPBLinkEvent.Command command) {
+ final ChannelUID activateChannel;
+
+ if (command == UPBLinkEvent.Command.ACTIVATED) {
+ activateChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_UPB_LINK_ACTIVATED_EVENT);
+ } else if (command == UPBLinkEvent.Command.DEACTIVATED) {
+ activateChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_UPB_LINK_DEACTIVATED_EVENT);
+ } else {
+ logger.debug("Received unsupported UPB link event: {}", command);
+ return;
+ }
+ triggerChannel(activateChannel, Integer.toString(link));
+ }
+
+ @Override
+ public void notConnectedEvent(@Nullable Exception e) {
+ if (e != null) {
+ logger.debug("Received an OmniLink Controller not connected event: {}", e.getMessage());
+ setOfflineAndReconnect(e.getMessage());
+ }
+ }
+
+ private void getSystemStatus() throws IOException, OmniNotConnectedException, OmniInvalidResponseException,
+ OmniUnknownMessageTypeException {
+ SystemStatus status = getOmniConnection().reqSystemStatus();
+ logger.debug("Received system status: {}", status);
+ // Let's update system time
+ String dateString = new StringBuilder().append(2000 + status.getYear()).append("-")
+ .append(String.format("%02d", status.getMonth())).append("-")
+ .append(String.format("%02d", status.getDay())).append("T")
+ .append(String.format("%02d", status.getHour())).append(":")
+ .append(String.format("%02d", status.getMinute())).append(":")
+ .append(String.format("%02d", status.getSecond())).toString();
+ updateState(CHANNEL_SYSTEMDATE, new DateTimeType(dateString));
+ }
+
+ public Message reqObjectProperties(int objectType, int objectNum, int direction, int filter1, int filter2,
+ int filter3) throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ return getOmniConnection().reqObjectProperties(objectType, objectNum, direction, filter1, filter2, filter3);
+ } catch (OmniNotConnectedException | IOException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ public Message requestAudioSourceStatus(final int source, final int position)
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ return getOmniConnection().reqAudioSourceStatus(source, position);
+ } catch (OmniNotConnectedException | IOException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ public ObjectStatus requestObjectStatus(final int objType, final int startObject, final int endObject,
+ boolean extended)
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ return getOmniConnection().reqObjectStatus(objType, startObject, endObject, extended);
+ } catch (OmniNotConnectedException | IOException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ public Optional getTemperatureFormat() {
+ try {
+ return Optional.of(TemperatureFormat.valueOf(reqSystemFormats().getTempFormat()));
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Could not request temperature format from controller: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+
+ public void updateChannels() {
+ try {
+ getSystemStatus();
+ updateState(CHANNEL_CONSOLE_ENABLE_DISABLE_BEEPER, UnDefType.UNDEF);
+ updateState(CHANNEL_CONSOLE_BEEP, UnDefType.UNDEF);
+ } catch (IOException | OmniNotConnectedException | OmniInvalidResponseException
+ | OmniUnknownMessageTypeException e) {
+ logger.warn("Unable to update bridge channels: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public void dispose() {
+ cancelReconnectJob(true);
+ cancelEventPolling();
+ final Connection connection = omniConnection;
+ if (connection != null) {
+ connection.removeDisconnectListener(this);
+ connection.disconnect();
+ }
+ }
+
+ private Optional getChildThing(ThingTypeUID type, int number) {
+ Bridge bridge = getThing();
+ return bridge.getThings().stream().filter(t -> t.getThingTypeUID().equals(type))
+ .filter(t -> ((Number) t.getConfiguration().get(THING_PROPERTIES_NUMBER)).intValue() == number)
+ .findFirst();
+ }
+
+ private Optional getUnitThing(int unitId) {
+ Optional theThing = getChildThing(THING_TYPE_UNIT_UPB, unitId);
+ if (!(theThing.isPresent())) {
+ theThing = getChildThing(THING_TYPE_ROOM, unitId);
+ }
+ if (!(theThing.isPresent())) {
+ theThing = getChildThing(THING_TYPE_FLAG, unitId);
+ }
+ if (!(theThing.isPresent())) {
+ theThing = getChildThing(THING_TYPE_OUTPUT, unitId);
+ }
+ if (!(theThing.isPresent())) {
+ theThing = getChildThing(THING_TYPE_DIMMABLE, unitId);
+ }
+ if (!(theThing.isPresent())) {
+ theThing = getChildThing(THING_TYPE_UNIT, unitId);
+ }
+
+ return theThing;
+ }
+
+ public Optional getAudioPlayer() {
+ return audioPlayer;
+ }
+
+ public Message readEventRecord(int eventNumber, int direction)
+ throws OmniInvalidResponseException, OmniUnknownMessageTypeException, BridgeOfflineException {
+ try {
+ return getOmniConnection().readEventRecord(eventNumber, direction);
+ } catch (OmniNotConnectedException | IOException e) {
+ setOfflineAndReconnect(e.getMessage());
+ throw new BridgeOfflineException(e);
+ }
+ }
+
+ private void updateBridgeProperties() {
+ try {
+ SystemInformation systemInformation = reqSystemInformation();
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_MODEL_NUMBER, Integer.toString(systemInformation.getModel()));
+ properties.put(THING_PROPERTIES_MAJOR_VERSION, Integer.toString(systemInformation.getMajor()));
+ properties.put(THING_PROPERTIES_MINOR_VERSION, Integer.toString(systemInformation.getMinor()));
+ properties.put(THING_PROPERTIES_REVISION, Integer.toString(systemInformation.getRevision()));
+ properties.put(THING_PROPERTIES_PHONE_NUMBER, systemInformation.getPhone());
+ updateProperties(properties);
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Could not request system information from OmniLink Controller: {}", e.getMessage());
+ }
+ }
+
+ @Override
+ public void initialize() {
+ scheduleReconnectJob();
+ }
+
+ private void scheduleReconnectJob() {
+ ScheduledFuture> currentReconnectJob = connectJob;
+ if (currentReconnectJob == null || currentReconnectJob.isDone()) {
+ connectJob = super.scheduler.scheduleWithFixedDelay(this::makeOmnilinkConnection, 0, autoReconnectPeriod,
+ TimeUnit.SECONDS);
+ }
+ }
+
+ private void cancelReconnectJob(boolean kill) {
+ ScheduledFuture> currentReconnectJob = connectJob;
+ if (currentReconnectJob != null) {
+ currentReconnectJob.cancel(kill);
+ }
+ }
+
+ private void setOfflineAndReconnect(@Nullable String message) {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
+ cancelEventPolling();
+ final Connection connection = omniConnection;
+ if (connection != null) {
+ connection.removeDisconnectListener(this);
+ }
+ scheduleReconnectJob();
+ }
+
+ private void startEventPolling(int interval) {
+ ScheduledFuture> eventPollingJobFuture = eventPollingJob;
+ if (eventPollingJobFuture == null || eventPollingJobFuture.isDone()) {
+ eventLogNumber = 0;
+ eventPollingJob = super.scheduler.scheduleWithFixedDelay(this::pollEvents, 0, interval, TimeUnit.SECONDS);
+ }
+ }
+
+ private void cancelEventPolling() {
+ ScheduledFuture> eventPollingJobFuture = eventPollingJob;
+ if (eventPollingJobFuture != null) {
+ eventPollingJobFuture.cancel(true);
+ }
+ }
+
+ private void pollEvents() {
+ // On first run, direction is -1 (most recent event), after its 1 for the next log message
+ try {
+ Message message;
+ do {
+ logger.trace("Polling for event log messages.");
+ int direction = eventLogNumber == 0 ? -1 : 1;
+ message = readEventRecord(eventLogNumber, direction);
+ if (message.getMessageType() == Message.MESG_TYPE_EVENT_LOG_DATA) {
+ EventLogData logData = (EventLogData) message;
+ logger.debug("Processing event log message number: {}", logData.getEventNumber());
+ eventLogNumber = logData.getEventNumber();
+ String json = gson.toJson(logData);
+ logger.debug("Receieved event log message: {}", json);
+ updateState(CHANNEL_EVENT_LOG, new StringType(json));
+ }
+ } while (message.getMessageType() != Message.MESG_TYPE_END_OF_DATA);
+
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Exception recieved while polling for event log messages: {}", e.getMessage());
+ }
+ }
+
+ private Connection getOmniConnection() throws OmniNotConnectedException {
+ final Connection connection = omniConnection;
+ if (connection != null) {
+ return connection;
+ } else {
+ throw new OmniNotConnectedException("Connection not yet established!");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TempSensorHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TempSensorHandler.java
new file mode 100644
index 00000000000..ddc4e122156
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TempSensorHandler.java
@@ -0,0 +1,182 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.measure.quantity.Temperature;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.ImperialUnits;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AuxSensorProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedAuxSensorStatus;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link TempSensorHandler} defines some methods that are used to interface
+ * with an OmniLink Temperature Sensor. This by extension also defines the
+ * Temperature Sensor thing that openHAB will be able to pick up and interface
+ * with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class TempSensorHandler extends AbstractOmnilinkStatusHandler {
+ private final Logger logger = LoggerFactory.getLogger(TempSensorHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public TempSensorHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateTempSensorProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Temperature Sensor!");
+ }
+ }
+
+ private void updateTempSensorProperties(OmnilinkBridgeHandler bridgeHandler) {
+ final List areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AUX_SENSORS, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (AuxSensorProperties auxSensorProperties : objectPropertyRequest) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, auxSensorProperties.getName());
+ properties.put(THING_PROPERTIES_AREA, Integer.toString(areaProperties.getNumber()));
+ updateProperties(properties);
+ }
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ Optional temperatureFormat = Optional.empty();
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ if (!(command instanceof QuantityType)) {
+ logger.debug("Invalid command: {}, must be QuantityType", command);
+ return;
+ }
+ if (bridgeHandler != null) {
+ temperatureFormat = bridgeHandler.getTemperatureFormat();
+ if (!temperatureFormat.isPresent()) {
+ logger.warn("Receieved null temperature format!");
+ return;
+ }
+ } else {
+ logger.warn("Could not connect to Bridge, failed to get temperature format!");
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_AUX_LOW_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HEAT_LOW_POINT.getNumber(),
+ temperatureFormat.get().formatToOmni(((QuantityType) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_AUX_HIGH_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(),
+ temperatureFormat.get().formatToOmni(((QuantityType) command).intValue()),
+ thingID);
+ break;
+ default:
+ logger.warn("Unknown channel for Temperature Sensor thing: {}", channelUID);
+ }
+ }
+
+ @Override
+ public void updateChannels(ExtendedAuxSensorStatus status) {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ Optional temperatureFormat = bridgeHandler.getTemperatureFormat();
+ if (temperatureFormat.isPresent()) {
+ updateState(CHANNEL_AUX_TEMP, new QuantityType<>(
+ temperatureFormat.get().omniToFormat(status.getTemperature()),
+ temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
+ updateState(CHANNEL_AUX_LOW_SETPOINT, new QuantityType<>(
+ temperatureFormat.get().omniToFormat(status.getCoolSetpoint()),
+ temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
+ updateState(CHANNEL_AUX_HIGH_SETPOINT, new QuantityType<>(
+ temperatureFormat.get().omniToFormat(status.getHeatSetpoint()),
+ temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
+ } else {
+ logger.warn("Receieved null temperature format, could not update Temperature Sensor channels!");
+ }
+ } else {
+ logger.debug("Received null bridge while updating Temperature Sensor channels!");
+ }
+ }
+
+ @Override
+ protected Optional retrieveStatus() {
+ try {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ ObjectStatus objStatus = bridgeHandler.requestObjectStatus(Message.OBJ_TYPE_AUX_SENSOR, thingID,
+ thingID, true);
+ return Optional.of((ExtendedAuxSensorStatus) objStatus.getStatuses()[0]);
+ } else {
+ logger.debug("Received null bridge while updating Temperature Sensor status!");
+ return Optional.empty();
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while refreshing Temperature Sensor status: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TemperatureFormat.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TemperatureFormat.java
new file mode 100644
index 00000000000..8379e0dfb2e
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/TemperatureFormat.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+import com.digitaldan.jomnilinkII.MessageUtils;
+
+/**
+ * The {@link TemperatureFormat} defines some methods that are used to
+ * convert OmniLink temperature values into Fahrenheit or Celsius.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public enum TemperatureFormat {
+ // Don't convert zero - it appears that is what omni returns when there is no value.
+ CELSIUS(2) {
+ @Override
+ public float omniToFormat(int omniNumber) {
+ return MessageUtils.omniToC(omniNumber);
+ }
+
+ @Override
+ public int formatToOmni(int celsius) {
+ return MessageUtils.CToOmni(celsius);
+ }
+ },
+ FAHRENHEIT(1) {
+ @Override
+ public float omniToFormat(int omniNumber) {
+ return MessageUtils.omniToF(omniNumber);
+ }
+
+ @Override
+ public int formatToOmni(int fahrenheit) {
+ return MessageUtils.FtoOmni(fahrenheit);
+ }
+ };
+
+ private final int formatNumber;
+
+ private TemperatureFormat(int formatNumber) {
+ this.formatNumber = formatNumber;
+ }
+
+ /**
+ * Convert a number represented by the omni to the format.
+ *
+ * @param omniNumber Number to convert
+ * @return Number converted to appropriate format.
+ */
+ public abstract float omniToFormat(int omniNumber);
+
+ /**
+ * Convert a number from this format into an omni number.
+ *
+ * @param format Number in the current format.
+ * @return Omni formatted number.
+ */
+ public abstract int formatToOmni(int format);
+
+ /**
+ * Get the number which identifies this format as defined by the omniprotocol.
+ *
+ * @return Number which identifies this temperature format.
+ */
+ public int getFormatNumber() {
+ return formatNumber;
+ }
+
+ public static TemperatureFormat valueOf(int tempFormat) {
+ if (tempFormat == CELSIUS.formatNumber) {
+ return CELSIUS;
+ } else if (tempFormat == FAHRENHEIT.formatNumber) {
+ return FAHRENHEIT;
+ } else {
+ throw new IllegalArgumentException("Invalid temperature format!");
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ThermostatHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ThermostatHandler.java
new file mode 100644
index 00000000000..ee61ee24032
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ThermostatHandler.java
@@ -0,0 +1,265 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.math.BigInteger;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import javax.measure.quantity.Dimensionless;
+import javax.measure.quantity.Temperature;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.QuantityType;
+import org.openhab.core.library.unit.ImperialUnits;
+import org.openhab.core.library.unit.SIUnits;
+import org.openhab.core.library.unit.Units;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ThermostatProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedThermostatStatus;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link ThermostatHandler} defines some methods that are used to
+ * interface with an OmniLink Thermostat. This by extension also defines the
+ * Thermostat thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class ThermostatHandler extends AbstractOmnilinkStatusHandler {
+ private final Logger logger = LoggerFactory.getLogger(ThermostatHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ private enum ThermostatStatus {
+ HEATING(0, 1),
+ COOLING(1, 2),
+ HUMIDIFYING(2, 3),
+ DEHUMIDIFYING(3, 4);
+
+ private final int bit;
+ private final int modeValue;
+
+ private ThermostatStatus(int bit, int modeValue) {
+ this.bit = bit;
+ this.modeValue = modeValue;
+ }
+ }
+
+ public ThermostatHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateThermostatProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Thermostat!");
+ }
+ }
+
+ private void updateThermostatProperties(OmnilinkBridgeHandler bridgeHandler) {
+ final List areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.THERMOSTAT, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (ThermostatProperties thermostatProperties : objectPropertyRequest) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, thermostatProperties.getName());
+ properties.put(THING_PROPERTIES_AREA, Integer.toString(areaProperties.getNumber()));
+ updateProperties(properties);
+ }
+ }
+ }
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ Optional temperatureFormat = Optional.empty();
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ if (!(command instanceof DecimalType) && !(command instanceof QuantityType)) {
+ logger.debug("Invalid command: {}, must be DecimalType or QuantityType", command);
+ return;
+ }
+ if (bridgeHandler != null) {
+ temperatureFormat = bridgeHandler.getTemperatureFormat();
+ if (!temperatureFormat.isPresent()) {
+ logger.warn("Receieved null temperature format!");
+ return;
+ }
+ } else {
+ logger.warn("Could not connect to Bridge, failed to get temperature format!");
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_THERMO_SYSTEM_MODE:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_SYSTEM_MODE.getNumber(),
+ ((DecimalType) command).intValue(), thingID);
+ break;
+ case CHANNEL_THERMO_FAN_MODE:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_FAN_MODE.getNumber(), ((DecimalType) command).intValue(),
+ thingID);
+ break;
+ case CHANNEL_THERMO_HOLD_STATUS:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HOLD_MODE.getNumber(),
+ ((DecimalType) command).intValue(), thingID);
+ break;
+ case CHANNEL_THERMO_HEAT_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HEAT_LOW_POINT.getNumber(),
+ temperatureFormat.get().formatToOmni(((QuantityType) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_THERMO_COOL_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(),
+ temperatureFormat.get().formatToOmni(((QuantityType) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_THERMO_HUMIDIFY_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HUMDIFY_POINT.getNumber(),
+ TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_THERMO_DEHUMIDIFY_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_DEHUMIDIFY_POINT.getNumber(),
+ TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType) command).intValue()),
+ thingID);
+ break;
+ default:
+ logger.warn("Unknown channel for Thermostat thing: {}", channelUID);
+ }
+ }
+
+ @Override
+ protected void updateChannels(ExtendedThermostatStatus status) {
+ logger.debug("updateChannels called for Thermostat status: {}", status);
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+
+ // Thermostat communication status
+ BigInteger thermostatAlarms = BigInteger.valueOf(status.getStatus());
+ updateState(CHANNEL_THERMO_COMM_FAILURE,
+ thermostatAlarms.testBit(0) ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
+ updateState(CHANNEL_THERMO_FREEZE_ALARM,
+ thermostatAlarms.testBit(1) ? OpenClosedType.CLOSED : OpenClosedType.OPEN);
+
+ // Thermostat operation status
+ BigInteger thermostatStatus = BigInteger.valueOf(status.getExtendedStatus());
+ if (thermostatStatus.testBit(ThermostatStatus.HEATING.bit)) {
+ updateState(CHANNEL_THERMO_STATUS, new DecimalType(ThermostatStatus.HEATING.modeValue));
+ } else if (thermostatStatus.testBit(ThermostatStatus.COOLING.bit)) {
+ updateState(CHANNEL_THERMO_STATUS, new DecimalType(ThermostatStatus.COOLING.modeValue));
+ } else if (thermostatStatus.testBit(ThermostatStatus.HUMIDIFYING.bit)) {
+ updateState(CHANNEL_THERMO_STATUS, new DecimalType(ThermostatStatus.HUMIDIFYING.modeValue));
+ } else if (thermostatStatus.testBit(ThermostatStatus.DEHUMIDIFYING.bit)) {
+ updateState(CHANNEL_THERMO_STATUS, new DecimalType(ThermostatStatus.DEHUMIDIFYING.modeValue));
+ } else {
+ updateState(CHANNEL_THERMO_STATUS, new DecimalType(0));
+ }
+
+ // Thermostat temperature status
+ if (bridgeHandler != null) {
+ Optional temperatureFormat = bridgeHandler.getTemperatureFormat();
+ if (temperatureFormat.isPresent()) {
+ updateState(CHANNEL_THERMO_CURRENT_TEMP, new QuantityType<>(
+ temperatureFormat.get().omniToFormat(status.getCurrentTemperature()),
+ temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
+ updateState(CHANNEL_THERMO_OUTDOOR_TEMP, new QuantityType<>(
+ temperatureFormat.get().omniToFormat(status.getOutdoorTemperature()),
+ temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
+ updateState(CHANNEL_THERMO_COOL_SETPOINT, new QuantityType<>(
+ temperatureFormat.get().omniToFormat(status.getCoolSetpoint()),
+ temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
+ updateState(CHANNEL_THERMO_HEAT_SETPOINT, new QuantityType<>(
+ temperatureFormat.get().omniToFormat(status.getHeatSetpoint()),
+ temperatureFormat.get().getFormatNumber() == 1 ? ImperialUnits.FAHRENHEIT : SIUnits.CELSIUS));
+ } else {
+ logger.warn("Receieved null temperature format, could not update Thermostat channels!");
+ }
+ } else {
+ logger.warn("Could not connect to Bridge, failed to get temperature format!");
+ return;
+ }
+
+ // Thermostat humidity status
+ updateState(CHANNEL_THERMO_HUMIDITY, new QuantityType<>(
+ TemperatureFormat.FAHRENHEIT.omniToFormat(status.getCurrentHumidity()), Units.PERCENT));
+ updateState(CHANNEL_THERMO_HUMIDIFY_SETPOINT, new QuantityType<>(
+ TemperatureFormat.FAHRENHEIT.omniToFormat(status.getHumidifySetpoint()), Units.PERCENT));
+ updateState(CHANNEL_THERMO_DEHUMIDIFY_SETPOINT, new QuantityType<>(
+ TemperatureFormat.FAHRENHEIT.omniToFormat(status.getDehumidifySetpoint()), Units.PERCENT));
+
+ // Thermostat mode, fan, and hold status
+ updateState(CHANNEL_THERMO_SYSTEM_MODE, new DecimalType(status.getSystemMode()));
+ updateState(CHANNEL_THERMO_FAN_MODE, new DecimalType(status.getFanMode()));
+ updateState(CHANNEL_THERMO_HOLD_STATUS,
+ new DecimalType(status.getHoldStatus() > 2 ? 1 : status.getHoldStatus()));
+ }
+
+ @Override
+ protected Optional retrieveStatus() {
+ try {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ ObjectStatus objStatus = bridgeHandler.requestObjectStatus(Message.OBJ_TYPE_THERMO, thingID, thingID,
+ true);
+ return Optional.of((ExtendedThermostatStatus) objStatus.getStatuses()[0]);
+ } else {
+ logger.debug("Received null bridge while updating Thermostat status!");
+ return Optional.empty();
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while refreshing Thermostat status: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/UnitHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/UnitHandler.java
new file mode 100644
index 00000000000..5370daefab4
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/UnitHandler.java
@@ -0,0 +1,214 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.UnitProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedUnitStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.systemevents.SwitchPressEvent;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link AbstractOmnilinkHandler} defines some methods that can be used across
+ * the many different Units exposed by the OmniLink protocol
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class UnitHandler extends AbstractOmnilinkStatusHandler {
+ private final Logger logger = LoggerFactory.getLogger(UnitHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public UnitHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateUnitProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Unit!");
+ }
+ }
+
+ private void updateUnitProperties(OmnilinkBridgeHandler bridgeHandler) {
+ final List areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.UNIT, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).selectAnyLoad().build();
+
+ for (UnitProperties unitProperties : objectPropertyRequest) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, unitProperties.getName());
+ properties.put(THING_PROPERTIES_AREA, Integer.toString(areaProperties.getNumber()));
+ updateProperties(properties);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_UNIT_LEVEL:
+ case CHANNEL_UNIT_SWITCH:
+ if (command instanceof OnOffType) {
+ handleOnOff(channelUID, (OnOffType) command);
+ } else {
+ logger.debug("Invalid command: {}, must be OnOffType", command);
+ }
+ break;
+ case CHANNEL_UNIT_ON_FOR_SECONDS:
+ case CHANNEL_UNIT_OFF_FOR_SECONDS:
+ case CHANNEL_UNIT_ON_FOR_MINUTES:
+ case CHANNEL_UNIT_OFF_FOR_MINUTES:
+ case CHANNEL_UNIT_ON_FOR_HOURS:
+ case CHANNEL_UNIT_OFF_FOR_HOURS:
+ if (command instanceof DecimalType) {
+ handleUnitDuration(channelUID, (DecimalType) command);
+ } else {
+ logger.debug("Invalid command: {}, must be DecimalType", command);
+ }
+ break;
+ default:
+ logger.warn("Unknown channel for Unit thing: {}", channelUID);
+ }
+ }
+
+ private void handleUnitDuration(ChannelUID channelUID, DecimalType command) {
+ logger.debug("handleUnitDuration called for channel: {}, command: {}", channelUID, command);
+ final String channelID = channelUID.getId();
+
+ int duration;
+ if (channelID.endsWith("seconds")) {
+ duration = command.intValue();
+ } else if (channelID.endsWith("minutes")) {
+ duration = command.intValue() + 100;
+ } else if (channelID.endsWith("hours")) {
+ duration = command.intValue() + 200;
+ } else {
+ logger.warn("Unknown channel for Unit duration: {}", channelUID);
+ return;
+ }
+
+ sendOmnilinkCommand(
+ channelID.startsWith("on") ? OmniLinkCmd.CMD_UNIT_ON.getNumber() : OmniLinkCmd.CMD_UNIT_OFF.getNumber(),
+ duration, thingID);
+ }
+
+ protected void handleOnOff(ChannelUID channelUID, OnOffType command) {
+ logger.debug("handleOnOff called for channel: {}, command: {}", channelUID, command);
+ sendOmnilinkCommand(OnOffType.ON.equals(command) ? OmniLinkCmd.CMD_UNIT_ON.getNumber()
+ : OmniLinkCmd.CMD_UNIT_OFF.getNumber(), 0, thingID);
+ }
+
+ @Override
+ public void updateChannels(ExtendedUnitStatus status) {
+ logger.debug("updateChannels called for Unit status: {}", status);
+ int unitStatus = status.getStatus();
+ int level = 0;
+
+ if (unitStatus == Status.UNIT_OFF) {
+ level = 0;
+ } else if (unitStatus == Status.UNIT_ON) {
+ level = 100;
+ } else if ((unitStatus >= Status.UNIT_SCENE_A) && (unitStatus <= Status.UNIT_SCENE_L)) {
+ level = 100;
+ } else if ((unitStatus >= Status.UNIT_LEVEL_0) && (unitStatus <= Status.UNIT_LEVEL_100)) {
+ level = unitStatus - Status.UNIT_LEVEL_0;
+ }
+
+ updateState(CHANNEL_UNIT_LEVEL, PercentType.valueOf(Integer.toString(level)));
+ updateState(CHANNEL_UNIT_SWITCH, OnOffType.from(level != 0));
+ }
+
+ @Override
+ protected Optional retrieveStatus() {
+ try {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ ObjectStatus objectStatus = bridgeHandler.requestObjectStatus(Message.OBJ_TYPE_UNIT, thingID, thingID,
+ true);
+ return Optional.of((ExtendedUnitStatus) objectStatus.getStatuses()[0]);
+ } else {
+ logger.debug("Received null bridge while updating Unit status!");
+ return Optional.empty();
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while refreshing Unit status: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+
+ private static class Status {
+ private static final int UNIT_OFF = 0;
+ private static final int UNIT_ON = 1;
+ private static final int UNIT_SCENE_A = 2;
+ private static final int UNIT_SCENE_L = 13;
+ private static final int UNIT_LEVEL_0 = 100;
+ private static final int UNIT_LEVEL_100 = 200;
+ }
+
+ /**
+ * Handle a switch press event by triggering the appropriate channel.
+ *
+ * @param switchPressEvent
+ */
+ public void handleSwitchPressEvent(SwitchPressEvent switchPressEvent) {
+ ChannelUID activateChannel = new ChannelUID(getThing().getUID(), TRIGGER_CHANNEL_SWITCH_PRESS_EVENT);
+ triggerChannel(activateChannel, Integer.toString(switchPressEvent.getSwitchValue()));
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ZoneHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ZoneHandler.java
new file mode 100644
index 00000000000..572de0b4ce6
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/ZoneHandler.java
@@ -0,0 +1,201 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler;
+
+import static com.digitaldan.jomnilinkII.MessageTypes.properties.AuxSensorProperties.SENSOR_TYPE_PROGRAMMABLE_ENERGY_SAVER_MODULE;
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequest;
+import org.openhab.binding.omnilink.internal.discovery.ObjectPropertyRequests;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OpenClosedType;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.State;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.Message;
+import com.digitaldan.jomnilinkII.MessageTypes.ObjectStatus;
+import com.digitaldan.jomnilinkII.MessageTypes.SecurityCodeValidation;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.AreaProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.properties.ZoneProperties;
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedZoneStatus;
+import com.digitaldan.jomnilinkII.OmniInvalidResponseException;
+import com.digitaldan.jomnilinkII.OmniUnknownMessageTypeException;
+
+/**
+ * The {@link ZoneHandler} defines some methods that are used to
+ * interface with an OmniLink Zone. This by extension also defines the
+ * OmniPro Zone thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ */
+@NonNullByDefault
+public class ZoneHandler extends AbstractOmnilinkStatusHandler {
+ private final Logger logger = LoggerFactory.getLogger(ZoneHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public ZoneHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void initialize() {
+ super.initialize();
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ if (bridgeHandler != null) {
+ updateZoneProperties(bridgeHandler);
+ } else {
+ updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR,
+ "Received null bridge while initializing Zone!");
+ }
+ }
+
+ private void updateZoneProperties(OmnilinkBridgeHandler bridgeHandler) {
+ final List areas = super.getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = super.bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.ZONE, getThingNumber(), 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (ZoneProperties zoneProperties : objectPropertyRequest) {
+ if (zoneProperties.getZoneType() <= SENSOR_TYPE_PROGRAMMABLE_ENERGY_SAVER_MODULE) {
+ Map properties = editProperties();
+ properties.put(THING_PROPERTIES_NAME, zoneProperties.getName());
+ properties.put(THING_PROPERTIES_AREA, Integer.toString(areaProperties.getNumber()));
+ updateProperties(properties);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+ int mode;
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ if (!(command instanceof StringType)) {
+ logger.debug("Invalid command: {}, must be StringType", command);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_ZONE_BYPASS:
+ mode = OmniLinkCmd.CMD_SECURITY_BYPASS_ZONE.getNumber();
+ break;
+ case CHANNEL_ZONE_RESTORE:
+ mode = OmniLinkCmd.CMD_SECURITY_RESTORE_ZONE.getNumber();
+ break;
+ default:
+ mode = -1;
+ }
+ int areaNumber = getAreaNumber();
+ logger.debug("mode {} on zone {} with code {}", mode, thingID, command.toFullString());
+ char[] code = command.toFullString().toCharArray();
+ if (code.length != 4) {
+ logger.warn("Invalid code length, code must be 4 digits");
+ } else {
+ try {
+ final OmnilinkBridgeHandler bridge = getOmnilinkBridgeHandler();
+ if (bridge != null) {
+ SecurityCodeValidation codeValidation = bridge.reqSecurityCodeValidation(areaNumber,
+ Character.getNumericValue(code[0]), Character.getNumericValue(code[1]),
+ Character.getNumericValue(code[2]), Character.getNumericValue(code[3]));
+ /*
+ * 0 Invalid code
+ * 1 Master
+ * 2 Manager
+ * 3 User
+ */
+ logger.debug("User code number: {} level: {}", codeValidation.getCodeNumber(),
+ codeValidation.getAuthorityLevel());
+ /*
+ * Valid user code number are 1-99, 251 is duress code, 0 means code does not exist
+ */
+ if ((codeValidation.getCodeNumber() > 0 && codeValidation.getCodeNumber() <= 99)
+ && codeValidation.getAuthorityLevel() > 0) {
+ sendOmnilinkCommand(mode, codeValidation.getCodeNumber(), thingID);
+ } else {
+ logger.warn("System reported an invalid code");
+ }
+ } else {
+ logger.debug("Received null bridge while sending zone command!");
+ }
+ } catch (OmniInvalidResponseException e) {
+ logger.debug("Zone command failed: {}", e.getMessage());
+ } catch (OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Could not send zone command: {}", e.getMessage());
+ }
+ }
+ // This is a send only channel, so don't store the user code
+ updateState(channelUID, UnDefType.UNDEF);
+ }
+
+ @Override
+ protected void updateChannels(ExtendedZoneStatus zoneStatus) {
+ // 0 Secure. 1 Not ready, 3 Trouble
+ int current = ((zoneStatus.getStatus() >> 0) & 0x03);
+ // 0 Secure, 1 Tripped, 2 Reset, but previously tripped
+ int latched = ((zoneStatus.getStatus() >> 2) & 0x03);
+ // 0 Disarmed, 1 Armed, 2 Bypass user, 3 Bypass system
+ int arming = ((zoneStatus.getStatus() >> 4) & 0x03);
+ State contactState = Integer.valueOf(current).equals(0) ? OpenClosedType.CLOSED : OpenClosedType.OPEN;
+ logger.debug("handling Zone Status change to state: {}, current: {}, latched: {}, arming: {}", contactState,
+ current, latched, arming);
+ updateState(CHANNEL_ZONE_CONTACT, contactState);
+ updateState(CHANNEL_ZONE_CURRENT_CONDITION, new DecimalType(current));
+ updateState(CHANNEL_ZONE_LATCHED_ALARM_STATUS, new DecimalType(latched));
+ updateState(CHANNEL_ZONE_ARMING_STATUS, new DecimalType(arming));
+ }
+
+ @Override
+ protected Optional retrieveStatus() {
+ try {
+ final OmnilinkBridgeHandler bridge = getOmnilinkBridgeHandler();
+ if (bridge != null) {
+ ObjectStatus objStatus = bridge.requestObjectStatus(Message.OBJ_TYPE_ZONE, thingID, thingID, true);
+ return Optional.of((ExtendedZoneStatus) objStatus.getStatuses()[0]);
+ } else {
+ logger.debug("Received null bridge while updating Zone status!");
+ return Optional.empty();
+ }
+ } catch (OmniInvalidResponseException | OmniUnknownMessageTypeException | BridgeOfflineException e) {
+ logger.debug("Received exception while refreshing Zone status: {}", e.getMessage());
+ return Optional.empty();
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/DimmableUnitHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/DimmableUnitHandler.java
new file mode 100644
index 00000000000..632197f0852
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/DimmableUnitHandler.java
@@ -0,0 +1,93 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler.units;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.CHANNEL_UNIT_LEVEL;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.handler.OmniLinkCmd;
+import org.openhab.binding.omnilink.internal.handler.UnitHandler;
+import org.openhab.core.library.types.IncreaseDecreaseType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.library.types.PercentType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link DimmableUnitHandler} defines some methods that are used to
+ * interface with an OmniLink Dimmable Unit. This by extension also defines the
+ * Dimmable Unit things that openHAB will be able to pick up and interface with.
+ *
+ * @author Brian O'Connell - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class DimmableUnitHandler extends UnitHandler {
+ private final Logger logger = LoggerFactory.getLogger(DimmableUnitHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public DimmableUnitHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+ switch (channelUID.getId()) {
+ case CHANNEL_UNIT_LEVEL:
+ handleUnitLevel(channelUID, command);
+ break;
+ default:
+ logger.debug("Unknown channel for Dimmable Unit thing: {}", channelUID);
+ super.handleCommand(channelUID, command);
+ }
+ }
+
+ private void handleUnitLevel(ChannelUID channelUID, Command command) {
+ logger.debug("handleUnitLevel called for channel: {}, command: {}", channelUID, command);
+ if (command instanceof PercentType) {
+ handlePercent(channelUID, (PercentType) command);
+ } else if (command instanceof IncreaseDecreaseType) {
+ handleIncreaseDecrease(channelUID, (IncreaseDecreaseType) command);
+ } else {
+ // Only handle percent or increase/decrease.
+ super.handleCommand(channelUID, command);
+ }
+ }
+
+ private void handlePercent(ChannelUID channelUID, PercentType command) {
+ logger.debug("handlePercent called for channel: {}, command: {}", channelUID, command);
+ int lightLevel = command.intValue();
+
+ if (lightLevel == 0) {
+ super.handleOnOff(channelUID, OnOffType.OFF);
+ } else if (lightLevel == 100) {
+ super.handleOnOff(channelUID, OnOffType.ON);
+ } else {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_UNIT_PERCENT.getNumber(), lightLevel, thingID);
+ }
+ }
+
+ private void handleIncreaseDecrease(ChannelUID channelUID, IncreaseDecreaseType command) {
+ logger.debug("handleIncreaseDecrease called for channel: {}, command: {}", channelUID, command);
+ sendOmnilinkCommand(
+ IncreaseDecreaseType.INCREASE.equals(command) ? OmniLinkCmd.CMD_UNIT_UNIT_BRIGHTEN_STEP_1.getNumber()
+ : OmniLinkCmd.CMD_UNIT_UNIT_DIM_STEP_1.getNumber(),
+ 0, thingID);
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/FlagHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/FlagHandler.java
new file mode 100644
index 00000000000..56874e767ff
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/FlagHandler.java
@@ -0,0 +1,90 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler.units;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.handler.OmniLinkCmd;
+import org.openhab.binding.omnilink.internal.handler.UnitHandler;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedUnitStatus;
+
+/**
+ * The {@link FlagHandler} defines some methods that are used to
+ * interface with an OmniLink Flag. This by extension also defines the
+ * Flag thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class FlagHandler extends UnitHandler {
+ private final Logger logger = LoggerFactory.getLogger(FlagHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public FlagHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_FLAG_VALUE:
+ if (command instanceof DecimalType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_UNIT_SET_COUNTER.getNumber(),
+ ((DecimalType) command).intValue(), thingID);
+ } else {
+ logger.debug("Invalid command: {}, must be DecimalType", command);
+ }
+ break;
+ case CHANNEL_FLAG_SWITCH:
+ if (command instanceof OnOffType) {
+ handleOnOff(channelUID, (OnOffType) command);
+ } else {
+ logger.debug("Invalid command: {}, must be OnOffType", command);
+ }
+ break;
+ default:
+ logger.debug("Unknown channel for Flag thing: {}", channelUID);
+ super.handleCommand(channelUID, command);
+ }
+ }
+
+ @Override
+ public void updateChannels(ExtendedUnitStatus status) {
+ logger.debug("updateChannels called for Flag status: {}", status);
+ updateState(CHANNEL_FLAG_VALUE, DecimalType.valueOf(Integer.toString(status.getStatus())));
+ updateState(CHANNEL_FLAG_SWITCH, OnOffType.from(status.getStatus() == 1));
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/OutputHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/OutputHandler.java
new file mode 100644
index 00000000000..5bfb194044c
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/OutputHandler.java
@@ -0,0 +1,35 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler.units;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.handler.UnitHandler;
+import org.openhab.core.thing.Thing;
+
+/**
+ * The {@link OutputHandler} defines some methods that are used to
+ * interface with an OmniLink Output. This by extension also defines the
+ * Output thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Brian O'Connell - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class OutputHandler extends UnitHandler {
+ public @Nullable String number;
+
+ public OutputHandler(Thing thing) {
+ super(thing);
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/UpbRoomHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/UpbRoomHandler.java
new file mode 100644
index 00000000000..f59344f34ef
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/UpbRoomHandler.java
@@ -0,0 +1,168 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler.units;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.*;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.handler.OmniLinkCmd;
+import org.openhab.binding.omnilink.internal.handler.UnitHandler;
+import org.openhab.core.library.types.DecimalType;
+import org.openhab.core.library.types.OnOffType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.thing.ThingStatus;
+import org.openhab.core.thing.ThingStatusDetail;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.digitaldan.jomnilinkII.MessageTypes.statuses.ExtendedUnitStatus;
+
+/**
+ * The {@link UpbRoomHandler} defines some methods that are used to
+ * interface with an OmniLink UPB Room. This by extension also defines the
+ * OmniPro UPB Room thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class UpbRoomHandler extends UnitHandler {
+ private final Logger logger = LoggerFactory.getLogger(UpbRoomHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public UpbRoomHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ retrieveStatus().ifPresentOrElse(this::updateChannels, () -> updateStatus(ThingStatus.OFFLINE,
+ ThingStatusDetail.OFFLINE.COMMUNICATION_ERROR, "Received null staus update!"));
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_ROOM_SCENE_A:
+ case CHANNEL_ROOM_SCENE_B:
+ case CHANNEL_ROOM_SCENE_C:
+ case CHANNEL_ROOM_SCENE_D:
+ if (command instanceof OnOffType) {
+ handleRoomScene(channelUID, (OnOffType) command);
+ } else {
+ logger.debug("Invalid command: {}, must be OnOffType", command);
+ }
+ break;
+ case CHANNEL_ROOM_SWITCH:
+ if (command instanceof OnOffType) {
+ super.handleOnOff(channelUID, (OnOffType) command);
+ } else {
+ logger.debug("Invalid command: {}, must be OnOffType", command);
+ }
+ break;
+ case CHANNEL_ROOM_STATE:
+ if (command instanceof DecimalType) {
+ handleRoomState(channelUID, (DecimalType) command);
+ } else {
+ logger.debug("Invalid command: {}, must be DecimalType", command);
+ }
+ break;
+ default:
+ logger.debug("Unknown channel for UPB Room thing: {}", channelUID);
+ super.handleCommand(channelUID, command);
+ }
+ }
+
+ private void handleRoomScene(ChannelUID channelUID, OnOffType command) {
+ logger.debug("handleRoomScene called for channel: {}, command: {}", channelUID, command);
+ int linkNum;
+
+ switch (channelUID.getId()) {
+ case "scene_a":
+ linkNum = 0;
+ break;
+ case "scene_b":
+ linkNum = 1;
+ break;
+ case "scene_c":
+ linkNum = 2;
+ break;
+ case "scene_d":
+ linkNum = 3;
+ break;
+ default:
+ logger.warn("Unexpected UPB Room scene: {}", channelUID);
+ return;
+ }
+ int roomNum = (thingID + 7) / 8;
+ int param2 = ((roomNum * 6) - 3) + linkNum;
+ sendOmnilinkCommand(OnOffType.ON.equals(command) ? OmniLinkCmd.CMD_UNIT_UPB_LINK_ON.getNumber()
+ : OmniLinkCmd.CMD_UNIT_UPB_LINK_OFF.getNumber(), 0, param2);
+ }
+
+ private void handleRoomState(ChannelUID channelUID, DecimalType command) {
+ logger.debug("handleRoomState called for channel: {}, command: {}", channelUID, command);
+ final int cmdValue = command.intValue();
+ int cmd;
+ int param2;
+
+ switch (cmdValue) {
+ case 0:
+ cmd = OmniLinkCmd.CMD_UNIT_OFF.getNumber();
+ param2 = thingID;
+ break;
+ case 1:
+ cmd = OmniLinkCmd.CMD_UNIT_ON.getNumber();
+ param2 = thingID;
+ break;
+ case 2:
+ case 3:
+ case 4:
+ case 5:
+ cmd = OmniLinkCmd.CMD_UNIT_UPB_LINK_ON.getNumber();
+ /*
+ * A little magic with the link #'s: 0 and 1 are off and on, respectively.
+ * So A ends up being 2, but OmniLink Protocol expects an offset of 0. That
+ * is why we subtract the 2.
+ */
+ int roomNum = (thingID + 7) / 8;
+ param2 = ((roomNum * 6) - 3) + cmdValue - 2;
+ break;
+ default:
+ logger.warn("Unexpected UPB Room state: {}", Integer.toString(cmdValue));
+ return;
+ }
+
+ sendOmnilinkCommand(cmd, 0, param2);
+ }
+
+ @Override
+ public void updateChannels(ExtendedUnitStatus status) {
+ logger.debug("updateChannels called for UPB Room status: {}", status);
+ int unitStatus = status.getStatus();
+
+ updateState(CHANNEL_ROOM_STATE, new DecimalType(unitStatus));
+ updateState(CHANNEL_ROOM_SWITCH, OnOffType.from(unitStatus == 1));
+ updateState(CHANNEL_ROOM_SCENE_A, OnOffType.from(unitStatus == 2));
+ updateState(CHANNEL_ROOM_SCENE_B, OnOffType.from(unitStatus == 3));
+ updateState(CHANNEL_ROOM_SCENE_C, OnOffType.from(unitStatus == 4));
+ updateState(CHANNEL_ROOM_SCENE_D, OnOffType.from(unitStatus == 5));
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/dimmable/UpbUnitHandler.java b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/dimmable/UpbUnitHandler.java
new file mode 100644
index 00000000000..2fda1ced90b
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/java/org/openhab/binding/omnilink/internal/handler/units/dimmable/UpbUnitHandler.java
@@ -0,0 +1,71 @@
+/**
+ * Copyright (c) 2010-2021 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.omnilink.internal.handler.units.dimmable;
+
+import static org.openhab.binding.omnilink.internal.OmnilinkBindingConstants.CHANNEL_UPB_STATUS;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.omnilink.internal.handler.OmniLinkCmd;
+import org.openhab.binding.omnilink.internal.handler.units.DimmableUnitHandler;
+import org.openhab.core.library.types.StringType;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.Thing;
+import org.openhab.core.types.Command;
+import org.openhab.core.types.RefreshType;
+import org.openhab.core.types.UnDefType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * The {@link UpbUnitHandler} defines some methods that are used to
+ * interface with an OmniLink UPB Unit. This by extension also defines the
+ * UPB Unit thing that openHAB will be able to pick up and interface with.
+ *
+ * @author Craig Hamilton - Initial contribution
+ * @author Ethan Dye - openHAB3 rewrite
+ */
+@NonNullByDefault
+public class UpbUnitHandler extends DimmableUnitHandler {
+ private final Logger logger = LoggerFactory.getLogger(UpbUnitHandler.class);
+ private final int thingID = getThingNumber();
+ public @Nullable String number;
+
+ public UpbUnitHandler(Thing thing) {
+ super(thing);
+ }
+
+ @Override
+ public void handleCommand(ChannelUID channelUID, Command command) {
+ logger.debug("handleCommand called for channel: {}, command: {}", channelUID, command);
+
+ if (command instanceof RefreshType) {
+ updateState(CHANNEL_UPB_STATUS, UnDefType.UNDEF);
+ return;
+ }
+
+ switch (channelUID.getId()) {
+ case CHANNEL_UPB_STATUS:
+ if (command instanceof StringType) {
+ sendOmnilinkCommand(OmniLinkCmd.CMD_UNIT_UPB_REQ_STATUS.getNumber(), 0, thingID);
+ updateState(CHANNEL_UPB_STATUS, UnDefType.UNDEF);
+ } else {
+ logger.debug("Invalid command: {}, must be StringType", command);
+ }
+ break;
+ default:
+ logger.debug("Unknown channel for UPB Unit thing: {}", channelUID);
+ super.handleCommand(channelUID, command);
+ }
+ }
+}
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/binding/binding.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/binding/binding.xml
new file mode 100644
index 00000000000..929d6ad5b3d
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/binding/binding.xml
@@ -0,0 +1,9 @@
+
+
+
+ OmniLink Binding
+ This is the binding for OmniLink, a security system that interfaces with many devices.
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/area.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/area.xml
new file mode 100644
index 00000000000..10475f6362b
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/area.xml
@@ -0,0 +1,163 @@
+
+
+
+
+
+
+
+
+ Omni Area
+ An Omni area configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Area Number
+ The area number.
+
+
+
+
+
+
+
+
+
+ Lumina Area
+ An Lumina area configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Area Number
+ The area number.
+
+
+
+
+
+
+ Switch
+ Area Alarm
+ Indicates if an alarm is active.
+ Alarm
+
+
+
+
+ Number
+ Security Mode
+ Represents the area security mode.
+ Alarm
+
+
+ Off
+ Day
+ Night
+ Away
+ Vacation
+ Day instant
+ Night delayed
+ Arming day
+ Arming night
+ Arming away
+ Arming vacation
+ Arming day instant
+ Arming night delayed
+
+
+
+
+
+ String
+ Security Command
+ Sends a 4 digit user code to activate the area command.
+ Alarm
+
+
+
+ Number
+ Activate Keypad Emergency
+ Activate a burglary, fire, or auxiliary keypad emergency alarm on Omni based models.
+ Alarm
+
+
+ Burglary
+ Fire
+ Auxiliary
+
+
+
+
+
+ Number
+ Security Mode
+ Represents the area security mode.
+ Alarm
+
+
+ Home
+ Sleep
+ Away
+ Vacation
+ Party
+ Special
+ Setting home
+ Setting sleep
+ Setting away
+ Setting vacation
+ Setting party
+ Setting special
+
+
+
+
+
+ trigger
+ All On/Off Event
+ Event sent when an all on/off event occurs.
+
+
+ Off
+ On
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/audio-source.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/audio-source.xml
new file mode 100644
index 00000000000..314ff9c3e8c
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/audio-source.xml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+ Audio Source
+ An audio source configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Audio Source Number
+ The audio source number.
+
+
+ Autostart Polling
+ Autostart polling of audio source on creation of thing.
+ true
+
+
+
+
+
+
+ String
+ Source Data
+ A line of metadata from this audio source.
+ Text
+
+
+
+ Switch
+ Audio Source Polling
+ Enable or disable polling of this audio source.
+ Switch
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/audio-zone.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/audio-zone.xml
new file mode 100644
index 00000000000..27cfce3666f
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/audio-zone.xml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+ Audio Zone
+ An audio zone configured in the controller.
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Audio Zone Number
+ The audio zone number.
+
+
+
+
+
+
+
+ Switch
+ Audio Zone Power
+ Power status of this audio zone.
+ Switch
+
+
+
+ Switch
+ Audio Zone Mute
+ Mute status of this audio zone.
+ Switch
+
+
+
+ Dimmer
+ Audio Zone Volume
+ Volume level of this audio zone.
+ Slider
+
+
+
+
+ Number
+ Source
+ Source for this audio zone.
+ MediaControl
+
+
+
+
+ Player
+ Control
+ Control the audio zone, e.g. start/stop/next/previous.
+ MediaControl
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/bridge.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/bridge.xml
new file mode 100644
index 00000000000..fef71b51c53
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/bridge.xml
@@ -0,0 +1,172 @@
+
+
+
+
+
+ OmniLink Controller
+ An OmniLink controller.
+
+
+
+ Console Beepers
+
+
+ Beep Consoles
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ network-address
+ IP or Host Name
+ The IP or host name of the controller.
+
+
+ Port
+ The port of the controller.
+ 4369
+
+
+ Key 1
+ The first network encription key.
+
+
+ Key 2
+ The second network encription key.
+
+
+ Log Polling Interval
+ The interval to poll for new log messages on the controller.
+ 1
+
+
+
+
+
+
+
+ DateTime
+ Date/Time
+ Set controller date/time.
+ Time
+
+
+
+
+ String
+ Last Log Entry
+ Last log message on the controller, represented in JSON.
+ Text
+
+
+
+ trigger
+ UPB Link
+ Event sent when a UPB link is activated.
+
+
+
+ trigger
+ UPB Link
+ Event sent when a UPB link is deactivated.
+
+
+
+ trigger
+ Phone Line Event
+ Event sent when the phone line changes state.
+
+
+ On Hook
+ Off Hook
+ Dead
+ Ring
+
+
+
+
+
+ trigger
+ AC Power Event
+ Event sent when AC trouble conditions are detected.
+
+
+ Off
+ Restored
+
+
+
+
+
+ trigger
+ Battery Event
+ Event sent when battery trouble conditions are detected.
+
+
+ Low
+ OK
+
+
+
+
+
+ trigger
+ DCM Event
+ Event sent when digital communicator trouble conditions are detected.
+
+
+ Trouble
+ OK
+
+
+
+
+
+ trigger
+ Energy Cost Event
+ Event sent when the cost of energy changes.
+
+
+ Trouble
+ Mid
+ High
+ Critical
+
+
+
+
+
+ trigger
+ Camera Trigger Event
+ Event sent when a camera trigger is detected.
+
+
+ Camera 1
+ Camera 2
+ Camera 3
+ Camera 4
+ Camera 5
+ Camera 6
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/button.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/button.xml
new file mode 100644
index 00000000000..3b5364f1108
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/button.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+ Button
+ A button configured in the controller.
+
+
+
+
+
+
+
+
+ number
+
+
+ Button Number
+ The button number.
+
+
+
+
+
+
+ Switch
+ Button Press
+ Sends a button event to the controller.
+ Switch
+
+
+
+ trigger
+ Button Activated
+ Event sent when a button is activated.
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/console.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/console.xml
new file mode 100644
index 00000000000..71435771237
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/console.xml
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+ Console
+ A console configured in the controller.
+
+
+
+
+
+
+ Console Number
+ The console number.
+
+
+
+
+
+
+ String
+ Enable/Disable Console Beeper
+ Enable/Disable the beeper for this/all console(s).
+ Switch
+
+
+ Off
+ On
+
+
+
+
+
+ Number
+ Beep Console
+ Send a beep command to this/all console(s).
+ SoundVolume
+
+
+ Off
+ Indefinitely
+ 1 time
+ 2 times
+ 3 times
+ 4 times
+ 5 times
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/humidity-sensor.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/humidity-sensor.xml
new file mode 100644
index 00000000000..af126b62f83
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/humidity-sensor.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+ Humidity Sensor
+ A humidity sensor configured in the controller.
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Humidity Sensor Number
+ The humidity sensor number.
+
+
+
+
+
+
+ Number:Dimensionless
+ Humidity
+ The current relative humidity at this humidity sensor.
+ Humidity
+
+
+
+
+ Number:Dimensionless
+ Low SetPoint
+ The current low setpoint for this humidity sensor.
+ Humidity
+
+
+
+
+ Number:Dimensionless
+ High SetPoint
+ The current high setpoint for this humidity sensor.
+ Humidity
+
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/lock.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/lock.xml
new file mode 100644
index 00000000000..8364b84914f
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/lock.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+ Lock
+ An access control reader lock configured in the controller.
+
+
+
+
+
+
+
+ number
+
+
+ Lock Number
+ The lock number.
+
+
+
+
+
+
+ Switch
+ Lock/Unlock
+ Lock or unlock this lock.
+ Switch
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/temp-sensor.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/temp-sensor.xml
new file mode 100644
index 00000000000..a7fd4ec8a48
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/temp-sensor.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+ Temperature Sensor
+ A temperature sensor configured in the controller.
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Temperature Sensor Number
+ The temperature sensor number.
+
+
+
+
+
+
+ Number:Temperature
+ Temperature
+ The current temperature at this temperature sensor.
+ Temperature
+
+
+
+
+ Number:Temperature
+ Low SetPoint
+ The current low setpoint of this temperature sensor.
+ Temperature
+
+
+
+
+ Number:Temperature
+ High SetPoint
+ The current high setpoint of this temperature sensor.
+ Temperature
+
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/thermostat.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/thermostat.xml
new file mode 100644
index 00000000000..1504f62a190
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/thermostat.xml
@@ -0,0 +1,175 @@
+
+
+
+
+
+
+
+
+ Thermostat
+ A thermostat configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Thermostat Number
+ The thermostat number.
+
+
+
+
+
+
+ Contact
+ Thermostat Freeze Alarm
+ Closed when freeze alarm is triggered by this thermostat.
+ Alarm
+
+
+
+
+ Contact
+ Thermostat Communications Failure
+ Closed during a communications failure with this thermostat.
+ Contact
+
+
+
+
+ Number
+ Thermostat Status
+ The current status of this thermostat.
+ Heating
+
+
+ Idle
+ Heating
+ Cooling
+ Humidifying
+ Dehumidifying
+
+
+
+
+
+ Number:Temperature
+ Temperature
+ The current temperature at this thermostat.
+ Temperature
+
+
+
+
+ Number:Temperature
+ Outdoor Temperature
+ The current outdoor temperature detected by this thermostat.
+ Temperature
+
+
+
+
+ Number:Temperature
+ Heat SetPoint
+ The current low/heating setpoint of this thermostat.
+ Temperature
+
+
+
+
+ Number:Temperature
+ Cool SetPoint
+ The current high/cooling setpoint of this thermostat.
+ Temperature
+
+
+
+
+ Number:Dimensionless
+ Humidity
+ The relative humidity at this thermostat.
+ Humidity
+
+
+
+
+ Number:Dimensionless
+ Humidify SetPoint
+ The current low/humidify setpoint for this thermostat.
+ Humidity
+
+
+
+
+ Number:Dimensionless
+ Dehumidify SetPoint
+ The current high/dehumidify setpoint for this thermostat.
+ Humidity
+
+
+
+
+ Number
+ System Mode
+ The current system mode of this thermostat.
+ Heating
+
+
+ Off
+ Heat
+ Cool
+ Auto
+ Emergency heat
+
+
+
+
+
+ Number
+ Fan Mode
+ The current fan mode of this thermostat.
+ Flow
+
+
+ Auto
+ On
+ Cycle
+
+
+
+
+
+ Number
+ Hold Status
+ The current hold status of this thermostat.
+ Heating
+
+
+ Off
+ Hold
+ Vacation hold
+
+
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/unit.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/unit.xml
new file mode 100644
index 00000000000..bc96c6e8f30
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/unit.xml
@@ -0,0 +1,323 @@
+
+
+
+
+
+
+
+
+ Unit
+ A basic unit configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Unit Number
+ The unit number.
+
+
+
+
+
+
+
+
+
+ Dimmable Unit
+ A dimmable unit configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Dimmable Unit Number
+ The dimmable unit number.
+
+
+
+
+
+
+
+
+
+ UPB Unit
+ A UPB unit configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ UPB Unit Number
+ The UPB unit number.
+
+
+
+
+
+
+
+
+
+ Flag
+ A flag configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Flag Number
+ The flag number.
+
+
+
+
+
+
+
+
+
+ Voltage Output
+ A voltage output configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Voltage Output Number
+ The voltage output number.
+
+
+
+
+
+
+
+
+
+ Room
+ A room configured in the controller.
+
+
+
+ Scene A
+
+
+ Scene B
+
+
+ Scene C
+
+
+ Scene D
+
+
+
+
+
+
+
+ number
+
+
+ Room Number
+ The room number.
+
+
+
+
+
+
+ Dimmer
+ Unit Level
+ Increase/Decrease the level of this unit.
+ Slider
+
+
+
+
+ Switch
+ Switch
+ Turn this unit on/off.
+ Switch
+
+
+
+ Number
+ On for Seconds
+ Turn on this unit for a specified number of seconds.
+ Switch
+
+
+
+
+ Number
+ Off for Seconds
+ Turn off this unit for a specified number of seconds.
+ Switch
+
+
+
+
+ Number
+ On for Minutes
+ Turn on this unit for a specified number of minutes.
+ Switch
+
+
+
+
+ Number
+ Off for Minutes
+ Turn off this unit for a specified number of minutes.
+ Switch
+
+
+
+
+ Number
+ On for Hours
+ Turn on this unit for a specified number of hours.
+ Switch
+
+
+
+
+ Number
+ Off for Hours
+ Turn off this unit for a specified number of hours.
+ Switch
+
+
+
+
+ String
+ UPB Status
+ Send a UPB status request message for this unit to the controller.
+ Status
+
+
+ Get Status
+
+
+
+
+
+ Switch
+ Switch
+ Turn this room on/off.
+ Switch
+
+
+
+ Switch
+ Scene Toggle
+ Turn this scene on/off.
+ Switch
+
+
+
+ Number
+ State
+ The current state of this room.
+ Switch
+
+
+ Off
+ On
+ Scene A
+ Scene B
+ Scene C
+ Scene D
+
+
+
+
+
+ Number
+ Flag Value
+ Numeric value of this flag.
+ Number
+
+
+
+
+ Switch
+ Flag Switch
+ Turn this flag on/off.
+ Switch
+
+
+
+ trigger
+ Switch Press Event
+ Event sent when an ALC, UPB, Radio RA, or Starlite switch is pressed.
+
+
+
diff --git a/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/zone.xml b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/zone.xml
new file mode 100644
index 00000000000..48f09b16241
--- /dev/null
+++ b/bundles/org.openhab.binding.omnilink/src/main/resources/OH-INF/thing/zone.xml
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+ Zone
+ A zone configured in the controller.
+
+
+
+
+
+
+
+
+
+
+
+
+ number
+
+
+ Zone Number
+ The zone number.
+
+
+
+
+
+
+ Contact
+ Contact State
+ Contact state information of this zone.
+ Contact
+
+
+
+
+ Number
+ Current Condition
+ Current condition of this zone.
+ Contact
+
+
+ Secure
+ Not Ready
+ Trouble
+
+
+
+
+
+ Number
+ Latched Alarm Status
+ Latched alarm status of this zone.
+ Contact
+
+
+ Secure
+ Tripped
+ Reset, but previously tripped
+
+
+
+
+
+ Number
+ Arming Status
+ Arming status of this zone.
+ Contact
+
+
+ Disarmed
+ Armed
+ Bypassed by user
+ Bypassed by system
+
+
+
+
+
+ String
+ Bypass Zone
+ Send a 4 digit user code to bypass this zone.
+ Alarm
+
+
+
+ String
+ Restore Zone
+ Send a 4 digit user code to restore this zone.
+ Alarm
+
+
+
diff --git a/bundles/pom.xml b/bundles/pom.xml
index f557f63b034..6df37e99c4b 100644
--- a/bundles/pom.xml
+++ b/bundles/pom.xml
@@ -216,6 +216,7 @@
org.openhab.binding.oceanic
org.openhab.binding.ojelectronics
org.openhab.binding.omnikinverter
+ org.openhab.binding.omnilink
org.openhab.binding.onebusaway
org.openhab.binding.onewiregpio
org.openhab.binding.onewire