Skip to content

Commit

Permalink
Merge pull request #1 from DaveL17/master
Browse files Browse the repository at this point in the history
TBH, I don't know if this API is still available on DIRECTV devices, but sure!
  • Loading branch information
indigo-jay authored Jan 8, 2024
2 parents 2ec3151 + 846e1e3 commit 73c7374
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 135 deletions.
4 changes: 2 additions & 2 deletions DIRECTV DVR Control.indigoPlugin/Contents/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
<plist version="1.0">
<dict>
<key>PluginVersion</key>
<string>1.1.1</string>
<string>2023.0.1</string>
<key>ServerApiVersion</key>
<string>1.19</string>
<string>3.0</string>
<key>CFBundleDisplayName</key>
<string>DIRECTV DVR Control</string>
<key>CFBundleIdentifier</key>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
</List>
</Field>
<Field id="description" type="textfield" hidden="true">
<Label></Label>
<Label/>
</Field>
</ConfigUI>
</Action>
Expand All @@ -86,7 +86,7 @@
<Label>Minor (not generally changed):</Label>
</Field>
<Field id="description" type="textfield" hidden="true">
<Label></Label>
<Label/>
</Field>
</ConfigUI>
</Action>
Expand Down
277 changes: 146 additions & 131 deletions DIRECTV DVR Control.indigoPlugin/Contents/Server Plugin/plugin.py
Original file line number Diff line number Diff line change
@@ -1,144 +1,159 @@
#! /usr/bin/env python
# -*- coding: utf-8 -*-
####################
# Copyright (c) 2014, Perceptive Automation, LLC. All rights reserved.
# http://www.indigodomo.com
"""
Plugin to control DirecTV Receivers and DVRs
Copyright (c) 2024, Perceptive Automation, LLC. All rights reserved.
http://www.indigodomo.com
"""
################################################################################
from datetime import datetime
import urllib
import socket
import simplejson as json
import requests
import socket # TODO: is socket needed anymore?

try:
import indigo
except ImportError:
pass

################################################################################
# Globals
################################################################################
keyText = {"power":"Power Toggle",
"poweron":"Power On",
"poweroff":"Power Off",
"play":"Play",
"pause":"Pause",
"rew":"Rewind",
"replay":"Replay",
"stop":"Stop",
"advance":"Advance",
"ffwd":"Fast Forward",
"record":"Record",
"guide":"Guide",
"active":"Active",
"list":"List",
"exit":"Exit",
"back":"Back",
"menu":"Menu",
"info":"Info",
"up":"Up",
"down":"Down",
"left":"Left",
"right":"Right",
"select":"Select",
"red":"Red",
"green":"Green",
"yellow":"Yellow",
"blue":"Blue",
"chanup":"Channel/Page Up",
"chandown":"Channel/Page Down",
"prev":"Previous Channel",
"0":"0",
"1":"1",
"2":"2",
"3":"3",
"4":"4",
"5":"5",
"6":"6",
"7":"7",
"8":"8",
"9":"9",
"enter":"Enter",
"dash":"Dash",
"format":"Format"}
keyText = {
"power": "Power Toggle",
"poweron": "Power On",
"poweroff": "Power Off",
"play": "Play",
"pause": "Pause",
"rew": "Rewind",
"replay": "Replay",
"stop": "Stop",
"advance": "Advance",
"ffwd": "Fast Forward",
"record": "Record",
"guide": "Guide",
"active": "Active",
"list": "List",
"exit": "Exit",
"back": "Back",
"menu": "Menu",
"info": "Info",
"up": "Up",
"down": "Down",
"left": "Left",
"right": "Right",
"select": "Select",
"red": "Red",
"green": "Green",
"yellow": "Yellow",
"blue": "Blue",
"chanup": "Channel/Page Up",
"chandown": "Channel/Page Down",
"prev": "Previous Channel",
"0": "0",
"1": "1",
"2": "2",
"3": "3",
"4": "4",
"5": "5",
"6": "6",
"7": "7",
"8": "8",
"9": "9",
"enter": "Enter",
"dash": "Dash",
"format": "Format"
}

################################################################################
class Plugin(indigo.PluginBase):
########################################
# Class properties
########################################
########################################
# Class properties
########################################

########################################
def __init__(self, plugin_id, plugin_display_name, plugin_version, plugin_prefs):
super().__init__(plugin_id, plugin_display_name, plugin_version, plugin_prefs)
# list of scripts that are currently running - gives us a chance to kill them if we need to when the plugin is
# asked to quit, and it also allows us to get the output from the script to insert into the variable if it's
# configured that way
# self.runningScripts = [] # TODO: this doesn't seem to be used anywhere.
self.debug = False
socket.setdefaulttimeout(5.0)

########################################
def validate_action_config_ui(self, values_dict: indigo.Dict, type_id: str, dev_id: int):
self.debugLog(f"Validating action config for type: {type_id}")
errors_dict: indigo.Dict = indigo.Dict()
ip_address: str = values_dict['address']
client_mac: str = values_dict['clientMAC']
if client_mac == "":
client_mac = "0"
if type_id == "setChannel":
try:
channel_number = int(values_dict['channelNumber'])
if channel_number < 1 or channel_number > 9999:
raise Exception
values_dict['description'] = f"DIRECTV Control: change to channel {channel_number}"
except:
errors_dict['channelNumber'] = 'invalid channel number, must be between 1 and 9999'
try:
minor_number = int(values_dict['minorNumber'])
if minor_number < 0 or minor_number > 99:
if minor_number != 65535:
raise Exception
except:
errors_dict['minorNumber'] = 'invalid minor number, must be between 0 and 999 or 65535 (default)'
else:
if 'keyToPress' not in values_dict:
errors_dict['keyToPress'] = 'you must select a key to press'
else:
values_dict['description'] = f"DIRECTV Control: Press key {keyText[values_dict['keyToPress']]}"
if len(errors_dict) > 0:
return (False, values_dict, errors_dict)
return (True, values_dict)

