Dct-channel

From MythTV Official Wiki
Jump to: navigation, search


Author Jim Paris
Description Channel-change script for DCT-2000-series cable boxes via a serial connection.
Supports


This page refers to the original dct-channel utility. It has since been updated.

dct-channel - switch channel on a DCT-2000-series cable box connected to the serial port via a straight-through cable and print the new channel, or just print the channel if no arguments are passed.

Tested on a Motorola DCT-2244 from Comcast.

Attempts to be smart about changing the channel. It will check and make sure we're not already on the channel, to avoid pulling up the OSD unnecessarily. It will also ensure that the box actually switches, attempting to exit menus and even power the box on if it doesn't appear to switch.

To use this from MythTV, just go into the MythTV card setup and point the external changer program to the "channel" binary. I also recommend the "-v" option.

The following command-line options may be useful if you have a problem:

-v Explain what's happening. Very useful.
-vv Explain, plus dump packets and extra debug.
--force Try to keep going after communication errors
--blind Blindly change the channel. This means that the code just sends a fixed sequence to the device and never checks for any response. It assumes that the channel change succeeded. This should be identical behavior to the old Python channel-changing scripts.
--nopower Never send KEY_POWER. Use this if your box seems to turn off unexpectedly.
-t 5 Scale all timeouts by 5x. Note that you can't go too high with the timeouts when using blind mode, because the box will stop waiting for keypresses if they come too slowly.

Known bugs:

If the box is off and on the correct channel, it will remain off. I don't know how to tell if the box is on or off except by noticing that it doesn't respond to a channel change request. If this is really a problem, using this program to change to a separate but valid channel and then back should ensure that the box turns on.

If a channel doesn't exist, the box will not switch to it, so this code will wrongly assume the box is off and send the power button, therefore turning it off.

Packet and sequence number handling needs to be better. See TODO.

Downloading

To download the source for this program, use the mythwikiscripts program:

 mkdir /tmp/dct-channel
 mythwikiscripts

and follow the prompts to download "dct-channel - Channel-change script for DCT-2000-series cable boxes."

Compiling

To compile the program:

 cd /tmp/dct-channel
 make
 make install


Script.png /tmp/dct-channel/COPYING

This program is free software; you can redistribute it and/or modify
it under the terms of version 2 of the GNU General Public License as
published by the Free Software Foundation, 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

For the complete license, please see http://www.fsf.org/licenses/gpl.txt
or request a copy from the author of this program.


Script.png /tmp/dct-channel/Makefile

CFLAGS = -Wall
PREFIX = /usr/local
MANPATH = ${PREFIX}/man/man1
BINPATH = ${PREFIX}/bin

all: channel.1 channel

version.h: VERSION
	echo "/* This file was automatically generated. */" >version.h
	echo "#define VERSION \"`cat VERSION` (`date +%Y-%m-%d`)\"" >>version.h

channel.o: crc.h serial.h packet.h debug.h remote.h opt.h version.h

crc.o: crc.h 

opt.o: opt.h

packet.o: crc.h serial.h packet.h debug.h

serial.o: crc.h serial.h debug.h

channel: channel.o crc.o opt.o packet.o serial.o

channel.1: channel.c version.h
	$(MAKE) channel
	if ! help2man -N --output=channel.1 ./channel ; then \
		echo "No manual page available." > channel.1 ; fi

install: channel.1 channel
	install -m 0755 channel ${BINPATH}
	install -m 0644 channel.1 ${MANPATH}

uninstall:
	rm -f ${BINPATH}/channel ${MANPATH}/channel.1

PACKAGE=dct-channel-`cat VERSION`
dist: channel.1 version.h
	mkdir -p ${PACKAGE}
	cp [A-Z]* *.[ch1] ${PACKAGE}
	tar cvzf ${PACKAGE}.tar.gz ${PACKAGE}
	rm -r ${PACKAGE}

clean distclean:
	rm -f *.o channel core

veryclean: clean
	rm -f channel.1


Script.png /tmp/dct-channel/NEWS

1.65, 2003-11-08
        Minor adjustments to key timings and retries

1.6, 2003-10-31
	Try 5 times instead of 4, and increase timeouts by 50% after
	second failed attempt to change channel.

1.5, 2003-10-26
	Wait for extra status messages after changing channel; without
        it, clearing the OSD would usually fail because we wouldn't
        know the proper sequence number
	
	Serial port is now locked properly to prevent multiple copies
        of channel from running.  If the port is locked, a new
        instance will wait up to 30 seconds for the lock to go away.

1.4, 2003-10-20
	Added new commandline options:
	      -f to ignore some errors during initialization
	      -n to never send KEY_POWER
	      -b to act just like the old Python script

1.3, 2003-10-18
        Removed autoconf/automake to cut down size

1.2, 2003-10-17
	Non-GPL code rewritten from scratch; now uses
	autoconf/automake and has more command line options.

1.1, 2003-10-08
	Minor improvements and bugfixes

