-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1 from DaveL17/master
TBH, I don't know if this API is still available on DIRECTV devices, but sure!
- Loading branch information
Showing
3 changed files
with
150 additions
and
135 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
277 changes: 146 additions & 131 deletions
277
DIRECTV DVR Control.indigoPlugin/Contents/Server Plugin/plugin.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") |