added migrated 2.x add-ons

Signed-off-by: Kai Kreuzer <kai@openhab.org>
This commit is contained in:
Kai Kreuzer
2020-09-21 01:58:32 +02:00
parent bbf1a7fd29
commit 6df6783b60
11662 changed files with 1302875 additions and 11 deletions

View File

@@ -0,0 +1,32 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-11">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.openhab.binding.atlona</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

View File

@@ -0,0 +1,13 @@
This content is produced and maintained by the openHAB project.
* Project home: https://www.openhab.org
== Declared Project Licenses
This program and the accompanying materials are made available under the terms
of the Eclipse Public License 2.0 which is available at
https://www.eclipse.org/legal/epl-2.0/.
== Source Code
https://github.com/openhab/openhab-addons

View File

@@ -0,0 +1,349 @@
# Atlona Binding
This binding integrates Atlona AT-UHD-PRO3 HdBaseT matrix switches [Atlona AT-UHD-PRO3 HdBaseT matrix switches](https://www.atlona.com) into your openHAB installation.
## Supported Things
This binding supports the following thing types:
| Thing | Thing Type | Description |
|---------------|------------|---------------------------------------------------------|
| pro3-44m | Thing | The AT-UHD-PRO3-44M 4x4 HDBaseT matrix. |
| pro3-66m | Thing | The AT-UHD-PRO3-66M 6x6 HDBaseT matrix. |
| pro3-88m | Thing | The AT-UHD-PRO3-88M 8x8 HDBaseT matrix. |
| pro3-1616m | Thing | The AT-UHD-PRO3-1616M 16x16 HDBaseT matrix. |
## Discovery
The Atlona AT-UHD-PRO3 switch can be discovered by starting a scan in the Paper UI and then logging into your switch and pressing the "SDDP" button on the "Network" tab.
The "SDDP" (simple device discovery protocol) button will initiate the discovery process.
If "Telnet Login" is enabled ("Network" tab from the switch configuration UI), you will need to set the username and password in the configuration of the newly discovered thing before a connection can be made.
## Binding configuration
```java
atlona:pro3-88m:home [ ipAddress="192.168.1.30", userName="me", password="12345", polling=600, ping=30, retryPolling=10 ]
```
- `ipAddress`: Hostname or IP address of the matrix switch
- `userName`: (optional) the username to login with (only if Telnet Login is enabled)
- `password`: (optional) the password to login with (only if Telnet Login is enabled)
- `polling`: (optional) the time (in seconds) to poll the state from the actual switch (default: 600)
- `ping`: (optional) the time (in seconds) to ping the switch to keep our connection alive (default: 30)
- `retryPolling`: (optional) the time (in seconds) to retry a connection if the connection has failed (default: 10)
### username/password
The userName/password configuration options are optional and are only required if you have your switch set with "Telnet Login" enabled (on the "Network" tab from the switch configuration UI).
The user must be a valid user listed on the "Users" tab of the switch configuration UI in this case.
### polling
Polling will automatically occur when (re)connecting to the switch to get the initial state of the switch.
If you have anything outside of openHAB that can modify the switch state (front panel, IR, telnet session or another automation system), you will likely want to set this setting to a much lower value.
### ping
The Atlona switch will time out any IP connection after a specific time (specified by "IP Timeout" on the "Network" tab from the switch configuration UI - 120 by default).
The ping setting MUST be lower than that value.
If it is higher than the "IP Timeout" value, the switch will timeout our connection and the thing will go OFFLINE (until a reconnect attempt is made).
## Channels
| Thing | Channel Type ID | Item Type | Access | Description |
|------------|-----------------------------------------------------------------|-----------|--------|-------------------------------------------------------------------------------------------|
| pro3-44m | primary#power | Switch | RW | Matrix Power Switch |
| pro3-44m | primary#panellock | Switch | RW | Sets the front panel locked or unlocked |
| pro3-44m | primary#irenable | Switch | RW | Enables/Disabled the front panel IR |
| pro3-44m | primary#presetcmd | Switch | W | Sends a preset command ('saveX', 'recallX', 'clearX') - see notes below |
| pro3-44m | primary#matrixcmd | Switch | W | Sends a matrix command ('resetmatrix', 'resetports', 'allportsX') - see notes below |
| pro3-44m | port1#portpower | Switch | RW | Enables/Disables output port #1 |
| pro3-44m | port1#portoutput | Number | RW | Sets output port #1 to the specified input port |
| pro3-44m | port2#portpower | Switch | RW | Enables/Disables output port #2 |
| pro3-44m | port2#portoutput | Number | RW | Sets output port #2 to the specified input port |
| pro3-44m | port3#portpower | Switch | RW | Enables/Disables output port #3 |
| pro3-44m | port3#portoutput | Number | RW | Sets output port #3 to the specified input port |
| pro3-44m | port4#portpower | Switch | RW | Enables/Disables output port #4 |
| pro3-44m | port4#portoutput | Number | RW | Sets output port #4 to the specified input port |
| pro3-44m | port5#portpower | Switch | RW | Enables/Disables output port #5 |
| pro3-44m | port5#portoutput | Number | RW | Sets output port #5 to the specified input port |
| pro3-44m | mirror5#portmirrorenabled | Number | RW | Sets hdmi port #5 to enable/disable port mirroring |
| pro3-44m | mirror5#portmirror | Number | RW | Sets hdmi port #5 to mirror the specified output port (if enabled) |
| pro3-44m | volume1#volume | Number | RW | Sets the volume of audio port #1 to the specified decibel level (between -79db to +15db) |
| pro3-44m | volume1#volumemute | Switch | RW | Mutes/Unmutes audio port #1 |
| pro3-44m | volume2#volume | Number | RW | Sets the volume of audio port #2 to the specified decibel level (between -79db to +15db) |
| pro3-44m | volume2#volumemute | Switch | RW | Mutes/Unmutes audio port #2 |
| pro3-44m | volume3#volume | Number | RW | Sets the volume of audio port #3 to the specified decibel level (between -79db to +15db) |
| pro3-44m | volume3#volumemute | Switch | RW | Mutes/Unmutes audio port #3 |
| | | | | |
| pro3-66m | ALL OF THE pro3-44M channels (except different mirror settings) | | | |
| pro3-66m | port6#portpower | Switch | RW | Enables/Disables output port #6 |
| pro3-66m | port6#portoutput | Number | RW | Sets output port #6 to the specified input port |
| pro3-66m | port7#portpower | Switch | RW | Enables/Disables output port #7 |
| pro3-66m | port7#portoutput | Number | RW | Sets output port #7 to the specified input port |
| pro3-66m | port8#portpower | Switch | RW | Enables/Disables output port #8 |
| pro3-66m | port8#portoutput | Number | RW | Sets output port #8 to the specified input port |
| pro3-66m | mirror6#portmirrorenabled | Number | RW | Sets hdmi port #6 to enable/disable port mirroring |
| pro3-66m | mirror6#portmirror | Number | RW | Sets hdmi port #6 to mirror the specified output port (if enabled) |
| pro3-66m | mirror8#portmirrorenabled | Number | RW | Sets hdmi port #8 to enable/disable port mirroring |
| pro3-66m | mirror8#portmirror | Number | RW | Sets hdmi port #8 to mirror the specified output port (if enabled) |
| pro3-66m | volume4#volume | Number | RW | Sets the volume of audio port #4 to the specified decibel level (between -79db to +15db) |
| pro3-66m | volume4#volumemute | Switch | RW | Mutes/Unmutes audio port #4 |
| | | | | |
| pro3-88m | ALL OF THE pro3-66M channels (except different mirror settings) | | | |
| pro3-88m | port9#portpower | Switch | RW | Enables/Disables output port #9 |
| pro3-88m | port9#portoutput | Number | RW | Sets output port #9 to the specified input port |
| pro3-88m | port10#portpower | Switch | RW | Enables/Disables output port #10 |
| pro3-88m | port10#portoutput | Number | RW | Sets output port #10 to the specified input port |
| pro3-88m | mirror8#portmirrorenabled | Number | RW | Sets hdmi port #8 to enable/disable port mirroring |
| pro3-88m | mirror8#portmirror | Number | RW | Sets hdmi port #8 to mirror the specified output port (if enabled) |
| pro3-88m | mirror10#portmirrorenabled | Number | RW | Sets hdmi port #10 to enable/disable port mirroring |
| pro3-88m | mirror10#portmirror | Number | RW | Sets hdmi port #10 to mirror the specified output port (if enabled) |
| pro3-88m | volume5#volume | Number | RW | Sets the volume of audio port #5 to the specified decibel level (between -79db to +15db) |
| pro3-88m | volume5#volumemute | Switch | RW | Mutes/Unmutes audio port #5 |
| pro3-88m | volume6#volume | Number | RW | Sets the volume of audio port #6 to the specified decibel level (between -79db to +15db) |
| pro3-88m | volume6#volumemute | Switch | RW | Mutes/Unmutes audio port #6 |
| | | | | |
| pro3-1616m | ALL OF THE pro3-88M channels (except different mirror settings) | | | |
| pro3-1616m | port11#portpower | Switch | RW | Enables/Disables output port #11 |
| pro3-1616m | port11#portoutput | Number | RW | Sets output port #11 to the specified input port |
| pro3-1616m | port12#portpower | Switch | RW | Enables/Disables output port #12 |
| pro3-1616m | port12#portoutput | Number | RW | Sets output port #12 to the specified input port |
| pro3-1616m | port13#portpower | Switch | RW | Enables/Disables output port #13 |
| pro3-1616m | port13#portoutput | Number | RW | Sets output port #13 to the specified input port |
| pro3-1616m | port14#portpower | Switch | RW | Enables/Disables output port #14 |
| pro3-1616m | port14#portoutput | Number | RW | Sets output port #14 to the specified input port |
| pro3-1616m | port15#portpower | Switch | RW | Enables/Disables output port #15 |
| pro3-1616m | port15#portoutput | Number | RW | Sets output port #15 to the specified input port |
| pro3-1616m | port16#portpower | Switch | RW | Enables/Disables output port #16 |
| pro3-1616m | port16#portoutput | Number | RW | Sets output port #16 to the specified input port |
| pro3-1616m | port17#portpower | Switch | RW | Enables/Disables output port #17 |
| pro3-1616m | port17#portoutput | Number | RW | Sets output port #17 to the specified input port |
| pro3-1616m | port18#portpower | Switch | RW | Enables/Disables output port #18 |
| pro3-1616m | port18#portoutput | Number | RW | Sets output port #18 to the specified input port |
| pro3-1616m | port19#portpower | Switch | RW | Enables/Disables output port #19 |
| pro3-1616m | port19#portoutput | Number | RW | Sets output port #19 to the specified input port |
| pro3-1616m | port20#portpower | Switch | RW | Enables/Disables output port #20 |
| pro3-1616m | port20#portoutput | Number | RW | Sets output port #20 to the specified input port |
| pro3-1616m | mirror17#portmirrorenabled | Number | RW | Sets hdmi port #17 to enable/disable port mirroring |
| pro3-1616m | mirror17#portmirror | Number | RW | Sets hdmi port #17 to mirror the specified output port (if enabled) |
| pro3-1616m | mirror18#portmirrorenabled | Number | RW | Sets hdmi port #18 to enable/disable port mirroring |
| pro3-1616m | mirror18#portmirror | Number | RW | Sets hdmi port #18 to mirror the specified output port (if enabled) |
| pro3-1616m | mirror19#portmirrorenabled | Number | RW | Sets hdmi port #19 to enable/disable port mirroring |
| pro3-1616m | mirror19#portmirror | Number | RW | Sets hdmi port #19 to mirror the specified output port (if enabled) |
| pro3-1616m | mirror20#portmirrorenabled | Number | RW | Sets hdmi port #20 to enable/disable port mirroring |
| pro3-1616m | mirror20#portmirror | Number | RW | Sets hdmi port #20 to mirror the specified output port (if enabled) |
| pro3-1616m | volume7#volume | Number | RW | Sets the volume of audio port #7 to the specified decibel level (between -79db to +15db) |
| pro3-1616m | volume7#volumemute | Switch | RW | Mutes/Unmutes audio port #7 |
| pro3-1616m | volume8#volume | Number | RW | Sets the volume of audio port #8 to the specified decibel level (between -79db to +15db) |
| pro3-1616m | volume8#volumemute | Switch | RW | Mutes/Unmutes audio port #8 |
| pro3-1616m | volume9#volume | Number | RW | Sets the volume of audio port #9 to the specified decibel level (between -79db to +15db) |
| pro3-1616m | volume9#volumemute | Switch | RW | Mutes/Unmutes audio port #9 |
| pro3-1616m | volume10#volume | Number | RW | Sets the volume of audio port #10 to the specified decibel level (between -79db to +15db) |
| pro3-1616m | volume10#volumemute | Switch | RW | Mutes/Unmutes audio port #10 |
| pro3-1616m | volume11#volume | Number | RW | Sets the volume of audio port #11 to the specified decibel level (between -79db to +15db) |
| pro3-1616m | volume11#volumemute | Switch | RW | Mutes/Unmutes audio port #11 |
| pro3-1616m | volume12#volume | Number | RW | Sets the volume of audio port #12 to the specified decibel level (between -79db to +15db) |
| pro3-1616m | volume12#volumemute | Switch | RW | Mutes/Unmutes audio port #12 |
### presetcmd
The presetcmd channel will take the following commands:
| Command | Description |
|---------|--------------------------------------------|
| saveX | Saves the current input/output to preset X |
| recallX | Sets the input/output to preset X |
| clearX | Clears the preset X |
Note: if X doesn't exist - nothing will occur.
The # of presets allowed depends on the firmware you are using (5 presets up to rev 13, 10 for rev 14 and above).
### matrixcmd
The matrixcmd channel will take the following commands:
| Command | Description |
|-------------|-------------------------------------------------------------------------------------------------------------------------------------|
| resetmatrix | Resets the matrix back to its default values (USE WITH CARE!). Note: some firmware upgrades require a resetmatrix after installing. |
| resetports | Resets the ports back to their default values (outputX=inputX) |
| allportsX | Sets all the output ports to the input port X |
Note: if X doesn't exist - nothing will occur.
The # of presets allowed depends on the firmware you are using (5 presets up to rev 13, 10 for rev 14 and above).
## Changes/Warnings
As of firmware 1.6.03 (rev 13), there are three issues on Atlona firmware (I have notified them on these issues):
- clearX command does not work. The TCP/IP command "ClearX" as specified in Atlona's protocol will ALWAYS return a "Command Failed". Please avoid this channel until atlona releases a new firmware.
- There is no way to query what the current status is of: panellock, and irenable. This add-on simply assumes that panellock is off and irenable is on at startup.
- If you make a change in the switches UI that requires a reboot (mainly changing any of the settings on the "Network" tab in the switch configuration UI), this add-on's connection will be inconsistently closed at different times.
The thing will go OFFLINE and then back ONLINE when the reconnect attempt is made - and then it starts all over again. Please make sure you reboot as soon as possible when the switch UI notifies you.
- a bug in the firmware will sometimes cause memory presets to disappear after a reboot
As of firmware 1.6.8 (rev 14),
- The "clearX" command has been fixed and works now.
- The number of presets have increased to 10
- If telnet mode is enabled, you must use the admin username/password to issue a matrixreset
## Example
### Things
Here is an example with minimal configuration parameters (using default values with no telnet login):
```java
atlona:pro3-88m:home [ ipAddress="192.168.1.30" ]
```
Here is another example with minimal configuration parameters (using default values with telnet login):
```java
atlona:pro3-88m:home [ ipAddress="192.168.1.30", userName="me", password="12345" ]
```
Here is a full configuration example:
```java
atlona:pro3-88m:home [ ipAddress="192.168.1.30", userName="me", password="12345", polling=600, ping=30, retryPolling=10 ]
```
### Items
Here is an example of items for the AT-UHD-PRO33-88M:
```java
Switch Atlona_Power "Power" { channel = "atlona:pro3-88m:home:primary#power" }
Switch Atlona_PanelLock "Panel Lock" { channel = "atlona:pro3-88m:home:primary#panellock" }
Switch Atlona_Presets "Preset Command" { channel = "atlona:pro3-88m:home:primary#presetcmd" }
Switch Atlona_IRLock "IR Lock" { channel = "atlona:pro3-88m:home:primary#irenable" }
Switch Atlona_PortPower1 "Port Power 1" { channel = "atlona:pro3-88m:home:port1#power" }
Switch Atlona_PortPower2 "Port Power 2" { channel = "atlona:pro3-88m:home:port2#power" }
Switch Atlona_PortPower3 "Port Power 3" { channel = "atlona:pro3-88m:home:port3#power" }
Switch Atlona_PortPower4 "Port Power 4" { channel = "atlona:pro3-88m:home:port4#power" }
Switch Atlona_PortPower5 "Port Power 5" { channel = "atlona:pro3-88m:home:port5#power" }
Switch Atlona_PortPower6 "Port Power 6" { channel = "atlona:pro3-88m:home:port6#power" }
Switch Atlona_PortPower7 "Port Power 7" { channel = "atlona:pro3-88m:home:port7#power" }
Switch Atlona_PortPower8 "Port Power 8" { channel = "atlona:pro3-88m:home:port8#power" }
Switch Atlona_PortPower9 "Port Power 9" { channel = "atlona:pro3-88m:home:port9#power" }
Switch Atlona_PortPower10 "Port Power 10" { channel = "atlona:pro3-88m:home:port10#power" }
Number Atlona_PortOutput1 "Living Room [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port1#portoutput" }
Number Atlona_PortOutput2 "Master Bed [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port2#portoutput" }
Number Atlona_PortOutput3 "Kitchen [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port3#portoutput" }
Number Atlona_PortOutput4 "Output 4 [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port4#portoutput" }
Number Atlona_PortOutput5 "Output 5 [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port5#portoutput" }
Number Atlona_PortOutput6 "Output 6 [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port6#portoutput" }
Number Atlona_PortOutput7 "Output 7 [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port7#portoutput" }
Number Atlona_PortOutput8 "Output 8 [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port8#portoutput" }
Number Atlona_PortOutput9 "Output 9 [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port9#portoutput" }
Number Atlona_PortOutput10 "Output 10 [MAP(atlonainputports.map):%s]" { channel = "atlona:pro3-88m:home:port10#portoutput" }
Number Atlona_PortMirror8 "Hdmi Mirror 8 [MAP(atlonaoutputports.map):%s]" { channel = "atlona:pro3-88m:home:mirror8#portmirror" }
Number Atlona_PortMirror10 "Hdmi Mirror 10 [MAP(atlonaoutputports.map):%s]" { channel = "atlona:pro3-88m:home:mirror10#portmirror" }
Number Atlona_Volume1 "Volume 1 [%s db]" { channel = "atlona:pro3-88m:home:volume1#volume" }
Number Atlona_Volume2 "Volume 2 [%s db]" { channel = "atlona:pro3-88m:home:volume2#volume" }
Number Atlona_Volume3 "Volume 3 [%s db]" { channel = "atlona:pro3-88m:home:volume3#volume" }
Number Atlona_Volume4 "Volume 4 [%s db]" { channel = "atlona:pro3-88m:home:volume4#volume" }
Number Atlona_Volume5 "Volume 5 [%s db]" { channel = "atlona:pro3-88m:home:volume5#volume" }
Number Atlona_Volume6 "Volume 6 [%s db]" { channel = "atlona:pro3-88m:home:volume6#volume" }
Switch Atlona_VolumeMute1 "Mute 1" { channel = "atlona:pro3-88m:home:volume1#volumemute" }
Switch Atlona_VolumeMute2 "Mute 2" { channel = "atlona:pro3-88m:home:volume1#volumemute" }
Switch Atlona_VolumeMute3 "Mute 3" { channel = "atlona:pro3-88m:home:volume1#volumemute" }
Switch Atlona_VolumeMute4 "Mute 4" { channel = "atlona:pro3-88m:home:volume1#volumemute" }
Switch Atlona_VolumeMute5 "Mute 5" { channel = "atlona:pro3-88m:home:volume1#volumemute" }
Switch Atlona_VolumeMute6 "Mute 6" { channel = "atlona:pro3-88m:home:volume1#volumemute" }
```
### SiteMap
```perl
sitemap demo label="Main Menu" {
Frame label="Atlona" {
Text label="Device" {
Switch item=Atlona_Power
Switch item=Atlona_PanelLock
Switch item=Atlona_IRLock
Text item=Atlona_Presets
}
Text label="Ports" {
Switch item=Atlona_PortPower1
Switch item=Atlona_PortPower2
Switch item=Atlona_PortPower3
Switch item=Atlona_PortPower4
Switch item=Atlona_PortPower5
Switch item=Atlona_PortPower6
Switch item=Atlona_PortPower7
Switch item=Atlona_PortPower8
Switch item=Atlona_PortPower9
Switch item=Atlona_PortPower10
Selection item=Atlona_PortOutput1 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"]
Selection item=Atlona_PortOutput2 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"]
Selection item=Atlona_PortOutput3 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"]
Selection item=Atlona_PortOutput4 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"]
Selection item=Atlona_PortOutput5 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"]
Selection item=Atlona_PortOutput6 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"]
Selection item=Atlona_PortOutput7 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"]
Selection item=Atlona_PortOutput8 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"] visibility=[Atlona_PortMirror8==0]
Selection item=Atlona_PortOutput9 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"]
Selection item=Atlona_PortOutput10 mappings=[1="CableBox",2="BluRay Player",3="Roku",4="Apple TV",5="Input 5",6="Input 6",7="Input 7",8="Input 8"] visibility=[Atlona_PortMirror10==0]
Selection item=Atlona_PortMirror8 mappings=[0="None",1="Living Room",2="Master Bed",3="Kitchen",4="Output 4",5="Output 5",6="Output 6",7="Output 7",9="Output 9"]
Selection item=Atlona_PortMirror10 mappings=[0="None",1="Living Room",2="Master Bed",3="Kitchen",4="Output 4",5="Output 5",6="Output 6",7="Output 7",9="Output 9"]
}
Text label="Audio" {
Setpoint item=Atlona_Volume1 minValue=-79 maxValue=15
Setpoint item=Atlona_Volume2 minValue=-79 maxValue=15
Setpoint item=Atlona_Volume3 minValue=-79 maxValue=15
Setpoint item=Atlona_Volume4 minValue=-79 maxValue=15
Setpoint item=Atlona_Volume5 minValue=-79 maxValue=15
Setpoint item=Atlona_Volume6 minValue=-79 maxValue=15
Switch item=Atlona_VolumeMute1
Switch item=Atlona_VolumeMute2
Switch item=Atlona_VolumeMute3
Switch item=Atlona_VolumeMute4
Switch item=Atlona_VolumeMute5
Switch item=Atlona_VolumeMute6
}
}
}
```
## Transformation Maps
The following is some example transformation maps you can create.
Be sure they are in sync with the mappings above.
### atlonainputports.map
```text
1=CableBox
2=BluRay Player
3=Roku
4=Apple TV
5=Input 5
6=Input 6
7=Input 7
8=Input 8
-=-
NULL=-
```
### atlonaoutputports.map
```text
1=Living Room
2=Master Bed
3=Kitchen
4=Output 4
5=Output 5
6=Output 6
7=Output 7
8=Output 8
9=Output 9
10=Output 10
-=-
NULL=-
```

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.openhab.addons.bundles</groupId>
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
<version>3.0.0-SNAPSHOT</version>
</parent>
<artifactId>org.openhab.binding.atlona</artifactId>
<name>openHAB Add-ons :: Bundles :: Atlona Binding</name>
</project>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<features name="org.openhab.binding.atlona-${project.version}" xmlns="http://karaf.apache.org/xmlns/features/v1.4.0">
<repository>mvn:org.openhab.core.features.karaf/org.openhab.core.features.karaf.openhab-core/${ohc.version}/xml/features</repository>
<feature name="openhab-binding-atlona" description="Atlona PRO3 Switch Binding" version="${project.version}">
<feature>openhab-runtime-base</feature>
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.atlona/${project.version}</bundle>
</feature>
</features>

View File

@@ -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.atlona.internal;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.openhab.core.thing.ThingTypeUID;
/**
* The {@link AtlonaBinding} class defines common constants, which are used across the whole binding.
*
* @author Tim Roberts - Initial contribution
*/
@NonNullByDefault
public class AtlonaBindingConstants {
/**
* The binding identifier for atlona
*/
public static final String BINDING_ID = "atlona";
/**
* Thing ID for the AT-UHD-PRO3-44m (4x4 hdbaset matrix)
*/
public static final ThingTypeUID THING_TYPE_PRO3_44M = new ThingTypeUID(BINDING_ID, "pro3-44m");
/**
* Thing ID for the AT-UHD-PRO3-66m (6x6 hdbaset matrix)
*/
public static final ThingTypeUID THING_TYPE_PRO3_66M = new ThingTypeUID(BINDING_ID, "pro3-66m");
/**
* Thing ID for the AT-UHD-PRO3-88m (8x8 hdbaset matrix)
*/
public static final ThingTypeUID THING_TYPE_PRO3_88M = new ThingTypeUID(BINDING_ID, "pro3-88m");
/**
* Thing ID for the AT-UHD-PRO3-1616m (16x16 hdbaset matrix)
*/
public static final ThingTypeUID THING_TYPE_PRO3_1616M = new ThingTypeUID(BINDING_ID, "pro3-1616m");
}

View File

@@ -0,0 +1,51 @@
/**
* 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.atlona.internal;
import org.openhab.binding.atlona.internal.handler.AtlonaHandler;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.State;
/**
*
* A callback to {@link AtlonaHandler} that can be used to update the status, properties and state of the thing.
*
* @author Tim Roberts - Initial contribution
*/
public interface AtlonaHandlerCallback {
/**
* Callback to the {@link AtlonaHandler} to update the status of the thing.
*
* @param status a non-null {@link org.openhab.core.thing.ThingStatus}
* @param detail a non-null {@link org.openhab.core.thing.ThingStatusDetail}
* @param msg a possibly null, possibly empty message
*/
void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg);
/**
* Callback to the {@link AtlonaHandler} to update the state of an item
*
* @param channelId the non-null, non-empty channel id
* @param state the new non-null {@State}
*/
void stateChanged(String channelId, State state);
/**
* Callback to the {@link AtlonaHandler} to update the property of a thing
*
* @param propertyName a non-null, non-empty property name
* @param propertyValue a non-null, possibly empty property value
*/
void setProperty(String propertyName, String propertyValue);
}