1.0, 2003-10-07
	Initial release


Script.png /tmp/dct-channel/README

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

dct-channel - switch channel on a DCT-2000-series cable box connected
to the serial port via a straight-through cable and print the new
channel, or just print the channel if no arguments are passed.

Tested on a Motorola DCT-2244 from Comcast.

Attempts to be smart about changing the channel.  It will check and
make sure we're not already on the channel, to avoid pulling up the
OSD unnecessarily.  It will also ensure that the box actually
switches, attempting to exit menus and even power the box on if it
doesn't appear to switch.

To use this from MythTV, just go into the MythTV card setup and point
the external changer program to the "channel" binary.  I also
recommend the "-v" option.

The following command-line options may be useful if you have
a problem:

-v	  Explain what's happening.  Very useful.
-vv	  Explain, plus dump packets and extra debug.
--force   Try to keep going after communication errors
--blind   Blindly change the channel.  This means that the code
	  just sends a fixed sequence to the device and never
	  checks for any response.  It assumes that the channel
	  change succeeded.  This should be identical behavior
	  to the old Python channel-changing scripts.
--nopower Never send KEY_POWER.  Use this if your box seems
	  to turn off unexpectedly.
-t 5	  Scale all timeouts by 5x.  Note that you can't go too
	  high with the timeouts when using blind mode, because
	  the box will stop waiting for keypresses if they come
	  too slowly.
 
Known bugs:

If the box is off and on the correct channel, it will remain off.
I don't know how to tell if the box is on or off except by
noticing that it doesn't respond to a channel change request. 
If this is really a problem, using this program to change to 
a separate but valid channel and then back should ensure that
the box turns on.

If a channel doesn't exist, the box will not switch to it,
so this code will wrongly assume the box is off and send
the power button, therefore turning it off.

Packet and sequence number handling needs to be better.  See TODO.


Script.png /tmp/dct-channel/TODO

Learn more about the DCT protocol and make this program smarter.

--

Figure out how to handle out-of-order packets.  For example, when
initializing the box, we may or may not get a channel status
after sending initialize_2.  If we wait for a channel status
and one never comes, then there's an unnecessary delay.
It would be better if we could just send our next command,
and while waiting for the acknowledgement for the new command,
properly handle a channel status that shows up.  Currently,
this won't work because sending the new command depends on the
sequence number of the previously received one, so the simple
algorithm gets messed up due to the misordering of the packets
(because the two link directions are asynchronous).
Also, the box may at any time send a 0x10 0x70 which means
it wants to get reinitalized, and we should handle that.

The "proper" thing to do, I think, is:

1) Figure out how to handle sequence numbers when we want to send two
   packets in a row without waiting for a response, etc.  It's more
   than just the simple "increment low nibble" or "increment high
   nibble" algorithm that is currently used.
2) Move all packet receiving to a separate thread that blocks 
   on reading from the serial port.
3) When we send a packet and expect a response, add it to some list or
   queue.
4) When we receive a packet, 
   a) Check to see if the box is requesting reinitialization.
      If so, reinitialize it and re-send all pending packets from 
      the list in #3.
   b) See if it's a response to any of the packets in the list in #3
      and if so, flag that they were answered.
5) In the main code, rather than waiting for a set amount of time for
   a single packet, wait a set amount of time for the flag set in #4.

So checking channel status should be something like

   packet p;
   make_channel_status_packet(&p, 1234);
   pthread_queue_put(send_queue, &p);
   pthread_cond_wait(1234, TIMEOUT);

Man, who ever thought changing a channel could be so hard?
I'm sure there are more complicated things you can do that warrant
having such a complicated protocol, but I really wish they had
provided a secondary, simpler interface for doing simple tasks.


Script.png /tmp/dct-channel/VERSION

1.7


Script.png /tmp/dct-channel/channel.c

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "crc.h"
#include "serial.h"
#include "packet.h"
#include "debug.h"
#include "remote.h"
#include "opt.h"
#include "version.h"

struct options opt[] = {
	{ 'p', "port", "port", "serial port (default: /dev/ttyS0)" },
	{ 'f', "force", NULL, "keep going after communication errors" },
	{ 'o', "ok", NULL, "send OK after channel number" },
	{ 'b', "blind", NULL, "send keys blindly and ignore returned data" },
	{ 'n', "nopower", NULL, "never attempt to turn box on" },
	{ 't', "timeout", "scale", "scale all timeouts by this much "
	                           "(default 1.0)" },
	{ 'q', "quiet", NULL, "suppress printing of channel on exit" },
	{ 'v', "verbose", NULL, "be verbose (use twice for extra debugging)" },
	{ 'h', "help", NULL, "this help" },
	{ 'V', "version", NULL, "show program version and exit" },
	{ 0, NULL, NULL, NULL }
};	

int verb_count = 0;
int send_ok = 0;

/* Timeouts, in msec. */
double timeout_scale = 1.0;


#define TIMEOUT_TINY (int)(timeout_scale*250)	/* Messages that may not
						   some (status, etc) */
