Copyright © 1996, 1997 Lucent Technologies Inc. All rights reserved.

12.2 Infrared remote control

This example shows two instances of a module for interfacing to a TV remote control; one is for the real remote, which in this case is connected to a serial port on a set-top box, and the other is simulated for testing programs running on a regular operating system. The techniques of special interest are the dynamic use of modules and the communication using a channel.

The module is used by creating a channel and passing it to the module's init function, which returns a success/error indicator and starts an asynchronous process to read the remote control. The user of the module executes a receive on the channel whenever it wishes to accept a button-push.

The (abridged) module declaration is

	Ir: module
	{
		# Codes buttons on IR remote control
		Zero:	con 0;
		One:	con 1;
		. . .
		Mute:	con 23;
		Error:	con 9999;

		init: fn(chan of int): int;
		PATH: con "/dis/ir.dis";
		SIMPATH: con "/dis/irsim.h";
	};
The implementation for the `real' remote control is
	implement Ir;

	include "ir.m";
	include "sys.m";
	FD, Dir: import Sys;

	sys: Sys;

	init(keys: chan of int): int
	{
		cfd, dfd: ref FD;

		sys = load Sys Sys->PATH;

		cfd = sys->open("/dev/eia1ctl", sys->OWRITE);
		if(cfd == nil)
			return -1;
		sys->fprint(cfd, "b9600");

		dfd = sys->open("/dev/eia1", sys->OREAD);
		cfd = nil;

		spawn reader(keys, dfd);
		return 0;
	}
The init routine accepts a chan argument; it will be used by the module to send codes for the buttons pressed by the user. In this routine, the calls to sys->open and sys->fprint open and set up the device data and control files /dev/eia1 and /dev/eia1ctl used to communicate with the device itself. The important step is at the end: the spawn statement creates a new, asynchronous task to read the device, using a routine that is passed the communications channel and the FD for the device:
	reader(keys: chan of int, dfd: ref FD)
	{
		n, ta, tb: int;
		dir: Dir;
		b1:= array[1] of byte;
		b2:= array[1] of byte;

		# find the number of bytes already
		# queued and flush that many
		(n, dir) = sys->fstat(dfd);
		if(n >= 0 && dir.length > 0) {
			while(dir.length) {
				n = sys->read(dfd,
				   array[dir.length] of byte,
				   dir.length);
				if(n < 0)
					break;
				dir.length -= n;
			}
		}
	loop:	for(;;) {
			n = sys->read(dfd, b1, len b1);
			if(n <= 0)
				break;
			ta = sys->millisec();
			# Button pushes are pairs of characters
			# that arrive closer together than
			# 200 ms.  Longer than that is likely
			# to be noise.
			for(;;) {
				n = sys->read(dfd, b2, 1);
				if(n <= 0)
					break loop;
				tb = sys->millisec();
				if(tb - ta <= 200)
					break;
				ta = tb;
				b1[0] = b2[0];
			}
			# map the character pair; the significant
			# bits are the lowest 5.
			case ((int b1[0]&16r1f)<<5) | (int b2[0]&16r1f) {
			975 =>	n = Ir->Zero;
			479 =>	n = Ir->One;
			. . .
			791 =>	n = Ir->Mute;
			* =>	n = Ir->Error;
			}
			# found a button-push; send the value
			keys <-= n;
		}
		keys <-= Ir->Error;
	}
The code in the middle is related to noise-filtering and is uninteresting in detail except as it illustrates some of the methods provided by the Sys module; the crucial actions are found at the bottom, where the routine sends either a true button-push or an error code over the channel to the module's client.

Here is another implementation of the same interface. Its init function performs the same kind of initialization as the other version, but using the operating system's keyboard files /dev/cons and /dev/consctl. In the Inferno environment, operations corresponding to the Unix `stty' primitive are accomplished by writing messages to a control file associated with the file that handles the data.

	implement Ir;

	include "ir.m";
	include "sys.m";
	FD: import Sys;

	sys: Sys;
	cctlfd: ref FD;

	init(keys: chan of int): int
	{
		dfd: ref FD;

		sys = load Sys Sys->PATH;

		cctlfd = sys->open("/dev/consctl", sys->OWRITE);
		if(cctlfd == nil)
			return -1;
		sys->write(cctlfd, array of byte "rawon", 5);

		dfd = sys->open("/dev/cons", sys->OREAD);
		if(dfd == nil)
			return -1;

		spawn reader(keys, dfd);
		return 0;
	}