View File

@@ -0,0 +1,97 @@
/**
* 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.atlona.internal;
import static org.openhab.binding.atlona.internal.AtlonaBindingConstants.*;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.atlona.internal.pro3.AtlonaPro3Capabilities;
import org.openhab.binding.atlona.internal.pro3.AtlonaPro3Handler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingTypeUID;
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
import org.openhab.core.thing.binding.ThingHandler;
import org.openhab.core.thing.binding.ThingHandlerFactory;
import org.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link org.openhab.binding.atlona.internal.AtlonaHandlerFactory} is responsible for creating things and thing
* handlers.
*
* @author Tim Roberts - Initial contribution
*/
@Component(service = ThingHandlerFactory.class, configurationPid = "binding.atlona")
public class AtlonaHandlerFactory extends BaseThingHandlerFactory {
private final Logger logger = LoggerFactory.getLogger(AtlonaHandlerFactory.class);
/**
* The set of supported Atlona products
*/
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.unmodifiableSet(
Stream.of(THING_TYPE_PRO3_44M, THING_TYPE_PRO3_66M, THING_TYPE_PRO3_88M, THING_TYPE_PRO3_1616M)
.collect(Collectors.toSet()));
/**
* {@inheritDoc}
*
* Simply returns true if the given thingTypeUID is within {@link #SUPPORTED_THING_TYPES_UIDS}
*/
@Override
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
}
/**
* {@inheritDoc}
*
* Creates the handler for the given thing given its thingTypeUID
*/
@Override
protected ThingHandler createHandler(Thing thing) {
if (thing == null) {
logger.error("createHandler was given a null thing!");
return null;
}
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
if (thingTypeUID.equals(THING_TYPE_PRO3_44M)) {
return new AtlonaPro3Handler(thing, new AtlonaPro3Capabilities(5, 3, Collections.singleton(5)));
}
if (thingTypeUID.equals(THING_TYPE_PRO3_66M)) {
return new AtlonaPro3Handler(thing, new AtlonaPro3Capabilities(8, 4,
Collections.unmodifiableSet(Stream.of(6, 8).collect(Collectors.toSet()))));
}
if (thingTypeUID.equals(THING_TYPE_PRO3_88M)) {
return new AtlonaPro3Handler(thing, new AtlonaPro3Capabilities(10, 6,
Collections.unmodifiableSet(Stream.of(8, 10).collect(Collectors.toSet()))));
}
if (thingTypeUID.equals(THING_TYPE_PRO3_1616M)) {
return new AtlonaPro3Handler(thing, new AtlonaPro3Capabilities(5, 3,
Collections.unmodifiableSet(Stream.of(17, 18, 19, 20).collect(Collectors.toSet()))));
}
logger.warn("Unknown binding: {}: {}", thingTypeUID.getId(), thingTypeUID.getBindingId());
return null;
}
}