#define TIMEOUT_SHORT (int)(timeout_scale*1500)	/* For most messages */
#define TIMEOUT_LONG (int)(timeout_scale*5000) 	/* Waiting for chan. change */
#define KEY_SLEEP (int)(timeout_scale*300)	/* After sending keypress */
#define POWER_SLEEP (int)(timeout_scale*6000)	/* For box to power on */
#define BLIND_SLEEP (int)(timeout_scale*1000)	/* Blind mode delay between 
						   keypresses */

int send_and_get_ack(packet *p, int timeout)
{
	packet s;
	int done=0;
	serial_sendpacket(p);
	while(!done) {
		if (serial_getpacket(&s,timeout)<0) {
			verb("No response to packet\n");
			return -1;
		}
		if (packet_is_ack(p,&s))
			done = 1;
		else 
			verb("Got response, but it's not an ACK; "
			     "waiting for more.\n");
	}
	return 0;
}

int initialize(void)
{
	packet p;
	verb("Attempting to initialize DCT\n");

	serial_flush();

	/* Send initialize_1 and get ack.  Sometimes the box
	   doesn't respond to this one. */
	packet_build_initialize_1(&p);
	if (send_and_get_ack(&p,TIMEOUT_TINY)<0)
		verb("No response to init_1; trying to continue\n");

	/* We should always get a response to initialize_2 */
	packet_build_initialize_2(&p);
	if (send_and_get_ack(&p,TIMEOUT_SHORT)<0) {
		verb("No response to initialize_2\n");
		return -1;
	}

	/* The DCT might now send us a status message, but 
	   not always.  Ignore it if it comes. */
	if (serial_getpacket(&p,TIMEOUT_TINY)<0)
		verb("No initial status message\n");
	       
	return 0;
}

int get_channel(void)
{
	int chan;
	packet p;

	verb("Reading current channel\n");

	/* Send channelquery and get ack */
	packet_build_channelquery(&p);
	if (send_and_get_ack(&p,TIMEOUT_SHORT)<0)
		return -1;
	
	/* Now we expect one more packet with the channel status */
	if (serial_getpacket(&p,TIMEOUT_SHORT)<0) {
		verb("Didn't get channel status message\n");
		return -1;
	}

	if ((chan=packet_extract_channel(&p))<0) {
		verb("Returned packet isn't channel status!\n");
		return -1;
	}

	return chan;
}

int send_keypress(int key)
{
	packet p;

	packet_build_keypress(&p,key);
	if (send_and_get_ack(&p,TIMEOUT_TINY)<0)
		return -1;

	/* After sending a keypress, we have to wait. 
	   0.1 sec is not quite enough; 0.2 sec seems to be */
	usleep(KEY_SLEEP * 1000);
	return 0;
}

int set_channel(int chan)
{
	packet p;
	int cur;

	if ((send_keypress(KEY_0+((chan/100)%10)))<0 ||
	   (send_keypress(KEY_0+((chan/10)%10)))<0 ||
	   (send_keypress(KEY_0+((chan/1)%10)))<0 ||
	   (send_ok && send_keypress(KEY_OK)<0)) {
		verb("Error sending channel keypresses\n");
		return -1;
	}

	/* Now the DCT should send us channel status */
	if (serial_getpacket(&p,TIMEOUT_LONG)<0) {
		verb("Didn't get channel status message\n");
		return -1;
	}
	if ((cur=packet_extract_channel(&p))<0) {
		verb("Returned packet isn't channel status!\n");
		return -1;
	}

	/* The DCT may send more channel status messages, if it
	   figures out something new about the signal.  Wait
	   for these, but don't wait long. */
	while(serial_getpacket(&p,TIMEOUT_TINY)>=0) {
		int newcur;
		if ((newcur=packet_extract_channel(&p))>=0) {
			debug("Got extra status back\n");
			cur = newcur;
		}
	}

	/* Send EXIT to clear the OSD, but don't
	   care if this doesn't work */
	send_keypress(KEY_EXIT);

	return cur;
}

/* Do everything blindly and slowly.
   Should work exactly like the Python script. */
void blind_key(int key)
{
	packet p;
	verb("Sending key %d\n",key);
	serial_flush();
	packet_build_initialize_1(&p);
	serial_sendpacket(&p);
	packet_build_initialize_2(&p);
	serial_sendpacket(&p);
	packet_build_keypress(&p,key);
	serial_sendpacket(&p);
	serial_flush();
}

void blind_channel(int chan)
{
	verb("Sending keys blindly.\n");
	blind_key(KEY_0+((chan/100)%10));
	usleep(BLIND_SLEEP * 1000);
	blind_key(KEY_0+((chan/10)%10));
	usleep(BLIND_SLEEP * 1000);
	blind_key(KEY_0+((chan/1)%10));
	if (send_ok) {
		usleep(BLIND_SLEEP * 1000);
		blind_key(KEY_OK);
	}
	usleep(BLIND_SLEEP * 1000 * 2);
	blind_key(KEY_EXIT);
	usleep(BLIND_SLEEP * 1000);
	serial_flush();	
}

