Dct2000serial
Note: The correct title of this article is dct200serial. It appears incorrectly here due to technical restrictions.
Author | Ian Forde |
Description | Serial control of cable boxes in MythTV |
Supports |
Serial control of cable boxes in MythTV
Or... "Want to get serial control working on your DCT-2000 cable box?"
Version History
See changechannel.py
Credits
README Written by Ian Forde ( ian a duckland d org)
Mostly using code from Chris Griffiths ( dct2000 a y-fronts d com ) to do the actual work
Additional cable box functions written by Lonny Selinger
Inspired by Embeem's Tivo serial code
And various snippets of code from around the net. ;)
Contents
changechannel.py crcgen.py
Prerequisites
- You have to have a Motorola/GI DCT-2000 series box. DCT-2244, DCT-2224, and so on...
- You have to have firmware version 7.54 or higher on the box. It's somewhere off of the main menu when using the cable remote.
- Your serial port has to be enabled.
- Python2.2 on your myth backend
Note that if you meet prerequisite #1, but meet only one of 2 and 3, then you'll need to have your cable provider either a) Upgrade your firmware/enable your serial port or b) Swap out your cable box. Be creative in how you approach them. And you *don't* have to tell them why you want this... Especially the serial port bit...
Actually, regarding the serial port bit, I believe they might enable them if you tell them that you have or are planning to get a Tivo (note that I'm not
- endorsing* that approach...)
In any case, once you meet the prereqs, you can continue.
Installation
Okay - Basically, place the two scripts (crcgen.py, changechannel.py) into /usr/local/bin, make sure they are executable, then you're done.
cp crcgen.py changechannel.py /usr/local/bin cd /usr/local/bin chown 755 crcgen.py changechannel.py # Edit the SERIALPORT variable in changechannel.py to reflect which serial port you'll be using.
If you're using MythTV, you can point it to the external change channel script provided. (Here's a hint - it's the one called changechannel.py)
That's it!
The Code
#!/usr/bin/env python # Chris Griffiths, June 8th 2003 (dct2000@y-fronts.com) # # CRC calculator preset for CRC-CCIT suitable for a Motorola DCT2000 # Please see http://rcswww.urz.tu-dresden.de/~sr21/crctester.c # and http://rcswww.urz.tu-dresden.de/~sr21/crc.html for the # original c code to this, a web based crc generator, and # a description of what these settings do order = 16; polynom = 0x1021; direct = 1; crcinit = 0x0000; crcxor = 0x0000; refin = 1; refout = 1; def reflect (crc, bitnum): i = 1 << (bitnum-1) j = 1 crcout=0 while i: if (crc & i): crcout = crcout | j j = j << 1 i = i >> 1 return crcout def calcrc(data_bytes): crcmask = (((1<<(order-1))-1)<<1)|1; crchighbit = 1<<(order-1); # compute missing initial CRC value if direct: crcinit_direct = crcinit; crc = crcinit; for i in range (order): bit = crc & 1; if bit: crc = crc ^ polynom; crc >>= 1; if bit: crc = crc | crchighbit; crcinit_nondirect = crc; else: crcinit_nondirect = crcinit crc = crcinit for i in range (order): bit = crc & crchighbit crc = crc << 1; if bit: crc= crc ^ polynom; crc = crc & crcmask; crcinit_direct = crc; # now compute the crc crc = crcinit_direct for i in range( len(data_bytes) ): c = ord(data_bytes[i]) if refin: c = reflect(c, 8) j=0x80; while j: bit = crc & crchighbit crc = crc << 1 if (c & j): bit = bit ^ crchighbit if bit: crc = crc ^ polynom j = j >> 1 if refout: crc=reflect(crc, order) crc = crc ^ crcxor crc = crc & crcmask return crc
#!/usr/bin/env python # # Chris Griffiths, June 8th 2003 (dct2000@y-fronts.com) # # Simple program to send remote control sequences via serial port to a # motorola dct2000 # It does not respect sequence numbers, nor check the returned codes for # success or failure # # Modified by Ian Forde (ian@duckland.org) (Sometime in early 2003 - I don't # remember when...) # # Modified by Lonny Selinger to support additional dct2000 functions # # Version history # 0.87: Added an EXIT_KEY code to make the dct2000 osd go away # as quickly as possible, since I don't know how to disable # it yet! # 0.86: Removed the debugging flag. That's my responsibility, # not everyone else's! # 0.85: Added a debugging flag # Cleaned up the channel code determination by removing # a whole bunch of 'if' statements and the corresponding # variable names. # Eliminated the need for a separate shell script by # including channel number length normalizing into the # script. # Turned off flow control # Created a variable for setting the serial port # Fixed a bug in the alternate button parsing # 0.81: Experimental code added by Lonny Selinger for additional # functions such as last_channel, mute, favorite, chup, # chdown. Not integrated into Myth yet! # 0.8: it works! import serial # pyserial (http://pyserial.sourceforge.net/) import crcgen # CRC generator - preset to generate CCITT polynomial crc's import time # std python lib import sys # define the serial port SERIALPORT='/dev/ttyS1' # define button codes: ONOFF='\x0A' CHAN_UP='\x0B' CHAN_DOWN='\x0C' VOLUME_UP='\x0D' VOLUME_DOWN='\x0E' MUTE='\x0F' MUSIC='\x10' SELECT_OK='\x11' ENTER='\x11' EXIT_KEY='\x12' LAST_CHANNEL='\x13' SWITCH_AB='\x14' FAVORITE='\x15' PPV_KEY='\x16' A_KEY='\x17' P_F='\x18' MENU='\x19' OUTPUT_CHAN='\x1C' FAST_FORWARD='\x1D' REVERSE='\x1E' PAUSE='\x1F' BROWSE='\x22' CANCEL='\x23' LOCK='\x24' LIST='\x25' THEME='\x26' GUIDE='\x30' RECORD='\x31' HELP='\x32' INFO='\x33' UP='\x34' DOWN='\x35' LEFT='\x36' RIGHT='\x37' DAY_PLUS='\x38' DAY_MINUS='\x39' PAGE_PLUS='\x3A' PAGE_MINUS='\x3B' B_KEY='\x27' C_KEY='\x28' # This function below was blatantly lifted from: # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52660 def IsAllDigits( str ): """ Is the given string composed entirely of digits? """ return all(letter.isdigit() for letter in str) def debug(ctrl_str): for hexChar in ctrl_str: print "%#x" % ord(hexChar), print '\n' def word2str(val): return chr((val & 0xFF00) / 256) + chr(val & 0x00FF) def get_response(ser): #response = '' #byte = ser.read() #while byte: # response = response + byte # byte = ser.read() byte = ser.read() response = byte byte = ser.read() while byte: response += byte byte = ser.read(7) #print "length of response is ",len(response) return response def calc_code(payload=""): # # code is 10 78 ll ll ss 40 pp pp cc 10 03 # where ss = seq num, pp pp = payload (for a remote keypress first byte is 22), cc = crc # It seems the sequence number can be ignored - so long as we send this double sequence before it - # this is what embleem was using with the last sequence being the on/off keypress # # It also looks like we can only send one keypress at a time - although correct use of sequence number # be get that working # code = '\x78' + word2str(len(payload)+2) + '\x10\x40' + payload crc = crcgen.calcrc(code) code = "\x10\x70\x00\x02\x03\x40\xc8\x27\x10\x03\x10\x78\x00\x03\x03\x40\x00\x68\x96\x10\x03" + '\x10' + code + word2str(crc) + '\x10\x03' return code def send_to_unit(ser, key_presses): # # Send each keypress, then get the returned status from the box (and subsequently ignore it) # You have to read the return code, otherwise the next send with fail # for key in key_presses: ser.write(calc_code('\x22' + key)) get_response(ser) cmdarg = sys.argv[1] # added an extra code block to handle a few more options that # can be passed to remote.py. # options: last_channel(), mute(), favorite(), chan_up(), # chan_down() .... for now anyway # # Lonny Selinger if not IsAllDigits(cmdarg): if cmdarg in ("l", "m", "f", "u", "d"): BUTNKEY='' if cmdarg == "l" : BUTNKEY=LAST_CHANNEL elif cmdarg == "m" : BUTNKEY=MUTE elif cmdarg == "f" : BUTNKEY=FAVORITE elif cmdarg == "u" : BUTNKEY=CHAN_UP elif cmdarg == "d" : BUTNKEY=CHAN_DOWN serCon = serial.Serial(SERIALPORT,baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1, xonxoff=0, rtscts=0,) send_to_unit(serCon, BUTNKEY) else: if len(cmdarg) > 3: print "too many chars!" else: chanset=cmdarg.rjust(3, '0') CHANCODE=''.join(chr(int(c)) for c in chanset) serCon = serial.Serial(SERIALPORT,baudrate=9600, bytesize=serial.EIGHTBITS, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=1, xonxoff=0, rtscts=0,) send_to_unit(serCon, CHANCODE) send_to_unit(serCon, EXIT_KEY)