Dct2000serial

From MythTV Official Wiki
Revision as of 04:49, 24 May 2011 by Newacct (talk | contribs)

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Important.png 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

  1. You have to have a Motorola/GI DCT-2000 series box. DCT-2244, DCT-2224, and so on...
  2. 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.
  3. Your serial port has to be enabled.
  4. 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

Script.png crcgen.py

#!/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


Script.png changechannel.py

#!/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)


Script.png Makefile

PREFIX = /usr/local
BINPATH = ${PREFIX}/bin

all:

install:
	install -m 0755 changechannel.py ${BINPATH}
	install -m 0755 crcgen.py ${BINPATH}

uninstall:
	rm -f ${BINPATH}/changechannel.py
	rm -f ${BINPATH}/crcgen.py