/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
<artifactId>org.openhab.binding.omnikinverter</artifactId>
<version>${project.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.binding.omnilink</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<dependency>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.binding.onebusaway</artifactId>
--- /dev/null
+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
--- /dev/null
+# 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]" <alarm> {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]" <switch> {channel="omnilink:upb:home:UpKitTable:level"}
+Dimmer UpOfcDesk "Desk Lights [%d]" <slider> {channel="omnilink:upb:home:UpOfcDesk:level"}
+
+/*
+ * Thermostat
+ */
+Group UpstrsThermo "Upstairs Thermostat"
+Number:Temperature UpstrsThermo_Temp "Temperature [%.1f %unit%]" <temperature> (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:temperature"}
+Number UpstrsThermo_Status "Status [MAP(therm-status.map):%s]" <heating> (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:status"}
+Number UpstrsThermo_System "System Mode [MAP(therm-tempmode.map):%s]" <temperature> (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:system_mode"}
+Number UpstrsThermo_Fan "Fan Mode [MAP(therm-fanmode.map):%s]" <fan> (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:fan_mode"}
+Number UpstrsThermo_Hold "Hold Mode [MAP(therm-holdmode.map):%s]" <fan> (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:hold_mode"}
+Number UpstrsThermo_HeatPoint "System HeatPoint [%d]" <temperature_hot> (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:heat_setpoint"}
+Number UpstrsThermo_CoolPoint "System CoolPoint [%d]" <temperature_cool> (UpstrsThermo) {channel="omnilink:thermostat:home:UpstrsThermo:cool_setpoint"}
+
+/*
+ * Motion and Doors
+ */
+Group:Contact:OR(OPEN, CLOSED) Doors "All Doors [%s]"
+Contact FrontDoor "Front Door" <door> (Doors) {channel="omnilink:zone:home:FrontDoor:contact"}
+Contact GarageDoor "Garage Door" <door> (Doors) {channel="omnilink:zone:home:GarageDoor:contact"}
+Contact BackDoor "Back Door" <door> (Doors) {channel="omnilink:zone:home:BackDoor:contact"}
+Contact BsmtBackDoor "Back Door" <door> (Doors) {channel="omnilink:zone:home:BsmtBackDoor:contact"}
+Contact MBRDeckDoor "Deck Door" <door> (Doors) {channel="omnilink:zone:home:MBRDeckDoor:contact"}
+Contact PorchDoor "Porch Door" <door> (Doors) {channel="omnilink:zone:home:PorchDoor:contact"}
+
+Group:Contact:OR(OPEN, CLOSED) GarageDoors "All Garage Doors [%s]"
+Contact TwoCarGarageDo "Two Car Garage Door" <garagedoor> (GarageDoors) {channel="omnilink:zone:home:TwoCarGarageDo:contact"}
+Contact OneCarGarageDo "One Car Garage Door" <garagedoor> (GarageDoors) {channel="omnilink:zone:home:OneCarGarageDo:contact"}
+
+Group:Contact:OR(OPEN, CLOSED) Motion "All Motion Sensors [%s]"
+Contact MBRMotion "Motion" <presence> (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:MBRMotion:contact"}
+Contact UpOffMotion "Motion" <presence> (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:UpOffMotion:contact"}
+Contact UpLivMotion "Motion" <presence> (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:UpLivMotion:contact"}
+Contact BsmtWORMotion "Motion" <presence> (Motion) {homekit="MotionSensor", channel="omnilink:zone:home:BsmtWORMotion:contact"}
+Contact GarageMotion "Motion" <presence> (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]" <switch> {channel="omnilink:button:home:MainButton:press"}
+
+/*
+ * Other OmniPro items
+ */
+DateTime OmniProTime "Last Time Update [%1$ta %1$tR]" <time> {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
+```
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.openhab.addons.bundles</groupId>
+ <artifactId>org.openhab.addons.reactor.bundles</artifactId>
+ <version>3.1.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>org.openhab.binding.omnilink</artifactId>
+
+ <name>openHAB Add-ons :: Bundles :: OmniLink Binding</name>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.github.digitaldan</groupId>
+ <artifactId>jomnilink</artifactId>
+ <version>1.4.0</version>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+
+</project>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+
+ 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
+
+-->
+<features name="org.openhab.binding.omnilink-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
+ <repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
+
+ <feature name="openhab-binding-omnilink" description="OmniLink Binding" version="${project.version}">
+ <feature>openhab-runtime-base</feature>
+ <bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.omnilink/${project.version}</bundle>
+ </feature>
+</features>
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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<AudioPlayer> getAudioPlayerForFeatureCode(int featureCode) {
+ return Arrays.stream(values()).filter(v -> v.featureCode == featureCode).findAny();
+ }
+}
--- /dev/null
+/**
+ * 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<ThingTypeUID> 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);
+}
--- /dev/null
+/**
+ * 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;
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<Integer> 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);
+ }
+}
--- /dev/null
+/**
+ * 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;
+ }
+}
--- /dev/null
+/**
+ * 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 <T>
+ */
+@NonNullByDefault
+public class ObjectPropertyRequest<T extends ObjectProperties> implements Iterable<T> {
+ private final Logger logger = LoggerFactory.getLogger(ObjectPropertyRequest.class);
+
+ public static <T extends ObjectProperties, U extends ObjectPropertyRequests<T>> Builder<T> builder(
+ OmnilinkBridgeHandler bridgeHandler, U request, int objectNumber, int offset) {
+ return new Builder<>(bridgeHandler, request, objectNumber, offset);
+ }
+
+ private final OmnilinkBridgeHandler bridgeHandler;
+ private final ObjectPropertyRequests<T> 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<T> 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<T> iterator() {
+ List<T> messages = new ArrayList<T>();
+ 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<T extends ObjectProperties> {
+ private final OmnilinkBridgeHandler bridgeHandler;
+ private final ObjectPropertyRequests<T> 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<T> request, int objectNumber,
+ int offset) {
+ this.bridgeHandler = bridgeHandler;
+ this.request = request;
+ this.objectNumber = objectNumber;
+ this.offset = offset;
+ }
+
+ public Builder<T> selectNamed() {
+ this.filter1 = ObjectProperties.FILTER_1_NAMED;
+ return this;
+ }
+
+ public Builder<T> areaFilter(int area) {
+ this.filter2 = area;
+ return this;
+ }
+
+ public Builder<T> selectAnyLoad() {
+ this.filter3 = ObjectProperties.FILTER_3_ANY_LOAD;
+ return this;
+ }
+
+ public ObjectPropertyRequest<T> build() {
+ return new ObjectPropertyRequest<T>(bridgeHandler, request, objectNumber, filter1, filter2, filter3,
+ offset);
+ }
+ }
+}
--- /dev/null
+/**
+ * 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 <T>
+ */
+@NonNullByDefault
+public class ObjectPropertyRequests<T extends ObjectProperties> {
+
+ public static final ObjectPropertyRequests<ThermostatProperties> THERMOSTAT = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_THERMO, ThermostatProperties.class);
+
+ public static final ObjectPropertyRequests<ButtonProperties> BUTTONS = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_BUTTON, ButtonProperties.class);
+
+ public static final ObjectPropertyRequests<AreaProperties> AREA = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_AREA, AreaProperties.class);
+
+ public static final ObjectPropertyRequests<ZoneProperties> ZONE = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_ZONE, ZoneProperties.class);
+
+ public static final ObjectPropertyRequests<UnitProperties> UNIT = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_UNIT, UnitProperties.class);
+
+ public static final ObjectPropertyRequests<AudioZoneProperties> AUDIO_ZONE = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_AUDIO_ZONE, AudioZoneProperties.class);
+
+ public static final ObjectPropertyRequests<AudioSourceProperties> AUDIO_SOURCE = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_AUDIO_SOURCE, AudioSourceProperties.class);
+
+ public static final ObjectPropertyRequests<AuxSensorProperties> AUX_SENSORS = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_AUX_SENSOR, AuxSensorProperties.class);
+
+ public static final ObjectPropertyRequests<AccessControlReaderProperties> LOCK = new ObjectPropertyRequests<>(
+ Message.OBJ_TYPE_CONTROL_READER, AccessControlReaderProperties.class);
+
+ private final int propertyRequest;
+ private final Class<T> responseType;
+
+ private ObjectPropertyRequests(int propertyRequest, Class<T> type) {
+ this.propertyRequest = propertyRequest;
+ this.responseType = type;
+ }
+
+ public int getPropertyRequest() {
+ return propertyRequest;
+ }
+
+ public Class<T> getResponseType() {
+ return responseType;
+ }
+}
--- /dev/null
+/**
+ * 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<AreaProperties> 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<AreaProperties> areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<ButtonProperties> 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<String, Object> 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<AccessControlReaderProperties> 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<String, Object> 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<AudioZoneProperties> 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<String, Object> 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<AudioSourceProperties> 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<String, Object> 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<AreaProperties> areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<AuxSensorProperties> 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<String, Object> 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<AreaProperties> areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<AuxSensorProperties> 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<String, Object> 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<AreaProperties> areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<ThermostatProperties> 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<String, Object> 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<AreaProperties> discoverAreas() {
+ final OmnilinkBridgeHandler handler = bridgeHandler;
+ if (handler != null) {
+ final ThingUID bridgeUID = handler.getThing().getUID();
+ List<AreaProperties> areas = new LinkedList<>();
+
+ ObjectPropertyRequest<AreaProperties> 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<String, Object> 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<AreaProperties> areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<UnitProperties> 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<String, Object> 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<AreaProperties> areas = this.areas;
+
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<ZoneProperties> 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<String, Object> 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);
+ }
+ }
+ }
+ }
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<ExtendedAreaStatus> {
+ 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<AreaProperties> areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ String thingName = areaProperties.getName();
+ if (areaProperties.getNumber() == 1 && "".equals(thingName)) {
+ thingName = "Main Area";
+ }
+ Map<String, String> 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<AreaAlarm> 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<ExtendedAreaStatus> 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<AreaProperties> getAreaProperties() {
+ final OmnilinkBridgeHandler bridgeHandler = getOmnilinkBridgeHandler();
+ List<AreaProperties> areas = new LinkedList<>();
+
+ if (bridgeHandler != null) {
+ ObjectPropertyRequest<AreaProperties> 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();
+ }
+}
--- /dev/null
+/**
+ * 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<T extends Status> extends AbstractOmnilinkHandler {
+ public AbstractOmnilinkStatusHandler(Thing thing) {
+ super(thing);
+ }
+
+ private volatile Optional<T> 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<T> 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!"));
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<AudioSourceProperties> objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AUDIO_SOURCE, thingID, 0).selectNamed().build();
+
+ for (AudioSourceProperties audioSourceProperties : objectPropertyRequest) {
+ Map<String, String> 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());
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<ExtendedAudioZoneStatus> {
+ 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<AudioZoneProperties> objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AUDIO_ZONE, thingID, 0).selectNamed().build();
+
+ for (AudioZoneProperties audioZoneProperties : objectPropertyRequest) {
+ Map<String, String> 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> 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> 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<ExtendedAudioZoneStatus> 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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<AreaProperties> areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<ButtonProperties> objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.BUTTONS, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (ButtonProperties buttonProperties : objectPropertyRequest) {
+ Map<String, String> 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);
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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<ExtendedAuxSensorStatus> {
+ 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<AreaProperties> areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<AuxSensorProperties> objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AUX_SENSORS, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (AuxSensorProperties auxSensorProperties : objectPropertyRequest) {
+ Map<String, String> 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<Dimensionless>) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_AUX_HIGH_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(),
+ TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType<Dimensionless>) 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<ExtendedAuxSensorStatus> 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<ExtendedAccessControlReaderLockStatus> {
+ 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<AccessControlReaderProperties> objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.LOCK, thingID, 0).selectNamed().build();
+
+ for (AccessControlReaderProperties lockProperties : objectPropertyRequest) {
+ Map<String, String> 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<ExtendedAccessControlReaderLockStatus> 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<AreaAlarm> 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<AreaAlarm> getAlarms() {
+ return LUMINA_ALARMS;
+ }
+}
--- /dev/null
+/**
+ * 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<AreaAlarm> 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<AreaAlarm> getAlarms() {
+ return OMNI_ALARMS;
+ }
+}
--- /dev/null
+/**
+ * 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();
+ }
+}
--- /dev/null
+/**
+ * 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> 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<Class<? extends ThingHandlerService>> 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<Thing> 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<Thing> 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<Thing> 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<Thing> 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<Thing> 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<Thing> 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<Thing> tempThing = getChildThing(THING_TYPE_TEMP_SENSOR, auxSensorNumber);
+ Optional<Thing> 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<Thing> buttonThing = getChildThing(THING_TYPE_BUTTON,
+ ((ButtonEvent) event).getButtonNumber());
+ buttonThing.map(Thing::getHandler)
+ .ifPresent(theHandler -> ((ButtonHandler) theHandler).buttonActivated());
+ break;
+ case ALL_ON_OFF:
+ Optional<Thing> 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<Thing> 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<TemperatureFormat> 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<Thing> 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<Thing> getUnitThing(int unitId) {
+ Optional<Thing> 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<AudioPlayer> 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<String, String> 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!");
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<ExtendedAuxSensorStatus> {
+ 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<AreaProperties> areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<AuxSensorProperties> objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.AUX_SENSORS, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (AuxSensorProperties auxSensorProperties : objectPropertyRequest) {
+ Map<String, String> 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> 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<Temperature>) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_AUX_HIGH_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(),
+ temperatureFormat.get().formatToOmni(((QuantityType<Temperature>) 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> 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<ExtendedAuxSensorStatus> 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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!");
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<ExtendedThermostatStatus> {
+ 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<AreaProperties> areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<ThermostatProperties> objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.THERMOSTAT, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).build();
+
+ for (ThermostatProperties thermostatProperties : objectPropertyRequest) {
+ Map<String, String> 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> 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<Temperature>) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_THERMO_COOL_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_COOL_HIGH_POINT.getNumber(),
+ temperatureFormat.get().formatToOmni(((QuantityType<Temperature>) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_THERMO_HUMIDIFY_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_HUMDIFY_POINT.getNumber(),
+ TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType<Dimensionless>) command).intValue()),
+ thingID);
+ break;
+ case CHANNEL_THERMO_DEHUMIDIFY_SETPOINT:
+ sendOmnilinkCommand(OmniLinkCmd.CMD_THERMO_SET_DEHUMIDIFY_POINT.getNumber(),
+ TemperatureFormat.FAHRENHEIT.formatToOmni(((QuantityType<Dimensionless>) 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> 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<ExtendedThermostatStatus> 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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<ExtendedUnitStatus> {
+ 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<AreaProperties> areas = getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<UnitProperties> objectPropertyRequest = ObjectPropertyRequest
+ .builder(bridgeHandler, ObjectPropertyRequests.UNIT, thingID, 0).selectNamed()
+ .areaFilter(areaFilter).selectAnyLoad().build();
+
+ for (UnitProperties unitProperties : objectPropertyRequest) {
+ Map<String, String> 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<ExtendedUnitStatus> 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()));
+ }
+}
--- /dev/null
+/**
+ * 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<ExtendedZoneStatus> {
+ 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<AreaProperties> areas = super.getAreaProperties();
+ if (areas != null) {
+ for (AreaProperties areaProperties : areas) {
+ int areaFilter = super.bitFilterForArea(areaProperties);
+
+ ObjectPropertyRequest<ZoneProperties> 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<String, String> 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<ExtendedZoneStatus> 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();
+ }
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+}
--- /dev/null
+/**
+ * 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));
+ }
+}
--- /dev/null
+/**
+ * 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);
+ }
+ }
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<binding:binding id="omnilink" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
+
+ <name>OmniLink Binding</name>
+ <description>This is the binding for OmniLink, a security system that interfaces with many devices.</description>
+
+</binding:binding>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Omni Area Thing -->
+ <thing-type id="area">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Omni Area</label>
+ <description>An Omni area configured in the controller.</description>
+ <channels>
+ <channel id="activate_keypad_emergency" typeId="omni_activate_keypad_emergency"/>
+ <channel id="alarm_burglary" typeId="area_alarm"/>
+ <channel id="alarm_fire" typeId="area_alarm"/>
+ <channel id="alarm_gas" typeId="area_alarm"/>
+ <channel id="alarm_auxiliary" typeId="area_alarm"/>
+ <channel id="alarm_freeze" typeId="area_alarm"/>
+ <channel id="alarm_water" typeId="area_alarm"/>
+ <channel id="alarm_duress" typeId="area_alarm"/>
+ <channel id="alarm_temperature" typeId="area_alarm"/>
+ <channel id="mode" typeId="omni_area_mode"/>
+ <channel id="disarm" typeId="area_command"/>
+ <channel id="day" typeId="area_command"/>
+ <channel id="night" typeId="area_command"/>
+ <channel id="away" typeId="area_command"/>
+ <channel id="vacation" typeId="area_command"/>
+ <channel id="day_instant" typeId="area_command"/>
+ <channel id="night_delayed" typeId="area_command"/>
+ <channel id="all_on_off_event" typeId="all_on_off_event"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Area Number</label>
+ <description>The area number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Lumina Area Thing -->
+ <thing-type id="lumina_area">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Lumina Area</label>
+ <description>An Lumina area configured in the controller.</description>
+ <channels>
+ <channel id="mode" typeId="lumina_area_mode"/>
+ <channel id="home" typeId="area_command"/>
+ <channel id="sleep" typeId="area_command"/>
+ <channel id="away" typeId="area_command"/>
+ <channel id="vacation" typeId="area_command"/>
+ <channel id="party" typeId="area_command"/>
+ <channel id="special" typeId="area_command"/>
+ <channel id="all_on_off_event" typeId="all_on_off_event"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ </properties>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Area Number</label>
+ <description>The area number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Area channels -->
+ <channel-type id="area_alarm">
+ <item-type>Switch</item-type>
+ <label>Area Alarm</label>
+ <description>Indicates if an alarm is active.</description>
+ <category>Alarm</category>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="omni_area_mode">
+ <item-type>Number</item-type>
+ <label>Security Mode</label>
+ <description>Represents the area security mode.</description>
+ <category>Alarm</category>
+ <state readOnly="true">
+ <options>
+ <option value="0">Off</option>
+ <option value="1">Day</option>
+ <option value="2">Night</option>
+ <option value="3">Away</option>
+ <option value="4">Vacation</option>
+ <option value="5">Day instant</option>
+ <option value="6">Night delayed</option>
+ <option value="9">Arming day</option>
+ <option value="10">Arming night</option>
+ <option value="11">Arming away</option>
+ <option value="12">Arming vacation</option>
+ <option value="13">Arming day instant</option>
+ <option value="14">Arming night delayed</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="area_command">
+ <item-type>String</item-type>
+ <label>Security Command</label>
+ <description>Sends a 4 digit user code to activate the area command.</description>
+ <category>Alarm</category>
+ </channel-type>
+
+ <channel-type id="omni_activate_keypad_emergency">
+ <item-type>Number</item-type>
+ <label>Activate Keypad Emergency</label>
+ <description>Activate a burglary, fire, or auxiliary keypad emergency alarm on Omni based models.</description>
+ <category>Alarm</category>
+ <state>
+ <options>
+ <option value="1">Burglary</option>
+ <option value="2">Fire</option>
+ <option value="3">Auxiliary</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="lumina_area_mode">
+ <item-type>Number</item-type>
+ <label>Security Mode</label>
+ <description>Represents the area security mode.</description>
+ <category>Alarm</category>
+ <state readOnly="true">
+ <options>
+ <option value="1">Home</option>
+ <option value="2">Sleep</option>
+ <option value="3">Away</option>
+ <option value="4">Vacation</option>
+ <option value="5">Party</option>
+ <option value="6">Special</option>
+ <option value="9">Setting home</option>
+ <option value="10">Setting sleep</option>
+ <option value="11">Setting away</option>
+ <option value="12">Setting vacation</option>
+ <option value="13">Setting party</option>
+ <option value="14">Setting special</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="all_on_off_event">
+ <kind>trigger</kind>
+ <label>All On/Off Event</label>
+ <description>Event sent when an all on/off event occurs.</description>
+ <event>
+ <options>
+ <option value="OFF">Off</option>
+ <option value="ON">On</option>
+ </options>
+ </event>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Audio Source Thing -->
+ <thing-type id="audio_source">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Audio Source</label>
+ <description>An audio source configured in the controller.</description>
+ <channels>
+ <channel id="source_text_1" typeId="audio_source_text"/>
+ <channel id="source_text_2" typeId="audio_source_text"/>
+ <channel id="source_text_3" typeId="audio_source_text"/>
+ <channel id="source_text_4" typeId="audio_source_text"/>
+ <channel id="source_text_5" typeId="audio_source_text"/>
+ <channel id="source_text_6" typeId="audio_source_text"/>
+ <channel id="polling" typeId="audio_source_polling"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Audio Source Number</label>
+ <description>The audio source number.</description>
+ </parameter>
+ <parameter name="autostart" type="boolean" required="false">
+ <label>Autostart Polling</label>
+ <description>Autostart polling of audio source on creation of thing.</description>
+ <default>true</default>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Audio Source Channels -->
+ <channel-type id="audio_source_text">
+ <item-type>String</item-type>
+ <label>Source Data</label>
+ <description>A line of metadata from this audio source.</description>
+ <category>Text</category>
+ </channel-type>
+
+ <channel-type id="audio_source_polling">
+ <item-type>Switch</item-type>
+ <label>Audio Source Polling</label>
+ <description>Enable or disable polling of this audio source.</description>
+ <category>Switch</category>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Audio Zone Thing -->
+ <thing-type id="audio_zone">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Audio Zone</label>
+ <description>An audio zone configured in the controller.</description>
+ <channels>
+ <channel id="zone_power" typeId="audio_zone_power"/>
+ <channel id="zone_mute" typeId="audio_zone_mute"/>
+ <channel id="zone_volume" typeId="audio_zone_volume"/>
+ <channel id="zone_source" typeId="audio_zone_source"/>
+ <channel id="zone_control" typeId="audio_zone_control"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Audio Zone Number</label>
+ <description>The audio zone number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+
+ <!-- Audio Zone Channels -->
+ <channel-type id="audio_zone_power">
+ <item-type>Switch</item-type>
+ <label>Audio Zone Power</label>
+ <description>Power status of this audio zone.</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="audio_zone_mute">
+ <item-type>Switch</item-type>
+ <label>Audio Zone Mute</label>
+ <description>Mute status of this audio zone.</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="audio_zone_volume">
+ <item-type>Dimmer</item-type>
+ <label>Audio Zone Volume</label>
+ <description>Volume level of this audio zone.</description>
+ <category>Slider</category>
+ <state min="0" max="100"/>
+ </channel-type>
+
+ <channel-type id="audio_zone_source">
+ <item-type>Number</item-type>
+ <label>Source</label>
+ <description>Source for this audio zone.</description>
+ <category>MediaControl</category>
+ <state min="1" max="100"/>
+ </channel-type>
+
+ <channel-type id="audio_zone_control">
+ <item-type>Player</item-type>
+ <label>Control</label>
+ <description>Control the audio zone, e.g. start/stop/next/previous.</description>
+ <category>MediaControl</category>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- OmniLink Controller Bridge -->
+ <bridge-type id="controller">
+ <label>OmniLink Controller</label>
+ <description>An OmniLink controller.</description>
+ <channels>
+ <channel id="sysdate" typeId="sysDate"/>
+ <channel id="enable_disable_beeper" typeId="console_enable_disable_beeper">
+ <label>Console Beepers</label>
+ </channel>
+ <channel id="beep" typeId="console_beep">
+ <label>Beep Consoles</label>
+ </channel>
+ <channel id="last_log" typeId="last_log"/>
+ <channel id="phone_line_event" typeId="phone_line_event"/>
+ <channel id="ac_power_event" typeId="ac_power_event"/>
+ <channel id="battery_event" typeId="battery_event"/>
+ <channel id="dcm_event" typeId="dcm_event"/>
+ <channel id="energy_cost_event" typeId="energy_cost_event"/>
+ <channel id="camera_trigger_event" typeId="camera_trigger_event"/>
+ <channel id="upb_link_activated_event" typeId="upb_link_activated_event"/>
+ <channel id="upb_link_deactivated_event" typeId="upb_link_deactivated_event"/>
+ </channels>
+ <properties>
+ <property name="model number"/>
+ <property name="major version"/>
+ <property name="minor version"/>
+ <property name="revision"/>
+ <property name="phone number"/>
+ </properties>
+ <config-description>
+ <parameter name="ipAddress" type="text" required="true">
+ <context>network-address</context>
+ <label>IP or Host Name</label>
+ <description>The IP or host name of the controller.</description>
+ </parameter>
+ <parameter name="port" type="integer" required="true">
+ <label>Port</label>
+ <description>The port of the controller.</description>
+ <default>4369</default>
+ </parameter>
+ <parameter name="key1" type="text" required="true">
+ <label>Key 1</label>
+ <description>The first network encription key.</description>
+ </parameter>
+ <parameter name="key2" type="text" required="true">
+ <label>Key 2</label>
+ <description>The second network encription key.</description>
+ </parameter>
+ <parameter name="logPollingInterval" type="integer" required="true">
+ <label>Log Polling Interval</label>
+ <description>The interval to poll for new log messages on the controller.</description>
+ <default>1</default>
+ </parameter>
+ </config-description>
+ </bridge-type>
+
+
+ <!-- Controller Channels -->
+ <channel-type id="sysDate">
+ <item-type>DateTime</item-type>
+ <label>Date/Time</label>
+ <description>Set controller date/time.</description>
+ <category>Time</category>
+ <state pattern="%1$tY-%1$tm-%1$td %1$tH:%1$tM:%1$tS"/>
+ </channel-type>
+
+ <channel-type id="last_log">
+ <item-type>String</item-type>
+ <label>Last Log Entry</label>
+ <description>Last log message on the controller, represented in JSON.</description>
+ <category>Text</category>
+ </channel-type>
+
+ <channel-type id="upb_link_activated_event">
+ <kind>trigger</kind>
+ <label>UPB Link</label>
+ <description>Event sent when a UPB link is activated.</description>
+ </channel-type>
+
+ <channel-type id="upb_link_deactivated_event">
+ <kind>trigger</kind>
+ <label>UPB Link</label>
+ <description>Event sent when a UPB link is deactivated.</description>
+ </channel-type>
+
+ <channel-type id="phone_line_event">
+ <kind>trigger</kind>
+ <label>Phone Line Event</label>
+ <description>Event sent when the phone line changes state.</description>
+ <event>
+ <options>
+ <option value="ON_HOOK">On Hook</option>
+ <option value="OFF_HOOK">Off Hook</option>
+ <option value="DEAD">Dead</option>
+ <option value="RING">Ring</option>
+ </options>
+ </event>
+ </channel-type>
+
+ <channel-type id="ac_power_event">
+ <kind>trigger</kind>
+ <label>AC Power Event</label>
+ <description>Event sent when AC trouble conditions are detected.</description>
+ <event>
+ <options>
+ <option value="OFF">Off</option>
+ <option value="RESTORED">Restored</option>
+ </options>
+ </event>
+ </channel-type>
+
+ <channel-type id="battery_event">
+ <kind>trigger</kind>
+ <label>Battery Event</label>
+ <description>Event sent when battery trouble conditions are detected.</description>
+ <event>
+ <options>
+ <option value="LOW">Low</option>
+ <option value="OK">OK</option>
+ </options>
+ </event>
+ </channel-type>
+
+ <channel-type id="dcm_event">
+ <kind>trigger</kind>
+ <label>DCM Event</label>
+ <description>Event sent when digital communicator trouble conditions are detected.</description>
+ <event>
+ <options>
+ <option value="TROUBLE">Trouble</option>
+ <option value="OK">OK</option>
+ </options>
+ </event>
+ </channel-type>
+
+ <channel-type id="energy_cost_event">
+ <kind>trigger</kind>
+ <label>Energy Cost Event</label>
+ <description>Event sent when the cost of energy changes.</description>
+ <event>
+ <options>
+ <option value="LOW">Trouble</option>
+ <option value="MID">Mid</option>
+ <option value="HIGH">High</option>
+ <option value="CRITCAL">Critical</option>
+ </options>
+ </event>
+ </channel-type>
+
+ <channel-type id="camera_trigger_event">
+ <kind>trigger</kind>
+ <label>Camera Trigger Event</label>
+ <description>Event sent when a camera trigger is detected.</description>
+ <event>
+ <options>
+ <option value="1">Camera 1</option>
+ <option value="2">Camera 2</option>
+ <option value="3">Camera 3</option>
+ <option value="4">Camera 4</option>
+ <option value="5">Camera 5</option>
+ <option value="6">Camera 6</option>
+ </options>
+ </event>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Button Thing -->
+ <thing-type id="button">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Button</label>
+ <description>A button configured in the controller.</description>
+ <channels>
+ <channel id="press" typeId="button_press"/>
+ <channel id="activated_event" typeId="button_activated"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Button Number</label>
+ <description>The button number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Button Channels -->
+ <channel-type id="button_press">
+ <item-type>Switch</item-type>
+ <label>Button Press</label>
+ <description>Sends a button event to the controller.</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="button_activated">
+ <kind>trigger</kind>
+ <label>Button Activated</label>
+ <description>Event sent when a button is activated.</description>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Console Thing -->
+ <thing-type id="console">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Console</label>
+ <description>A console configured in the controller.</description>
+ <channels>
+ <channel id="enable_disable_beeper" typeId="console_enable_disable_beeper"/>
+ <channel id="beep" typeId="console_beep"/>
+ </channels>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Console Number</label>
+ <description>The console number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Console Channels -->
+ <channel-type id="console_enable_disable_beeper">
+ <item-type>String</item-type>
+ <label>Enable/Disable Console Beeper</label>
+ <description>Enable/Disable the beeper for this/all console(s).</description>
+ <category>Switch</category>
+ <command>
+ <options>
+ <option value="OFF">Off</option>
+ <option value="ON">On</option>
+ </options>
+ </command>
+ </channel-type>
+
+ <channel-type id="console_beep">
+ <item-type>Number</item-type>
+ <label>Beep Console</label>
+ <description>Send a beep command to this/all console(s).</description>
+ <category>SoundVolume</category>
+ <state>
+ <options>
+ <option value="0">Off</option>
+ <option value="1">Indefinitely</option>
+ <option value="2">1 time</option>
+ <option value="3">2 times</option>
+ <option value="4">3 times</option>
+ <option value="5">4 times</option>
+ <option value="6">5 times</option>
+ </options>
+ </state>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Humidity Thing -->
+ <thing-type id="humidity_sensor">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Humidity Sensor</label>
+ <description>A humidity sensor configured in the controller.</description>
+ <channels>
+ <channel id="humidity" typeId="sensor_humidity"/>
+ <channel id="low_setpoint" typeId="sensor_humidity_low_setpoint"/>
+ <channel id="high_setpoint" typeId="sensor_humidity_high_setpoint"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Humidity Sensor Number</label>
+ <description>The humidity sensor number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Humidity Channels -->
+ <channel-type id="sensor_humidity">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Humidity</label>
+ <description>The current relative humidity at this humidity sensor.</description>
+ <category>Humidity</category>
+ <state readOnly="true" min="0" max="100" pattern="%d %%"/>
+ </channel-type>
+
+ <channel-type id="sensor_humidity_low_setpoint">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Low SetPoint</label>
+ <description>The current low setpoint for this humidity sensor.</description>
+ <category>Humidity</category>
+ <state min="0" max="100" pattern="%d %%"/>
+ </channel-type>
+
+ <channel-type id="sensor_humidity_high_setpoint">
+ <item-type>Number:Dimensionless</item-type>
+ <label>High SetPoint</label>
+ <description>The current high setpoint for this humidity sensor.</description>
+ <category>Humidity</category>
+ <state min="0" max="100" pattern="%d %%"/>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Lock Thing -->
+ <thing-type id="lock">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Lock</label>
+ <description>An access control reader lock configured in the controller.</description>
+ <channels>
+ <channel id="switch" typeId="lock_switch"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Lock Number</label>
+ <description>The lock number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Lock Channels -->
+ <channel-type id="lock_switch">
+ <item-type>Switch</item-type>
+ <label>Lock/Unlock</label>
+ <description>Lock or unlock this lock.</description>
+ <category>Switch</category>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Temperature Sensor Thing -->
+ <thing-type id="temp_sensor">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Temperature Sensor</label>
+ <description>A temperature sensor configured in the controller.</description>
+ <channels>
+ <channel id="temperature" typeId="sensor_temperature"/>
+ <channel id="low_setpoint" typeId="sensor_temp_low_setpoint"/>
+ <channel id="high_setpoint" typeId="sensor_temp_high_setpoint"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Temperature Sensor Number</label>
+ <description>The temperature sensor number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Temperature Sensor Channels -->
+ <channel-type id="sensor_temperature">
+ <item-type>Number:Temperature</item-type>
+ <label>Temperature</label>
+ <description>The current temperature at this temperature sensor.</description>
+ <category>Temperature</category>
+ <state readOnly="true" pattern="%.1f %unit%"/>
+ </channel-type>
+
+ <channel-type id="sensor_temp_low_setpoint">
+ <item-type>Number:Temperature</item-type>
+ <label>Low SetPoint</label>
+ <description>The current low setpoint of this temperature sensor.</description>
+ <category>Temperature</category>
+ <state pattern="%.1f %unit%"/>
+ </channel-type>
+
+ <channel-type id="sensor_temp_high_setpoint">
+ <item-type>Number:Temperature</item-type>
+ <label>High SetPoint</label>
+ <description>The current high setpoint of this temperature sensor.</description>
+ <category>Temperature</category>
+ <state pattern="%.1f %unit%"/>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Thermostat Thing -->
+ <thing-type id="thermostat">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Thermostat</label>
+ <description>A thermostat configured in the controller.</description>
+ <channels>
+ <channel id="freeze_alarm" typeId="thermostat_freeze_alarm"/>
+ <channel id="comm_failure" typeId="thermostat_comm_failure"/>
+ <channel id="status" typeId="thermostat_status"/>
+ <channel id="temperature" typeId="thermostat_temperature"/>
+ <channel id="outdoor_temperature" typeId="thermostat_outdoor_temperature"/>
+ <channel id="heat_setpoint" typeId="thermostat_heat_setpoint"/>
+ <channel id="cool_setpoint" typeId="thermostat_cool_setpoint"/>
+ <channel id="humidity" typeId="thermostat_humidity"/>
+ <channel id="humidify_setpoint" typeId="thermostat_humidify_setpoint"/>
+ <channel id="dehumidify_setpoint" typeId="thermostat_dehumidify_setpoint"/>
+ <channel id="system_mode" typeId="thermostat_system_mode"/>
+ <channel id="fan_mode" typeId="thermostat_fan_mode"/>
+ <channel id="hold_status" typeId="thermostat_hold_status"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Thermostat Number</label>
+ <description>The thermostat number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Thermostat Channels -->
+ <channel-type id="thermostat_freeze_alarm">
+ <item-type>Contact</item-type>
+ <label>Thermostat Freeze Alarm</label>
+ <description>Closed when freeze alarm is triggered by this thermostat.</description>
+ <category>Alarm</category>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="thermostat_comm_failure">
+ <item-type>Contact</item-type>
+ <label>Thermostat Communications Failure</label>
+ <description>Closed during a communications failure with this thermostat.</description>
+ <category>Contact</category>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="thermostat_status">
+ <item-type>Number</item-type>
+ <label>Thermostat Status</label>
+ <description>The current status of this thermostat.</description>
+ <category>Heating</category>
+ <state readOnly="true" pattern="%d">
+ <options>
+ <option value="0">Idle</option>
+ <option value="1">Heating</option>
+ <option value="2">Cooling</option>
+ <option value="3">Humidifying</option>
+ <option value="4">Dehumidifying</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="thermostat_temperature">
+ <item-type>Number:Temperature</item-type>
+ <label>Temperature</label>
+ <description>The current temperature at this thermostat.</description>
+ <category>Temperature</category>
+ <state readOnly="true" pattern="%.1f %unit%"/>
+ </channel-type>
+
+ <channel-type id="thermostat_outdoor_temperature">
+ <item-type>Number:Temperature</item-type>
+ <label>Outdoor Temperature</label>
+ <description>The current outdoor temperature detected by this thermostat.</description>
+ <category>Temperature</category>
+ <state readOnly="true" pattern="%.1f %unit%"/>
+ </channel-type>
+
+ <channel-type id="thermostat_heat_setpoint">
+ <item-type>Number:Temperature</item-type>
+ <label>Heat SetPoint</label>
+ <description>The current low/heating setpoint of this thermostat.</description>
+ <category>Temperature</category>
+ <state pattern="%.1f %unit%"/>
+ </channel-type>
+
+ <channel-type id="thermostat_cool_setpoint">
+ <item-type>Number:Temperature</item-type>
+ <label>Cool SetPoint</label>
+ <description>The current high/cooling setpoint of this thermostat.</description>
+ <category>Temperature</category>
+ <state pattern="%.1f %unit%"/>
+ </channel-type>
+
+ <channel-type id="thermostat_humidity">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Humidity</label>
+ <description>The relative humidity at this thermostat.</description>
+ <category>Humidity</category>
+ <state readOnly="true" min="0" max="100" pattern="%d %%"/>
+ </channel-type>
+
+ <channel-type id="thermostat_humidify_setpoint">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Humidify SetPoint</label>
+ <description>The current low/humidify setpoint for this thermostat.</description>
+ <category>Humidity</category>
+ <state min="0" max="100" pattern="%d %%"/>
+ </channel-type>
+
+ <channel-type id="thermostat_dehumidify_setpoint">
+ <item-type>Number:Dimensionless</item-type>
+ <label>Dehumidify SetPoint</label>
+ <description>The current high/dehumidify setpoint for this thermostat.</description>
+ <category>Humidity</category>
+ <state min="0" max="100" pattern="%d %%"/>
+ </channel-type>
+
+ <channel-type id="thermostat_system_mode">
+ <item-type>Number</item-type>
+ <label>System Mode</label>
+ <description>The current system mode of this thermostat.</description>
+ <category>Heating</category>
+ <state pattern="%d">
+ <options>
+ <option value="0">Off</option>
+ <option value="1">Heat</option>
+ <option value="2">Cool</option>
+ <option value="3">Auto</option>
+ <option value="4">Emergency heat</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="thermostat_fan_mode">
+ <item-type>Number</item-type>
+ <label>Fan Mode</label>
+ <description>The current fan mode of this thermostat.</description>
+ <category>Flow</category>
+ <state>
+ <options>
+ <option value="0">Auto</option>
+ <option value="1">On</option>
+ <option value="2">Cycle</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="thermostat_hold_status">
+ <item-type>Number</item-type>
+ <label>Hold Status</label>
+ <description>The current hold status of this thermostat.</description>
+ <category>Heating</category>
+ <state>
+ <options>
+ <option value="0">Off</option>
+ <option value="1">Hold</option>
+ <option value="2">Vacation hold</option>
+ </options>
+ </state>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Unit Thing -->
+ <thing-type id="unit">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Unit</label>
+ <description>A basic unit configured in the controller.</description>
+ <channels>
+ <channel id="level" typeId="unit_level"/>
+ <channel id="switch" typeId="unit_switch"/>
+ <channel id="on_for_seconds" typeId="on_for_seconds"/>
+ <channel id="off_for_seconds" typeId="off_for_seconds"/>
+ <channel id="on_for_minutes" typeId="on_for_minutes"/>
+ <channel id="off_for_minutes" typeId="off_for_minutes"/>
+ <channel id="on_for_hours" typeId="on_for_hours"/>
+ <channel id="off_for_hours" typeId="off_for_hours"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Unit Number</label>
+ <description>The unit number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Dimmable Thing -->
+ <thing-type id="dimmable">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Dimmable Unit</label>
+ <description>A dimmable unit configured in the controller.</description>
+ <channels>
+ <channel id="level" typeId="unit_level"/>
+ <channel id="switch" typeId="unit_switch"/>
+ <channel id="on_for_seconds" typeId="on_for_seconds"/>
+ <channel id="off_for_seconds" typeId="off_for_seconds"/>
+ <channel id="on_for_minutes" typeId="on_for_minutes"/>
+ <channel id="off_for_minutes" typeId="off_for_minutes"/>
+ <channel id="on_for_hours" typeId="on_for_hours"/>
+ <channel id="off_for_hours" typeId="off_for_hours"/>
+ <channel id="switch_press_event" typeId="switch_press_event"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Dimmable Unit Number</label>
+ <description>The dimmable unit number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- UPB Thing -->
+ <thing-type id="upb">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>UPB Unit</label>
+ <description>A UPB unit configured in the controller.</description>
+ <channels>
+ <channel id="level" typeId="unit_level"/>
+ <channel id="switch" typeId="unit_switch"/>
+ <channel id="on_for_seconds" typeId="on_for_seconds"/>
+ <channel id="off_for_seconds" typeId="off_for_seconds"/>
+ <channel id="on_for_minutes" typeId="on_for_minutes"/>
+ <channel id="off_for_minutes" typeId="off_for_minutes"/>
+ <channel id="on_for_hours" typeId="on_for_hours"/>
+ <channel id="off_for_hours" typeId="off_for_hours"/>
+ <channel id="upb_status" typeId="upb_status"/>
+ <channel id="switch_press_event" typeId="switch_press_event"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>UPB Unit Number</label>
+ <description>The UPB unit number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Flag Thing -->
+ <thing-type id="flag">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Flag</label>
+ <description>A flag configured in the controller.</description>
+ <channels>
+ <channel id="value" typeId="flag_value"/>
+ <channel id="switch" typeId="flag_switch"/>
+ <channel id="on_for_seconds" typeId="on_for_seconds"/>
+ <channel id="off_for_seconds" typeId="off_for_seconds"/>
+ <channel id="on_for_minutes" typeId="on_for_minutes"/>
+ <channel id="off_for_minutes" typeId="off_for_minutes"/>
+ <channel id="on_for_hours" typeId="on_for_hours"/>
+ <channel id="off_for_hours" typeId="off_for_hours"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Flag Number</label>
+ <description>The flag number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Output Thing -->
+ <thing-type id="output">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Voltage Output</label>
+ <description>A voltage output configured in the controller.</description>
+ <channels>
+ <channel id="switch" typeId="unit_switch"/>
+ <channel id="on_for_seconds" typeId="on_for_seconds"/>
+ <channel id="off_for_seconds" typeId="off_for_seconds"/>
+ <channel id="on_for_minutes" typeId="on_for_minutes"/>
+ <channel id="off_for_minutes" typeId="off_for_minutes"/>
+ <channel id="on_for_hours" typeId="on_for_hours"/>
+ <channel id="off_for_hours" typeId="off_for_hours"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Voltage Output Number</label>
+ <description>The voltage output number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Room Thing -->
+ <thing-type id="room">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Room</label>
+ <description>A room configured in the controller.</description>
+ <channels>
+ <channel id="switch" typeId="room_switch"/>
+ <channel id="scene_a" typeId="scene_toggle">
+ <label>Scene A</label>
+ </channel>
+ <channel id="scene_b" typeId="scene_toggle">
+ <label>Scene B</label>
+ </channel>
+ <channel id="scene_c" typeId="scene_toggle">
+ <label>Scene C</label>
+ </channel>
+ <channel id="scene_d" typeId="scene_toggle">
+ <label>Scene D</label>
+ </channel>
+ <channel id="state" typeId="room_state"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Room Number</label>
+ <description>The room number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Unit channels -->
+ <channel-type id="unit_level">
+ <item-type>Dimmer</item-type>
+ <label>Unit Level</label>
+ <description>Increase/Decrease the level of this unit.</description>
+ <category>Slider</category>
+ <state min="0" max="100" pattern="%d %%"/>
+ </channel-type>
+
+ <channel-type id="unit_switch">
+ <item-type>Switch</item-type>
+ <label>Switch</label>
+ <description>Turn this unit on/off.</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="on_for_seconds">
+ <item-type>Number</item-type>
+ <label>On for Seconds</label>
+ <description>Turn on this unit for a specified number of seconds.</description>
+ <category>Switch</category>
+ <state min="1" max="99"/>
+ </channel-type>
+
+ <channel-type id="off_for_seconds">
+ <item-type>Number</item-type>
+ <label>Off for Seconds</label>
+ <description>Turn off this unit for a specified number of seconds.</description>
+ <category>Switch</category>
+ <state min="1" max="99"/>
+ </channel-type>
+
+ <channel-type id="on_for_minutes">
+ <item-type>Number</item-type>
+ <label>On for Minutes</label>
+ <description>Turn on this unit for a specified number of minutes.</description>
+ <category>Switch</category>
+ <state min="1" max="99"/>
+ </channel-type>
+
+ <channel-type id="off_for_minutes">
+ <item-type>Number</item-type>
+ <label>Off for Minutes</label>
+ <description>Turn off this unit for a specified number of minutes.</description>
+ <category>Switch</category>
+ <state min="1" max="99"/>
+ </channel-type>
+
+ <channel-type id="on_for_hours">
+ <item-type>Number</item-type>
+ <label>On for Hours</label>
+ <description>Turn on this unit for a specified number of hours.</description>
+ <category>Switch</category>
+ <state min="1" max="18"/>
+ </channel-type>
+
+ <channel-type id="off_for_hours">
+ <item-type>Number</item-type>
+ <label>Off for Hours</label>
+ <description>Turn off this unit for a specified number of hours.</description>
+ <category>Switch</category>
+ <state min="1" max="18"/>
+ </channel-type>
+
+ <channel-type id="upb_status">
+ <item-type>String</item-type>
+ <label>UPB Status</label>
+ <description>Send a UPB status request message for this unit to the controller.</description>
+ <category>Status</category>
+ <command>
+ <options>
+ <option value="GET STATUS">Get Status</option>
+ </options>
+ </command>
+ </channel-type>
+
+ <channel-type id="room_switch">
+ <item-type>Switch</item-type>
+ <label>Switch</label>
+ <description>Turn this room on/off.</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="scene_toggle">
+ <item-type>Switch</item-type>
+ <label>Scene Toggle</label>
+ <description>Turn this scene on/off.</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="room_state">
+ <item-type>Number</item-type>
+ <label>State</label>
+ <description>The current state of this room.</description>
+ <category>Switch</category>
+ <state pattern="%d">
+ <options>
+ <option value="0">Off</option>
+ <option value="1">On</option>
+ <option value="2">Scene A</option>
+ <option value="3">Scene B</option>
+ <option value="4">Scene C</option>
+ <option value="5">Scene D</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="flag_value">
+ <item-type>Number</item-type>
+ <label>Flag Value</label>
+ <description>Numeric value of this flag.</description>
+ <category>Number</category>
+ <state min="0" max="255" pattern="%d"/>
+ </channel-type>
+
+ <channel-type id="flag_switch">
+ <item-type>Switch</item-type>
+ <label>Flag Switch</label>
+ <description>Turn this flag on/off.</description>
+ <category>Switch</category>
+ </channel-type>
+
+ <channel-type id="switch_press_event">
+ <kind>trigger</kind>
+ <label>Switch Press Event</label>
+ <description>Event sent when an ALC, UPB, Radio RA, or Starlite switch is pressed.</description>
+ </channel-type>
+
+</thing:thing-descriptions>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<thing:thing-descriptions bindingId="omnilink"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
+ xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
+
+ <!-- Zone Thing -->
+ <thing-type id="zone">
+ <supported-bridge-type-refs>
+ <bridge-type-ref id="controller"/>
+ </supported-bridge-type-refs>
+ <label>Zone</label>
+ <description>A zone configured in the controller.</description>
+ <channels>
+ <channel id="contact" typeId="zone_contact"/>
+ <channel id="current_condition" typeId="zone_current_condition"/>
+ <channel id="latched_alarm_status" typeId="zone_latched_alarm_status"/>
+ <channel id="arming_status" typeId="zone_arming_status"/>
+ <channel id="bypass" typeId="zone_bypass"/>
+ <channel id="restore" typeId="zone_restore"/>
+ </channels>
+ <properties>
+ <property name="name"/>
+ <property name="area"/>
+ </properties>
+ <representation-property>number</representation-property>
+ <config-description>
+ <parameter name="number" type="integer" required="true">
+ <label>Zone Number</label>
+ <description>The zone number.</description>
+ </parameter>
+ </config-description>
+ </thing-type>
+
+ <!-- Zone Channels -->
+ <channel-type id="zone_contact">
+ <item-type>Contact</item-type>
+ <label>Contact State</label>
+ <description>Contact state information of this zone.</description>
+ <category>Contact</category>
+ <state readOnly="true"/>
+ </channel-type>
+
+ <channel-type id="zone_current_condition">
+ <item-type>Number</item-type>
+ <label>Current Condition</label>
+ <description>Current condition of this zone.</description>
+ <category>Contact</category>
+ <state readOnly="true" pattern="%d">
+ <options>
+ <option value="0">Secure</option>
+ <option value="1">Not Ready</option>
+ <option value="2">Trouble</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="zone_latched_alarm_status">
+ <item-type>Number</item-type>
+ <label>Latched Alarm Status</label>
+ <description>Latched alarm status of this zone.</description>
+ <category>Contact</category>
+ <state readOnly="true" pattern="%d">
+ <options>
+ <option value="0">Secure</option>
+ <option value="1">Tripped</option>
+ <option value="2">Reset, but previously tripped</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="zone_arming_status">
+ <item-type>Number</item-type>
+ <label>Arming Status</label>
+ <description>Arming status of this zone.</description>
+ <category>Contact</category>
+ <state readOnly="true" pattern="%d">
+ <options>
+ <option value="0">Disarmed</option>
+ <option value="1">Armed</option>
+ <option value="2">Bypassed by user</option>
+ <option value="3">Bypassed by system</option>
+ </options>
+ </state>
+ </channel-type>
+
+ <channel-type id="zone_bypass">
+ <item-type>String</item-type>
+ <label>Bypass Zone</label>
+ <description>Send a 4 digit user code to bypass this zone.</description>
+ <category>Alarm</category>
+ </channel-type>
+
+ <channel-type id="zone_restore">
+ <item-type>String</item-type>
+ <label>Restore Zone</label>
+ <description>Send a 4 digit user code to restore this zone.</description>
+ <category>Alarm</category>
+ </channel-type>
+
+</thing:thing-descriptions>
<module>org.openhab.binding.oceanic</module>
<module>org.openhab.binding.ojelectronics</module>
<module>org.openhab.binding.omnikinverter</module>
+ <module>org.openhab.binding.omnilink</module>
<module>org.openhab.binding.onebusaway</module>
<module>org.openhab.binding.onewiregpio</module>
<module>org.openhab.binding.onewire</module>