View File

@@ -0,0 +1,146 @@
/**
* 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.atlona.internal;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.commons.lang.StringUtils;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.State;
/**
* Defines an implementation of {@link AtlonaHandlerCallback} that will remember the last state
* for an channelId and suppress the callback if the state hasn't changed
*
* @author Tim Roberts - Initial contribution
*/
public class StatefulHandlerCallback implements AtlonaHandlerCallback {
/** The wrapped callback */
private final AtlonaHandlerCallback wrappedCallback;
/** The state by channel id */
private final Map<String, State> state = new ConcurrentHashMap<>();
private final Lock statusLock = new ReentrantLock();
private ThingStatus lastThingStatus;
private ThingStatusDetail lastThingStatusDetail;
/**
* Create the callback from the other {@link AtlonaHandlerCallback}
*
* @param wrappedCallback a non-null {@link AtlonaHandlerCallback}
* @throws IllegalArgumentException if wrappedCallback is null
*/
public StatefulHandlerCallback(AtlonaHandlerCallback wrappedCallback) {
if (wrappedCallback == null) {
throw new IllegalArgumentException("wrappedCallback cannot be null");
}
this.wrappedCallback = wrappedCallback;
}
/**
* Overrides the status changed to simply call the {@link #wrappedCallback}
*
* @param status the new status
* @param detail the new detail
* @param msg the new message
*/
@Override
public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
statusLock.lock();
try {
// Simply return we match the last status change (prevents loops if changing to the same status)
if (status == lastThingStatus && detail == lastThingStatusDetail) {
return;
}
lastThingStatus = status;
lastThingStatusDetail = detail;
} finally {
statusLock.unlock();
}
// If we got this far - call the underlying one
wrappedCallback.statusChanged(status, detail, msg);
}
/**
* Overrides the state changed to determine if the state is new or changed and then
* to call the {@link #wrappedCallback} if it has
*
* @param channelId the channel id that changed
* @param state the new state
*/
@Override
public void stateChanged(String channelId, State state) {
if (StringUtils.isEmpty(channelId)) {
return;
}
final State oldState = this.state.get(channelId);
// If both null OR the same value (enums), nothing changed
if (oldState == state) {
return;
}
// If they are equal - nothing changed
if (oldState != null && oldState.equals(state)) {
return;
}
// Something changed - save the new state and call the underlying wrapped
this.state.put(channelId, state);
wrappedCallback.stateChanged(channelId, state);
}
/**
* Removes the state associated with the channel id. If the channelid
* doesn't exist (or is null or is empty), this method will do nothing.
*
* @param channelId the channel id to remove state
*/
public void removeState(String channelId) {
if (StringUtils.isEmpty(channelId)) {
return;
}
state.remove(channelId);
}
/**
* Overrides the set property to simply call the {@link #wrappedCallback}
*
* @param propertyName a non-null, non-empty property name
* @param propertyValue a non-null, possibly empty property value
*/
@Override
public void setProperty(String propertyName, String propertyValue) {
wrappedCallback.setProperty(propertyName, propertyValue);
}
/**
* Callback to get the {@link State} for a given property name
*
* @param propertyName a possibly null, possibly empty property name
* @return the {@link State} for the propertyName or null if not found
*/
public State getState(String propertyName) {
// TODO Auto-generated method stub
return state.get(propertyName);
}
}

View File

