mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[androidtv] AndroidTV Binding initial contribution (#14282)
Signed-off-by: Ben Rosenblum <rosenblumb@gmail.com>
This commit is contained in:
parent
5a1daa252e
commit
46039efd0a
@ -24,6 +24,7 @@
|
||||
/bundles/org.openhab.binding.ambientweather/ @mhilbush
|
||||
/bundles/org.openhab.binding.amplipi/ @kaikreuzer
|
||||
/bundles/org.openhab.binding.androiddebugbridge/ @GiviMAD
|
||||
/bundles/org.openhab.binding.androidtv/ @morph166955
|
||||
/bundles/org.openhab.binding.anel/ @paphko
|
||||
/bundles/org.openhab.binding.anthem/ @mhilbush
|
||||
/bundles/org.openhab.binding.astro/ @gerrieg
|
||||
|
@ -111,6 +111,11 @@
|
||||
<artifactId>org.openhab.binding.androiddebugbridge</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.androidtv</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openhab.addons.bundles</groupId>
|
||||
<artifactId>org.openhab.binding.anel</artifactId>
|
||||
|
13
bundles/org.openhab.binding.androidtv/NOTICE
Normal file
13
bundles/org.openhab.binding.androidtv/NOTICE
Normal 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
|
484
bundles/org.openhab.binding.androidtv/README.md
Normal file
484
bundles/org.openhab.binding.androidtv/README.md
Normal file
@ -0,0 +1,484 @@
|
||||
# AndroidTV Binding
|
||||
|
||||
This binding is designed to emulate different protocols to interact with the AndroidTV platform.
|
||||
Currently it emulates both the Google Video App to interact with a variety of AndroidTVs for purposes of remote control.
|
||||
It also currently emulates the Nvidia ShieldTV Android App to interact with an Nvidia ShieldTV for purposes of remote control.
|
||||
|
||||
## Supported Things
|
||||
|
||||
This binding supports two thing types:
|
||||
|
||||
- **googletv** - An AndroidTV running Google Video
|
||||
- **shieldtv** - An Nvidia ShieldTV
|
||||
|
||||
## Discovery
|
||||
|
||||
Both GoogleTVs and ShieldTVs should be added automatically to the inbox through the mDNS discovery process.
|
||||
|
||||
In the case of the ShieldTV, openHAB will likely create an inbox entry for both a GoogleTV and a ShieldTV device.
|
||||
Only the ShieldTV device should be configured, the GoogleTV can be ignored.
|
||||
There is no benefit to configuring two things for a ShieldTV device.
|
||||
This could cause undesired effects.
|
||||
|
||||
## Binding Configuration
|
||||
|
||||
This binding does not require any special configuration files.
|
||||
|
||||
This binding does require a PIN login process (documented below) upon first connection.
|
||||
|
||||
This binding requires GoogleTV to be installed on the device (https://play.google.com/store/apps/details?id=com.google.android.videos)
|
||||
|
||||
## Thing Configuration
|
||||
|
||||
There are three required fields to connect successfully to a ShieldTV.
|
||||
|
||||
| Name | Type | Description | Default | Required | Advanced |
|
||||
|------------------|---------|---------------------------------------|---------|----------|----------|
|
||||
| ipAddress | text | IP address of the device | N/A | yes | no |
|
||||
| keystore | text | Location of the Java Keystore | N/A | no | no |
|
||||
| keystorePassword | text | Password of the Java Keystore | N/A | no | no |
|
||||
|
||||
```java
|
||||
Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ]
|
||||
Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ]
|
||||
```
|
||||
|
||||
## Channels
|
||||
|
||||
| Channel | Type | Description | GoogleTV | ShieldTV |
|
||||
|------------|--------|-----------------------------|----------|----------|
|
||||
| keyboard | String | Keyboard Data Entry | RW | RW |
|
||||
| keypress | String | Manual Key Press Entry | RW | RW |
|
||||
| keycode | String | Direct KEYCODE Entry | RW | RW |
|
||||
| pincode | String | PIN Code Entry | RW | RW |
|
||||
| app | String | App Control | RO | RW |
|
||||
| appname | String | App Name | N/A | RW |
|
||||
| appurl | String | App URL | N/A | RW |
|
||||
| player | Player | Player Control | RW | RW |
|
||||
| power | Switch | Power Control | RW | RW |
|
||||
| volume | Dimmer | Volume Control | RO | RO |
|
||||
| mute | Switch | Mute Control | RW | RW |
|
||||
|
||||
|
||||
```java
|
||||
String ShieldTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:shieldtv:livingroom:keyboard" }
|
||||
String ShieldTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:shieldtv:livingroom:keypress" }
|
||||
String ShieldTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:shieldtv:livingroom:keycode" }
|
||||
String ShieldTV_PINCODE "PINCODE [%s]" { channel = "androidtv:shieldtv:livingroom:pincode" }
|
||||
String ShieldTV_APP "APP [%s]" { channel = "androidtv:shieldtv:livingroom:app" }
|
||||
String ShieldTV_APPNAME "APPNAME [%s]" { channel = "androidtv:shieldtv:livingroom:appname" }
|
||||
String ShieldTV_APPURL "APPURL [%s]" { channel = "androidtv:shieldtv:livingroom:appurl" }
|
||||
Player ShieldTV_PLAYER "PLAYER [%s]" { channel = "androidtv:shieldtv:livingroom:player" }
|
||||
Switch ShieldTV_POWER "POWER [%s]" { channel = "androidtv:shieldtv:livingroom:power" }
|
||||
Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" }
|
||||
Switch ShieldTV_MUTE "MUTE [%s]" { channel = "androidtv:shieldtv:livingroom:mute" }
|
||||
|
||||
String GoogleTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:googletv:theater:keyboard" }
|
||||
String GoogleTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" }
|
||||
String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" }
|
||||
String GoogleTV_PINCODE "PINCODE [%s]" { channel = "androidtv:googletv:theater:pincode" }
|
||||
String GoogleTV_APP "APP [%s]" { channel = "androidtv:googletv:theater:app" }
|
||||
Player GoogleTV_PLAYER "PLAYER [%s]" { channel = "androidtv:googletv:theater:player" }
|
||||
Switch GoogleTV_POWER "POWER [%s]" { channel = "androidtv:googletv:theater:power" }
|
||||
Dimmer GoogleTV_VOLUME "VOLUME [%s]" { channel = "androidtv:googletv:theater:volume" }
|
||||
Switch GoogleTV_MUTE "MUTE [%s]" { channel = "androidtv:googletv:theater:mute" }
|
||||
```
|
||||
|
||||
KEYPRESS will accept the following commands as strings (case sensitive):
|
||||
|
||||
- KEY_UP
|
||||
- KEY_DOWN
|
||||
- KEY_RIGHT
|
||||
- KEY_LEFT
|
||||
- KEY_ENTER
|
||||
- KEY_HOME
|
||||
- KEY_BACK
|
||||
- KEY_MENU
|
||||
- KEY_PLAY
|
||||
- KEY_PAUSE
|
||||
- KEY_PLAYPAUSE
|
||||
- KEY_STOP
|
||||
- KEY_NEXT
|
||||
- KEY_PREVIOUS
|
||||
- KEY_REWIND
|
||||
- KEY_FORWARD
|
||||
- KEY_POWER
|
||||
- KEY_GOOGLE
|
||||
- KEY_VOLUP
|
||||
- KEY_VOLDOWN
|
||||
- KEY_MUTE
|
||||
- KEY_SUBMIT
|
||||
|
||||
The list above causes an instantanious "press and release" of each button.
|
||||
If you would like to manually control the press and release of each you may append _PRESS and _RELEASE to the end of each.
|
||||
(e.g. KEY_FORWARD_PRESS or KEY_FORWARD_RELEASE)
|
||||
|
||||
You may also send an ASCII character as a single letter to simulate a key entry (e.g KEY_A, KEY_1, KEY_z).
|
||||
Use KEY_SUBMIT when full text entry is complete to tell the shield to process the line.
|
||||
KEY_SUBMIT is automatically sent by KEYBOARD when a command is sent to the channel.
|
||||
|
||||
APP will display the currently active app as presented by the AndroidTV.
|
||||
You may also send it a command of the app package name (e.g. com.google.android.youtube.tv) to start/change-to that app.
|
||||
|
||||
KEYCODE values are listed at the bottom of this README.
|
||||
NOTE: Not all KEYCODES work on all devices. Keycodes above 255 have not been tested.
|
||||
|
||||
## Pin Code Process
|
||||
|
||||
For the AndroidTV to be successfully accessed an on-screen PIN authentication is required on the first connection.
|
||||
|
||||
To begin the PIN process, send the text "REQUEST" to the pincode channel while watching your AndroidTV.
|
||||
|
||||
A 6 digit PIN should be displayed on the screen.
|
||||
|
||||
To complete the PIN process, send the PIN displayed to the pincode channel.
|
||||
|
||||
The display should return back to where it was originally.
|
||||
|
||||
If you are on a ShieldTV you must run that process a second time to authenticate the GoogleTV protocol stack.
|
||||
|
||||
This completes the PIN process.
|
||||
|
||||
Upon reconnection (either from reconfiguration or a restart of OpenHAB), you should now see a message of "Login Successful" in openhab.log
|
||||
|
||||
## Full Example
|
||||
|
||||
```java
|
||||
Thing androidtv:shieldtv:livingroom [ ipAddress="192.168.1.2" ]
|
||||
Thing androidtv:googletv:theater [ ipAddress="192.168.1.3" ]
|
||||
```
|
||||
|
||||
```java
|
||||
String ShieldTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:shieldtv:livingroom:keyboard" }
|
||||
String ShieldTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:shieldtv:livingroom:keypress" }
|
||||
String ShieldTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:shieldtv:livingroom:keycode" }
|
||||
String ShieldTV_PINCODE "PINCODE [%s]" { channel = "androidtv:shieldtv:livingroom:pincode" }
|
||||
String ShieldTV_APP "APP [%s]" { channel = "androidtv:shieldtv:livingroom:app" }
|
||||
String ShieldTV_APPNAME "APPNAME [%s]" { channel = "androidtv:shieldtv:livingroom:appname" }
|
||||
String ShieldTV_APPURL "APPURL [%s]" { channel = "androidtv:shieldtv:livingroom:appurl" }
|
||||
Player ShieldTV_PLAYER "PLAYER [%s]" { channel = "androidtv:shieldtv:livingroom:player" }
|
||||
Switch ShieldTV_POWER "POWER [%s]" { channel = "androidtv:shieldtv:livingroom:power" }
|
||||
Dimmer ShieldTV_VOLUME "VOLUME [%s]" { channel = "androidtv:shieldtv:livingroom:volume" }
|
||||
Switch ShieldTV_MUTE "MUTE [%s]" { channel = "androidtv:shieldtv:livingroom:mute" }
|
||||
|
||||
String GoogleTV_KEYBOARD "KEYBOARD [%s]" { channel = "androidtv:googletv:theater:keyboard" }
|
||||
String GoogleTV_KEYPRESS "KEYPRESS [%s]" { channel = "androidtv:googletv:theater:keypress" }
|
||||
String GoogleTV_KEYCODE "KEYCODE [%s]" { channel = "androidtv:googletv:theater:keycode" }
|
||||
String GoogleTV_PINCODE "PINCODE [%s]" { channel = "androidtv:googletv:theater:pincode" }
|
||||
String GoogleTV_APP "APP [%s]" { channel = "androidtv:googletv:theater:app" }
|
||||
Player GoogleTV_PLAYER "PLAYER [%s]" { channel = "androidtv:googletv:theater:player" }
|
||||
Switch GoogleTV_POWER "POWER [%s]" { channel = "androidtv:googletv:theater:power" }
|
||||
Dimmer GoogleTV_VOLUME "VOLUME [%s]" { channel = "androidtv:googletv:theater:volume" }
|
||||
Switch GoogleTV_MUTE "MUTE [%s]" { channel = "androidtv:googletv:theater:mute" }
|
||||
```
|
||||
|
||||
## Google Keycodes
|
||||
|
||||
| CODE | BUTTON |
|
||||
|------|--------|
|
||||
| 0 | KEYCODE_UNKNOWN |
|
||||
| 1 | KEYCODE_SOFT_LEFT |
|
||||
| 2 | KEYCODE_SOFT_RIGHT |
|
||||
| 3 | KEYCODE_HOME |
|
||||
| 4 | KEYCODE_BACK |
|
||||
| 5 | KEYCODE_CALL |
|
||||
| 6 | KEYCODE_ENDCALL |
|
||||
| 7 | KEYCODE_0 |
|
||||
| 8 | KEYCODE_1 |
|
||||
| 9 | KEYCODE_2 |
|
||||
| 10 | KEYCODE_3 |
|
||||
| 11 | KEYCODE_4 |
|
||||
| 12 | KEYCODE_5 |
|
||||
| 13 | KEYCODE_6 |
|
||||
| 14 | KEYCODE_7 |
|
||||
| 15 | KEYCODE_8 |
|
||||
| 16 | KEYCODE_9 |
|
||||
| 17 | KEYCODE_STAR |
|
||||
| 18 | KEYCODE_POUND |
|
||||
| 19 | KEYCODE_DPAD_UP |
|
||||
| 20 | KEYCODE_DPAD_DOWN |
|
||||
| 21 | KEYCODE_DPAD_LEFT |
|
||||
| 22 | KEYCODE_DPAD_RIGHT |
|
||||
| 23 | KEYCODE_DPAD_CENTER |
|
||||
| 24 | KEYCODE_VOLUME_UP |
|
||||
| 25 | KEYCODE_VOLUME_DOWN |
|
||||
| 26 | KEYCODE_POWER |
|
||||
| 27 | KEYCODE_CAMERA |
|
||||
| 28 | KEYCODE_CLEAR |
|
||||
| 29 | KEYCODE_A |
|
||||
| 30 | KEYCODE_B |
|
||||
| 31 | KEYCODE_C |
|
||||
| 32 | KEYCODE_D |
|
||||
| 33 | KEYCODE_E |
|
||||
| 34 | KEYCODE_F |
|
||||
| 35 | KEYCODE_G |
|
||||
| 36 | KEYCODE_H |
|
||||
| 37 | KEYCODE_I |
|
||||
| 38 | KEYCODE_J |
|
||||
| 39 | KEYCODE_K |
|
||||
| 40 | KEYCODE_L |
|
||||
| 41 | KEYCODE_M |
|
||||
| 42 | KEYCODE_N |
|
||||
| 43 | KEYCODE_O |
|
||||
| 44 | KEYCODE_P |
|
||||
| 45 | KEYCODE_Q |
|
||||
| 46 | KEYCODE_R |
|
||||
| 47 | KEYCODE_S |
|
||||
| 48 | KEYCODE_T |
|
||||
| 49 | KEYCODE_U |
|
||||
| 50 | KEYCODE_V |
|
||||
| 51 | KEYCODE_W |
|
||||
| 52 | KEYCODE_X |
|
||||
| 53 | KEYCODE_Y |
|
||||
| 54 | KEYCODE_Z |
|
||||
| 55 | KEYCODE_COMMA |
|
||||
| 56 | KEYCODE_PERIOD |
|
||||
| 57 | KEYCODE_ALT_LEFT |
|
||||
| 58 | KEYCODE_ALT_RIGHT |
|
||||
| 59 | KEYCODE_SHIFT_LEFT |
|
||||
| 60 | KEYCODE_SHIFT_RIGHT |
|
||||
| 61 | KEYCODE_TAB |
|
||||
| 62 | KEYCODE_SPACE |
|
||||
| 63 | KEYCODE_SYM |
|
||||
| 64 | KEYCODE_EXPLORER |
|
||||
| 65 | KEYCODE_ENVELOPE |
|
||||
| 66 | KEYCODE_ENTER |
|
||||
| 67 | KEYCODE_DEL |
|
||||
| 68 | KEYCODE_GRAVE |
|
||||
| 69 | KEYCODE_MINUS |
|
||||
| 70 | KEYCODE_EQUALS |
|
||||
| 71 | KEYCODE_LEFT_BRACKET |
|
||||
| 72 | KEYCODE_RIGHT_BRACKET |
|
||||
| 73 | KEYCODE_BACKSLASH |
|
||||
| 74 | KEYCODE_SEMICOLON |
|
||||
| 75 | KEYCODE_APOSTROPHE |
|
||||
| 76 | KEYCODE_SLASH |
|
||||
| 77 | KEYCODE_AT |
|
||||
| 78 | KEYCODE_NUM |
|
||||
| 79 | KEYCODE_HEADSETHOOK |
|
||||
| 80 | KEYCODE_FOCUS |
|
||||
| 81 | KEYCODE_PLUS |
|
||||
| 82 | KEYCODE_MENU |
|
||||
| 83 | KEYCODE_NOTIFICATION |
|
||||
| 84 | KEYCODE_SEARCH |
|
||||
| 85 | KEYCODE_MEDIA_PLAY_PAUSE |
|
||||
| 86 | KEYCODE_MEDIA_STOP |
|
||||
| 87 | KEYCODE_MEDIA_NEXT |
|
||||
| 88 | KEYCODE_MEDIA_PREVIOUS |
|
||||
| 89 | KEYCODE_MEDIA_REWIND |
|
||||
| 90 | KEYCODE_MEDIA_FAST_FORWARD |
|
||||
| 91 | KEYCODE_MUTE |
|
||||
| 92 | KEYCODE_PAGE_UP |
|
||||
| 93 | KEYCODE_PAGE_DOWN |
|
||||
| 94 | KEYCODE_PICTSYMBOLS |
|
||||
| 95 | KEYCODE_SWITCH_CHARSET |
|
||||
| 96 | KEYCODE_BUTTON_A |
|
||||
| 97 | KEYCODE_BUTTON_B |
|
||||
| 98 | KEYCODE_BUTTON_C |
|
||||
| 99 | KEYCODE_BUTTON_X |
|
||||
| 100 | KEYCODE_BUTTON_Y |
|
||||
| 101 | KEYCODE_BUTTON_Z |
|
||||
| 102 | KEYCODE_BUTTON_L1 |
|
||||
| 103 | KEYCODE_BUTTON_R1 |
|
||||
| 104 | KEYCODE_BUTTON_L2 |
|
||||
| 105 | KEYCODE_BUTTON_R2 |
|
||||
| 106 | KEYCODE_BUTTON_THUMBL |
|
||||
| 107 | KEYCODE_BUTTON_THUMBR |
|
||||
| 108 | KEYCODE_BUTTON_START |
|
||||
| 109 | KEYCODE_BUTTON_SELECT |
|
||||
| 110 | KEYCODE_BUTTON_MODE |
|
||||
| 111 | KEYCODE_ESCAPE |
|
||||
| 112 | KEYCODE_FORWARD_DEL |
|
||||
| 113 | KEYCODE_CTRL_LEFT |
|
||||
| 114 | KEYCODE_CTRL_RIGHT |
|
||||
| 115 | KEYCODE_CAPS_LOCK |
|
||||
| 116 | KEYCODE_SCROLL_LOCK |
|
||||
| 117 | KEYCODE_META_LEFT |
|
||||
| 118 | KEYCODE_META_RIGHT |
|
||||
| 119 | KEYCODE_FUNCTION |
|
||||
| 120 | KEYCODE_SYSRQ |
|
||||
| 121 | KEYCODE_BREAK |
|
||||
| 122 | KEYCODE_MOVE_HOME |
|
||||
| 123 | KEYCODE_MOVE_END |
|
||||
| 124 | KEYCODE_INSERT |
|
||||
| 125 | KEYCODE_FORWARD |
|
||||
| 126 | KEYCODE_MEDIA_PLAY |
|
||||
| 127 | KEYCODE_MEDIA_PAUSE |
|
||||
| 128 | KEYCODE_MEDIA_CLOSE |
|
||||
| 129 | KEYCODE_MEDIA_EJECT |
|
||||
| 130 | KEYCODE_MEDIA_RECORD |
|
||||
| 131 | KEYCODE_F1 |
|
||||
| 132 | KEYCODE_F2 |
|
||||
| 133 | KEYCODE_F3 |
|
||||
| 134 | KEYCODE_F4 |
|
||||
| 135 | KEYCODE_F5 |
|
||||
| 136 | KEYCODE_F6 |
|
||||
| 137 | KEYCODE_F7 |
|
||||
| 138 | KEYCODE_F8 |
|
||||
| 139 | KEYCODE_F9 |
|
||||
| 140 | KEYCODE_F10 |
|
||||
| 141 | KEYCODE_F11 |
|
||||
| 142 | KEYCODE_F12 |
|
||||
| 143 | KEYCODE_NUM_LOCK |
|
||||
| 144 | KEYCODE_NUMPAD_0 |
|
||||
| 145 | KEYCODE_NUMPAD_1 |
|
||||
| 146 | KEYCODE_NUMPAD_2 |
|
||||
| 147 | KEYCODE_NUMPAD_3 |
|
||||
| 148 | KEYCODE_NUMPAD_4 |
|
||||
| 149 | KEYCODE_NUMPAD_5 |
|
||||
| 150 | KEYCODE_NUMPAD_6 |
|
||||
| 151 | KEYCODE_NUMPAD_7 |
|
||||
| 152 | KEYCODE_NUMPAD_8 |
|
||||
| 153 | KEYCODE_NUMPAD_9 |
|
||||
| 154 | KEYCODE_NUMPAD_DIVIDE |
|
||||
| 155 | KEYCODE_NUMPAD_MULTIPLY |
|
||||
| 156 | KEYCODE_NUMPAD_SUBTRACT |
|
||||
| 157 | KEYCODE_NUMPAD_ADD |
|
||||
| 158 | KEYCODE_NUMPAD_DOT |
|
||||
| 159 | KEYCODE_NUMPAD_COMMA |
|
||||
| 160 | KEYCODE_NUMPAD_ENTER |
|
||||
| 161 | KEYCODE_NUMPAD_EQUALS |
|
||||
| 162 | KEYCODE_NUMPAD_LEFT_PAREN |
|
||||
| 163 | KEYCODE_NUMPAD_RIGHT_PAREN |
|
||||
| 164 | KEYCODE_VOLUME_MUTE |
|
||||
| 165 | KEYCODE_INFO |
|
||||
| 166 | KEYCODE_CHANNEL_UP |
|
||||
| 167 | KEYCODE_CHANNEL_DOWN |
|
||||
| 168 | KEYCODE_ZOOM_IN |
|
||||
| 169 | KEYCODE_ZOOM_OUT |
|
||||
| 170 | KEYCODE_TV |
|
||||
| 171 | KEYCODE_WINDOW |
|
||||
| 172 | KEYCODE_GUIDE |
|
||||
| 173 | KEYCODE_DVR |
|
||||
| 174 | KEYCODE_BOOKMARK |
|
||||
| 175 | KEYCODE_CAPTIONS |
|
||||
| 176 | KEYCODE_SETTINGS |
|
||||
| 177 | KEYCODE_TV_POWER |
|
||||
| 178 | KEYCODE_TV_INPUT |
|
||||
| 179 | KEYCODE_STB_POWER |
|
||||
| 180 | KEYCODE_STB_INPUT |
|
||||
| 181 | KEYCODE_AVR_POWER |
|
||||
| 182 | KEYCODE_AVR_INPUT |
|
||||
| 183 | KEYCODE_PROG_RED |
|
||||
| 184 | KEYCODE_PROG_GREEN |
|
||||
| 185 | KEYCODE_PROG_YELLOW |
|
||||
| 186 | KEYCODE_PROG_BLUE |
|
||||
| 187 | KEYCODE_APP_SWITCH |
|
||||
| 188 | KEYCODE_BUTTON_1 |
|
||||
| 189 | KEYCODE_BUTTON_2 |
|
||||
| 190 | KEYCODE_BUTTON_3 |
|
||||
| 191 | KEYCODE_BUTTON_4 |
|
||||
| 192 | KEYCODE_BUTTON_5 |
|
||||
| 193 | KEYCODE_BUTTON_6 |
|
||||
| 194 | KEYCODE_BUTTON_7 |
|
||||
| 195 | KEYCODE_BUTTON_8 |
|
||||
| 196 | KEYCODE_BUTTON_9 |
|
||||
| 197 | KEYCODE_BUTTON_10 |
|
||||
| 198 | KEYCODE_BUTTON_11 |
|
||||
| 199 | KEYCODE_BUTTON_12 |
|
||||
| 200 | KEYCODE_BUTTON_13 |
|
||||
| 201 | KEYCODE_BUTTON_14 |
|
||||
| 202 | KEYCODE_BUTTON_15 |
|
||||
| 203 | KEYCODE_BUTTON_16 |
|
||||
| 204 | KEYCODE_LANGUAGE_SWITCH |
|
||||
| 205 | KEYCODE_MANNER_MODE |
|
||||
| 206 | KEYCODE_3D_MODE |
|
||||
| 207 | KEYCODE_CONTACTS |
|
||||
| 208 | KEYCODE_CALENDAR |
|
||||
| 209 | KEYCODE_MUSIC |
|
||||
| 210 | KEYCODE_CALCULATOR |
|
||||
| 211 | KEYCODE_ZENKAKU_HANKAKU |
|
||||
| 212 | KEYCODE_EISU |
|
||||
| 213 | KEYCODE_MUHENKAN |
|
||||
| 214 | KEYCODE_HENKAN |
|
||||
| 215 | KEYCODE_KATAKANA_HIRAGANA |
|
||||
| 216 | KEYCODE_YEN |
|
||||
| 217 | KEYCODE_RO |
|
||||
| 218 | KEYCODE_KANA |
|
||||
| 219 | KEYCODE_ASSIST |
|
||||
| 220 | KEYCODE_BRIGHTNESS_DOWN |
|
||||
| 221 | KEYCODE_BRIGHTNESS_UP |
|
||||
| 222 | KEYCODE_MEDIA_AUDIO_TRACK |
|
||||
| 223 | KEYCODE_SLEEP |
|
||||
| 224 | KEYCODE_WAKEUP |
|
||||
| 225 | KEYCODE_PAIRING |
|
||||
| 226 | KEYCODE_MEDIA_TOP_MENU |
|
||||
| 227 | KEYCODE_11 |
|
||||
| 228 | KEYCODE_12 |
|
||||
| 229 | KEYCODE_LAST_CHANNEL |
|
||||
| 230 | KEYCODE_TV_DATA_SERVICE |
|
||||
| 231 | KEYCODE_VOICE_ASSIST |
|
||||
| 232 | KEYCODE_TV_RADIO_SERVICE |
|
||||
| 233 | KEYCODE_TV_TELETEXT |
|
||||
| 234 | KEYCODE_TV_NUMBER_ENTRY |
|
||||
| 235 | KEYCODE_TV_TERRESTRIAL_ANALOG |
|
||||
| 236 | KEYCODE_TV_TERRESTRIAL_DIGITAL |
|
||||
| 237 | KEYCODE_TV_SATELLITE |
|
||||
| 238 | KEYCODE_TV_SATELLITE_BS |
|
||||
| 239 | KEYCODE_TV_SATELLITE_CS |
|
||||
| 240 | KEYCODE_TV_SATELLITE_SERVICE |
|
||||
| 241 | KEYCODE_TV_NETWORK |
|
||||
| 242 | KEYCODE_TV_ANTENNA_CABLE |
|
||||
| 243 | KEYCODE_TV_INPUT_HDMI_1 |
|
||||
| 244 | KEYCODE_TV_INPUT_HDMI_2 |
|
||||
| 245 | KEYCODE_TV_INPUT_HDMI_3 |
|
||||
| 246 | KEYCODE_TV_INPUT_HDMI_4 |
|
||||
| 247 | KEYCODE_TV_INPUT_COMPOSITE_1 |
|
||||
| 248 | KEYCODE_TV_INPUT_COMPOSITE_2 |
|
||||
| 249 | KEYCODE_TV_INPUT_COMPONENT_1 |
|
||||
| 250 | KEYCODE_TV_INPUT_COMPONENT_2 |
|
||||
| 251 | KEYCODE_TV_INPUT_VGA_1 |
|
||||
| 252 | KEYCODE_TV_AUDIO_DESCRIPTION |
|
||||
| 253 | KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP |
|
||||
| 254 | KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN |
|
||||
| 255 | KEYCODE_TV_ZOOM_MODE |
|
||||
| 256 | KEYCODE_TV_CONTENTS_MENU |
|
||||
| 257 | KEYCODE_TV_MEDIA_CONTEXT_MENU |
|
||||
| 258 | KEYCODE_TV_TIMER_PROGRAMMING |
|
||||
| 259 | KEYCODE_HELP |
|
||||
| 260 | KEYCODE_NAVIGATE_PREVIOUS |
|
||||
| 261 | KEYCODE_NAVIGATE_NEXT |
|
||||
| 262 | KEYCODE_NAVIGATE_IN |
|
||||
| 263 | KEYCODE_NAVIGATE_OUT |
|
||||
| 264 | KEYCODE_STEM_PRIMARY |
|
||||
| 265 | KEYCODE_STEM_1 |
|
||||
| 266 | KEYCODE_STEM_2 |
|
||||
| 267 | KEYCODE_STEM_3 |
|
||||
| 268 | KEYCODE_DPAD_UP_LEFT |
|
||||
| 269 | KEYCODE_DPAD_DOWN_LEFT |
|
||||
| 270 | KEYCODE_DPAD_UP_RIGHT |
|
||||
| 271 | KEYCODE_DPAD_DOWN_RIGHT |
|
||||
| 272 | KEYCODE_MEDIA_SKIP_FORWARD |
|
||||
| 273 | KEYCODE_MEDIA_SKIP_BACKWARD |
|
||||
| 274 | KEYCODE_MEDIA_STEP_FORWARD |
|
||||
| 275 | KEYCODE_MEDIA_STEP_BACKWARD |
|
||||
| 276 | KEYCODE_SOFT_SLEEP |
|
||||
| 277 | KEYCODE_CUT |
|
||||
| 278 | KEYCODE_COPY |
|
||||
| 279 | KEYCODE_PASTE |
|
||||
| 280 | KEYCODE_SYSTEM_NAVIGATION_UP |
|
||||
| 281 | KEYCODE_SYSTEM_NAVIGATION_DOWN |
|
||||
| 282 | KEYCODE_SYSTEM_NAVIGATION_LEFT |
|
||||
| 283 | KEYCODE_SYSTEM_NAVIGATION_RIGHT |
|
||||
| 284 | KEYCODE_ALL_APPS |
|
||||
| 285 | KEYCODE_REFRESH |
|
||||
| 286 | KEYCODE_THUMBS_UP |
|
||||
| 287 | KEYCODE_THUMBS_DOWN |
|
||||
| 288 | KEYCODE_PROFILE_SWITCH |
|
||||
| 289 | KEYCODE_VIDEO_APP_1 |
|
||||
| 290 | KEYCODE_VIDEO_APP_2 |
|
||||
| 291 | KEYCODE_VIDEO_APP_3 |
|
||||
| 292 | KEYCODE_VIDEO_APP_4 |
|
||||
| 293 | KEYCODE_VIDEO_APP_5 |
|
||||
| 294 | KEYCODE_VIDEO_APP_6 |
|
||||
| 295 | KEYCODE_VIDEO_APP_7 |
|
||||
| 296 | KEYCODE_VIDEO_APP_8 |
|
||||
| 297 | KEYCODE_FEATURED_APP_1 |
|
||||
| 298 | KEYCODE_FEATURED_APP_2 |
|
||||
| 299 | KEYCODE_FEATURED_APP_3 |
|
||||
| 300 | KEYCODE_FEATURED_APP_4 |
|
||||
| 301 | KEYCODE_DEMO_APP_1 |
|
||||
| 302 | KEYCODE_DEMO_APP_2 |
|
||||
| 303 | KEYCODE_DEMO_APP_3 |
|
||||
| 304 | KEYCODE_DEMO_APP_4 |
|
||||
|
32
bundles/org.openhab.binding.androidtv/pom.xml
Normal file
32
bundles/org.openhab.binding.androidtv/pom.xml
Normal file
@ -0,0 +1,32 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://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>4.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>org.openhab.binding.androidtv</artifactId>
|
||||
|
||||
<name>openHAB Add-ons :: Bundles :: AndroidTV Binding</name>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk15on</artifactId>
|
||||
<version>1.52</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcprov-jdk15on</artifactId>
|
||||
<version>1.52</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</project>
|
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<features name="org.openhab.binding.androidtv-${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-androidtv" description="AndroidTV Binding" version="${project.version}">
|
||||
<feature>openhab-runtime-base</feature>
|
||||
<feature>openhab-transport-mdns</feature>
|
||||
<bundle start-level="80">mvn:org.openhab.addons.bundles/org.openhab.binding.androidtv/${project.version}</bundle>
|
||||
</feature>
|
||||
</features>
|
@ -0,0 +1,56 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.thing.ThingTypeUID;
|
||||
|
||||
/**
|
||||
* The {@link AndroidTVBindingConstants} class defines common constants, which are
|
||||
* used across the whole binding.
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AndroidTVBindingConstants {
|
||||
|
||||
private static final String BINDING_ID = "androidtv";
|
||||
|
||||
// List of all Thing Type UIDs
|
||||
public static final ThingTypeUID THING_TYPE_GOOGLETV = new ThingTypeUID(BINDING_ID, "googletv");
|
||||
public static final ThingTypeUID THING_TYPE_SHIELDTV = new ThingTypeUID(BINDING_ID, "shieldtv");
|
||||
|
||||
public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_GOOGLETV, THING_TYPE_SHIELDTV);
|
||||
|
||||
// List of all Channel ids
|
||||
public static final String CHANNEL_DEBUG = "debug";
|
||||
public static final String CHANNEL_KEYBOARD = "keyboard";
|
||||
public static final String CHANNEL_KEYPRESS = "keypress";
|
||||
public static final String CHANNEL_KEYCODE = "keycode";
|
||||
public static final String CHANNEL_PINCODE = "pincode";
|
||||
public static final String CHANNEL_APP = "app";
|
||||
public static final String CHANNEL_APPNAME = "appname";
|
||||
public static final String CHANNEL_APPURL = "appurl";
|
||||
public static final String CHANNEL_POWER = "power";
|
||||
public static final String CHANNEL_VOLUME = "volume";
|
||||
public static final String CHANNEL_MUTE = "mute";
|
||||
public static final String CHANNEL_PLAYER = "player";
|
||||
|
||||
// List of all config properties
|
||||
public static final String PROPERTY_IP_ADDRESS = "ipAddress";
|
||||
|
||||
// List of all static String literals
|
||||
public static final String PIN_REQUEST = "REQUEST";
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.openhab.core.events.EventPublisher;
|
||||
import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider;
|
||||
import org.openhab.core.thing.i18n.ChannelTypeI18nLocalizationService;
|
||||
import org.openhab.core.thing.link.ItemChannelLinkRegistry;
|
||||
import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
|
||||
import org.osgi.service.component.annotations.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* Dynamic provider of command options.
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*
|
||||
* Originally written for ADB by Christoph Weitkamp - Initial contribution
|
||||
*/
|
||||
@Component(service = { DynamicCommandDescriptionProvider.class, AndroidTVDynamicCommandDescriptionProvider.class })
|
||||
@NonNullByDefault
|
||||
public class AndroidTVDynamicCommandDescriptionProvider extends BaseDynamicCommandDescriptionProvider {
|
||||
@Activate
|
||||
public AndroidTVDynamicCommandDescriptionProvider(final @Reference EventPublisher eventPublisher, //
|
||||
final @Reference ItemChannelLinkRegistry itemChannelLinkRegistry, //
|
||||
final @Reference ChannelTypeI18nLocalizationService channelTypeI18nLocalizationService) {
|
||||
this.eventPublisher = eventPublisher;
|
||||
this.itemChannelLinkRegistry = itemChannelLinkRegistry;
|
||||
this.channelTypeI18nLocalizationService = channelTypeI18nLocalizationService;
|
||||
}
|
||||
}
|
@ -0,0 +1,261 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal;
|
||||
|
||||
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConfiguration;
|
||||
import org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConnectionManager;
|
||||
import org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConfiguration;
|
||||
import org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConnectionManager;
|
||||
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.thing.ThingTypeUID;
|
||||
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||
import org.openhab.core.types.Command;
|
||||
import org.openhab.core.types.CommandOption;
|
||||
import org.openhab.core.types.State;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AndroidTVHandler} is responsible for handling commands, which are
|
||||
* sent to one of the channels.
|
||||
*
|
||||
* Significant portions reused from Lutron binding with permission from Bob A.
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AndroidTVHandler extends BaseThingHandler {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AndroidTVHandler.class);
|
||||
|
||||
private @Nullable ShieldTVConnectionManager shieldtvConnectionManager;
|
||||
private @Nullable GoogleTVConnectionManager googletvConnectionManager;
|
||||
|
||||
private @Nullable ScheduledFuture<?> monitorThingStatusJob;
|
||||
private final Object monitorThingStatusJobLock = new Object();
|
||||
private static final int THING_STATUS_FREQUENCY = 250;
|
||||
|
||||
private final AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider;
|
||||
private final ThingTypeUID thingTypeUID;
|
||||
private final String thingID;
|
||||
|
||||
public AndroidTVHandler(Thing thing, AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider,
|
||||
ThingTypeUID thingTypeUID) {
|
||||
super(thing);
|
||||
this.commandDescriptionProvider = commandDescriptionProvider;
|
||||
this.thingTypeUID = thingTypeUID;
|
||||
this.thingID = this.getThing().getUID().getId();
|
||||
}
|
||||
|
||||
public void setThingProperty(String property, String value) {
|
||||
thing.setProperty(property, value);
|
||||
}
|
||||
|
||||
public String getThingID() {
|
||||
return this.thingID;
|
||||
}
|
||||
|
||||
public void updateChannelState(String channel, State state) {
|
||||
updateState(channel, state);
|
||||
}
|
||||
|
||||
public ScheduledExecutorService getScheduler() {
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
public void updateCDP(String channelName, Map<String, String> cdpMap) {
|
||||
logger.trace("{} - Updating CDP for {}", this.thingID, channelName);
|
||||
List<CommandOption> commandOptions = new ArrayList<CommandOption>();
|
||||
cdpMap.forEach((key, value) -> commandOptions.add(new CommandOption(key, value)));
|
||||
logger.trace("{} - CDP List: {}", this.thingID, commandOptions);
|
||||
commandDescriptionProvider.setCommandOptions(new ChannelUID(getThing().getUID(), channelName), commandOptions);
|
||||
}
|
||||
|
||||
private void monitorThingStatus() {
|
||||
synchronized (monitorThingStatusJobLock) {
|
||||
checkThingStatus();
|
||||
monitorThingStatusJob = scheduler.schedule(this::monitorThingStatus, THING_STATUS_FREQUENCY,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
public void checkThingStatus() {
|
||||
String statusMessage = "";
|
||||
boolean failed = false;
|
||||
|
||||
GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager;
|
||||
ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager;
|
||||
|
||||
if (googletvConnectionManager != null) {
|
||||
if (!googletvConnectionManager.getLoggedIn()) {
|
||||
statusMessage = "GoogleTV: " + googletvConnectionManager.getStatusMessage();
|
||||
failed = true;
|
||||
} else {
|
||||
statusMessage = "GoogleTV: ONLINE";
|
||||
}
|
||||
}
|
||||
|
||||
if (THING_TYPE_SHIELDTV.equals(thingTypeUID)) {
|
||||
if (shieldtvConnectionManager != null) {
|
||||
if (!shieldtvConnectionManager.getLoggedIn()) {
|
||||
statusMessage = statusMessage + " | ShieldTV: " + shieldtvConnectionManager.getStatusMessage();
|
||||
failed = true;
|
||||
} else {
|
||||
statusMessage = statusMessage + " | ShieldTV: ONLINE";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, statusMessage);
|
||||
} else {
|
||||
updateStatus(ThingStatus.ONLINE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialize() {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.NONE, "@text/offline.protocols-starting");
|
||||
|
||||
GoogleTVConfiguration googletvConfig = getConfigAs(GoogleTVConfiguration.class);
|
||||
String ipAddress = googletvConfig.ipAddress;
|
||||
|
||||
if (ipAddress.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.googletv-address-not-specified");
|
||||
return;
|
||||
}
|
||||
|
||||
googletvConnectionManager = new GoogleTVConnectionManager(this, googletvConfig);
|
||||
|
||||
if (THING_TYPE_SHIELDTV.equals(thingTypeUID)) {
|
||||
ShieldTVConfiguration shieldtvConfig = getConfigAs(ShieldTVConfiguration.class);
|
||||
ipAddress = shieldtvConfig.ipAddress;
|
||||
|
||||
if (ipAddress.isBlank()) {
|
||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||
"@text/offline.shieldtv-address-not-specified");
|
||||
return;
|
||||
}
|
||||
|
||||
shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig);
|
||||
}
|
||||
|
||||
monitorThingStatusJob = scheduler.schedule(this::monitorThingStatus, THING_STATUS_FREQUENCY,
|
||||
TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||
logger.trace("{} - Command received at handler: {} {}", this.thingID, channelUID.getId(), command);
|
||||
|
||||
if (command.toString().equals("REFRESH")) {
|
||||
// REFRESH causes issues on some channels. Block for now until implemented.
|
||||
return;
|
||||
}
|
||||
|
||||
GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager;
|
||||
ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager;
|
||||
|
||||
if (CHANNEL_DEBUG.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType) {
|
||||
if (command.toString().equals("GOOGLETV_HALT") && (googletvConnectionManager != null)) {
|
||||
googletvConnectionManager.dispose();
|
||||
googletvConnectionManager = null;
|
||||
} else if (command.toString().equals("GOOGLETV_START")) {
|
||||
GoogleTVConfiguration googletvConfig = getConfigAs(GoogleTVConfiguration.class);
|
||||
googletvConnectionManager = new GoogleTVConnectionManager(this, googletvConfig);
|
||||
} else if (command.toString().equals("GOOGLETV_SHIM") && (googletvConnectionManager == null)) {
|
||||
GoogleTVConfiguration googletvConfig = getConfigAs(GoogleTVConfiguration.class);
|
||||
googletvConfig.shim = true;
|
||||
googletvConnectionManager = new GoogleTVConnectionManager(this, googletvConfig);
|
||||
} else if (command.toString().equals("SHIELDTV_HALT") && (shieldtvConnectionManager != null)) {
|
||||
shieldtvConnectionManager.dispose();
|
||||
shieldtvConnectionManager = null;
|
||||
} else if (command.toString().equals("SHIELDTV_START")) {
|
||||
ShieldTVConfiguration shieldtvConfig = getConfigAs(ShieldTVConfiguration.class);
|
||||
shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig);
|
||||
} else if (command.toString().equals("SHIELDTV_SHIM") && (shieldtvConnectionManager == null)) {
|
||||
ShieldTVConfiguration shieldtvConfig = getConfigAs(ShieldTVConfiguration.class);
|
||||
shieldtvConfig.shim = true;
|
||||
shieldtvConnectionManager = new ShieldTVConnectionManager(this, shieldtvConfig);
|
||||
} else if (command.toString().startsWith("GOOGLETV") && (googletvConnectionManager != null)) {
|
||||
googletvConnectionManager.handleCommand(channelUID, command);
|
||||
} else if (command.toString().startsWith("SHIELDTV") && (shieldtvConnectionManager != null)) {
|
||||
shieldtvConnectionManager.handleCommand(channelUID, command);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (THING_TYPE_SHIELDTV.equals(thingTypeUID) && (shieldtvConnectionManager != null)) {
|
||||
if (CHANNEL_PINCODE.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType) {
|
||||
if (!shieldtvConnectionManager.getLoggedIn()) {
|
||||
shieldtvConnectionManager.handleCommand(channelUID, command);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else if (CHANNEL_APP.equals(channelUID.getId())) {
|
||||
if (command instanceof StringType) {
|
||||
shieldtvConnectionManager.handleCommand(channelUID, command);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (googletvConnectionManager != null) {
|
||||
googletvConnectionManager.handleCommand(channelUID, command);
|
||||
return;
|
||||
}
|
||||
|
||||
logger.warn("{} - Commands All Failed. Please report this as a bug. {} {}", thingID, channelUID.getId(),
|
||||
command);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
synchronized (monitorThingStatusJobLock) {
|
||||
ScheduledFuture<?> monitorThingStatusJob = this.monitorThingStatusJob;
|
||||
if (monitorThingStatusJob != null) {
|
||||
monitorThingStatusJob.cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
GoogleTVConnectionManager googletvConnectionManager = this.googletvConnectionManager;
|
||||
ShieldTVConnectionManager shieldtvConnectionManager = this.shieldtvConnectionManager;
|
||||
|
||||
if (shieldtvConnectionManager != null) {
|
||||
shieldtvConnectionManager.dispose();
|
||||
}
|
||||
|
||||
if (googletvConnectionManager != null) {
|
||||
googletvConnectionManager.dispose();
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal;
|
||||
|
||||
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
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.Activate;
|
||||
import org.osgi.service.component.annotations.Component;
|
||||
import org.osgi.service.component.annotations.Reference;
|
||||
|
||||
/**
|
||||
* The {@link AndroidTVHandlerFactory} is responsible for creating things and thing
|
||||
* handlers.
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(configurationPid = "binding.androidtv", service = ThingHandlerFactory.class)
|
||||
public class AndroidTVHandlerFactory extends BaseThingHandlerFactory {
|
||||
|
||||
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Set.of(THING_TYPE_GOOGLETV,
|
||||
THING_TYPE_SHIELDTV);
|
||||
|
||||
private final AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider;
|
||||
|
||||
@Activate
|
||||
public AndroidTVHandlerFactory(
|
||||
final @Reference AndroidTVDynamicCommandDescriptionProvider commandDescriptionProvider) {
|
||||
this.commandDescriptionProvider = commandDescriptionProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||
return new AndroidTVHandler(thing, commandDescriptionProvider, thingTypeUID);
|
||||
}
|
||||
}
|
@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MDNSDiscoveryParticipant} that will discover GOOGLETV(s).
|
||||
*
|
||||
* @author Ben Rosenblum - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = MDNSDiscoveryParticipant.class, immediate = true, configurationPid = "discovery.googletv")
|
||||
public class GoogleTVDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(GoogleTVDiscoveryParticipant.class);
|
||||
private static final String GOOGLETV_MDNS_SERVICE_TYPE = "_androidtvremote2._tcp.local.";
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_THING_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return GOOGLETV_MDNS_SERVICE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(@Nullable ServiceInfo service) {
|
||||
if ((service == null) || !service.hasData()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InetAddress[] ipAddresses = service.getInet4Addresses();
|
||||
|
||||
if (ipAddresses.length > 0) {
|
||||
String ipAddress = ipAddresses[0].getHostAddress();
|
||||
String macAddress = service.getPropertyString("bt");
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
String nice = service.getNiceTextString();
|
||||
String qualifiedName = service.getQualifiedName();
|
||||
logger.debug("GoogleTV mDNS discovery notified of GoogleTV mDNS service: {}", nice);
|
||||
logger.trace("GoogleTV mDNS service qualifiedName: {}", qualifiedName);
|
||||
logger.trace("GoogleTV mDNS service ipAddresses: {} ({})", ipAddresses, ipAddresses.length);
|
||||
logger.trace("GoogleTV mDNS service selected ipAddress: {}", ipAddress);
|
||||
logger.trace("GoogleTV mDNS service property macAddress: {}", macAddress);
|
||||
}
|
||||
|
||||
final ThingUID uid = getThingUID(service);
|
||||
if (uid != null) {
|
||||
final String id = uid.getId();
|
||||
final String label = service.getName() + " (" + id + ")";
|
||||
final Map<String, Object> properties = Map.of(PROPERTY_IP_ADDRESS, ipAddress);
|
||||
|
||||
return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(@Nullable ServiceInfo service) {
|
||||
if ((service == null) || !service.hasData() || (service.getPropertyString("bt") == null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ThingUID(THING_TYPE_GOOGLETV, service.getPropertyString("bt").replace(":", ""));
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.discovery;
|
||||
|
||||
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.jmdns.ServiceInfo;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.openhab.core.config.discovery.DiscoveryResult;
|
||||
import org.openhab.core.config.discovery.DiscoveryResultBuilder;
|
||||
import org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant;
|
||||
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;
|
||||
|
||||
/**
|
||||
* Implementation of {@link MDNSDiscoveryParticipant} that will discover SHIELDTV(s).
|
||||
*
|
||||
* @author Ben Rosenblum - initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
@Component(service = MDNSDiscoveryParticipant.class, immediate = true, configurationPid = "discovery.shieldtv")
|
||||
public class ShieldTVDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(ShieldTVDiscoveryParticipant.class);
|
||||
private static final String SHIELDTV_MDNS_SERVICE_TYPE = "_nv_shield_remote._tcp.local.";
|
||||
|
||||
@Override
|
||||
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||
return SUPPORTED_THING_TYPES;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getServiceType() {
|
||||
return SHIELDTV_MDNS_SERVICE_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable DiscoveryResult createResult(@Nullable ServiceInfo service) {
|
||||
if (service == null || !service.hasData()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
InetAddress[] ipAddresses = service.getInet4Addresses();
|
||||
|
||||
if (ipAddresses.length > 0) {
|
||||
String ipAddress = ipAddresses[0].getHostAddress();
|
||||
String serverId = service.getPropertyString("SERVER");
|
||||
String serverCapability = service.getPropertyString("SERVER_CAPABILITY");
|
||||
|
||||
if (logger.isDebugEnabled()) {
|
||||
String nice = service.getNiceTextString();
|
||||
String qualifiedName = service.getQualifiedName();
|
||||
logger.debug("ShieldTV mDNS discovery notified of ShieldTV mDNS service: {}", nice);
|
||||
logger.trace("ShieldTV mDNS service qualifiedName: {}", qualifiedName);
|
||||
logger.trace("ShieldTV mDNS service ipAddresses: {} ({})", ipAddresses, ipAddresses.length);
|
||||
logger.trace("ShieldTV mDNS service selected ipAddress: {}", ipAddress);
|
||||
logger.trace("ShieldTV mDNS service property SERVER: {}", serverId);
|
||||
logger.trace("ShieldTV mDNS service property SERVER_CAPABILITY: {}", serverCapability);
|
||||
}
|
||||
|
||||
final ThingUID uid = getThingUID(service);
|
||||
if (uid != null) {
|
||||
final String id = uid.getId();
|
||||
final String label = service.getName() + " (" + id + ")";
|
||||
final Map<String, Object> properties = Map.of(PROPERTY_IP_ADDRESS, ipAddress);
|
||||
|
||||
return DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel(label).build();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nullable ThingUID getThingUID(@Nullable ServiceInfo service) {
|
||||
if (service == null || !service.hasData() || (service.getPropertyString("SERVER") == null)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ThingUID(THING_TYPE_SHIELDTV, service.getPropertyString("SERVER").substring(8));
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.googletv;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* GoogleTVCommand represents a GoogleTV protocol command
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GoogleTVCommand {
|
||||
private String command;
|
||||
|
||||
public GoogleTVCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return command.isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.googletv;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link GoogleTVConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GoogleTVConfiguration {
|
||||
|
||||
public String ipAddress = "";
|
||||
public int port = 6466;
|
||||
public int reconnect;
|
||||
public int heartbeat;
|
||||
public String keystoreFileName = "";
|
||||
public String keystorePassword = "";
|
||||
public int delay = 0;
|
||||
public boolean shim;
|
||||
public boolean shimNewKeys;
|
||||
public String mode = "";
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.googletv;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link GoogleTVConstants} class defines common constants, which are
|
||||
* used across the googletv protocol.
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GoogleTVConstants {
|
||||
|
||||
// List of all static String literals
|
||||
public static final String DELIMITER_00 = "00";
|
||||
public static final String DELIMITER_01 = "01";
|
||||
public static final String DELIMITER_02 = "02";
|
||||
public static final String DELIMITER_08 = "08";
|
||||
public static final String DELIMITER_0A = "0a";
|
||||
public static final String DELIMITER_10 = "10";
|
||||
public static final String DELIMITER_12 = "12";
|
||||
public static final String DELIMITER_1A = "1a";
|
||||
public static final String DELIMITER_42 = "42";
|
||||
public static final String DELIMITER_92 = "92";
|
||||
public static final String DELIMITER_A2 = "a2";
|
||||
public static final String DELIMITER_C2 = "c2";
|
||||
|
||||
public static final String MESSAGE_POWEROFF = "c202020800";
|
||||
public static final String MESSAGE_POWERON = "c202020801";
|
||||
public static final String MESSAGE_PINSUCCESS = "080210c801ca02";
|
||||
public static final String HARD_DROP = "ffffffff";
|
||||
}
|
@ -0,0 +1,336 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.googletv;
|
||||
|
||||
import static org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class responsible for parsing incoming GoogleTV messages. Calls back to an object implementing the
|
||||
* GoogleTVMessageParserCallbacks interface.
|
||||
*
|
||||
* Adapted from Lutron Leap binding
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class GoogleTVMessageParser {
|
||||
private final Logger logger = LoggerFactory.getLogger(GoogleTVMessageParser.class);
|
||||
|
||||
private final GoogleTVConnectionManager callback;
|
||||
|
||||
public GoogleTVMessageParser(GoogleTVConnectionManager callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void handleMessage(String msg) {
|
||||
if (msg.trim().isEmpty()) {
|
||||
return; // Ignore empty lines
|
||||
}
|
||||
|
||||
String thingId = callback.getThingID();
|
||||
char[] charArray = msg.toCharArray();
|
||||
String lenString = "" + charArray[0] + charArray[1];
|
||||
int len = Integer.parseInt(lenString, 16);
|
||||
msg = msg.substring(2);
|
||||
charArray = msg.toCharArray();
|
||||
|
||||
logger.trace("{} - Received GoogleTV message - Length: {} Message: {}", thingId, len, msg);
|
||||
|
||||
callback.validMessageReceived();
|
||||
|
||||
try {
|
||||
if (msg.startsWith(DELIMITER_1A)) {
|
||||
logger.warn("{} - GoogleTV Error Message: {}", thingId, msg);
|
||||
} else if (msg.startsWith(DELIMITER_0A)) {
|
||||
// First message on connection from GTV
|
||||
//
|
||||
// 0a 5b08 ff 041256 0a 11 534849454c4420416e64726f6964205456 12 06 4e5649444941 18 01 22 02 3131 2a
|
||||
// ---------------------LEN-SHIELD Android TV--------------------LEN-NVIDIA---------LEN---LEN-Android
|
||||
// 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
|
||||
// LEN-com.google.android.tv.remote.service
|
||||
// 0d 352e322e343733323534313333
|
||||
// LEN-5.2.473254133
|
||||
//
|
||||
// 0a 5308 ff 04124e 0a 0c 42524156494120344b204742 12 04 536f6e79 18 01 22 01 39 2a
|
||||
// ---------------------LEN-BRAVIA 4K GB---------------LEN-Sony-------LEN---LEN-Android Version
|
||||
// 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
|
||||
// 0d 352e322e343733323534313333
|
||||
//
|
||||
// 0a 5408 ff 04124f 0a 0a 4368726f6d6563617374 12 06 476f6f676c65 18 01 22 02 3132 2a
|
||||
// ---------------------LEN-Chromecast-------------LEN-Google---------LEN---LEN-Android Version
|
||||
// 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
|
||||
// 0d 352e322e343733323534313333
|
||||
//
|
||||
// 0a 5708 ff 041252 0a 0d 4368726f6d6563617374204844 12 06 476f6f676c65 18 01 22 02 3132 2a
|
||||
// ---------------------LEN-Chromecast HD----------------LEN-Google---------LEN---LEN-Android Version
|
||||
// 24 636f6d2e676f6f676c652e616e64726f69642e74762e72656d6f74652e73657276696365 32
|
||||
// 0d352e322e343733323534313333
|
||||
|
||||
if (callback.getLoggedIn()) {
|
||||
logger.warn("{} - Unexpected Login Message: {}", thingId, msg);
|
||||
} else {
|
||||
callback.sendCommand(
|
||||
new GoogleTVCommand(GoogleTVRequest.encodeMessage(GoogleTVRequest.loginRequest(4))));
|
||||
}
|
||||
|
||||
String st = "";
|
||||
int length = 0;
|
||||
StringBuilder preambleSb = new StringBuilder();
|
||||
StringBuilder manufacturerSb = new StringBuilder();
|
||||
StringBuilder modelSb = new StringBuilder();
|
||||
StringBuilder androidVersionSb = new StringBuilder();
|
||||
StringBuilder remoteServerSb = new StringBuilder();
|
||||
StringBuilder remoteServerVersionSb = new StringBuilder();
|
||||
|
||||
int i = 0;
|
||||
int current = 0;
|
||||
|
||||
for (; i < 14; i++) {
|
||||
preambleSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
i += 2; // 0a delimiter
|
||||
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i++) {
|
||||
modelSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
i += 2; // 12 delimiter
|
||||
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i++) {
|
||||
manufacturerSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
i += 6; // 18 01 22
|
||||
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i++) {
|
||||
androidVersionSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
i += 2; // 2a delimiter
|
||||
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i++) {
|
||||
remoteServerSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
i += 2; // 32 delimiter
|
||||
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i++) {
|
||||
remoteServerVersionSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
String preamble = preambleSb.toString();
|
||||
String model = GoogleTVRequest.encodeMessage(modelSb.toString());
|
||||
String manufacturer = GoogleTVRequest.encodeMessage(manufacturerSb.toString());
|
||||
String androidVersion = GoogleTVRequest.encodeMessage(androidVersionSb.toString());
|
||||
String remoteServer = GoogleTVRequest.encodeMessage(remoteServerSb.toString());
|
||||
String remoteServerVersion = GoogleTVRequest.encodeMessage(remoteServerVersionSb.toString());
|
||||
|
||||
logger.debug("{} - {} \"{}\" \"{}\" {} {} {}", thingId, preamble, model, manufacturer, androidVersion,
|
||||
remoteServer, remoteServerVersion);
|
||||
|
||||
callback.setModel(model);
|
||||
callback.setManufacturer(manufacturer);
|
||||
callback.setAndroidVersion(androidVersion);
|
||||
callback.setRemoteServer(remoteServer);
|
||||
callback.setRemoteServerVersion(remoteServerVersion);
|
||||
|
||||
} else if (msg.startsWith(DELIMITER_12)) {
|
||||
// Second message on connection from GTV
|
||||
// Login successful
|
||||
callback.sendCommand(
|
||||
new GoogleTVCommand(GoogleTVRequest.encodeMessage(GoogleTVRequest.loginRequest(5))));
|
||||
logger.info("{} - Login Successful", thingId);
|
||||
callback.setLoggedIn(true);
|
||||
} else if (msg.startsWith(DELIMITER_92)) {
|
||||
// Third message on connection from GTV
|
||||
// Also sent on power state change (to ON only unless keypress triggers)i
|
||||
// 9203 21 08 02 10 02 1a 11 534849454c4420416e64726f6964205456 20 02 2800 30 0f 38 0e 40 00
|
||||
// --------DD----DD----DD-LEN-SHIELD Android TV
|
||||
// 9203 1e 08 9610 10 09 1a 0d 4368726f6d6563617374204844 20 02 2800 30 19 38 0a 40 00
|
||||
// --------DD------DD----DD-LEN-Chromecast HD
|
||||
// 9203 1a 08 f304 10 09 1a 11 534849454c4420416e64726f6964205456 20 01
|
||||
// 9203 1a 08 8205 10 09 1a 11 534849454c4420416e64726f6964205456 20 01
|
||||
// --------DD------DD----DD-LEN-SHIELD Android TV
|
||||
//
|
||||
// VOLUME:
|
||||
// ---------------DD----DD----DD-LEN-BRAVIA 4K GB------------DD---------DD-MAX---VOL---MUTE
|
||||
// 00 --- 9203 1c 08 03 10 06 1a 0c 42524156494120344b204742 20 02 2800 30 64 38 00 40 00
|
||||
// 01 --- 9203 1c 08 03 10 06 1a 0c 42524156494120344b204742 20 02 2800 30 64 38 01 40 00
|
||||
// 100 -- 9203 1c 08 03 10 06 1a 0c 42524156494120344b204742 20 02 2800 30 64 38 64 40 00
|
||||
// MUTE - 9203 1c 08 03 10 06 1a 0c 42524156494120344b204742 20 02 2800 30 64 38 00 40 01
|
||||
|
||||
String st = "";
|
||||
int length = 0;
|
||||
|
||||
StringBuilder preambleSb = new StringBuilder();
|
||||
StringBuilder modelSb = new StringBuilder();
|
||||
String volMax = "";
|
||||
String volCurr = "";
|
||||
String volMute = "";
|
||||
String audioMode = "";
|
||||
|
||||
int i = 0;
|
||||
int current = 0;
|
||||
|
||||
for (; i < 12; i++) {
|
||||
preambleSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
do {
|
||||
if (!DELIMITER_1A.equals(st)) {
|
||||
preambleSb.append(st);
|
||||
i += 2;
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
}
|
||||
} while (!DELIMITER_1A.equals(st));
|
||||
|
||||
i += 2; // 1a delimiter
|
||||
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i++) {
|
||||
modelSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
i += 2; // 20 delimiter
|
||||
|
||||
st = "" + charArray[i] + charArray[i + 1];
|
||||
|
||||
audioMode = st; // 01 remote audio - 02 local audio
|
||||
|
||||
if (DELIMITER_02.equals(st)) {
|
||||
i += 2; // 02 longer message
|
||||
i += 4; // Unknown 2800 message
|
||||
i += 2; // 30 delimiter
|
||||
volMax = "" + charArray[i] + charArray[i + 1];
|
||||
i += 4; // volMax + 38 delimiter
|
||||
volCurr = "" + charArray[i] + charArray[i + 1];
|
||||
i += 4; // volCurr + 40 delimiter
|
||||
volMute = "" + charArray[i] + charArray[i + 1];
|
||||
|
||||
callback.setVolMax(volMax);
|
||||
callback.setVolCurr(volCurr);
|
||||
callback.setVolMute(volMute);
|
||||
}
|
||||
|
||||
String preamble = preambleSb.toString();
|
||||
String model = GoogleTVRequest.encodeMessage(modelSb.toString());
|
||||
logger.debug("{} - Device Update: {} \"{}\" {} {} {} {}", thingId, preamble, model, audioMode, volMax,
|
||||
volCurr, volMute);
|
||||
callback.setAudioMode(audioMode);
|
||||
|
||||
} else if (msg.startsWith(DELIMITER_08)) {
|
||||
// PIN Process Messages. Only used on 6467.
|
||||
if (msg.startsWith(MESSAGE_PINSUCCESS)) {
|
||||
// PIN Process Successful
|
||||
logger.debug("{} - PIN Process Successful!", thingId);
|
||||
callback.finishPinProcess();
|
||||
} else {
|
||||
// 080210c801a201081204080310061801
|
||||
// 080210c801fa0100
|
||||
logger.debug("{} - PIN Intermediary Message: {}", thingId, msg);
|
||||
}
|
||||
} else if (msg.startsWith(DELIMITER_C2)) {
|
||||
// Power State
|
||||
// c202020800 - OFF
|
||||
// c202020801 - ON
|
||||
if (MESSAGE_POWEROFF.equals(msg)) {
|
||||
callback.setPower(false);
|
||||
} else if (MESSAGE_POWERON.equals(msg)) {
|
||||
callback.setPower(true);
|
||||
} else {
|
||||
logger.info("{} - Unknown power state received. {}", thingId, msg);
|
||||
}
|
||||
} else if (msg.startsWith(DELIMITER_42)) {
|
||||
// Keepalive request
|
||||
callback.sendKeepAlive(msg);
|
||||
} else if (msg.startsWith(DELIMITER_A2)) {
|
||||
// Current app name. Sent on keypress and power change.
|
||||
// a201 21 0a 1f 62 1d 636f6d2e676f6f676c652e616e64726f69642e796f75747562652e7476
|
||||
// -----------------LEN-com.google.android.youtube.tv
|
||||
// a201 21 0a 1f 62 1d 636f6d2e676f6f676c652e616e64726f69642e74766c61756e63686572
|
||||
// -----------------LEN-com.google.android.tvlauncher
|
||||
// a201 14 0a 12 62 10 636f6d2e736f6e792e6474762e747678
|
||||
// -----------------LEN-com.sony.dtv.tvx
|
||||
// a201 15 0a 13 62 11 636f6d2e6e6574666c69782e6e696e6a61
|
||||
// -----------------LEN-com.netflix.ninja
|
||||
|
||||
StringBuilder preambleSb = new StringBuilder();
|
||||
StringBuilder appNameSb = new StringBuilder();
|
||||
int i = 0;
|
||||
int current = 0;
|
||||
|
||||
for (; i < 10; i++) {
|
||||
preambleSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
i += 2; // 62 delimiter
|
||||
|
||||
String st = "" + charArray[i] + charArray[i + 1];
|
||||
int length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i++) {
|
||||
appNameSb.append(charArray[i]);
|
||||
}
|
||||
|
||||
String preamble = preambleSb.toString();
|
||||
String appName = GoogleTVRequest.encodeMessage(appNameSb.toString());
|
||||
|
||||
logger.debug("{} - Current App: {} {}", thingId, preamble, appName);
|
||||
callback.setCurrentApp(appName);
|
||||
} else {
|
||||
logger.info("{} - Unknown payload received. {} {}", thingId, len, msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.debug("{} - Message Parser Exception on {}", thingId, msg);
|
||||
logger.debug("Message Parser Caught Exception", e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.googletv;
|
||||
|
||||
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
|
||||
import static org.openhab.binding.androidtv.internal.protocol.googletv.GoogleTVConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Contains static methods for constructing LEAP messages
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GoogleTVRequest {
|
||||
|
||||
public static String encodeMessage(String message) {
|
||||
StringBuilder reply = new StringBuilder();
|
||||
char[] charArray = message.toCharArray();
|
||||
for (int i = 0; i < charArray.length; i = i + 2) {
|
||||
String st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
char ch = (char) Integer.parseInt(st, 16);
|
||||
reply.append(ch);
|
||||
}
|
||||
return reply.toString();
|
||||
}
|
||||
|
||||
public static String decodeMessage(String message) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char ch[] = message.toCharArray();
|
||||
for (int i = 0; i < ch.length; i++) {
|
||||
String hexString = Integer.toHexString(ch[i]);
|
||||
if (hexString.length() % 2 > 0) {
|
||||
sb.append('0');
|
||||
}
|
||||
sb.append(hexString);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String pinRequest(String pin) {
|
||||
// OLD
|
||||
if (PIN_REQUEST.equals(pin)) {
|
||||
return loginRequest(3);
|
||||
} else {
|
||||
// 080210c801c202 22 0a 20 0e066c3d1c3a6686edb6b2648ff25fcb3f0bf9cc81deeee9fad1a26073645e17
|
||||
// 080210c801c202 22 0a 20 530bb7c7ba06069997285aff6e0106adfb19ab23c18a7422f5f643b35a6467b3
|
||||
// -------------------------SHA HASH OF PIN
|
||||
|
||||
int length = pin.length() / 2;
|
||||
String len1 = GoogleTVRequest.fixMessage(Integer.toHexString(length + 2));
|
||||
String len2 = GoogleTVRequest.fixMessage(Integer.toHexString(length));
|
||||
return "080210c801c202" + len1 + "0a" + len2 + pin;
|
||||
}
|
||||
}
|
||||
|
||||
public static String loginRequest(int messageId) {
|
||||
String message = "";
|
||||
if (messageId == 1) {
|
||||
// Send app and device name
|
||||
// 080210c801522d 0a 19 636f6d2e676f6f676c652e616e64726f69642e766964656f73 12 10
|
||||
// 73616d73756e6720534d2d4739393855
|
||||
// ------------------LEN com.google.android.videos----------------------------LEN samsung SM-G998U
|
||||
message = "080210c801522d0a19636f6d2e676f6f676c652e616e64726f69642e766964656f73121073616d73756e6720534d2d4739393855";
|
||||
} else if (messageId == 2) {
|
||||
// Unknown but required
|
||||
// 080210c801a201 0e 0a 04 08031006 0a 04 08031004 1802
|
||||
// ---------------LEN---LEN------------LEN
|
||||
message = "080210c801a2010e0a04080310060a04080310041802";
|
||||
} else if (messageId == 3) {
|
||||
// Trigger PIN OSD
|
||||
// ---------------LEN---LEN
|
||||
// 080210c801a201 08 12 04 08031006 1801
|
||||
// 080210c801f201 08 0a 04 08031006 1001
|
||||
message = "080210c801f201080a04080310061001";
|
||||
} else if (messageId == 4) {
|
||||
// 0a41087e123d0a 08 534d2d4739393855 12 07 73616d73756e67 18 01 22 02 3133 2a
|
||||
// ---------------LEN--SM-G998U----------LEN--samsung---------
|
||||
// 19 636f6d2e676f6f676c652e616e64726f69642e766964656f73 32
|
||||
// LEN-com.google.android.videos----------------------------
|
||||
// 07 342e33382e3138
|
||||
// LEN-4.38.18
|
||||
// message =
|
||||
// "0a41087e123d0a08534d2d4739393855120773616d73756e671801220231332a19636f6d2e676f6f676c652e616e64726f69642e766964656f733207342e33382e3138";
|
||||
|
||||
// 0a5708fe0412520a 08 534d2d4739393855 12 07 73616d73756e67 18 01 22 02 3133 2a
|
||||
// -----------------LEN--SM-G998U----------LEN--samsung---------
|
||||
// 19 636f6d2e676f6f676c652e616e64726f69642e766964656f73 32
|
||||
// LEN-com.google.android.videos---------------------------
|
||||
// 1c 342e33392e3538342e3532393538383538332e372d72656c65617365
|
||||
// LEN-4.39.584.529588583.7-release
|
||||
message = "0a5708fe0412520a08534d2d4739393855120773616d73756e671801220231332a19636f6d2e676f6f676c652e616e64726f69642e766964656f73321c342e33392e3538342e3532393538383538332e372d72656c65617365";
|
||||
} else if (messageId == 5) {
|
||||
// Unknown. Sent after "1200" received
|
||||
message = "1202087e";
|
||||
}
|
||||
return message;
|
||||
}
|
||||
|
||||
public static String keepAlive(String request) {
|
||||
// 42 07 08 01 10 e4f1 8d01
|
||||
// 4a 02 08 01
|
||||
|
||||
// 42 08 08 7f 10 b4 908a a819
|
||||
// 4a 02 08 7f
|
||||
|
||||
// 42 09 08 8001 10 ed b78a a819
|
||||
// 4a 03 08 8001
|
||||
|
||||
char[] charArray = request.toCharArray();
|
||||
StringBuilder sb = new StringBuilder();
|
||||
sb.append(request);
|
||||
sb.setLength(sb.toString().length() - 6);
|
||||
String st = "";
|
||||
do {
|
||||
int sbLen = sb.toString().length();
|
||||
st = "" + charArray[sbLen - 2] + charArray[sbLen - 1];
|
||||
if (!DELIMITER_10.equals(st)) {
|
||||
sb.setLength(sbLen - 2);
|
||||
}
|
||||
} while (!DELIMITER_10.equals(st));
|
||||
sb.setLength(sb.toString().length() - 2);
|
||||
|
||||
StringBuilder sbReply = new StringBuilder();
|
||||
for (int i = 4; i < sb.toString().length(); i++) {
|
||||
sbReply.append(charArray[i]);
|
||||
}
|
||||
return "4a" + fixMessage(Integer.toHexString(sbReply.toString().length() / 2)) + sbReply.toString();
|
||||
}
|
||||
|
||||
public static String fixMessage(String tempMsg) {
|
||||
if (tempMsg.length() % 2 > 0) {
|
||||
tempMsg = "0" + tempMsg;
|
||||
}
|
||||
return tempMsg;
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.googletv;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* GoogleTVCommand represents a GoogleTV protocol command
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class GoogleTVUtils {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(GoogleTVUtils.class);
|
||||
|
||||
private static String processMag(final byte[] magnitude) {
|
||||
final int length = magnitude.length;
|
||||
if (length != 0) {
|
||||
final BigInteger bigInteger = new BigInteger(1, magnitude);
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append("%0");
|
||||
sb.append(length + length);
|
||||
sb.append("x");
|
||||
return String.format(sb.toString(), bigInteger);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
private static final byte[] processDigestArray(final byte[] array) {
|
||||
int n = 0;
|
||||
int length;
|
||||
while (true) {
|
||||
length = array.length;
|
||||
if (n >= length || array[n] != 0) {
|
||||
break;
|
||||
}
|
||||
++n;
|
||||
}
|
||||
final int n2 = length - n;
|
||||
final byte[] array2 = new byte[n2];
|
||||
System.arraycopy(array, n, array2, 0, n2);
|
||||
return array2;
|
||||
}
|
||||
|
||||
public static final byte[] processDigest(byte[] digest, Certificate clientCert, Certificate serverCert) {
|
||||
final PublicKey clientPublicKey = clientCert.getPublicKey();
|
||||
final PublicKey serverPublicKey = serverCert.getPublicKey();
|
||||
processMag(digest);
|
||||
if (clientPublicKey instanceof RSAPublicKey && serverPublicKey instanceof RSAPublicKey) {
|
||||
final RSAPublicKey clientRSAPublicKey = (RSAPublicKey) clientPublicKey;
|
||||
final RSAPublicKey serverRSAPublicKey = (RSAPublicKey) serverPublicKey;
|
||||
try {
|
||||
final MessageDigest instance = MessageDigest.getInstance("SHA-256");
|
||||
final byte[] byteArray1 = clientRSAPublicKey.getModulus().abs().toByteArray();
|
||||
final byte[] byteArray2 = clientRSAPublicKey.getPublicExponent().abs().toByteArray();
|
||||
final byte[] byteArray3 = serverRSAPublicKey.getModulus().abs().toByteArray();
|
||||
final byte[] byteArray4 = serverRSAPublicKey.getPublicExponent().abs().toByteArray();
|
||||
final byte[] r1 = processDigestArray(byteArray1);
|
||||
final byte[] r2 = processDigestArray(byteArray2);
|
||||
final byte[] r3 = processDigestArray(byteArray3);
|
||||
final byte[] r4 = processDigestArray(byteArray4);
|
||||
processMag(r1);
|
||||
processMag(r2);
|
||||
processMag(r3);
|
||||
processMag(r4);
|
||||
processMag(digest);
|
||||
instance.update(r1);
|
||||
instance.update(r2);
|
||||
instance.update(r3);
|
||||
instance.update(r4);
|
||||
instance.update(digest);
|
||||
digest = instance.digest();
|
||||
processMag(digest);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
LOGGER.warn("NoSuchAlgorithmException Exception", e);
|
||||
}
|
||||
}
|
||||
return digest;
|
||||
}
|
||||
|
||||
public static String byteArrayToString(byte[] array) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < array.length; i++) {
|
||||
sb.append((char) (array[i] & 0xFF));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String validatePIN(String pin, Certificate clientCert, Certificate serverCert) {
|
||||
char[] charArray = pin.toCharArray();
|
||||
|
||||
String s1 = "" + charArray[0] + charArray[1];
|
||||
String s2 = "" + charArray[2] + charArray[3];
|
||||
String s3 = "" + charArray[4] + charArray[5];
|
||||
int si1 = Integer.parseInt(s1, 16);
|
||||
int si2 = Integer.parseInt(s2, 16);
|
||||
int si3 = Integer.parseInt(s3, 16);
|
||||
|
||||
byte[] sb123 = new byte[] { (byte) si1, (byte) si2, (byte) si3 };
|
||||
byte[] sb23 = new byte[] { (byte) si2, (byte) si3 };
|
||||
byte[] digest = processDigest(sb23, clientCert, serverCert);
|
||||
String digestString = GoogleTVRequest.decodeMessage(byteArrayToString(digest));
|
||||
|
||||
byte[] validPinB = new byte[] { digest[0], (byte) si2, (byte) si3 };
|
||||
String validPin = GoogleTVRequest.decodeMessage(byteArrayToString(validPinB));
|
||||
LOGGER.trace("validatePIN {} {} {} {} {} {}", sb123, digest[0], sb23, validPinB, validPin, digestString);
|
||||
|
||||
return digestString;
|
||||
}
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.shieldtv;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* ShieldTVCommand represents a ShieldTV protocol command
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShieldTVCommand {
|
||||
private String command;
|
||||
|
||||
public ShieldTVCommand(String command) {
|
||||
this.command = command;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return command;
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return command.isEmpty();
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.shieldtv;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ShieldTVConfiguration} class contains fields mapping thing configuration parameters.
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShieldTVConfiguration {
|
||||
|
||||
public String ipAddress = "";
|
||||
public int port = 8987;
|
||||
public int reconnect;
|
||||
public int heartbeat;
|
||||
public String keystoreFileName = "";
|
||||
public String keystorePassword = "";
|
||||
public int delay = 0;
|
||||
public boolean shim;
|
||||
public boolean shimNewKeys;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,61 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.shieldtv;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* The {@link ShieldTVConstants} class defines common constants, which are
|
||||
* used across the shieldtv protocol.
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShieldTVConstants {
|
||||
|
||||
// List of all static String literals
|
||||
public static final String DELIMITER_0 = "0";
|
||||
public static final String DELIMITER_00 = "00";
|
||||
public static final String DELIMITER_08 = "08";
|
||||
public static final String DELIMITER_0A = "0a";
|
||||
public static final String DELIMITER_12 = "12";
|
||||
public static final String DELIMITER_18 = "18";
|
||||
public static final String DELIMITER_22 = "22";
|
||||
public static final String DELIMITER_2A = "2a";
|
||||
public static final String DELIMITER_E9 = "e9";
|
||||
public static final String DELIMITER_EC = "ec";
|
||||
public static final String DELIMITER_F0 = "f0";
|
||||
public static final String DELIMITER_F1 = "f1";
|
||||
public static final String DELIMITER_F3 = "f3";
|
||||
|
||||
public static final String HARD_DROP = "ffffffff";
|
||||
|
||||
public static final String APP_START_SUCCESS = "08f1071202080318f107";
|
||||
public static final String APP_START_FAILED = "08f107120608031202080118f107";
|
||||
public static final String KEEPALIVE_REPLY = "080028fae0a6c0d130";
|
||||
public static final String TIMEOUT = "080a121108b510120c0804120854696d65206f7574180a";
|
||||
|
||||
public static final String MESSAGE_LOWPRIV = "080a12";
|
||||
public static final String MESSAGE_HOSTNAME = "080b12";
|
||||
public static final String MESSAGE_APPDB = "08f10712";
|
||||
public static final String MESSAGE_GOOD_COMMAND = "08f30712";
|
||||
public static final String MESSAGE_PINSTART = "0308cf08";
|
||||
public static final String MESSAGE_CERT_COMING = "20";
|
||||
public static final String MESSAGE_SUCCESS = "08f007";
|
||||
public static final String MESSAGE_APP_SUCCESS = "08ec07";
|
||||
public static final String MESSAGE_APP_GET_SUCCESS = "0803";
|
||||
public static final String MESSAGE_APP_CURRENT = "0807";
|
||||
public static final String MESSAGE_SHORTNAME = "08e807";
|
||||
public static final String MESSAGE_CERT = "08b510";
|
||||
public static final String MESSAGE_CERT_PAYLOAD = "0753756363657373";
|
||||
}
|
@ -0,0 +1,445 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.shieldtv;
|
||||
|
||||
import static org.openhab.binding.androidtv.internal.protocol.shieldtv.ShieldTVConstants.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import javax.xml.bind.DatatypeConverter;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Class responsible for parsing incoming ShieldTV messages. Calls back to an object implementing the
|
||||
* ShieldTVMessageParserCallbacks interface.
|
||||
*
|
||||
* Adapted from Lutron Leap binding
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
|
||||
@NonNullByDefault
|
||||
public class ShieldTVMessageParser {
|
||||
private final Logger logger = LoggerFactory.getLogger(ShieldTVMessageParser.class);
|
||||
|
||||
private final ShieldTVConnectionManager callback;
|
||||
|
||||
public ShieldTVMessageParser(ShieldTVConnectionManager callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
public void handleMessage(String msg) {
|
||||
if (msg.trim().isEmpty()) {
|
||||
return; // Ignore empty lines
|
||||
}
|
||||
|
||||
String thingId = callback.getThingID();
|
||||
String hostName = callback.getHostName();
|
||||
logger.trace("{} - Received ShieldTV message from: {} - Message: {}", thingId, hostName, msg);
|
||||
|
||||
callback.validMessageReceived();
|
||||
|
||||
char[] charArray = msg.toCharArray();
|
||||
|
||||
try {
|
||||
// All lengths are little endian when larger than 0xff
|
||||
if (msg.startsWith(MESSAGE_LOWPRIV) && msg.startsWith(MESSAGE_SHORTNAME, 8)) {
|
||||
// Pre-login Hostname of Shield Replied
|
||||
// 080a 12 1408e807 12 0f08e807 12 LEN Hostname 18d7fd04 180a
|
||||
// 080a 12 1d08e807 12 180801 12 LEN Hostname 18d7fd04 180a
|
||||
// 080a 12 2208e807 12 1d08e807 12 LEN Hostname 18d7fd04 180a
|
||||
// Each chunk ends in 12
|
||||
// 4th chunk represent length of the name.
|
||||
// 5th chunk is the name
|
||||
int chunk = 0;
|
||||
int i = 0;
|
||||
String st = "";
|
||||
StringBuilder hostname = new StringBuilder();
|
||||
while (chunk < 3) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
if (DELIMITER_12.equals(st)) {
|
||||
chunk++;
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
i += 2;
|
||||
int length = Integer.parseInt(st, 16) * 2;
|
||||
int current = i;
|
||||
for (; i < current + length; i = i + 2) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
hostname.append(st);
|
||||
}
|
||||
logger.trace("{} - Shield Hostname: {} {}", thingId, hostname, length);
|
||||
String encHostname = ShieldTVRequest.encodeMessage(hostname.toString());
|
||||
logger.debug("{} - Shield Hostname Encoded: {}", thingId, encHostname);
|
||||
callback.setHostName(encHostname);
|
||||
} else if (msg.startsWith(MESSAGE_HOSTNAME)) {
|
||||
// Longer hostname reply
|
||||
// 080b 12 5b08b510 12 TOTALLEN? 0a LEN Hostname 12 LEN IPADDR Padding? 22 LEN DeviceID 2a LEN arm64-v8a
|
||||
// 2a LEN armeabi-v7a 2a LEN armeabi 180b
|
||||
// It's possible for there to be more or less of the arm lists
|
||||
logger.trace("{} - Longer Hostname Reply", thingId);
|
||||
|
||||
int i = 20;
|
||||
int length;
|
||||
int current;
|
||||
|
||||
// Hostname
|
||||
String st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
|
||||
StringBuilder hostname = new StringBuilder();
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i = i + 2) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
hostname.append(st);
|
||||
}
|
||||
|
||||
i += 2; // 12
|
||||
|
||||
// ipAddress
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
|
||||
StringBuilder ipAddress = new StringBuilder();
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i = i + 2) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
ipAddress.append(st);
|
||||
}
|
||||
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
while (!DELIMITER_22.equals(st)) {
|
||||
i += 2;
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
}
|
||||
|
||||
i += 2; // 22
|
||||
|
||||
// deviceId
|
||||
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
|
||||
StringBuilder deviceId = new StringBuilder();
|
||||
current = i;
|
||||
|
||||
for (; i < current + length; i = i + 2) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
deviceId.append(st);
|
||||
}
|
||||
|
||||
// architectures
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
StringBuilder arch = new StringBuilder();
|
||||
while (DELIMITER_2A.equals(st)) {
|
||||
i += 2;
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
for (; i < current + length; i = i + 2) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
arch.append(st);
|
||||
}
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
if (DELIMITER_2A.equals(st)) {
|
||||
arch.append("2c");
|
||||
}
|
||||
}
|
||||
|
||||
String encHostname = ShieldTVRequest.encodeMessage(hostname.toString());
|
||||
String encIpAddress = ShieldTVRequest.encodeMessage(ipAddress.toString());
|
||||
String encDeviceId = ShieldTVRequest.encodeMessage(deviceId.toString());
|
||||
String encArch = ShieldTVRequest.encodeMessage(arch.toString());
|
||||
logger.debug("{} - Hostname: {} - ipAddress: {} - deviceId: {} - arch: {}", thingId, encHostname,
|
||||
encIpAddress, encDeviceId, encArch);
|
||||
callback.setHostName(encHostname);
|
||||
callback.setDeviceID(encDeviceId);
|
||||
callback.setArch(encArch);
|
||||
} else if (APP_START_SUCCESS.equals(msg)) {
|
||||
// App successfully started
|
||||
logger.debug("{} - App started successfully", thingId);
|
||||
} else if (APP_START_FAILED.equals(msg)) {
|
||||
// App failed to start
|
||||
logger.debug("{} - App failed to start", thingId);
|
||||
} else if (msg.startsWith(MESSAGE_APPDB) && msg.startsWith(DELIMITER_0A, 18)) {
|
||||
// Individual update?
|
||||
// 08f10712 5808061254 0a LEN app.name 12 LEN app.real.name 22 LEN URL 2801 300118f107
|
||||
logger.info("{} - Individual App Update - Please Report This: {}", thingId, msg);
|
||||
} else if (msg.startsWith(MESSAGE_APPDB) && (msg.length() > 30)) {
|
||||
// Massive dump of currently installed apps
|
||||
// 08f10712 d81f080112 d31f0a540a LEN app.name 12 LEN app.real.name 22 LEN URL 2801 30010a650a LEN
|
||||
Map<String, String> appNameDB = new HashMap<>();
|
||||
Map<String, String> appURLDB = new HashMap<>();
|
||||
int appCount = 0;
|
||||
int i = 18;
|
||||
String st = "";
|
||||
int length;
|
||||
int current;
|
||||
StringBuilder appSBPrepend = new StringBuilder();
|
||||
StringBuilder appSBDN = new StringBuilder();
|
||||
|
||||
// Load default apps that don't get sent in payload
|
||||
|
||||
appNameDB.put("com.google.android.tvlauncher", "Android TV Home");
|
||||
appURLDB.put("com.google.android.tvlauncher", "");
|
||||
|
||||
appNameDB.put("com.google.android.katniss", "Google app for Android TV");
|
||||
appURLDB.put("com.google.android.katniss", "");
|
||||
|
||||
appNameDB.put("com.google.android.katnisspx", "Google app for Android TV (Pictures)");
|
||||
appURLDB.put("com.google.android.katnisspx", "");
|
||||
|
||||
appNameDB.put("com.google.android.backdrop", "Backdrop Daydream");
|
||||
appURLDB.put("com.google.android.backdrop", "");
|
||||
|
||||
// Packet will end with 300118f107 after last entry
|
||||
|
||||
while (i < msg.length() - 10) {
|
||||
StringBuilder appSBName = new StringBuilder();
|
||||
StringBuilder appSBURL = new StringBuilder();
|
||||
|
||||
// There are instances such as plex where multiple apps are sent as part of the same payload
|
||||
// This is identified when 12 is the beginning of the set
|
||||
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
|
||||
if (!DELIMITER_12.equals(st)) {
|
||||
appSBPrepend = new StringBuilder();
|
||||
appSBDN = new StringBuilder();
|
||||
|
||||
appCount++;
|
||||
|
||||
// App Prepend
|
||||
// Usually 10 in length but can be longer or shorter so look for 0a twice
|
||||
do {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
appSBPrepend.append(st);
|
||||
i += 2;
|
||||
} while (!DELIMITER_0A.equals(st));
|
||||
do {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
appSBPrepend.append(st);
|
||||
i += 2;
|
||||
} while (!DELIMITER_0A.equals(st));
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
|
||||
// Look for a third 0a, but only if 12 is not down the line
|
||||
// If 12 is exactly 20 away from 0a that means that the DN was actually 10 long
|
||||
String st2 = "" + charArray[i + 22] + "" + charArray[i + 23];
|
||||
if (DELIMITER_0A.equals(st.toString()) && !DELIMITER_12.equals(st2)) {
|
||||
appSBPrepend.append(st);
|
||||
i += 2;
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
}
|
||||
|
||||
// app DN
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
i += 2;
|
||||
current = i;
|
||||
for (; i < current + length; i = i + 2) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
appSBDN.append(st);
|
||||
}
|
||||
} else {
|
||||
logger.trace("Second Entry");
|
||||
}
|
||||
|
||||
// App Name
|
||||
|
||||
i += 2; // 12 delimiter
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
i += 2;
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
current = i;
|
||||
for (; i < current + length; i = i + 2) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
appSBName.append(st);
|
||||
}
|
||||
|
||||
// There are times where there is padding here for no reason beyond the specified length.
|
||||
// Proceed forward until we get to the 22 delimiter
|
||||
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
while (!DELIMITER_22.equals(st)) {
|
||||
i += 2;
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
}
|
||||
|
||||
// App URL
|
||||
i += 2; // 22 delimiter
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
i += 2;
|
||||
length = Integer.parseInt(st, 16) * 2;
|
||||
current = i;
|
||||
for (; i < current + length; i = i + 2) {
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
appSBURL.append(st);
|
||||
}
|
||||
st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
if (!DELIMITER_12.equals(st)) {
|
||||
i += 4; // terminates 2801
|
||||
}
|
||||
String appPrepend = appSBPrepend.toString();
|
||||
String appDN = ShieldTVRequest.encodeMessage(appSBDN.toString());
|
||||
String appName = ShieldTVRequest.encodeMessage(appSBName.toString());
|
||||
String appURL = ShieldTVRequest.encodeMessage(appSBURL.toString());
|
||||
logger.debug("{} - AppPrepend: {} AppDN: {} AppName: {} AppURL: {}", thingId, appPrepend, appDN,
|
||||
appName, appURL);
|
||||
appNameDB.put(appDN, appName);
|
||||
appURLDB.put(appDN, appURL);
|
||||
}
|
||||
if (appCount > 0) {
|
||||
Map<String, String> sortedAppNameDB = new LinkedHashMap<>();
|
||||
List<String> valueList = new ArrayList<>();
|
||||
for (Map.Entry<String, String> entry : appNameDB.entrySet()) {
|
||||
valueList.add(entry.getValue());
|
||||
}
|
||||
Collections.sort(valueList);
|
||||
for (String str : valueList) {
|
||||
for (Entry<String, String> entry : appNameDB.entrySet()) {
|
||||
if (entry.getValue().equals(str)) {
|
||||
sortedAppNameDB.put(entry.getKey(), str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.trace("{} - MP appNameDB: {} sortedAppNameDB: {} appURLDB: {}", thingId,
|
||||
appNameDB.toString(), sortedAppNameDB.toString(), appURLDB.toString());
|
||||
callback.setAppDB(sortedAppNameDB, appURLDB);
|
||||
} else {
|
||||
logger.warn("{} - MP empty msg: {} appDB appNameDB: {} appURLDB: {}", thingId, msg,
|
||||
appNameDB.toString(), appURLDB.toString());
|
||||
}
|
||||
} else if (msg.startsWith(MESSAGE_GOOD_COMMAND)) {
|
||||
// This has something to do with successful command response, maybe.
|
||||
} else if (KEEPALIVE_REPLY.equals(msg)) {
|
||||
// Keepalive Reply
|
||||
} else if (msg.startsWith(MESSAGE_LOWPRIV) && msg.startsWith(MESSAGE_PINSTART, 6)) {
|
||||
// 080a 12 0308cf08 180a
|
||||
logger.debug("PIN Process Started");
|
||||
} else if (msg.startsWith(MESSAGE_CERT_COMING) && msg.length() == 6) {
|
||||
// This seems to be 20**** when observed. It is unclear what this does.
|
||||
// This seems to send immediately before the certificate reply and as a reply to the pin being sent
|
||||
} else if (msg.startsWith(MESSAGE_SUCCESS)) {
|
||||
// Successful command received
|
||||
// 08f007 12 0c 0804 12 08 0a0608 01100c200f 18f007 - GOOD LOGIN
|
||||
// 08f007 12 LEN 0804 12 LEN 0a0608 01100c200f 18f007
|
||||
//
|
||||
// 08f00712 0c 0804 12 08 0a0608 01100e200f 18f007 KEY_VOLDOWN
|
||||
// 08f00712 0c 0804 12 08 0a0608 01100f200f 18f007 KEY_VOLUP
|
||||
// 08f00712 0c 0804 12 08 0a0608 01200f2801 18f007 KEY_MUTE
|
||||
logger.info("{} - Login Successful to {}", thingId, callback.getHostName());
|
||||
callback.setLoggedIn(true);
|
||||
} else if (TIMEOUT.equals(msg)) {
|
||||
// Timeout
|
||||
// 080a 12 1108b510 12 0c0804 12 08 54696d65206f7574 180a
|
||||
// 080a 12 1108b510 12 0c0804 12 LEN Timeout 180a
|
||||
logger.debug("{} - Timeout {}", thingId, msg);
|
||||
} else if (msg.startsWith(MESSAGE_APP_SUCCESS) && msg.startsWith(MESSAGE_APP_GET_SUCCESS, 10)) {
|
||||
// Get current app command successful. Usually paired with 0807 reply below.
|
||||
} else if (msg.startsWith(MESSAGE_APP_SUCCESS) && msg.startsWith(MESSAGE_APP_CURRENT, 10)) {
|
||||
// Current App
|
||||
// 08ec07 12 2a0807 22 262205 656e5f555342 1d 636f6d2e676f6f676c652e616e64726f69642e74766c61756e63686572
|
||||
// 18ec07
|
||||
// 08ec07 12 2a0807 22 262205 en_USB LEN AppName 18ec07
|
||||
StringBuilder appName = new StringBuilder();
|
||||
String lengthStr = "" + charArray[34] + charArray[35];
|
||||
int length = Integer.parseInt(lengthStr, 16) * 2;
|
||||
for (int i = 36; i < 36 + length; i++) {
|
||||
appName.append(charArray[i]);
|
||||
}
|
||||
logger.debug("{} - Current App: {}", thingId, ShieldTVRequest.encodeMessage(appName.toString()));
|
||||
callback.setCurrentApp(ShieldTVRequest.encodeMessage(appName.toString()));
|
||||
} else if (msg.startsWith(MESSAGE_LOWPRIV) && msg.startsWith(MESSAGE_CERT, 10)) {
|
||||
// Certificate Reply
|
||||
// |--6-----------12-----------10---------------16---------6--- = 50 characters long
|
||||
// |080a 12 ad10 08b510 12 a710 0801 12 07 53756363657373 1ac009 3082... 3082... 180a
|
||||
// |080a 12 9f10 08b510 12 9910 0801 12 07 53756363657373 1ac209 3082... 3082... 180a
|
||||
// |--------Little Endian Total Payload Length
|
||||
// |-----------------------Little Endian Remaining Payload Length
|
||||
// |-----------------------------------Length of SUCCESS
|
||||
// |--------------------------------------ASCII: SUCCESS
|
||||
// |-----------------------------------------------------Little Endian Length (e.g. 09c0 and 09c2 above)
|
||||
// |------------------------------------------------------------Priv Key RSA 2048
|
||||
// |--------------------------------------------------------------------Cert X.509
|
||||
if (msg.startsWith(MESSAGE_CERT_PAYLOAD, 28)) {
|
||||
StringBuilder preamble = new StringBuilder();
|
||||
StringBuilder privKey = new StringBuilder();
|
||||
StringBuilder pubKey = new StringBuilder();
|
||||
int i = 0;
|
||||
int current;
|
||||
for (; i < 44; i++) {
|
||||
preamble.append(charArray[i]);
|
||||
}
|
||||
logger.trace("{} - Cert Preamble: {}", thingId, preamble.toString());
|
||||
|
||||
i += 2; // 1a
|
||||
String st = "" + charArray[i + 2] + "" + charArray[i + 3] + "" + charArray[i] + ""
|
||||
+ charArray[i + 1];
|
||||
int privLen = 2246 + ((Integer.parseInt(st, 16) - 2400) * 2);
|
||||
i += 4; // length
|
||||
current = i;
|
||||
|
||||
logger.trace("{} - Cert privLen: {} {}", thingId, st, privLen);
|
||||
|
||||
for (; i < current + privLen; i++) {
|
||||
privKey.append(charArray[i]);
|
||||
}
|
||||
|
||||
logger.trace("{} - Cert privKey: {} {}", thingId, privLen, privKey.toString());
|
||||
|
||||
for (; i < msg.length() - 4; i++) {
|
||||
pubKey.append(charArray[i]);
|
||||
}
|
||||
|
||||
logger.trace("{} - Cert pubKey: {} {}", thingId, msg.length() - privLen - 4, pubKey.toString());
|
||||
|
||||
logger.debug("{} - Cert Pair Received privLen: {} pubLen: {}", thingId, privLen,
|
||||
msg.length() - privLen - 4);
|
||||
|
||||
byte[] privKeyB64Byte = DatatypeConverter.parseHexBinary(privKey.toString());
|
||||
byte[] pubKeyB64Byte = DatatypeConverter.parseHexBinary(pubKey.toString());
|
||||
|
||||
String privKeyB64 = Base64.getEncoder().encodeToString(privKeyB64Byte);
|
||||
String pubKeyB64 = Base64.getEncoder().encodeToString(pubKeyB64Byte);
|
||||
|
||||
callback.setKeys(privKeyB64, pubKeyB64);
|
||||
} else {
|
||||
logger.info("{} - Pin Process Failed.", thingId);
|
||||
}
|
||||
} else {
|
||||
logger.info("{} - Unknown payload received. {}", thingId, msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.info("{} - Message Parser Caught Exception", thingId, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,103 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.protocol.shieldtv;
|
||||
|
||||
import static org.openhab.binding.androidtv.internal.AndroidTVBindingConstants.*;
|
||||
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
|
||||
/**
|
||||
* Contains static methods for constructing LEAP messages
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class ShieldTVRequest {
|
||||
|
||||
public static String encodeMessage(String message) {
|
||||
StringBuilder reply = new StringBuilder();
|
||||
char[] charArray = message.toCharArray();
|
||||
for (int i = 0; i < charArray.length; i = i + 2) {
|
||||
String st = "" + charArray[i] + "" + charArray[i + 1];
|
||||
char ch = (char) Integer.parseInt(st, 16);
|
||||
reply.append(ch);
|
||||
}
|
||||
return reply.toString();
|
||||
}
|
||||
|
||||
public static String decodeMessage(String message) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
char ch[] = message.toCharArray();
|
||||
for (int i = 0; i < ch.length; i++) {
|
||||
String hexString = Integer.toHexString(ch[i]);
|
||||
if (hexString.length() % 2 > 0) {
|
||||
sb.append('0');
|
||||
}
|
||||
sb.append(hexString);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public static String pinRequest(String pin) {
|
||||
if (PIN_REQUEST.equals(pin)) {
|
||||
String message = "080a120308cd08";
|
||||
return message;
|
||||
} else {
|
||||
String prefix = "080a121f08d108121a0a06";
|
||||
String encodedPin = decodeMessage(pin);
|
||||
String suffix = "121036646564646461326639366635646261";
|
||||
return prefix + encodedPin + suffix;
|
||||
}
|
||||
}
|
||||
|
||||
public static String loginRequest() {
|
||||
String message = "0801121a0801121073616d73756e6720534d2d4739393855180128fbff04";
|
||||
return message;
|
||||
}
|
||||
|
||||
public static String keepAlive() {
|
||||
String message = "080028fae0a6c0d130";
|
||||
return message;
|
||||
}
|
||||
|
||||
private static String fixMessage(String tempMsg) {
|
||||
if (tempMsg.length() % 2 > 0) {
|
||||
tempMsg = "0" + tempMsg;
|
||||
}
|
||||
return tempMsg;
|
||||
}
|
||||
|
||||
public static String startApp(String message) {
|
||||
int length = message.length();
|
||||
String len1 = fixMessage(Integer.toHexString(length + 6));
|
||||
String len2 = fixMessage(Integer.toHexString(length + 2));
|
||||
String len3 = fixMessage(Integer.toHexString(length));
|
||||
String reply = "08f10712" + len1 + "080212" + len2 + "0a" + len3 + decodeMessage(message);
|
||||
return reply;
|
||||
}
|
||||
// 080b120308cd08 - Longer Hostname Reply
|
||||
// 08f30712020805 - Unknown
|
||||
// 08f10712020800 - Get all apps
|
||||
// 08ec0712020806 - Get current app
|
||||
|
||||
public static String keyboardEntry(String entry) {
|
||||
// 08ec07120d08081205616263646532020a0a
|
||||
// 08ec0712 0d 0808 12 05 6162636465 3202 0a0a
|
||||
int length = entry.length();
|
||||
String len1 = fixMessage(Integer.toHexString(length + 8));
|
||||
String len2 = fixMessage(Integer.toHexString(length));
|
||||
String len3 = fixMessage(Integer.toHexString(length * 2));
|
||||
String reply = "08ec0712" + len1 + "080812" + len2 + decodeMessage(entry) + "3202" + len3 + len3;
|
||||
return reply;
|
||||
}
|
||||
}
|
@ -0,0 +1,291 @@
|
||||
/**
|
||||
* Copyright (c) 2010-2023 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.androidtv.internal.utils;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.KeyStore;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.Security;
|
||||
import java.security.Signature;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.security.spec.PKCS8EncodedKeySpec;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.spec.GCMParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
|
||||
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
|
||||
import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
import org.bouncycastle.operator.ContentSigner;
|
||||
import org.bouncycastle.operator.OperatorCreationException;
|
||||
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||
import org.eclipse.jdt.annotation.Nullable;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* The {@link AndroidTVPKI} class controls all aspects of the PKI/keyStore
|
||||
*
|
||||
* Some methods adapted from Bosch binding
|
||||
*
|
||||
* @author Ben Rosenblum - Initial contribution
|
||||
*/
|
||||
@NonNullByDefault
|
||||
public class AndroidTVPKI {
|
||||
|
||||
private final Logger logger = LoggerFactory.getLogger(AndroidTVPKI.class);
|
||||
|
||||
private final int keySize = 128;
|
||||
private final int dataLength = 128;
|
||||
|
||||
private String privKey = "";
|
||||
private String cert = "";
|
||||
private String keystoreFileName = "";
|
||||
private String keystoreAlgorithm = "RSA";
|
||||
private int keyLength = 2048;
|
||||
private String alias = "openhab";
|
||||
private String distName = "CN=openHAB, O=openHAB, L=None, ST=None, C=None";
|
||||
private String cipher = "AES/GCM/NoPadding";
|
||||
private String keyAlgorithm = "";
|
||||
|
||||
private @Nullable Cipher encryptionCipher;
|
||||
|
||||
public AndroidTVPKI() {
|
||||
try {
|
||||
encryptionCipher = Cipher.getInstance(cipher);
|
||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
|
||||
logger.debug("Could not get cipher instance", e);
|
||||
}
|
||||
}
|
||||
|
||||
public byte[] generateEncryptionKey() {
|
||||
Key key;
|
||||
try {
|
||||
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
|
||||
keyGenerator.init(keySize);
|
||||
key = keyGenerator.generateKey();
|
||||
byte[] newKey = key.getEncoded();
|
||||
this.keyAlgorithm = key.getAlgorithm();
|
||||
return newKey;
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
logger.debug("Could not generate encryption keys", e);
|
||||
}
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
private Key convertByteToKey(byte[] keyString) {
|
||||
Key key = new SecretKeySpec(keyString, keyAlgorithm);
|
||||
return key;
|
||||
}
|
||||
|
||||
public String encrypt(String data, Key key) throws Exception {
|
||||
return encrypt(data, key, this.cipher);
|
||||
}
|
||||
|
||||
public String encrypt(String data, Key key, String cipher) throws Exception {
|
||||
byte[] dataInBytes = data.getBytes();
|
||||
Cipher encryptionCipher = this.encryptionCipher;
|
||||
if (encryptionCipher != null) {
|
||||
encryptionCipher.init(Cipher.ENCRYPT_MODE, key);
|
||||
byte[] encryptedBytes = encryptionCipher.doFinal(dataInBytes);
|
||||
return Base64.getEncoder().encodeToString(encryptedBytes);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public String decrypt(String encryptedData, Key key) throws Exception {
|
||||
return decrypt(encryptedData, key, this.cipher);
|
||||
}
|
||||
|
||||
public String decrypt(String encryptedData, Key key, String cipher) throws Exception {
|
||||
byte[] dataInBytes = Base64.getDecoder().decode(encryptedData);
|
||||
Cipher decryptionCipher = Cipher.getInstance(cipher);
|
||||
Cipher encryptionCipher = this.encryptionCipher;
|
||||
if (encryptionCipher != null) {
|
||||
GCMParameterSpec spec = new GCMParameterSpec(dataLength, encryptionCipher.getIV());
|
||||
decryptionCipher.init(Cipher.DECRYPT_MODE, key, spec);
|
||||
byte[] decryptedBytes = decryptionCipher.doFinal(dataInBytes);
|
||||
return new String(decryptedBytes);
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public void setPrivKey(String privKey, byte[] keyString) throws Exception {
|
||||
Key key = convertByteToKey(keyString);
|
||||
this.privKey = encrypt(privKey, key);
|
||||
}
|
||||
|
||||
public String getPrivKey(byte[] keyString) throws Exception {
|
||||
Key key = convertByteToKey(keyString);
|
||||
return decrypt(this.privKey, key);
|
||||
}
|
||||
|
||||
public void setCert(String cert) {
|
||||
this.cert = cert;
|
||||
}
|
||||
|
||||
public void setCert(Certificate cert) throws CertificateEncodingException {
|
||||
this.cert = new String(Base64.getEncoder().encode(cert.getEncoded()));
|
||||
}
|
||||
|
||||
public Certificate getCert() throws CertificateException {
|
||||
Certificate cert = CertificateFactory.getInstance("X.509")
|
||||
.generateCertificate(new ByteArrayInputStream(Base64.getDecoder().decode(this.cert.getBytes())));
|
||||
return cert;
|
||||
}
|
||||
|
||||
public void setAlias(String alias) {
|
||||
this.alias = alias;
|
||||
}
|
||||
|
||||
public String getAlias() {
|
||||
return this.alias;
|
||||
}
|
||||
|
||||
public void setAlgorithm(String keystoreAlgorithm) {
|
||||
this.keystoreAlgorithm = keystoreAlgorithm;
|
||||
}
|
||||
|
||||
public String getAlgorithm() {
|
||||
return this.keystoreAlgorithm;
|
||||
}
|
||||
|
||||
public void setKeyLength(int keyLength) {
|
||||
this.keyLength = keyLength;
|
||||
}
|
||||
|
||||
public int getKeyLength() {
|
||||
return this.keyLength;
|
||||
}
|
||||
|
||||
public void setDistName(String distName) {
|
||||
this.distName = distName;
|
||||
}
|
||||
|
||||
public String getDistName() {
|
||||
return this.distName;
|
||||
}
|
||||
|
||||
public void setKeystoreFileName(String keystoreFileName) {
|
||||
this.keystoreFileName = keystoreFileName;
|
||||
}
|
||||
|
||||
public String getKeystoreFileName() {
|
||||
return this.keystoreFileName;
|
||||
}
|
||||
|
||||
public void setKeys(String privKey, byte[] keyString, String cert) throws GeneralSecurityException, Exception {
|
||||
setPrivKey(privKey, keyString);
|
||||
setCert(cert);
|
||||
}
|
||||
|
||||
public void setKeyStore(String keystoreFileName) {
|
||||
this.keystoreFileName = keystoreFileName;
|
||||
}
|
||||
|
||||
public void loadFromKeyStore(String keystoreFileName, String keystorePassword, byte[] keyString)
|
||||
throws GeneralSecurityException, IOException, Exception {
|
||||
this.keystoreFileName = keystoreFileName;
|
||||
loadFromKeyStore(keystorePassword, keyString);
|
||||
}
|
||||
|
||||
public void loadFromKeyStore(String keystorePassword, byte[] keyString)
|
||||
throws GeneralSecurityException, IOException, Exception {
|
||||
Key key = convertByteToKey(keyString);
|
||||
KeyStore keystore = KeyStore.getInstance("JKS");
|
||||
FileInputStream keystoreInputStream = new FileInputStream(this.keystoreFileName);
|
||||
keystore.load(keystoreInputStream, keystorePassword.toCharArray());
|
||||
byte[] byteKey = keystore.getKey(this.alias, keystorePassword.toCharArray()).getEncoded();
|
||||
this.privKey = encrypt(new String(Base64.getEncoder().encode(byteKey)), key);
|
||||
setCert(keystore.getCertificate(this.alias));
|
||||
}
|
||||
|
||||
public KeyStore getKeyStore(String keystorePassword, byte[] keyString)
|
||||
throws GeneralSecurityException, IOException, Exception {
|
||||
KeyStore keystore = KeyStore.getInstance("JKS");
|
||||
keystore.load(null, null);
|
||||
byte[] pkcs8EncodedBytes = Base64.getDecoder().decode(getPrivKey(keyString));
|
||||
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(pkcs8EncodedBytes);
|
||||
KeyFactory kf = KeyFactory.getInstance(this.keystoreAlgorithm);
|
||||
keystore.setKeyEntry(this.alias, kf.generatePrivate(keySpec), keystorePassword.toCharArray(),
|
||||
new java.security.cert.Certificate[] { getCert() });
|
||||
return keystore;
|
||||
}
|
||||
|
||||
public void saveKeyStore(String keystorePassword, byte[] keyString)
|
||||
throws GeneralSecurityException, IOException, Exception {
|
||||
saveKeyStore(this.keystoreFileName, keystorePassword, keyString);
|
||||
}
|
||||
|
||||
public void saveKeyStore(String keystoreFileName, String keystorePassword, byte[] keyString)
|
||||
throws GeneralSecurityException, IOException, Exception {
|
||||
FileOutputStream keystoreStream = new FileOutputStream(keystoreFileName);
|
||||
KeyStore keystore = getKeyStore(keystorePassword, keyString);
|
||||
keystore.store(keystoreStream, keystorePassword.toCharArray());
|
||||
}
|
||||
|
||||
private X509Certificate generateSelfSignedCertificate(KeyPair keyPair, String distName)
|
||||
throws GeneralSecurityException, OperatorCreationException {
|
||||
final Instant now = Instant.now();
|
||||
final Date notBefore = Date.from(now);
|
||||
final Date notAfter = Date.from(now.plus(Duration.ofDays(365 * 10)));
|
||||
X500Name name = new X500Name(distName);
|
||||
X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(name,
|
||||
BigInteger.valueOf(now.toEpochMilli()), notBefore, notAfter, name, keyPair.getPublic());
|
||||
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSA").build(keyPair.getPrivate());
|
||||
return new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider())
|
||||
.getCertificate(certificateBuilder.build(contentSigner));
|
||||
}
|
||||
|
||||
public void generateNewKeyPair(byte[] keyString)
|
||||
throws GeneralSecurityException, OperatorCreationException, IOException, Exception {
|
||||
Key key = convertByteToKey(keyString);
|
||||
KeyPairGenerator kpg = KeyPairGenerator.getInstance(this.keystoreAlgorithm);
|
||||
kpg.initialize(this.keyLength);
|
||||
KeyPair kp = kpg.generateKeyPair();
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
Signature signer = Signature.getInstance("SHA256withRSA", "BC");
|
||||
signer.initSign(kp.getPrivate());
|
||||
signer.update("openhab".getBytes(StandardCharsets.UTF_8));
|
||||
signer.sign();
|
||||
X509Certificate signedcert = generateSelfSignedCertificate(kp, this.distName);
|
||||
this.privKey = encrypt(new String(Base64.getEncoder().encode(kp.getPrivate().getEncoded())), key);
|
||||
setCert(signedcert);
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<addon:addon id="androidtv" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:addon="https://openhab.org/schemas/addon/v1.0.0"
|
||||
xsi:schemaLocation="https://openhab.org/schemas/addon/v1.0.0 https://openhab.org/schemas/addon-1.0.0.xsd">
|
||||
|
||||
<type>binding</type>
|
||||
<name>AndroidTV Binding</name>
|
||||
<description>This is the add-on for AndroidTV.</description>
|
||||
<connection>local</connection>
|
||||
|
||||
</addon:addon>
|
@ -0,0 +1,69 @@
|
||||
# add-on
|
||||
|
||||
addon.androidtv.name = AndroidTV Binding
|
||||
addon.androidtv.description = This is the add-on for AndroidTV.
|
||||
|
||||
# thing types
|
||||
|
||||
thing-type.androidtv.googletv.label = GoogleTV
|
||||
thing-type.androidtv.googletv.description = GoogleTV
|
||||
thing-type.androidtv.shieldtv.label = ShieldTV
|
||||
thing-type.androidtv.shieldtv.description = Nvidia ShieldTV
|
||||
|
||||
# thing types config
|
||||
|
||||
thing-type.config.androidtv.googletv.delay.label = Delay
|
||||
thing-type.config.androidtv.googletv.delay.description = Delay between messages
|
||||
thing-type.config.androidtv.googletv.heartbeat.label = Heartbeat Frequency
|
||||
thing-type.config.androidtv.googletv.heartbeat.description = Frequency of heartbeats
|
||||
thing-type.config.androidtv.googletv.ipAddress.label = Hostname
|
||||
thing-type.config.androidtv.googletv.ipAddress.description = Hostname or IP address of the device
|
||||
thing-type.config.androidtv.googletv.keystoreFileName.label = Keystore File Name
|
||||
thing-type.config.androidtv.googletv.keystoreFileName.description = Java keystore containing key and certs
|
||||
thing-type.config.androidtv.googletv.keystorePassword.label = Keystore Password
|
||||
thing-type.config.androidtv.googletv.keystorePassword.description = Password for the keystore file
|
||||
thing-type.config.androidtv.googletv.port.label = Port
|
||||
thing-type.config.androidtv.googletv.port.description = Port to connect to
|
||||
thing-type.config.androidtv.googletv.reconnect.label = Reconnect Delay
|
||||
thing-type.config.androidtv.googletv.reconnect.description = Delay between reconnection attempts
|
||||
thing-type.config.androidtv.shieldtv.delay.label = Delay
|
||||
thing-type.config.androidtv.shieldtv.delay.description = Delay between messages
|
||||
thing-type.config.androidtv.shieldtv.heartbeat.label = Hearbeat Frequency
|
||||
thing-type.config.androidtv.shieldtv.heartbeat.description = Frequency of heartbeats
|
||||
thing-type.config.androidtv.shieldtv.ipAddress.label = Hostname
|
||||
thing-type.config.androidtv.shieldtv.ipAddress.description = Hostname or IP address of the device
|
||||
thing-type.config.androidtv.shieldtv.keystoreFileName.label = Keystore File Name
|
||||
thing-type.config.androidtv.shieldtv.keystoreFileName.description = Java keystore containing key and certs
|
||||
thing-type.config.androidtv.shieldtv.keystorePassword.label = Keystore Password
|
||||
thing-type.config.androidtv.shieldtv.keystorePassword.description = Password for the keystore file
|
||||
thing-type.config.androidtv.shieldtv.port.label = Port
|
||||
thing-type.config.androidtv.shieldtv.port.description = Port to connect to
|
||||
thing-type.config.androidtv.shieldtv.reconnect.label = Reconnect Delay
|
||||
thing-type.config.androidtv.shieldtv.reconnect.description = Delay between reconnection attempts
|
||||
|
||||
# channel types
|
||||
|
||||
channel-type.androidtv.app.label = App
|
||||
channel-type.androidtv.app.description = App Control
|
||||
channel-type.androidtv.appname.label = App Name
|
||||
channel-type.androidtv.appname.description = App Name
|
||||
channel-type.androidtv.appurl.label = App URL
|
||||
channel-type.androidtv.appurl.description = App URL
|
||||
channel-type.androidtv.debug.label = DEBUG Command
|
||||
channel-type.androidtv.debug.description = Binding control (for debugging)
|
||||
channel-type.androidtv.keyboard.label = Keyboard
|
||||
channel-type.androidtv.keyboard.description = Keyboard Entry
|
||||
channel-type.androidtv.keycode.label = Keycode
|
||||
channel-type.androidtv.keycode.description = Send keycode
|
||||
channel-type.androidtv.keypress.label = Key Press
|
||||
channel-type.androidtv.keypress.description = Send key press
|
||||
channel-type.androidtv.pincode.label = Pin Code
|
||||
channel-type.androidtv.pincode.description = Send Pin Code
|
||||
channel-type.androidtv.player.label = Player
|
||||
channel-type.androidtv.player.description = Player Control
|
||||
|
||||
# custom thing status
|
||||
offline.protocols-starting = Protocols Starting
|
||||
offline.googletv-address-not-specified = googletv address not specified
|
||||
offline.shieldtv-address-not-specified = shieldtv address not specified
|
||||
|
@ -0,0 +1,192 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<thing:thing-descriptions bindingId="androidtv"
|
||||
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">
|
||||
|
||||
<thing-type id="shieldtv">
|
||||
|
||||
<label>ShieldTV</label>
|
||||
<description>Nvidia ShieldTV</description>
|
||||
|
||||
<channels>
|
||||
<channel id="debug" typeId="debug"/>
|
||||
<channel id="keypress" typeId="keypress"/>
|
||||
<channel id="keyboard" typeId="keyboard"/>
|
||||
<channel id="keycode" typeId="keycode"/>
|
||||
<channel id="pincode" typeId="pincode"/>
|
||||
<channel id="app" typeId="app"/>
|
||||
<channel id="appname" typeId="appname"/>
|
||||
<channel id="appurl" typeId="appurl"/>
|
||||
<channel id="player" typeId="player"/>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="volume" typeId="system.volume"/>
|
||||
<channel id="mute" typeId="system.mute"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="deviceName">unknown</property>
|
||||
<property name="deviceID">unknown</property>
|
||||
<property name="architectures">unknown</property>
|
||||
<property name="manufacturer">unknown</property>
|
||||
<property name="model">unknown</property>
|
||||
<property name="androidVersion">unknown</property>
|
||||
<property name="remoteServer">unknown</property>
|
||||
<property name="remoteServerVersion">unknown</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>ipAddress</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Hostname</label>
|
||||
<description>Hostname or IP address of the device</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer">
|
||||
<label>Port</label>
|
||||
<description>Port to connect to</description>
|
||||
</parameter>
|
||||
<parameter name="keystoreFileName" type="text">
|
||||
<label>Keystore File Name</label>
|
||||
<description>Java keystore containing key and certs</description>
|
||||
</parameter>
|
||||
<parameter name="keystorePassword" type="text">
|
||||
<context>password</context>
|
||||
<label>Keystore Password</label>
|
||||
<description>Password for the keystore file</description>
|
||||
</parameter>
|
||||
<parameter name="reconnect" type="integer">
|
||||
<label>Reconnect Delay</label>
|
||||
<description>Delay between reconnection attempts</description>
|
||||
</parameter>
|
||||
<parameter name="heartbeat" type="integer">
|
||||
<label>Hearbeat Frequency</label>
|
||||
<description>Frequency of heartbeats</description>
|
||||
</parameter>
|
||||
<parameter name="delay" type="integer">
|
||||
<label>Delay</label>
|
||||
<description>Delay between messages</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<thing-type id="googletv">
|
||||
|
||||
<label>GoogleTV</label>
|
||||
<description>GoogleTV</description>
|
||||
|
||||
<channels>
|
||||
<channel id="debug" typeId="debug"/>
|
||||
<channel id="keypress" typeId="keypress"/>
|
||||
<channel id="keyboard" typeId="keyboard"/>
|
||||
<channel id="keycode" typeId="keycode"/>
|
||||
<channel id="pincode" typeId="pincode"/>
|
||||
<channel id="app" typeId="app"/>
|
||||
<channel id="player" typeId="player"/>
|
||||
<channel id="power" typeId="system.power"/>
|
||||
<channel id="volume" typeId="system.volume"/>
|
||||
<channel id="mute" typeId="system.mute"/>
|
||||
</channels>
|
||||
|
||||
<properties>
|
||||
<property name="manufacturer">unknown</property>
|
||||
<property name="model">unknown</property>
|
||||
<property name="androidVersion">unknown</property>
|
||||
<property name="remoteServer">unknown</property>
|
||||
<property name="remoteServerVersion">unknown</property>
|
||||
</properties>
|
||||
|
||||
<representation-property>ipAddress</representation-property>
|
||||
|
||||
<config-description>
|
||||
<parameter name="ipAddress" type="text" required="true">
|
||||
<context>network-address</context>
|
||||
<label>Hostname</label>
|
||||
<description>Hostname or IP address of the device</description>
|
||||
</parameter>
|
||||
<parameter name="port" type="integer">
|
||||
<label>Port</label>
|
||||
<description>Port to connect to</description>
|
||||
</parameter>
|
||||
<parameter name="keystoreFileName" type="text">
|
||||
<label>Keystore File Name</label>
|
||||
<description>Java keystore containing key and certs</description>
|
||||
</parameter>
|
||||
<parameter name="keystorePassword" type="text">
|
||||
<context>password</context>
|
||||
<label>Keystore Password</label>
|
||||
<description>Password for the keystore file</description>
|
||||
</parameter>
|
||||
<parameter name="reconnect" type="integer">
|
||||
<label>Reconnect Delay</label>
|
||||
<description>Delay between reconnection attempts</description>
|
||||
</parameter>
|
||||
<parameter name="heartbeat" type="integer">
|
||||
<label>Heartbeat Frequency</label>
|
||||
<description>Frequency of heartbeats</description>
|
||||
</parameter>
|
||||
<parameter name="delay" type="integer">
|
||||
<label>Delay</label>
|
||||
<description>Delay between messages</description>
|
||||
</parameter>
|
||||
</config-description>
|
||||
|
||||
</thing-type>
|
||||
|
||||
<channel-type id="debug" advanced="true">
|
||||
<item-type>String</item-type>
|
||||
<label>DEBUG Command</label>
|
||||
<description>Binding control (for debugging)</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="app">
|
||||
<item-type>String</item-type>
|
||||
<label>App</label>
|
||||
<description>App Control</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="appname">
|
||||
<item-type>String</item-type>
|
||||
<label>App Name</label>
|
||||
<description>App Name</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="appurl">
|
||||
<item-type>String</item-type>
|
||||
<label>App URL</label>
|
||||
<description>App URL</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="keypress">
|
||||
<item-type>String</item-type>
|
||||
<label>Key Press</label>
|
||||
<description>Send key press</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="keycode">
|
||||
<item-type>String</item-type>
|
||||
<label>Keycode</label>
|
||||
<description>Send keycode</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="keyboard">
|
||||
<item-type>String</item-type>
|
||||
<label>Keyboard</label>
|
||||
<description>Keyboard Entry</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="pincode">
|
||||
<item-type>String</item-type>
|
||||
<label>Pin Code</label>
|
||||
<description>Send Pin Code</description>
|
||||
</channel-type>
|
||||
|
||||
<channel-type id="player">
|
||||
<item-type>Player</item-type>
|
||||
<label>Player</label>
|
||||
<description>Player Control</description>
|
||||
</channel-type>
|
||||
|
||||
</thing:thing-descriptions>
|
@ -56,6 +56,7 @@
|
||||
<module>org.openhab.binding.ambientweather</module>
|
||||
<module>org.openhab.binding.amplipi</module>
|
||||
<module>org.openhab.binding.androiddebugbridge</module>
|
||||
<module>org.openhab.binding.androidtv</module>
|
||||
<module>org.openhab.binding.anel</module>
|
||||
<module>org.openhab.binding.anthem</module>
|
||||
<module>org.openhab.binding.astro</module>
|
||||
|
Loading…
Reference in New Issue
Block a user