int main(int argc, char *argv[])
{
	int optind;
	char *optarg;
	char c;
	FILE *help = stderr;
	int tries=0;
	int chan=0;
	int cur=0;
	int newcur;
	int quiet=0;
	int force=0;
	int nopower=0;
	int blind=0;
	char *t;
	char *port = "/dev/ttyS0";

	opt_init(&optind);
	while((c=opt_parse(argc,argv,&optind,&optarg,opt))!=0) {
		switch(c) {
		case 'v':
		        verb_count++;
			break;
		case 'f':
			force++;
			break;
		case 'n':
			nopower++;
			break;
		case 'o':
			send_ok++;
			break;
		case 'b':
			blind++;
			break;
		case 'q':
			quiet++;
			break;
		case 't':
			timeout_scale = strtod(optarg, &t);
			if (timeout_scale<0 || *t!=0 ||
			   optarg[0]<'0' || optarg[0]>'9') {
				fprintf(stderr,"Invalid time scale: %s\n",
					optarg);
				goto printhelp;
			}
			verb("Scaling timeouts by %f\n",timeout_scale);
			break;				
		case 'p':
			port = optarg;
			break;
		case 'V':
			printf("dct-channel " VERSION "\n");
			printf("Written by Jim Paris <jim a jtan d com>\n");
			printf("This program comes with no warranty and is "
			       "provided under the GPLv2.\n");
			return 0;
			break;
		case 'h':
			help=stdout;
		default:
		printhelp:
			fprintf(help,"Usage: %s [options] [channel]\n",*argv);
			opt_help(opt,help);
			fprintf(help,"Interfaces with a DCT-2000 series cable "
				     "box over RS232.\n");
			fprintf(help,"Changes channel, if one is supplied, "
				     "and prints it.\n");
			fprintf(help,"With no arguments, prints current "
				     "channel.\n");
			fprintf(help,"See README for more details.\n");
			return (help==stdout)?0:1;
		}
	}

	if ((optind+1)<argc) {
		fprintf(stderr,"Error: too many arguments (%s)\n\n",
			argv[optind+1]);
		goto printhelp;
	}

	if (optind<argc) {
		char *end;
		chan = strtol(argv[optind],&end,10);
		if (*end!='\0' || chan<1 || chan>999) {
			fprintf(stderr,"Invalid channel: %s\n",argv[optind]);
			fprintf(stderr,"valid channels are 1 - 999\n");
			goto printhelp;
		}
	}

	if (blind && !chan) {
		fprintf(stderr,"Can't read channel number in blind mode;\n");
		fprintf(stderr,"turning off blind mode.\n");
		blind=0;
	}

	if ((serial_init(port))<0) 
		err(1,port);

	atexit(serial_close);

	if (blind) {
		blind_channel(chan);
		if (!quiet) printf("%d\n",chan);
		return 0;
	}

	while(tries < 5) {
		tries++;
		if (initialize()<0) {
			verb("Initialization failed\n");
			if (!force) continue;
		}

		if ((newcur=get_channel())<0) {
			verb("Failed to read current channel\n");
			if (!force) continue;
			newcur = 0;
		}
		cur = newcur;

		if (chan!=0 && chan!=cur) {
			verb("Changing from channel %d to channel %d\n",cur,chan);
			/* Switch channel. */
			if ((newcur=set_channel(chan))<0 &&
			   (newcur=get_channel())!=chan) {
				int i;

				verb("Failed to set new channel\n");
				switch(tries) {
				case 1:
				case 2:
					/* Try increasing timeouts by 50%,
					   which will also increase the
					   delay between keypresses */
					timeout_scale *= 1.5;
					verb("Increased timeout_scale to %f\n",
					     timeout_scale);
					break;
				case 3:
					/* Try sending exit key a few
					   times to make sure we're
					   not in a menu or something */
					verb("Attempting to exit menus\n");
					for(i=0;i<4;i++)
						if (send_keypress(KEY_EXIT)<0)
							break;
					break;
				case 4:
					/* After 4 unsuccessful tries,
					   try sending power button,
					   in case the box is turned
					   off.  Need to wait for it
					   to actually power on, and
					   flush buffers since it will
					   probably send garbage. */
					if (nopower) 
						break;
					verb("Attempting to turn box on\n");
					if (send_keypress(KEY_POWER)>=0) {
						usleep(POWER_SLEEP*1000);
					}
 					serial_flush();
					break;
				default:
					break;
				}
				continue;
			}
			cur = newcur;

			if (cur != chan) {
				debug("channel is wrong; didn't switch?\n");
				continue;
			}

			verb("Success!\n");
		}
		if (!quiet) printf("%d\n",cur);
		return 0;
	}

	if (!quiet) {
		fprintf(stderr,"Communication failed after %d tries\n", tries);
		printf("%d\n",cur);
	}
	return 1;
}