@@ -0,0 +1,273 @@
/**
* 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.atlona.internal.discovery;
import static org.openhab.binding.atlona.internal.AtlonaBindingConstants.*;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.NetworkInterface;
import java.net.SocketTimeoutException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.openhab.binding.atlona.internal.pro3.AtlonaPro3Config;
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.osgi.service.component.annotations.Component;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Discovery class for the Atlona PRO3 line. The PRO3 line uses SDDP (simple device discovery protocol) for discovery
* (similar to UPNP but defined by Control4). The user should start the discovery process in openhab and then log into
* the switch, go to the Network options and press the SDDP button (which initiates the SDDP conversation).
*
* @author Tim Roberts - Initial contribution
*/
@Component(service = DiscoveryService.class, immediate = true, configurationPid = "discovery.atlona")
public class AtlonaDiscovery extends AbstractDiscoveryService {
private final Logger logger = LoggerFactory.getLogger(AtlonaDiscovery.class);
/**
* Address SDDP broadcasts on
*/
private static final String SDDP_ADDR = "239.255.255.250";
/**
* Port number SDDP uses
*/
private static final int SDDP_PORT = 1902;
/**
* SDDP packet should be only 512 in size - make it 600 to give us some room
*/
private static final int BUFFER_SIZE = 600;
/**
* Socket read timeout (in ms) - allows us to shutdown the listening every TIMEOUT
*/
private static final int TIMEOUT = 1000;
/**
* Whether we are currently scanning or not
*/
private boolean scanning;
/**
* The {@link ExecutorService} to run the listening threads on.
*/
private ExecutorService executorService;
/**
* Constructs the discovery class using the thing IDs that we can discover.
*/
public AtlonaDiscovery() {
super(Collections.unmodifiableSet(
Stream.of(THING_TYPE_PRO3_44M, THING_TYPE_PRO3_66M, THING_TYPE_PRO3_88M, THING_TYPE_PRO3_1616M)
.collect(Collectors.toSet())),
30, false);
}
/**
* {@inheritDoc}
*
* Starts the scan. This discovery will:
* <ul>
* <li>Request all the network interfaces</li>
* <li>For each network interface, create a listening thread using {@link #executorService}</li>
* <li>Each listening thread will open up a {@link MulticastSocket} using {@link #SDDP_ADDR} and {@link #SDDP_PORT}
* and
* will receive any {@link DatagramPacket} that comes in</li>
* <li>The {@link DatagramPacket} is then investigated to see if is a SDDP packet and will create a new thing from
* it</li>
* </ul>
* The process will continue until {@link #stopScan()} is called.
*/
@Override
protected void startScan() {
if (executorService != null) {
stopScan();
}
logger.debug("Starting Discovery");
try {
final InetAddress addr = InetAddress.getByName(SDDP_ADDR);
final List<NetworkInterface> networkInterfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
executorService = Executors.newFixedThreadPool(networkInterfaces.size());
scanning = true;
for (final NetworkInterface netint : networkInterfaces) {
executorService.execute(() -> {
try {
MulticastSocket multiSocket = new MulticastSocket(SDDP_PORT);
multiSocket.setSoTimeout(TIMEOUT);
multiSocket.setNetworkInterface(netint);
multiSocket.joinGroup(addr);
while (scanning) {
DatagramPacket receivePacket = new DatagramPacket(new byte[BUFFER_SIZE], BUFFER_SIZE);
try {
multiSocket.receive(receivePacket);
String message = new String(receivePacket.getData()).trim();
if (message != null && message.length() > 0) {
messageReceive(message);
}
} catch (SocketTimeoutException e) {
// ignore
}
}
multiSocket.close();
} catch (Exception e) {
if (!e.getMessage().contains("No IP addresses bound to interface")) {
logger.debug("Error getting ip addresses: {}", e.getMessage(), e);
}
}
});
}
} catch (IOException e) {
logger.debug("Error getting ip addresses: {}", e.getMessage(), e);
}
}
/**
* SDDP message has the following format
*
* <pre>
* NOTIFY ALIVE SDDP/1.0
* From: "192.168.1.30:1902"
* Host: "AT-UHD-PRO3-88M_B898B0030F4D"
* Type: "AT-UHD-PRO3-88M"
* Max-Age: 1800
* Primary-Proxy: "avswitch"
* Proxies: "avswitch"
* Manufacturer: "Atlona"
* Model: "AT-UHD-PRO3-88M"
* Driver: "avswitch_Atlona_AT-UHD-PRO3-88M_IP.c4i"
* Config-URL: "http://192.168.1.30/"
* </pre>
*
* First parse the manufacturer, host, model and IP address from the message. For the "Host" field, we parse out the
* serial #. For the From field, we parse out the IP address (minus the port #). If we successfully found all four
* and the manufacturer is "Atlona" and it's a model we recognize, we then create our thing from it.
*
* @param message possibly null, possibly empty SDDP message
*/
private void messageReceive(String message) {
if (message == null || message.trim().length() == 0) {
return;
}
String host = null;
String model = null;
String from = null;
String manufacturer = null;
for (String msg : message.split("\r\n")) {
int idx = msg.indexOf(':');
if (idx > 0) {
String name = msg.substring(0, idx);
if (name.equalsIgnoreCase("Host")) {
host = msg.substring(idx + 1).trim().replaceAll("\"", "");
int sep = host.indexOf('_');
if (sep >= 0) {
host = host.substring(sep + 1);
}
} else if (name.equalsIgnoreCase("Model")) {
model = msg.substring(idx + 1).trim().replaceAll("\"", "");
} else if (name.equalsIgnoreCase("Manufacturer")) {
manufacturer = msg.substring(idx + 1).trim().replaceAll("\"", "");
} else if (name.equalsIgnoreCase("From")) {
from = msg.substring(idx + 1).trim().replaceAll("\"", "");
int sep = from.indexOf(':');
if (sep >= 0) {
from = from.substring(0, sep);
}
}
}
}
if (!"Atlona".equalsIgnoreCase(manufacturer)) {
return;
}
if (host != null && model != null && from != null) {
ThingTypeUID typeId = null;
if (model.equalsIgnoreCase("AT-UHD-PRO3-44M")) {
typeId = THING_TYPE_PRO3_44M;
} else if (model.equalsIgnoreCase("AT-UHD-PRO3-66M")) {
typeId = THING_TYPE_PRO3_66M;
} else if (model.equalsIgnoreCase("AT-UHD-PRO3-88M")) {
typeId = THING_TYPE_PRO3_88M;
} else if (model.equalsIgnoreCase("AT-UHD-PRO3-1616M")) {
typeId = THING_TYPE_PRO3_1616M;
} else {
logger.warn("Unknown model #: {}", model);
}
if (typeId != null) {
logger.debug("Creating binding for {} ({})", model, from);
ThingUID j = new ThingUID(typeId, host);
Map<String, Object> properties = new HashMap<>(1);
properties.put(AtlonaPro3Config.IP_ADDRESS, from);
DiscoveryResult result = DiscoveryResultBuilder.create(j).withProperties(properties)
.withLabel(model + " (" + from + ")").build();
thingDiscovered(result);
}
}
}
/**
* {@inheritDoc}
*
* Stops the discovery scan. We set {@link #scanning} to false (allowing the listening threads to end naturally
* within {@link #TIMEOUT) * 5 time then shutdown the {@link #executorService}
*/
@Override
protected synchronized void stopScan() {
super.stopScan();
if (executorService == null) {
return;
}
scanning = false;
try {
executorService.awaitTermination(TIMEOUT * 5, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
}
executorService.shutdown();
executorService = null;
}
}

View File

@@ -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.atlona.internal.handler;
/**
* Any model specific capabilities class should inherit from this base class. Currently doesn't provide any generic
* functionality.
*
* @author Tim Roberts - Initial contribution
*/
public abstract class AtlonaCapabilities {
}

View File

@@ -0,0 +1,53 @@
/**
* 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.atlona.internal.handler;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.binding.BaseThingHandler;
/**
* This abstract class should be the base class for any Atlona product line handler.
*
* @author Tim Roberts - Initial contribution
*/
public abstract class AtlonaHandler<C extends AtlonaCapabilities> extends BaseThingHandler {
/**
* The model specific capabilities
*/
private final C capabilities;
/**
* Constructs the handler from the specified thing and capabilities
*
* @param thing a non-null {@link org.openhab.core.thing.Thing}
* @param capabilities a non-null {@link org.openhab.binding.atlona.internal.handler.AtlonaCapabilities}
*/
public AtlonaHandler(Thing thing, C capabilities) {
super(thing);
if (capabilities == null) {
throw new IllegalArgumentException("capabilities cannot be null");
}
this.capabilities = capabilities;
}
/**
* Returns the model specific capabilities
*
* @return a non-null {@link org.openhab.binding.atlona.internal.handler.AtlonaCapabilities}
*/
protected C getCapabilities() {
return capabilities;
}
}

View File

@@ -0,0 +1,382 @@
/**
* 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.atlona.internal.net;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.SocketChannel;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Represents a restartable socket connection to the underlying telnet session. Commands can be sent via
* {@link #sendCommand(String)} and responses will be received on any {@link SocketSessionListener}. This implementation
* of {@link SocketSession} communicates using a {@link SocketChannel} connection.
*
* @author Tim Roberts - Initial contribution
*/
public class SocketChannelSession implements SocketSession {
private final Logger logger = LoggerFactory.getLogger(SocketChannelSession.class);
/**
* The host/ip address to connect to
*/
private final String host;
/**
* The port to connect to
*/
private final int port;
/**
* The actual socket being used. Will be null if not connected
*/
private final AtomicReference<SocketChannel> socketChannel = new AtomicReference<>();
/**
* The {@link ResponseReader} that will be used to read from {@link #_readBuffer}
*/
private final ResponseReader responseReader = new ResponseReader();
/**
* The responses read from the {@link #responseReader}
*/
private final BlockingQueue<Object> responses = new ArrayBlockingQueue<>(50);
/**
* The dispatcher of responses from {@link #responses}
*/
private final Dispatcher dispatcher = new Dispatcher();
/**
* The {@link SocketSessionListener} that the {@link #dispatcher} will call
*/
private List<SocketSessionListener> listeners = new CopyOnWriteArrayList<>();
/**
* Creates the socket session from the given host and port
*
* @param host a non-null, non-empty host/ip address
* @param port the port number between 1 and 65535
*/
public SocketChannelSession(String host, int port) {
if (host == null || host.trim().length() == 0) {
throw new IllegalArgumentException("Host cannot be null or empty");
}
if (port < 1 || port > 65535) {
throw new IllegalArgumentException("Port must be between 1 and 65535");
}
this.host = host;
this.port = port;
}
@Override
public void addListener(SocketSessionListener listener) {
if (listener == null) {
throw new IllegalArgumentException("listener cannot be null");
}
listeners.add(listener);
}
@Override
public void clearListeners() {
listeners.clear();
}
@Override
public boolean removeListener(SocketSessionListener listener) {
return listeners.remove(listener);
}
@Override
public void connect() throws IOException {
disconnect();
final SocketChannel channel = SocketChannel.open();
channel.configureBlocking(true);
logger.debug("Connecting to {}:{}", host, port);
channel.connect(new InetSocketAddress(host, port));
logger.debug("Waiting for connect");
while (!channel.finishConnect()) {
try {
Thread.sleep(250);
} catch (InterruptedException e) {
}
}
socketChannel.set(channel);
new Thread(dispatcher).start();
new Thread(responseReader).start();
}
@Override
public void disconnect() throws IOException {
if (isConnected()) {
logger.debug("Disconnecting from {}:{}", host, port);
final SocketChannel channel = socketChannel.getAndSet(null);
channel.close();
dispatcher.stopRunning();
responseReader.stopRunning();
responses.clear();
}
}
@Override
public boolean isConnected() {
final SocketChannel channel = socketChannel.get();
return channel != null && channel.isConnected();
}
@Override
public synchronized void sendCommand(String command) throws IOException {
if (command == null) {
throw new IllegalArgumentException("command cannot be null");
}
if (!isConnected()) {
throw new IOException("Cannot send message - disconnected");
}
ByteBuffer toSend = ByteBuffer.wrap((command + "\r\n").getBytes());
final SocketChannel channel = socketChannel.get();
if (channel == null) {
logger.debug("Cannot send command '{}' - socket channel was closed", command);
} else {
logger.debug("Sending Command: '{}'", command);
channel.write(toSend);
}
}
/**
* This is the runnable that will read from the socket and add messages to the responses queue (to be processed by
* the dispatcher)
*
* @author Tim Roberts
*
*/
private class ResponseReader implements Runnable {
/**
* Whether the reader is currently running
*/
private final AtomicBoolean isRunning = new AtomicBoolean(false);
/**
* Locking to allow proper shutdown of the reader
*/
private final CountDownLatch running = new CountDownLatch(1);
/**
* Stops the reader. Will wait 5 seconds for the runnable to stop
*/
public void stopRunning() {
if (isRunning.getAndSet(false)) {
try {
if (!running.await(5, TimeUnit.SECONDS)) {
logger.warn("Waited too long for response reader to finish");
}
} catch (InterruptedException e) {
// Do nothing
}
}
}
/**
* Runs the logic to read from the socket until {@link #isRunning} is false. A 'response' is anything that ends
* with a carriage-return/newline combo. Additionally, the special "Login: " and "Password: " prompts are
* treated as responses for purposes of logging in.
*/
@Override
public void run() {
final StringBuilder sb = new StringBuilder(100);
final ByteBuffer readBuffer = ByteBuffer.allocate(1024);
isRunning.set(true);
responses.clear();
while (isRunning.get()) {
try {
// if reader is null, sleep and try again
if (readBuffer == null) {
Thread.sleep(250);
continue;
}
final SocketChannel channel = socketChannel.get();
if (channel == null) {
// socket was closed
isRunning.set(false);
break;
}
int bytesRead = channel.read(readBuffer);
if (bytesRead == -1) {
responses.put(new IOException("server closed connection"));
isRunning.set(false);
break;
} else if (bytesRead == 0) {
readBuffer.clear();
continue;
}
readBuffer.flip();
while (readBuffer.hasRemaining()) {
final char ch = (char) readBuffer.get();
sb.append(ch);
if (ch == '\n' || ch == ' ') {
final String str = sb.toString();
if (str.endsWith("\r\n") || str.endsWith("Login: ") || str.endsWith("Password: ")) {
sb.setLength(0);
final String response = str.substring(0, str.length() - 2);
responses.put(response);
}
}
}
readBuffer.flip();
} catch (InterruptedException e) {
// Do nothing - probably shutting down
} catch (AsynchronousCloseException e) {
// socket was definitely closed by another thread
} catch (IOException e) {
try {
isRunning.set(false);
responses.put(e);
} catch (InterruptedException e1) {
// Do nothing - probably shutting down
}
}
}
running.countDown();
}
}
/**
* The dispatcher runnable is responsible for reading the response queue and dispatching it to the current callable.
* Since the dispatcher is ONLY started when a callable is set, responses may pile up in the queue and be dispatched
* when a callable is set. Unlike the socket reader, this can be assigned to another thread (no state outside of the
* class).
*
* @author Tim Roberts
*/
private class Dispatcher implements Runnable {
/**
* Whether the dispatcher is running or not
*/
private final AtomicBoolean isRunning = new AtomicBoolean(false);
/**
* Locking to allow proper shutdown of the reader
*/
private final CountDownLatch running = new CountDownLatch(1);
/**
* Whether the dispatcher is currently processing a message
*/
private final AtomicReference<Thread> processingThread = new AtomicReference<>();
/**
* Stops the reader. Will wait 5 seconds for the runnable to stop (should stop within 1 second based on the poll
* timeout below)
*/
public void stopRunning() {
if (isRunning.getAndSet(false)) {
// only wait if stopRunning didn't get called as part of processing a message
// (which would happen if we are processing an exception that forced a session close)
final Thread processingThread = this.processingThread.get();
if (processingThread != null && Thread.currentThread() != processingThread) {
try {
if (!running.await(5, TimeUnit.SECONDS)) {
logger.warn("Waited too long for dispatcher to finish");
}
} catch (InterruptedException e) {
// do nothing
}
}
}
}
/**
* Runs the logic to dispatch any responses to the current listeners until {@link #isRunning} is false.
*/
@Override
public void run() {
processingThread.set(Thread.currentThread());
isRunning.set(true);
while (isRunning.get()) {
try {
// if no listeners, we don't want to start dispatching yet.
if (listeners.size() == 0) {
Thread.sleep(250);
continue;
}
final Object response = responses.poll(1, TimeUnit.SECONDS);
if (response != null) {
if (response instanceof String) {
try {
logger.debug("Dispatching response: {}", response);
final SocketSessionListener[] listeners = SocketChannelSession.this.listeners
.toArray(new SocketSessionListener[0]);
for (SocketSessionListener listener : listeners) {
listener.responseReceived((String) response);
}
} catch (Exception e) {
logger.warn("Exception occurred processing the response '{}': ", response, e);
}
} else if (response instanceof Exception) {
logger.debug("Dispatching exception: {}", response);
final SocketSessionListener[] listeners = SocketChannelSession.this.listeners
.toArray(new SocketSessionListener[0]);
for (SocketSessionListener listener : listeners) {
listener.responseException((Exception) response);
}
} else {
logger.warn("Unknown response class: {}", response);
}
}
} catch (InterruptedException e) {
// Do nothing
} catch (Exception e) {
logger.debug("Uncaught exception {}", e.getMessage(), e);
break;
}
}
isRunning.set(false);
processingThread.set(null);
running.countDown();
}
}
}

View File

@@ -0,0 +1,78 @@
/**
* 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.atlona.internal.net;
import java.io.IOException;
/**
* This is a socket session interface that defines the contract for a socket session. A socket session will initiate
* communications with the underlying device and provide message back via the {@link SocketSessionListener}
*
* @author Tim Roberts - Initial contribution
*/
public interface SocketSession {
/**
* Adds a {@link SocketSessionListener} to call when responses/exceptions have been received
*
* @param listener a non-null {@link SocketSessionListener} to use
*/
void addListener(SocketSessionListener listener);
/**
* Clears all listeners
*/
void clearListeners();
/**
* Removes a {@link SocketSessionListener} from this session
*
* @param listener a non-null {@link SocketSessionListener} to remove
* @return true if removed, false otherwise
*/
boolean removeListener(SocketSessionListener listener);
/**
* Will attempt to connect to the {@link #_host} on port {@link #_port}. If we are current connected, will
* {@link #disconnect()} first. Once connected, the {@link #_writer} and {@link #_reader} will be created, the
* {@link #_dispatcher} and {@link #_responseReader} will be started.
*
* @throws java.io.IOException if an exception occurs during the connection attempt
*/
void connect() throws IOException;
/**
* Disconnects from the {@link #_host} if we are {@link #isConnected()}. The {@link #_writer}, {@link #_reader} and
* {@link #_client}
* will be closed and set to null. The {@link #_dispatcher} and {@link #_responseReader} will be stopped, the
* {@link #_listeners} will be nulled and the {@link #_responses} will be cleared.
*
* @throws java.io.IOException if an exception occurs during the disconnect attempt
*/
void disconnect() throws IOException;
/**
* Returns true if we are connected ({@link #_client} is not null and is connected)
*
* @return true if connected, false otherwise
*/
boolean isConnected();
/**
* Sends the specified command to the underlying socket
*
* @param command a non-null, non-empty command
* @throws java.io.IOException an exception that occurred while sending
*/
void sendCommand(String command) throws IOException;
}

View File

@@ -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.atlona.internal.net;
/**
* Interface defining a listener to a {@link SocketSession} that will receive responses and/or exceptions from the
* socket
*
* @author Tim Roberts - Initial contribution
*/
public interface SocketSessionListener {
/**
* Called when a command has completed with the response for the command
*
* @param response a non-null, possibly empty response
*/
public void responseReceived(String response);
/**
* Called when a command finished with an exception or a general exception occurred while reading
*
* @param e a non-null exception
*/
public void responseException(Exception e);
}

View File

@@ -0,0 +1,101 @@
/**
* 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.atlona.internal.pro3;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import org.openhab.binding.atlona.internal.handler.AtlonaCapabilities;
/**
* The capabilities class for the Atlona PRO3 line. Each PRO3 model differs in the number of (output) ports that can be
* powered, the number of audio ports there are and which (output) ports are HDMI ports.
*
* @author Tim Roberts - Initial contribution
*/
public class AtlonaPro3Capabilities extends AtlonaCapabilities {
/**
* Number of power ports
*/
private final int nbrPowerPorts;
/**
* Number of audio ports
*/
private final int nbrAudioPorts;
/**
* The set of output ports that are HDMI ports
*/
private final Set<Integer> hdmiPorts;
/**
* Constructs the capabilities from the parms
*
* @param nbrPowerPorts a greater than 0 number of power ports
* @param nbrAudioPorts a greater than 0 number of audio ports
* @param hdmiPorts a non-null, non-empty set of hdmi ports
*/
public AtlonaPro3Capabilities(int nbrPowerPorts, int nbrAudioPorts, Set<Integer> hdmiPorts) {
super();
if (nbrPowerPorts < 1) {
throw new IllegalArgumentException("nbrPowerPorts must be greater than 0");
}
if (nbrAudioPorts < 1) {
throw new IllegalArgumentException("nbrAudioPorts must be greater than 0");
}
if (hdmiPorts == null) {
throw new IllegalArgumentException("hdmiPorts cannot be null");
}
if (hdmiPorts.isEmpty()) {
throw new IllegalArgumentException("hdmiPorts cannot be empty");
}
this.nbrPowerPorts = nbrPowerPorts;
this.nbrAudioPorts = nbrAudioPorts;
this.hdmiPorts = Collections.unmodifiableSet(new HashSet<>(hdmiPorts));
}
/**
* Returns the number of power ports
*
* @return a greater than 0 number of power ports
*/
int getNbrPowerPorts() {
return nbrPowerPorts;
}
/**
* Returns the number of audio ports
*
* @return a greater than 0 number of audio ports
*/
int getNbrAudioPorts() {
return nbrAudioPorts;
}
/**
* Returns the set of hdmi ports
*
* @return a non-null, non-empty immutable set of hdmi ports
*/
Set<Integer> getHdmiPorts() {
return hdmiPorts;
}
}

View File

@@ -0,0 +1,168 @@
/**
* 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.atlona.internal.pro3;
import org.openhab.binding.atlona.internal.discovery.AtlonaDiscovery;
/**
* Configuration class for the Atlona Pro3 line of switchers
*
* @author Tim Roberts - Initial contribution
*/
public class AtlonaPro3Config {
/**
* Constant field used in {@link AtlonaDiscovery} to set the config property during discovery. Value of this field
* needs to match {@link #ipAddress}
*/
public static final String IP_ADDRESS = "ipAddress";
/**
* IP Address (or host name) of switch
*/
private String ipAddress;
/**
* Optional username to login in with. Only used if the switch has it's "Telnet Login" option turned on
*/
private String userName;
/**
* Optional password to login in with. Only used if the switch has it's "Telnet Login" option turned on
*/
private String password;
/**
* Polling time (in seconds) to refresh state from the switch itself. Only useful if something else modifies the
* switch (usually through the front panel or the IR link)
*/
private int polling;
/**
* Ping time (in seconds) to keep the connection alive. Should be less than the IP Timeout on the switch.
*/
private int ping;
/**
* Polling time (in seconds) to attempt a reconnect if the socket session has failed
*/
private int retryPolling;
/**
* Returns the IP address or host name of the switch
*
* @return the IP address or host name of the swtich
*/
public String getIpAddress() {
return ipAddress;
}
/**
* Sets the IP address or host name of the switch
*
* @param ipAddress the IP Address or host name of the switch
*/
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
/**
* Gets the username used to login with
*
* @return the username used to login with
*/
public String getUserName() {
return userName;
}
/**
* Sets the username used to login with
*
* @param userName the username used to login with
*/
public void setUserName(String userName) {
this.userName = userName;
}
/**
* Gets the password used to login with
*
* @return the password used to login with
*/
public String getPassword() {
return password;
}
/**
* Sets the password used to login with
*
* @param password the password used to login with
*/
public void setPassword(String password) {
this.password = password;
}
/**
* Gets the polling (in seconds) to refresh state
*
* @return the polling (in seconds) to refresh state
*/
public int getPolling() {
return polling;
}
/**
* Sets the polling (in seconds) to refresh state
*
* @param polling the polling (in seconds) to refresh state
*/
public void setPolling(int polling) {
this.polling = polling;
}
/**
* Gets the polling (in seconds) to reconnect
*
* @return the polling (in seconds) to reconnect
*/
public int getRetryPolling() {
return retryPolling;
}
/**
* Sets the polling (in seconds) to reconnect
*
* @param retryPolling the polling (in seconds to reconnect)
*/
public void setRetryPolling(int retryPolling) {
this.retryPolling = retryPolling;
}
/**
* Gets the ping interval (in seconds)
*
* @return the ping interval (in seconds)
*/
public int getPing() {
return ping;
}
/**
* Sets the ping interval (in seconds)
*
* @param ping the ping interval (in seconds)
*/
public void setPing(int ping) {
this.ping = ping;
}
}

View File

@@ -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.atlona.internal.pro3;
/**
* The {@link AtlonaPro3Binding} class defines common constants, which are
* used across the whole binding.
*
* @author Tim Roberts - Initial contribution
*/
class AtlonaPro3Constants {
// Properties
static final String PROPERTY_VERSION = "version";
static final String PROPERTY_TYPE = "type";
static final String GROUP_PRIMARY = "primary";
static final String GROUP_PORT = "port";
static final String GROUP_MIRROR = "mirror";
static final String GROUP_VOLUME = "volume";
// List of all Channel ids
static final String CHANNEL_POWER = "power";
static final String CHANNEL_PANELLOCK = "panellock";
static final String CHANNEL_IRENABLE = "irenable";
static final String CHANNEL_PRESETCMDS = "presetcmd";
static final String CHANNEL_MATRIXCMDS = "matrixcmd";
static final String CHANNEL_PORTPOWER = "portpower";
static final String CHANNEL_PORTOUTPUT = "portoutput";
static final String CHANNEL_PORTMIRROR = "portmirror";
static final String CHANNEL_PORTMIRRORENABLED = "portmirrorenabled";
static final String CHANNEL_VOLUME = "volume";
static final String CHANNEL_VOLUME_MUTE = "volumemute";
// static final String CHANNEL_RS232 = "rs232cmd";
static final String CONFIG_HOSTNAME = "hostname";
static final String CONFIG_OUTPUT = "output";
// Preset commands
static final String CMD_PRESETSAVE = "save";
static final String CMD_PRESETRECALL = "recall";
static final String CMD_PRESETCLEAR = "clear";
// Matrix commands
static final String CMD_MATRIXRESET = "resetmatrix";
static final String CMD_MATRIXRESETPORTS = "resetports";
static final String CMD_MATRIXPORTALL = "allports";
}

View File

@@ -0,0 +1,608 @@
/**
* 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.atlona.internal.pro3;
import java.io.IOException;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.openhab.binding.atlona.internal.AtlonaHandlerCallback;
import org.openhab.binding.atlona.internal.StatefulHandlerCallback;
import org.openhab.binding.atlona.internal.handler.AtlonaHandler;
import org.openhab.binding.atlona.internal.net.SocketChannelSession;
import org.openhab.binding.atlona.internal.net.SocketSession;
import org.openhab.core.library.types.DecimalType;
import org.openhab.core.library.types.OnOffType;
import org.openhab.core.library.types.StringType;
import org.openhab.core.thing.ChannelUID;
import org.openhab.core.thing.Thing;
import org.openhab.core.thing.ThingStatus;
import org.openhab.core.thing.ThingStatusDetail;
import org.openhab.core.types.Command;
import org.openhab.core.types.RefreshType;
import org.openhab.core.types.State;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The {@link org.openhab.binding.atlona.internal.pro3.AtlonaPro3Handler} is responsible for handling commands, which
* are sent to one of the channels.
*
* @author Tim Roberts - Initial contribution
*/
public class AtlonaPro3Handler extends AtlonaHandler<AtlonaPro3Capabilities> {
private final Logger logger = LoggerFactory.getLogger(AtlonaPro3Handler.class);
/**
* The {@link AtlonaPro3PortocolHandler} protocol handler
*/
private AtlonaPro3PortocolHandler atlonaHandler;
/**
* The {@link SocketSession} telnet session to the switch. Will be null if not connected.
*/
private SocketSession session;
/**
* The polling job to poll the actual state from the {@link #session}
*/
private ScheduledFuture<?> polling;
/**
* The retry connection event
*/
private ScheduledFuture<?> retryConnection;
/**
* The ping event
*/
private ScheduledFuture<?> ping;
// List of all the groups patterns we recognize
private static final Pattern GROUP_PRIMARY_PATTERN = Pattern.compile("^" + AtlonaPro3Constants.GROUP_PRIMARY + "$");
private static final Pattern GROUP_PORT_PATTERN = Pattern
.compile("^" + AtlonaPro3Constants.GROUP_PORT + "(\\d{1,2})$");
private static final Pattern GROUP_MIRROR_PATTERN = Pattern
.compile("^" + AtlonaPro3Constants.GROUP_MIRROR + "(\\d{1,2})$");
private static final Pattern GROUP_VOLUME_PATTERN = Pattern
.compile("^" + AtlonaPro3Constants.GROUP_VOLUME + "(\\d{1,2})$");
// List of preset commands we recognize
private static final Pattern CMD_PRESETSAVE = Pattern
.compile("^" + AtlonaPro3Constants.CMD_PRESETSAVE + "(\\d{1,2})$");
private static final Pattern CMD_PRESETRECALL = Pattern
.compile("^" + AtlonaPro3Constants.CMD_PRESETRECALL + "(\\d{1,2})$");
private static final Pattern CMD_PRESETCLEAR = Pattern
.compile("^" + AtlonaPro3Constants.CMD_PRESETCLEAR + "(\\d{1,2})$");
// List of matrix commands we recognize
private static final Pattern CMD_MATRIXRESET = Pattern.compile("^" + AtlonaPro3Constants.CMD_MATRIXRESET + "$");
private static final Pattern CMD_MATRIXRESETPORTS = Pattern
.compile("^" + AtlonaPro3Constants.CMD_MATRIXRESETPORTS + "$");
private static final Pattern CMD_MATRIXPORTALL = Pattern
.compile("^" + AtlonaPro3Constants.CMD_MATRIXPORTALL + "(\\d{1,2})$");
/**
* Constructs the handler from the {@link org.openhab.core.thing.Thing} with the number of power ports and
* audio ports the switch supports.
*
* @param thing a non-null {@link org.openhab.core.thing.Thing} the handler is for
* @param capabilities a non-null {@link org.openhab.binding.atlona.internal.pro3.AtlonaPro3Capabilities}
*/
public AtlonaPro3Handler(Thing thing, AtlonaPro3Capabilities capabilities) {
super(thing, capabilities);
if (thing == null) {
throw new IllegalArgumentException("thing cannot be null");
}
}
/**
* {@inheritDoc}
*
* Handles commands to specific channels. This implementation will offload much of its work to the
* {@link AtlonaPro3PortocolHandler}. Basically we validate the type of command for the channel then call the
* {@link AtlonaPro3PortocolHandler} to handle the actual protocol. Special use case is the {@link RefreshType}
* where we call {{@link #handleRefresh(String)} to handle a refresh of the specific channel (which in turn calls
* {@link AtlonaPro3PortocolHandler} to handle the actual refresh
*/
@Override
public void handleCommand(ChannelUID channelUID, Command command) {
if (command instanceof RefreshType) {
handleRefresh(channelUID);
return;
}
final String group = channelUID.getGroupId().toLowerCase();
final String id = channelUID.getIdWithoutGroup().toLowerCase();
Matcher m;
if ((m = GROUP_PRIMARY_PATTERN.matcher(group)).matches()) {
switch (id) {
case AtlonaPro3Constants.CHANNEL_POWER:
if (command instanceof OnOffType) {
final boolean makeOn = ((OnOffType) command) == OnOffType.ON;
atlonaHandler.setPower(makeOn);
} else {
logger.debug("Received a POWER channel command with a non OnOffType: {}", command);
}
break;
case AtlonaPro3Constants.CHANNEL_PANELLOCK:
if (command instanceof OnOffType) {
final boolean makeOn = ((OnOffType) command) == OnOffType.ON;
atlonaHandler.setPanelLock(makeOn);
} else {
logger.debug("Received a PANELLOCK channel command with a non OnOffType: {}", command);
}
break;
case AtlonaPro3Constants.CHANNEL_IRENABLE:
if (command instanceof OnOffType) {
final boolean makeOn = ((OnOffType) command) == OnOffType.ON;
atlonaHandler.setIrOn(makeOn);
} else {
logger.debug("Received a IRLOCK channel command with a non OnOffType: {}", command);
}
break;
case AtlonaPro3Constants.CHANNEL_MATRIXCMDS:
if (command instanceof StringType) {
final String matrixCmd = command.toString();
Matcher cmd;
try {
if ((cmd = CMD_MATRIXRESET.matcher(matrixCmd)).matches()) {
atlonaHandler.resetMatrix();
} else if ((cmd = CMD_MATRIXRESETPORTS.matcher(matrixCmd)).matches()) {
atlonaHandler.resetAllPorts();
} else if ((cmd = CMD_MATRIXPORTALL.matcher(matrixCmd)).matches()) {
if (cmd.groupCount() == 1) {
final int portNbr = Integer.parseInt(cmd.group(1));
atlonaHandler.setPortAll(portNbr);
} else {
logger.debug("Unknown matirx set port command: '{}'", matrixCmd);
}
} else {
logger.debug("Unknown matrix command: '{}'", cmd);
}
} catch (NumberFormatException e) {
logger.debug("Could not parse the port number from the command: '{}'", matrixCmd);
}
}
break;
case AtlonaPro3Constants.CHANNEL_PRESETCMDS:
if (command instanceof StringType) {
final String presetCmd = command.toString();
Matcher cmd;
try {
if ((cmd = CMD_PRESETSAVE.matcher(presetCmd)).matches()) {
if (cmd.groupCount() == 1) {
final int presetNbr = Integer.parseInt(cmd.group(1));
atlonaHandler.saveIoSettings(presetNbr);
} else {
logger.debug("Unknown preset save command: '{}'", presetCmd);
}
} else if ((cmd = CMD_PRESETRECALL.matcher(presetCmd)).matches()) {
if (cmd.groupCount() == 1) {
final int presetNbr = Integer.parseInt(cmd.group(1));
atlonaHandler.recallIoSettings(presetNbr);
} else {
logger.debug("Unknown preset recall command: '{}'", presetCmd);
}
} else if ((cmd = CMD_PRESETCLEAR.matcher(presetCmd)).matches()) {
if (cmd.groupCount() == 1) {
final int presetNbr = Integer.parseInt(cmd.group(1));
atlonaHandler.clearIoSettings(presetNbr);
} else {
logger.debug("Unknown preset clear command: '{}'", presetCmd);
}
} else {
logger.debug("Unknown preset command: '{}'", cmd);
}
} catch (NumberFormatException e) {
logger.debug("Could not parse the preset number from the command: '{}'", presetCmd);
}
}
break;
default:
logger.debug("Unknown/Unsupported Primary Channel: {}", channelUID.getAsString());
break;
}
} else if ((m = GROUP_PORT_PATTERN.matcher(group)).matches()) {
if (m.groupCount() == 1) {
try {
final int portNbr = Integer.parseInt(m.group(1));
switch (id) {
case AtlonaPro3Constants.CHANNEL_PORTOUTPUT:
if (command instanceof DecimalType) {
final int inpNbr = ((DecimalType) command).intValue();
atlonaHandler.setPortSwitch(inpNbr, portNbr);
} else {
logger.debug("Received a PORTOUTPUT channel command with a non DecimalType: {}",
command);
}
break;
case AtlonaPro3Constants.CHANNEL_PORTPOWER:
if (command instanceof OnOffType) {
final boolean makeOn = ((OnOffType) command) == OnOffType.ON;
atlonaHandler.setPortPower(portNbr, makeOn);
} else {
logger.debug("Received a PORTPOWER channel command with a non OnOffType: {}", command);
}
break;
default:
logger.debug("Unknown/Unsupported Port Channel: {}", channelUID.getAsString());
break;
}
} catch (NumberFormatException e) {
logger.debug("Bad Port Channel (can't parse the port nbr): {}", channelUID.getAsString());
}
}
} else if ((m = GROUP_MIRROR_PATTERN.matcher(group)).matches()) {
if (m.groupCount() == 1) {
try {
final int hdmiPortNbr = Integer.parseInt(m.group(1));
switch (id) {
case AtlonaPro3Constants.CHANNEL_PORTMIRROR:
if (command instanceof DecimalType) {
final int outPortNbr = ((DecimalType) command).intValue();
if (outPortNbr <= 0) {
atlonaHandler.removePortMirror(hdmiPortNbr);
} else {
atlonaHandler.setPortMirror(hdmiPortNbr, outPortNbr);
}
} else {
logger.debug("Received a PORTMIRROR channel command with a non DecimalType: {}",
command);
}
break;
case AtlonaPro3Constants.CHANNEL_PORTMIRRORENABLED:
if (command instanceof OnOffType) {
if (command == OnOffType.ON) {
final StatefulHandlerCallback callback = (StatefulHandlerCallback) atlonaHandler
.getCallback();
final State state = callback.getState(AtlonaPro3Constants.CHANNEL_PORTMIRROR);
int outPortNbr = 1;
if (state != null && state instanceof DecimalType) {
outPortNbr = ((DecimalType) state).intValue();
}
atlonaHandler.setPortMirror(hdmiPortNbr, outPortNbr);
} else {
atlonaHandler.removePortMirror(hdmiPortNbr);
}
} else {
logger.debug("Received a PORTMIRROR channel command with a non DecimalType: {}",
command);
}
break;
default:
logger.debug("Unknown/Unsupported Mirror Channel: {}", channelUID.getAsString());
break;
}
} catch (NumberFormatException e) {
logger.debug("Bad Mirror Channel (can't parse the port nbr): {}", channelUID.getAsString());
}
}
} else if ((m = GROUP_VOLUME_PATTERN.matcher(group)).matches()) {
if (m.groupCount() == 1) {
try {
final int portNbr = Integer.parseInt(m.group(1));
switch (id) {
case AtlonaPro3Constants.CHANNEL_VOLUME_MUTE:
if (command instanceof OnOffType) {
atlonaHandler.setVolumeMute(portNbr, ((OnOffType) command) == OnOffType.ON);
} else {
logger.debug("Received a VOLUME MUTE channel command with a non OnOffType: {}",
command);
}
break;
case AtlonaPro3Constants.CHANNEL_VOLUME:
if (command instanceof DecimalType) {
final double level = ((DecimalType) command).doubleValue();
atlonaHandler.setVolume(portNbr, level);
} else {
logger.debug("Received a VOLUME channel command with a non DecimalType: {}", command);
}
break;
default:
logger.debug("Unknown/Unsupported Volume Channel: {}", channelUID.getAsString());
break;
}
} catch (NumberFormatException e) {
logger.debug("Bad Volume Channel (can't parse the port nbr): {}", channelUID.getAsString());
}
}
} else {
logger.debug("Unknown/Unsupported Channel: {}", channelUID.getAsString());
}
}
/**
* Method that handles the {@link RefreshType} command specifically. Calls the {@link AtlonaPro3PortocolHandler} to
* handle the actual refresh based on the channel id.
*
* @param id a non-null, possibly empty channel id to refresh
*/
private void handleRefresh(ChannelUID channelUID) {
if (getThing().getStatus() != ThingStatus.ONLINE) {
return;
}
final String group = channelUID.getGroupId().toLowerCase();
final String id = channelUID.getIdWithoutGroup().toLowerCase();
final StatefulHandlerCallback callback = (StatefulHandlerCallback) atlonaHandler.getCallback();
Matcher m;
if ((m = GROUP_PRIMARY_PATTERN.matcher(group)).matches()) {
switch (id) {
case AtlonaPro3Constants.CHANNEL_POWER:
callback.removeState(AtlonaPro3Utilities.createChannelID(group, id));
atlonaHandler.refreshPower();
break;
default:
break;
}
} else if ((m = GROUP_PORT_PATTERN.matcher(group)).matches()) {
if (m.groupCount() == 1) {
try {
final int portNbr = Integer.parseInt(m.group(1));
callback.removeState(AtlonaPro3Utilities.createChannelID(group, portNbr, id));
switch (id) {
case AtlonaPro3Constants.CHANNEL_PORTOUTPUT:
atlonaHandler.refreshPortStatus(portNbr);
break;
case AtlonaPro3Constants.CHANNEL_PORTPOWER:
atlonaHandler.refreshPortPower(portNbr);
break;
default:
break;
}
} catch (NumberFormatException e) {
logger.debug("Bad Port Channel (can't parse the port nbr): {}", channelUID.getAsString());
}
}
} else if ((m = GROUP_MIRROR_PATTERN.matcher(group)).matches()) {
if (m.groupCount() == 1) {
try {
final int hdmiPortNbr = Integer.parseInt(m.group(1));
callback.removeState(AtlonaPro3Utilities.createChannelID(group, hdmiPortNbr, id));
atlonaHandler.refreshPortMirror(hdmiPortNbr);
} catch (NumberFormatException e) {
logger.debug("Bad Mirror Channel (can't parse the port nbr): {}", channelUID.getAsString());
}
}
} else if ((m = GROUP_VOLUME_PATTERN.matcher(group)).matches()) {
if (m.groupCount() == 1) {
try {
final int portNbr = Integer.parseInt(m.group(1));
callback.removeState(AtlonaPro3Utilities.createChannelID(group, portNbr, id));
switch (id) {
case AtlonaPro3Constants.CHANNEL_VOLUME_MUTE:
atlonaHandler.refreshVolumeMute(portNbr);
break;
case AtlonaPro3Constants.CHANNEL_VOLUME:
atlonaHandler.refreshVolumeStatus(portNbr);
break;
default:
break;
}
} catch (NumberFormatException e) {
logger.debug("Bad Volume Channel (can't parse the port nbr): {}", channelUID.getAsString());
}
}
}
}
/**
* {@inheritDoc}
*
* Initializes the handler. This initialization will read/validate the configuration, then will create the
* {@link SocketSession}, initialize the {@link AtlonaPro3PortocolHandler} and will attempt to connect to the switch
* (via {{@link #retryConnect()}.
*/
@Override
public void initialize() {
final AtlonaPro3Config config = getAtlonaConfig();
if (config == null) {
return;
}
if (config.getIpAddress() == null || config.getIpAddress().trim().length() == 0) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
"IP Address of Atlona Pro3 is missing from configuration");
return;
}
session = new SocketChannelSession(config.getIpAddress(), 23);
atlonaHandler = new AtlonaPro3PortocolHandler(session, config, getCapabilities(),
new StatefulHandlerCallback(new AtlonaHandlerCallback() {
@Override
public void stateChanged(String channelId, State state) {
updateState(channelId, state);
}
@Override
public void statusChanged(ThingStatus status, ThingStatusDetail detail, String msg) {
updateStatus(status, detail, msg);
if (status != ThingStatus.ONLINE) {
disconnect(true);
}
}
@Override
public void setProperty(String propertyName, String propertyValue) {
getThing().setProperty(propertyName, propertyValue);
}
}));
// Try initial connection in a scheduled task
this.scheduler.schedule(this::connect, 1, TimeUnit.SECONDS);
}
/**
* Attempts to connect to the switch. If successfully connect, the {@link AtlonaPro3PortocolHandler#login()} will be
* called to log into the switch (if needed). Once completed, a polling job will be created to poll the switch's
* actual state and a ping job to ping the server. If a connection cannot be established (or login failed), the
* connection attempt will be retried later (via {@link #retryConnect()})
*/
private void connect() {
String response = "Server is offline - will try to reconnect later";
try {
// clear listeners to avoid any 'old' listener from handling initial messages
session.clearListeners();
session.connect();
response = atlonaHandler.login();
if (response == null) {
final AtlonaPro3Config config = getAtlonaConfig();
if (config != null) {
polling = this.scheduler.scheduleWithFixedDelay(() -> {
final ThingStatus status = getThing().getStatus();
if (status == ThingStatus.ONLINE) {
if (session.isConnected()) {
atlonaHandler.refreshAll();
} else {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
"Atlona PRO3 has disconnected. Will try to reconnect later.");
}
} else if (status == ThingStatus.OFFLINE) {
disconnect(true);
}
}, config.getPolling(), config.getPolling(), TimeUnit.SECONDS);
ping = this.scheduler.scheduleWithFixedDelay(() -> {
final ThingStatus status = getThing().getStatus();
if (status == ThingStatus.ONLINE) {
if (session.isConnected()) {
atlonaHandler.ping();
}
}
}, config.getPing(), config.getPing(), TimeUnit.SECONDS);
updateStatus(ThingStatus.ONLINE);
return;
}
}
} catch (Exception e) {
// do nothing
}
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, response);
retryConnect();
}
/**
* Attempts to disconnect from the session and will optionally retry the connection attempt. The {@link #polling}
* will be cancelled, the {@link #ping} will be cancelled and both set to null then the {@link #session} will be
* disconnected.
*
* @param retryConnection true to retry connection attempts after the disconnect
*/
private void disconnect(boolean retryConnection) {
// Cancel polling
if (polling != null) {
polling.cancel(true);
polling = null;
}
// Cancel ping
if (ping != null) {
ping.cancel(true);
ping = null;
}
try {
session.disconnect();
} catch (IOException e) {
// ignore - we don't care
}
if (retryConnection) {
retryConnect();
}
}
/**
* Retries the connection attempt - schedules a job in {@link AtlonaPro3Config#getRetryPolling()} seconds to call
* the
* {@link #connect()} method. If a retry attempt is pending, the request is ignored.
*/
private void retryConnect() {
if (retryConnection == null) {
final AtlonaPro3Config config = getAtlonaConfig();
if (config != null) {
logger.info("Will try to reconnect in {} seconds", config.getRetryPolling());
retryConnection = this.scheduler.schedule(() -> {
retryConnection = null;
connect();
}, config.getRetryPolling(), TimeUnit.SECONDS);
}
} else {
logger.debug("RetryConnection called when a retry connection is pending - ignoring request");
}
}
/**
* Simple gets the {@link AtlonaPro3Config} from the {@link Thing} and will set the status to offline if not found.
*
* @return a possible null {@link AtlonaPro3Config}
*/
private AtlonaPro3Config getAtlonaConfig() {
final AtlonaPro3Config config = getThing().getConfiguration().as(AtlonaPro3Config.class);
if (config == null) {
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR);
}
return config;
}
/**
* {@inheritDoc}
*
* Disposes of the handler. Will simply call {@link #disconnect(boolean)} to disconnect and NOT retry the
* connection
*/
@Override
public void dispose() {
disconnect(false);
}
}