########################################
def sendKeyPress(self, action):
address: str = action.props.get('address', "")
client_mac: str = action.props.get('clientMAC', "0")
key: str = action.props.get('keyToPress', "")
self.debugLog(f"sendKeyPress called: send {key} to {address} client {client_mac}")
if (address == "") or (key == ""):
self.errorLog("Key Press action misconfigured, no key sent")
else:
try:
url = f"http://{address}:8080/remote/processKey?key={key}&clientAddr={client_mac}"
f = requests.get(url, timeout=5)
reply = f.json()
status_code = int(reply['status']['code'])
if status_code != 200:
self.errorLog(
f"Send key press action failed with status code: {status_code:d} message: "
f"{reply['status']['msg']} (probably an incorrect key name: '{key}')"
)
except:
self.errorLog("Send key press action failed with a network error - check your DVR to make sure it's on")

########################################
def __init__(self, pluginId, pluginDisplayName, pluginVersion, pluginPrefs):
super(Plugin, self).__init__(pluginId, pluginDisplayName, pluginVersion, pluginPrefs)
# list of scripts that are currently running - gives us a chance to kill them if we need to
# when the plugin is asked to quit and it also allows us to get the output from the script
# to insert into the variable if it's configured that way
self.runningScripts = list()
self.debug = False
socket.setdefaulttimeout(5.0)

########################################
def validateActionConfigUi(self, valuesDict, typeId, devId):
self.debugLog(u"Validating action config for type: " + typeId)
errorsDict = indigo.Dict()
ipAddress = valuesDict['address']
clientMAC = valuesDict['clientMAC']
if clientMAC == "":
clientMac = "0"
if typeId == "setChannel":
try:
channelNumber = int(valuesDict['channelNumber'])
if channelNumber < 1 or channelNumber > 9999:
raise
valuesDict['description'] = "DIRECTV Control: change to channel " + str(channelNumber)
except:
errorsDict['channelNumber'] = 'invalid channel number, must be between 1 and 9999'
try:
minorNumber = int(valuesDict['minorNumber'])
if minorNumber < 0 or minorNumber > 99:
if minorNumber != 65535:
raise
except:
errorsDict['minorNumber'] = 'invalid minor number, must be between 0 and 999 or 65535 (default)'
else:
if 'keyToPress' not in valuesDict:
errorsDict['keyToPress'] = 'you must select a key to press'
else:
valuesDict['description'] = "DIRECTV Control: Press key " + keyText[valuesDict['keyToPress']]
if len(errorsDict) > 0:
return (False, valuesDict, errorsDict)
return (True, valuesDict)

########################################
def sendKeyPress(self, action):
address = action.props.get('address',"")
clientMAC = action.props.get('clientMAC', "0")
key = action.props.get('keyToPress', "")
self.debugLog(u"sendKeyPress called: send %s to %s client %s" % (key,address, clientMAC))
if (address == "") or (key == ""):
self.errorLog(u"Key Press action misconfigured, no key sent")
else:
try:
f = urllib.urlopen("http://%s:8080/remote/processKey?key=%s&clientAddr=%s" % (address, key, clientMAC))
reply = json.load(f)
statusCode = int(reply['status']['code'])
if statusCode != 200:
self.errorLog(u"Send key press action failed with status code: %i message: %s (probably an incorrect key name: '%s')" % (statusCode,reply['status']['msg'],key))
except:
self.errorLog(u"Send key press action failed with a network error - check your DVR to make sure it's on")

########################################
def setChannel(self, action):
address = action.props.get('address',"")
clientMAC = action.props.get('clientMAC', "0")
channel = action.props.get('channelNumber', "")
minor = action.props.get('minorNumber', "65535")
self.debugLog(u"setChannel called: send channel %s minor %s to %s client %s" % (channel,minor,address, clientMAC))
if (address == "") or (channel == "") or (minor == ""):
self.errorLog(u"Go To Channel action misconfigured")
else:
try:
f = urllib.urlopen("http://%s:8080/tv/tune?major=%s&minor=%s&clientAddr=%s" % (address, channel, minor, clientMAC))
reply = json.load(f)
statusCode = int(reply['status']['code'])
if statusCode != 200:
self.errorLog(u"Go To Channel action failed with status code: %i message: %s" % (statusCode,reply['status']['msg']))
except:
self.errorLog(u"Go To Channel action failed with a network error - check your DVR to make sure it's on")

########################################
def setChannel(self, action):
address: str = action.props.get('address', "")
channel: str = action.props.get('channelNumber', "")
client_mac: str = action.props.get('clientMAC', "0")
minor: str = action.props.get('minorNumber', "65535")
self.debugLog(f"setChannel called: send channel {channel} minor {minor} to {address} client {client_mac}")
if (address == "") or (channel == "") or (minor == ""):
self.errorLog("Go To Channel action misconfigured")
else:
try:
url = f"http://{address}:8080/tv/tune?major={channel}&minor={minor}&clientAddr={client_mac}"
f = requests.get(url, timeout=5)
reply = f.json()
status_code = int(reply['status']['code'])
if status_code != 200:
self.errorLog(
f"Go To Channel action failed with status code: {status_code:d} message: "
f"{reply['status']['msg']}"
)
except:
self.errorLog("Go To Channel action failed with a network error - check your DVR to make sure it's on")

0 comments on commit 73c7374

Please sign in to comment.