Script.png /tmp/dct-channel/crc.c

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include "crc.h"

/* Incrementally calculate CRC for DCT-2000 series cable boxes */
/* (pass 0 for initial CRC) */
uint16_t makecrc(uint16_t crc, const uint8_t *data, unsigned int len)
{
	uint8_t d, j;

	for ( ; len-- ; data++ )
		for ( j=0,d=*data ; j<8 ; j++,d>>=1 )
			crc = (crc >> 1) ^ (((crc ^ d) & 1) ? 0x8408 : 0);

	return crc;
}

uint16_t makecrc_byte(uint16_t crc, uint8_t c)
{
	return makecrc(crc,&c,1);
}


Script.png /tmp/dct-channel/crc.h

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#ifndef CRC_H
#define CRC_H

#include <stdint.h>

uint16_t makecrc(uint16_t crc, const uint8_t *data, unsigned int len);
uint16_t makecrc_byte(uint16_t crc, uint8_t c);

#endif


Script.png /tmp/dct-channel/debug.h

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#ifndef DEBUG_H
#define DEBUG_H

extern int verb_count;

#define debug(x...) ({ \
	if (verb_count >= 2) \
		fprintf(stderr,x); \
})

#define verb(x...) ({ \
	if (verb_count >= 1) \
		fprintf(stderr,x); \
})

#endif


Script.png /tmp/dct-channel/opt.c

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "opt.h"

void opt_init(int *optind) {
	*optind=0;
}

char opt_parse(int argc, char **argv, int *optind, char **optarg,
	       struct options *opt) {
	char c;
	int i;
	(*optind)++;
	if (*optind>=argc)
		return 0;
	
	if (argv[*optind][0]=='-' && 
	   argv[*optind][1]!='-' &&
	   argv[*optind][1]!=0) {
		/* Short option (or a bunch of 'em) */
		/* Save this and shift others over */
		c=argv[*optind][1];
		for(i=2;argv[*optind][i]!=0;i++)
			argv[*optind][i-1]=argv[*optind][i];
		argv[*optind][i-1]=0;
		if (argv[*optind][1]!=0)
			(*optind)--;
		/* Now find it */
		for(i=0;opt[i].shortopt!=0;i++)
			if (opt[i].shortopt==c)
				break;
		if (opt[i].shortopt==0) {
			fprintf(stderr,"Error: unknown option '-%c'\n",c);
			return '?';
		}
		if (opt[i].arg==NULL)
			return c;
		(*optind)++;
		if (*optind>=argc || (argv[*optind][0]=='-' &&
			argv[*optind][1]!=0)) {
			fprintf(stderr,"Error: option '-%c' requires an "
				"argument\n",c);
			return '?';
		} 
		(*optarg)=argv[*optind];
		return c;
	} else if (argv[*optind][0]=='-' &&
		  argv[*optind][1]=='-' &&
		  argv[*optind][2]!=0) {
		/* Long option */
		for(i=0;(c=opt[i].shortopt)!=0;i++)
			if (strcmp(opt[i].longopt,argv[*optind]+2)==0)
				break;
		if (opt[i].shortopt==0) {
			fprintf(stderr,"Error: unknown option '%s'\n",
				argv[*optind]);
			return '?';
		}
		if (opt[i].arg==NULL)
			return c;
		(*optind)++;
		if (*optind>=argc || (argv[*optind][0]=='-' &&
			argv[*optind][1]!=0)) {
			fprintf(stderr,"Error: option '%s' requires an "
				"argument\n",argv[*optind-1]);
			return '?';
		} 
		(*optarg)=argv[*optind];
		return c;
	} else {
		/* End of options */
		return 0;
	}
}

void opt_help(struct options *opt, FILE *out) {
	int i;
	int printed;

	for(i=0;opt[i].shortopt!=0;i++) {
		fprintf(out,"  -%c, --%s%n",opt[i].shortopt,
			opt[i].longopt,&printed);
		fprintf(out," %-*s%s\n",30-printed,
			opt[i].arg?opt[i].arg:"",opt[i].help);
	}
}


Script.png /tmp/dct-channel/opt.h

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#ifndef OPT_H
#define OPT_H

#include <stdlib.h>

struct options { 
	char shortopt;
	char *longopt;
	char *arg;
	char *help;
};

void opt_init(int *optind);

char opt_parse(int argc, char **argv, int *optind, char **optarg,
	       struct options *opt);

void opt_help(struct options *opt, FILE *out);

#endif


Script.png /tmp/dct-channel/packet.c

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

/* Process data from the DCT2000 and fill data structures accordingly.
 * 
 */ 

#include <stdio.h>
#include <stdlib.h>
#include <err.h>
#include <string.h>
#include <stdint.h>

#include "crc.h"
#include "serial.h"
#include "packet.h"
#include "debug.h"

int seq_ack, seq_req;
/* CRC includes type, len msb, len lsb, seq, id, payload */

