mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-10 15:11:59 +01:00
[tivo] Update tivo binding for OH3 (#9302)
* Baseline original code from AndyXMB * Initial updates for OH3 * fix null warnings and add sub-channel handling Signed-off-by: Michael Lobstein <michael.lobstein@gmail.com>
This commit is contained in:
parent
207d300ca5
commit
9776de236f
@ -255,6 +255,7 @@
|
|||||||
/bundles/org.openhab.binding.tellstick/ @jarlebh
|
/bundles/org.openhab.binding.tellstick/ @jarlebh
|
||||||
/bundles/org.openhab.binding.tesla/ @kgoderis
|
/bundles/org.openhab.binding.tesla/ @kgoderis
|
||||||
/bundles/org.openhab.binding.tibber/ @kjoglum
|
/bundles/org.openhab.binding.tibber/ @kjoglum
|
||||||
|
/bundles/org.openhab.binding.tivo/ @mlobstein
|
||||||
/bundles/org.openhab.binding.touchwand/ @roieg
|
/bundles/org.openhab.binding.touchwand/ @roieg
|
||||||
/bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand
|
/bundles/org.openhab.binding.tplinksmarthome/ @Hilbrand
|
||||||
/bundles/org.openhab.binding.tr064/ @J-N-K
|
/bundles/org.openhab.binding.tr064/ @J-N-K
|
||||||
|
@ -1261,6 +1261,11 @@
|
|||||||
<artifactId>org.openhab.binding.tibber</artifactId>
|
<artifactId>org.openhab.binding.tibber</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.binding.tivo</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.openhab.addons.bundles</groupId>
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
<artifactId>org.openhab.binding.touchwand</artifactId>
|
<artifactId>org.openhab.binding.touchwand</artifactId>
|
||||||
|
13
bundles/org.openhab.binding.tivo/NOTICE
Normal file
13
bundles/org.openhab.binding.tivo/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
|
194
bundles/org.openhab.binding.tivo/README.md
Normal file
194
bundles/org.openhab.binding.tivo/README.md
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
# TiVo Binding
|
||||||
|
|
||||||
|
![TiVo Logo](doc/TiVo_lockup_BLK.png)
|
||||||
|
|
||||||
|
This binding controls a [TiVo](https://www.tivo.com/) Digital Video Recorder (DVR) that supports the TiVo TCP Control Protocol v1.1 (see TiVo_TCP_Network_Remote_Control_Protocol.pdf).
|
||||||
|
|
||||||
|
## Supported Things
|
||||||
|
|
||||||
|
Most TiVo DVRs that support network remote control can be managed/supported by this binding.
|
||||||
|
|
||||||
|
All TiVo devices must:
|
||||||
|
|
||||||
|
1. Be connected to a local area TCP/IP network that can be reached by the openHAB instance (this is not the WAN network interface used by cable service providers on some TiVos to provide the TV signals).
|
||||||
|
2. Have the Network Remote Control function enabled to support discovery and control of the device. This setting can be found using the remote control at:
|
||||||
|
|
||||||
|
* TiVo branded boxes - using the remote go to TiVo Central > Messages & Settings > Settings > Remote, CableCARD & Devices > Network Remote Control. Choose Enabled, press Select.
|
||||||
|
* Virgin Media branded boxes - using the remote select Home > Help and Settings > Settings > Devices > Network Remote Control. Select the option Allow network based remote controls.
|
||||||
|
|
||||||
|
## Discovery
|
||||||
|
|
||||||
|
TiVo devices with the network remote control interface enabled will be displayed automatically within the Inbox.
|
||||||
|
|
||||||
|
## Binding Configuration
|
||||||
|
|
||||||
|
There are no overall binding configuration settings that need to be set.
|
||||||
|
All settings are through thing configuration parameters.
|
||||||
|
|
||||||
|
## Thing Configuration
|
||||||
|
|
||||||
|
The thing has the following configuration parameters:
|
||||||
|
|
||||||
|
| Parameter | Display Name | Description |
|
||||||
|
|-------------------|--------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| host | Address | The IP address or hostname of your TiVo DVR. |
|
||||||
|
| tcpPort | TCP Port | The TCP port number used to connect to the TiVo. **Default: 31339** |
|
||||||
|
| numRetry | Connection Retries | The number of times to attempt reconnection to the TiVo DVR, if there is a connection failure. **Default: 5** |
|
||||||
|
| keepConActive | Keep Connection Open | Keep connection to the TiVo open. Recommended for monitoring the TiVo for changes in TV channels. <br><br>Disable if other applications that use the Remote Control Protocol port will also be used e.g. mobile phone remote control applications. **Default: True (Enabled)** |
|
||||||
|
| pollForChanges | Poll for Channel Changes | Check TiVo for channel changes. Enable if openHAB and a physical remote control (or other services use the Remote Control Protocol) will be used. **Default: True (Enabled)** |
|
||||||
|
| pollInterval | Polling Interval (Seconds) | Number of seconds between polling jobs to update status information from the TiVo. **Default: 10** |
|
||||||
|
| cmdWaitInterval | Command Wait Interval (Milliseconds) | Period to wait *after* a command is sent to the TiVo in milliseconds, before checking that the command has completed. **Default: 200** |
|
||||||
|
|
||||||
|
Some notes:
|
||||||
|
|
||||||
|
* If openHAB is the only device or application that you have that makes use of the Network Remote Control functions of your TiVo, enable the **Keep Connection Open** option. This will connect and lock the port in-use preventing any other device from connecting it. If you use some other application, disable this option. Performance is improved if the connection is kept open.
|
||||||
|
* **Poll for Channel Changes** only needs to be enabled if you also plan to use the TiVo remote control or other application to change channel. If openHAB is your only method of control, you can disable this option. Turning polling off, minimizes the periodic polling overhead on your hardware.
|
||||||
|
|
||||||
|
## Channels
|
||||||
|
|
||||||
|
All devices support the following channels:
|
||||||
|
|
||||||
|
| Channel Type ID | Item Type | Display Name | Description |
|
||||||
|
|-----------------|-----------------|---------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||||
|
| channelSet | Number (1-9999) | Current Channel - Request (SETCH) | Displays the current channel number. When changed, tunes the DVR to the specified channel (unless a recording is in progress on all available tuners). The TiVo must be in Live TV mode for this command to work. |
|
||||||
|
| channelForce | Number (1-9999) | Current Channel - Forced (FORCECH) | Displays the current channel number. When changed, tunes the DVR to the specified channel, **cancelling any recordings in progress if necessary** i.e. when all tuners are already in use / recording. The TiVo must be in Live TV mode for this command to work. |
|
||||||
|
| menuTeleport | String | Change Special/Menu Screen (TELEPORT) | Change to one of the following TiVo menu screens: TIVO (Home), LIVE TV, GUIDE, NOW PLAYING (My Shows), NETFLIX. |
|
||||||
|
| irCommand | String | Remote Control Button (IRCOMMAND) | Send a simulated button push from the remote control to the TiVo. See Appendix A in document TCP Remote Protocol 1.1 for supported codes. |
|
||||||
|
| kbdCommand | String | Keyboard Command (KEYBOARD) | Sends a code corresponding to a keyboard key press to the TiVo e.g. A-Z. See Appendix A in document TCP Remote Protocol 1.1 for supported characters and special character codes. |
|
||||||
|
| dvrStatus | String | TiVo Status | Action return code / channel information returned by the TiVo. |
|
||||||
|
|
||||||
|
* To change channels simply post/send the number of the channel to channelSet or channelForce. For OTA channels, a decimal for the sub-channel must be specified (ie: 2.1), for all others just send the channel as a whole number (ie: 100).
|
||||||
|
* Keyboard commands must currently be issued one character at a time to the item (this is how the TiVo natively supports this command).
|
||||||
|
* To send multiple copies of the same keyboard command, append an asterisk with the number of repeats required e.g. NUM2*4 would send the number 2 four times. This is useful for performing searches where the number characters can only be accessed by pressing the keys multiple times in rapid succession i.e. each key press cycles through characters A, B, C, 2.
|
||||||
|
* Special characters must also be changed to the appropriate command e.g. the comma symbol(`,`) must not be sent it should be replaced by 'COMMA'.
|
||||||
|
|
||||||
|
## Full Example
|
||||||
|
|
||||||
|
**tivo.things**
|
||||||
|
|
||||||
|
````java
|
||||||
|
tivo:sckt:Living_Room "Living Room TiVo" [ host="192.168.0.19" ]
|
||||||
|
```
|
||||||
|
|
||||||
|
**tivo.items:**
|
||||||
|
|
||||||
|
```
|
||||||
|
/* TIVO */
|
||||||
|
String TiVo_Status "Status" {channel="tivo:sckt:Living_Room:dvrStatus"}
|
||||||
|
String TiVo_MenuScreen "Menu Screen" {channel="tivo:sckt:Living_Room:menuTeleport", autoupdate="false"}
|
||||||
|
Number TiVo_SetChannel "Current Channel" {channel="tivo:sckt:Living_Room:channelSet"}
|
||||||
|
Number TiVo_SetChannelName "Channel Name [MAP(tivo.map):%s]" {channel="tivo:sckt:Living_Room:channelSet"}
|
||||||
|
Number TiVo_ForceChannel "Force Channel" {channel="tivo:sckt:Living_Room:channelForce"}
|
||||||
|
Number TiVo_Recording "Recording [MAP(tivo.map):rec-%s]" {channel="tivo:sckt:Living_Room:isRecording"}
|
||||||
|
String TiVo_IRCmd "Ir Cmd" {channel="tivo:sckt:Living_Room:irCommand", autoupdate="false"}
|
||||||
|
String TiVo_KbdCmd "Keyboard Cmd" {channel="tivo:sckt:Living_Room:kbdCommand", autoupdate="false"}
|
||||||
|
String TiVo_KeyboardStr "Search String"
|
||||||
|
Switch TiVo_Search "Search Demo"
|
||||||
|
```
|
||||||
|
|
||||||
|
* The item `TiVo_SetChannelName` depends upon a valid `tivo.map` file to translate channel numbers to channel names. The openHAB **MAP** transformation service must also be installed.
|
||||||
|
|
||||||
|
**tivo.sitemap:**
|
||||||
|
|
||||||
|
```
|
||||||
|
sitemap tivo label="Tivo Central" {
|
||||||
|
Frame label="Tivo" {
|
||||||
|
Text item=TiVo_SetChannel label="Current Channel [%s]" icon="screen"
|
||||||
|
Text item=TiVo_SetChannelName label="Channel Name" icon="screen"
|
||||||
|
Text item=TiVo_Recording label="Recording" icon="screen"
|
||||||
|
Switch item=TiVo_IRCmd label="Channel" icon="screen" mappings=["CHANNELDOWN"="CH -","CHANNELUP"="CH +"]
|
||||||
|
Switch item=TiVo_IRCmd label="Media" icon="screen" mappings=["REVERSE"="⏪", "PAUSE"="⏸", "PLAY"="▶", /*(DVD TiVo only!) "STOP"="⏹",*/ "FORWARD"="⏩", "RECORD"="⏺" ]
|
||||||
|
Switch item=TiVo_MenuScreen label="Menus" icon="screen" mappings=["TIVO"="Home", "LIVETV"="Live Tv", "GUIDE"="Guide", "NOWPLAYING"="My Shows", "NETFLIX"="Netflix" ]
|
||||||
|
Switch item=TiVo_SetChannel label="Fav TV" icon="screen" mappings=[2.1="CBS", 4.1="NBC", 7.1="ABC", 11.1="FOX", 5.2="AntennaTV"]
|
||||||
|
Switch item=TiVo_IRCmd label="Navigation" icon="screen" mappings=["UP"="˄", "DOWN"="˅", "LEFT"="<", "RIGHT"=">", "SELECT"="Select", "EXIT"="Exit" ]
|
||||||
|
Switch item=TiVo_IRCmd label="Actions" icon="screen" mappings=["ACTION_A"="Red","ACTION_B"="Green","ACTION_C"="Yellow","ACTION_D"="Blue"]
|
||||||
|
Switch item=TiVo_IRCmd label="Likes" icon="screen" mappings=["THUMBSUP"="Thumbs Up", "THUMBSDOWN"="Thumbs Down"]
|
||||||
|
Switch item=TiVo_IRCmd label="Remote" icon="screen" mappings=["FIND_REMOTE"="Find Remote"]
|
||||||
|
Switch item=TiVo_IRCmd label="Standby" icon="screen" mappings=["STANDBY"="Standby","TIVO"="Wake Up"]
|
||||||
|
Text item=TiVo_Status label="Status" icon="screen"
|
||||||
|
Switch item=TiVo_Search mappings=[ON="Search Demo"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
* This example does not use the 'Current Channel - Forced (FORCECH)' channel. This method will interrupt your recordings in progress when all your tuners are busy, so it is omitted for safety's sake.
|
||||||
|
|
||||||
|
**tivo.map:**
|
||||||
|
|
||||||
|
```
|
||||||
|
NULL=Unknown
|
||||||
|
-=Unknown
|
||||||
|
rec-0=Not Recording
|
||||||
|
rec-1=Recording
|
||||||
|
100=HBO
|
||||||
|
101=TNT
|
||||||
|
102=BBC America
|
||||||
|
103=ITV
|
||||||
|
104=Channel 4
|
||||||
|
105=Channel 5
|
||||||
|
2.1=CBS
|
||||||
|
2.2=StartTv
|
||||||
|
4.1=NBC
|
||||||
|
|
||||||
|
etc...
|
||||||
|
```
|
||||||
|
|
||||||
|
**tivo.rules:**
|
||||||
|
|
||||||
|
|
||||||
|
* This rule was used to overcome limitations within the HABpanel user interface at the moment when using transform/map functionality.
|
||||||
|
|
||||||
|
* The following rule shows how a string change to the item `TiVo_KeyboardStr` is split into individual characters and sent to the TiVo. The method to send a keystroke multiple times is used to simulate rapid keystrokes required to achieve number based searched.
|
||||||
|
|
||||||
|
* A simple custom template widget can be used within the HABpanel user interface for tablet-based searches. See [this discussion thread] (https://community.openhab.org/t/tivo-1-1-protocol-new-binding-contribution/5572/21?u=andymb).
|
||||||
|
|
||||||
|
```
|
||||||
|
rule "TiVo Search Command"
|
||||||
|
when
|
||||||
|
Item TiVo_Search received command
|
||||||
|
then
|
||||||
|
TiVo_KeyboardStr.sendCommand("Evening News")
|
||||||
|
end
|
||||||
|
|
||||||
|
rule "TiVo Search"
|
||||||
|
when
|
||||||
|
Item TiVo_KeyboardStr received update
|
||||||
|
then
|
||||||
|
if (TiVo_KeyboardStr.state != NULL && TiVo_KeyboardStr.state.toString.length > 0) {
|
||||||
|
|
||||||
|
// Commands to get us to the TiVo/Home menu and select the search menu using the 'remote' number keys
|
||||||
|
sendCommand(TiVo_MenuScreen, "TIVO")
|
||||||
|
Thread::sleep(1500)
|
||||||
|
sendCommand(TiVo_KbdCmd, "NUM4")
|
||||||
|
Thread::sleep(1500)
|
||||||
|
|
||||||
|
var i = 0
|
||||||
|
var char txt = ""
|
||||||
|
var srch = TiVo_KeyboardStr.state.toString.toUpperCase
|
||||||
|
logInfo("tivo.search"," Searching for: " + srch)
|
||||||
|
|
||||||
|
while (i < (srch.length)) {
|
||||||
|
logDebug("tivo.search"," Loop i=: " + i)
|
||||||
|
txt = srch.charAt(i)
|
||||||
|
logDebug("tivo.search"," txt: " + txt.toString)
|
||||||
|
if (txt.toString.matches("[A-Z]")) {
|
||||||
|
// Check for upper case A-Z
|
||||||
|
sendCommand(TiVo_KbdCmd, txt.toString)
|
||||||
|
} else if (txt.toString.matches(" ")) {
|
||||||
|
// Check for Space
|
||||||
|
sendCommand(TiVo_KbdCmd, "SPACE")
|
||||||
|
} else if (txt.toString.matches("[0-9]")) {
|
||||||
|
// Check for numbers 0-9
|
||||||
|
sendCommand(TiVo_KbdCmd, "NUM" + txt.toString)
|
||||||
|
} else {
|
||||||
|
logWarn("tivo.search"," Character not supported by script: " + txt)
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
* You may need to adjust the two `Thread::sleep(1500)` lines, depending on the performance of your TiVo
|
||||||
|
* In testing, response times have varied considerably at different times of the day, etc. You may need to increase the delay until there is sufficient time added for the system to respond consistently to the 'remote control' menu commands.
|
BIN
bundles/org.openhab.binding.tivo/doc/TiVo_lockup_BLK.png
Normal file
BIN
bundles/org.openhab.binding.tivo/doc/TiVo_lockup_BLK.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.0 KiB |
17
bundles/org.openhab.binding.tivo/pom.xml
Normal file
17
bundles/org.openhab.binding.tivo/pom.xml
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<parent>
|
||||||
|
<groupId>org.openhab.addons.bundles</groupId>
|
||||||
|
<artifactId>org.openhab.addons.reactor.bundles</artifactId>
|
||||||
|
<version>3.1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<artifactId>org.openhab.binding.tivo</artifactId>
|
||||||
|
|
||||||
|
<name>openHAB Add-ons :: Bundles :: TiVo Binding</name>
|
||||||
|
|
||||||
|
</project>
|
@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<features name="org.openhab.binding.tivo-${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-tivo" description="TiVo 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.tivo/${project.version}</bundle>
|
||||||
|
</feature>
|
||||||
|
</features>
|
@ -0,0 +1,48 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.tivo.internal;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link TiVoBinding} class defines common constants that are
|
||||||
|
* used across the whole binding.
|
||||||
|
*
|
||||||
|
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||||
|
* @author Andrew Black (AndyXMB) - Addition of Min / Max Channel and channel scanning properties
|
||||||
|
* @author Michael Lobstein - Updated for OH3
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
public class TiVoBindingConstants {
|
||||||
|
public static final String BINDING_ID = "tivo";
|
||||||
|
public static final int CONFIG_SOCKET_TIMEOUT_MS = 1000;
|
||||||
|
public static final int INIT_POLLING_DELAY_S = 5;
|
||||||
|
|
||||||
|
// List of all Thing Type UIDs
|
||||||
|
public static final ThingTypeUID THING_TYPE_TIVO = new ThingTypeUID(BINDING_ID, "sckt");
|
||||||
|
|
||||||
|
// List of all Channel ids
|
||||||
|
public static final String CHANNEL_TIVO_CHANNEL_FORCE = "channelForce";
|
||||||
|
public static final String CHANNEL_TIVO_CHANNEL_SET = "channelSet";
|
||||||
|
public static final String CHANNEL_TIVO_IS_RECORDING = "isRecording";
|
||||||
|
public static final String CHANNEL_TIVO_TELEPORT = "menuTeleport";
|
||||||
|
public static final String CHANNEL_TIVO_IRCMD = "irCommand";
|
||||||
|
public static final String CHANNEL_TIVO_KBDCMD = "kbdCommand";
|
||||||
|
public static final String CHANNEL_TIVO_STATUS = "dvrStatus";
|
||||||
|
|
||||||
|
// List of all configuration Properties
|
||||||
|
public static final String CONFIG_HOST = "host";
|
||||||
|
public static final String CONFIG_PORT = "tcpPort";
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.tivo.internal;
|
||||||
|
|
||||||
|
import static org.openhab.binding.tivo.internal.TiVoBindingConstants.THING_TYPE_TIVO;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.tivo.internal.handler.TiVoHandler;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingTypeUID;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandlerFactory;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandler;
|
||||||
|
import org.openhab.core.thing.binding.ThingHandlerFactory;
|
||||||
|
import org.osgi.service.component.annotations.Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link TiVoHandlerFactory} is responsible for creating things and thing
|
||||||
|
* handlers.
|
||||||
|
*
|
||||||
|
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||||
|
* @author Andrew Black (AndyXMB) - minor updates, removal of unused DiscoveryService functionality.
|
||||||
|
* @author Michael Lobstein - Updated for OH3
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(configurationPid = "binding.tivo", service = ThingHandlerFactory.class)
|
||||||
|
public class TiVoHandlerFactory extends BaseThingHandlerFactory {
|
||||||
|
private static final Set<ThingTypeUID> SUPPORTED_THING_TYPES_UIDS = Collections.singleton(THING_TYPE_TIVO);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supportsThingType(ThingTypeUID thingTypeUID) {
|
||||||
|
return SUPPORTED_THING_TYPES_UIDS.contains(thingTypeUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected @Nullable ThingHandler createHandler(Thing thing) {
|
||||||
|
ThingTypeUID thingTypeUID = thing.getThingTypeUID();
|
||||||
|
|
||||||
|
if (thingTypeUID.equals(THING_TYPE_TIVO)) {
|
||||||
|
return new TiVoHandler(thing);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,127 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.tivo.internal.discovery;
|
||||||
|
|
||||||
|
import static org.openhab.binding.tivo.internal.TiVoBindingConstants.*;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class TiVoDiscoveryParticipant.
|
||||||
|
* *
|
||||||
|
*
|
||||||
|
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||||
|
* @author Andrew Black (AndyXMB) - minor updates.
|
||||||
|
* @author Michael Lobstein - Updated for OH3
|
||||||
|
*/
|
||||||
|
@NonNullByDefault
|
||||||
|
@Component(configurationPid = "discovery.tivo")
|
||||||
|
public class TiVoDiscoveryParticipant implements MDNSDiscoveryParticipant {
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(TiVoDiscoveryParticipant.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<ThingTypeUID> getSupportedThingTypeUIDs() {
|
||||||
|
return Collections.singleton(THING_TYPE_TIVO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getServiceType() {
|
||||||
|
return "_tivo-remote._tcp.local.";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable DiscoveryResult createResult(ServiceInfo service) {
|
||||||
|
DiscoveryResult result = null;
|
||||||
|
|
||||||
|
ThingUID uid = getThingUID(service);
|
||||||
|
if (uid != null) {
|
||||||
|
Map<String, Object> properties = new HashMap<>(2);
|
||||||
|
// remove the domain from the name
|
||||||
|
InetAddress ip = getIpAddress(service);
|
||||||
|
if (ip == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
String inetAddress = ip.toString().substring(1); // trim leading slash
|
||||||
|
String label = service.getName();
|
||||||
|
int port = service.getPort();
|
||||||
|
|
||||||
|
properties.put(CONFIG_HOST, inetAddress);
|
||||||
|
properties.put(CONFIG_PORT, port);
|
||||||
|
|
||||||
|
result = DiscoveryResultBuilder.create(uid).withProperties(properties).withLabel("Tivo: " + label)
|
||||||
|
.withProperty(CONFIG_HOST, inetAddress).withRepresentationProperty(CONFIG_HOST).build();
|
||||||
|
logger.debug("Created {} for TiVo host '{}' name '{}'", result, inetAddress, label);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see org.openhab.core.config.discovery.mdns.MDNSDiscoveryParticipant#getThingUID(javax.jmdns.ServiceInfo)
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public @Nullable ThingUID getThingUID(ServiceInfo service) {
|
||||||
|
if (service.getType() != null) {
|
||||||
|
if (service.getType().equals(getServiceType())) {
|
||||||
|
String uidName = getUIDName(service);
|
||||||
|
return new ThingUID(THING_TYPE_TIVO, uidName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the UID name, replacing any non AlphaNumeric characters with underscores.
|
||||||
|
*
|
||||||
|
* @param service the service
|
||||||
|
* @return the UID name
|
||||||
|
*/
|
||||||
|
private String getUIDName(ServiceInfo service) {
|
||||||
|
return service.getName().replaceAll("[^A-Za-z0-9_]", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link InetAddress} gets the IP address of the device in v4 or v6 format.
|
||||||
|
*
|
||||||
|
* @param ServiceInfo service
|
||||||
|
* @return InetAddress the IP address
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private @Nullable InetAddress getIpAddress(ServiceInfo service) {
|
||||||
|
InetAddress address = null;
|
||||||
|
for (InetAddress addr : service.getInet4Addresses()) {
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
// Fall back for Inet6addresses
|
||||||
|
for (InetAddress addr : service.getInet6Addresses()) {
|
||||||
|
return addr;
|
||||||
|
}
|
||||||
|
return address;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,369 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.tivo.internal.handler;
|
||||||
|
|
||||||
|
import static org.openhab.binding.tivo.internal.TiVoBindingConstants.*;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.concurrent.ScheduledFuture;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.tivo.internal.service.TivoConfigData;
|
||||||
|
import org.openhab.binding.tivo.internal.service.TivoStatusData;
|
||||||
|
import org.openhab.binding.tivo.internal.service.TivoStatusData.ConnectionStatus;
|
||||||
|
import org.openhab.binding.tivo.internal.service.TivoStatusProvider;
|
||||||
|
import org.openhab.core.library.types.DecimalType;
|
||||||
|
import org.openhab.core.library.types.OnOffType;
|
||||||
|
import org.openhab.core.library.types.StringType;
|
||||||
|
import org.openhab.core.thing.ChannelUID;
|
||||||
|
import org.openhab.core.thing.Thing;
|
||||||
|
import org.openhab.core.thing.ThingStatus;
|
||||||
|
import org.openhab.core.thing.ThingStatusDetail;
|
||||||
|
import org.openhab.core.thing.binding.BaseThingHandler;
|
||||||
|
import org.openhab.core.types.Command;
|
||||||
|
import org.openhab.core.types.RefreshType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link TiVoHandler} is the BaseThingHandler responsible for handling commands that are
|
||||||
|
* sent to one of the Tivo's channels.
|
||||||
|
*
|
||||||
|
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||||
|
* @author Andrew Black (AndyXMB) - Updates / compilation corrections. Addition of channel scanning functionality.
|
||||||
|
* @author Michael Lobstein - Updated for OH3
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
public class TiVoHandler extends BaseThingHandler {
|
||||||
|
private static final Pattern NUMERIC_PATTERN = Pattern.compile("(\\d+)\\.?(\\d+)?");
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(TiVoHandler.class);
|
||||||
|
private TivoConfigData tivoConfigData = new TivoConfigData();
|
||||||
|
private ConnectionStatus lastConnectionStatus = ConnectionStatus.UNKNOWN;
|
||||||
|
private Optional<TivoStatusProvider> tivoConnection = Optional.empty();
|
||||||
|
private @Nullable ScheduledFuture<?> refreshJob;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new TiVo handler.
|
||||||
|
*
|
||||||
|
* @param thing the thing
|
||||||
|
*/
|
||||||
|
public TiVoHandler(Thing thing) {
|
||||||
|
super(thing);
|
||||||
|
logger.debug("TiVoHandler '{}' - creating", getThing().getUID());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void handleCommand(ChannelUID channelUID, Command command) {
|
||||||
|
// Handles the commands from the various TiVo channel objects
|
||||||
|
logger.debug("handleCommand '{}', parameter: {}", channelUID, command);
|
||||||
|
|
||||||
|
if (!isInitialized() || !tivoConnection.isPresent()) {
|
||||||
|
logger.debug("handleCommand '{}' device is not initialized yet, command '{}' will be ignored.",
|
||||||
|
getThing().getUID(), channelUID + " " + command);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TivoStatusData currentStatus = tivoConnection.get().getServiceStatus();
|
||||||
|
String commandKeyword = "";
|
||||||
|
|
||||||
|
String commandParameter = command.toString().toUpperCase();
|
||||||
|
if (command instanceof RefreshType) {
|
||||||
|
// Future enhancement, if we can come up with a sensible set of actions when a REFRESH is issued
|
||||||
|
logger.debug("TiVo '{}' skipping REFRESH command for channel: '{}'.", getThing().getUID(),
|
||||||
|
channelUID.getId());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (channelUID.getId()) {
|
||||||
|
case CHANNEL_TIVO_CHANNEL_FORCE:
|
||||||
|
commandKeyword = "FORCECH";
|
||||||
|
break;
|
||||||
|
case CHANNEL_TIVO_CHANNEL_SET:
|
||||||
|
commandKeyword = "SETCH";
|
||||||
|
break;
|
||||||
|
case CHANNEL_TIVO_TELEPORT:
|
||||||
|
commandKeyword = "TELEPORT";
|
||||||
|
break;
|
||||||
|
case CHANNEL_TIVO_IRCMD:
|
||||||
|
commandKeyword = "IRCODE";
|
||||||
|
break;
|
||||||
|
case CHANNEL_TIVO_KBDCMD:
|
||||||
|
commandKeyword = "KEYBOARD";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
sendCommand(commandKeyword, commandParameter, currentStatus);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TiVo handler disposed or openHAB exiting, do nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStatusOffline() {
|
||||||
|
this.updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"Power on device or check network configuration/connection.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendCommand(String commandKeyword, String commandParameter, TivoStatusData currentStatus)
|
||||||
|
throws InterruptedException {
|
||||||
|
if (!tivoConnection.isPresent()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TivoStatusData deviceStatus = tivoConnection.get().getServiceStatus();
|
||||||
|
TivoStatusData commandResult = null;
|
||||||
|
logger.debug("handleCommand '{}' - {} found!", getThing().getUID(), commandKeyword);
|
||||||
|
// Re-write command keyword if we are in STANDBY, as only IRCODE TIVO will wake the unit from
|
||||||
|
// standby mode
|
||||||
|
if (deviceStatus.getConnectionStatus() == ConnectionStatus.STANDBY && commandKeyword.contentEquals("TELEPORT")
|
||||||
|
&& commandParameter.contentEquals("TIVO")) {
|
||||||
|
String command = "IRCODE " + commandParameter;
|
||||||
|
logger.debug("TiVo '{}' TELEPORT re-mapped to IRCODE as we are in standby: '{}'", getThing().getUID(),
|
||||||
|
command);
|
||||||
|
}
|
||||||
|
// Execute command
|
||||||
|
if (commandKeyword.contentEquals("FORCECH") || commandKeyword.contentEquals("SETCH")) {
|
||||||
|
commandResult = chChannelChange(commandKeyword, commandParameter);
|
||||||
|
} else {
|
||||||
|
commandResult = tivoConnection.get().cmdTivoSend(commandKeyword + " " + commandParameter);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Post processing
|
||||||
|
if (commandResult != null && commandParameter.contentEquals("STANDBY")) {
|
||||||
|
// Force thing state into STANDBY as this command does not return a status when executed
|
||||||
|
commandResult.setConnectionStatus(ConnectionStatus.STANDBY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Push status updates
|
||||||
|
if (commandResult != null && commandResult.isCmdOk()) {
|
||||||
|
updateTivoStatus(currentStatus, commandResult);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tivoConfigData.isKeepConnActive()) {
|
||||||
|
// disconnect once command is complete
|
||||||
|
tivoConnection.get().connTivoDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize() {
|
||||||
|
logger.debug("Initializing a TiVo '{}' with config options", getThing().getUID());
|
||||||
|
|
||||||
|
tivoConfigData = getConfigAs(TivoConfigData.class);
|
||||||
|
|
||||||
|
tivoConfigData.setCfgIdentifier(getThing().getUID().getAsString());
|
||||||
|
tivoConnection = Optional.of(new TivoStatusProvider(tivoConfigData, this));
|
||||||
|
|
||||||
|
updateStatus(ThingStatus.UNKNOWN);
|
||||||
|
lastConnectionStatus = ConnectionStatus.UNKNOWN;
|
||||||
|
logger.debug("Initializing a TiVo handler for thing '{}' - finished!", getThing().getUID());
|
||||||
|
|
||||||
|
startPollStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
logger.debug("Disposing of a TiVo handler for thing '{}'", getThing().getUID());
|
||||||
|
|
||||||
|
ScheduledFuture<?> refreshJob = this.refreshJob;
|
||||||
|
if (refreshJob != null) {
|
||||||
|
refreshJob.cancel(false);
|
||||||
|
this.refreshJob = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tivoConnection.isPresent()) {
|
||||||
|
try {
|
||||||
|
tivoConnection.get().connTivoDisconnect();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TiVo handler disposed or openHAB exiting, do nothing
|
||||||
|
}
|
||||||
|
tivoConnection = Optional.empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link startPollStatus} scheduled job to poll for changes in state.
|
||||||
|
*/
|
||||||
|
private void startPollStatus() {
|
||||||
|
Runnable runnable = () -> {
|
||||||
|
logger.debug("startPollStatus '{}' @ rate of '{}' seconds", getThing().getUID(),
|
||||||
|
tivoConfigData.getPollInterval());
|
||||||
|
tivoConnection.ifPresent(connection -> {
|
||||||
|
try {
|
||||||
|
connection.statusRefresh();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TiVo handler disposed or openHAB exiting, do nothing
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (tivoConfigData.isKeepConnActive()) {
|
||||||
|
// Run once
|
||||||
|
refreshJob = scheduler.schedule(runnable, INIT_POLLING_DELAY_S, TimeUnit.SECONDS);
|
||||||
|
logger.debug("Status collection '{}' will start in '{}' seconds.", getThing().getUID(),
|
||||||
|
INIT_POLLING_DELAY_S);
|
||||||
|
} else if (tivoConfigData.doPollChanges()) {
|
||||||
|
// Run at intervals
|
||||||
|
refreshJob = scheduler.scheduleWithFixedDelay(runnable, INIT_POLLING_DELAY_S,
|
||||||
|
tivoConfigData.getPollInterval(), TimeUnit.SECONDS);
|
||||||
|
logger.debug("Status polling '{}' will start in '{}' seconds.", getThing().getUID(), INIT_POLLING_DELAY_S);
|
||||||
|
} else {
|
||||||
|
// Just update the status now
|
||||||
|
tivoConnection.ifPresent(connection -> {
|
||||||
|
try {
|
||||||
|
connection.statusRefresh();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// TiVo handler disposed or openHAB exiting, do nothing
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link chChannelChange} performs channel changing operations.
|
||||||
|
*
|
||||||
|
* @param commandKeyword the TiVo command object.
|
||||||
|
* @param command the command parameter.
|
||||||
|
* @return TivoStatusData status of the command.
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
private TivoStatusData chChannelChange(String commandKeyword, String command) throws InterruptedException {
|
||||||
|
int channel = -1;
|
||||||
|
int subChannel = -1;
|
||||||
|
|
||||||
|
TivoStatusData tmpStatus = tivoConnection.get().getServiceStatus();
|
||||||
|
try {
|
||||||
|
// Parse the channel number and if there is a decimal, the sub-channel number (OTA channels)
|
||||||
|
Matcher matcher = NUMERIC_PATTERN.matcher(command);
|
||||||
|
if (matcher.find()) {
|
||||||
|
if (matcher.groupCount() >= 1) {
|
||||||
|
channel = Integer.parseInt(matcher.group(1).trim());
|
||||||
|
}
|
||||||
|
if (matcher.groupCount() >= 2 && matcher.group(2) != null) {
|
||||||
|
subChannel = Integer.parseInt(matcher.group(2).trim());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The command string was not a number, throw exception to catch & log below
|
||||||
|
throw new NumberFormatException();
|
||||||
|
}
|
||||||
|
|
||||||
|
String tmpCommand = commandKeyword + " " + channel + ((subChannel != -1) ? (" " + subChannel) : "");
|
||||||
|
logger.debug("chChannelChange '{}' sending command to tivo: '{}'", getThing().getUID(), tmpCommand);
|
||||||
|
|
||||||
|
// Attempt to execute the command on the tivo
|
||||||
|
tivoConnection.get().cmdTivoSend(tmpCommand);
|
||||||
|
TimeUnit.MILLISECONDS.sleep(tivoConfigData.getCmdWaitInterval() * 2);
|
||||||
|
|
||||||
|
tmpStatus = tivoConnection.get().getServiceStatus();
|
||||||
|
|
||||||
|
// Check to see if the command was successful
|
||||||
|
if (tmpStatus.getConnectionStatus() != ConnectionStatus.INIT && tmpStatus.isCmdOk()) {
|
||||||
|
if (tmpStatus.getMsg().contains("CH_STATUS")) {
|
||||||
|
return tmpStatus;
|
||||||
|
}
|
||||||
|
} else if (tmpStatus.getConnectionStatus() != ConnectionStatus.INIT) {
|
||||||
|
logger.warn("TiVo'{}' set channel command failed '{}' with msg '{}'", getThing().getUID(), tmpCommand,
|
||||||
|
tmpStatus.getMsg());
|
||||||
|
switch (tmpStatus.getMsg()) {
|
||||||
|
case "CH_FAILED NO_LIVE":
|
||||||
|
tmpStatus.setChannelNum(channel);
|
||||||
|
tmpStatus.setSubChannelNum(subChannel);
|
||||||
|
return tmpStatus;
|
||||||
|
case "CH_FAILED RECORDING":
|
||||||
|
case "CH_FAILED MISSING_CHANNEL":
|
||||||
|
case "CH_FAILED MALFORMED_CHANNEL":
|
||||||
|
case "CH_FAILED INVALID_CHANNEL":
|
||||||
|
return tmpStatus;
|
||||||
|
case "NO_STATUS_DATA_RETURNED":
|
||||||
|
tmpStatus.setChannelNum(-1);
|
||||||
|
tmpStatus.setSubChannelNum(-1);
|
||||||
|
tmpStatus.setRecording(false);
|
||||||
|
return tmpStatus;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
logger.warn("TiVo'{}' unable to parse channel integer, value sent was: '{}'", getThing().getUID(),
|
||||||
|
command.toString());
|
||||||
|
}
|
||||||
|
return tmpStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link updateTivoStatus} populates the items with the status / channel information.
|
||||||
|
*
|
||||||
|
* @param tivoStatusData the {@link TivoStatusData}
|
||||||
|
*/
|
||||||
|
public void updateTivoStatus(TivoStatusData oldStatusData, TivoStatusData newStatusData) {
|
||||||
|
if (newStatusData.getConnectionStatus() != ConnectionStatus.INIT) {
|
||||||
|
// Update Item Status
|
||||||
|
if (newStatusData.getPubToUI()) {
|
||||||
|
if (oldStatusData.getConnectionStatus() == ConnectionStatus.INIT
|
||||||
|
|| !(oldStatusData.getMsg().contentEquals(newStatusData.getMsg()))) {
|
||||||
|
updateState(CHANNEL_TIVO_STATUS, new StringType(newStatusData.getMsg()));
|
||||||
|
}
|
||||||
|
// If the cmd was successful, publish the channel numbers
|
||||||
|
if (newStatusData.isCmdOk() && newStatusData.getChannelNum() != -1) {
|
||||||
|
if (oldStatusData.getConnectionStatus() == ConnectionStatus.INIT
|
||||||
|
|| oldStatusData.getChannelNum() != newStatusData.getChannelNum()
|
||||||
|
|| oldStatusData.getSubChannelNum() != newStatusData.getSubChannelNum()) {
|
||||||
|
if (newStatusData.getSubChannelNum() == -1) {
|
||||||
|
updateState(CHANNEL_TIVO_CHANNEL_FORCE, new DecimalType(newStatusData.getChannelNum()));
|
||||||
|
updateState(CHANNEL_TIVO_CHANNEL_SET, new DecimalType(newStatusData.getChannelNum()));
|
||||||
|
} else {
|
||||||
|
updateState(CHANNEL_TIVO_CHANNEL_FORCE, new DecimalType(
|
||||||
|
newStatusData.getChannelNum() + "." + newStatusData.getSubChannelNum()));
|
||||||
|
updateState(CHANNEL_TIVO_CHANNEL_SET, new DecimalType(
|
||||||
|
newStatusData.getChannelNum() + "." + newStatusData.getSubChannelNum()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
updateState(CHANNEL_TIVO_IS_RECORDING, newStatusData.isRecording() ? OnOffType.ON : OnOffType.OFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now set the pubToUI flag to false, as we have already published this status
|
||||||
|
if (isLinked(CHANNEL_TIVO_STATUS) || isLinked(CHANNEL_TIVO_CHANNEL_FORCE)
|
||||||
|
|| isLinked(CHANNEL_TIVO_CHANNEL_SET)) {
|
||||||
|
newStatusData.setPubToUI(false);
|
||||||
|
tivoConnection.get().setServiceStatus(newStatusData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Thing status
|
||||||
|
if (newStatusData.getConnectionStatus() != lastConnectionStatus) {
|
||||||
|
switch (newStatusData.getConnectionStatus()) {
|
||||||
|
case OFFLINE:
|
||||||
|
this.setStatusOffline();
|
||||||
|
break;
|
||||||
|
case ONLINE:
|
||||||
|
updateStatus(ThingStatus.ONLINE);
|
||||||
|
break;
|
||||||
|
case STANDBY:
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE,
|
||||||
|
"STANDBY MODE: Send command TIVO to Remote Control Button (IRCODE) item to wakeup.");
|
||||||
|
break;
|
||||||
|
case UNKNOWN:
|
||||||
|
updateStatus(ThingStatus.OFFLINE);
|
||||||
|
break;
|
||||||
|
case INIT:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
lastConnectionStatus = newStatusData.getConnectionStatus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,202 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.tivo.internal.service;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Class {@link TivoConfigData} stores the dynamic configuration parameters used within the {@link TivoHandler } and
|
||||||
|
* {@link TivoConfigStatusProvider}.
|
||||||
|
*
|
||||||
|
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||||
|
* @author Andrew Black (AndyXMB) - minor updates, removal of unused DiscoveryService functionality.
|
||||||
|
* @author Michael Lobstein - Updated for OH3
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
public class TivoConfigData {
|
||||||
|
private @Nullable String host = null;
|
||||||
|
private int tcpPort = 31339;
|
||||||
|
private int numRetry = 0;
|
||||||
|
private int pollInterval = 30;
|
||||||
|
private boolean pollForChanges = false;
|
||||||
|
private boolean keepConActive = false;
|
||||||
|
private int cmdWaitInterval = 0;
|
||||||
|
private String cfgIdentifier = "";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link toString} returns each of the configuration items as a single concatenated string.
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
* @see java.lang.Object#toString()
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TivoConfigData [host=" + host + ", tcpPort=" + tcpPort + ", numRetry=" + numRetry + ", pollInterval="
|
||||||
|
+ pollInterval + ", pollForChanges=" + pollForChanges + ", keepConActive=" + keepConActive
|
||||||
|
+ ", cmdWaitInterval=" + cmdWaitInterval + ", cfgIdentifier=" + cfgIdentifier + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the cfgIdentifier representing the thing name of the TiVo device.
|
||||||
|
*
|
||||||
|
* @return the cfgIdentifier
|
||||||
|
*/
|
||||||
|
public String getCfgIdentifier() {
|
||||||
|
return this.cfgIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cfgIdentifier representing the thing name of the TiVo device.
|
||||||
|
*
|
||||||
|
* @param cfgIdentifier the cfgIdentifier to set
|
||||||
|
*/
|
||||||
|
public void setCfgIdentifier(String cfgIdentifier) {
|
||||||
|
this.cfgIdentifier = cfgIdentifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the host representing the host name or IP address of the device.
|
||||||
|
*
|
||||||
|
* @return the host
|
||||||
|
*/
|
||||||
|
public @Nullable String getHost() {
|
||||||
|
return host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* the host representing the host name or IP address of the device.
|
||||||
|
*
|
||||||
|
* @param host the host to set
|
||||||
|
*/
|
||||||
|
public void setHost(String host) {
|
||||||
|
this.host = host;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the cfgTcp representing the IP port of the Remote Control Protocol service on the device.
|
||||||
|
*
|
||||||
|
* @return the tcpPort
|
||||||
|
*/
|
||||||
|
public int getTcpPort() {
|
||||||
|
return tcpPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cfgTcp representing the IP port of the Remote Control Protocol service on the device (31339).
|
||||||
|
*
|
||||||
|
* @param tcpPort the tcpPort to set
|
||||||
|
*/
|
||||||
|
public void setTcpPort(int tcpPort) {
|
||||||
|
this.tcpPort = tcpPort;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the numRetry value. This determines the number of connection attempts made to the IP/Port of the
|
||||||
|
* service and the number of read attempts that are made when a command is submitted to the device, separated by
|
||||||
|
* the
|
||||||
|
* interval specified in the Command Wait Interval.
|
||||||
|
*
|
||||||
|
* @return the numRetry
|
||||||
|
*/
|
||||||
|
public int getNumRetry() {
|
||||||
|
return numRetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the numRetry value. This determines the number of connection attempts made to the IP/Port of the
|
||||||
|
* service and the number of read attempts that are made when a command is submitted to the device, separated by
|
||||||
|
* the
|
||||||
|
* interval specified in the Command Wait Interval.
|
||||||
|
*
|
||||||
|
* @param numRetry the numRetry to set
|
||||||
|
*/
|
||||||
|
public void setNumRetry(int numRetry) {
|
||||||
|
this.numRetry = numRetry;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the pollInterval representing the interval in seconds between polling attempts to collect any updated
|
||||||
|
* status information.
|
||||||
|
*
|
||||||
|
* @return the pollInterval
|
||||||
|
*/
|
||||||
|
public int getPollInterval() {
|
||||||
|
return pollInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the pollInterval representing the interval in seconds between polling attempts to collect any updated
|
||||||
|
* status information.
|
||||||
|
*
|
||||||
|
* @param pollInterval the pollInterval to set
|
||||||
|
*/
|
||||||
|
public void setPollInterval(int pollInterval) {
|
||||||
|
this.pollInterval = pollInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if is cfg poll changes.
|
||||||
|
*
|
||||||
|
* @return the pollForChanges
|
||||||
|
*/
|
||||||
|
public boolean doPollChanges() {
|
||||||
|
return pollForChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cfg poll changes.
|
||||||
|
*
|
||||||
|
* @param pollForChanges the pollForChanges to set
|
||||||
|
*/
|
||||||
|
public void setPollForChanges(boolean pollForChanges) {
|
||||||
|
this.pollForChanges = pollForChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if is cfg keep conn open.
|
||||||
|
*
|
||||||
|
* @return the keepConActive
|
||||||
|
*/
|
||||||
|
public boolean isKeepConnActive() {
|
||||||
|
return keepConActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cfg keep conn open.
|
||||||
|
*
|
||||||
|
* @param keepConActive the keepConActive to set
|
||||||
|
*/
|
||||||
|
public void setKeepConnActive(boolean keepConActive) {
|
||||||
|
this.keepConActive = keepConActive;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the cfg cmd wait.
|
||||||
|
*
|
||||||
|
* @return the cmdWaitInterval
|
||||||
|
*/
|
||||||
|
public int getCmdWaitInterval() {
|
||||||
|
return cmdWaitInterval;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the cfg cmd wait.
|
||||||
|
*
|
||||||
|
* @param cmdWaitInterval the cmdWaitInterval to set
|
||||||
|
*/
|
||||||
|
public void setCmdWaitInterval(int cmdWaitInterval) {
|
||||||
|
this.cmdWaitInterval = cmdWaitInterval;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,229 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.tivo.internal.service;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TivoStatusData class stores the data from the last status query from the TiVo and any other errors / status
|
||||||
|
* codes.
|
||||||
|
*
|
||||||
|
* @author Jayson Kubilis (DigitalBytes) - Initial contribution
|
||||||
|
* @author Andrew Black (AndyXMB) - minor updates, removal of unused functions.
|
||||||
|
* @author Michael Lobstein - Updated for OH3
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
public class TivoStatusData {
|
||||||
|
private boolean cmdOk = false;
|
||||||
|
private Date time = new Date();
|
||||||
|
private int channelNum = -1;
|
||||||
|
private int subChannelNum = -1;
|
||||||
|
private boolean isRecording = false;
|
||||||
|
private String msg = "NO STATUS QUERIED YET";
|
||||||
|
private boolean pubToUI = true;
|
||||||
|
private ConnectionStatus connectionStatus = ConnectionStatus.INIT;
|
||||||
|
|
||||||
|
public TivoStatusData() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* {@link TivoStatusData} class stores the data from the last status query from the TiVo and any other errors /
|
||||||
|
* status codes.
|
||||||
|
*
|
||||||
|
* @param cmdOk boolean true = last command executed correctly, false = last command failed with error message
|
||||||
|
*
|
||||||
|
* @param channelNum int = channel number, -1 indicates no channel received. Valid channel range 1-9999.
|
||||||
|
*
|
||||||
|
* @param subChannelNum int = sub-channel number, -1 indicates no sub-channel received. Valid sub-channel range
|
||||||
|
* 1-9999.
|
||||||
|
*
|
||||||
|
* @param isRecording boolean true = indicates the current channel is recording
|
||||||
|
*
|
||||||
|
* @param msg string status message from the TiVo socket
|
||||||
|
*
|
||||||
|
* @param pubToUI boolean true = this status needs to be published to the UI / Thing, false = do not publish (or it
|
||||||
|
* already has been)
|
||||||
|
*
|
||||||
|
* @param connectionStatus ConnectionStatus enum UNKNOWN= test not run/default, OFFLINE = offline, STANDBY = TiVo is
|
||||||
|
* in standby, ONLINE = Online
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public TivoStatusData(boolean cmdOk, int channelNum, int subChannelNum, boolean isRecording, String msg,
|
||||||
|
boolean pubToUI, ConnectionStatus connectionStatus) {
|
||||||
|
this.cmdOk = cmdOk;
|
||||||
|
this.time = new Date();
|
||||||
|
this.channelNum = channelNum;
|
||||||
|
this.subChannelNum = subChannelNum;
|
||||||
|
this.isRecording = isRecording;
|
||||||
|
this.msg = msg;
|
||||||
|
this.pubToUI = pubToUI;
|
||||||
|
this.connectionStatus = connectionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ConnectionStatus {
|
||||||
|
INIT,
|
||||||
|
UNKNOWN,
|
||||||
|
OFFLINE,
|
||||||
|
STANDBY,
|
||||||
|
ONLINE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link TivoStatusData} class stores the data from the last status query from the TiVo and any other errors /
|
||||||
|
* status codes.
|
||||||
|
*
|
||||||
|
* @param cmdOk boolean true = last command executed correctly, false = last command failed with error message
|
||||||
|
* @param channelNum int = channel number, -1 indicates no channel received. Valid channel range 1-9999.
|
||||||
|
* @param msg string status message from the TiVo socket
|
||||||
|
* @param pubToUI boolean true = this status needs to be published to the UI, false = do not publish (or it
|
||||||
|
* already has been)
|
||||||
|
* @param connectionStatus enum UNKNOWN= test not run/default, OFFLINE = offline, STANDBY = TiVo is in standby
|
||||||
|
* , ONLINE = Online
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "TivoStatusData [cmdOk=" + cmdOk + ", time=" + time + ", channelNum=" + channelNum + ", subChannelNum="
|
||||||
|
+ subChannelNum + ", msg=" + msg + ", pubToUI=" + pubToUI + ", connectionStatus=" + connectionStatus
|
||||||
|
+ "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link isCmdOK} indicates if the last command executed correctly.
|
||||||
|
*
|
||||||
|
* @return cmdOk boolean true = executed correctly, false = last command failed with error message
|
||||||
|
*/
|
||||||
|
public boolean isCmdOk() {
|
||||||
|
return cmdOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link} sets the value indicating if the last command executed correctly.
|
||||||
|
*
|
||||||
|
* @param cmdOk boolean true = executed correctly, false = last command failed with error message
|
||||||
|
*/
|
||||||
|
public void setCmdOk(boolean cmdOk) {
|
||||||
|
this.cmdOk = cmdOk;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link getChannelNum} gets the channel number, -1 indicates no channel received. Valid channel range 1-9999.
|
||||||
|
*
|
||||||
|
* @return the channel number
|
||||||
|
*/
|
||||||
|
public int getChannelNum() {
|
||||||
|
return channelNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link setChannelNum} sets the channel number, -1 indicates no channel received. Valid channel range 1-9999.
|
||||||
|
*
|
||||||
|
* @param channelNum the new channel number
|
||||||
|
*/
|
||||||
|
public void setChannelNum(int channelNum) {
|
||||||
|
this.channelNum = channelNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link getSubChannelNum} gets the sub channel number, -1 indicates no sub channel received. Valid channel range
|
||||||
|
* 1-9999.
|
||||||
|
*
|
||||||
|
* @return the sub channel number
|
||||||
|
*/
|
||||||
|
public int getSubChannelNum() {
|
||||||
|
return subChannelNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link setSubChannelNum} sets the sub channel number, -1 indicates no sub channel received. Valid channel range
|
||||||
|
* 1-9999.
|
||||||
|
*
|
||||||
|
* @param subChannelNum the new sub channel number
|
||||||
|
*/
|
||||||
|
public void setSubChannelNum(int subChannelNum) {
|
||||||
|
this.subChannelNum = subChannelNum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link setRecording} set to true if current channel is recording
|
||||||
|
*
|
||||||
|
* @param isRecording true = current channel is recording
|
||||||
|
*/
|
||||||
|
public void setRecording(boolean isRecording) {
|
||||||
|
this.isRecording = isRecording;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link getPubToUI} get status indicating if current channel is recording
|
||||||
|
*
|
||||||
|
* @return isRecording true = current channel is recording
|
||||||
|
*/
|
||||||
|
public boolean isRecording() {
|
||||||
|
return isRecording;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link getMsg} gets status message string
|
||||||
|
*
|
||||||
|
* @return msg string
|
||||||
|
*/
|
||||||
|
public String getMsg() {
|
||||||
|
return msg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link setPubToUI} set to true if this status needs to be published to the channel / UI / Thing, false = do not
|
||||||
|
* publish (or it already has been).
|
||||||
|
*
|
||||||
|
* @param pubToUI true = publish status to the channel objects
|
||||||
|
*/
|
||||||
|
public void setPubToUI(boolean pubToUI) {
|
||||||
|
this.pubToUI = pubToUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link getPubToUI} get status indicating that the event needs to be published to the channel / UI / Thing, false
|
||||||
|
* = do not publish (or it already has been).
|
||||||
|
*
|
||||||
|
* @return pubToUI true = publish status to the channel objects
|
||||||
|
*/
|
||||||
|
public boolean getPubToUI() {
|
||||||
|
return pubToUI;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link setConnectionStatus} indicates the state of the connection / connection tests. Drives online/offline state
|
||||||
|
* of the
|
||||||
|
* Thing and connection process.
|
||||||
|
*
|
||||||
|
* @param connectionStatus enum UNKNOWN= test not run/default, OFFLINE = offline, STANDBY = TiVo is in standby,
|
||||||
|
* ONLINE = Online
|
||||||
|
*/
|
||||||
|
public void setConnectionStatus(ConnectionStatus connectionStatus) {
|
||||||
|
this.connectionStatus = connectionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link getConnectionStatus} returns the state of the connection / connection tests. Drives online/offline state
|
||||||
|
* of the
|
||||||
|
* Thing and connection process.
|
||||||
|
*
|
||||||
|
* @return ConnectionStatus enum UNKNOWN= test not run/default, OFFLINE = offline, STANDBY = TiVo is in standby,
|
||||||
|
* ONLINE = Online
|
||||||
|
*/
|
||||||
|
public ConnectionStatus getConnectionStatus() {
|
||||||
|
return connectionStatus;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,466 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010-2021 Contributors to the openHAB project
|
||||||
|
*
|
||||||
|
* See the NOTICE file(s) distributed with this work for additional
|
||||||
|
* information.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available under the
|
||||||
|
* terms of the Eclipse Public License 2.0 which is available at
|
||||||
|
* http://www.eclipse.org/legal/epl-2.0
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: EPL-2.0
|
||||||
|
*/
|
||||||
|
package org.openhab.binding.tivo.internal.service;
|
||||||
|
|
||||||
|
import static org.openhab.binding.tivo.internal.TiVoBindingConstants.CONFIG_SOCKET_TIMEOUT_MS;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.net.SocketTimeoutException;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
|
import org.openhab.binding.tivo.internal.handler.TiVoHandler;
|
||||||
|
import org.openhab.binding.tivo.internal.service.TivoStatusData.ConnectionStatus;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TivoStatusProvider class to maintain a connection out to the Tivo, monitor and process status messages returned..
|
||||||
|
*
|
||||||
|
* @author Jayson Kubilis - Initial contribution
|
||||||
|
* @author Andrew Black - Updates / compilation corrections
|
||||||
|
* @author Michael Lobstein - Updated for OH3
|
||||||
|
*/
|
||||||
|
|
||||||
|
@NonNullByDefault
|
||||||
|
public class TivoStatusProvider {
|
||||||
|
private static final Pattern TIVO_STATUS_PATTERN = Pattern.compile("^CH_STATUS (\\d{4}) (?:(\\d{4}))?");
|
||||||
|
private static final int TIMEOUT_SEC = 3000;
|
||||||
|
|
||||||
|
private final Logger logger = LoggerFactory.getLogger(TivoStatusProvider.class);
|
||||||
|
private @Nullable Socket tivoSocket = null;
|
||||||
|
private @Nullable PrintStream streamWriter = null;
|
||||||
|
private @Nullable StreamReader streamReader = null;
|
||||||
|
private @Nullable TiVoHandler tivoHandler = null;
|
||||||
|
private TivoStatusData tivoStatusData = new TivoStatusData();
|
||||||
|
private TivoConfigData tivoConfigData = new TivoConfigData();
|
||||||
|
private final String thingUid;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new TivoConfigStatusProvider.
|
||||||
|
*
|
||||||
|
* @param tivoConfigData {@link TivoConfigData} configuration data for the specific thing.
|
||||||
|
* @param tivoStatusData {@link TivoStatusData} status data for the specific thing.
|
||||||
|
* @param tivoHandler {@link TivoHandler} parent handler object for the TivoConfigStatusProvider.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
public TivoStatusProvider(TivoConfigData tivoConfigData, TiVoHandler tivoHandler) {
|
||||||
|
this.tivoStatusData = new TivoStatusData(false, -1, -1, false, "INITIALISING", false, ConnectionStatus.UNKNOWN);
|
||||||
|
this.tivoConfigData = tivoConfigData;
|
||||||
|
this.tivoHandler = tivoHandler;
|
||||||
|
this.thingUid = tivoHandler.getThing().getUID().getAsString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link statusRefresh} initiates a connection to the TiVo. When a new connection is made and the TiVo is online,
|
||||||
|
* the current channel is always returned. The connection is then closed (allows the socket to be used by other
|
||||||
|
* devices).
|
||||||
|
*
|
||||||
|
* @return {@link TivoStatusData} object
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void statusRefresh() throws InterruptedException {
|
||||||
|
if (tivoStatusData.getConnectionStatus() != ConnectionStatus.INIT) {
|
||||||
|
logger.debug(" statusRefresh '{}' - EXISTING status data - '{}'", tivoConfigData.getCfgIdentifier(),
|
||||||
|
tivoStatusData.toString());
|
||||||
|
}
|
||||||
|
connTivoConnect();
|
||||||
|
doNappTime();
|
||||||
|
if (!tivoConfigData.isKeepConnActive()) {
|
||||||
|
connTivoDisconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link cmdTivoSend} sends a command to the Tivo.
|
||||||
|
*
|
||||||
|
* @param tivoCommand the complete command string (KEYWORD + PARAMETERS e.g. SETCH 102) to send.
|
||||||
|
* @return {@link TivoStatusData} status data object, contains the result of the command.
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public @Nullable TivoStatusData cmdTivoSend(String tivoCommand) throws InterruptedException {
|
||||||
|
boolean connected = connTivoConnect();
|
||||||
|
PrintStream streamWriter = this.streamWriter;
|
||||||
|
|
||||||
|
if (!connected || streamWriter == null) {
|
||||||
|
return new TivoStatusData(false, -1, -1, false, "CONNECTION FAILED", false, ConnectionStatus.OFFLINE);
|
||||||
|
}
|
||||||
|
logger.debug("TiVo '{}' - sending command: '{}'", tivoConfigData.getCfgIdentifier(), tivoCommand);
|
||||||
|
int repeatCount = 1;
|
||||||
|
// Handle special keyboard "repeat" commands
|
||||||
|
if (tivoCommand.contains("*")) {
|
||||||
|
repeatCount = Integer.parseInt(tivoCommand.substring(tivoCommand.indexOf("*") + 1));
|
||||||
|
tivoCommand = tivoCommand.substring(0, tivoCommand.indexOf("*"));
|
||||||
|
logger.debug("TiVo '{}' - repeating command: '{}' for '{}' times", tivoConfigData.getCfgIdentifier(),
|
||||||
|
tivoCommand, repeatCount);
|
||||||
|
}
|
||||||
|
for (int i = 1; i <= repeatCount; i++) {
|
||||||
|
// Send the command
|
||||||
|
streamWriter.println(tivoCommand.toString() + "\r");
|
||||||
|
if (streamWriter.checkError()) {
|
||||||
|
logger.debug("TiVo '{}' - called cmdTivoSend and encountered an IO error",
|
||||||
|
tivoConfigData.getCfgIdentifier());
|
||||||
|
tivoStatusData = new TivoStatusData(false, -1, -1, false, "CONNECTION FAILED", false,
|
||||||
|
ConnectionStatus.OFFLINE);
|
||||||
|
connTivoReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tivoStatusData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link statusParse} processes the {@link TivoStatusData} status message returned from the TiVo.
|
||||||
|
*
|
||||||
|
* For channel status messages form 'CH_STATUS channel reason' or 'CH_STATUS channel sub-channel reason' calls
|
||||||
|
* {@link getParsedChannel} and returns the channel number (if a match is found in a valid formatted message).
|
||||||
|
*
|
||||||
|
* @param rawStatus string representing the message text returned by the TiVo
|
||||||
|
* @return TivoStatusData object conditionally populated based upon the raw status message
|
||||||
|
*/
|
||||||
|
private TivoStatusData statusParse(String rawStatus) {
|
||||||
|
logger.debug(" statusParse '{}' - running on string '{}'", tivoConfigData.getCfgIdentifier(), rawStatus);
|
||||||
|
|
||||||
|
if (rawStatus.contentEquals("COMMAND_TIMEOUT")) {
|
||||||
|
// Ignore COMMAND_TIMEOUT, they occur a few seconds after each successful command, just return existing
|
||||||
|
// status again
|
||||||
|
return this.tivoStatusData;
|
||||||
|
} else {
|
||||||
|
switch (rawStatus) {
|
||||||
|
case "":
|
||||||
|
return new TivoStatusData(false, -1, -1, false, "NO_STATUS_DATA_RETURNED", false,
|
||||||
|
tivoStatusData.getConnectionStatus());
|
||||||
|
case "LIVETV_READY":
|
||||||
|
return new TivoStatusData(true, -1, -1, false, "LIVETV_READY", true, ConnectionStatus.ONLINE);
|
||||||
|
case "CH_FAILED NO_LIVE":
|
||||||
|
return new TivoStatusData(false, -1, -1, false, "CH_FAILED NO_LIVE", true,
|
||||||
|
ConnectionStatus.STANDBY);
|
||||||
|
case "CH_FAILED RECORDING":
|
||||||
|
case "CH_FAILED MISSING_CHANNEL":
|
||||||
|
case "CH_FAILED MALFORMED_CHANNEL":
|
||||||
|
case "CH_FAILED INVALID_CHANNEL":
|
||||||
|
return new TivoStatusData(false, -1, -1, false, rawStatus, true, ConnectionStatus.ONLINE);
|
||||||
|
case "INVALID_COMMAND":
|
||||||
|
return new TivoStatusData(false, -1, -1, false, "INVALID_COMMAND", false, ConnectionStatus.ONLINE);
|
||||||
|
case "CONNECTION_RETRIES_EXHAUSTED":
|
||||||
|
return new TivoStatusData(false, -1, -1, false, "CONNECTION_RETRIES_EXHAUSTED", true,
|
||||||
|
ConnectionStatus.OFFLINE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only other documented status is in the form 'CH_STATUS channel reason' or
|
||||||
|
// 'CH_STATUS channel sub-channel reason'
|
||||||
|
Matcher matcher = TIVO_STATUS_PATTERN.matcher(rawStatus);
|
||||||
|
int chNum = -1; // -1 used globally to indicate channel number error
|
||||||
|
int subChNum = -1;
|
||||||
|
boolean isRecording = false;
|
||||||
|
|
||||||
|
if (matcher.find()) {
|
||||||
|
logger.debug(" statusParse '{}' - groups '{}' with group count of '{}'", tivoConfigData.getCfgIdentifier(),
|
||||||
|
matcher.group(), matcher.groupCount());
|
||||||
|
if (matcher.groupCount() == 1 || matcher.groupCount() == 2) {
|
||||||
|
chNum = Integer.parseInt(matcher.group(1).trim());
|
||||||
|
logger.debug(" statusParse '{}' - parsed channel '{}'", tivoConfigData.getCfgIdentifier(), chNum);
|
||||||
|
}
|
||||||
|
if (matcher.groupCount() == 2) {
|
||||||
|
subChNum = Integer.parseInt(matcher.group(2).trim());
|
||||||
|
logger.debug(" statusParse '{}' - parsed sub-channel '{}'", tivoConfigData.getCfgIdentifier(),
|
||||||
|
subChNum);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rawStatus.contains("RECORDING")) {
|
||||||
|
isRecording = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
rawStatus = rawStatus.replace(" REMOTE", "");
|
||||||
|
rawStatus = rawStatus.replace(" LOCAL", "");
|
||||||
|
return new TivoStatusData(true, chNum, subChNum, isRecording, rawStatus, true, ConnectionStatus.ONLINE);
|
||||||
|
}
|
||||||
|
logger.warn(" TiVo '{}' - Unhandled/unexpected status message: '{}'", tivoConfigData.getCfgIdentifier(),
|
||||||
|
rawStatus);
|
||||||
|
return new TivoStatusData(false, -1, -1, false, rawStatus, false, tivoStatusData.getConnectionStatus());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link connIsConnected} returns the connection state of the Socket, streamWriter and streamReader objects.
|
||||||
|
*
|
||||||
|
* @return true = connection exists and all objects look OK, false = connection does not exist or a problem has
|
||||||
|
* occurred
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
private boolean connIsConnected() {
|
||||||
|
Socket tivoSocket = this.tivoSocket;
|
||||||
|
PrintStream streamWriter = this.streamWriter;
|
||||||
|
|
||||||
|
if (tivoSocket == null) {
|
||||||
|
logger.debug(" connIsConnected '{}' - FALSE: tivoSocket=null", tivoConfigData.getCfgIdentifier());
|
||||||
|
return false;
|
||||||
|
} else if (!tivoSocket.isConnected()) {
|
||||||
|
logger.debug(" connIsConnected '{}' - FALSE: tivoSocket.isConnected=false",
|
||||||
|
tivoConfigData.getCfgIdentifier());
|
||||||
|
return false;
|
||||||
|
} else if (tivoSocket.isClosed()) {
|
||||||
|
logger.debug(" connIsConnected '{}' - FALSE: tivoSocket.isClosed=true", tivoConfigData.getCfgIdentifier());
|
||||||
|
return false;
|
||||||
|
} else if (streamWriter == null) {
|
||||||
|
logger.debug(" connIsConnected '{}' - FALSE: tivoIOSendCommand=null", tivoConfigData.getCfgIdentifier());
|
||||||
|
return false;
|
||||||
|
} else if (streamWriter.checkError()) {
|
||||||
|
logger.debug(" connIsConnected '{}' - FALSE: tivoIOSendCommand.checkError()=true",
|
||||||
|
tivoConfigData.getCfgIdentifier());
|
||||||
|
return false;
|
||||||
|
} else if (streamReader == null) {
|
||||||
|
logger.debug(" connIsConnected '{}' - FALSE: streamReader=null", tivoConfigData.getCfgIdentifier());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link connTivoConnect} manages the creation / retry process of the socket connection.
|
||||||
|
*
|
||||||
|
* @return true = connected, false = not connected
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public boolean connTivoConnect() throws InterruptedException {
|
||||||
|
for (int iL = 1; iL <= tivoConfigData.getNumRetry(); iL++) {
|
||||||
|
logger.debug(" connTivoConnect '{}' - starting connection process '{}' of '{}'.",
|
||||||
|
tivoConfigData.getCfgIdentifier(), iL, tivoConfigData.getNumRetry());
|
||||||
|
|
||||||
|
// Sort out the socket connection
|
||||||
|
if (connSocketConnect()) {
|
||||||
|
logger.debug(" connTivoConnect '{}' - Socket created / connection made.",
|
||||||
|
tivoConfigData.getCfgIdentifier());
|
||||||
|
StreamReader streamReader = this.streamReader;
|
||||||
|
if (streamReader != null && streamReader.isAlive()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debug(" connTivoConnect '{}' - Socket creation failed.", tivoConfigData.getCfgIdentifier());
|
||||||
|
TiVoHandler tivoHandler = this.tivoHandler;
|
||||||
|
if (tivoHandler != null) {
|
||||||
|
tivoHandler.setStatusOffline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Sleep and retry
|
||||||
|
doNappTime();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link connTivoReconnect} disconnect and reconnect the socket connection to the TiVo.
|
||||||
|
*
|
||||||
|
* @return boolean true = connection succeeded, false = connection failed
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public boolean connTivoReconnect() throws InterruptedException {
|
||||||
|
connTivoDisconnect();
|
||||||
|
doNappTime();
|
||||||
|
return connTivoConnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link connTivoDisconnect} cleanly closes the socket connection and dependent objects
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public void connTivoDisconnect() throws InterruptedException {
|
||||||
|
TiVoHandler tivoHandler = this.tivoHandler;
|
||||||
|
StreamReader streamReader = this.streamReader;
|
||||||
|
PrintStream streamWriter = this.streamWriter;
|
||||||
|
Socket tivoSocket = this.tivoSocket;
|
||||||
|
|
||||||
|
logger.debug(" connTivoSocket '{}' - requested to disconnect/cleanup connection objects",
|
||||||
|
tivoConfigData.getCfgIdentifier());
|
||||||
|
|
||||||
|
// if isCfgKeepConnOpen = false, don't set status to OFFLINE since the socket is closed after each command
|
||||||
|
if (tivoHandler != null && tivoConfigData.isKeepConnActive()) {
|
||||||
|
tivoHandler.setStatusOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streamWriter != null) {
|
||||||
|
streamWriter.close();
|
||||||
|
this.streamWriter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (tivoSocket != null) {
|
||||||
|
tivoSocket.close();
|
||||||
|
this.tivoSocket = null;
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug(" TiVo '{}' - I/O exception while disconnecting: '{}'. Connection closed.",
|
||||||
|
tivoConfigData.getCfgIdentifier(), e.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (streamReader != null) {
|
||||||
|
streamReader.interrupt();
|
||||||
|
streamReader.join(TIMEOUT_SEC);
|
||||||
|
this.streamReader = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link connSocketConnect} opens a Socket connection to the TiVo. Creates a {@link StreamReader} (Input)
|
||||||
|
* thread to read the responses from the TiVo and a PrintStream (Output) {@link cmdTivoSend}
|
||||||
|
* to send commands to the device.
|
||||||
|
*
|
||||||
|
* @param pConnect true = make a new connection , false = close existing connection
|
||||||
|
* @return boolean true = connection succeeded, false = connection failed
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
private synchronized boolean connSocketConnect() throws InterruptedException {
|
||||||
|
logger.debug(" connSocketConnect '{}' - attempting connection to host '{}', port '{}'",
|
||||||
|
tivoConfigData.getCfgIdentifier(), tivoConfigData.getHost(), tivoConfigData.getTcpPort());
|
||||||
|
|
||||||
|
if (connIsConnected()) {
|
||||||
|
logger.debug(" connSocketConnect '{}' - already connected to host '{}', port '{}'",
|
||||||
|
tivoConfigData.getCfgIdentifier(), tivoConfigData.getHost(), tivoConfigData.getTcpPort());
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// something is wrong, so force a disconnect/clean up so we can try again
|
||||||
|
connTivoDisconnect();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
Socket tivoSocket = new Socket(tivoConfigData.getHost(), tivoConfigData.getTcpPort());
|
||||||
|
tivoSocket.setKeepAlive(true);
|
||||||
|
tivoSocket.setSoTimeout(CONFIG_SOCKET_TIMEOUT_MS);
|
||||||
|
tivoSocket.setReuseAddress(true);
|
||||||
|
|
||||||
|
if (tivoSocket.isConnected() && !tivoSocket.isClosed()) {
|
||||||
|
if (streamWriter == null) {
|
||||||
|
streamWriter = new PrintStream(tivoSocket.getOutputStream(), false);
|
||||||
|
}
|
||||||
|
if (this.streamReader == null) {
|
||||||
|
StreamReader streamReader = new StreamReader(tivoSocket.getInputStream());
|
||||||
|
streamReader.start();
|
||||||
|
this.streamReader = streamReader;
|
||||||
|
}
|
||||||
|
this.tivoSocket = tivoSocket;
|
||||||
|
} else {
|
||||||
|
logger.debug(" connSocketConnect '{}' - socket creation failed to host '{}', port '{}'",
|
||||||
|
tivoConfigData.getCfgIdentifier(), tivoConfigData.getHost(), tivoConfigData.getTcpPort());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
logger.debug(" TiVo '{}' - while connecting, unexpected host error: '{}'",
|
||||||
|
tivoConfigData.getCfgIdentifier(), e.getMessage());
|
||||||
|
} catch (IOException e) {
|
||||||
|
if (tivoStatusData.getConnectionStatus() != ConnectionStatus.OFFLINE) {
|
||||||
|
logger.debug(" TiVo '{}' - I/O exception while connecting: '{}'", tivoConfigData.getCfgIdentifier(),
|
||||||
|
e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link doNappTime} sleeps for the period specified by the getCmdWaitInterval parameter. Primarily used to allow
|
||||||
|
* the TiVo time to process responses after a command is issued.
|
||||||
|
*
|
||||||
|
* @throws InterruptedException
|
||||||
|
*/
|
||||||
|
public void doNappTime() throws InterruptedException {
|
||||||
|
TimeUnit.MILLISECONDS.sleep(tivoConfigData.getCmdWaitInterval());
|
||||||
|
}
|
||||||
|
|
||||||
|
public TivoStatusData getServiceStatus() {
|
||||||
|
return tivoStatusData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServiceStatus(TivoStatusData tivoStatusData) {
|
||||||
|
this.tivoStatusData = tivoStatusData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link StreamReader} data stream reader that reads the status data returned from the TiVo.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class StreamReader extends Thread {
|
||||||
|
private @Nullable BufferedReader bufferedReader = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link StreamReader} construct a data stream reader that reads the status data returned from the TiVo via a
|
||||||
|
* BufferedReader.
|
||||||
|
*
|
||||||
|
* @param inputStream socket input stream.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public StreamReader(InputStream inputStream) {
|
||||||
|
this.setName("OH-binding-" + thingUid + "-" + tivoConfigData.getHost() + ":" + tivoConfigData.getTcpPort());
|
||||||
|
this.bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
|
||||||
|
this.setDaemon(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
logger.debug("streamReader {} is running. ", tivoConfigData.getCfgIdentifier());
|
||||||
|
while (!Thread.currentThread().isInterrupted()) {
|
||||||
|
String receivedData = null;
|
||||||
|
BufferedReader reader = bufferedReader;
|
||||||
|
if (reader == null) {
|
||||||
|
throw new IOException("streamReader failed: input stream is null");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
receivedData = reader.readLine();
|
||||||
|
} catch (SocketTimeoutException e) {
|
||||||
|
// Do nothing. Just allow the thread to check if it has to stop.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (receivedData != null) {
|
||||||
|
logger.debug("TiVo {} data received: {}", tivoConfigData.getCfgIdentifier(), receivedData);
|
||||||
|
TivoStatusData commandResult = statusParse(receivedData);
|
||||||
|
TiVoHandler handler = tivoHandler;
|
||||||
|
if (handler != null) {
|
||||||
|
handler.updateTivoStatus(tivoStatusData, commandResult);
|
||||||
|
}
|
||||||
|
tivoStatusData = commandResult;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
closeBufferedReader();
|
||||||
|
logger.debug("TiVo {} is disconnected. ", tivoConfigData.getCfgIdentifier(), e);
|
||||||
|
}
|
||||||
|
closeBufferedReader();
|
||||||
|
logger.debug("streamReader {} is stopped. ", tivoConfigData.getCfgIdentifier());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void closeBufferedReader() {
|
||||||
|
BufferedReader reader = bufferedReader;
|
||||||
|
if (reader != null) {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
this.bufferedReader = null;
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.debug("Error closing bufferedReader: {}", e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<binding:binding id="tivo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xmlns:binding="https://openhab.org/schemas/binding/v1.0.0"
|
||||||
|
xsi:schemaLocation="https://openhab.org/schemas/binding/v1.0.0 https://openhab.org/schemas/binding-1.0.0.xsd">
|
||||||
|
|
||||||
|
<name>TiVo DVR Binding</name>
|
||||||
|
<description>Controls TiVo DVRs that support the TiVo TCP Control Protocol v1.1</description>
|
||||||
|
|
||||||
|
</binding:binding>
|
@ -0,0 +1,139 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<thing:thing-descriptions bindingId="tivo"
|
||||||
|
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="sckt">
|
||||||
|
<label>TiVo DVR</label>
|
||||||
|
<description>Monitor and control your TiVo via DIRECT SOCKET commands leveraging the TIVO protocol 1.1 specification.
|
||||||
|
The TiVo TCP Control Protocol is an ASCII-based command protocol for remote control of a TiVo DVR over a TCP local
|
||||||
|
network connection. The commands allow control of channel changes, user interface navigation and allow the client to
|
||||||
|
send simulated remote control button presses to the Digital Video Recorder.</description>
|
||||||
|
<channels>
|
||||||
|
<channel id="channelSet" typeId="channelSet"/>
|
||||||
|
<channel id="channelForce" typeId="channelForce"/>
|
||||||
|
<channel id="isRecording" typeId="isRecording"/>
|
||||||
|
<channel id="menuTeleport" typeId="menuTeleport"/>
|
||||||
|
<channel id="irCommand" typeId="irCommand"/>
|
||||||
|
<channel id="kbdCommand" typeId="kbdCommand"/>
|
||||||
|
<channel id="dvrStatus" typeId="dvrStatus"/>
|
||||||
|
</channels>
|
||||||
|
|
||||||
|
<representation-property>host</representation-property>
|
||||||
|
|
||||||
|
<config-description>
|
||||||
|
<parameter name="host" type="text" required="true">
|
||||||
|
<label>Address</label>
|
||||||
|
<description>The IP address or host name of your TiVo DVR</description>
|
||||||
|
<context>network-address</context>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="tcpPort" type="integer" max="65535" min="1" required="true">
|
||||||
|
<default>31339</default>
|
||||||
|
<label>TCP Port</label>
|
||||||
|
<description>The
|
||||||
|
TCP port number used to connect to the TiVo. <![CDATA[ <b> ]]>Default:
|
||||||
|
31339<![CDATA[ </b> ]]></description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="numRetry" type="integer" max="20" min="0" required="true">
|
||||||
|
<default>5</default>
|
||||||
|
<label>Connection Retries</label>
|
||||||
|
<description>The
|
||||||
|
number of times to attempt reconnection to the TiVo box, if there is a connection failure.<![CDATA[ <b> ]]>Default:
|
||||||
|
5<![CDATA[ </b> ]]></description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="keepConActive" type="boolean" required="true">
|
||||||
|
<default>true</default>
|
||||||
|
<label>Keep Connection Open</label>
|
||||||
|
<description>Keep
|
||||||
|
connection to the TiVo open. Recommended for monitoring the TiVo for changes in TV channels. Disable if other
|
||||||
|
applications that use the Remote Control Protocol port will also be used e.g. mobile remote control applications.<![CDATA[ <b> ]]>Default:
|
||||||
|
True (Enabled)<![CDATA[ </b> ]]></description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="pollForChanges" type="boolean" required="true">
|
||||||
|
<default>true</default>
|
||||||
|
<label>Poll for Channel Changes</label>
|
||||||
|
<description>Check
|
||||||
|
TiVo for channel changes. Enable if openHAB and a physical remote control (or other services use the Remote Control
|
||||||
|
Protocol) will be used. <![CDATA[ <b> ]]>Default:
|
||||||
|
True (Enabled)<![CDATA[ </b> ]]></description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="pollInterval" type="integer" max="65535" min="3" required="true">
|
||||||
|
<default>10</default>
|
||||||
|
<label>Polling Interval (Seconds)</label>
|
||||||
|
<description>Number
|
||||||
|
of seconds between polling jobs to update status information from the TiVo. <![CDATA[ <b> ]]>Default:
|
||||||
|
10<![CDATA[ </b> ]]></description>
|
||||||
|
</parameter>
|
||||||
|
<parameter name="cmdWaitInterval" type="integer" max="500" min="0" required="true">
|
||||||
|
<default>200</default>
|
||||||
|
<label>Command Wait Interval (Milliseconds)</label>
|
||||||
|
<description>Period
|
||||||
|
to wait AFTER a command is sent to the TiVo in milliseconds, before checking that the command has completed. <![CDATA[ <b> ]]>Default:
|
||||||
|
200<![CDATA[ </b> ]]></description>
|
||||||
|
<advanced>true</advanced>
|
||||||
|
</parameter>
|
||||||
|
</config-description>
|
||||||
|
</thing-type>
|
||||||
|
|
||||||
|
<channel-type id="channelSet">
|
||||||
|
<item-type>Number</item-type>
|
||||||
|
<label>Current Channel - Request</label>
|
||||||
|
<description>Displays the current channel number. When changed (SETCH), tunes the DVR to the specified channel (unless
|
||||||
|
a recording is in progress on all available tuners). The TiVo must be in Live TV mode for this command to work. Type:
|
||||||
|
Number (1-9999) [Decimals allowed for OTA sub-channels], DisplayFormat: %d</description>
|
||||||
|
<state min="1" max="9999" step="0.1" pattern="%d"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="channelForce">
|
||||||
|
<item-type>Number</item-type>
|
||||||
|
<label>Current Channel - Forced</label>
|
||||||
|
<description>Displays the current channel number. When changed (FORCECH), tunes the DVR to the specified channel,
|
||||||
|
cancelling any recordings in progress if necessary i.e. all tuners are already in use / recording. The TiVo must be
|
||||||
|
in Live TV mode for this command to work. Type: Number (1-9999) [Decimals allowed for OTA sub-channels],
|
||||||
|
DisplayFormat: %d</description>
|
||||||
|
<state min="1" max="9999" step="0.1" pattern="%d"></state>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="isRecording">
|
||||||
|
<item-type>Switch</item-type>
|
||||||
|
<label>Recording</label>
|
||||||
|
<description>Indicates if the current channel is recording.</description>
|
||||||
|
<state readOnly="true"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="menuTeleport">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Change Menu Screen</label>
|
||||||
|
<description>Change(TELEPORT) to one of the following TiVo menu screens: TIVO (Home), LIVE TV, GUIDE, NOW PLAYING (My
|
||||||
|
Shows), NETFLIX. Type: String</description>
|
||||||
|
<state readOnly="false">
|
||||||
|
<options>
|
||||||
|
<option value="TIVO">TIVO</option>
|
||||||
|
<option value="LIVETV">LIVETV</option>
|
||||||
|
<option value="GUIDE">GUIDE</option>
|
||||||
|
<option value="NOWPLAYING">NOWPLAYING</option>
|
||||||
|
<option value="NETFLIX">NETFLIX</option>
|
||||||
|
</options>
|
||||||
|
</state>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="irCommand">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Remote Control Button</label>
|
||||||
|
<description>Send a simulated button push (IRCODE) from the remote control to the TiVo. See Appendix A in document TCP
|
||||||
|
Remote Protocol 1.1 for supported codes. Type: String</description>
|
||||||
|
<state readOnly="false"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="kbdCommand">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>Keyboard Command</label>
|
||||||
|
<description>Sends a code (KEYBOARD) corresponding to a keyboard key press to the TiVo e.g. A-Z. See Appendix A in
|
||||||
|
document TCP Remote Protocol 1.1 for supported characters and special character codes. Type: String</description>
|
||||||
|
<state readOnly="false"/>
|
||||||
|
</channel-type>
|
||||||
|
<channel-type id="dvrStatus">
|
||||||
|
<item-type>String</item-type>
|
||||||
|
<label>TiVo Status</label>
|
||||||
|
<description>Action return code / channel information returned by the TiVo. Type: String {ReadOnly)</description>
|
||||||
|
<state readOnly="true" pattern="%s"/>
|
||||||
|
</channel-type>
|
||||||
|
</thing:thing-descriptions>
|
@ -286,6 +286,7 @@
|
|||||||
<module>org.openhab.binding.tellstick</module>
|
<module>org.openhab.binding.tellstick</module>
|
||||||
<module>org.openhab.binding.tesla</module>
|
<module>org.openhab.binding.tesla</module>
|
||||||
<module>org.openhab.binding.tibber</module>
|
<module>org.openhab.binding.tibber</module>
|
||||||
|
<module>org.openhab.binding.tivo</module>
|
||||||
<module>org.openhab.binding.touchwand</module>
|
<module>org.openhab.binding.touchwand</module>
|
||||||
<module>org.openhab.binding.tplinksmarthome</module>
|
<module>org.openhab.binding.tplinksmarthome</module>
|
||||||
<module>org.openhab.binding.tr064</module>
|
<module>org.openhab.binding.tr064</module>
|
||||||
|
Loading…
Reference in New Issue
Block a user