View File

@@ -0,0 +1,42 @@
/**
* 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.atlona.internal.pro3;
/**
*
* @author Tim Roberts - Initial contribution
*/
public class AtlonaPro3Utilities {
/**
* Helper method to create a channel id from a group with no port number attached
*
* @param group a group name
* @param channelId the channel id
* @return group + "#" + channelId
*/
public static String createChannelID(String group, String channelId) {
return group + "#" + channelId;
}
/**
* Helper method to create a channel id from a group, port number and channel id
*
* @param group the group name
* @param portNbr the port number
* @param channelId the channel id
* @return group + portNbr + "#" + channelId
*/
public static String createChannelID(String group, int portNbr, String channelId) {
return group + portNbr + "#" + channelId;
}
}

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<binding:binding id="atlona" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
<name>Atlona Products</name>
<description>Binding for Atlona PRO3 HDBaseT Matrix switches.</description>
<author>Tim Roberts</author>
</binding:binding>

View File

@@ -0,0 +1,613 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="atlona"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="https://openhab.org/schemas/thing-description/v1.0.0"
xsi:schemaLocation="https://openhab.org/schemas/thing-description/v1.0.0 https://openhab.org/schemas/thing-description-1.0.0.xsd">
<!-- AT-UHD-PRO3-44M -->
<thing-type id="pro3-44m">
<label>Atlona Pro3 4x4 HDBaseT Matrix</label>
<description>Atlona Pro3 4x4 HDBaseT Matrix (Model AT-UHD-PRO3-44M)</description>
<channel-groups>
<channel-group id="primary" typeId="primarygroup"/>
<channel-group id="port1" typeId="portgroup">
<label>Port 1</label>
<description>Output Port 1 Channels</description>
</channel-group>
<channel-group id="port2" typeId="portgroup">
<label>Port 2</label>
<description>Output Port 2 Channels</description>
</channel-group>
<channel-group id="port3" typeId="portgroup">
<label>Port 3</label>
<description>Output Port 3 Channels</description>
</channel-group>
<channel-group id="port4" typeId="portgroup">
<label>Port 4</label>
<description>Output Port 4 Channels</description>
</channel-group>
<channel-group id="port5" typeId="portgroup">
<label>Port 5</label>
<description>Output Port 5 Channels</description>
</channel-group>
<channel-group id="mirror5" typeId="mirrorgroup">
<label>HDMI Port 5</label>
<description>HDMI Port 5 Mirroring Channels</description>
</channel-group>
<channel-group id="volume1" typeId="volumegroup">
<label>Volume 1</label>
<description>Volume 1 channels</description>
</channel-group>
<channel-group id="volume2" typeId="volumegroup">
<label>Volume 2</label>
<description>Volume 2 channels</description>
</channel-group>
<channel-group id="volume3" typeId="volumegroup">
<label>Volume 3</label>
<description>Volume 3 channels</description>
</channel-group>
</channel-groups>
<config-description>
<parameter name="ipAddress" type="text">
<context>network-address</context>
<label>IP or Host Name</label>
<description>IP or Host name of Atlona Pro3 44M Switch</description>
<required>true</required>
</parameter>
<parameter name="userName" type="text">
<label>UserName to Login With</label>
<description>User Name to login with if Telnet Login is on</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Password to Login With</label>
<description>Password to login with if Telnet Login is on</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="polling" type="integer">
<label>Polling Interval</label>
<description>Interval (in seconds) to poll the actual state of the Matrix</description>
<default>600</default>
<advanced>true</advanced>
</parameter>
<parameter name="ping" type="integer">
<label>Pint Interval</label>
<description>Ping Interval (in seconds) to keep the connection alive</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="retryPolling" type="integer">
<label>Polling Interval to Try to Reconnect</label>
<description>Interval (in seconds) to try to (re)connect to the matrix</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<!-- AT-UHD-PRO3-66M -->
<thing-type id="pro3-66m">
<label>Atlona Pro3 6x6 HDBaseT Matrix</label>
<description>Atlona Pro3 6x6 HDBaseT Matrix (Model AT-UHD-PRO3-66M)</description>
<channel-groups>
<channel-group id="primary" typeId="primarygroup"/>
<channel-group id="port1" typeId="portgroup">
<label>Port 1</label>
<description>Output Port 1 Channels</description>
</channel-group>
<channel-group id="port2" typeId="portgroup">
<label>Port 2</label>
<description>Output Port 2 Channels</description>
</channel-group>
<channel-group id="port3" typeId="portgroup">
<label>Port 3</label>
<description>Output Port 3 Channels</description>
</channel-group>
<channel-group id="port4" typeId="portgroup">
<label>Port 4</label>
<description>Output Port 4 Channels</description>
</channel-group>
<channel-group id="port5" typeId="portgroup">
<label>Port 5</label>
<description>Output Port 5 Channels</description>
</channel-group>
<channel-group id="port6" typeId="portgroup">
<label>Port 6</label>
<description>Output Port 6 Channels</description>
</channel-group>
<channel-group id="port7" typeId="portgroup">
<label>Port 7</label>
<description>Output Port 7 Channels</description>
</channel-group>
<channel-group id="port8" typeId="portgroup">
<label>Port 8</label>
<description>Output Port 8 Channels</description>
</channel-group>
<channel-group id="mirror6" typeId="mirrorgroup">
<label>HDMI Port 6</label>
<description>HDMI Port 6 Mirroring Channels</description>
</channel-group>
<channel-group id="mirror8" typeId="mirrorgroup">
<label>HDMI Port 8</label>
<description>HDMI Port 8 Mirroring Channels</description>
</channel-group>
<channel-group id="volume1" typeId="volumegroup">
<label>Volume 1</label>
<description>Volume 1 channels</description>
</channel-group>
<channel-group id="volume2" typeId="volumegroup">
<label>Volume 2</label>
<description>Volume 2 channels</description>
</channel-group>
<channel-group id="volume3" typeId="volumegroup">
<label>Volume 3</label>
<description>Volume 3 channels</description>
</channel-group>
<channel-group id="volume4" typeId="volumegroup">
<label>Volume 4</label>
<description>Volume 4 channels</description>
</channel-group>
</channel-groups>
<config-description>
<parameter name="ipAddress" type="text">
<context>network-address</context>
<label>IP or Host Name</label>
<description>IP or Host name of Atlona Pro3 66M Switch</description>
<required>true</required>
</parameter>
<parameter name="userName" type="text">
<label>UserName to Login With</label>
<description>User Name to login with if Telnet Login is on</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Password to Login With</label>
<description>Password to login with if Telnet Login is on</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="polling" type="integer">
<label>Polling Interval</label>
<description>Interval (in seconds) to poll the actual state of the Matrix</description>
<default>600</default>
<advanced>true</advanced>
</parameter>
<parameter name="ping" type="integer">
<label>Pint Interval</label>
<description>Ping Interval (in seconds) to keep the connection alive</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="retryPolling" type="integer">
<label>Polling Interval to Try to Reconnect</label>
<description>Interval (in seconds) to try to (re)connect to the matrix</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<!-- AT-UHD-PRO3-88M -->
<thing-type id="pro3-88m">
<label>Atlona Pro3 8x8 HDBaseT Matrix</label>
<description>Atlona Pro3 8x8 HDBaseT Matrix (Model AT-UHD-PRO3-66M)</description>
<channel-groups>
<channel-group id="primary" typeId="primarygroup"/>
<channel-group id="port1" typeId="portgroup">
<label>Port 1</label>
<description>Output Port 1 Channels</description>
</channel-group>
<channel-group id="port2" typeId="portgroup">
<label>Port 2</label>
<description>Output Port 2 Channels</description>
</channel-group>
<channel-group id="port3" typeId="portgroup">
<label>Port 3</label>
<description>Output Port 3 Channels</description>
</channel-group>
<channel-group id="port4" typeId="portgroup">
<label>Port 4</label>
<description>Output Port 4 Channels</description>
</channel-group>
<channel-group id="port5" typeId="portgroup">
<label>Port 5</label>
<description>Output Port 5 Channels</description>
</channel-group>
<channel-group id="port6" typeId="portgroup">
<label>Port 6</label>
<description>Output Port 6 Channels</description>
</channel-group>
<channel-group id="port7" typeId="portgroup">
<label>Port 7</label>
<description>Output Port 7 Channels</description>
</channel-group>
<channel-group id="port8" typeId="portgroup">
<label>Port 8</label>
<description>Output Port 8 Channels</description>
</channel-group>
<channel-group id="port9" typeId="portgroup">
<label>Port 9</label>
<description>Output Port 9 Channels</description>
</channel-group>
<channel-group id="port10" typeId="portgroup">
<label>Port 10</label>
<description>Output Port 10 Channels</description>
</channel-group>
<channel-group id="mirror8" typeId="mirrorgroup">
<label>HDMI Port 8</label>
<description>HDMI Port 8 Mirroring Channels</description>
</channel-group>
<channel-group id="mirror10" typeId="mirrorgroup">
<label>HDMI Port 10</label>
<description>HDMI Port 10 Mirroring Channels</description>
</channel-group>
<channel-group id="volume1" typeId="volumegroup">
<label>Volume 1</label>
<description>Volume 1 channels</description>
</channel-group>
<channel-group id="volume2" typeId="volumegroup">
<label>Volume 2</label>
<description>Volume 2 channels</description>
</channel-group>
<channel-group id="volume3" typeId="volumegroup">
<label>Volume 3</label>
<description>Volume 3 channels</description>
</channel-group>
<channel-group id="volume4" typeId="volumegroup">
<label>Volume 4</label>
<description>Volume 4 channels</description>
</channel-group>
<channel-group id="volume5" typeId="volumegroup">
<label>Volume 5</label>
<description>Volume 5 channels</description>
</channel-group>
<channel-group id="volume6" typeId="volumegroup">
<label>Volume 6</label>
<description>Volume 6 channels</description>
</channel-group>
</channel-groups>
<config-description>
<parameter name="ipAddress" type="text" required="true">
<context>network-address</context>
<label>IP or Host Name</label>
<description>IP or Host name of Atlona Switch</description>
<default></default>
</parameter>
<parameter name="userName" type="text">
<label>UserName</label>
<description>User Name to use (if Telnet Login is ON)</description>
<default></default>
<advanced>true</advanced>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Password to Login With</label>
<description>Password to use (if Telnet Login is ON)</description>
<default></default>
<advanced>true</advanced>
</parameter>
<parameter name="polling" type="integer">
<label>Polling Interval</label>
<description>Interval (in seconds) to poll the actual state</description>
<default>600</default>
<advanced>true</advanced>
</parameter>
<parameter name="ping" type="integer">
<label>Pint Interval</label>
<description>Ping Interval (in seconds) to keep the connection alive</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="retryPolling" type="integer">
<label>Polling Interval to Try to Reconnect</label>
<description>Interval (in seconds) to try to (re)connect</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<!-- AT-UHD-PRO3-1616M -->
<thing-type id="pro3-1616m">
<label>Atlona Pro3 16x16 HDBaseT Matrix</label>
<description>Atlona Pro3 16x16 HDBaseT Matrix (Model AT-UHD-PRO3-1616M)</description>
<channel-groups>
<channel-group id="primary" typeId="primarygroup"/>
<channel-group id="port1" typeId="portgroup">
<label>Port 1</label>
<description>Output Port 1 Channels</description>
</channel-group>
<channel-group id="port2" typeId="portgroup">
<label>Port 2</label>
<description>Output Port 2 Channels</description>
</channel-group>
<channel-group id="port3" typeId="portgroup">
<label>Port 3</label>
<description>Output Port 3 Channels</description>
</channel-group>
<channel-group id="port4" typeId="portgroup">
<label>Port 4</label>
<description>Output Port 4 Channels</description>
</channel-group>
<channel-group id="port5" typeId="portgroup">
<label>Port 5</label>
<description>Output Port 5 Channels</description>
</channel-group>
<channel-group id="port6" typeId="portgroup">
<label>Port 6</label>
<description>Output Port 6 Channels</description>
</channel-group>
<channel-group id="port7" typeId="portgroup">
<label>Port 7</label>
<description>Output Port 7 Channels</description>
</channel-group>
<channel-group id="port8" typeId="portgroup">
<label>Port 8</label>
<description>Output Port 8 Channels</description>
</channel-group>
<channel-group id="port9" typeId="portgroup">
<label>Port 9</label>
<description>Output Port 9 Channels</description>
</channel-group>
<channel-group id="port10" typeId="portgroup">
<label>Port 10</label>
<description>Output Port 10 Channels</description>
</channel-group>
<channel-group id="port11" typeId="portgroup">
<label>Port 11</label>
<description>Output Port 11 Channels</description>
</channel-group>
<channel-group id="port12" typeId="portgroup">
<label>Port 12</label>
<description>Output Port 12 Channels</description>
</channel-group>
<channel-group id="port13" typeId="portgroup">
<label>Port 13</label>
<description>Output Port 13 Channels</description>
</channel-group>
<channel-group id="port14" typeId="portgroup">
<label>Port 14</label>
<description>Output Port 14 Channels</description>
</channel-group>
<channel-group id="port15" typeId="portgroup">
<label>Port 15</label>
<description>Output Port 15 Channels</description>
</channel-group>
<channel-group id="port16" typeId="portgroup">
<label>Port 16</label>
<description>Output Port 16 Channels</description>
</channel-group>
<channel-group id="port17" typeId="portgroup">
<label>Port 17</label>
<description>Output Port 17 Channels</description>
</channel-group>
<channel-group id="port18" typeId="portgroup">
<label>Port 18</label>
<description>Output Port 18 Channels</description>
</channel-group>
<channel-group id="port19" typeId="portgroup">
<label>Port 19</label>
<description>Output Port 19 Channels</description>
</channel-group>
<channel-group id="port20" typeId="portgroup">
<label>Port 20</label>
<description>Output Port 20 Channels</description>
</channel-group>
<channel-group id="mirror17" typeId="mirrorgroup">
<label>HDMI Port 17</label>
<description>HDMI Port 17 Mirroring Channels</description>
</channel-group>
<channel-group id="mirror18" typeId="mirrorgroup">
<label>HDMI Port 18</label>
<description>HDMI Port 18 Mirroring Channels</description>
</channel-group>
<channel-group id="mirror19" typeId="mirrorgroup">
<label>HDMI Port 19</label>
<description>HDMI Port 19 Mirroring Channels</description>
</channel-group>
<channel-group id="mirror20" typeId="mirrorgroup">
<label>HDMI Port 20</label>
<description>HDMI Port 20 Mirroring Channels</description>
</channel-group>
<channel-group id="volume1" typeId="volumegroup">
<label>Volume 1</label>
<description>Volume 1 channels</description>
</channel-group>
<channel-group id="volume2" typeId="volumegroup">
<label>Volume 2</label>
<description>Volume 2 channels</description>
</channel-group>
<channel-group id="volume3" typeId="volumegroup">
<label>Volume 3</label>
<description>Volume 3 channels</description>
</channel-group>
<channel-group id="volume4" typeId="volumegroup">
<label>Volume 4</label>
<description>Volume 4 channels</description>
</channel-group>
<channel-group id="volume5" typeId="volumegroup">
<label>Volume 5</label>
<description>Volume 5 channels</description>
</channel-group>
<channel-group id="volume6" typeId="volumegroup">
<label>Volume 6</label>
<description>Volume 6 channels</description>
</channel-group>
<channel-group id="volume7" typeId="volumegroup">
<label>Volume 7</label>
<description>Volume 7 channels</description>
</channel-group>
<channel-group id="volume8" typeId="volumegroup">
<label>Volume 8</label>
<description>Volume 8 channels</description>
</channel-group>
<channel-group id="volume9" typeId="volumegroup">
<label>Volume 9</label>
<description>Volume 9 channels</description>
</channel-group>
<channel-group id="volume10" typeId="volumegroup">
<label>Volume 10</label>
<description>Volume 10 channels</description>
</channel-group>
<channel-group id="volume11" typeId="volumegroup">
<label>Volume 11</label>
<description>Volume 11 channels</description>
</channel-group>
<channel-group id="volume12" typeId="volumegroup">
<label>Volume 12</label>
<description>Volume 12 channels</description>
</channel-group>
</channel-groups>
<config-description>
<parameter name="ipAddress" type="text">
<context>network-address</context>
<label>IP or Host Name</label>
<description>IP or Host name of Atlona Pro3 1616M Switch</description>
<required>true</required>
</parameter>
<parameter name="userName" type="text">
<label>UserName to Login With</label>
<description>User Name to login with if Telnet Login is on</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="password" type="text">
<context>password</context>
<label>Password to Login With</label>
<description>Password to login with if Telnet Login is on</description>
<required>false</required>
<advanced>true</advanced>
</parameter>
<parameter name="polling" type="integer">
<label>Polling Interval</label>
<description>Interval (in seconds) to poll the actual state of the Matrix</description>
<default>600</default>
<advanced>true</advanced>
</parameter>
<parameter name="ping" type="integer">
<label>Pint Interval</label>
<description>Ping Interval (in seconds) to keep the connection alive</description>
<default>30</default>
<advanced>true</advanced>
</parameter>
<parameter name="retryPolling" type="integer">
<label>Polling Interval to Try to Reconnect</label>
<description>Interval (in seconds) to try to (re)connect to the matrix</description>
<default>10</default>
<advanced>true</advanced>
</parameter>
</config-description>
</thing-type>
<channel-group-type id="primarygroup">
<label>Primary Channels</label>
<description>Primary Channels</description>
<channels>
<channel id="power" typeId="power"/>
<channel id="panellock" typeId="panellock"/>
<channel id="irenable" typeId="irenable"/>
<channel id="presetcmd" typeId="presetcmd"/>
<channel id="matrixcmd" typeId="matrixcmd"/>
</channels>
</channel-group-type>
<channel-group-type id="portgroup">
<label>Port</label>
<description>Output Port Channels</description>
<channels>
<channel id="portpower" typeId="portpower"/>
<channel id="portoutput" typeId="portoutput"/>
</channels>
</channel-group-type>
<channel-group-type id="mirrorgroup">
<label>HDMI Port</label>
<description>HDMI Port Mirroring Channels</description>
<channels>
<channel id="portmirror" typeId="portmirror"/>
<channel id="portmirrorenabled" typeId="portmirrorenabled"/>
</channels>
</channel-group-type>
<channel-group-type id="volumegroup">
<label>Volume</label>
<description>Volume channels</description>
<channels>
<channel id="volume" typeId="volume"/>
<channel id="volumemute" typeId="volumemute"/>
</channels>
</channel-group-type>
<!-- Port Channel Type -->
<channel-type id="power">
<item-type>Switch</item-type>
<label>Power</label>
<description>Whether the matrix is on or not</description>
</channel-type>
<channel-type id="panellock" advanced="true">
<item-type>Switch</item-type>
<label>Panel Lock</label>
<description>Whether the front panel buttons are locked or not</description>
</channel-type>
<channel-type id="irenable" advanced="true">
<item-type>Switch</item-type>
<label>IR Enable</label>
<description>Enables or Disables IR</description>
</channel-type>
<channel-type id="presetcmd" advanced="true">
<item-type>String</item-type>
<label>Preset Command</label>
<description>Send a preset command ("saveX", "recallX", "clearX")</description>
</channel-type>
<channel-type id="matrixcmd" advanced="true">
<item-type>String</item-type>
<label>Matrix Command</label>
<description>Send a matrix command ("resetmatrix", "resetports", "allportsX")</description>
</channel-type>
<channel-type id="portpower" advanced="true">
<item-type>Switch</item-type>
<label>Output Port Power</label>
<description>Turns on/off the output port</description>
</channel-type>
<channel-type id="portoutput">
<item-type>Number</item-type>
<label>Output Port</label>
<description>Sets the output port to the input port</description>
</channel-type>
<channel-type id="portmirror">
<item-type>Number</item-type>
<label>Mirror Port</label>
<description>Sets the port to be mirrored on the output port</description>
</channel-type>
<channel-type id="portmirrorenabled">
<item-type>Switch</item-type>
<label>Mirror Enabled</label>
<description>Whether the HDMI port mirroring is enabled or not</description>
</channel-type>
<channel-type id="volume">
<item-type>Number</item-type>
<label>Output Volume</label>
<description>Sets the volume (in db) of the output port (default: -40db with range from -79db to 15db)</description>
<state min="-79" max="15"/>
</channel-type>
<channel-type id="volumemute">
<item-type>Switch</item-type>
<label>Mute</label>
<description>Sets the output to muted or not</description>
</channel-type>
</thing:thing-descriptions>