/* Global state; only deals with one stream at a time */
packet stream_p;
int stream_byte;
int stream_escaped;
uint16_t stream_crc;
enum {
	wait_type, wait_len_msb, wait_len_lsb, wait_seq,
	wait_id, wait_payload, wait_crc_msb, wait_crc_lsb,
	wait_end
} stream_state;

void (*packet_received_handler)(packet *p) = NULL;

void packet_stream_init(void)
{
	stream_byte = 0;
	stream_escaped = 0;
	stream_state = wait_type;
}

packet *packet_stream_add(uint8_t c)
{
	if (c == 0x10 && !stream_escaped) {
		stream_escaped = 1;
		return NULL;
	}

	switch(stream_state)
	{
	case wait_type:
		stream_p.type = c;
		if (!stream_escaped) 
			debug("Start not escaped; waiting more\n");
		else
			stream_state = wait_len_msb;
		break;
	case wait_len_msb:
		stream_p.len = WORD(c,0);
		stream_state = wait_len_lsb;
		break;
	case wait_len_lsb:
		stream_p.len |= WORD(0,c);
		stream_state = wait_seq;
		break;
	case wait_seq:
		stream_p.seq = c;
		stream_state = wait_id;
		break;
	case wait_id:
		stream_p.id = c;
		stream_state = wait_payload;
		break;
	case wait_payload:
		if (stream_byte < (stream_p.len-2)) {
			if (stream_byte < MAX_PAYLOAD)
				stream_p.payload[stream_byte] = c;
			stream_byte++;
			break;
		}
		stream_state = wait_crc_msb;
		/* Fall through */
	case wait_crc_msb:
		stream_crc = WORD(c,0);
		stream_state = wait_crc_lsb;
		break;
	case wait_crc_lsb:
		stream_crc |= WORD(0,c);
		packet_fill_crc(&stream_p);
		if (stream_p.crc != stream_crc)
			debug("Bad CRC: expected 0x%04x, got 0x%04x\n",
			      stream_p.crc, stream_crc);
		stream_state = wait_end;
		break;
	case wait_end:
		if (c!=0x03 || !stream_escaped) 
			debug("Bad end: expected escaped 0x03, got "
			      "%sescaped 0x%02x\n",stream_escaped?"":"un",c);
		return &stream_p;
	}
	stream_escaped = 0;
	return NULL;
}

void packet_fill_crc(packet *p)
{
	uint16_t crc = 0;

	crc = makecrc_byte(crc, p->type);
	crc = makecrc_byte(crc, MSB(p->len));
	crc = makecrc_byte(crc, LSB(p->len));
	crc = makecrc_byte(crc, p->seq);
	crc = makecrc_byte(crc, p->id);
	crc = makecrc(crc, p->payload, p->len-2);

	p->crc = crc;
}

void packet_build_channelquery(packet *p)
{
	p->type = 0x78;
	p->len = 3;
	packet_fill_seq(p);
	p->id = id_pc;
	p->payload[0] = cmd_statusreq;
	packet_fill_crc(p);
}

void packet_build_initialize_1(packet *p)
{
	p->type = 0x70;
	p->len = 2;
	p->seq = 0x03;		/* Fixed seq. for init */
	p->id = id_pc;
	packet_fill_crc(p);
}

void packet_build_initialize_2(packet *p)
{
	p->type = 0x78;
	p->len = 3;
	p->seq = 0x03;		/* Fixed seq. for init */
	p->id = id_pc;
	p->payload[0] = cmd_init;
	packet_fill_crc(p);
}

void packet_build_ack(packet *in, packet *out)
{
	if (in->seq & 0x80)
		warnx("building a response to an ACK!\n");
	out->type = in->type;
	out->len = 2;
	packet_fill_seq_ack(out);
	out->id = id_pc;
	packet_fill_crc(out);
}

void packet_build_keypress(packet *p, uint8_t key)
{
	p->type = 0x78;
	p->len = 4;
	packet_fill_seq(p);
	p->id = id_pc;
	p->payload[0] = cmd_key;
	p->payload[1] = key;
	packet_fill_crc(p);
}

void packet_fill_seq_ack(packet *p)
{
	seq_ack = (seq_ack + 1) & 0x3;
	p->seq = 0x80 | (seq_req << 4) | seq_ack;
}

void packet_fill_seq(packet *p)
{
	seq_req = (seq_req + 1) & 0x3;
	p->seq = (seq_req << 4) | seq_ack;
}

void packet_received(packet *p)
{
	debug("RECV: type 0x%02x, len %d, id 0x%02x, seq 0x%02x\n",
	     p->type, p->len, p->id, p->seq);
	if (p->len>2) {
		int i;
		debug("      data");
		for(i=0;i<p->len-2;i++) 
			debug(" 0x%02x",p->payload[i]);
		debug("\n");
	}

	if (packet_received_handler != NULL) {
		(*packet_received_handler)(p);
		return;
	}

	seq_ack = p->seq & 0x3;
	seq_req = (p->seq >> 4) & 0x3;
	if (!(p->seq & 0x80)) {
		packet r;
		debug("Sending automatic ACK\n");
		packet_build_ack(p,&r);
		serial_sendpacket(&r);
	}
}

