mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[lutron] Add LEAP protocol support (#8650)
* [lutron] Add LEAP protocol support Signed-off-by: Bob Adair <bob.github@att.net>
This commit is contained in:
parent
25826854b4
commit
6f659f2308
@ -3,31 +3,32 @@
|
||||
This binding integrates with [Lutron](http://www.lutron.com) lighting control and home automation systems.
|
||||
It contains separate binding support for four different types of Lutron systems:
|
||||
|
||||
* RadioRA 2, HomeWorks QS, and other systems that can be controlled by Lutron Integration Protocol, such as RA2 Select, and Caseta Pro
|
||||
* RadioRA 2, HomeWorks QS, and other systems that can be controlled by Lutron Integration Protocol (LIP) or LEAP, such as RA2 Select, and Caseta
|
||||
* The original RadioRA system, referred to here as RadioRA Classic
|
||||
* Legacy HomeWorks RS232 Processors
|
||||
* Grafik Eye 3x/4x systems with GRX-PRG or GRX-CI-PRG control interfaces
|
||||
|
||||
Each is described in a separate section below.
|
||||
|
||||
# Lutron RadioRA 2/HomeWorks QS Binding
|
||||
# Lutron RadioRA 2/HomeWorks QS/Caseta Binding
|
||||
|
||||
**Note:** While the integration protocol used by this binding should largely be compatible with other current Lutron systems, this binding has only been fully tested with RadioRA 2, HomeWorks QS, and Caseta with Smart Bridge Pro.
|
||||
**Note:** While the Lutron Integration Protocol used by this binding should largely be compatible with other current Lutron systems, this binding has only been fully tested with RadioRA 2, HomeWorks QS, and Caseta with Smart Bridge Pro.
|
||||
Homeworks QS support is still a work in progress, since not all features/devices are supported yet.
|
||||
RA2 Select systems have been reported to work with the binding, but full support is still unconfirmed.
|
||||
The binding has not been tested with Quantum, QS Standalone, or myRoom Plus systems.
|
||||
|
||||
**Note:** Caseta support is only possible with the Smart Bridge **Pro** hub.
|
||||
The standard Caseta hub does not support Lutron Integration Protocol.
|
||||
RA2 Select systems work with the binding, but full support for all devices still needs to be confirmed.
|
||||
Caseta Smart Bridge (non-Pro model) support and support for Caseta occupancy sensors is available only through the experimental leapbridge thing.
|
||||
The binding has not been tested with Quantum, QS Standalone, myRoom Plus, or Athena systems.
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding currently supports the following thing types:
|
||||
|
||||
* **ipbridge** - The Lutron main repeater/processor/hub
|
||||
* **dimmer** - Dimmer or fan controller
|
||||
* **leapbridge** - Experimental bridge that uses LEAP protocol (Caseta & RA2 Select only)
|
||||
* **dimmer** - Light dimmer
|
||||
* **switch** - Switch or relay module
|
||||
* **fan** - Fan controller
|
||||
* **occupancysensor** - Occupancy/vacancy sensor
|
||||
* **ogroup** - Occupancy group
|
||||
* **keypad** - Lutron seeTouch or Hybrid seeTouch Keypad
|
||||
* **ttkeypad** - Tabletop seeTouch Keypad
|
||||
* **intlkeypad** - International seeTouch Keypad (HomeWorks QS only)
|
||||
@ -53,8 +54,8 @@ Discovered repeaters/processors will be accessed using the default integration c
|
||||
These can be changed in the bridge thing configuration.
|
||||
Discovered keypad devices should now have their model parameters automatically set to the correct value.
|
||||
|
||||
Caseta Smart Bridge PRO 2 hubs and RA2 Select main repeaters should now be discovered automatically via mDNS.
|
||||
Devices attached to them still need to be configured manually.
|
||||
Caseta Smart Bridge hubs, Smart Bridge Pro 2 hubs, and RA2 Select main repeaters should now be discovered automatically via mDNS.
|
||||
Devices attached to them still need to be configured manually unless the experimental leapbridge is used.
|
||||
|
||||
Other supported Lutron systems must be configured manually.
|
||||
|
||||
@ -68,11 +69,42 @@ Each Lutron thing requires the integration ID of the corresponding item in the L
|
||||
The integration IDs can be retrieved from the integration report generated by the Lutron software.
|
||||
If a thing will not come online, but instead has the status "UNKNOWN: Awaiting initial response", it is likely that you have configured the wrong integration ID for it.
|
||||
|
||||
### Bridge
|
||||
### Bridges
|
||||
|
||||
A bridge may currently be a RadioRA 2 main repeater, a HomeWorks QS Processor, a Caseta Smart Bridge Pro, or a RA2 Select main repeater.
|
||||
The bridge configuration requires the IP address of the bridge as well as the telnet username and password to log in to the bridge.
|
||||
Two different bridges are now supported by the binding, ipbridge and leapbridge.
|
||||
The LIP protocol is supported by ipbridge while the LEAP protocol is supported by leapbridge.
|
||||
Current Lutron systems support one or both protocols as shown below.
|
||||
|
||||
|Bridge Device | LIP | LEAP |
|
||||
|------------------------|-----|------|
|
||||
|HomeWorks QS Processor | X | |
|
||||
|RadioRA 2 Main Repeater | X | |
|
||||
|RA2 Select Main Repeater| X | X |
|
||||
|Caseta Smart Bridge Pro | X | X |
|
||||
|Caseta Smart Bridge | | X |
|
||||
|
||||
If your system supports only one protocol, then the choice of bridge is easy.
|
||||
If you have a system that supports both protocols, you must decide which you wish to use.
|
||||
|
||||
You should be aware of the following functional differences between the protocols:
|
||||
|
||||
* Using LIP on Caseta you can’t receive notifications of occupancy group status changes (occupied/unoccupied/unknown), but using LEAP you can.
|
||||
* Conversely, LIP provides notifications of keypad key presses, while LEAP does not (as far as is currently known).
|
||||
This means that using ipbridge you can trigger rules and take actions on keypad key presses/releases, but using leapbridge you can’t.
|
||||
* Caseta and RA2 Select device discovery is supported via LEAP, but not via LIP.
|
||||
* The leapbridge is a bit more complicated to configure because LEAP uses an SSL connections and authenticates using certificates.
|
||||
* LIP is a publicly documented protocol, while LEAP is not. This means that Lutron could make a change that breaks LEAP support at any time.
|
||||
|
||||
It is possible to run leapbridge and ipbridge at the same time, for the same bridge device, but each managed device (e.g. keypad or dimmer) should only be configured through *one* bridge.
|
||||
Remember that LEAP device IDs and LIP integration IDs are not necessarily equal!
|
||||
|
||||
#### ipbridge
|
||||
|
||||
This is the standard bridge which should be used with most Lutron systems.
|
||||
It relies on Lutron Integration Protocol (LIP) over TCP/IP to communicate with the target device.
|
||||
It can currently be used with a RadioRA 2 main repeater, a HomeWorks QS Processor, a Caseta Smart Bridge Pro, or a RA2 Select main repeater.
|
||||
|
||||
The ipbridge configuration requires the IP address of the bridge as well as the telnet username and password to log in to the bridge.
|
||||
It is recommended that main repeaters/processors be configured with static IP addresses.
|
||||
However if automatic discovery is used, the bridge thing will work with DHCP-configured addresses.
|
||||
|
||||
@ -102,7 +134,45 @@ Bridge lutron:ipbridge:radiora2 [ ipAddress="192.168.1.2", user="lutron", passwo
|
||||
}
|
||||
```
|
||||
|
||||
### Dimmers
|
||||
#### leapbridge [**experimental**]
|
||||
|
||||
The leapbridge is an experimental bridge which allows the binding to work with the Caseta Smart Hub (non-Pro version).
|
||||
It can also be used to provide additional features, such as support for occupancy groups and device discovery, when used with Caseta Smart Hub Pro or RA2 Select.
|
||||
It uses the LEAP protocol over SSL, which is an undocumented protocol supported by some of Lutron's newer systems.
|
||||
Note that the LEAP protocol will not notify the bridge of keypad key presses.
|
||||
If you need this useful feature, you should use ipbridge instead.
|
||||
You can use both ipbridge and leapbridge at the same time, but each device should only be configured through one bridge.
|
||||
You should also be aware that LEAP and LIP integration IDs for the same device can be different.
|
||||
|
||||
For instructions on configuring authentication for leapbridge, see the [Leap Notes](doc/leapnotes.md) document.
|
||||
|
||||
The `ipAddress`, `keystore` and `keystorePassword` parameters must be set.
|
||||
The optional `port` parameter defaults to 8081 and should not normally need to be changed.
|
||||
|
||||
The optional parameter `certValidate` defaults to true. It should be set to false only if validation of the hub's server certificate is failing, possibly because the hostname you are using for it does not match its internal hostname.
|
||||
If this happens, the leapbridge status will be: "OFFLINE - COMMUNICATION_ERROR - Error opening SSL connection", and a message like the following may be logged:
|
||||
```Error opening SSL connection: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target```.
|
||||
|
||||
The optional advanced parameter `heartbeat` can be used to set the interval between connection keepalive heartbeat messages, in minutes.
|
||||
It defaults to 5.
|
||||
Note that the handler will wait up to 30 seconds for a heartbeat response before attempting to reconnect.
|
||||
The optional advanced parameter `reconnect` can be used to set the connection retry interval, in minutes.
|
||||
It also defaults to 5.
|
||||
The optional advanced parameter `delay` can be used to set a delay (in milliseconds) between transmission of LEAP commands to the bridge device.
|
||||
It should not normally need to be changed.
|
||||
|
||||
Thing configuration file example:
|
||||
|
||||
```
|
||||
Bridge lutron:leapbridge:caseta [ ipAddress="192.168.1.3", keystore="/home/openhab/lutron.keystore", keystorePassword="secret" ] {
|
||||
Thing ...
|
||||
Thing ...
|
||||
}
|
||||
```
|
||||
|
||||
### Devices
|
||||
|
||||
#### Dimmers
|
||||
|
||||
Dimmers can optionally be configured to specify a default fade in and fade out time in seconds using the `fadeInTime` and `fadeOutTime` parameters.
|
||||
These are used for ON and OFF commands, respectively, and default to 1 second if not set.
|
||||
@ -116,6 +186,7 @@ If set to "true", the dimmer will go to its last non-zero level when sent an ON
|
||||
If the last non-zero level cannot be determined, the value of `onLevel` will be used instead.
|
||||
|
||||
A **dimmer** thing has a single channel *lightlevel* with type Dimmer and category DimmableLight.
|
||||
The **dimmer** thing was previously also used to control fan speed controllers, but now you should use the **fan** thing instead.
|
||||
|
||||
Thing configuration file example:
|
||||
|
||||
@ -136,7 +207,7 @@ Times of 100 seconds or more will be rounded to the nearest integer value.
|
||||
|
||||
See below for an example rule using thing actions.
|
||||
|
||||
### Switches
|
||||
#### Switches
|
||||
|
||||
Switches take no additional parameters besides `integrationId`.
|
||||
A **switch** thing has a single channel *switchstatus* with type Switch and category Switch.
|
||||
@ -147,9 +218,24 @@ Thing configuration file example:
|
||||
Thing switch porch [ integrationId=8 ]
|
||||
```
|
||||
|
||||
### Occupancy Sensors
|
||||
#### Fans
|
||||
|
||||
Fan speed controllers are interfaced with using the **fan** thing.
|
||||
It accepts no additional parameters besides `integrationId`.
|
||||
A **fan** thing has two channels, *fanspeed* and *fanlevel*.
|
||||
|
||||
Thing configuration file example:
|
||||
|
||||
```
|
||||
Thing fan porchfan [ integrationId=12 ]
|
||||
```
|
||||
|
||||
#### Occupancy Sensors
|
||||
|
||||
An **occupancysensor** thing interfaces to Lutron Radio Powr Savr wireless occupancy/vacancy sensors on RadioRA 2 and HomeWorks QS systems.
|
||||
On these systems, you should generally choose to interface to either an occupancy group or individual occupancy sensors for a given area.
|
||||
For Caseta Smart Motion Sensors, you must use the **group** thing instead.
|
||||
|
||||
An **occupancysensor** thing interfaces to Lutron Radio Powr Savr wireless occupancy/vacancy sensors.
|
||||
It accepts no configuration parameters other than `integrationId`.
|
||||
|
||||
The binding creates one *occupancystatus* channel, Item type Switch, category Motion.
|
||||
@ -163,7 +249,23 @@ Thing configuration file example:
|
||||
Thing occupancysensor shopsensor [ integrationId=7 ]
|
||||
```
|
||||
|
||||
### seeTouch and Hybrid seeTouch Keypads
|
||||
#### Occupancy Groups
|
||||
|
||||
A **ogroup** thing interfaces to an occupancy group, which shows occcupancy/vacancy status for an area or room with one or more occupancy sensors.
|
||||
On RadioRA2 and HomeWorks QS systems, you should generally choose to interface to either an occupancy group or individual occupancy sensors for a given area.
|
||||
On Caseta systems, you cannot interface to individual sensors and must use the *ogroup* thing.
|
||||
The `integrationId` parameter must be set to the occupancy group ID.
|
||||
|
||||
The binding creates one read-only *groupstate* channel, item type String, category Motion.
|
||||
The value can be "OCCUPIED", "UNOCCUPIED", or "UNKNOWN".
|
||||
|
||||
Thing configuration file example:
|
||||
|
||||
```
|
||||
Thing ogroup lrgroup [ integrationId=7 ]
|
||||
```
|
||||
|
||||
#### seeTouch and Hybrid seeTouch Keypads
|
||||
|
||||
seeTouch and Hybrid seeTouch keypads are interfaced with using the **keypad** thing.
|
||||
In addition to the usual `integrationId` parameter, it accepts `model` and `autorelease` parameters.
|
||||
@ -191,7 +293,8 @@ Ditto for the indicator LED channels.
|
||||
Note, however, that version 11.6 or higher of the RadioRA 2 software may be required in order to drive keypad LED states, and then this may only be done on unbound buttons.
|
||||
|
||||
Component numbering: For button and LED layouts and numbering, see the Lutron Integration Protocol Guide (rev. AA) p.104 (https://www.lutron.com/TechnicalDocumentLibrary/040249.pdf).
|
||||
If you are having problems determining which channels have been created for a given keypad model, click on the thing under Configuration/Things in the Paper UI, or run the command `things show <thingUID>` (e.g. `things show lutron:keypad:radiora2:entrykeypad`) from the openHAB CLI to list the channels.
|
||||
If you are having problems determining which channels have been created for a given keypad model, select the appropriate keypad thing under Settings/Things in the Administration UI and click on the Channels tab.
|
||||
You can also run the command `things show <thingUID>` (e.g. `things show lutron:keypad:radiora2:entrykeypad`) from the openHAB CLI to list the channels.
|
||||
|
||||
Supported settings for `model` parameter: H1RLD, H2RLD, H3BSRL, H3S, H4S, H5BRL, H6BRL, HN1RLD, HN2RLD, HN3S, HN3BSRL, HN4S, HN5BRL, HN6BRL, W1RLD, W2RLD, W3BD, W3BRL, W3BSRL, W3S, W4S, W5BRL, W5BRLIR, W6BRL, W7B, Generic (default)
|
||||
|
||||
@ -212,14 +315,16 @@ then
|
||||
end
|
||||
```
|
||||
|
||||
### Tabletop seeTouch Keypads
|
||||
#### Tabletop seeTouch Keypads
|
||||
|
||||
Tabletop seeTouch keypads use the **ttkeypad** thing.
|
||||
It accepts the same `integrationId`, `model`, and `autorelease` parameters and creates the same channel types as the **keypad** thing.
|
||||
See the **keypad** section above for a full discussion of configuration and use.
|
||||
|
||||
Component numbering: For button and LED layouts and numbering, see the Lutron Integration Protocol Guide (rev. AA) p.110 (https://www.lutron.com/TechnicalDocumentLibrary/040249.pdf).
|
||||
If you are having problems determining which channels have been created for a given keypad model, click on the thing under Configuration/Things in the Paper UI, or run the command `things show <thingUID>` (e.g. `things show lutron:ttkeypad:radiora2:bedroomkeypad`) from the openHAB CLI to list the channels.
|
||||
If you are having problems determining which channels have been created for a given keypad model, select the appropriate ttkeypad thing under Settings/Things in the Administration UI and click on the Channels tab.
|
||||
You can also run the command `things show <thingUID>` (e.g. `things show lutron:ttkeypad:radiora2:bedroomkeypad`) from the openHAB CLI to list the channels.
|
||||
|
||||
|
||||
Supported settings for `model` parameter: T5RL, T10RL, T15RL, T5CRL, T10CRL, T15CRL, Generic (default)
|
||||
|
||||
@ -229,18 +334,19 @@ Thing configuration file example:
|
||||
Thing ttkeypad bedroomkeypad [ integrationId=11, model="T10RL" autorelease=true ]
|
||||
```
|
||||
|
||||
### International seeTouch Keypads (HomeWorks QS)
|
||||
#### International seeTouch Keypads (HomeWorks QS)
|
||||
|
||||
International seeTouch keypads used in the HomeWorks QS system use the **intlkeypad** thing.
|
||||
It accepts the same `integrationId`, `model`, and `autorelease` parameters and creates the same button and LED channel types as the **keypad** thing.
|
||||
See the **keypad** section above for a full discussion of configuration and use.
|
||||
|
||||
To support this keypad's contact closure inputs, CCI channels named *cci1* and *cci2* are created with item type Contact and category Switch.
|
||||
They are marked as Advanced, so they will not be automatically linked to items in the Paper UI's Simple Mode.
|
||||
They are marked as Advanced, so you will need to check "Show advanced" in order to see them listed in the Administration UI.
|
||||
They present ON/OFF states the same as a keypad button.
|
||||
|
||||
Component numbering: For button and LED layouts and numbering, see the Lutron Integration Protocol Guide (rev. AA) p.107 (https://www.lutron.com/TechnicalDocumentLibrary/040249.pdf).
|
||||
If you are having problems determining which channels have been created for a given keypad model, click on the thing under Configuration/Things in the Paper UI, or run the command `things show <thingUID>` (e.g. `things show lutron:intlkeypad:hwprocessor:kitchenkeypad`) from the openHAB CLI to list the channels.
|
||||
If you are having problems determining which channels have been created for a given keypad model, select the appropriate intlkeypad thing under Settings/Things in the Administration UI and click on the Channels tab.
|
||||
You can also run the command `things show <thingUID>` (e.g. `things show lutron:intlkeypad:hwprocessor:kitchenkeypad`) from the openHAB CLI to list the channels.
|
||||
|
||||
Supported settings for `model` parameter: 2B, 3B, 4B, 5BRL, 6BRL, 7BRL, 8BRL, 10BRL / Generic (default)
|
||||
|
||||
@ -250,14 +356,15 @@ Thing configuration file example:
|
||||
Thing intlkeypad kitchenkeypad [ integrationId=15, model="10BRL" autorelease=true ]
|
||||
```
|
||||
|
||||
### Palladiom Keypads (HomeWorks QS)
|
||||
#### Palladiom Keypads (HomeWorks QS)
|
||||
|
||||
Palladiom keypads used in the HomeWorks QS system use the **palladiomkeypad** thing.
|
||||
It accepts the same `integrationId`, `model`, and `autorelease` parameters and creates the same button and LED channel types as the **keypad** thing.
|
||||
See the **keypad** section above for a full discussion of configuration and use.
|
||||
|
||||
Component numbering: For button and LED layouts and numbering, see the Lutron Integration Protocol Guide (rev. AA) p.95 (https://www.lutron.com/TechnicalDocumentLibrary/040249.pdf).
|
||||
If you are having problems determining which channels have been created for a given keypad model, click on the thing under Configuration/Things in the Paper UI, or run the command `things show <thingUID>` (e.g. `things show lutron:palladiomkeypad:hwprocessor:kitchenkeypad`) from the openHAB CLI to list the channels.
|
||||
If you are having problems determining which channels have been created for a given keypad model, select the appropriate palladiomkeypad thing under Settings/Things in the Administration UI and click on the Channels tab.
|
||||
You can also run the command `things show <thingUID>` (e.g. `things show lutron:palladiomkeypad:hwprocessor:kitchenkeypad`) from the openHAB CLI to list the channels.
|
||||
|
||||
Supported settings for `model` parameter: 2W, 3W, 4W, RW, 22W, 24W, 42W, 44W, 2RW, 4RW, RRW
|
||||
|
||||
@ -268,7 +375,7 @@ Thing palladiomkeypad kitchenkeypad [ integrationId=16, model="4W" autorelease=t
|
||||
```
|
||||
|
||||
|
||||
### Pico Keypads
|
||||
#### Pico Keypads
|
||||
|
||||
Pico keypads use the **pico** thing.
|
||||
It accepts the same `integrationId`, `model`, and `autorelease` parameters and creates the same channel types as the **keypad** and **ttkeypad** things.
|
||||
@ -276,7 +383,8 @@ The only difference is that no LED channels will be created, since Pico keypads
|
||||
See the discussion above for a full discussion of configuration and use.
|
||||
|
||||
Component numbering: For button layouts and numbering, see the Lutron Integration Protocol Guide (rev. AA) p.113 (https://www.lutron.com/TechnicalDocumentLibrary/040249.pdf).
|
||||
If you are having problems determining which channels have been created for a given keypad model, click on the thing under Configuration/Things in the Paper UI, or run the command `things show <thingUID>` (e.g. `things show lutron:pico:radiora2:hallpico`) from the openHAB CLI to list the channels.
|
||||
If you are having problems determining which channels have been created for a given keypad model, select the appropriate pico thing under Settings/Things in the Administration UI and click on the Channels tab.
|
||||
You can also run the command `things show <thingUID>` (e.g. `things show lutron:pico:radiora2:hallpico`) from the openHAB CLI to list the channels.
|
||||
|
||||
Supported settings for `model` parameter: 2B, 2BRL, 3B, 3BRL, 4B, Generic (default)
|
||||
|
||||
@ -286,7 +394,7 @@ Thing configuration file example:
|
||||
Thing pico hallpico [ integrationId=12, model="3BRL", autorelease=true ]
|
||||
```
|
||||
|
||||
### GRAFIK Eye QS Keypads (in RadioRA 2/HomeWorks QS systems)
|
||||
#### GRAFIK Eye QS Keypads (in RadioRA 2/HomeWorks QS systems)
|
||||
|
||||
GRAFIK Eye devices can contain up to 6 lighting dimmers, a scene controller, a time clock, and a front panel with a column of 5 programmable scene buttons and 0 to 3 columns of programmable shade or lighting control buttons.
|
||||
They can be used as peripheral devices in a RadioRA 2 or HomeWorks QS system, or can be used as stand-alone controllers that themselves can control other Lutron devices.
|
||||
@ -301,11 +409,12 @@ The model parameter should be set to indicate whether there are zero, one, two,
|
||||
Note that this count does not include the column of 5 scene buttons always found on the right side of the panel.
|
||||
|
||||
To support the GRAFIK Eye's contact closure input, a CCI channel named *cci1* will be created with item type Contact and category Switch.
|
||||
It is marked as Advanced, so it will not be automatically linked to items in the Paper UI's Simple Mode.
|
||||
It is marked as Advanced, so you will need to check "Show advanced" in order to see it listed in the Administration UI.
|
||||
It presents ON/OFF states the same as a keypad button.
|
||||
|
||||
Component numbering: The buttons and LEDs on the GRAFIK Eye are numbered top to bottom, starting with the 5 scene buttons in a column on the right side of the panel, and then proceeding with the columns of buttons (if any) on the left side of the panel, working left to right.
|
||||
If you are having problems determining which channels have been created for a given model setting, click on the thing under Configuration/Things in the Paper UI, or run the command `things show <thingUID>` (e.g. `things show lutron:grafikeyekeypad:radiora2:theaterkeypad`) from the openHAB CLI to list the channels.
|
||||
If you are having problems determining which channels have been created for a given model setting, select the appropriate grafikeyekeypad thing under Settings/Things in the Administration UI and click on the Channels tab.
|
||||
You can also run the command `things show <thingUID>` (e.g. `things show lutron:grafikeyekeypad:radiora2:theaterkeypad`) from the openHAB CLI to list the channels.
|
||||
|
||||
Supported settings for `model` parameter: 0COL, 1COL, 2COL, 3COL (default)
|
||||
|
||||
@ -315,7 +424,7 @@ Thing configuration file example:
|
||||
Thing lutron:grafikeyekeypad:theaterkeypad (lutron:ipbridge:radiora2) [ integrationId=12, model="3COL", autorelease="true" ]
|
||||
```
|
||||
|
||||
### Virtual Keypads
|
||||
#### Virtual Keypads
|
||||
|
||||
The **virtualkeypad** thing is used to interface to the virtual buttons on the RadioRA 2 main repeater or HomeWorks processor.
|
||||
These are sometimes referred to in the Lutron documentation as phantom buttons or integration buttons, and are used only for integration.
|
||||
@ -327,7 +436,7 @@ For this to work, the optional `model` parameter must be set to `Caseta`.
|
||||
When used with Caseta, no virtual indicator LED channels are created.
|
||||
|
||||
The behavior of this binding is the same as the other keypad bindings, with the exception that the button and LED channels created have the Advanced flag set.
|
||||
This means, among other things, that they will not be automatically linked to items in the Paper UI's Simple Mode.
|
||||
This means, among other things, that you will need to check "Show advanced" in order to see them listed in the Administration UI.
|
||||
|
||||
In most cases the integrationId parameter should be set to 1.
|
||||
|
||||
@ -339,7 +448,7 @@ Thing configuration file example:
|
||||
Thing virtualkeypad repeaterbuttons [ integrationId=1, autorelease=true ]
|
||||
```
|
||||
|
||||
### VCRX Modules
|
||||
#### VCRX Modules
|
||||
|
||||
The Lutron VCRX appears to openHAB as multiple devices.
|
||||
The 6 buttons (which can be activated remotely by HomeLink remote controls), 6 corresponding LEDs, and 4 contact closure inputs (CCIs) are handled by the **vcrx** thing, which behaves like a keypad.
|
||||
@ -350,7 +459,7 @@ Supplying a model is not required, as there is only one model.
|
||||
|
||||
To support the contact closure inputs, CCI channels named *cci[n]* are created with item type Contact and category Switch.
|
||||
The VCRX security (Full/Flash) input controls both the cci1 and cci2 channels, while input connections 1 and 2 map to the cci3 and cci4 channels respectively.
|
||||
The cci channels are marked as Advanced, so they will not be automatically linked to items in the Paper UI's Simple Mode.
|
||||
The cci channels are marked as Advanced, so you will need to check "Show advanced" in order to see them listed in the Administration UI.
|
||||
They present OPEN/CLOSED states but do not accept commands since Contact items are read-only in openHAB.
|
||||
Note that the `autorelease` option **does not** apply to CCI channels.
|
||||
|
||||
@ -360,7 +469,7 @@ Thing configuration file example:
|
||||
Thing vcrx vcrx1 [ integrationId=13, autorelease=true ]
|
||||
```
|
||||
|
||||
### QS IO Interface (HomeWorks QS)
|
||||
#### QS IO Interface (HomeWorks QS)
|
||||
|
||||
The Lutron QS IO Interface (QSE-IO) appears to openHAB as multiple devices.
|
||||
The 5 contact closure inputs (CCIs) are handled by the **qsio** thing.
|
||||
@ -368,7 +477,7 @@ The 5 contact closure outputs (CCOs) are handled by the **cco** thing (see below
|
||||
The only configuration option is `integrationId`
|
||||
|
||||
To support the contact closure inputs, CCI channels named *cci[n]* are created with item type Contact and category Switch.
|
||||
They are marked as Advanced, so they will not be automatically linked to items in the Paper UI's Simple Mode.
|
||||
They are marked as Advanced, so you will need to check "Show advanced" in order to see them listed in the Administration UI.
|
||||
They present OPEN/CLOSED states but do not accept commands as Contact items are read-only in openHAB.
|
||||
|
||||
Some functionality may depend on QSE-IO DIP switch settings. See the Lutron documentation for more information.
|
||||
@ -379,7 +488,7 @@ Thing configuration file example:
|
||||
Thing qsio sensorinputs [ integrationId=42 ]
|
||||
```
|
||||
|
||||
### QS Wallbox Closure Interface (WCI) (HomeWorks QS only)
|
||||
#### QS Wallbox Closure Interface (WCI) (HomeWorks QS only)
|
||||
|
||||
The Lutron Wallbox Closure Interface (QSE-CI-WCI) is used to interface to contact closure keypads.
|
||||
It is handled by the **wci** thing.
|
||||
@ -397,7 +506,7 @@ Thing configuration file example:
|
||||
Thing wci specialkeypad [ integrationId=48, autorelease=true ]
|
||||
```
|
||||
|
||||
### CCO Modules
|
||||
#### CCO Modules
|
||||
|
||||
Contact closure output (**cco**) things accept `outputType` and `pulseLength` parameters.
|
||||
The `outputType` parameter is a string that should be set to "Pulsed" for pulsed CCOs or "Maintained" for non-pulsed CCOs.
|
||||
@ -424,7 +533,7 @@ Thing cco garage [ integrationId=5, outputType="Pulsed", pulseLength=0.5 ]
|
||||
Thing cco relay1 [ integrationId=7, outputType="Maintained"]
|
||||
```
|
||||
|
||||
### Shades
|
||||
#### Shades
|
||||
|
||||
Each Lutron shade, motorized drape, or QS motor controller output (LQSE-4M-D) is controlled by a **shade** thing.
|
||||
The only configuration parameter it accepts is `integrationId`.
|
||||
@ -449,7 +558,7 @@ Thing configuration file example:
|
||||
Thing shade libraryshade [ integrationId=33]
|
||||
```
|
||||
|
||||
### Blinds [**Experimental**]
|
||||
#### Blinds [**Experimental**]
|
||||
|
||||
Each Lutron Sivoia QS Venetian Blind or Horizontal Sheer Blind is controlled by a **blind** thing.
|
||||
Besides `integrationId`, it requires that the parameter `type` be set to either "Venetian" for venetian blinds or "Sheer" for horizontal sheer blinds.
|
||||
@ -475,7 +584,7 @@ Thing configuration file example:
|
||||
Thing blind officeblinds [ integrationId=76, type="Venetian"]
|
||||
```
|
||||
|
||||
### Green Mode
|
||||
#### Green Mode
|
||||
|
||||
Radio RA2 and HomeWorks QS systems have a "Green Mode" or "Green Button" feature which allows the system to be placed in to one or more user-defined power saving modes called "steps".
|
||||
Each step can take actions such as trimming down the 100% level on selected lighting dimmers by a specified percentage, shutting off certain loads, modifying thermostat settings, etc.
|
||||
@ -501,7 +610,7 @@ Thing configuration file example:
|
||||
Thing greenmode greenmode [ integrationId=22 ]
|
||||
```
|
||||
|
||||
### Timeclock
|
||||
#### Timeclock
|
||||
|
||||
RadioRA 2 and Homeworks QS have timeclock subsystems that provide scheduled execution of tasks at set times, randomized times or at arbitrary offsets from local sunrise/sunset.
|
||||
The tasks executed depend on the currently selected timeclock mode (e.g. Normal, Away, Suspend) and the modes themselves are user-definable (RadioRA 2 only).
|
||||
@ -545,7 +654,7 @@ then
|
||||
end
|
||||
```
|
||||
|
||||
### System State Variables (HomeWorks QS only) [**Experimental**]
|
||||
#### System State Variables (HomeWorks QS only) [**Experimental**]
|
||||
|
||||
HomeWorks QS systems allow for conditional programming logic based on state variables.
|
||||
The **sysvar** thing allows state variable values to be read and set from openHAB.
|
||||
@ -569,7 +678,10 @@ The following is a summary of channels for all RadioRA 2 binding things:
|
||||
|---------------------|-------------------|---------------|--------------------------------------------- |
|
||||
| dimmer | lightlevel | Dimmer | Increase/decrease the light level |
|
||||
| switch | switchstatus | Switch | On/off status of the switch |
|
||||
| occupancysensor | occupancystatus | Switch | Occupancy status |
|
||||
| fan | fanspeed | String | Set/get fan speed using string options |
|
||||
| fan | fanlevel | Dimmer | Set/get fan speed using a dimmer channel |
|
||||
| occupancysensor | occupancystatus | Switch | Occupancy sensor status |
|
||||
| ogroup | groupstate | String | Occupancy group status |
|
||||
| cco | switchstatus | Switch | On/off status of the CCO |
|
||||
| keypads (all) | button* | Switch | Keypad button |
|
||||
| keypads(except pico)| led* | Switch | LED indicator for the associated button |
|
||||
@ -595,7 +707,10 @@ Appropriate channels will be created automatically by the keypad, ttkeypad, intl
|
||||
|-----------|---------------|--------------|-------------------------------------------------------|
|
||||
|dimmer |lightlevel |PercentType |OnOffType, PercentType (rounded/truncated to integer) |
|
||||
|switch |switchstatus |OnOffType |OnOffType |
|
||||
|fan |fanspeed |StringType |"OFF","LOW","MEDIUM","MEDIUMHIGH","HIGH" |
|
||||
|fan |fanlevel |PercentType |OnOffType, PercentType |
|
||||
|occ. sensor|occupancystatus|OnOffType |(*readonly*) |
|
||||
|ogroup |groupstate |StringType |"OCCUPIED","UNOCCUPIED","UNKNOWN" (*readonly*) |
|
||||
|cco |switchstatus |OnOffType |OnOffType, RefreshType |
|
||||
|keypads |button* |OnOffType |OnOffType |
|
||||
| |led* |OnOffType |OnOffType, RefreshType |
|
||||
|
40
bundles/org.openhab.binding.lutron/doc/leapnotes.md
Normal file
40
bundles/org.openhab.binding.lutron/doc/leapnotes.md
Normal file
@ -0,0 +1,40 @@
|
||||
# Configuring LEAP Authentication
|
||||
|
||||
Unlike LIP, which was designed to use a simple serial or telnet connection and authenticates using a username/password, LEAP uses a SSL connection and authenticates using certificates.
|
||||
This necessarily makes configuration more complicated.
|
||||
There are several open source utilities available for generating the certificate files necessary to access your Caseta or RA2 Select hub.
|
||||
One good choice is the get_lutron_cert.py script included with the pylutron library which is available on Github at https://github.com/gurumitts/pylutron-caseta .
|
||||
On a unix system, you can easily retrieve it using curl with a command like:
|
||||
|
||||
```
|
||||
curl https://raw.githubusercontent.com/gurumitts/pylutron-caseta/dev/get_lutron_cert.py >get_lutron_cert.py
|
||||
```
|
||||
|
||||
Remember that the get_lutron_cert.py script must be run using python3, not 2!
|
||||
Also, the script will prompt you to press the button on your smart hub to authorize key generation, so you should be somewhere near the hub when you run it.
|
||||
Running it will not affect your existing hub configuration or Lutron app installations.
|
||||
When it has completed, it will have generated three files: caseta.crt, caseta.key, and caseta-bridge.crt.
|
||||
|
||||
Once the key and certificate files have been generated, you will need to load them into a java keystore.
|
||||
|
||||
|
||||
You can load a keystore from the key and certificate files on a linux system with the following commands.
|
||||
You’ll need access to both the java keytool and openssl.
|
||||
|
||||
```
|
||||
openssl pkcs12 -export -in caseta.crt -inkey caseta.key -out caseta.p12 -name caseta
|
||||
|
||||
keytool -importkeystore -destkeystore lutron.keystore -srckeystore caseta.p12 -srcstoretype PKCS12 -srcstorepass secret -alias caseta
|
||||
|
||||
keytool -importcert -file caseta-bridge.crt -keystore lutron.keystore -alias caseta-bridge
|
||||
```
|
||||
|
||||
Respond to the password prompt(s) with a password, and then use that password in the -srcstorepass parameter of the keytool command and in the keystorePassword parameter for leapbridge.
|
||||
In the example above, the pkcs12 store password was set to “secret”, but hopefully you can think of a better one.
|
||||
The lutron.keystore file that you end up with is the one you’ll need to give the binding access to.
|
||||
The caseta.p12 file is just an intermediate file that you can delete later.
|
||||
|
||||
Finally you’ll then need to set the ipAddress, keystore, and keystorePassword parameters of the leapbridge thing.
|
||||
The ipAddress will be set for you if you used discovery to detect a Caseta Smart Bridge.
|
||||
|
||||
|
@ -28,6 +28,7 @@ public class LutronBindingConstants {
|
||||
|
||||
// Bridge Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_IPBRIDGE = new ThingTypeUID(BINDING_ID, "ipbridge");
|
||||
public static final ThingTypeUID THING_TYPE_LEAPBRIDGE = new ThingTypeUID(BINDING_ID, "leapbridge");
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_DIMMER = new ThingTypeUID(BINDING_ID, "dimmer");
|
||||
@ -51,12 +52,15 @@ public class LutronBindingConstants {
|
||||
public static final ThingTypeUID THING_TYPE_PALLADIOMKEYPAD = new ThingTypeUID(BINDING_ID, "palladiomkeypad");
|
||||
public static final ThingTypeUID THING_TYPE_WCI = new ThingTypeUID(BINDING_ID, "wci");
|
||||
public static final ThingTypeUID THING_TYPE_SYSVAR = new ThingTypeUID(BINDING_ID, "sysvar");
|
||||
public static final ThingTypeUID THING_TYPE_OGROUP = new ThingTypeUID(BINDING_ID, "ogroup");
|
||||
public static final ThingTypeUID THING_TYPE_FAN = new ThingTypeUID(BINDING_ID, "fan");
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_LIGHTLEVEL = "lightlevel";
|
||||
public static final String CHANNEL_SHADELEVEL = "shadelevel";
|
||||
public static final String CHANNEL_SWITCH = "switchstatus";
|
||||
public static final String CHANNEL_OCCUPANCYSTATUS = "occupancystatus";
|
||||
public static final String CHANNEL_GROUPSTATE = "groupstate";
|
||||
public static final String CHANNEL_CLOCKMODE = "clockmode";
|
||||
public static final String CHANNEL_SUNRISE = "sunrise";
|
||||
public static final String CHANNEL_SUNSET = "sunset";
|
||||
@ -67,6 +71,9 @@ public class LutronBindingConstants {
|
||||
public static final String CHANNEL_BLINDLIFTLEVEL = "blindliftlevel";
|
||||
public static final String CHANNEL_BLINDTILTLEVEL = "blindtiltlevel";
|
||||
public static final String CHANNEL_VARSTATE = "varstate";
|
||||
public static final String CHANNEL_FANSPEED = "fanspeed";
|
||||
public static final String CHANNEL_FANLEVEL = "fanlevel";
|
||||
public static final String CHANNEL_COMMAND = "command"; // For LEAP bridge debugging
|
||||
|
||||
// Bridge config properties (used by discovery service)
|
||||
public static final String HOST = "ipAddress";
|
||||
|
@ -31,12 +31,15 @@ import org.openhab.binding.lutron.internal.grxprg.PrgConstants;
|
||||
import org.openhab.binding.lutron.internal.handler.BlindHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.CcoHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.DimmerHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.FanHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.GrafikEyeKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.GreenModeHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.IPBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.IntlKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.KeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.MaintainedCcoHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.OGroupHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.OccupancySensorHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.PalladiomKeypadHandler;
|
||||
import org.openhab.binding.lutron.internal.handler.PicoKeypadHandler;
|
||||
@ -87,11 +90,13 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
// Used by LutronDeviceDiscoveryService to discover these types
|
||||
public static final Set<ThingTypeUID> DISCOVERABLE_DEVICE_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_DIMMER, THING_TYPE_SWITCH, THING_TYPE_OCCUPANCYSENSOR,
|
||||
THING_TYPE_KEYPAD, THING_TYPE_TTKEYPAD, THING_TYPE_INTLKEYPAD, THING_TYPE_PICO,
|
||||
THING_TYPE_VIRTUALKEYPAD, THING_TYPE_VCRX, THING_TYPE_CCO, THING_TYPE_SHADE, THING_TYPE_TIMECLOCK,
|
||||
THING_TYPE_GREENMODE, THING_TYPE_QSIO, THING_TYPE_GRAFIKEYEKEYPAD, THING_TYPE_BLIND,
|
||||
THING_TYPE_PALLADIOMKEYPAD, THING_TYPE_WCI).collect(Collectors.toSet()));
|
||||
.unmodifiableSet(Stream
|
||||
.of(THING_TYPE_DIMMER, THING_TYPE_SWITCH, THING_TYPE_OCCUPANCYSENSOR, THING_TYPE_KEYPAD,
|
||||
THING_TYPE_TTKEYPAD, THING_TYPE_INTLKEYPAD, THING_TYPE_PICO, THING_TYPE_VIRTUALKEYPAD,
|
||||
THING_TYPE_VCRX, THING_TYPE_CCO, THING_TYPE_SHADE, THING_TYPE_TIMECLOCK,
|
||||
THING_TYPE_GREENMODE, THING_TYPE_QSIO, THING_TYPE_GRAFIKEYEKEYPAD, THING_TYPE_BLIND,
|
||||
THING_TYPE_PALLADIOMKEYPAD, THING_TYPE_WCI, THING_TYPE_OGROUP, THING_TYPE_FAN)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
// Used by the HwDiscoveryService
|
||||
public static final Set<ThingTypeUID> HW_DISCOVERABLE_DEVICE_TYPES_UIDS = Collections
|
||||
@ -99,11 +104,13 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
// Other types that can be initiated but not discovered
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections
|
||||
.unmodifiableSet(Stream.of(THING_TYPE_IPBRIDGE, PrgConstants.THING_TYPE_PRGBRIDGE,
|
||||
PrgConstants.THING_TYPE_GRAFIKEYE, RadioRAConstants.THING_TYPE_RS232,
|
||||
RadioRAConstants.THING_TYPE_DIMMER, RadioRAConstants.THING_TYPE_SWITCH,
|
||||
RadioRAConstants.THING_TYPE_PHANTOM, HwConstants.THING_TYPE_HWSERIALBRIDGE, THING_TYPE_CCO_PULSED,
|
||||
THING_TYPE_CCO_MAINTAINED, THING_TYPE_SYSVAR).collect(Collectors.toSet()));
|
||||
.unmodifiableSet(Stream
|
||||
.of(THING_TYPE_IPBRIDGE, THING_TYPE_LEAPBRIDGE, PrgConstants.THING_TYPE_PRGBRIDGE,
|
||||
PrgConstants.THING_TYPE_GRAFIKEYE, RadioRAConstants.THING_TYPE_RS232,
|
||||
RadioRAConstants.THING_TYPE_DIMMER, RadioRAConstants.THING_TYPE_SWITCH,
|
||||
RadioRAConstants.THING_TYPE_PHANTOM, HwConstants.THING_TYPE_HWSERIALBRIDGE,
|
||||
THING_TYPE_CCO_PULSED, THING_TYPE_CCO_MAINTAINED, THING_TYPE_SYSVAR)
|
||||
.collect(Collectors.toSet()));
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronHandlerFactory.class);
|
||||
|
||||
@ -135,6 +142,9 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
IPBridgeHandler bridgeHandler = new IPBridgeHandler((Bridge) thing);
|
||||
registerDiscoveryService(bridgeHandler);
|
||||
return bridgeHandler;
|
||||
} else if (thingTypeUID.equals(THING_TYPE_LEAPBRIDGE)) {
|
||||
LeapBridgeHandler bridgeHandler = new LeapBridgeHandler((Bridge) thing);
|
||||
return bridgeHandler;
|
||||
} else if (thingTypeUID.equals(THING_TYPE_DIMMER)) {
|
||||
return new DimmerHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_SHADE)) {
|
||||
@ -177,6 +187,10 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
return new BlindHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_SYSVAR)) {
|
||||
return new SysvarHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_OGROUP)) {
|
||||
return new OGroupHandler(thing);
|
||||
} else if (thingTypeUID.equals(THING_TYPE_FAN)) {
|
||||
return new FanHandler(thing);
|
||||
} else if (thingTypeUID.equals(PrgConstants.THING_TYPE_PRGBRIDGE)) {
|
||||
return new PrgBridgeHandler((Bridge) thing);
|
||||
} else if (thingTypeUID.equals(PrgConstants.THING_TYPE_GRAFIKEYE)) {
|
||||
@ -203,7 +217,7 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
if (thingHandler instanceof IPBridgeHandler) {
|
||||
ServiceRegistration<?> serviceReg = discoveryServiceRegMap.remove(thingHandler.getThing().getUID());
|
||||
if (serviceReg != null) {
|
||||
logger.debug("Unregistering discovery service.");
|
||||
logger.debug("Unregistering device discovery service.");
|
||||
serviceReg.unregister();
|
||||
}
|
||||
}
|
||||
@ -212,10 +226,10 @@ public class LutronHandlerFactory extends BaseThingHandlerFactory {
|
||||
/**
|
||||
* Register a discovery service for an IP bridge handler.
|
||||
*
|
||||
* @param bridgeHandler bridge handler for which to register the discovery service
|
||||
* @param bridgeHandler IP bridge handler for which to register the discovery service
|
||||
*/
|
||||
private synchronized void registerDiscoveryService(IPBridgeHandler bridgeHandler) {
|
||||
logger.debug("Registering discovery service.");
|
||||
logger.debug("Registering XML device discovery service.");
|
||||
LutronDeviceDiscoveryService discoveryService = new LutronDeviceDiscoveryService(bridgeHandler, httpClient);
|
||||
bridgeHandler.setDiscoveryService(discoveryService);
|
||||
discoveryServiceRegMap.put(bridgeHandler.getThing().getUID(),
|
||||
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Configuration settings for a {@link org.openhab.binding.lutron.internal.handler.FanHandler}.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FanConfig {
|
||||
public int integrationId = 0; // Initialize to invalid value
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Configuration settings for an {@link org.openhab.binding.lutron.internal.handler.LeapBridgeHandler}.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapBridgeConfig {
|
||||
public @Nullable String ipAddress;
|
||||
public int port = 8081;
|
||||
public @Nullable String keystore;
|
||||
public @Nullable String keystorePassword;
|
||||
public boolean certValidate = false;
|
||||
public int reconnect;
|
||||
public int heartbeat;
|
||||
public int delay = 0;
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.config;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Configuration settings for a {@link org.openhab.binding.lutron.internal.handler.OGroupHandler}.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OGroupConfig {
|
||||
public int integrationId;
|
||||
}
|
@ -0,0 +1,222 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.LutronHandlerFactory;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
|
||||
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.ThingTypeUID;
|
||||
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;
|
||||
|
||||
/**
|
||||
* The {@link LeapDeviceDiscoveryService} discovers devices paired with Lutron bridges using the LEAP protocol.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapDeviceDiscoveryService extends AbstractDiscoveryService
|
||||
implements DiscoveryService, ThingHandlerService {
|
||||
|
||||
private static final int DISCOVERY_SERVICE_TIMEOUT = 0; // seconds
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LeapDeviceDiscoveryService.class);
|
||||
|
||||
/** Area number to name map **/
|
||||
private @Nullable Map<Integer, String> areaMap;
|
||||
private @Nullable List<OccupancyGroup> oGroupList;
|
||||
|
||||
private @NonNullByDefault({}) LeapBridgeHandler bridgeHandler;
|
||||
|
||||
public LeapDeviceDiscoveryService() {
|
||||
super(LutronHandlerFactory.DISCOVERABLE_DEVICE_TYPES_UIDS, DISCOVERY_SERVICE_TIMEOUT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setThingHandler(ThingHandler handler) {
|
||||
if (handler instanceof LeapBridgeHandler) {
|
||||
bridgeHandler = (LeapBridgeHandler) handler;
|
||||
bridgeHandler.setDiscoveryService(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingHandler getThingHandler() {
|
||||
return bridgeHandler;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void startScan() {
|
||||
logger.debug("Active discovery scan started");
|
||||
bridgeHandler.queryDiscoveryData();
|
||||
}
|
||||
|
||||
public void processDeviceDefinitions(List<Device> deviceList) {
|
||||
for (Device device : deviceList) {
|
||||
// Integer zoneid = device.getZone();
|
||||
Integer deviceId = device.getDevice();
|
||||
String label = device.getFullyQualifiedName();
|
||||
if (deviceId > 0) {
|
||||
logger.debug("Discovered device: {} type: {} id: {}", label, device.deviceType, deviceId);
|
||||
if (device.deviceType != null) {
|
||||
switch (device.deviceType) {
|
||||
case "SmartBridge":
|
||||
case "RA2SelectMainRepeater":
|
||||
notifyDiscovery(THING_TYPE_VIRTUALKEYPAD, deviceId, label, "model", "Caseta");
|
||||
break;
|
||||
case "WallDimmer":
|
||||
case "PlugInDimmer":
|
||||
notifyDiscovery(THING_TYPE_DIMMER, deviceId, label);
|
||||
break;
|
||||
case "WallSwitch":
|
||||
case "PlugInSwitch":
|
||||
notifyDiscovery(THING_TYPE_SWITCH, deviceId, label);
|
||||
break;
|
||||
case "CasetaFanSpeedController":
|
||||
case "MaestroFanSpeedController":
|
||||
notifyDiscovery(THING_TYPE_FAN, deviceId, label);
|
||||
break;
|
||||
case "Pico2Button":
|
||||
notifyDiscovery(THING_TYPE_PICO, deviceId, label, "model", "2B");
|
||||
break;
|
||||
case "Pico2ButtonRaiseLower":
|
||||
notifyDiscovery(THING_TYPE_PICO, deviceId, label, "model", "2BRL");
|
||||
break;
|
||||
case "Pico3ButtonRaiseLower":
|
||||
notifyDiscovery(THING_TYPE_PICO, deviceId, label, "model", "3BRL");
|
||||
break;
|
||||
case "SerenaRollerShade":
|
||||
case "SerenaHoneycombShade":
|
||||
case "TriathlonRollerShade":
|
||||
case "TriathlonHoneycombShade":
|
||||
case "QsWirelessShade":
|
||||
notifyDiscovery(THING_TYPE_SHADE, deviceId, label);
|
||||
break;
|
||||
case "RPSOccupancySensor":
|
||||
// Don't discover sensors. Using occupancy groups instead.
|
||||
break;
|
||||
default:
|
||||
logger.info("Unrecognized device type: {}", device.deviceType);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void processOccupancyGroups() {
|
||||
Map<Integer, String> areaMap = this.areaMap;
|
||||
List<OccupancyGroup> oGroupList = this.oGroupList;
|
||||
|
||||
if (areaMap != null && oGroupList != null) {
|
||||
logger.trace("Processing occupancy groups");
|
||||
for (OccupancyGroup oGroup : oGroupList) {
|
||||
logger.trace("Processing OccupancyGroup: {}", oGroup.href);
|
||||
int groupNum = oGroup.getOccupancyGroup();
|
||||
// Only process occupancy groups with associated occupancy sensors
|
||||
if (groupNum > 0 && oGroup.associatedSensors != null) {
|
||||
String areaName;
|
||||
if (oGroup.associatedAreas.length > 0) {
|
||||
// If multiple associated areas are listed, use only the first
|
||||
areaName = areaMap.get(oGroup.associatedAreas[0].getAreaNumber());
|
||||
} else {
|
||||
areaName = "Occupancy Group";
|
||||
}
|
||||
logger.debug("Discovered occupancy group: {} areas: {} area name: {}", groupNum,
|
||||
oGroup.associatedAreas.length, areaName);
|
||||
notifyDiscovery(THING_TYPE_OGROUP, groupNum, areaName);
|
||||
}
|
||||
}
|
||||
this.areaMap = null;
|
||||
this.oGroupList = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void setOccupancyGroups(List<OccupancyGroup> oGroupList) {
|
||||
logger.trace("Setting occupancy groups list");
|
||||
this.oGroupList = oGroupList;
|
||||
|
||||
if (areaMap != null) {
|
||||
processOccupancyGroups();
|
||||
}
|
||||
}
|
||||
|
||||
public void setAreas(List<Area> areaList) {
|
||||
Map<Integer, String> areaMap = new HashMap<>();
|
||||
|
||||
logger.trace("Setting areas map");
|
||||
for (Area area : areaList) {
|
||||
int areaNum = area.getArea();
|
||||
logger.trace("Inserting area into map - num: {} name: {}", areaNum, area.name);
|
||||
if (areaNum > 0) {
|
||||
areaMap.put(areaNum, area.name);
|
||||
} else {
|
||||
logger.debug("Ignoring area with unparsable href {}", area.href);
|
||||
}
|
||||
}
|
||||
this.areaMap = areaMap;
|
||||
|
||||
if (oGroupList != null) {
|
||||
processOccupancyGroups();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyDiscovery(ThingTypeUID thingTypeUID, @Nullable Integer integrationId, String label,
|
||||
@Nullable String propName, @Nullable Object propValue) {
|
||||
if (integrationId == null) {
|
||||
logger.debug("Discovered {} with no integration ID", label);
|
||||
return;
|
||||
}
|
||||
ThingUID bridgeUID = this.bridgeHandler.getThing().getUID();
|
||||
ThingUID uid = new ThingUID(thingTypeUID, bridgeUID, integrationId.toString());
|
||||
|
||||
Map<String, Object> properties = new HashMap<>();
|
||||
|
||||
properties.put(INTEGRATION_ID, integrationId);
|
||||
if (propName != null && propValue != null) {
|
||||
properties.put(propName, propValue);
|
||||
}
|
||||
|
||||
DiscoveryResult result = DiscoveryResultBuilder.create(uid).withBridge(bridgeUID).withLabel(label)
|
||||
.withProperties(properties).withRepresentationProperty(INTEGRATION_ID).build();
|
||||
thingDiscovered(result);
|
||||
logger.trace("Discovered {}", uid);
|
||||
}
|
||||
|
||||
private void notifyDiscovery(ThingTypeUID thingTypeUID, Integer integrationId, String label) {
|
||||
notifyDiscovery(thingTypeUID, integrationId, label, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deactivate() {
|
||||
super.deactivate();
|
||||
}
|
||||
}
|
@ -94,7 +94,7 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LutronDeviceDiscoveryService.class);
|
||||
|
||||
private IPBridgeHandler bridgeHandler;
|
||||
private final IPBridgeHandler bridgeHandler;
|
||||
private DbXmlInfoReader dbXmlInfoReader = new DbXmlInfoReader();
|
||||
|
||||
private final HttpClient httpClient;
|
||||
@ -111,8 +111,9 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
@Override
|
||||
protected synchronized void startScan() {
|
||||
Future<?> scanTask = this.scanTask;
|
||||
if (scanTask == null || scanTask.isDone()) {
|
||||
scanTask = scheduler.submit(this::asyncDiscoveryTask);
|
||||
this.scanTask = scheduler.submit(this::asyncDiscoveryTask);
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,7 +132,7 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
private void readDeviceDatabase() {
|
||||
Project project = null;
|
||||
|
||||
if (bridgeHandler == null || bridgeHandler.getIPBridgeConfig() == null) {
|
||||
if (bridgeHandler.getIPBridgeConfig() == null) {
|
||||
logger.debug("Unable to get bridge config. Exiting.");
|
||||
return;
|
||||
}
|
||||
@ -240,9 +241,9 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
|
||||
for (DeviceNode deviceNode : area.getDeviceNodes()) {
|
||||
if (deviceNode instanceof DeviceGroup) {
|
||||
processDeviceGroup((DeviceGroup) deviceNode, context);
|
||||
processDeviceGroup(area, (DeviceGroup) deviceNode, context);
|
||||
} else if (deviceNode instanceof Device) {
|
||||
processDevice((Device) deviceNode, context);
|
||||
processDevice(area, (Device) deviceNode, context);
|
||||
}
|
||||
}
|
||||
|
||||
@ -257,17 +258,17 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
context.pop();
|
||||
}
|
||||
|
||||
private void processDeviceGroup(DeviceGroup deviceGroup, Stack<String> context) {
|
||||
private void processDeviceGroup(Area area, DeviceGroup deviceGroup, Stack<String> context) {
|
||||
context.push(deviceGroup.getName());
|
||||
|
||||
for (Device device : deviceGroup.getDevices()) {
|
||||
processDevice(device, context);
|
||||
processDevice(area, device, context);
|
||||
}
|
||||
|
||||
context.pop();
|
||||
}
|
||||
|
||||
private void processDevice(Device device, Stack<String> context) {
|
||||
private void processDevice(Area area, Device device, Stack<String> context) {
|
||||
List<Integer> buttons;
|
||||
KeypadConfig kpConfig;
|
||||
String kpModel;
|
||||
@ -280,6 +281,7 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
switch (type) {
|
||||
case MOTION_SENSOR:
|
||||
notifyDiscovery(THING_TYPE_OCCUPANCYSENSOR, device.getIntegrationId(), label);
|
||||
notifyDiscovery(THING_TYPE_OGROUP, area.getIntegrationId(), area.getName());
|
||||
break;
|
||||
|
||||
case SEETOUCH_KEYPAD:
|
||||
@ -389,10 +391,13 @@ public class LutronDeviceDiscoveryService extends AbstractDiscoveryService {
|
||||
case FLUORESCENT_DB:
|
||||
case ZERO_TO_TEN:
|
||||
case AUTO_DETECT:
|
||||
case CEILING_FAN_TYPE:
|
||||
notifyDiscovery(THING_TYPE_DIMMER, output.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case CEILING_FAN_TYPE:
|
||||
notifyDiscovery(THING_TYPE_FAN, output.getIntegrationId(), label);
|
||||
break;
|
||||
|
||||
case NON_DIM:
|
||||
case NON_DIM_INC:
|
||||
case NON_DIM_ELV:
|
||||
|
@ -38,8 +38,8 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link LutronMdnsBridgeDiscoveryService} discovers Lutron Caseta Smart Bridge Pro and eventually RA2 Select Main
|
||||
* Repeater and other Lutron devices on the network using mDNS.
|
||||
* The {@link LutronMdnsBridgeDiscoveryService} discovers Lutron Caseta Smart Bridge, Caseta Smart Bridge Pro, RA2
|
||||
* Select Main Repeater, and other Lutron devices on the network using mDNS.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@ -51,11 +51,15 @@ public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipan
|
||||
private static final String LUTRON_MDNS_SERVICE_TYPE = "_lutron._tcp.local.";
|
||||
|
||||
private static final String PRODFAM_CASETA = "Caseta";
|
||||
private static final String PRODTYP_CASETA_SB = "Smart Bridge";
|
||||
private static final String DEVCLASS_CASETA_SB = "08040100";
|
||||
private static final String PRODTYP_CASETA_SBP2 = "Smart Bridge Pro 2";
|
||||
private static final String DEVCLASS_CASETA_SBP2 = "08050100";
|
||||
|
||||
private static final String PRODFAM_RA2_SELECT = "RA2 Select";
|
||||
private static final String PRODTYP_RA2_SELECT = "Main Repeater";
|
||||
private static final String DEVCLASS_RA2_SELECT = "080E0401";
|
||||
|
||||
private static final String DEVCLASS_CONNECT_BRIDGE = "08090301";
|
||||
private static final String DEFAULT_LABEL = "Unknown Lutron bridge";
|
||||
|
||||
@ -108,7 +112,11 @@ public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipan
|
||||
String bridgeHostName = ipAddresses[0].getHostName();
|
||||
logger.debug("Lutron mDNS bridge hostname: {}", bridgeHostName);
|
||||
|
||||
if (DEVCLASS_CASETA_SBP2.equals(devclass)) {
|
||||
if (DEVCLASS_CASETA_SB.equals(devclass)) {
|
||||
properties.put(PROPERTY_PRODFAM, PRODFAM_CASETA);
|
||||
properties.put(PROPERTY_PRODTYP, PRODTYP_CASETA_SB);
|
||||
label = PRODFAM_CASETA + " " + PRODTYP_CASETA_SB;
|
||||
} else if (DEVCLASS_CASETA_SBP2.equals(devclass)) {
|
||||
properties.put(PROPERTY_PRODFAM, PRODFAM_CASETA);
|
||||
properties.put(PROPERTY_PRODTYP, PRODTYP_CASETA_SBP2);
|
||||
label = PRODFAM_CASETA + " " + PRODTYP_CASETA_SBP2;
|
||||
@ -122,7 +130,7 @@ public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipan
|
||||
} else {
|
||||
logger.info("Lutron device with unknown DEVCLASS discovered via mDNS: {}. Configure device manually.",
|
||||
devclass);
|
||||
return null; // For now, exit if service has unknown DEVCLASS
|
||||
return null; // Exit if service has unknown DEVCLASS
|
||||
}
|
||||
|
||||
if (!bridgeHostName.equals(ipAddresses[0].getHostAddress())) {
|
||||
@ -161,10 +169,15 @@ public class LutronMdnsBridgeDiscoveryService implements MDNSDiscoveryParticipan
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||
String serial = getSerial(service);
|
||||
String devclass = service.getPropertyString("DEVCLASS");
|
||||
if (serial == null) {
|
||||
return null;
|
||||
} else {
|
||||
return new ThingUID(THING_TYPE_IPBRIDGE, serial);
|
||||
if (DEVCLASS_CASETA_SB.equals(devclass)) {
|
||||
return new ThingUID(THING_TYPE_LEAPBRIDGE, serial);
|
||||
} else {
|
||||
return new ThingUID(THING_TYPE_IPBRIDGE, serial);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import java.util.List;
|
||||
*/
|
||||
public class Area {
|
||||
private String name;
|
||||
private Integer integrationId;
|
||||
private List<DeviceNode> deviceNodes;
|
||||
private List<Output> outputs;
|
||||
private List<Area> areas;
|
||||
@ -32,6 +33,10 @@ public class Area {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Integer getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
public List<DeviceNode> getDeviceNodes() {
|
||||
return deviceNodes != null ? deviceNodes : Collections.<DeviceNode> emptyList();
|
||||
}
|
||||
|
@ -23,7 +23,9 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.keypadconfig.KeypadConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.DeviceCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.OpenClosedType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@ -46,23 +48,14 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Bob Adair - Initial contribution, based partly on Allan Tong's KeypadHandler class
|
||||
*/
|
||||
public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
|
||||
protected static final Integer ACTION_PRESS = 3;
|
||||
protected static final Integer ACTION_RELEASE = 4;
|
||||
protected static final Integer ACTION_HOLD = 5;
|
||||
protected static final Integer ACTION_LED_STATE = 9;
|
||||
|
||||
protected static final Integer LED_OFF = 0;
|
||||
protected static final Integer LED_ON = 1;
|
||||
protected static final Integer LED_FLASH = 2; // Same as 1 on RA2 keypads
|
||||
protected static final Integer LED_RAPIDFLASH = 3; // Same as 1 on RA2 keypads
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(BaseKeypadHandler.class);
|
||||
|
||||
protected List<KeypadComponent> buttonList = new ArrayList<>();
|
||||
protected List<KeypadComponent> ledList = new ArrayList<>();
|
||||
protected List<KeypadComponent> cciList = new ArrayList<>();
|
||||
|
||||
Map<Integer, Integer> leapButtonMap;
|
||||
|
||||
protected int integrationId;
|
||||
protected String model;
|
||||
protected Boolean autoRelease;
|
||||
@ -75,6 +68,7 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
private final Object asyncInitLock = new Object();
|
||||
|
||||
protected KeypadConfig kp;
|
||||
protected TargetType commandTargetType = TargetType.KEYPAD; // For LEAP bridge
|
||||
|
||||
public BaseKeypadHandler(Thing thing) {
|
||||
super(thing);
|
||||
@ -123,7 +117,7 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
List<Channel> channelList = new ArrayList<>();
|
||||
List<Channel> existingChannels = getThing().getChannels();
|
||||
|
||||
if (existingChannels != null && !existingChannels.isEmpty()) {
|
||||
if (!existingChannels.isEmpty()) {
|
||||
// Clear existing channels
|
||||
logger.debug("Clearing existing channels for keypad {}", integrationId);
|
||||
ThingBuilder thingBuilder = editThing();
|
||||
@ -261,7 +255,7 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
// To reduce query volume, query only 1st LED and LEDs with linked channels.
|
||||
for (KeypadComponent component : ledList) {
|
||||
if (component.id() == ledList.get(0).id() || isLinked(channelFromComponent(component.id()))) {
|
||||
queryDevice(component.id(), ACTION_LED_STATE);
|
||||
queryDevice(commandTargetType, component.id(), DeviceCommand.ACTION_LED_STATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -290,12 +284,12 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
// For LEDs, handle RefreshType and OnOffType commands
|
||||
if (isLed(componentID)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryDevice(componentID, ACTION_LED_STATE);
|
||||
queryDevice(commandTargetType, componentID, DeviceCommand.ACTION_LED_STATE);
|
||||
} else if (command instanceof OnOffType) {
|
||||
if (command == OnOffType.ON) {
|
||||
device(componentID, ACTION_LED_STATE, LED_ON);
|
||||
device(commandTargetType, componentID, null, DeviceCommand.ACTION_LED_STATE, DeviceCommand.LED_ON);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
device(componentID, ACTION_LED_STATE, LED_OFF);
|
||||
device(commandTargetType, componentID, null, DeviceCommand.ACTION_LED_STATE, DeviceCommand.LED_OFF);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Invalid command {} received for channel {} device {}", command, channelUID,
|
||||
@ -307,13 +301,15 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
// For buttons, handle OnOffType commands
|
||||
if (isButton(componentID)) {
|
||||
if (command instanceof OnOffType) {
|
||||
// Annotate commands with LEAP button number for LEAP bridge
|
||||
Integer leapComponent = (this.leapButtonMap == null) ? null : leapButtonMap.get(componentID);
|
||||
if (command == OnOffType.ON) {
|
||||
device(componentID, ACTION_PRESS);
|
||||
device(commandTargetType, componentID, leapComponent, DeviceCommand.ACTION_PRESS, null);
|
||||
if (autoRelease) {
|
||||
device(componentID, ACTION_RELEASE);
|
||||
device(commandTargetType, componentID, leapComponent, DeviceCommand.ACTION_RELEASE, null);
|
||||
}
|
||||
} else if (command == OnOffType.OFF) {
|
||||
device(componentID, ACTION_RELEASE);
|
||||
device(commandTargetType, componentID, leapComponent, DeviceCommand.ACTION_RELEASE, null);
|
||||
}
|
||||
} else {
|
||||
logger.warn("Invalid command type {} received for channel {} device {}", command, channelUID,
|
||||
@ -342,7 +338,7 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
|
||||
// if this channel is for an LED, query the Lutron controller for the current state
|
||||
if (isLed(id)) {
|
||||
queryDevice(id, ACTION_LED_STATE);
|
||||
queryDevice(commandTargetType, id, DeviceCommand.ACTION_LED_STATE);
|
||||
}
|
||||
// Button and CCI state can't be queried, only monitored for updates.
|
||||
// Init button state to OFF on channel init.
|
||||
@ -368,16 +364,16 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
ChannelUID channelUID = channelFromComponent(component);
|
||||
|
||||
if (channelUID != null) {
|
||||
if (ACTION_LED_STATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
if (DeviceCommand.ACTION_LED_STATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE); // set thing status online if this is an initial response
|
||||
}
|
||||
if (LED_ON.toString().equals(parameters[2])) {
|
||||
if (DeviceCommand.LED_ON.toString().equals(parameters[2])) {
|
||||
updateState(channelUID, OnOffType.ON);
|
||||
} else if (LED_OFF.toString().equals(parameters[2])) {
|
||||
} else if (DeviceCommand.LED_OFF.toString().equals(parameters[2])) {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
}
|
||||
} else if (ACTION_PRESS.toString().equals(parameters[1])) {
|
||||
} else if (DeviceCommand.ACTION_PRESS.toString().equals(parameters[1])) {
|
||||
if (isButton(component)) {
|
||||
updateState(channelUID, OnOffType.ON);
|
||||
if (autoRelease) {
|
||||
@ -386,13 +382,13 @@ public abstract class BaseKeypadHandler extends LutronHandler {
|
||||
} else { // component is CCI
|
||||
updateState(channelUID, OpenClosedType.CLOSED);
|
||||
}
|
||||
} else if (ACTION_RELEASE.toString().equals(parameters[1])) {
|
||||
} else if (DeviceCommand.ACTION_RELEASE.toString().equals(parameters[1])) {
|
||||
if (isButton(component)) {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
} else { // component is CCI
|
||||
updateState(channelUID, OpenClosedType.OPEN);
|
||||
}
|
||||
} else if (ACTION_HOLD.toString().equals(parameters[1])) {
|
||||
} else if (DeviceCommand.ACTION_HOLD.toString().equals(parameters[1])) {
|
||||
updateState(channelUID, OnOffType.OFF); // Signal a release if we receive a hold code as we will not
|
||||
// get a subsequent release.
|
||||
}
|
||||
|
@ -17,7 +17,9 @@ import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.openhab.binding.lutron.internal.config.BlindConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
@ -37,16 +39,6 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Bob Adair - Initial contribution based on Alan Tong's DimmerHandler
|
||||
*/
|
||||
public class BlindHandler extends LutronHandler {
|
||||
private static final Integer ACTION_LIFTLEVEL = 1;
|
||||
private static final Integer ACTION_TILTLEVEL = 9;
|
||||
private static final Integer ACTION_LIFTTILTLEVEL = 10;
|
||||
private static final Integer ACTION_STARTRAISINGTILT = 11;
|
||||
private static final Integer ACTION_STARTLOWERINGTILT = 12;
|
||||
private static final Integer ACTION_STOPTILT = 13;
|
||||
private static final Integer ACTION_STARTRAISINGLIFT = 14;
|
||||
private static final Integer ACTION_STARTLOWERINGLIFT = 15;
|
||||
private static final Integer ACTION_STOPLIFT = 16;
|
||||
private static final Integer ACTION_POSITION_UPDATE = 32; // undocumented in integration protocol guide
|
||||
private static final Integer PARAMETER_POSITION_UPDATE = 2; // undocumented in integration protocol guide
|
||||
|
||||
private int tiltMax = 100; // max 50 for horizontal sheer, 100 for venetian
|
||||
@ -96,8 +88,9 @@ public class BlindHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_LIFTLEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_LIFTLEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_TILTLEVEL);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@ -107,9 +100,9 @@ public class BlindHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
// Refresh state when new item is linked.
|
||||
if (channelUID.getId().equals(CHANNEL_BLINDLIFTLEVEL)) {
|
||||
queryOutput(ACTION_LIFTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_LIFTLEVEL);
|
||||
} else if (channelUID.getId().equals(CHANNEL_BLINDTILTLEVEL)) {
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_TILTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,30 +118,30 @@ public class BlindHandler extends LutronHandler {
|
||||
private void handleLiftCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_LIFTLEVEL, level, 0);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_LIFTLEVEL, level, null, null);
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISINGLIFT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STARTRAISINGLIFT, null, null, null);
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERINGLIFT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STARTLOWERINGLIFT, null, null, null);
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOPLIFT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STOPLIFT, null, null, null);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_LIFTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_LIFTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTiltCommand(Command command) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_TILTLEVEL, Math.min(level, tiltMax), 0);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_TILTLEVEL, Math.min(level, tiltMax), null, null);
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISINGTILT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STARTRAISINGTILT, null, null, null);
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERINGTILT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STARTLOWERINGTILT, null, null, null);
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOPTILT);
|
||||
output(TargetType.BLIND, OutputCommand.ACTION_STOPTILT, null, null, null);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_TILTLEVEL);
|
||||
queryOutput(TargetType.BLIND, OutputCommand.ACTION_TILTLEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -159,21 +152,21 @@ public class BlindHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
||||
if (ACTION_LIFTLEVEL.toString().equals(parameters[0])) {
|
||||
if (OutputCommand.ACTION_LIFTLEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal liftLevel = new BigDecimal(parameters[1]);
|
||||
logger.trace("Blind {} received lift level: {}", getIntegrationId(), liftLevel);
|
||||
updateState(CHANNEL_BLINDLIFTLEVEL, new PercentType(liftLevel));
|
||||
} else if (ACTION_TILTLEVEL.toString().equals(parameters[0])) {
|
||||
} else if (OutputCommand.ACTION_TILTLEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal tiltLevel = new BigDecimal(parameters[1]);
|
||||
logger.trace("Blind {} received tilt level: {}", getIntegrationId(), tiltLevel);
|
||||
updateState(CHANNEL_BLINDTILTLEVEL, new PercentType(tiltLevel));
|
||||
} else if (ACTION_LIFTTILTLEVEL.toString().equals(parameters[0]) && parameters.length > 2) {
|
||||
} else if (OutputCommand.ACTION_LIFTTILTLEVEL.toString().equals(parameters[0]) && parameters.length > 2) {
|
||||
BigDecimal liftLevel = new BigDecimal(parameters[1]);
|
||||
BigDecimal tiltLevel = new BigDecimal(parameters[2]);
|
||||
logger.trace("Blind {} received lift/tilt level: {} {}", getIntegrationId(), liftLevel, tiltLevel);
|
||||
updateState(CHANNEL_BLINDLIFTLEVEL, new PercentType(liftLevel));
|
||||
updateState(CHANNEL_BLINDTILTLEVEL, new PercentType(tiltLevel));
|
||||
} else if (ACTION_POSITION_UPDATE.toString().equals(parameters[0])
|
||||
} else if (OutputCommand.ACTION_POSITION_UPDATE.toString().equals(parameters[0])
|
||||
&& PARAMETER_POSITION_UPDATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
BigDecimal level = new BigDecimal(parameters[2]);
|
||||
logger.trace("Blind {} received lift level position update: {}", getIntegrationId(), level);
|
||||
|
@ -15,12 +15,13 @@ package org.openhab.binding.lutron.internal.handler;
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -45,9 +46,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class CcoHandler extends LutronHandler {
|
||||
private static final Integer ACTION_PULSE = 6;
|
||||
private static final Integer ACTION_STATE = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(CcoHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
@ -123,7 +121,8 @@ public class CcoHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_STATE); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@ -139,7 +138,7 @@ public class CcoHandler extends LutronHandler {
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
} else if (outputType == CcoOutputType.MAINTAINED) {
|
||||
// Query the device state and let the service routine update the channel state
|
||||
queryOutput(ACTION_STATE);
|
||||
queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
|
||||
} else {
|
||||
logger.warn("invalid output type defined for CCO {}", integrationId);
|
||||
}
|
||||
@ -153,22 +152,22 @@ public class CcoHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_SWITCH)) {
|
||||
if (command instanceof OnOffType && command == OnOffType.ON) {
|
||||
if (outputType == CcoOutputType.PULSED) {
|
||||
output(ACTION_PULSE, String.format(Locale.ROOT, "%.2f", defaultPulse));
|
||||
output(TargetType.CCO, OutputCommand.ACTION_PULSE, Double.valueOf(defaultPulse), null, null);
|
||||
updateState(channelUID, OnOffType.OFF);
|
||||
} else {
|
||||
output(ACTION_STATE, 100);
|
||||
output(TargetType.CCO, OutputCommand.ACTION_STATE, 100, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
else if (command instanceof OnOffType && command == OnOffType.OFF) {
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
output(ACTION_STATE, 0);
|
||||
output(TargetType.CCO, OutputCommand.ACTION_STATE, 0, null, null);
|
||||
}
|
||||
}
|
||||
|
||||
else if (command instanceof RefreshType) {
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
queryOutput(ACTION_STATE);
|
||||
queryOutput(TargetType.CCO, OutputCommand.ACTION_STATE);
|
||||
} else {
|
||||
updateState(CHANNEL_SWITCH, OnOffType.OFF);
|
||||
}
|
||||
@ -186,7 +185,7 @@ public class CcoHandler extends LutronHandler {
|
||||
|
||||
if (outputType == CcoOutputType.MAINTAINED) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& ACTION_STATE.toString().equals(parameters[0])) {
|
||||
&& OutputCommand.ACTION_STATE.toString().equals(parameters[0])) {
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
|
@ -21,8 +21,10 @@ import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import org.openhab.binding.lutron.internal.action.DimmerActions;
|
||||
import org.openhab.binding.lutron.internal.config.DimmerConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronDuration;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@ -42,8 +44,6 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Bob Adair - Added initDeviceState method, and onLevel and onToLast parameters
|
||||
*/
|
||||
public class DimmerHandler extends LutronHandler {
|
||||
private static final Integer ACTION_ZONELEVEL = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DimmerHandler.class);
|
||||
private DimmerConfig config;
|
||||
private LutronDuration fadeInTime;
|
||||
@ -90,7 +90,8 @@ public class DimmerHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_ZONELEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
lastLightLevel.set(config.onLevel);
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
@ -101,7 +102,7 @@ public class DimmerHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_LIGHTLEVEL)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
queryOutput(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,28 +111,28 @@ public class DimmerHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_LIGHTLEVEL)) {
|
||||
if (command instanceof Number) {
|
||||
int level = ((Number) command).intValue();
|
||||
output(ACTION_ZONELEVEL, level, 0.25);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, level, new LutronDuration("0.25"), null);
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
if (config.onToLast) {
|
||||
output(ACTION_ZONELEVEL, lastLightLevel.get(), fadeInTime);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, lastLightLevel.get(), fadeInTime, null);
|
||||
} else {
|
||||
output(ACTION_ZONELEVEL, config.onLevel, fadeInTime);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, config.onLevel, fadeInTime, null);
|
||||
}
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
output(ACTION_ZONELEVEL, 0, fadeOutTime);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, 0, fadeOutTime, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setLightLevel(BigDecimal level, LutronDuration fade, LutronDuration delay) {
|
||||
int intLevel = level.intValue();
|
||||
output(ACTION_ZONELEVEL, intLevel, fade, delay);
|
||||
output(TargetType.DIMMER, OutputCommand.ACTION_ZONELEVEL, intLevel, fade, delay);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
&& OutputCommand.ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.handler;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.config.FanConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
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.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating with ceiling fan speed controllers.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class FanHandler extends LutronHandler {
|
||||
private final Logger logger = LoggerFactory.getLogger(FanHandler.class);
|
||||
|
||||
private FanConfig config = new FanConfig();
|
||||
|
||||
public FanHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
if (config.integrationId <= 0) {
|
||||
throw new IllegalStateException("handler not initialized");
|
||||
} else {
|
||||
return config.integrationId;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(FanConfig.class);
|
||||
if (config.integrationId <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No integrationId configured");
|
||||
return;
|
||||
}
|
||||
logger.debug("Initializing Fan handler for integration ID {}", getIntegrationId());
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Fan {}", getIntegrationId());
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(TargetType.FAN, OutputCommand.ACTION_ZONELEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_FANSPEED) || channelUID.getId().equals(CHANNEL_FANLEVEL)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryOutput(TargetType.FAN, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_FANLEVEL)) {
|
||||
if (command instanceof Number) {
|
||||
int level = ((Number) command).intValue();
|
||||
FanSpeedType speed = FanSpeedType.toFanSpeedType(level);
|
||||
// output(TargetType.FAN, LutronCommand.ACTION_ZONELEVEL, level, null, null);
|
||||
sendCommand(new OutputCommand(TargetType.FAN, LutronOperation.EXECUTE, getIntegrationId(),
|
||||
OutputCommand.ACTION_ZONELEVEL, speed, null, null));
|
||||
} else if (command.equals(OnOffType.ON)) {
|
||||
// output(TargetType.FAN, LutronCommand.ACTION_ZONELEVEL, 100, null, null);
|
||||
sendCommand(new OutputCommand(TargetType.FAN, LutronOperation.EXECUTE, getIntegrationId(),
|
||||
OutputCommand.ACTION_ZONELEVEL, FanSpeedType.HIGH, null, null));
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
// output(TargetType.FAN, LutronCommand.ACTION_ZONELEVEL, 0, null, null);
|
||||
sendCommand(new OutputCommand(TargetType.FAN, LutronOperation.EXECUTE, getIntegrationId(),
|
||||
OutputCommand.ACTION_ZONELEVEL, FanSpeedType.OFF, null, null));
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_FANSPEED)) {
|
||||
if (command instanceof StringType) {
|
||||
FanSpeedType speed = FanSpeedType.toFanSpeedType(command.toString());
|
||||
sendCommand(new OutputCommand(TargetType.FAN, LutronOperation.EXECUTE, getIntegrationId(),
|
||||
OutputCommand.ACTION_ZONELEVEL, speed, null, null));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& OutputCommand.ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
updateState(CHANNEL_FANLEVEL, new PercentType(level));
|
||||
FanSpeedType fanSpeed = FanSpeedType.toFanSpeedType(level.intValue());
|
||||
updateState(CHANNEL_FANSPEED, new StringType(fanSpeed.toString()));
|
||||
}
|
||||
}
|
||||
}
|
@ -19,7 +19,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.ModeCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@ -39,8 +40,7 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GreenModeHandler extends LutronHandler {
|
||||
private static final Integer ACTION_STEP = 1;
|
||||
public static final int GREENSTEP_MIN = 1;
|
||||
private static final int GREENSTEP_MIN = 1;
|
||||
|
||||
// poll interval parameters are in minutes
|
||||
private static final int POLL_INTERVAL_DEFAULT = 15;
|
||||
@ -94,7 +94,8 @@ public class GreenModeHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryGreenMode(ACTION_STEP); // handleUpdate() will set thing status to online when response arrives
|
||||
queryGreenMode(ModeCommand.ACTION_STEP);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@ -123,13 +124,13 @@ public class GreenModeHandler extends LutronHandler {
|
||||
|
||||
private synchronized void pollState() {
|
||||
logger.trace("Executing green mode polling job for integration ID {}", integrationId);
|
||||
queryGreenMode(ACTION_STEP);
|
||||
queryGreenMode(ModeCommand.ACTION_STEP);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_STEP)) {
|
||||
queryGreenMode(ACTION_STEP);
|
||||
queryGreenMode(ModeCommand.ACTION_STEP);
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,16 +138,16 @@ public class GreenModeHandler extends LutronHandler {
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_STEP)) {
|
||||
if (command == OnOffType.ON) {
|
||||
greenMode(ACTION_STEP, 2);
|
||||
greenMode(ModeCommand.ACTION_STEP, 2);
|
||||
} else if (command == OnOffType.OFF) {
|
||||
greenMode(ACTION_STEP, 1);
|
||||
greenMode(ModeCommand.ACTION_STEP, 1);
|
||||
} else if (command instanceof Number) {
|
||||
Integer step = ((Number) command).intValue();
|
||||
if (step.intValue() >= GREENSTEP_MIN) {
|
||||
greenMode(ACTION_STEP, step);
|
||||
greenMode(ModeCommand.ACTION_STEP, step);
|
||||
}
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryGreenMode(ACTION_STEP);
|
||||
queryGreenMode(ModeCommand.ACTION_STEP);
|
||||
} else {
|
||||
logger.debug("Ignoring invalid command {} for id {}", command, integrationId);
|
||||
}
|
||||
@ -159,7 +160,7 @@ public class GreenModeHandler extends LutronHandler {
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
try {
|
||||
if (type == LutronCommandType.MODE && parameters.length > 1
|
||||
&& ACTION_STEP.toString().equals(parameters[0])) {
|
||||
&& ModeCommand.ACTION_STEP.toString().equals(parameters[0])) {
|
||||
Long step = Long.valueOf(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
@ -30,15 +30,16 @@ import org.openhab.binding.lutron.internal.config.IPBridgeConfig;
|
||||
import org.openhab.binding.lutron.internal.discovery.LutronDeviceDiscoveryService;
|
||||
import org.openhab.binding.lutron.internal.net.TelnetSession;
|
||||
import org.openhab.binding.lutron.internal.net.TelnetSessionListener;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.LIPCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
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.binding.BaseBridgeHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
@ -51,9 +52,9 @@ import org.slf4j.LoggerFactory;
|
||||
* @author Bob Adair - Added reconnect and heartbeat config parameters, moved discovery service registration to
|
||||
* LutronHandlerFactory
|
||||
*/
|
||||
public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
public class IPBridgeHandler extends LutronBridgeHandler {
|
||||
private static final Pattern RESPONSE_REGEX = Pattern
|
||||
.compile("~(OUTPUT|DEVICE|SYSTEM|TIMECLOCK|MODE|SYSVAR),([0-9\\.:/]+),([0-9,\\.:/]*)\\Z");
|
||||
.compile("~(OUTPUT|DEVICE|SYSTEM|TIMECLOCK|MODE|SYSVAR|GROUP),([0-9\\.:/]+),([0-9,\\.:/]*)\\Z");
|
||||
|
||||
private static final String DB_UPDATE_DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";
|
||||
|
||||
@ -85,7 +86,7 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
private int sendDelay;
|
||||
|
||||
private TelnetSession session;
|
||||
private BlockingQueue<LutronCommand> sendQueue = new LinkedBlockingQueue<>();
|
||||
private BlockingQueue<LutronCommandNew> sendQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
private Thread messageSender;
|
||||
private ScheduledFuture<?> keepAlive;
|
||||
@ -206,8 +207,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
||||
// Disable prompts
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MONITORING, -1, MONITOR_PROMPT,
|
||||
MONITOR_DISABLE));
|
||||
sendCommand(new LIPCommand(TargetType.BRIDGE, LutronOperation.EXECUTE, LutronCommandType.MONITORING, null,
|
||||
MONITOR_PROMPT, MONITOR_DISABLE));
|
||||
|
||||
if (requireSysvarMonitoring.get()) {
|
||||
setSysvarMonitoring(true);
|
||||
@ -215,7 +216,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
// Check the time device database was last updated. On the initial connect, this will trigger
|
||||
// a scan for paired devices.
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.SYSTEM, -1, SYSTEM_DBEXPORTDATETIME));
|
||||
sendCommand(new LIPCommand(TargetType.BRIDGE, LutronOperation.QUERY, LutronCommandType.SYSTEM, null,
|
||||
SYSTEM_DBEXPORTDATETIME));
|
||||
|
||||
messageSender = new Thread(this::sendCommandsThread, "Lutron sender");
|
||||
messageSender.start();
|
||||
@ -228,7 +230,7 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
private void sendCommandsThread() {
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
LutronCommand command = sendQueue.take();
|
||||
LutronCommandNew command = sendQueue.take();
|
||||
|
||||
logger.debug("Sending command {}", command);
|
||||
|
||||
@ -317,8 +319,9 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
void sendCommand(LutronCommand command) {
|
||||
this.sendQueue.add(command);
|
||||
@Override
|
||||
public void sendCommand(LutronCommandNew command) {
|
||||
sendQueue.add(command);
|
||||
}
|
||||
|
||||
private LutronHandler findThingHandler(int integrationId) {
|
||||
@ -423,7 +426,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
keepAliveReconnect = scheduler.schedule(this::reconnect, KEEPALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
|
||||
logger.trace("Sending keepalive query");
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.SYSTEM, -1, SYSTEM_DBEXPORTDATETIME));
|
||||
sendCommand(new LIPCommand(TargetType.BRIDGE, LutronOperation.QUERY, LutronCommandType.SYSTEM, null,
|
||||
SYSTEM_DBEXPORTDATETIME));
|
||||
}
|
||||
|
||||
private void setDbUpdateDate(String dateString, String timeString) {
|
||||
@ -455,8 +459,8 @@ public class IPBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
private void setSysvarMonitoring(boolean enable) {
|
||||
Integer setting = (enable) ? MONITOR_ENABLE : MONITOR_DISABLE;
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MONITORING, -1, MONITOR_SYSVAR, setting));
|
||||
sendCommand(new LIPCommand(TargetType.BRIDGE, LutronOperation.EXECUTE, LutronCommandType.MONITORING, null,
|
||||
MONITOR_SYSVAR, setting));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,802 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.handler;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.*;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.UnrecoverableKeyException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.net.ssl.KeyManagerFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.config.LeapBridgeConfig;
|
||||
import org.openhab.binding.lutron.internal.discovery.LeapDeviceDiscoveryService;
|
||||
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
|
||||
import org.openhab.binding.lutron.internal.protocol.GroupCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapMessageParser;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapMessageParserCallbacks;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.Request;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
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.ThingStatusInfo;
|
||||
import org.openhab.core.thing.binding.ThingHandler;
|
||||
import org.openhab.core.thing.binding.ThingHandlerService;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Bridge handler responsible for communicating with Lutron hubs that support the LEAP protocol, such as Caseta and
|
||||
* RA2 Select.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapBridgeHandler extends LutronBridgeHandler implements LeapMessageParserCallbacks {
|
||||
private static final int DEFAULT_RECONNECT_MINUTES = 5;
|
||||
private static final int DEFAULT_HEARTBEAT_MINUTES = 5;
|
||||
private static final long KEEPALIVE_TIMEOUT_SECONDS = 30;
|
||||
|
||||
private static final String STATUS_INITIALIZING = "Initializing";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(LeapBridgeHandler.class);
|
||||
|
||||
private @NonNullByDefault({}) LeapBridgeConfig config;
|
||||
private int reconnectInterval;
|
||||
private int heartbeatInterval;
|
||||
private int sendDelay;
|
||||
|
||||
private @NonNullByDefault({}) SSLSocketFactory sslsocketfactory;
|
||||
private @Nullable SSLSocket sslsocket;
|
||||
private @Nullable BufferedWriter writer;
|
||||
private @Nullable BufferedReader reader;
|
||||
|
||||
private @NonNullByDefault({}) LeapMessageParser leapMessageParser;
|
||||
|
||||
private final BlockingQueue<LeapCommand> sendQueue = new LinkedBlockingQueue<>();
|
||||
|
||||
private @Nullable Future<?> asyncInitializeTask;
|
||||
|
||||
private @Nullable Thread senderThread;
|
||||
private @Nullable Thread readerThread;
|
||||
|
||||
private @Nullable ScheduledFuture<?> keepAliveJob;
|
||||
private @Nullable ScheduledFuture<?> keepAliveReconnectJob;
|
||||
private @Nullable ScheduledFuture<?> connectRetryJob;
|
||||
private final Object keepAliveReconnectLock = new Object();
|
||||
|
||||
private final Map<Integer, Integer> zoneToDevice = new HashMap<>();
|
||||
private final Map<Integer, Integer> deviceToZone = new HashMap<>();
|
||||
private final Object zoneMapsLock = new Object();
|
||||
|
||||
private @Nullable Map<Integer, List<Integer>> deviceButtonMap;
|
||||
private final Object deviceButtonMapLock = new Object();
|
||||
|
||||
private volatile boolean deviceDataLoaded = false;
|
||||
private volatile boolean buttonDataLoaded = false;
|
||||
|
||||
private final Map<Integer, LutronHandler> childHandlerMap = new ConcurrentHashMap<>();
|
||||
private final Map<Integer, OGroupHandler> groupHandlerMap = new ConcurrentHashMap<>();
|
||||
|
||||
private @Nullable LeapDeviceDiscoveryService discoveryService;
|
||||
|
||||
public void setDiscoveryService(LeapDeviceDiscoveryService discoveryService) {
|
||||
this.discoveryService = discoveryService;
|
||||
}
|
||||
|
||||
public LeapBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
leapMessageParser = new LeapMessageParser(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Class<? extends ThingHandlerService>> getServices() {
|
||||
return Collections.singleton(LeapDeviceDiscoveryService.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
SSLContext sslContext;
|
||||
|
||||
childHandlerMap.clear();
|
||||
groupHandlerMap.clear();
|
||||
|
||||
config = getConfigAs(LeapBridgeConfig.class);
|
||||
String keystorePassword = (config.keystorePassword == null) ? "" : config.keystorePassword;
|
||||
|
||||
String ipAddress = config.ipAddress;
|
||||
if (ipAddress == null || ipAddress.isEmpty()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "bridge address not specified");
|
||||
return;
|
||||
}
|
||||
|
||||
reconnectInterval = (config.reconnect > 0) ? config.reconnect : DEFAULT_RECONNECT_MINUTES;
|
||||
heartbeatInterval = (config.heartbeat > 0) ? config.heartbeat : DEFAULT_HEARTBEAT_MINUTES;
|
||||
sendDelay = (config.delay < 0) ? 0 : config.delay;
|
||||
|
||||
if (config.keystore == null || keystorePassword == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Keystore/keystore password not configured");
|
||||
return;
|
||||
} else {
|
||||
try (FileInputStream keystoreInputStream = new FileInputStream(config.keystore)) {
|
||||
logger.trace("Initializing keystore");
|
||||
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
|
||||
|
||||
keystore.load(keystoreInputStream, keystorePassword.toCharArray());
|
||||
|
||||
logger.trace("Initializing SSL Context");
|
||||
KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
|
||||
kmf.init(keystore, keystorePassword.toCharArray());
|
||||
|
||||
TrustManager[] trustManagers;
|
||||
if (config.certValidate) {
|
||||
// Use default trust manager which will attempt to validate server certificate from hub
|
||||
TrustManagerFactory tmf = TrustManagerFactory
|
||||
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
|
||||
tmf.init(keystore);
|
||||
trustManagers = tmf.getTrustManagers();
|
||||
} else {
|
||||
// Use no-op trust manager which will not verify certificates
|
||||
trustManagers = defineNoOpTrustManager();
|
||||
}
|
||||
|
||||
sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(kmf.getKeyManagers(), trustManagers, null);
|
||||
|
||||
sslsocketfactory = sslContext.getSocketFactory();
|
||||
} catch (FileNotFoundException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Keystore file not found");
|
||||
return;
|
||||
} catch (CertificateException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Certificate exception");
|
||||
return;
|
||||
} catch (UnrecoverableKeyException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"Key unrecoverable with supplied password");
|
||||
return;
|
||||
} catch (KeyManagementException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Key management exception");
|
||||
logger.debug("Key management exception", e);
|
||||
return;
|
||||
} catch (KeyStoreException | NoSuchAlgorithmException | IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Error initializing keystore");
|
||||
logger.debug("Error initializing keystore", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "Connecting");
|
||||
asyncInitializeTask = scheduler.submit(this::connect); // start the async connect task
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a no-op SSL trust manager which will not verify server or client certificates.
|
||||
*/
|
||||
private TrustManager[] defineNoOpTrustManager() {
|
||||
return new TrustManager[] { new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) {
|
||||
logger.debug("Assuming client certificate is valid");
|
||||
if (chain != null && logger.isTraceEnabled()) {
|
||||
for (int cert = 0; cert < chain.length; cert++) {
|
||||
logger.trace("Subject DN: {}", chain[cert].getSubjectDN());
|
||||
logger.trace("Issuer DN: {}", chain[cert].getIssuerDN());
|
||||
logger.trace("Serial number {}:", chain[cert].getSerialNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(final X509Certificate @Nullable [] chain, final @Nullable String authType) {
|
||||
logger.debug("Assuming server certificate is valid");
|
||||
if (chain != null && logger.isTraceEnabled()) {
|
||||
for (int cert = 0; cert < chain.length; cert++) {
|
||||
logger.trace("Subject DN: {}", chain[cert].getSubjectDN());
|
||||
logger.trace("Issuer DN: {}", chain[cert].getIssuerDN());
|
||||
logger.trace("Serial number: {}", chain[cert].getSerialNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate @Nullable [] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
} };
|
||||
}
|
||||
|
||||
private synchronized void connect() {
|
||||
deviceDataLoaded = false;
|
||||
buttonDataLoaded = false;
|
||||
|
||||
try {
|
||||
logger.debug("Opening SSL connection to {}:{}", config.ipAddress, config.port);
|
||||
SSLSocket sslsocket = (SSLSocket) sslsocketfactory.createSocket(config.ipAddress, config.port);
|
||||
sslsocket.startHandshake();
|
||||
writer = new BufferedWriter(new OutputStreamWriter(sslsocket.getOutputStream()));
|
||||
reader = new BufferedReader(new InputStreamReader(sslsocket.getInputStream()));
|
||||
this.sslsocket = sslsocket;
|
||||
} catch (UnknownHostException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Unknown host");
|
||||
return;
|
||||
} catch (IllegalArgumentException e) {
|
||||
// port out of valid range
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "Invalid port number");
|
||||
return;
|
||||
} catch (InterruptedIOException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
logger.debug("Interrupted while establishing connection");
|
||||
return;
|
||||
} catch (IOException e) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||
"Error opening SSL connection. Check log.");
|
||||
logger.info("Error opening SSL connection: {}", e.getMessage());
|
||||
disconnect(false);
|
||||
scheduleConnectRetry(reconnectInterval); // Possibly a temporary problem. Try again later.
|
||||
return;
|
||||
}
|
||||
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, STATUS_INITIALIZING);
|
||||
|
||||
Thread readerThread = new Thread(this::readerThreadJob, "Lutron reader");
|
||||
readerThread.setDaemon(true);
|
||||
readerThread.start();
|
||||
this.readerThread = readerThread;
|
||||
|
||||
Thread senderThread = new Thread(this::senderThreadJob, "Lutron sender");
|
||||
senderThread.setDaemon(true);
|
||||
senderThread.start();
|
||||
this.senderThread = senderThread;
|
||||
|
||||
sendCommand(new LeapCommand(Request.getButtonGroups()));
|
||||
queryDiscoveryData();
|
||||
sendCommand(new LeapCommand(Request.subscribeOccupancyGroupStatus()));
|
||||
|
||||
logger.debug("Starting keepalive job with interval {}", heartbeatInterval);
|
||||
keepAliveJob = scheduler.scheduleWithFixedDelay(this::sendKeepAlive, heartbeatInterval, heartbeatInterval,
|
||||
TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by connect() and discovery service to request fresh discovery data
|
||||
*/
|
||||
public void queryDiscoveryData() {
|
||||
sendCommand(new LeapCommand(Request.getDevices()));
|
||||
sendCommand(new LeapCommand(Request.getAreas()));
|
||||
sendCommand(new LeapCommand(Request.getOccupancyGroups()));
|
||||
}
|
||||
|
||||
private void scheduleConnectRetry(long waitMinutes) {
|
||||
logger.debug("Scheduling connection retry in {} minutes", waitMinutes);
|
||||
connectRetryJob = scheduler.schedule(this::connect, waitMinutes, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect from bridge, cancel retry and keepalive jobs, stop reader and writer threads, and clean up.
|
||||
*
|
||||
* @param interruptAll Set if reconnect task should be interrupted if running. Should be false when calling from
|
||||
* connect or reconnect, and true when calling from dispose.
|
||||
*/
|
||||
private synchronized void disconnect(boolean interruptAll) {
|
||||
logger.debug("Disconnecting");
|
||||
|
||||
Thread senderThread = this.senderThread;
|
||||
Thread readerThread = this.readerThread;
|
||||
|
||||
ScheduledFuture<?> connectRetryJob = this.connectRetryJob;
|
||||
if (connectRetryJob != null) {
|
||||
connectRetryJob.cancel(true);
|
||||
}
|
||||
ScheduledFuture<?> keepAliveJob = this.keepAliveJob;
|
||||
if (keepAliveJob != null) {
|
||||
keepAliveJob.cancel(true);
|
||||
}
|
||||
|
||||
reconnectTaskCancel(interruptAll); // May be called from keepAliveReconnectJob thread
|
||||
|
||||
if (senderThread != null && senderThread.isAlive()) {
|
||||
senderThread.interrupt();
|
||||
}
|
||||
if (readerThread != null && readerThread.isAlive()) {
|
||||
readerThread.interrupt();
|
||||
}
|
||||
SSLSocket sslsocket = this.sslsocket;
|
||||
if (sslsocket != null) {
|
||||
try {
|
||||
sslsocket.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing SSL socket: {}", e.getMessage());
|
||||
}
|
||||
this.sslsocket = null;
|
||||
}
|
||||
BufferedReader reader = this.reader;
|
||||
if (reader != null) {
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing reader: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
BufferedWriter writer = this.writer;
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.close();
|
||||
} catch (IOException e) {
|
||||
logger.debug("Error closing writer: {}", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
deviceDataLoaded = false;
|
||||
buttonDataLoaded = false;
|
||||
}
|
||||
|
||||
private synchronized void reconnect() {
|
||||
logger.debug("Attempting to reconnect to the bridge");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "reconnecting");
|
||||
disconnect(false);
|
||||
connect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed by the message sender thread (senderThread)
|
||||
*/
|
||||
private void senderThreadJob() {
|
||||
logger.debug("Command sender thread started");
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted() && writer != null) {
|
||||
LeapCommand command = sendQueue.take();
|
||||
logger.trace("Sending command {}", command);
|
||||
|
||||
try {
|
||||
BufferedWriter writer = this.writer;
|
||||
if (writer != null) {
|
||||
writer.write(command.toString() + "\n");
|
||||
writer.flush();
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
logger.debug("Interrupted while sending");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Interrupted");
|
||||
break; // exit loop and terminate thread
|
||||
} catch (IOException e) {
|
||||
logger.warn("Communication error, will try to reconnect. Error: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR);
|
||||
sendQueue.add(command); // Requeue command
|
||||
reconnect();
|
||||
break; // reconnect() will start a new thread; terminate this one
|
||||
}
|
||||
if (sendDelay > 0) {
|
||||
Thread.sleep(sendDelay); // introduce delay to throttle send rate
|
||||
}
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
} finally {
|
||||
logger.debug("Command sender thread exiting");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method executed by the message reader thread (readerThread)
|
||||
*/
|
||||
private void readerThreadJob() {
|
||||
logger.debug("Message reader thread started");
|
||||
String msg = null;
|
||||
try {
|
||||
BufferedReader reader = this.reader;
|
||||
while (!Thread.interrupted() && reader != null && (msg = reader.readLine()) != null) {
|
||||
leapMessageParser.handleMessage(msg);
|
||||
}
|
||||
if (msg == null) {
|
||||
logger.debug("End of input stream detected");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Connection lost");
|
||||
}
|
||||
} catch (InterruptedIOException e) {
|
||||
logger.debug("Interrupted while reading");
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, "Interrupted");
|
||||
} catch (IOException e) {
|
||||
logger.debug("I/O error while reading from stream: {}", e.getMessage());
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception in reader thread", e);
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, e.getMessage());
|
||||
} finally {
|
||||
logger.debug("Message reader thread exiting");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called if NoContent response received for a buttongroup read request. Creates empty deviceButtonMap.
|
||||
*/
|
||||
@Override
|
||||
public void handleEmptyButtonGroupDefinition() {
|
||||
logger.debug("No content in button group definition. Creating empty deviceButtonMap.");
|
||||
Map<Integer, List<Integer>> deviceButtonMap = new HashMap<>();
|
||||
synchronized (deviceButtonMapLock) {
|
||||
this.deviceButtonMap = deviceButtonMap;
|
||||
buttonDataLoaded = true;
|
||||
}
|
||||
checkInitialized();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set state to online if offline/initializing and all required initialization info is loaded.
|
||||
* Currently this means device (zone) and button group data.
|
||||
*/
|
||||
private void checkInitialized() {
|
||||
ThingStatusInfo statusInfo = getThing().getStatusInfo();
|
||||
if (statusInfo.getStatus() == ThingStatus.OFFLINE && STATUS_INITIALIZING.equals(statusInfo.getDescription())) {
|
||||
if (deviceDataLoaded && buttonDataLoaded) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify child thing handler of a zonelevel update from a received zone status message.
|
||||
*/
|
||||
@Override
|
||||
public void handleZoneUpdate(ZoneStatus zoneStatus) {
|
||||
logger.trace("Zone: {} level: {}", zoneStatus.getZone(), zoneStatus.level);
|
||||
Integer integrationId = zoneToDevice(zoneStatus.getZone());
|
||||
|
||||
if (integrationId == null) {
|
||||
logger.debug("Unable to map zone {} to device", zoneStatus.getZone());
|
||||
return;
|
||||
}
|
||||
logger.trace("Zone {} mapped to device id {}", zoneStatus.getZone(), integrationId);
|
||||
|
||||
// dispatch update to proper thing handler
|
||||
LutronHandler handler = findThingHandler(integrationId);
|
||||
if (handler != null) {
|
||||
if (zoneStatus.fanSpeed != null) {
|
||||
// handle fan controller
|
||||
FanSpeedType fanSpeed = zoneStatus.fanSpeed;
|
||||
try {
|
||||
handler.handleUpdate(LutronCommandType.OUTPUT, OutputCommand.ACTION_ZONELEVEL.toString(),
|
||||
Integer.valueOf(fanSpeed.speed()).toString());
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Number format exception parsing update");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception while processing update");
|
||||
}
|
||||
} else {
|
||||
// handle switch/dimmer/shade
|
||||
try {
|
||||
handler.handleUpdate(LutronCommandType.OUTPUT, OutputCommand.ACTION_ZONELEVEL.toString(),
|
||||
Integer.valueOf(zoneStatus.level).toString());
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Number format exception parsing update");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception while processing update");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug("No thing configured for integration ID {}", integrationId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify child group handler of a received occupancy group update.
|
||||
*
|
||||
* @param occupancyStatus
|
||||
* @param groupNumber
|
||||
*/
|
||||
@Override
|
||||
public void handleGroupUpdate(int groupNumber, String occupancyStatus) {
|
||||
logger.trace("Group {} state update: {}", groupNumber, occupancyStatus);
|
||||
|
||||
// dispatch update to proper handler
|
||||
OGroupHandler handler = findGroupHandler(groupNumber);
|
||||
if (handler != null) {
|
||||
try {
|
||||
switch (occupancyStatus) {
|
||||
case "Occupied":
|
||||
handler.handleUpdate(LutronCommandType.GROUP, GroupCommand.ACTION_GROUPSTATE.toString(),
|
||||
GroupCommand.STATE_GRP_OCCUPIED.toString());
|
||||
break;
|
||||
case "Unoccupied":
|
||||
handler.handleUpdate(LutronCommandType.GROUP, GroupCommand.ACTION_GROUPSTATE.toString(),
|
||||
GroupCommand.STATE_GRP_UNOCCUPIED.toString());
|
||||
break;
|
||||
case "Unknown":
|
||||
handler.handleUpdate(LutronCommandType.GROUP, GroupCommand.ACTION_GROUPSTATE.toString(),
|
||||
GroupCommand.STATE_GRP_UNKNOWN.toString());
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unexpected occupancy status: {}", occupancyStatus);
|
||||
return;
|
||||
}
|
||||
} catch (NumberFormatException e) {
|
||||
logger.warn("Number format exception parsing update");
|
||||
} catch (RuntimeException e) {
|
||||
logger.warn("Runtime exception while processing update");
|
||||
}
|
||||
} else {
|
||||
logger.debug("No group thing configured for group ID {}", groupNumber);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultipleButtonGroupDefinition(List<ButtonGroup> buttonGroupList) {
|
||||
Map<Integer, List<Integer>> deviceButtonMap = new HashMap<>();
|
||||
|
||||
for (ButtonGroup buttonGroup : buttonGroupList) {
|
||||
int parentDevice = buttonGroup.getParentDevice();
|
||||
logger.trace("Found ButtonGroup: {} parent device: {}", buttonGroup.getButtonGroup(), parentDevice);
|
||||
List<Integer> buttonList = buttonGroup.getButtonList();
|
||||
deviceButtonMap.put(parentDevice, buttonList);
|
||||
}
|
||||
synchronized (deviceButtonMapLock) {
|
||||
this.deviceButtonMap = deviceButtonMap;
|
||||
buttonDataLoaded = true;
|
||||
}
|
||||
checkInitialized();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultipleDeviceDefintion(List<Device> deviceList) {
|
||||
synchronized (zoneMapsLock) {
|
||||
zoneToDevice.clear();
|
||||
deviceToZone.clear();
|
||||
for (Device device : deviceList) {
|
||||
Integer zoneid = device.getZone();
|
||||
Integer deviceid = device.getDevice();
|
||||
logger.trace("Found device: {} id: {} zone: {}", device.name, deviceid, zoneid);
|
||||
if (zoneid > 0 && deviceid > 0) {
|
||||
zoneToDevice.put(zoneid, deviceid);
|
||||
deviceToZone.put(deviceid, zoneid);
|
||||
}
|
||||
if (deviceid == 1) { // ID 1 is the bridge
|
||||
setBridgeProperties(device);
|
||||
}
|
||||
}
|
||||
}
|
||||
deviceDataLoaded = true;
|
||||
checkInitialized();
|
||||
|
||||
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
|
||||
if (discoveryService != null) {
|
||||
discoveryService.processDeviceDefinitions(deviceList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultipleAreaDefinition(List<Area> areaList) {
|
||||
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
|
||||
if (discoveryService != null) {
|
||||
discoveryService.setAreas(areaList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleMultipleOccupancyGroupDefinition(List<OccupancyGroup> oGroupList) {
|
||||
LeapDeviceDiscoveryService discoveryService = this.discoveryService;
|
||||
if (discoveryService != null) {
|
||||
discoveryService.setOccupancyGroups(oGroupList);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validMessageReceived(String communiqueType) {
|
||||
reconnectTaskCancel(true); // Got a good message, so cancel reconnect task.
|
||||
}
|
||||
|
||||
/**
|
||||
* Set informational bridge properties from the Device entry for the hub/repeater
|
||||
*/
|
||||
private void setBridgeProperties(Device device) {
|
||||
if (device.getDevice() == 1 && device.repeaterProperties != null) {
|
||||
Map<String, String> properties = editProperties();
|
||||
if (device.name != null) {
|
||||
properties.put(PROPERTY_PRODTYP, device.name);
|
||||
}
|
||||
if (device.modelNumber != null) {
|
||||
properties.put(Thing.PROPERTY_MODEL_ID, device.modelNumber);
|
||||
}
|
||||
if (device.serialNumber != null) {
|
||||
properties.put(Thing.PROPERTY_SERIAL_NUMBER, device.serialNumber);
|
||||
}
|
||||
if (device.firmwareImage != null && device.firmwareImage.firmware != null
|
||||
&& device.firmwareImage.firmware.displayName != null) {
|
||||
properties.put(Thing.PROPERTY_FIRMWARE_VERSION, device.firmwareImage.firmware.displayName);
|
||||
}
|
||||
updateProperties(properties);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Queue a LeapCommand for transmission by the sender thread.
|
||||
*/
|
||||
public void sendCommand(@Nullable LeapCommand command) {
|
||||
if (command != null) {
|
||||
sendQueue.add(command);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a LutronCommand into a LeapCommand and queue it for transmission by the sender thread.
|
||||
*/
|
||||
@Override
|
||||
public void sendCommand(LutronCommandNew command) {
|
||||
logger.trace("Received request to send Lutron command: {}", command);
|
||||
sendCommand(command.leapCommand(this, deviceToZone(command.getIntegrationId())));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns LEAP button number for given integrationID and component. Returns 0 if button number cannot be
|
||||
* determined.
|
||||
*/
|
||||
public int getButton(int integrationID, int component) {
|
||||
synchronized (deviceButtonMapLock) {
|
||||
if (deviceButtonMap != null) {
|
||||
List<Integer> buttonList = deviceButtonMap.get(integrationID);
|
||||
if (buttonList != null && component <= buttonList.size()) {
|
||||
return buttonList.get(component - 1);
|
||||
} else {
|
||||
logger.debug("Could not find button component {} for id {}", component, integrationID);
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Device to button map not populated");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable LutronHandler findThingHandler(@Nullable Integer integrationId) {
|
||||
if (integrationId != null) {
|
||||
return childHandlerMap.get(integrationId);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable OGroupHandler findGroupHandler(int integrationId) {
|
||||
return groupHandlerMap.get(integrationId);
|
||||
}
|
||||
|
||||
private @Nullable Integer zoneToDevice(int zone) {
|
||||
synchronized (zoneMapsLock) {
|
||||
return zoneToDevice.get(zone);
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable Integer deviceToZone(@Nullable Integer device) {
|
||||
if (device == null) {
|
||||
return null;
|
||||
}
|
||||
synchronized (zoneMapsLock) {
|
||||
return deviceToZone.get(device);
|
||||
}
|
||||
}
|
||||
|
||||
private void sendKeepAlive() {
|
||||
logger.trace("Sending keepalive query");
|
||||
sendCommand(new LeapCommand(Request.ping()));
|
||||
// Reconnect if no response is received within KEEPALIVE_TIMEOUT_SECONDS.
|
||||
reconnectTaskSchedule();
|
||||
}
|
||||
|
||||
private void reconnectTaskSchedule() {
|
||||
synchronized (keepAliveReconnectLock) {
|
||||
keepAliveReconnectJob = scheduler.schedule(this::reconnect, KEEPALIVE_TIMEOUT_SECONDS, TimeUnit.SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
private void reconnectTaskCancel(boolean interrupt) {
|
||||
synchronized (keepAliveReconnectLock) {
|
||||
ScheduledFuture<?> keepAliveReconnectJob = this.keepAliveReconnectJob;
|
||||
if (keepAliveReconnectJob != null) {
|
||||
logger.trace("Canceling scheduled reconnect job.");
|
||||
keepAliveReconnectJob.cancel(interrupt);
|
||||
this.keepAliveReconnectJob = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_COMMAND)) {
|
||||
if (command instanceof StringType) {
|
||||
sendCommand(new LeapCommand(command.toString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerInitialized(ThingHandler childHandler, Thing childThing) {
|
||||
if (childHandler instanceof OGroupHandler) {
|
||||
// We need a different map for group things because the numbering is separate
|
||||
OGroupHandler handler = (OGroupHandler) childHandler;
|
||||
int groupId = handler.getIntegrationId();
|
||||
groupHandlerMap.put(groupId, handler);
|
||||
logger.trace("Registered group handler for ID {}", groupId);
|
||||
} else {
|
||||
LutronHandler handler = (LutronHandler) childHandler;
|
||||
int intId = handler.getIntegrationId();
|
||||
childHandlerMap.put(intId, handler);
|
||||
logger.trace("Registered child handler for ID {}", intId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void childHandlerDisposed(ThingHandler childHandler, Thing childThing) {
|
||||
if (childHandler instanceof OGroupHandler) {
|
||||
OGroupHandler handler = (OGroupHandler) childHandler;
|
||||
int groupId = handler.getIntegrationId();
|
||||
groupHandlerMap.remove(groupId);
|
||||
logger.trace("Unregistered group handler for ID {}", groupId);
|
||||
} else {
|
||||
LutronHandler handler = (LutronHandler) childHandler;
|
||||
int intId = handler.getIntegrationId();
|
||||
childHandlerMap.remove(intId);
|
||||
logger.trace("Unregistered child handler for ID {}", intId);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
Future<?> asyncInitializeTask = this.asyncInitializeTask;
|
||||
if (asyncInitializeTask != null && !asyncInitializeTask.isDone()) {
|
||||
asyncInitializeTask.cancel(true); // Interrupt async init task if it isn't done yet
|
||||
}
|
||||
disconnect(true);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.binding.BaseBridgeHandler;
|
||||
|
||||
/**
|
||||
* Abstract base class for Lutron bridge handlers
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class LutronBridgeHandler extends BaseBridgeHandler {
|
||||
|
||||
public LutronBridgeHandler(Bridge bridge) {
|
||||
super(bridge);
|
||||
}
|
||||
|
||||
public abstract void sendCommand(LutronCommandNew command);
|
||||
}
|
@ -14,9 +14,17 @@ package org.openhab.binding.lutron.internal.handler;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.DeviceCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.GroupCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandNew;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronDuration;
|
||||
import org.openhab.binding.lutron.internal.protocol.ModeCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.SysvarCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.TimeclockCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.openhab.core.thing.ThingStatus;
|
||||
@ -30,8 +38,8 @@ import org.slf4j.LoggerFactory;
|
||||
* Base type for all Lutron thing handlers.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
* @author Bob Adair - Added additional commands and methods for status and state management
|
||||
*
|
||||
* @author Bob Adair - Added additional commands and methods for status and state management. Added TargetType to
|
||||
* LutronCommand for LEAP bridge.
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class LutronHandler extends BaseThingHandler {
|
||||
@ -58,10 +66,10 @@ public abstract class LutronHandler extends BaseThingHandler {
|
||||
protected void thingOfflineNotify() {
|
||||
}
|
||||
|
||||
protected @Nullable IPBridgeHandler getBridgeHandler() {
|
||||
protected @Nullable LutronBridgeHandler getBridgeHandler() {
|
||||
Bridge bridge = getBridge();
|
||||
|
||||
return bridge == null ? null : (IPBridgeHandler) bridge.getHandler();
|
||||
return bridge == null ? null : (LutronBridgeHandler) bridge.getHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -79,8 +87,8 @@ public abstract class LutronHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private void sendCommand(LutronCommand command) {
|
||||
IPBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
protected void sendCommand(LutronCommandNew command) {
|
||||
LutronBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
|
||||
if (bridgeHandler == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.HANDLER_MISSING_ERROR, "No bridge associated");
|
||||
@ -90,58 +98,52 @@ public abstract class LutronHandler extends BaseThingHandler {
|
||||
}
|
||||
}
|
||||
|
||||
protected void output(Object... parameters) {
|
||||
protected void output(TargetType type, int action, @Nullable Number parameter, @Nullable LutronDuration fade,
|
||||
@Nullable LutronDuration delay) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.OUTPUT, getIntegrationId(), parameters));
|
||||
new OutputCommand(type, LutronOperation.EXECUTE, getIntegrationId(), action, parameter, fade, delay));
|
||||
}
|
||||
|
||||
protected void device(Object... parameters) {
|
||||
protected void queryOutput(TargetType type, int action) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.DEVICE, getIntegrationId(), parameters));
|
||||
new OutputCommand(type, LutronOperation.QUERY, getIntegrationId(), action, (Integer) null, null, null));
|
||||
}
|
||||
|
||||
protected void timeclock(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.TIMECLOCK, getIntegrationId(),
|
||||
parameters));
|
||||
protected void device(TargetType type, Integer component, @Nullable Integer leapComponent, Integer action,
|
||||
@Nullable Object parameter) {
|
||||
sendCommand(new DeviceCommand(type, LutronOperation.EXECUTE, getIntegrationId(), component, leapComponent,
|
||||
action, parameter));
|
||||
}
|
||||
|
||||
protected void greenMode(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.MODE, getIntegrationId(), parameters));
|
||||
protected void queryDevice(TargetType type, Integer component, Integer action) {
|
||||
sendCommand(new DeviceCommand(type, LutronOperation.QUERY, getIntegrationId(), component, null, action, null));
|
||||
}
|
||||
|
||||
protected void sysvar(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.SYSVAR, getIntegrationId(), parameters));
|
||||
protected void timeclock(Integer action, @Nullable Object parameter, @Nullable Boolean enable) {
|
||||
sendCommand(new TimeclockCommand(LutronOperation.EXECUTE, getIntegrationId(), action, parameter, enable));
|
||||
}
|
||||
|
||||
protected void shadegrp(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.EXECUTE, LutronCommandType.SHADEGRP, getIntegrationId(), parameters));
|
||||
protected void queryTimeclock(Integer action) {
|
||||
sendCommand(new TimeclockCommand(LutronOperation.QUERY, getIntegrationId(), action, null, null));
|
||||
}
|
||||
|
||||
protected void queryOutput(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.OUTPUT, getIntegrationId(), parameters));
|
||||
protected void sysvar(Integer action, Object parameter) {
|
||||
sendCommand(new SysvarCommand(LutronOperation.EXECUTE, getIntegrationId(), action, parameter));
|
||||
}
|
||||
|
||||
protected void queryDevice(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.DEVICE, getIntegrationId(), parameters));
|
||||
protected void querySysvar(Integer action) {
|
||||
sendCommand(new SysvarCommand(LutronOperation.QUERY, getIntegrationId(), action, null));
|
||||
}
|
||||
|
||||
protected void queryTimeclock(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.QUERY, LutronCommandType.TIMECLOCK, getIntegrationId(), parameters));
|
||||
protected void greenMode(Integer action, @Nullable Integer parameter) {
|
||||
sendCommand(new ModeCommand(LutronOperation.EXECUTE, getIntegrationId(), action, parameter));
|
||||
}
|
||||
|
||||
protected void queryGreenMode(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.MODE, getIntegrationId(), parameters));
|
||||
protected void queryGreenMode(Integer action) {
|
||||
sendCommand(new ModeCommand(LutronOperation.QUERY, getIntegrationId(), action, null));
|
||||
}
|
||||
|
||||
protected void querySysvar(Object... parameters) {
|
||||
sendCommand(new LutronCommand(LutronOperation.QUERY, LutronCommandType.SYSVAR, getIntegrationId(), parameters));
|
||||
}
|
||||
|
||||
protected void queryShadegrp(Object... parameters) {
|
||||
sendCommand(
|
||||
new LutronCommand(LutronOperation.QUERY, LutronCommandType.SHADEGRP, getIntegrationId(), parameters));
|
||||
protected void queryGroup(Integer action) {
|
||||
sendCommand(new GroupCommand(LutronOperation.QUERY, getIntegrationId(), action, null));
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,130 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.handler;
|
||||
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL_GROUPSTATE;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.config.OGroupConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.GroupCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
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.types.Command;
|
||||
import org.openhab.core.types.RefreshType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Handler responsible for communicating occupancy group states.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OGroupHandler extends LutronHandler {
|
||||
private static final String STATE_OCCUPIED = "OCCUPIED";
|
||||
private static final String STATE_UNOCCUPIED = "UNOCCUPIED";
|
||||
private static final String STATE_UNKNOWN = "UNKNOWN";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OGroupHandler.class);
|
||||
|
||||
private @NonNullByDefault({}) OGroupConfig config;
|
||||
|
||||
public OGroupHandler(Thing thing) {
|
||||
super(thing);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getIntegrationId() {
|
||||
if (this.config == null) {
|
||||
throw new IllegalStateException("handler not initialized");
|
||||
}
|
||||
return config.integrationId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
config = getConfigAs(OGroupConfig.class);
|
||||
if (config.integrationId <= 0) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"No valid integrationId configured");
|
||||
return;
|
||||
}
|
||||
logger.debug("Initializing Occupancy Group handler for integration ID {}", getIntegrationId());
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void initDeviceState() {
|
||||
logger.debug("Initializing device state for Occupancy Group {}", getIntegrationId());
|
||||
Bridge bridge = getBridge();
|
||||
if (bridge == null) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryGroup(GroupCommand.ACTION_GROUPSTATE);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_GROUPSTATE)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryGroup(GroupCommand.ACTION_GROUPSTATE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_GROUPSTATE)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryGroup(GroupCommand.ACTION_GROUPSTATE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
int state;
|
||||
|
||||
if (type == LutronCommandType.GROUP && parameters.length > 1
|
||||
&& GroupCommand.ACTION_GROUPSTATE.toString().equals(parameters[0])) {
|
||||
try {
|
||||
state = Integer.parseInt(parameters[1]);
|
||||
} catch (NumberFormatException e) {
|
||||
logger.debug("Error parsing response parameter: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
if (state == GroupCommand.STATE_GRP_OCCUPIED) {
|
||||
updateState(CHANNEL_GROUPSTATE, new StringType(STATE_OCCUPIED));
|
||||
} else if (state == GroupCommand.STATE_GRP_UNOCCUPIED) {
|
||||
updateState(CHANNEL_GROUPSTATE, new StringType(STATE_UNOCCUPIED));
|
||||
} else if (state == GroupCommand.STATE_GRP_UNKNOWN) {
|
||||
updateState(CHANNEL_GROUPSTATE, new StringType(STATE_UNKNOWN));
|
||||
} else {
|
||||
logger.debug("Invalid occupancy state received: {}", state);
|
||||
updateState(CHANNEL_GROUPSTATE, new StringType(STATE_UNKNOWN));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,8 @@ package org.openhab.binding.lutron.internal.handler;
|
||||
import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL_OCCUPANCYSTATUS;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.DeviceCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -33,10 +34,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OccupancySensorHandler extends LutronHandler {
|
||||
private static final String OCCUPIED_STATE_UPDATE = "2";
|
||||
private static final String STATE_OCCUPIED = "3";
|
||||
private static final String STATE_UNOCCUPIED = "4";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OccupancySensorHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
@ -82,10 +79,11 @@ public class OccupancySensorHandler extends LutronHandler {
|
||||
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.DEVICE && parameters.length == 2 && OCCUPIED_STATE_UPDATE.equals(parameters[0])) {
|
||||
if (STATE_OCCUPIED.equals(parameters[1])) {
|
||||
if (type == LutronCommandType.DEVICE && parameters.length == 2
|
||||
&& DeviceCommand.OCCUPIED_STATE_COMPONENT.equals(parameters[0])) {
|
||||
if (DeviceCommand.STATE_OCCUPIED.equals(parameters[1])) {
|
||||
updateState(CHANNEL_OCCUPANCYSTATUS, OnOffType.ON);
|
||||
} else if (STATE_UNOCCUPIED.equals(parameters[1])) {
|
||||
} else if (DeviceCommand.STATE_UNOCCUPIED.equals(parameters[1])) {
|
||||
updateState(CHANNEL_OCCUPANCYSTATUS, OnOffType.OFF);
|
||||
}
|
||||
}
|
||||
|
@ -42,10 +42,20 @@ public class PicoKeypadHandler extends BaseKeypadHandler {
|
||||
|
||||
switch (mod) {
|
||||
case "2B":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_2B;
|
||||
break;
|
||||
case "2BRL":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_2BRL;
|
||||
break;
|
||||
case "3B":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_3B;
|
||||
break;
|
||||
case "4B":
|
||||
buttonList = kp.getComponents(mod, ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_4B;
|
||||
break;
|
||||
default:
|
||||
logger.warn("No valid keypad model defined ({}). Assuming model 3BRL.", mod);
|
||||
@ -53,6 +63,7 @@ public class PicoKeypadHandler extends BaseKeypadHandler {
|
||||
case "Generic":
|
||||
case "3BRL":
|
||||
buttonList = kp.getComponents("3BRL", ComponentType.BUTTON);
|
||||
leapButtonMap = KeypadConfigPico.LEAPBUTTONS_3BRL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,9 @@ import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.PercentType;
|
||||
import org.openhab.core.library.types.StopMoveType;
|
||||
import org.openhab.core.library.types.UpDownType;
|
||||
@ -38,16 +40,12 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShadeHandler extends LutronHandler {
|
||||
private static final Integer ACTION_ZONELEVEL = 1;
|
||||
private static final Integer ACTION_STARTRAISING = 2;
|
||||
private static final Integer ACTION_STARTLOWERING = 3;
|
||||
private static final Integer ACTION_STOP = 4;
|
||||
private static final Integer ACTION_POSITION_UPDATE = 32; // undocumented in integration protocol guide
|
||||
private static final Integer PARAMETER_POSITION_UPDATE = 2; // undocumented in integration protocol guide
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ShadeHandler.class);
|
||||
|
||||
protected int integrationId;
|
||||
private boolean leap = false;
|
||||
|
||||
public ShadeHandler(Thing thing) {
|
||||
super(thing);
|
||||
@ -68,6 +66,11 @@ public class ShadeHandler extends LutronHandler {
|
||||
integrationId = id.intValue();
|
||||
logger.debug("Initializing Shade handler for integration ID {}", id);
|
||||
|
||||
LutronBridgeHandler bridgeHandler = getBridgeHandler();
|
||||
if (bridgeHandler instanceof LeapBridgeHandler) {
|
||||
leap = true;
|
||||
}
|
||||
|
||||
initDeviceState();
|
||||
}
|
||||
|
||||
@ -79,7 +82,8 @@ public class ShadeHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_ZONELEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.SHADE, OutputCommand.ACTION_ZONELEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@ -89,7 +93,7 @@ public class ShadeHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
// Refresh state when new item is linked.
|
||||
if (channelUID.getId().equals(CHANNEL_SHADELEVEL)) {
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
queryOutput(TargetType.SHADE, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,15 +102,27 @@ public class ShadeHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_SHADELEVEL)) {
|
||||
if (command instanceof PercentType) {
|
||||
int level = ((PercentType) command).intValue();
|
||||
output(ACTION_ZONELEVEL, level, 0);
|
||||
output(TargetType.SHADE, OutputCommand.ACTION_ZONELEVEL, level, null, null);
|
||||
if (leap) {
|
||||
// LEAP may not send back a position update
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(level));
|
||||
}
|
||||
} else if (command.equals(UpDownType.UP)) {
|
||||
output(ACTION_STARTRAISING);
|
||||
output(TargetType.SHADE, OutputCommand.ACTION_STARTRAISING, null, null, null);
|
||||
if (leap) {
|
||||
// LEAP won't send a position update when fully open
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(100));
|
||||
}
|
||||
} else if (command.equals(UpDownType.DOWN)) {
|
||||
output(ACTION_STARTLOWERING);
|
||||
output(TargetType.SHADE, OutputCommand.ACTION_STARTLOWERING, null, null, null);
|
||||
if (leap) {
|
||||
// LEAP won't send a position update when fully closed
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(0));
|
||||
}
|
||||
} else if (command.equals(StopMoveType.STOP)) {
|
||||
output(ACTION_STOP);
|
||||
output(TargetType.SHADE, OutputCommand.ACTION_STOP, null, null, null);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
queryOutput(TargetType.SHADE, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -114,14 +130,14 @@ public class ShadeHandler extends LutronHandler {
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length >= 2) {
|
||||
if (ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
if (OutputCommand.ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
logger.trace("Shade {} received zone level: {}", getIntegrationId(), level);
|
||||
updateState(CHANNEL_SHADELEVEL, new PercentType(level));
|
||||
} else if (ACTION_POSITION_UPDATE.toString().equals(parameters[0])
|
||||
} else if (OutputCommand.ACTION_POSITION_UPDATE.toString().equals(parameters[0])
|
||||
&& PARAMETER_POSITION_UPDATE.toString().equals(parameters[1]) && parameters.length >= 3) {
|
||||
BigDecimal level = new BigDecimal(parameters[2]);
|
||||
logger.trace("Shade {} received position update: {}", getIntegrationId(), level);
|
||||
|
@ -17,7 +17,9 @@ import static org.openhab.binding.lutron.internal.LutronBindingConstants.CHANNEL
|
||||
import java.math.BigDecimal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.OutputCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.library.types.OnOffType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -36,8 +38,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SwitchHandler extends LutronHandler {
|
||||
private static final Integer ACTION_ZONELEVEL = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SwitchHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
@ -67,7 +67,8 @@ public class SwitchHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryOutput(ACTION_ZONELEVEL); // handleUpdate() will set thing status to online when response arrives
|
||||
queryOutput(TargetType.SWITCH, OutputCommand.ACTION_ZONELEVEL);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@ -77,9 +78,9 @@ public class SwitchHandler extends LutronHandler {
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
if (channelUID.getId().equals(CHANNEL_SWITCH)) {
|
||||
if (command.equals(OnOffType.ON)) {
|
||||
output(ACTION_ZONELEVEL, 100);
|
||||
output(TargetType.SWITCH, OutputCommand.ACTION_ZONELEVEL, 100, null, null);
|
||||
} else if (command.equals(OnOffType.OFF)) {
|
||||
output(ACTION_ZONELEVEL, 0);
|
||||
output(TargetType.SWITCH, OutputCommand.ACTION_ZONELEVEL, 0, null, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -92,7 +93,7 @@ public class SwitchHandler extends LutronHandler {
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.OUTPUT && parameters.length > 1
|
||||
&& ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
&& OutputCommand.ACTION_ZONELEVEL.toString().equals(parameters[0])) {
|
||||
BigDecimal level = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
@ -105,7 +106,7 @@ public class SwitchHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_SWITCH)) {
|
||||
// Refresh state when new item is linked.
|
||||
queryOutput(ACTION_ZONELEVEL);
|
||||
queryOutput(TargetType.SWITCH, OutputCommand.ACTION_ZONELEVEL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -19,7 +19,8 @@ import java.math.BigDecimal;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.config.SysvarConfig;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.SysvarCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
import org.openhab.core.thing.ChannelUID;
|
||||
@ -38,8 +39,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SysvarHandler extends LutronHandler {
|
||||
private static final Integer ACTION_GETSETSYSVAR = 1;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(SysvarHandler.class);
|
||||
|
||||
private @Nullable SysvarConfig config;
|
||||
@ -80,7 +79,8 @@ public class SysvarHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
querySysvar(ACTION_GETSETSYSVAR); // handleUpdate() will set thing status to online when response arrives
|
||||
querySysvar(SysvarCommand.ACTION_GETSETSYSVAR);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@ -90,7 +90,7 @@ public class SysvarHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
if (channelUID.getId().equals(CHANNEL_VARSTATE)) {
|
||||
// Refresh state when new item is linked.
|
||||
querySysvar(ACTION_GETSETSYSVAR);
|
||||
querySysvar(SysvarCommand.ACTION_GETSETSYSVAR);
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +99,7 @@ public class SysvarHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_VARSTATE)) {
|
||||
if (command instanceof Number) {
|
||||
int state = ((Number) command).intValue();
|
||||
sysvar(ACTION_GETSETSYSVAR, state);
|
||||
sysvar(SysvarCommand.ACTION_GETSETSYSVAR, state);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -107,7 +107,7 @@ public class SysvarHandler extends LutronHandler {
|
||||
@Override
|
||||
public void handleUpdate(LutronCommandType type, String... parameters) {
|
||||
if (type == LutronCommandType.SYSVAR && parameters.length > 1
|
||||
&& ACTION_GETSETSYSVAR.toString().equals(parameters[0])) {
|
||||
&& SysvarCommand.ACTION_GETSETSYSVAR.toString().equals(parameters[0])) {
|
||||
BigDecimal state = new BigDecimal(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
|
@ -20,7 +20,8 @@ import java.util.Calendar;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.TimeclockCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.core.library.types.DateTimeType;
|
||||
import org.openhab.core.library.types.DecimalType;
|
||||
import org.openhab.core.thing.Bridge;
|
||||
@ -40,14 +41,6 @@ import org.slf4j.LoggerFactory;
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TimeclockHandler extends LutronHandler {
|
||||
private static final Integer ACTION_CLOCKMODE = 1;
|
||||
private static final Integer ACTION_SUNRISE = 2;
|
||||
private static final Integer ACTION_SUNSET = 3;
|
||||
private static final Integer ACTION_EXECEVENT = 5;
|
||||
private static final Integer ACTION_SETEVENT = 6;
|
||||
private static final Integer EVENT_ENABLE = 1;
|
||||
private static final Integer EVENT_DISABLE = 2;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(TimeclockHandler.class);
|
||||
|
||||
private int integrationId;
|
||||
@ -81,7 +74,8 @@ public class TimeclockHandler extends LutronHandler {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No bridge configured");
|
||||
} else if (bridge.getStatus() == ThingStatus.ONLINE) {
|
||||
updateStatus(ThingStatus.UNKNOWN, ThingStatusDetail.NONE, "Awaiting initial response");
|
||||
queryTimeclock(ACTION_CLOCKMODE); // handleUpdate() will set thing status to online when response arrives
|
||||
queryTimeclock(TimeclockCommand.ACTION_CLOCKMODE);
|
||||
// handleUpdate() will set thing status to online when response arrives
|
||||
} else {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
|
||||
}
|
||||
@ -91,7 +85,7 @@ public class TimeclockHandler extends LutronHandler {
|
||||
public void channelLinked(ChannelUID channelUID) {
|
||||
logger.debug("Handling channel link request for timeclock {}", integrationId);
|
||||
if (channelUID.getId().equals(CHANNEL_CLOCKMODE)) {
|
||||
queryTimeclock(ACTION_CLOCKMODE);
|
||||
queryTimeclock(TimeclockCommand.ACTION_CLOCKMODE);
|
||||
}
|
||||
}
|
||||
|
||||
@ -103,42 +97,42 @@ public class TimeclockHandler extends LutronHandler {
|
||||
if (channelUID.getId().equals(CHANNEL_CLOCKMODE)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer mode = ((DecimalType) command).intValue();
|
||||
timeclock(ACTION_CLOCKMODE, mode);
|
||||
timeclock(TimeclockCommand.ACTION_CLOCKMODE, mode, null);
|
||||
} else if (command instanceof RefreshType) {
|
||||
queryTimeclock(ACTION_CLOCKMODE);
|
||||
queryTimeclock(TimeclockCommand.ACTION_CLOCKMODE);
|
||||
} else {
|
||||
logger.debug("Invalid command type for clockmode channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_EXECEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = ((DecimalType) command).intValue();
|
||||
timeclock(ACTION_EXECEVENT, index);
|
||||
timeclock(TimeclockCommand.ACTION_EXECEVENT, index, null);
|
||||
} else {
|
||||
logger.debug("Invalid command type for execevent channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_SUNRISE)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryTimeclock(ACTION_SUNRISE);
|
||||
queryTimeclock(TimeclockCommand.ACTION_SUNRISE);
|
||||
} else {
|
||||
logger.debug("Invalid command type for sunrise channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_SUNSET)) {
|
||||
if (command instanceof RefreshType) {
|
||||
queryTimeclock(ACTION_SUNSET);
|
||||
queryTimeclock(TimeclockCommand.ACTION_SUNSET);
|
||||
} else {
|
||||
logger.debug("Invalid command type for sunset channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_ENABLEEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = ((DecimalType) command).intValue();
|
||||
timeclock(ACTION_SETEVENT, index, EVENT_ENABLE);
|
||||
timeclock(TimeclockCommand.ACTION_SETEVENT, index, true);
|
||||
} else {
|
||||
logger.debug("Invalid command type for enableevent channnel");
|
||||
}
|
||||
} else if (channelUID.getId().equals(CHANNEL_DISABLEEVENT)) {
|
||||
if (command instanceof DecimalType) {
|
||||
Integer index = ((DecimalType) command).intValue();
|
||||
timeclock(ACTION_SETEVENT, index, EVENT_DISABLE);
|
||||
timeclock(TimeclockCommand.ACTION_SETEVENT, index, false);
|
||||
} else {
|
||||
logger.debug("Invalid command type for disableevent channnel");
|
||||
}
|
||||
@ -172,37 +166,37 @@ public class TimeclockHandler extends LutronHandler {
|
||||
logger.debug("Handling update received from timeclock {}", integrationId);
|
||||
|
||||
try {
|
||||
if (parameters.length >= 2 && ACTION_CLOCKMODE.toString().equals(parameters[0])) {
|
||||
if (parameters.length >= 2 && TimeclockCommand.ACTION_CLOCKMODE.toString().equals(parameters[0])) {
|
||||
Integer mode = Integer.valueOf(parameters[1]);
|
||||
if (getThing().getStatus() == ThingStatus.UNKNOWN) {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
updateState(CHANNEL_CLOCKMODE, new DecimalType(mode));
|
||||
|
||||
} else if (parameters.length >= 2 && ACTION_SUNRISE.toString().equals(parameters[0])) {
|
||||
} else if (parameters.length >= 2 && TimeclockCommand.ACTION_SUNRISE.toString().equals(parameters[0])) {
|
||||
Calendar calendar = parseLutronTime(parameters[1]);
|
||||
if (calendar != null) {
|
||||
updateState(CHANNEL_SUNRISE,
|
||||
new DateTimeType(ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault())));
|
||||
}
|
||||
|
||||
} else if (parameters.length >= 2 && ACTION_SUNSET.toString().equals(parameters[0])) {
|
||||
} else if (parameters.length >= 2 && TimeclockCommand.ACTION_SUNSET.toString().equals(parameters[0])) {
|
||||
Calendar calendar = parseLutronTime(parameters[1]);
|
||||
if (calendar != null) {
|
||||
updateState(CHANNEL_SUNSET,
|
||||
new DateTimeType(ZonedDateTime.ofInstant(calendar.toInstant(), ZoneId.systemDefault())));
|
||||
}
|
||||
|
||||
} else if (parameters.length >= 2 && ACTION_EXECEVENT.toString().equals(parameters[0])) {
|
||||
} else if (parameters.length >= 2 && TimeclockCommand.ACTION_EXECEVENT.toString().equals(parameters[0])) {
|
||||
Integer index = Integer.valueOf(parameters[1]);
|
||||
updateState(CHANNEL_EXECEVENT, new DecimalType(index));
|
||||
|
||||
} else if (parameters.length >= 3 && ACTION_SETEVENT.toString().equals(parameters[0])) {
|
||||
} else if (parameters.length >= 3 && TimeclockCommand.ACTION_SETEVENT.toString().equals(parameters[0])) {
|
||||
Integer index = Integer.valueOf(parameters[1]);
|
||||
Integer state = Integer.valueOf(parameters[2]);
|
||||
if (state.equals(EVENT_ENABLE)) {
|
||||
if (state.equals(TimeclockCommand.EVENT_ENABLE)) {
|
||||
updateState(CHANNEL_ENABLEEVENT, new DecimalType(index));
|
||||
} else if (state.equals(EVENT_DISABLE)) {
|
||||
} else if (state.equals(TimeclockCommand.EVENT_DISABLE)) {
|
||||
updateState(CHANNEL_DISABLEEVENT, new DecimalType(index));
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.openhab.core.thing.Thing;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@ -100,5 +101,6 @@ public class VirtualKeypadHandler extends BaseKeypadHandler {
|
||||
super(thing);
|
||||
// Mark all channels "Advanced" since most are unlikely to be used in any particular config
|
||||
advancedChannels = true;
|
||||
commandTargetType = TargetType.VIRTUALKEYPAD; // For the LEAP bridge
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,7 @@
|
||||
package org.openhab.binding.lutron.internal.keypadconfig;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Map;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.KeypadComponent;
|
||||
@ -26,6 +27,13 @@ import org.openhab.binding.lutron.internal.discovery.project.ComponentType;
|
||||
@NonNullByDefault
|
||||
public final class KeypadConfigPico extends KeypadConfig {
|
||||
|
||||
// Button mappings for LEAP protocol
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_2B = Map.of(2, 1, 4, 2);
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_2BRL = Map.of(2, 1, 4, 2, 5, 3, 6, 4);
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_3B = Map.of(2, 1, 3, 2, 4, 3);
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_4B = Map.of(8, 1, 9, 2, 10, 3, 11, 4);
|
||||
public static final Map<Integer, Integer> LEAPBUTTONS_3BRL = Map.of(2, 1, 3, 2, 4, 3, 5, 4, 6, 5);
|
||||
|
||||
private static enum Component implements KeypadComponent {
|
||||
// Buttons for 2B, 2BRL, 3B, and 3BRL models
|
||||
BUTTON1(2, "button1", "Button 1", ComponentType.BUTTON),
|
||||
|
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.CommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.Request;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Lutron DEVICE command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class DeviceCommand extends LutronCommandNew {
|
||||
// keypad defs
|
||||
public static final Integer ACTION_PRESS = 3;
|
||||
public static final Integer ACTION_RELEASE = 4;
|
||||
public static final Integer ACTION_HOLD = 5;
|
||||
public static final Integer ACTION_LED_STATE = 9;
|
||||
public static final Integer LED_OFF = 0;
|
||||
public static final Integer LED_ON = 1;
|
||||
public static final Integer LED_FLASH = 2; // Same as 1 on RA2 keypads
|
||||
public static final Integer LED_RAPIDFLASH = 3; // Same as 1 on RA2 keypads
|
||||
|
||||
// occupancy sensor defs
|
||||
public static final String OCCUPIED_STATE_COMPONENT = "2";
|
||||
public static final String STATE_OCCUPIED = "3";
|
||||
public static final String STATE_UNOCCUPIED = "4";
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(DeviceCommand.class);
|
||||
|
||||
private final Integer component;
|
||||
private final @Nullable Integer leapComponent;
|
||||
private final Integer action;
|
||||
private final @Nullable Object parameter;
|
||||
|
||||
public DeviceCommand(TargetType targetType, LutronOperation operation, Integer integrationId, Integer component,
|
||||
@Nullable Integer leapComponent, Integer action, @Nullable Object parameter) {
|
||||
super(targetType, operation, LutronCommandType.DEVICE, integrationId);
|
||||
this.action = action;
|
||||
this.component = component;
|
||||
this.leapComponent = leapComponent;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(component);
|
||||
builder.append(',').append(action);
|
||||
if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
if (targetType == TargetType.KEYPAD) {
|
||||
if (leapComponent == null) {
|
||||
logger.debug("Ignoring device command. No leap component in command.");
|
||||
return null;
|
||||
}
|
||||
|
||||
Integer integrationId = this.integrationId;
|
||||
Integer leapComponent = this.leapComponent; // make the broken null checker happy
|
||||
if (action.equals(DeviceCommand.ACTION_PRESS) && integrationId != null && leapComponent != null) {
|
||||
int button = bridgeHandler.getButton(integrationId, leapComponent);
|
||||
if (button > 0) {
|
||||
return new LeapCommand(Request.buttonCommand(button, CommandType.PRESSANDHOLD));
|
||||
}
|
||||
} else if (action.equals(DeviceCommand.ACTION_RELEASE) && integrationId != null && leapComponent != null) {
|
||||
int button = bridgeHandler.getButton(integrationId, leapComponent);
|
||||
if (button > 0) {
|
||||
return new LeapCommand(Request.buttonCommand(button, CommandType.RELEASE));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring device command with unsupported action.");
|
||||
return null;
|
||||
}
|
||||
} else if (targetType == TargetType.VIRTUALKEYPAD) {
|
||||
if (action.equals(DeviceCommand.ACTION_PRESS)) {
|
||||
return new LeapCommand(Request.virtualButtonCommand(component, CommandType.PRESSANDRELEASE));
|
||||
} else if (!action.equals(DeviceCommand.ACTION_RELEASE)) {
|
||||
logger.debug("Ignoring device command with unsupported action.");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring device command with unsupported target type.");
|
||||
return null;
|
||||
}
|
||||
logger.debug("Ignoring unsupported device command.");
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* Defines Lutron fan controller speed settings
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum FanSpeedType {
|
||||
@SerializedName("High")
|
||||
HIGH(100, "High"),
|
||||
@SerializedName("MediumHigh")
|
||||
MEDIUMHIGH(75, "MediumHigh"),
|
||||
@SerializedName("Medium")
|
||||
MEDIUM(50, "Medium"),
|
||||
@SerializedName("Low")
|
||||
LOW(25, "Low"),
|
||||
@SerializedName("Off")
|
||||
OFF(0, "Off");
|
||||
|
||||
/** Fan speed expressed as a percentage **/
|
||||
private final int speed;
|
||||
|
||||
/** Fan speed expressed as a String (used by LEAP) **/
|
||||
private final String leapValue;
|
||||
|
||||
FanSpeedType(int speed, String leapValue) {
|
||||
this.speed = speed;
|
||||
this.leapValue = leapValue;
|
||||
}
|
||||
|
||||
public int speed() {
|
||||
return speed;
|
||||
}
|
||||
|
||||
public String leapValue() {
|
||||
return leapValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return leapValue;
|
||||
}
|
||||
|
||||
public static FanSpeedType toFanSpeedType(int percentage) {
|
||||
if (percentage == OFF.speed) {
|
||||
return FanSpeedType.OFF;
|
||||
} else if (percentage > OFF.speed && percentage <= LOW.speed) {
|
||||
return FanSpeedType.LOW;
|
||||
} else if (percentage > LOW.speed && percentage <= MEDIUM.speed) {
|
||||
return FanSpeedType.MEDIUM;
|
||||
} else if (percentage > MEDIUM.speed && percentage <= MEDIUMHIGH.speed) {
|
||||
return FanSpeedType.MEDIUMHIGH;
|
||||
} else {
|
||||
return FanSpeedType.HIGH;
|
||||
}
|
||||
}
|
||||
|
||||
public static FanSpeedType toFanSpeedType(String speedString) {
|
||||
for (FanSpeedType enumValue : FanSpeedType.values()) {
|
||||
if (enumValue.leapValue.equalsIgnoreCase(speedString)) {
|
||||
return enumValue;
|
||||
}
|
||||
}
|
||||
return OFF;
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.Request;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron GROUP command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GroupCommand extends LutronCommandNew {
|
||||
public static final Integer ACTION_GROUPSTATE = 3;
|
||||
public static final Integer STATE_GRP_OCCUPIED = 3;
|
||||
public static final Integer STATE_GRP_UNOCCUPIED = 4;
|
||||
public static final Integer STATE_GRP_UNKNOWN = 255;
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Integer state;
|
||||
|
||||
/**
|
||||
* GroupCommand constructor
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param state
|
||||
*/
|
||||
public GroupCommand(LutronOperation operation, Integer integrationId, Integer action, @Nullable Integer state) {
|
||||
super(TargetType.GROUP, operation, LutronCommandType.GROUP, integrationId);
|
||||
this.action = action;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
if (state != null) {
|
||||
builder.append(',').append(state);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
if (action.equals(GroupCommand.ACTION_GROUPSTATE)) {
|
||||
// Get status for all occupancy groups because you can't query just one
|
||||
return new LeapCommand(Request.getOccupancyGroupStatus());
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Generic LIP command for use inside bridge handler
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LIPCommand extends LutronCommandNew {
|
||||
private final Object[] parameters;
|
||||
|
||||
public LIPCommand(TargetType targetType, LutronOperation operation, LutronCommandType CommandType,
|
||||
@Nullable Integer integrationId, Object... parameters) {
|
||||
super(targetType, operation, CommandType, integrationId);
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
if (integrationId != null) {
|
||||
builder.append(',').append(integrationId);
|
||||
}
|
||||
if (parameters != null) { // This CAN be null
|
||||
for (Object parameter : parameters) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
|
||||
public int getNumberParameter(int position) {
|
||||
if (parameters.length > position && parameters[position] instanceof Number) {
|
||||
Number num = (Number) parameters[position];
|
||||
return num.intValue();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid command parameter");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,62 +0,0 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
/**
|
||||
* Command to a Lutron integration access point.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*
|
||||
*/
|
||||
public class LutronCommand {
|
||||
private final LutronOperation operation;
|
||||
private final LutronCommandType type;
|
||||
private final int integrationId;
|
||||
private final Object[] parameters;
|
||||
|
||||
public LutronCommand(LutronOperation operation, LutronCommandType type, int integrationId, Object... parameters) {
|
||||
this.operation = operation;
|
||||
this.type = type;
|
||||
this.integrationId = integrationId;
|
||||
this.parameters = parameters;
|
||||
}
|
||||
|
||||
public LutronCommandType getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
public int getIntegrationId() {
|
||||
return this.integrationId;
|
||||
}
|
||||
|
||||
public Object[] getParameters() {
|
||||
return this.parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder().append(this.operation).append(this.type);
|
||||
|
||||
if (integrationId >= 0) {
|
||||
builder.append(',').append(this.integrationId);
|
||||
}
|
||||
|
||||
if (parameters != null) {
|
||||
for (Object parameter : parameters) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron command abstract base class
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class LutronCommandNew {
|
||||
public final TargetType targetType;
|
||||
protected final LutronOperation operation;
|
||||
protected final LutronCommandType commandType;
|
||||
protected final @Nullable Integer integrationId;
|
||||
|
||||
public LutronCommandNew(TargetType targetType, LutronOperation operation, LutronCommandType type,
|
||||
@Nullable Integer integrationId) {
|
||||
this.targetType = targetType;
|
||||
this.operation = operation;
|
||||
this.commandType = type;
|
||||
this.integrationId = integrationId;
|
||||
}
|
||||
|
||||
public LutronCommandType getType() {
|
||||
return commandType;
|
||||
}
|
||||
|
||||
public LutronOperation getOperation() {
|
||||
return operation;
|
||||
}
|
||||
|
||||
public @Nullable Integer getIntegrationId() {
|
||||
return integrationId;
|
||||
}
|
||||
|
||||
public abstract String lipCommand();
|
||||
|
||||
public abstract @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone);
|
||||
}
|
@ -131,7 +131,15 @@ public class LutronDuration {
|
||||
}
|
||||
|
||||
public String asLeapString() {
|
||||
return ""; // TBD
|
||||
Integer seconds = this.seconds;
|
||||
if (seconds.equals(0) && hundredths > 0) {
|
||||
// use 1 second if interval is > 0 and < 1
|
||||
seconds = 1;
|
||||
} else if (hundredths >= 50) {
|
||||
// else apply normal rounding of hundredths
|
||||
seconds++;
|
||||
}
|
||||
return String.format("%02d:%02d:%02d", seconds / 3600, (seconds % 3600) / 60, (seconds % 60));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -0,0 +1,62 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron MODE command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ModeCommand extends LutronCommandNew {
|
||||
public static final Integer ACTION_STEP = 1;
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Integer parameter;
|
||||
|
||||
public ModeCommand(LutronOperation operation, Integer integrationId, Integer action, @Nullable Integer parameter) {
|
||||
super(TargetType.GREENMODE, operation, LutronCommandType.MODE, integrationId);
|
||||
this.action = action;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.CommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.Request;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Lutron OUTPUT command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class OutputCommand extends LutronCommandNew {
|
||||
// shade, blind, dimmer defs
|
||||
public static final Integer ACTION_ZONELEVEL = 1;
|
||||
public static final Integer ACTION_STARTRAISING = 2;
|
||||
public static final Integer ACTION_STARTLOWERING = 3;
|
||||
public static final Integer ACTION_STOP = 4;
|
||||
public static final Integer ACTION_POSITION_UPDATE = 32; // For shades/blinds. Undocumented in protocol guide.
|
||||
|
||||
// blind defs
|
||||
public static final Integer ACTION_LIFTLEVEL = 1;
|
||||
public static final Integer ACTION_TILTLEVEL = 9;
|
||||
public static final Integer ACTION_LIFTTILTLEVEL = 10;
|
||||
public static final Integer ACTION_STARTRAISINGTILT = 11;
|
||||
public static final Integer ACTION_STARTLOWERINGTILT = 12;
|
||||
public static final Integer ACTION_STOPTILT = 13;
|
||||
public static final Integer ACTION_STARTRAISINGLIFT = 14;
|
||||
public static final Integer ACTION_STARTLOWERINGLIFT = 15;
|
||||
public static final Integer ACTION_STOPLIFT = 16;
|
||||
|
||||
// cco defs
|
||||
public static final Integer ACTION_STATE = 1;
|
||||
public static final Integer ACTION_PULSE = 6;
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(OutputCommand.class);
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Number parameter;
|
||||
private final @Nullable LutronDuration fadeTime;
|
||||
private final @Nullable LutronDuration delayTime;
|
||||
private final FanSpeedType fanSpeed;
|
||||
|
||||
/**
|
||||
* OutputCommand constructor
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param parameter
|
||||
* @param fadeTime
|
||||
* @param delayTime
|
||||
*/
|
||||
public OutputCommand(TargetType targetType, LutronOperation operation, Integer integrationId, Integer action,
|
||||
@Nullable Number parameter, @Nullable LutronDuration fadeTime, @Nullable LutronDuration delayTime) {
|
||||
super(targetType, operation, LutronCommandType.OUTPUT, integrationId);
|
||||
this.action = action;
|
||||
this.parameter = parameter;
|
||||
if (parameter != null) {
|
||||
this.fanSpeed = FanSpeedType.toFanSpeedType(parameter.intValue());
|
||||
} else {
|
||||
this.fanSpeed = FanSpeedType.OFF;
|
||||
}
|
||||
this.fadeTime = fadeTime;
|
||||
this.delayTime = delayTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* OutputCommand constructor for fan commands
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param fanSpeed
|
||||
* @param fadeTime
|
||||
* @param delayTime
|
||||
*/
|
||||
public OutputCommand(TargetType targetType, LutronOperation operation, Integer integrationId, Integer action,
|
||||
FanSpeedType fanSpeed, @Nullable LutronDuration fadeTime, @Nullable LutronDuration delayTime) {
|
||||
super(targetType, operation, LutronCommandType.OUTPUT, integrationId);
|
||||
this.action = action;
|
||||
this.fanSpeed = fanSpeed;
|
||||
this.parameter = fanSpeed.speed();
|
||||
this.fadeTime = fadeTime;
|
||||
this.delayTime = delayTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
|
||||
if (parameter != null && targetType == TargetType.CCO && action.equals(OutputCommand.ACTION_PULSE)) {
|
||||
builder.append(',').append(String.format(Locale.ROOT, "%.2f", parameter));
|
||||
} else if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
|
||||
if (fadeTime != null) {
|
||||
builder.append(',').append(fadeTime);
|
||||
} else if (fadeTime == null && delayTime != null) {
|
||||
// must add 0 placeholder here in order to set delay time
|
||||
builder.append(',').append("0");
|
||||
}
|
||||
if (delayTime != null) {
|
||||
builder.append(',').append(delayTime);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
int zone;
|
||||
Number parameter = this.parameter;
|
||||
|
||||
if (leapZone == null) {
|
||||
return null;
|
||||
} else {
|
||||
zone = leapZone;
|
||||
}
|
||||
|
||||
if (operation == LutronOperation.QUERY) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL)) {
|
||||
return new LeapCommand(Request.getZoneStatus(zone));
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported query action");
|
||||
return null;
|
||||
}
|
||||
} else if (operation == LutronOperation.EXECUTE) {
|
||||
if (targetType == TargetType.SWITCH) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL) && parameter != null) {
|
||||
return new LeapCommand(Request.goToLevel(zone, parameter.intValue()));
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported switch action");
|
||||
return null;
|
||||
}
|
||||
} else if (targetType == TargetType.DIMMER) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL) && parameter != null) {
|
||||
if (fadeTime == null && delayTime == null) {
|
||||
return new LeapCommand(Request.goToLevel(zone, parameter.intValue()));
|
||||
} else {
|
||||
LutronDuration fade = (fadeTime == null) ? new LutronDuration(0) : fadeTime;
|
||||
LutronDuration delay = (delayTime == null) ? new LutronDuration(0) : delayTime;
|
||||
return new LeapCommand(Request.goToDimmedLevel(zone, parameter.intValue(), fade.asLeapString(),
|
||||
delay.asLeapString()));
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported dimmer action");
|
||||
return null;
|
||||
}
|
||||
} else if (targetType == TargetType.FAN) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL)) {
|
||||
return new LeapCommand(Request.goToFanSpeed(zone, fanSpeed));
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported fan action");
|
||||
return null;
|
||||
}
|
||||
} else if (targetType == TargetType.SHADE) {
|
||||
if (action.equals(OutputCommand.ACTION_ZONELEVEL) && parameter != null) {
|
||||
return new LeapCommand(Request.goToLevel(zone, parameter.intValue()));
|
||||
} else if (action.equals(OutputCommand.ACTION_STARTRAISING)) {
|
||||
return new LeapCommand(Request.zoneCommand(zone, CommandType.RAISE));
|
||||
} else if (action.equals(OutputCommand.ACTION_STARTLOWERING)) {
|
||||
return new LeapCommand(Request.zoneCommand(zone, CommandType.LOWER));
|
||||
} else if (action.equals(OutputCommand.ACTION_STOP)) {
|
||||
return new LeapCommand(Request.zoneCommand(zone, CommandType.STOP));
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported shade action");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported target type: {}", targetType);
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
logger.debug("Ignoring unsupported operation: {}", operation);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron SYSVAR command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class SysvarCommand extends LutronCommandNew {
|
||||
public static final Integer ACTION_GETSETSYSVAR = 1;
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Object parameter;
|
||||
|
||||
/**
|
||||
* SysvarCommand constructor
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param parameter
|
||||
*/
|
||||
public SysvarCommand(LutronOperation operation, Integer integrationId, Integer action, @Nullable Object parameter) {
|
||||
super(TargetType.SYSVAR, operation, LutronCommandType.SYSVAR, integrationId);
|
||||
this.action = action;
|
||||
this.parameter = parameter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
return null; // No equivalent LEAP command
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.handler.LeapBridgeHandler;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.LeapCommand;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronCommandType;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.LutronOperation;
|
||||
import org.openhab.binding.lutron.internal.protocol.lip.TargetType;
|
||||
|
||||
/**
|
||||
* Lutron TIMECLOCK command object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class TimeclockCommand extends LutronCommandNew {
|
||||
public static final Integer ACTION_CLOCKMODE = 1;
|
||||
public static final Integer ACTION_SUNRISE = 2;
|
||||
public static final Integer ACTION_SUNSET = 3;
|
||||
public static final Integer ACTION_EXECEVENT = 5;
|
||||
public static final Integer ACTION_SETEVENT = 6;
|
||||
public static final Integer EVENT_ENABLE = 1;
|
||||
public static final Integer EVENT_DISABLE = 2;
|
||||
|
||||
private final Integer action;
|
||||
private final @Nullable Object parameter;
|
||||
private final @Nullable Boolean enable;
|
||||
|
||||
/**
|
||||
* TimeclockCommand constructor
|
||||
*
|
||||
* @param targetType
|
||||
* @param operation
|
||||
* @param integrationId
|
||||
* @param action
|
||||
* @param parameter
|
||||
* @param enable true = enable, false = disable
|
||||
*/
|
||||
public TimeclockCommand(LutronOperation operation, Integer integrationId, Integer action,
|
||||
@Nullable Object parameter, @Nullable Boolean enable) {
|
||||
super(TargetType.TIMECLOCK, operation, LutronCommandType.TIMECLOCK, integrationId);
|
||||
this.action = action;
|
||||
this.parameter = parameter;
|
||||
this.enable = enable;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String lipCommand() {
|
||||
StringBuilder builder = new StringBuilder().append(operation).append(commandType);
|
||||
builder.append(',').append(integrationId);
|
||||
builder.append(',').append(action);
|
||||
if (parameter != null) {
|
||||
builder.append(',').append(parameter);
|
||||
}
|
||||
if (enable != null) {
|
||||
builder.append(',');
|
||||
builder.append((enable) ? '1' : '2');
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable LeapCommand leapCommand(LeapBridgeHandler bridgeHandler, @Nullable Integer leapZone) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return lipCommand();
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
|
||||
/**
|
||||
*
|
||||
* Abstract base class for LEAP message body objects
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public abstract class AbstractMessageBody {
|
||||
|
||||
/**
|
||||
* Utility method to extract the int from a href String using the supplied Pattern.
|
||||
*
|
||||
* @return the int or 0 if unable to extract
|
||||
*/
|
||||
protected static int hrefNumber(Pattern pattern, @Nullable String href) {
|
||||
if (href == null) {
|
||||
return 0;
|
||||
}
|
||||
Matcher matcher = pattern.matcher(href);
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
return Integer.parseInt(matcher.group(1));
|
||||
} catch (NumberFormatException e) {
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP CommandType enum
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum CommandType {
|
||||
@SerializedName("GoToDimmedLevel")
|
||||
GOTODIMMEDLEVEL("GoToDimmedLevel"),
|
||||
@SerializedName("GoToFanSpeed")
|
||||
GOTOFANSPEED("GoToFanSpeed"),
|
||||
@SerializedName("GoToLevel")
|
||||
GOTOLEVEL("GoToLevel"),
|
||||
@SerializedName("PressAndHold")
|
||||
PRESSANDHOLD("PressAndHold"),
|
||||
@SerializedName("PressAndRelease")
|
||||
PRESSANDRELEASE("PressAndRelease"),
|
||||
@SerializedName("Release")
|
||||
RELEASE("Release"),
|
||||
@SerializedName("ShadeLimitLower")
|
||||
SHADELIMITLOWER("ShadeLimitLower"),
|
||||
@SerializedName("ShadeLimitRaise")
|
||||
SHADELIMITRAISE("ShadeLimitRaise"),
|
||||
@SerializedName("Raise")
|
||||
RAISE("Raise"),
|
||||
@SerializedName("Lower")
|
||||
LOWER("Lower"),
|
||||
@SerializedName("Stop")
|
||||
STOP("Stop");
|
||||
|
||||
private final transient String string;
|
||||
|
||||
CommandType(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP CommuniqueType enum
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum CommuniqueType {
|
||||
// Requests
|
||||
@SerializedName("CreateRequest")
|
||||
CREATEREQUEST("CreateRequest"),
|
||||
@SerializedName("ReadRequest")
|
||||
READREQUEST("ReadRequest"),
|
||||
@SerializedName("UpdateRequest")
|
||||
UPDATEREQUEST("UpdateRequest"),
|
||||
@SerializedName("DeleteRequest")
|
||||
DELETEREQUEST("DeleteRequest"), // ?
|
||||
@SerializedName("SubscribeRequest")
|
||||
SUBSCRIBEREQUEST("SubscribeRequest"),
|
||||
@SerializedName("UnubscribeRequest")
|
||||
UNSUBSCRIBEREQUEST("UnubscribeRequest"),
|
||||
@SerializedName("Execute")
|
||||
EXECUTEREQUEST("Execute"),
|
||||
|
||||
// Responses
|
||||
@SerializedName("CreateResponse")
|
||||
CREATERESPONSE("CreateResponse"),
|
||||
@SerializedName("ReadResponse")
|
||||
READRESPONSE("ReadResponse"),
|
||||
@SerializedName("UpdateResponse")
|
||||
UPDATERESPONSE("UpdateResponse"),
|
||||
@SerializedName("DeleteResponse")
|
||||
DELETERESPONSE("DeleteResponse"), // ?
|
||||
@SerializedName("SubscribeResponse")
|
||||
SUBSCRIBERESPONSE("SubscribeResponse"),
|
||||
@SerializedName("UnsubscribeResponse")
|
||||
UNSUBSCRIBERESPONSE("UnsubscribeResponse"),
|
||||
@SerializedName("ExecuteResponse")
|
||||
EXECUTERESPONSE("ExecuteResponse"), // ?
|
||||
@SerializedName("ExceptionResponse")
|
||||
EXCEPTIONRESPONSE("ExceptionResponse");
|
||||
|
||||
private final transient String string;
|
||||
|
||||
CommuniqueType(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* LeapCommand represents a LEAP protocol command
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapCommand {
|
||||
private String command;
|
||||
|
||||
public LeapCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return command;
|
||||
}
|
||||
}
|
@ -0,0 +1,310 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ExceptionDetail;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Header;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroupStatus;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
import com.google.gson.JsonObject;
|
||||
import com.google.gson.JsonParseException;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.google.gson.JsonSyntaxException;
|
||||
|
||||
/**
|
||||
* Class responsible for parsing incoming LEAP messages
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class LeapMessageParser {
|
||||
private final Logger logger = LoggerFactory.getLogger(LeapMessageParser.class);
|
||||
|
||||
private final Gson gson;
|
||||
private final LeapMessageParserCallbacks callback;
|
||||
|
||||
/**
|
||||
* LeapMessageParser Constructor
|
||||
*
|
||||
* @param callback Object implementing the LeapMessageParserCallbacks interface
|
||||
*/
|
||||
public LeapMessageParser(LeapMessageParserCallbacks callback) {
|
||||
gson = new GsonBuilder().create();
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse and process a LEAP protocol message
|
||||
*
|
||||
* @param msg String containing the LEAP message
|
||||
*/
|
||||
public void handleMessage(String msg) {
|
||||
if (msg.trim().equals("")) {
|
||||
return; // Ignore empty lines
|
||||
}
|
||||
logger.trace("Received message: {}", msg);
|
||||
|
||||
try {
|
||||
JsonObject message = (JsonObject) new JsonParser().parse(msg);
|
||||
|
||||
if (!message.has("CommuniqueType")) {
|
||||
logger.debug("No CommuniqueType found in message: {}", msg);
|
||||
return;
|
||||
}
|
||||
|
||||
String communiqueType = message.get("CommuniqueType").getAsString();
|
||||
// CommuniqueType type = CommuniqueType.valueOf(communiqueType);
|
||||
logger.debug("Received CommuniqueType: {}", communiqueType);
|
||||
callback.validMessageReceived(communiqueType);
|
||||
|
||||
switch (communiqueType) {
|
||||
case "CreateResponse":
|
||||
return;
|
||||
case "ReadResponse":
|
||||
handleReadResponseMessage(message);
|
||||
break;
|
||||
case "UpdateResponse":
|
||||
break;
|
||||
case "SubscribeResponse":
|
||||
// Subscribe responses can contain bodies with data
|
||||
handleReadResponseMessage(message);
|
||||
return;
|
||||
case "UnsubscribeResponse":
|
||||
return;
|
||||
case "ExceptionResponse":
|
||||
handleExceptionResponse(message);
|
||||
return;
|
||||
default:
|
||||
logger.debug("Unknown CommuniqueType received: {}", communiqueType);
|
||||
break;
|
||||
}
|
||||
} catch (JsonParseException e) {
|
||||
logger.debug("Error parsing message: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by handleMessage() to handle all LEAP ExceptionResponse messages.
|
||||
*
|
||||
* @param message LEAP message
|
||||
*/
|
||||
private void handleExceptionResponse(JsonObject message) {
|
||||
String detailMessage = "";
|
||||
|
||||
try {
|
||||
JsonObject header = message.get("Header").getAsJsonObject();
|
||||
Header headerObj = gson.fromJson(header, Header.class);
|
||||
|
||||
if (MessageBodyType.ExceptionDetail.toString().equalsIgnoreCase(headerObj.messageBodyType)
|
||||
&& message.has("Body")) {
|
||||
JsonObject body = message.get("Body").getAsJsonObject();
|
||||
ExceptionDetail exceptionDetail = gson.fromJson(body, ExceptionDetail.class);
|
||||
if (exceptionDetail != null) {
|
||||
detailMessage = exceptionDetail.message;
|
||||
}
|
||||
}
|
||||
logger.debug("Exception response received. Status: {} URL: {} Message: {}", headerObj.statusCode,
|
||||
headerObj.url, detailMessage);
|
||||
|
||||
} catch (JsonParseException | IllegalStateException e) {
|
||||
logger.debug("Exception response received. Error parsing exception message: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Method called by handleMessage() to handle all LEAP ReadResponse and SubscribeResponse messages.
|
||||
*
|
||||
* @param message LEAP message
|
||||
*/
|
||||
private void handleReadResponseMessage(JsonObject message) {
|
||||
try {
|
||||
JsonObject header = message.get("Header").getAsJsonObject();
|
||||
Header headerObj = gson.fromJson(header, Header.class);
|
||||
|
||||
// if 204/NoContent response received for buttongroup request, create empty button map
|
||||
if (Request.BUTTON_GROUP_URL.equals(headerObj.url)
|
||||
&& Header.STATUS_NO_CONTENT.equalsIgnoreCase(headerObj.statusCode)) {
|
||||
callback.handleEmptyButtonGroupDefinition();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!header.has("MessageBodyType")) {
|
||||
logger.trace("No MessageBodyType in header");
|
||||
return;
|
||||
}
|
||||
String messageBodyType = header.get("MessageBodyType").getAsString();
|
||||
logger.trace("MessageBodyType: {}", messageBodyType);
|
||||
|
||||
if (!message.has("Body")) {
|
||||
logger.debug("No Body found in message");
|
||||
return;
|
||||
}
|
||||
JsonObject body = message.get("Body").getAsJsonObject();
|
||||
|
||||
switch (messageBodyType) {
|
||||
case "OnePingResponse":
|
||||
parseOnePingResponse(body);
|
||||
break;
|
||||
case "OneZoneStatus":
|
||||
parseOneZoneStatus(body);
|
||||
break;
|
||||
case "MultipleAreaDefinition":
|
||||
parseMultipleAreaDefinition(body);
|
||||
break;
|
||||
case "MultipleButtonGroupDefinition":
|
||||
parseMultipleButtonGroupDefinition(body);
|
||||
break;
|
||||
case "MultipleDeviceDefinition":
|
||||
parseMultipleDeviceDefinition(body);
|
||||
break;
|
||||
case "MultipleOccupancyGroupDefinition":
|
||||
parseMultipleOccupancyGroupDefinition(body);
|
||||
break;
|
||||
case "MultipleOccupancyGroupStatus":
|
||||
parseMultipleOccupancyGroupStatus(body);
|
||||
break;
|
||||
case "MultipleVirtualButtonDefinition":
|
||||
break;
|
||||
default:
|
||||
logger.debug("Unknown MessageBodyType received: {}", messageBodyType);
|
||||
break;
|
||||
}
|
||||
} catch (JsonParseException | IllegalStateException e) {
|
||||
logger.debug("Error parsing message: {}", e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private @Nullable <T extends AbstractMessageBody> T parseBodySingle(JsonObject messageBody, String memberName,
|
||||
Class<T> type) {
|
||||
try {
|
||||
if (messageBody.has(memberName)) {
|
||||
JsonObject jsonObject = messageBody.get(memberName).getAsJsonObject();
|
||||
T obj = gson.fromJson(jsonObject, type);
|
||||
return obj;
|
||||
} else {
|
||||
logger.debug("Member name {} not found in JSON message", memberName);
|
||||
return null;
|
||||
}
|
||||
} catch (IllegalStateException | JsonSyntaxException e) {
|
||||
logger.debug("Error parsing JSON message: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private <T extends AbstractMessageBody> List<T> parseBodyMultiple(JsonObject messageBody, String memberName,
|
||||
Class<T> type) {
|
||||
List<T> objList = new LinkedList<T>();
|
||||
try {
|
||||
if (messageBody.has(memberName)) {
|
||||
JsonArray jsonArray = messageBody.get(memberName).getAsJsonArray();
|
||||
|
||||
for (JsonElement element : jsonArray) {
|
||||
JsonObject jsonObject = element.getAsJsonObject();
|
||||
T obj = gson.fromJson(jsonObject, type);
|
||||
objList.add(obj);
|
||||
}
|
||||
return objList;
|
||||
} else {
|
||||
logger.debug("Member name {} not found in JSON message", memberName);
|
||||
return objList;
|
||||
}
|
||||
} catch (IllegalStateException | JsonSyntaxException e) {
|
||||
logger.debug("Error parsing JSON message: {}", e.getMessage());
|
||||
return objList;
|
||||
}
|
||||
}
|
||||
|
||||
private void parseOnePingResponse(JsonObject messageBody) {
|
||||
logger.debug("Ping response received");
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a OneZoneStatus message body. Calls handleZoneUpdate() to dispatch zone updates.
|
||||
*/
|
||||
private void parseOneZoneStatus(JsonObject messageBody) {
|
||||
ZoneStatus zoneStatus = parseBodySingle(messageBody, "ZoneStatus", ZoneStatus.class);
|
||||
if (zoneStatus != null) {
|
||||
callback.handleZoneUpdate(zoneStatus);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MultipleAreaDefinition message body.
|
||||
*/
|
||||
private void parseMultipleAreaDefinition(JsonObject messageBody) {
|
||||
logger.trace("Parsing area list");
|
||||
List<Area> areaList = parseBodyMultiple(messageBody, "Areas", Area.class);
|
||||
callback.handleMultipleAreaDefinition(areaList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MultipleOccupancyGroupDefinition message body.
|
||||
*/
|
||||
private void parseMultipleOccupancyGroupDefinition(JsonObject messageBody) {
|
||||
logger.trace("Parsing occupancy group list");
|
||||
List<OccupancyGroup> oGroupList = parseBodyMultiple(messageBody, "OccupancyGroups", OccupancyGroup.class);
|
||||
callback.handleMultipleOccupancyGroupDefinition(oGroupList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MultipleOccupancyGroupStatus message body and updates occupancy status.
|
||||
*/
|
||||
private void parseMultipleOccupancyGroupStatus(JsonObject messageBody) {
|
||||
logger.trace("Parsing occupancy group status list");
|
||||
List<OccupancyGroupStatus> statusList = parseBodyMultiple(messageBody, "OccupancyGroupStatuses",
|
||||
OccupancyGroupStatus.class);
|
||||
for (OccupancyGroupStatus status : statusList) {
|
||||
int groupNumber = status.getOccupancyGroup();
|
||||
if (groupNumber > 0) {
|
||||
logger.debug("OccupancyGroup: {} Status: {}", groupNumber, status.occupancyStatus);
|
||||
callback.handleGroupUpdate(groupNumber, status.occupancyStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a MultipleDeviceDefinition message body and loads the zoneToDevice and deviceToZone maps. Also passes the
|
||||
* device data on to the discovery service and calls setBridgeProperties() with the hub's device entry.
|
||||
*/
|
||||
private void parseMultipleDeviceDefinition(JsonObject messageBody) {
|
||||
List<Device> deviceList = parseBodyMultiple(messageBody, "Devices", Device.class);
|
||||
callback.handleMultipleDeviceDefintion(deviceList);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a MultipleButtonGroupDefinition message body and load the results into deviceButtonMap.
|
||||
*/
|
||||
private void parseMultipleButtonGroupDefinition(JsonObject messageBody) {
|
||||
List<ButtonGroup> buttonGroupList = parseBodyMultiple(messageBody, "ButtonGroups", ButtonGroup.class);
|
||||
callback.handleMultipleButtonGroupDefinition(buttonGroupList);
|
||||
}
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Area;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ButtonGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.Device;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.OccupancyGroup;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.dto.ZoneStatus;
|
||||
|
||||
/**
|
||||
* Interface defining callback routines used by LeapMessageParser
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public interface LeapMessageParserCallbacks {
|
||||
|
||||
public void validMessageReceived(String communiqueType);
|
||||
|
||||
public void handleEmptyButtonGroupDefinition();
|
||||
|
||||
public void handleZoneUpdate(ZoneStatus zoneStatus);
|
||||
|
||||
public void handleGroupUpdate(int groupNumber, String occupancyStatus);
|
||||
|
||||
public void handleMultipleButtonGroupDefinition(List<ButtonGroup> buttonGroupList);
|
||||
|
||||
public void handleMultipleDeviceDefintion(List<Device> deviceList);
|
||||
|
||||
public void handleMultipleAreaDefinition(List<Area> areaList);
|
||||
|
||||
public void handleMultipleOccupancyGroupDefinition(List<OccupancyGroup> oGroupList);
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* LEAP MessageBodyType enum
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum MessageBodyType {
|
||||
ExceptionDetail("ExceptionDetail"),
|
||||
MultipleAffectedZoneDefinition("MultipleAffectedZoneDefinition"),
|
||||
MultipleAreaDefinition("MultipleAreaDefinition"),
|
||||
MultipleButtonDefinition("MultipleButtonDefinition"),
|
||||
MultipleButtonGroupDefinition("MultipleButtonGroupDefinition"),
|
||||
MultipleDeviceDefinition("MultipleDeviceDefinition"),
|
||||
MultipleDeviceStatus("MultipleDeviceStatus"),
|
||||
MultipleOccupancyGroupDefinition("MultipleOccupancyGroupDefinition"),
|
||||
MultipleOccupancyGroupStatus("MultipleOccupancyGroupStatus"),
|
||||
MultiplePresetAssignmentDefinition("MultiplePresetAssignmentDefinition"),
|
||||
MultipleProgrammingModelDefinition("MultipleProgrammingModelDefinition"),
|
||||
MultipleServerDefinition("MultipleServerDefinition"),
|
||||
MultipleServiceDefinition("MultipleServiceDefinition"),
|
||||
MultipleTimeclockDefinition("MultipleTimeclockDefinition"),
|
||||
MultipleVirtualButtonDefinition("MultipleVirtualButtonDefinition"),
|
||||
MultipleZoneDefinition("MultipleZoneDefinition"),
|
||||
MultipleZoneStatus("MultipleZoneStatus"),
|
||||
OneAffectedZoneDefinition("OneAffectedZoneDefinition"),
|
||||
OneAlexaDataSummaryDefinition("OneAlexaDataSummaryDefinition"),
|
||||
OneAreaDefinition("OneAreaDefinition"),
|
||||
OneAreaLoadSheddingDefinition("OneAreaLoadSheddingDefinition"),
|
||||
OneButtonDefinition("OneButtonDefinition"),
|
||||
OneButtonGroupDefinition("OneButtonGroupDefinition"),
|
||||
OneDeviceDefinition("OneDeviceDefinition"),
|
||||
OneDeviceRulesDefinition("OneDeviceRulesDefinition"),
|
||||
OneDeviceStatus("OneDeviceStatus"),
|
||||
OneGoogleHomeDataSummaryDefinition("OneGoogleHomeDataSummaryDefinition"),
|
||||
OneLinkNodeDefinition("OneLinkNodeDefinition"),
|
||||
OneLIPIdListDefinition("OneLIPIdListDefinition"),
|
||||
OneNetworkInterfaceDefinition("OneNetworkInterfaceDefinition"),
|
||||
OneOccupancyGroupDefinition("OneOccupancyGroupDefinition"),
|
||||
OnePairingListDefinition("OnePairingListDefinition"),
|
||||
OnePingResponse("OnePingResponse"),
|
||||
OnePresetAssignmentDefinition("OnePresetAssignmentDefinition"),
|
||||
OnePresetDefinition("OnePresetDefinition"),
|
||||
OneProgrammingModelDefinition("OneProgrammingModelDefinition"),
|
||||
OneProjectDefinition("OneProjectDefinition"),
|
||||
OneServerDefinition("OneServerDefinition"),
|
||||
OneServiceDefinition("OneServiceDefinition"),
|
||||
OneSystemDefinition("OneSystemDefinition"),
|
||||
OneTimeclockEventRulesDefinition("OneTimeclockEventRulesDefinition"),
|
||||
OneVirtualButtonDefinition("OneVirtualButtonDefinition"),
|
||||
OneZoneDefinition("OneZoneDefinition"),
|
||||
OneZoneStatus("OneZoneStatus");
|
||||
|
||||
private final transient String string;
|
||||
|
||||
MessageBodyType(String string) {
|
||||
this.string = string;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return string;
|
||||
}
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
|
||||
|
||||
/**
|
||||
* Contains static methods for constructing LEAP messages
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class Request {
|
||||
public static final String BUTTON_GROUP_URL = "/buttongroup";
|
||||
|
||||
public static String goToLevel(int zone, int value) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"}," + "\"Body\": {" + "\"Command\": {"
|
||||
+ "\"CommandType\": \"GoToLevel\"," + "\"Parameter\": [{\"Type\": \"Level\", \"Value\": %d}]}}}";
|
||||
return String.format(request, zone, value);
|
||||
}
|
||||
|
||||
/** fadeTime must be in the format hh:mm:ss **/
|
||||
public static String goToDimmedLevel(int zone, int value, String fadeTime) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"},\"Body\": {\"Command\": {"
|
||||
+ "\"CommandType\": \"GoToDimmedLevel\","
|
||||
+ "\"DimmedLevelParameters\": {\"Level\": %d, \"FadeTime\": \"%s\"}}}}";
|
||||
return String.format(request, zone, value, fadeTime);
|
||||
}
|
||||
|
||||
/** fadeTime and delayTime must be in the format hh:mm:ss **/
|
||||
public static String goToDimmedLevel(int zone, int value, String fadeTime, String delayTime) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"},\"Body\": {\"Command\": {"
|
||||
+ "\"CommandType\": \"GoToDimmedLevel\","
|
||||
+ "\"DimmedLevelParameters\": {\"Level\": %d, \"FadeTime\": \"%s\", \"DelayTime\": \"%s\"}}}}";
|
||||
return String.format(request, zone, value, fadeTime, delayTime);
|
||||
}
|
||||
|
||||
public static String goToFanSpeed(int zone, FanSpeedType fanSpeed) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"}," + "\"Body\": {"
|
||||
+ "\"Command\": {\"CommandType\": \"GoToFanSpeed\","
|
||||
+ "\"FanSpeedParameters\": {\"FanSpeed\": \"%s\"}}}}";
|
||||
return String.format(request, zone, fanSpeed.leapValue());
|
||||
}
|
||||
|
||||
public static String buttonCommand(int button, CommandType command) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/button/%d/commandprocessor\"},"
|
||||
+ "\"Body\": {\"Command\": {\"CommandType\": \"%s\"}}}";
|
||||
return String.format(request, button, command.toString());
|
||||
}
|
||||
|
||||
public static String virtualButtonCommand(int virtualbutton, CommandType command) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/virtualbutton/%d/commandprocessor\"},"
|
||||
+ "\"Body\": {\"Command\": {\"CommandType\": \"%s\"}}}";
|
||||
return String.format(request, virtualbutton, command.toString());
|
||||
}
|
||||
|
||||
public static String zoneCommand(int zone, CommandType commandType) {
|
||||
String request = "{\"CommuniqueType\": \"CreateRequest\","
|
||||
+ "\"Header\": {\"Url\": \"/zone/%d/commandprocessor\"}," + "\"Body\": {" + "\"Command\": {"
|
||||
+ "\"CommandType\": \"%s\"}}}";
|
||||
return String.format(request, zone, commandType.toString());
|
||||
}
|
||||
|
||||
public static String request(CommuniqueType cType, String url) {
|
||||
String request = "{\"CommuniqueType\": \"%s\",\"Header\": {\"Url\": \"%s\"}}";
|
||||
return String.format(request, cType.toString(), url);
|
||||
}
|
||||
|
||||
public static String ping() {
|
||||
return request(CommuniqueType.READREQUEST, "/server/1/status/ping");
|
||||
}
|
||||
|
||||
public static String getDevices() {
|
||||
return request(CommuniqueType.READREQUEST, "/device");
|
||||
}
|
||||
|
||||
public static String getVirtualButtons() {
|
||||
return request(CommuniqueType.READREQUEST, "/virtualbutton");
|
||||
}
|
||||
|
||||
public static String getButtonGroups() {
|
||||
return request(CommuniqueType.READREQUEST, BUTTON_GROUP_URL);
|
||||
}
|
||||
|
||||
public static String getAreas() {
|
||||
return request(CommuniqueType.READREQUEST, "/area");
|
||||
}
|
||||
|
||||
public static String getOccupancyGroups() {
|
||||
return request(CommuniqueType.READREQUEST, "/occupancygroup");
|
||||
}
|
||||
|
||||
public static String getZoneStatus(int zone) {
|
||||
return request(CommuniqueType.READREQUEST, String.format("/zone/%d/status", zone));
|
||||
}
|
||||
|
||||
public static String getOccupancyGroupStatus() {
|
||||
return request(CommuniqueType.READREQUEST, "/occupancygroup/status");
|
||||
}
|
||||
|
||||
public static String subscribeOccupancyGroupStatus() {
|
||||
return request(CommuniqueType.SUBSCRIBEREQUEST, "/occupancygroup/status");
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP AffectedZone Object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class AffectedZone {
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
@SerializedName("Zone")
|
||||
public Href zone = new Href();
|
||||
|
||||
public AffectedZone() {
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP Area object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Area extends AbstractMessageBody {
|
||||
public static final Pattern AREA_HREF_PATTERN = Pattern.compile("/area/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
|
||||
@SerializedName("Name")
|
||||
public String name;
|
||||
|
||||
@SerializedName("Parent")
|
||||
public Href parent;
|
||||
|
||||
// @SerializedName("Category")
|
||||
// public Category category;
|
||||
|
||||
@SerializedName("AssociatedDevices")
|
||||
public Href[] associatedDevices;
|
||||
|
||||
@SerializedName("AssociatedOccupancyGroups")
|
||||
public Href[] associatedOccupancyGroups;
|
||||
|
||||
@SerializedName("LoadShedding")
|
||||
public Href loadShedding;
|
||||
|
||||
@SerializedName("OccupancySettings")
|
||||
public Href occupancySettings;
|
||||
|
||||
@SerializedName("OccupancySensorSettings")
|
||||
public Href occupancySensorSettings;
|
||||
|
||||
@SerializedName("DaylightingGainSettings")
|
||||
public Href daylightingGainSettings;
|
||||
|
||||
public Area() {
|
||||
}
|
||||
|
||||
public int getArea() {
|
||||
return hrefNumber(AREA_HREF_PATTERN, href);
|
||||
}
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP ButtonGroup Object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class ButtonGroup extends AbstractMessageBody {
|
||||
public static final Pattern BUTTONGROUP_HREF_PATTERN = Pattern.compile("/buttongroup/([0-9]+)");
|
||||
public static final Pattern BUTTON_HREF_PATTERN = Pattern.compile("/button/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
@SerializedName("Parent") // device href
|
||||
public Href parent = new Href();
|
||||
@SerializedName("Buttons")
|
||||
public Href[] buttons;
|
||||
@SerializedName("AffectedZones")
|
||||
public AffectedZone[] affectedZones;
|
||||
@SerializedName("SortOrder")
|
||||
public Integer sortOrder;
|
||||
@SerializedName("StopIfMoving")
|
||||
public String stopIfMoving; // Enabled or Disabled
|
||||
@SerializedName("ProgrammingType")
|
||||
public String programmingType; // Column
|
||||
|
||||
public ButtonGroup() {
|
||||
}
|
||||
|
||||
public int getButtonGroup() {
|
||||
return hrefNumber(BUTTONGROUP_HREF_PATTERN, href);
|
||||
}
|
||||
|
||||
public int getParentDevice() {
|
||||
if (parent != null && parent.href != null) {
|
||||
return hrefNumber(Device.DEVICE_HREF_PATTERN, parent.href);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public List<Integer> getButtonList() {
|
||||
LinkedList<Integer> buttonNumList = new LinkedList<>();
|
||||
for (Href button : buttons) {
|
||||
int buttonNum = hrefNumber(BUTTON_HREF_PATTERN, button.href);
|
||||
buttonNumList.add(buttonNum);
|
||||
}
|
||||
return buttonNumList;
|
||||
}
|
||||
}
|
@ -0,0 +1,132 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP Device Object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Device extends AbstractMessageBody {
|
||||
public static final Pattern DEVICE_HREF_PATTERN = Pattern.compile("/device/([0-9]+)");
|
||||
private static final Pattern ZONE_HREF_PATTERN = Pattern.compile("/zone/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
|
||||
@SerializedName("Name")
|
||||
public String name;
|
||||
|
||||
@SerializedName("FullyQualifiedName")
|
||||
public String[] fullyQualifiedName;
|
||||
|
||||
@SerializedName("Parent")
|
||||
public Href parent = new Href();
|
||||
|
||||
@SerializedName("SerialNumber")
|
||||
public String serialNumber;
|
||||
|
||||
@SerializedName("ModelNumber")
|
||||
public String modelNumber;
|
||||
|
||||
@SerializedName("DeviceType")
|
||||
public String deviceType;
|
||||
|
||||
@SerializedName("LocalZones")
|
||||
public Href[] localZones;
|
||||
|
||||
@SerializedName("AssociatedArea")
|
||||
public Href associatedArea = new Href();
|
||||
|
||||
@SerializedName("OccupancySensors")
|
||||
public Href[] occupancySensors;
|
||||
|
||||
@SerializedName("LinkNodes")
|
||||
public Href[] linkNodes;
|
||||
|
||||
@SerializedName("DeviceRules")
|
||||
public Href[] deviceRules;
|
||||
|
||||
@SerializedName("RepeaterProperties")
|
||||
public RepeaterProperties repeaterProperties;
|
||||
|
||||
@SerializedName("FirmwareImage")
|
||||
public FirmwareImage firmwareImage;
|
||||
|
||||
public class FirmwareImage {
|
||||
@SerializedName("Firmware")
|
||||
public Firmware firmware;
|
||||
@SerializedName("Installed")
|
||||
public Installed installed;
|
||||
}
|
||||
|
||||
public class Firmware {
|
||||
@SerializedName("DisplayName")
|
||||
public String displayName;
|
||||
}
|
||||
|
||||
public class Installed {
|
||||
@SerializedName("Year")
|
||||
public int year;
|
||||
@SerializedName("Month")
|
||||
public int month;
|
||||
@SerializedName("Day")
|
||||
public int day;
|
||||
@SerializedName("Hour")
|
||||
public int hour;
|
||||
@SerializedName("Minute")
|
||||
public int minute;
|
||||
@SerializedName("Second")
|
||||
public int second;
|
||||
@SerializedName("Utc")
|
||||
public String utc;
|
||||
}
|
||||
|
||||
public class RepeaterProperties {
|
||||
@SerializedName("IsRepeater")
|
||||
public boolean isRepeater;
|
||||
}
|
||||
|
||||
public Device() {
|
||||
}
|
||||
|
||||
public int getDevice() {
|
||||
return hrefNumber(DEVICE_HREF_PATTERN, href);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the zone number of the first zone listed in LocalZones.
|
||||
* Currently devices should only have one zone listed.
|
||||
*/
|
||||
public int getZone() {
|
||||
if (localZones != null && localZones.length > 0) {
|
||||
return hrefNumber(ZONE_HREF_PATTERN, localZones[0].href);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public String getFullyQualifiedName() {
|
||||
if (fullyQualifiedName != null && fullyQualifiedName.length > 0) {
|
||||
return String.join(" ", fullyQualifiedName);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP ExceptionDetail object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class ExceptionDetail extends AbstractMessageBody {
|
||||
@SerializedName("Message")
|
||||
public String message = "";
|
||||
|
||||
public ExceptionDetail() {
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP message header
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Header {
|
||||
public static final String STATUS_NO_CONTENT = "204 NoContent";
|
||||
public static final String STATUS_OK = "200 OK";
|
||||
|
||||
@SerializedName("MessageBodyType")
|
||||
public String messageBodyType;
|
||||
@SerializedName("StatusCode")
|
||||
public String statusCode;
|
||||
@SerializedName("Url")
|
||||
public String url;
|
||||
|
||||
public Header() {
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class Href {
|
||||
@SerializedName("href")
|
||||
public String href = "";
|
||||
|
||||
public Href() {
|
||||
}
|
||||
|
||||
public Href(String href) {
|
||||
this.href = href;
|
||||
}
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP OccupancyGroup object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class OccupancyGroup extends AbstractMessageBody {
|
||||
public static final Pattern OGROUP_HREF_PATTERN = Pattern.compile("/occupancygroup/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
|
||||
@SerializedName("AssociatedSensors")
|
||||
public OccupancySensor[] associatedSensors;
|
||||
|
||||
@SerializedName("AssociatedAreas")
|
||||
public AreaHref[] associatedAreas;
|
||||
|
||||
@SerializedName("ProgrammingType")
|
||||
public String programmingType;
|
||||
|
||||
@SerializedName("ProgrammingModel")
|
||||
public Href programmingModel;
|
||||
|
||||
public class AreaHref {
|
||||
@SerializedName("Area")
|
||||
public Href area;
|
||||
|
||||
public int getAreaNumber() {
|
||||
return hrefNumber(Area.AREA_HREF_PATTERN, area.href);
|
||||
}
|
||||
}
|
||||
|
||||
public OccupancyGroup() {
|
||||
}
|
||||
|
||||
public int getOccupancyGroup() {
|
||||
return hrefNumber(OGROUP_HREF_PATTERN, href);
|
||||
}
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP OccupancyGroupStatus object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class OccupancyGroupStatus extends AbstractMessageBody {
|
||||
public static final Pattern OGROUP_HREF_PATTERN = Pattern.compile("/occupancygroup/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href;
|
||||
|
||||
@SerializedName("OccupancyGroup")
|
||||
public Href occupancyGroup;
|
||||
|
||||
@SerializedName("OccupancyStatus")
|
||||
public String occupancyStatus; // Occupied, Unoccupied, or Unknown
|
||||
|
||||
public OccupancyGroupStatus() {
|
||||
}
|
||||
|
||||
public int getOccupancyGroup() {
|
||||
if (occupancyGroup != null && occupancyGroup.href != null) {
|
||||
return hrefNumber(OGROUP_HREF_PATTERN, occupancyGroup.href);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class OccupancySensor {
|
||||
@SerializedName("OccupancySensor")
|
||||
public Href occupancySensor;
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.leap.dto;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.openhab.binding.lutron.internal.protocol.FanSpeedType;
|
||||
import org.openhab.binding.lutron.internal.protocol.leap.AbstractMessageBody;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* LEAP ZoneStatus Object
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
public class ZoneStatus extends AbstractMessageBody {
|
||||
public static final Pattern ZONE_HREF_PATTERN = Pattern.compile("/zone/([0-9]+)");
|
||||
|
||||
@SerializedName("href")
|
||||
public String href = "";
|
||||
@SerializedName("Level")
|
||||
public int level; // 0-100
|
||||
@SerializedName("SwitchedLevel")
|
||||
public String switchedLevel = ""; // "On" or "Off"
|
||||
@SerializedName("FanSpeed")
|
||||
public FanSpeedType fanSpeed;
|
||||
@SerializedName("Zone")
|
||||
public Href zone = new Href();;
|
||||
@SerializedName("StatusAccuracy")
|
||||
public String statusAccuracy = ""; // "Good" or ??
|
||||
|
||||
public ZoneStatus() {
|
||||
}
|
||||
|
||||
public int getZone() {
|
||||
if (zone != null) {
|
||||
return hrefNumber(ZONE_HREF_PATTERN, zone.href);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean statusAccuracyGood() {
|
||||
return "Good".equals(statusAccuracy);
|
||||
}
|
||||
|
||||
public boolean switchedLevelOn() {
|
||||
return "On".equals(switchedLevel);
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.protocol;
|
||||
package org.openhab.binding.lutron.internal.protocol.lip;
|
||||
|
||||
/**
|
||||
* Type of command in the Lutron integration protocol.
|
@ -10,17 +10,18 @@
|
||||
*
|
||||
* SPDX-License-Identifier: EPL-2.0
|
||||
*/
|
||||
package org.openhab.binding.lutron.internal.protocol;
|
||||
package org.openhab.binding.lutron.internal.protocol.lip;
|
||||
|
||||
/**
|
||||
* Requested operation of a command to the Lutron integration protocol.
|
||||
* Requested operation of a command in the Lutron integration protocol.
|
||||
*
|
||||
* @author Allan Tong - Initial contribution
|
||||
*
|
||||
*/
|
||||
public enum LutronOperation {
|
||||
EXECUTE("#"),
|
||||
QUERY("?");
|
||||
QUERY("?"),
|
||||
RESPONSE("~");
|
||||
|
||||
private final String operationChar;
|
||||
|
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2020 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.lutron.internal.protocol.lip;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Target device type enum. Used to annotate LutronCommand objects so the LEAP bridge can translate them.
|
||||
*
|
||||
* @author Bob Adair - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public enum TargetType {
|
||||
BLIND,
|
||||
BRIDGE,
|
||||
CCO,
|
||||
DIMMER,
|
||||
FAN,
|
||||
GREENMODE,
|
||||
GROUP,
|
||||
KEYPAD,
|
||||
SHADE,
|
||||
SWITCH,
|
||||
SYSVAR,
|
||||
TIMECLOCK,
|
||||
VIRTUALKEYPAD;
|
||||
}
|
@ -74,6 +74,8 @@ public class DbXmlInfoReader {
|
||||
xstream.alias("Area", Area.class);
|
||||
xstream.aliasField("Name", Area.class, "name");
|
||||
xstream.useAttributeFor(Area.class, "name");
|
||||
xstream.aliasField("IntegrationID", Area.class, "integrationId");
|
||||
xstream.useAttributeFor(Area.class, "integrationId");
|
||||
xstream.aliasField("DeviceGroups", Area.class, "deviceNodes");
|
||||
xstream.aliasField("Outputs", Area.class, "outputs");
|
||||
xstream.aliasField("Areas", Area.class, "areas");
|
||||
|
@ -6,7 +6,7 @@
|
||||
|
||||
<bridge-type id="ipbridge">
|
||||
<label>Lutron IP Access Point</label>
|
||||
<description>Ethernet access point to Lutron lighting control system</description>
|
||||
<description>A Lutron controller using Lutron Integration Protocol (LIP) over TCP/IP</description>
|
||||
<properties>
|
||||
<property name="vendor">Lutron</property>
|
||||
</properties>
|
||||
@ -57,9 +57,79 @@
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<bridge-type id="leapbridge">
|
||||
<label>Lutron LEAP Access Point</label>
|
||||
<description>A Lutron controller using LEAP protocol over TCP/IP</description>
|
||||
|
||||
<channels>
|
||||
<channel id="command" typeId="command-type"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="vendor">Lutron</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>serialNumber</representation-property>
|
||||
|
||||
<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 Lutron integration access point</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer">
|
||||
<label>TCP Port</label>
|
||||
<description>The bridge TCP port</description>
|
||||
<default>8081</default>
|
||||
</parameter>
|
||||
<parameter name="keystore" type="text" required="true">
|
||||
<label>Keystore File</label>
|
||||
<description>Java keystore containing caseta key and certs</description>
|
||||
</parameter>
|
||||
<parameter name="keystorePassword" type="text">
|
||||
<context>password</context>
|
||||
<label>Keystore Password</label>
|
||||
<description>Password for the keystore file</description>
|
||||
</parameter>
|
||||
<parameter name="certValidate" type="boolean" required="false">
|
||||
<label>Validate SSL Certificates</label>
|
||||
<description>Validate server SSL certificate</description>
|
||||
<default>true</default>
|
||||
</parameter>
|
||||
<parameter name="reconnect" type="integer" min="1" max="60" unit="min">
|
||||
<label>Reconnect Interval</label>
|
||||
<description>The period in minutes that the handler will wait between connection attempts</description>
|
||||
<unitLabel>minutes</unitLabel>
|
||||
<default>5</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="heartbeat" type="integer" min="1" max="60" unit="min">
|
||||
<label>Keepalive Heartbeat Interval</label>
|
||||
<description>The period in minutes between connection heartbeat checks</description>
|
||||
<unitLabel>minutes</unitLabel>
|
||||
<default>5</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
<parameter name="delay" type="integer" min="0" max="250" unit="ms">
|
||||
<label>Send Delay</label>
|
||||
<description>The delay in milliseconds between sending integration commands (for throttling)</description>
|
||||
<unitLabel>ms</unitLabel>
|
||||
<default>0</default>
|
||||
<advanced>true</advanced>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</bridge-type>
|
||||
|
||||
<channel-type id="command-type" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>LEAP Command</label>
|
||||
<description>LEAP command to send (for debugging)</description>
|
||||
</channel-type>
|
||||
|
||||
<thing-type id="dimmer">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Maestro Dimmer</label>
|
||||
@ -102,9 +172,34 @@
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="fan">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Fan Speed Controller</label>
|
||||
<description>Controls ceiling fans</description>
|
||||
|
||||
<channels>
|
||||
<channel id="fanspeed" typeId="fanControl"/>
|
||||
<channel id="fanlevel" typeId="lightDimmer"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>integrationId</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="integrationId" type="integer" required="true">
|
||||
<label>Integration ID</label>
|
||||
<description>Address of fan controller in the Lutron system</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="shade">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Sivoia QS Shade</label>
|
||||
@ -160,6 +255,7 @@
|
||||
<thing-type id="switch">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Maestro Switch</label>
|
||||
@ -307,6 +403,29 @@
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="ogroup">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Occupancy Group</label>
|
||||
<description>Shows state of occupancy sensor group</description>
|
||||
|
||||
<channels>
|
||||
<channel id="groupstate" typeId="groupState"/>
|
||||
</channels>
|
||||
|
||||
<representation-property>integrationId</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="integrationId" type="integer" required="true">
|
||||
<label>Integration ID</label>
|
||||
<description>Occupancy group number</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="keypad">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
@ -484,6 +603,7 @@
|
||||
<thing-type id="pico">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Pico Keypad</label>
|
||||
@ -601,6 +721,7 @@
|
||||
<thing-type id="virtualkeypad">
|
||||
<supported-bridge-type-refs>
|
||||
<bridge-type-ref id="ipbridge"/>
|
||||
<bridge-type-ref id="leapbridge"/>
|
||||
</supported-bridge-type-refs>
|
||||
|
||||
<label>Virtual Keypad</label>
|
||||
@ -617,8 +738,8 @@
|
||||
<label>Virtual Keypad Type</label>
|
||||
<description>System type for virtual keypad</description>
|
||||
<options>
|
||||
<option value="Caseta">Caseta Scene Buttons</option>
|
||||
<option value="Other">HomeWorks/RA 2/Other</option>
|
||||
<option value="Caseta">Caseta/RA2 Select Scene Buttons</option>
|
||||
<option value="Other">HomeWorks/RadioRA 2/Other</option>
|
||||
</options>
|
||||
<default>Other</default>
|
||||
</parameter>
|
||||
@ -1172,6 +1293,33 @@
|
||||
<state readOnly="true"/>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="fanControl">
|
||||
<item-type>String</item-type>
|
||||
<label>Fan Speed</label>
|
||||
<state readOnly="false">
|
||||
<options>
|
||||
<option value="OFF">Off</option>
|
||||
<option value="LOW">Low</option>
|
||||
<option value="MEDIUM">Medium</option>
|
||||
<option value="MEDIUMHIGH">MediumHigh</option>
|
||||
<option value="HIGH">High</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="groupState">
|
||||
<item-type>String</item-type>
|
||||
<label>Occupancy State</label>
|
||||
<category>Motion</category>
|
||||
<state readOnly="true">
|
||||
<options>
|
||||
<option value="OCCUPIED">Occupied</option>
|
||||
<option value="UNOCCUPIED">Unoccupied</option>
|
||||
<option value="UNKNOWN">Unknown</option>
|
||||
</options>
|
||||
</state>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="button">
|
||||
<item-type>Switch</item-type>
|
||||
<label>Keypad Button</label>
|
||||
|
Loading…
Reference in New Issue
Block a user