A fine point: the variable cctlfd that contains the FD for the control device is declared external to the init function, even though it appears to be used only inside it. Programming cleanliness suggests that its declaration be moved inside, but here that won't work; device control files in Inferno retain settings like `raw mode' only while they remain open. If cctlfd were declared inside init, then returning from init would destroy the last reference to the FD for the control file, and the device would be closed automatically.

The reader function for this module has the same structure as the first example, but doesn't have to worry about a noisy infrared detector:

	reader(keys: chan of int, dfd: ref FD)
	{
		n: int;
		b:= array[1] of byte;

		for(;;) {
			n = sys->read(dfd, b, 1);
			if(n != 1)
				break;
			case int b[0] {
			'0' =>	n = Ir->Zero;
			'1' =>	n = Ir->One;
			. . .
			16r7f =>	n = Ir->Mute;
			* =>	n = Ir->Error;
			}
			keys <-= n;
		}
		keys <-= Ir->Error;
	}
The following module can be used to test the above code. It simply prints the name of the button that was pressed.
	implement Irtest;

	include "sys.m";
	include "draw.m";
	FD: import Sys;
	include "ir.m";

	Irtest: module
	{
		init:	fn(nil: ref Draw->Context, nil: list of string);
	};
	ir: Ir;
	sys: Sys;
	init(nil: ref Draw->Context, nil: list of string)
	{
		c: int;
		stderr: ref FD;
		irchan := chan of int;

		sys = load Sys Sys->PATH;
		stderr = sys->fildes(2);

		# If the real IR remote application can
		# be found, use it, otherwise use the simulator:
		ir = load Ir Ir->PATH;
		if(ir == nil)
			ir = load Ir Ir->SIMPATH;
		if(ir == nil) {
			# %r format code means the last system error string
			sys->fprint(stderr, "load ir: %r\n");
			return;
		}
		if(ir->init(irchan) != 0) {
			sys->fprint(stderr, "Ir.init: %r\n");
			return;
		}
		names := array[] of {
			"Zero",
			"One",
			. . .
			"Mute",
		};
		for(;;) {
			c = <-irchan;
			if(c == ir->Error)
				sys->print("Error %d\n", c);
			else
				sys->print("%s\n", names[c]);
		}	
	}
Finally, here is a snippet from a movie application that uses the IR module; it demonstrates how alt is useful for dealing with multiple events. This is only one of the functions of the movie module, so not everything is defined. It uses the Mpeg module, which actually copies the MPEG data stream to the screen asynchronously. Its play function takes, as one of its arguments, a channel; before starting to play it writes a string on the channel. An empty string indicates success at locating the movie; a non-empty string contains an error message. When it finishes, it writes another string.
	movie(entry: ref Dbinfo, cc: chan of int)
	{
		i: int;
		m: Mpeg;
		b: ref Image;

		m = load Mpeg Mpeg->PATH;
		if (m == nil)
			return;
		# make a place on the screen
		w := screen.window(screen.image.r);

		mr := chan of string;
		s := m->play(w, 1, w.r, entry.movie, mr);
		if(s != "")
			return;
		# wait for the end of the movie
		# while watching for button pushes
		for(;;) {
			alt {
			<-mr =>
				return;
			i = <-cc =>
				case i {
				Ir->Select =>
					m->ctl("stop");
				Ir->Up or Ir->Dn =>
					m->ctl("pause");
				}
			}	
		}
	}

05/Jun/97