void packet_sent(packet *p)
{
	debug("SENT: type 0x%02x, len %d, id 0x%02x, seq 0x%02x\n",
	     p->type, p->len, p->id, p->seq);
	if (p->len>2) {
		int i;
		debug("      data");
		for(i=0;i<p->len-2;i++) 
			debug(" 0x%02x",p->payload[i]);
		debug("\n");
	}		

	seq_ack = p->seq & 0x3;
	seq_req = (p->seq >> 4) & 0x3;
}

int packet_is_ack(packet *p, packet *ack)
{
	if (ack->len != 0x02) 
		return 0;

	if (ack->type != p->type)
		return 0;

	if (ack->seq != (((p->seq + 0x01) & 0x33) | 0x80))
		debug("Wrong sequence number in ACK?\n");

	return 1;
}

int packet_extract_channel(packet *p)
{
	uint16_t chan;
	if (p->type!=0x78 || p->len<0x04) {
		debug("channel status: wrong type or too short\n");
		return -1;
	}
	if (p->payload[0]!=cmd_status1 &&
	   p->payload[0]!=cmd_status2) {
		debug("channel status: wrong payload 0x%02x\n",p->payload[0]);
		return -1;
	}
	chan = WORD(p->payload[1],p->payload[2]);
	return chan;
}


Script.png /tmp/dct-channel/packet.h

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

/* 
 * Process data from the DCT2000 and fill data structures accordingly.
 */ 

#ifndef PACKET_H
#define PACKET_H

#include <stdint.h>

#define MSB(x) ((uint8_t)(((x)>>8)&0xff))
#define LSB(x) ((uint8_t)((x)&0xff))
#define WORD(msb,lsb) ((uint16_t)((((uint8_t)(msb))<<8) | ((uint8_t)(lsb))))

typedef enum {
	id_pc = 0x04,
	id_dct = 0x40
} enum_id;

typedef enum {
	cmd_statusreq = 0x21,
	cmd_status1 = 0x20,
	cmd_status2 = 0xa1,
	cmd_key = 0x22,
	cmd_init = 0x00
} enum_cmd;

/* Payload is fixed length buffer for simplicity; extra data is
   ignored on receipt. */
#define MAX_PAYLOAD 128
typedef struct {
	uint8_t type;
	uint16_t len;
	uint8_t seq;
	uint8_t id;
	uint8_t payload[128];
	uint16_t crc;
} packet;

void packet_stream_init(void);
packet *packet_stream_add(uint8_t c);

void packet_fill_crc(packet *p);
void packet_build_channelquery(packet *p);
void packet_build_initialize_1(packet *p);
void packet_build_initialize_2(packet *p);
void packet_build_ack(packet *in, packet *out);
void packet_build_keypress(packet *p, uint8_t key);
void packet_fill_seq_ack(packet *p);
void packet_fill_seq(packet *p);
void packet_received(packet *p);
void packet_sent(packet *p);

int packet_is_ack(packet *p, packet *ack);
int packet_extract_channel(packet *p);

#endif


Script.png /tmp/dct-channel/remote.h

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#ifndef REMOTE_H
#define REMOTE_H
 	
#define KEY_0		0x00
#define KEY_1		0x01
#define KEY_2		0x02
#define KEY_3		0x03
#define KEY_4		0x04
#define KEY_5		0x05
#define KEY_6		0x06
#define KEY_7		0x07
#define KEY_8		0x08
#define KEY_9		0x09
#define KEY_POWER	0x0A
#define KEY_CHUP	0x0B
#define KEY_CHDOWN	0x0C
#define KEY_VOLUP	0x0D
#define KEY_VOLDOWN	0x0E
#define KEY_MUTE	0x0F
#define KEY_MUSIC	0x10
#define KEY_OK		0x11
#define KEY_EXIT	0x12
#define KEY_LAST	0x13
#define KEY_SWITCH_AB	0x14
#define KEY_FAVORITE	0x15
#define KEY_PPV		0x16
#define KEY_A		0x17
#define KEY_P_F		0x18
#define KEY_MENU	0x19
#define KEY_OUTPUT_CHAN	0x1C
#define KEY_FF		0x1D
#define KEY_REVERSE	0x1E
#define KEY_PAUSE	0x1F
#define KEY_BROWSE	0x22
#define KEY_CANCEL	0x23
#define KEY_LOCK	0x24
#define KEY_LIST	0x25
#define KEY_THEME	0x26
#define KEY_B		0x27
#define KEY_C		0x28
#define KEY_GUIDE	0x30
#define KEY_RECORD	0x31
#define KEY_HELP	0x32
#define KEY_INFO	0x33
#define KEY_UP		0x34
#define KEY_DOWN	0x35
#define KEY_LEFT	0x36
#define KEY_RIGHT	0x37
#define KEY_DAYUP	0x38
#define KEY_DAYDOWN	0x39
#define KEY_PAGEUP	0x3A
#define KEY_PAGEDOWN	0x3B

#endif


Script.png /tmp/dct-channel/serial.c

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#include "serial.h"
#include "crc.h"
#include "debug.h"
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <termios.h>
#include <string.h>
#include <strings.h>
#include <sys/file.h>
#include <errno.h>
#include <sys/poll.h>

int serial_fd;
#define LOCKTRIES 60

/* Open and initialize serial port.
   Returns 0 on success, or -1 if error */
int serial_init(char *port)
{
	struct termios t;
	int locktried = 0;

	if ((serial_fd=open(port,O_RDWR,O_NONBLOCK))<0)
		return -1;
 trylock:
	if (flock(serial_fd,LOCK_EX|LOCK_NB)<0) {
		if ((errno==EAGAIN) && locktried<LOCKTRIES) {
			verb("%s is locked; waiting...\n",port);
			locktried++;
			sleep(1);
			goto trylock;
		}
		verb("Failed to get lock: %s\n",strerror(errno));
		return -1;
	}
	if (locktried>0) {
		verb("Just got lock, so giving box time to settle\n");
		sleep(2);
	}	
	if (tcgetattr(serial_fd,&t)<0)
		return -1;
	t.c_cflag |= CLOCAL;
	if (tcsetattr(serial_fd,TCSANOW,&t)<0)
		return -1;
	
	bzero(&t,sizeof(t));
	t.c_iflag=IGNBRK|IGNPAR;
	t.c_cflag=CS8|CREAD|CLOCAL;
	t.c_cc[VMIN]=1;
	if (cfsetispeed(&t,B9600)==-1)
		return -1;
	if (tcsetattr(serial_fd,TCSANOW,&t)==-1)
		return -1;

	return 0;
}

void serial_flush(void) {
  char junk[1024];
  fcntl(serial_fd, F_SETFL, fcntl(serial_fd, F_GETFL, 0) | O_NONBLOCK);
  while(read(serial_fd,junk,1024)>0)
    continue;
  fcntl(serial_fd, F_SETFL, fcntl(serial_fd, F_GETFL, 0) & ~O_NONBLOCK);
}

/* Close a serial port */
void serial_close(void)
{
	flock(serial_fd,LOCK_UN);
	close(serial_fd);
}

/* returns -1 on timeout/error, otherwise 0 */
int serial_getnextbyte(char *ch, int timeout_msec)
{
	struct pollfd pfd;

	pfd.fd = serial_fd;
	pfd.events = POLLIN|POLLERR;

	/* Wait 2 seconds for the byte */
	if (poll(&pfd, 1, timeout_msec)!=1)
		return -1;
	if (pfd.revents & POLLERR)
		return -1;
	
	if (read(serial_fd, ch, 1)!=1)
		return -1;

	return 0;
}

/* Read a packet from the serial port and return it.
   Return 0 on success, or -1 on error/timeout */
int serial_getpacket(packet *p, int timeout_msec)
{
	char ch;
	packet *new;

	packet_stream_init();

 more:
	if (serial_getnextbyte(&ch,timeout_msec)<0) {
		debug("Serial timeout\n");
		return -1;
	}
	if ((new=packet_stream_add(ch))==NULL)
		goto more;

	memcpy(p,new,sizeof(packet));
	
	packet_received(p);

	return 0;
}

void serial_write(unsigned char c)
{
	write(serial_fd,&c,1);
}

void serial_escape_write(unsigned char c)
{
	if (c==0x10) serial_write(c);
	serial_write(c);
}
	
/* Send a packet. */
void serial_sendpacket(packet *p)
{
	int i;

	serial_write(0x10);
	serial_write(p->type);
	serial_escape_write(MSB(p->len));
	serial_escape_write(LSB(p->len));
	serial_escape_write(p->seq);
	serial_escape_write(p->id);
	for(i=0;i<p->len-2;i++)
		serial_escape_write(p->payload[i]);
	serial_escape_write(MSB(p->crc));
	serial_escape_write(LSB(p->crc));
	serial_write(0x10);
	serial_write(0x03);

	packet_sent(p);
}


Script.png /tmp/dct-channel/serial.h

/*
 * dct-channel
 * Copyright (c) 2003 Jim Paris <jim a jtan d com>
 *
 * This is free software; you can redistribute it and/or modify it and
 * it is provided under the terms of version 2 of the GNU General Public
 * License as published by the Free Software Foundation; see COPYING.
 */

#ifndef SERIAL_H
#define SERIAL_H

#include "packet.h"

/* Only one port open at a time. */
int serial_init(char *device);
void serial_close();
void serial_flush();

int serial_getpacket(packet *p, int timeout_msec);
void serial_sendpacket(packet *p);

#endif


Script.png /tmp/dct-channel/version.h

/* This file was automatically generated. */
#define VERSION "1.7 (2004-01-14)"