
Coupling the Logitech Space Mouse
and the MondoTronics Tractor

by Luke Hoffman1 and Luis Lopez2

1- COLSA Inc., Huntsville, AL
2 - U.S. Army Space and Strategic Defense Command, Huntsville, AL


Introduction

The ability to interact with computer generated environments is essential to virtual 
reality applications.  Not only should the user be able to affect the environment, the 
environment should be able to affect the user.  We are going to present here some of 
our experiments with coupling the Logitech Space Mouse and the MondoTronics 
Tractor feedback device.

The Logitech Space Mouse is an acoustic tracking device.  It uses three acoustic 
transducers mounted on a triangle attached to the mouse and three acoustic receivers 
on a separate triangle.  Both the mouse and the receiving triangle are plugged into a 
control box which is linked to the computer using an RS-232 interface.  The mouse 
has the normal three buttons plus an additional button mounted on the side.  
Logitech's intent in adding  the side button was to allow the user the option of 
moving the mouse around without changing the mouse's position reflected in the 
virtual world.

The MondoTronics Tractor is a tactile feedback device that uses a titanium and nickle 
alloy wire or "muscle" wire to create a tactile feedback sensation.  The muscle wire 
has a unique property; when it is stretched, a current running through the wire will 
cause the wire to try to return to its original length.  The force generated by this 
process can be used in a number of ways, for example creating tactile feedback.   
The Tractor is connected to a PC through an RS232 port and an interface box 
provided by MondoTronics.


Interfacing the Logitech Space Mouse

The Space mouse is capable of working in two different tracking modes.  It has one 
mode for head tracking and another for normal mouse tracking.  Mouse mode is the 
default mode.  It tracks the mouse inside a 2 foot cube with an accuracy of 200 dots 
per inch.  In this mode the mouse can respond with a maximum of 50 position reports 
per second.  The head tracking mode is designed to track inside a seven foot cube 
with a reduced resolution of 50 dots per inch.  In this mode the mouse can respond a 
maximum of 25 reports per second.  The rotational resolution is 0.1 degrees in mouse 
mode and 0.5 degrees in head tracking mode.

The mouse has three different reporting modes: stream, incremental, and demand.  In 
demand reporting the computer asks the mouse for a report each time the application 
needs to know the mouse's position.  In stream reporting the mouse sends reports 
back continuously.  Incremental reporting is similar to stream reporting, except the 
mouse sends reports back only while it is moving.



Mouse Commands

                   *R  Software reset
                   *D  Enter demand reporting mode
                   *d  Demand a report
                   *I  Enter incremental tracking mode
                   *S  Enter stream reporting mode
                   *^E Perform diagnostic tests
                   *G  Enter Euler mode
                   *Q  Enter quaternion mode
                   *H  Enter head tracking mode
                   *h  Enter mouse mode

Table 1 - Mouse Commands


The mouse has two formats for returning data to the computer, Euler and Quaternion.  
In Euler mode the mouse returns the status of the buttons, the x, y, and z location, 
and the roll, pitch, and yaw of the mouse in a 16 byte record.  In Quaternion mode 
the mouse returns the status of the buttons, x, y, and z location, quaternion twist, and 
quaternion x, y, and z.  In this article we will be using the Euler mode for our data.





Table 2 - Euler Mode Data Format

                Byte     Data 

                Byte 1   Status Data
                Byte 2   X     Bits 20 - 14
                Byte 3   X     Bits 13 - 7
                Byte 4   X     Bits  6 - 0
                Byte 5   Y     Bits 20 - 14
                Byte 6   Y     Bits 13 - 7
                Byte 7   Y     Bits  6 - 0
                Byte 8   Z     Bits 20 - 14
                Byte 9   Z     Bits 13 - 7
                Byte 10  Z     Bits  6 - 0
                Byte 11  Pitch Bits 13 - 7
                Byte 12  Pitch Bits  6 - 0
                Byte 13  Yaw   Bits 13 - 7
                Byte 14  Yaw   Bits  6 - 0
                Byte 15  Roll  Bits 13 - 7
                Byte 16  Roll  Bits  6 - 0

Table 2 - Euler Mode Data Format

 
The first byte of data returned in either mode is a set of status bits.  This byte has bits 
set when the side, left, middle, and right buttons are pressed.  There are two other 
status bits relating to the mouse's position.  The first is the fringe area bit.  This bit is 
set when the mouse is almost out of range.  The other bit is set when the mouse is out 
of range.  The first bit in the status byte is always one. This can be useful for finding 
the first byte of a record; since in all the other data bytes returned by the mouse, the 
first bit is 0.



Table 3 - Status Data Byte

               Bit    Meaning
                 7      Always Set
                 6      Set when mouse is in Fringe Area
                 5      Set when mouse is out of range
                 4      Not used
                 3      Set when side button pressed
                 2      Set when left button pressed
                 1      Set when middle button pressed
                 0      Set when right button pressed


The x, y, and z location data is returned in three byte groups.  Each of these bytes 
contains seven bits of data to form a 21 bit signed integer value.  Each bit of the 
location data represents approximately 1/1000 of an inch in the real world.  Of course 
the scales in virtual world will vary.

The roll, pitch, and yaw data is returned in two byte groups.  Again, each of these 
bytes contains seven bits of data to form a 14 bit signed integer value.  Each bit of the 
location data represents 1/40 of a degree.  Our Space Mouse has a peculiarity; as any 
of the angles rotate through zero there is a data jump.  We have found that we have 
to test for a negative angle and add an offset to the data.

Interfacing the Mondotronics Tractor

The MondoTronics Tractor is almost trivial to interface to the PC.  All that is required 
to make the tractor provide feedback to the user is writing a byte out the PC's serial 
port.  It just doesn't get much easier than that.  The Tractor can respond to outputs as 
fast as twice a second.

Coupling the two devices

We have coupled our Space Mouse and Tractor together by using velcro tape to 
attach the tractors control box to the bottom of the mouse and the tractor to the left 
mouse button.  This allows us to provide the user a feedback when the mouse is in 
certain areas or is able to pick up objects in the virtual world.

DOS Driver

We have included a DOS program to test the Space Mouse and the tractor.  The 
program consists of three files test.c, mouse6d.c, and ibmcom.c.  Test.c is the main 
program to test the position of the mouse and pulse the tractor when the mouse goes 
into a fringe area.  Mouse6d.c is a set of routines to use the mouse and the tractor.  
Ibmcom.c is the same ibmcom.c seen in earlier issues of PCVR.

Windows Driver

The Windows 3.1 version will require a message-based approach -- as with any good 
windows program.  We have included a C++ class, called Logitech, that provides 
multiple Logitech 6DOF mouse support under Windows 3.1.  Some interesting 
problems arise in this environment and I don't claim that the code here is the optimal 
solution -- I do happen to know its a good solution.  The main messages to be aware 
of are the WM_COMMNOTIFY and your own user defined message that you'll 
give the Logitech object when requesting data.  With the Logitech object you really 
don't have much to do with the WM_COMMNOTIFY message.  The object must 
process this message internally using its own special ProcessCommNotify() function.  
When you need mouse data the you use the object's Request6DOF() function  to tell the 
driver you'd like mouse data. The driver will let you know when its ready by sending 
out a message that you specified in the Request6DOF() arguments.  To use this class with 
multiple mice (mouses?) you'll create as many objects as you have ports with Logitech devices and 
separate the WM_COMMNOTIFY messages via the wParam argument.  This should be crystal clear 
to Windows programmers and obvious in the source code comments and object calls.    

Conclusions

The Logitech Space Mouse and MondoTronics Tractor make an excellent low cost 
alternative to a data glove.  The combination provides the capability of 6 degree of 
freedom input and tactile feedback.  The use of C++ in the Windows version is a 
natural, easy way to provide mulitple mouse support and device encapsulation.





DOS Driver Code


TEST.C :

/*****************************************************************/
/*                                                               */
/*  DOS Test Routine for Logitec Space Mouse and MondoTronics    */
/*  Tractor combination                                          */
/*                                                               */
/*****************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include "mouse6d.h"

main(argc,argv)
int argc;
char *argv[];
{
 int stat;            /* Status return                        */
 int x,y,z;           /* Mouse location                       */
 int roll,pitch,yaw;  /* Mouse orientation                    */
 int buttons;         /* Button and status data               */
 int port;            /* serial port 1=com1: 2=com2:          */
 int nmiss=0;         /* Number of bad mouse records returned */
 unsigned long diags; /* Diag return status                   */
 char bstat[10];      /* Status data output                   */
 int i;

 /* Make sure the user entered the right number of arguments */
 
 if(argc < 3) {
   printf("useage:%s mouse_port_numeber tractor_port_number\n",argv[0]);
   exit(1);
 }
 
 /* Initialize the mouse */
 port=atoi(argv[1]);
 init_mouse6d(port);
 diags=mouse_diags();
 if(diags != ALL_DIAGS_OK) {
   printf("Trouble with the mouse!\n");
   printf("Diagnostics have failed. %lx\n",diags);
   exit(1);
 }

 /* Initialize the tractor */
 port=atoi(argv[2]);
 init_tractor(port);

 /* clear the screen and put up some default information */
 clrscr();
 gotoxy(4,16);
 printf("Number of bad records %5d",nmiss);
 gotoxy(4,18);
 printf("Any key exits program");
 
 /* This is the main loop */
 while(!kbhit()) {
   stat=get_mouse6d(&x,&y,&z,&pitch,&roll,&yaw,&buttons);
   /* if we got a good data record */
   if(stat == 0) {
     for(i=0;i<9;i++) bstat[i]=' '; /* clear the status stuff */
     if(buttons & OUT_OF_RANGE) {
       /* set the status to out of range */
       bstat[0]='O';bstat[1]='U';bstat[2]='T';
     } else {
       /* set the status flags */
       if(buttons & FRINGE_AREA) {
         bstat[0]='F';
         pulse_tractor();  /* pulse the tractor while in fringe area */
       }
       if(buttons & SIDE_BUTTON) bstat[2]='S';
       if(buttons & LEFT_BUTTON) bstat[4]='L';
       if(buttons & MIDDLE_BUTTON) bstat[6]='M';
       if(buttons & RIGHT_BUTTON) bstat[8]='R';
     }
     /* Display the mouse data */
     bstat[9]=0;
     gotoxy(4,10);
     printf("Buttons %4x  %s",buttons,bstat);
     gotoxy(4,12);
     printf("x %6d y %6d z %6d",x,y,z);
     gotoxy(4,14);
     printf("roll %6d pitch %6d yaw %6d",roll,pitch,yaw);
   } else {
     /* report a bad data record here */
     nmiss++;
     gotoxy(4,16);
     printf("Number of bad records %5d",nmiss);
   }
 }
 close_mouse6d();
 clrscr();
 return(0);
}




MOUSE6D.C

#include <stdio.h>
#include <fcntl.h>
#include <bios.h>
#include <time.h>
#include "ibmcom.h"

/* Space Mouse Parameters */
#define POSITION_MASK 0xfff00000
#define ROTATION_MASK 0xffffc000
#define ROTATION_OFFSET 1992

/* These are commands for the bioscom function  */

#define SETUP_PORT 0
#define XMT_BYTE   1
#define D8_BITS    0x03
#define NO_PARITY  0x00
#define BAUD110    0x00

int tractorID;            /* serial port for the tractor */

unsigned char mbuff[20];  /* internal data buffer for Space Mouse */

/* Mouse commands */

unsigned char m_init[]=  {'*','R' ,0};
unsigned char m_diag[]=  {'*',0x05,0};
unsigned char m_euler[]= {'*','G' ,0};
unsigned char m_report[]={'*','d' ,0};
unsigned char m_quat[]=  {'*','Q' ,0};

/*******************************************************************/
/* Routine to initialize the serial port                           */
/*   returns 0 if succesfull                                       */
/*   returns -1 if failure                                         */
/*******************************************************************/
int init_mouse6d(port)
int port;
{
 int ret;

 ret=com_install(port);  
 if(ret) {
   printf("init_mouse6d: could not open mouse serial port\n");
   return(-1);
  }
  
 /* set up the serial port */
 com_raise_dtr();
 com_set_speed(19200);
 com_set_parity(COM_NONE,1);
 return(0);
}
/************************************************************/
/*                                                          */
/*  Routine to return mouse diagnostics                     */
/*  returns two diagnostic status bytes in one lone word or */
/*  -1 if it can't read anything from the mouse.            */
/*                                                          */
/************************************************************/
long mouse_diags()
{
 unsigned char d1,d2;
 unsigned long diag;

 /* Send the diagnostic command */
 com_tx(m_diag[0]);
 com_tx(m_diag[1]);
 /* wait for the mouse to perform diags */
 delay(200);
 /* Make sure we've got some data */
 if(com_rx_empty()) return(-1);
 /* read the data bytes */
 d1=com_rx();
 d2=com_rx();
 /* put the two status bytes into one long word */
 diag=(0x0000ffff & (d1<<8 | d2));
 return (diag);
}

/********************************************************/
/*                                                      */
/*  This routine reads Logtech mouse data into an       */
/*  an internal buffer                                  */
/*  returns 0 if succesfull or -1 if failure            */
/*                                                      */
/********************************************************/
int read_mouse6d()
{
 int i,j;
 unsigned char dum;

 /* make sure there's no extra data in the receive buffer */ 
 while(!com_rx_empty()) dum=com_rx();

 /* demand a report from the mouse */
 com_tx(m_report[0]);
 com_tx(m_report[1]);
 /* normaly we would go off and do some work here */
 delay(25);
 for(i=0;i<16;i++) {
   j=0;
    while(com_rx_empty()) {
     j++;
     if(j > 5000) {  /* if we get to many retries we exit */
       return (-1);
     }
    }
    mbuff[i]=com_rx(); /* read data into internal buffer */
 }
 return (0);
}

/**********************************************************/
/*                                                        */
/*  This routine is used to dump out data in the internal */
/*  buffer                                                */
/*                                                        */
/**********************************************************/
void print_mouse6d()
{
 printf("Buttons    %x\n",mbuff[0]);
 printf("X          %x %x %x\n",mbuff[1],mbuff[2],mbuff[3]);
 printf("Y          %x %x %x\n",mbuff[4],mbuff[5],mbuff[6]);
 printf("Z          %x %x %x\n",mbuff[7],mbuff[8],mbuff[9]);
 printf("Pitch      %x %x\n",mbuff[10],mbuff[11]);
 printf("Yaw        %x %x\n",mbuff[12],mbuff[13]);
 printf("Roll       %x %x\n",mbuff[14],mbuff[15]);
}

/**************************************************************/
/*                                                            */
/* This routine returns the mouse data it takes pointes to    */
/* x,y,z,roll, pitch,yaw, and the button/status data          */
/* returns 0 if succesful and -1 if failure                   */
/*                                                            */
/**************************************************************/
int get_mouse6d(x,y,z,roll,pitch,yaw,buttons)
int *x,*y,*z,*roll,*pitch,*yaw;
int *buttons;
{
 int stat;

 /* Read the data into the internal buffer */
 stat=read_mouse6d();
 if(stat != 0) return(-1);

 *x=0;
 *x|=((mbuff[1]<<14) | (mbuff[2]<<7) | mbuff[3]);
 if(mbuff[1] & 0x40)  *x|=POSITION_MASK;

 *y=0;
 *y|=(mbuff[4]<<14) | (mbuff[5]<<7) | mbuff[6];
 if(mbuff[4] & 0x40) *y|=POSITION_MASK;

 *z=0;
 *z|=(mbuff[7]<<14) | (mbuff[8]<<7) | mbuff[9];
 if(mbuff[7] & 0x40) *z|=POSITION_MASK;

 *pitch= ((mbuff[10]<<7) | mbuff[11]);
 if(mbuff[10] & 0x40) {
  *pitch|=ROTATION_MASK;
  *pitch+=ROTATION_OFFSET;
 }

 *yaw=(mbuff[12]<<7) | mbuff[13];
 if(mbuff[12] & 0x40) {
   *yaw|=ROTATION_MASK;
   *yaw+=ROTATION_OFFSET;
 }


 *roll=(mbuff[14]<<7) | mbuff[15];
 if(mbuff[14] & 0x40) {
   *roll|= ROTATION_MASK;
   *roll+=ROTATION_OFFSET;
 }

 *buttons=mbuff[0];
 return(0);
}
/*************************************************************/
/*                                                           */
/*  Shut down Logitech mouse processing                      */
/*                                                           */
/*************************************************************/
void close_mouse6d()
{
 com_lower_dtr();
 com_deinstall();
}


/*******************************************************************/
/*                                                                 */
/* Routine to initialize the tractor serail port                   */
/* returns 0 is succesful -1 if failure                            */
/*                                                                 */
/*******************************************************************/
int init_tractor(port) 
int port;
{
/*  use the bioscom function here instead of IBMCOM.C since */
/*  the tractor I/O is so simple                            */   

 tractorID=port-1;  /* bioscom starts port numbering at 0 */
 bioscom(SETUP_PORT,D8_BITS|NO_PARITY|BAUD110,tractorID);
 return(0);
}

/*****************************************************************/
/*                                                               */
/* Routine to pulse tractor                                      */
/*                                                               */
/*****************************************************************/
void pulse_tractor()
{
 static clock_t last=0;  /* last time the tractor was pulsed */
 clock_t now;

 now=clock();
 /*
   If it's been less than 1/2 sec since the tractor was pulsed
   this call will be ignored.  The tractor is only capable of responding
   about twice a second
*/
 if((now-last) < CLK_TCK/2) return;
 bioscom(XMT_BYTE,'P',tractorID);
 last=now;  /* update the last pulse time */
}






Windows Driver:

6DOFWIN.CPP

// **************************************************************************************
#include <windows.h>
#include <string.h>  
#include <ctype.h>

#include "logitech.h" 	// denfines the Logitech class object
#include "resource.h" 	// defines the menu item values

//	GLOBAL DEFS, OBJECTS, AND CONTROL FLAG   

#define APPNAME 			"Logitech 6DOF test app"
#define SERIALPORT  			2
#define LOGITECH_DATA_READY	1

class 	  Logitech *VR_mouse;	// here is the Logitech class pointer declaration ... you'll have one for each tracker 

int	 Roll,Pitch,Yaw,Xpos,Ypos,Zpos,Buttons;   // some variables to hold the data & control the testing
BOOL	TestFlag=FALSE;

//---------------------------------------------------------------------------
// EXPORTS & FUNCTION PROTOTYPES
LRESULT _export FAR PASCAL WndProc( HWND, UINT, WPARAM, LPARAM ) ;
BOOL NEAR InitApplication( HANDLE );  
HWND NEAR InitInstance( HANDLE, int  );
//---------------------------------------------------------------------------

int _export PASCAL WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance,LPSTR lpszCmdLine, int nCmdShow )
{
   	MSG   msg ;
      	if (!hPrevInstance)
      	if (!InitApplication( hInstance ))
        		return ( FALSE ) ;
	if (NULL ==InitInstance(hInstance, nCmdShow))
      	return ( FALSE ) ; 
      
   	while (GetMessage( &msg, NULL, 0, 0 ))
   	{ 
 		if (!TranslateMessage( &msg ) )
      		{
         			TranslateMessage( &msg ) ;
         			DispatchMessage( &msg ) ;      		
      		}
   	}
   	return ( (int) msg.wParam ) ;
} 
//---------------------------------------------------------------------------
BOOL NEAR InitApplication( HANDLE hInstance )
{
   WNDCLASS  wndclass;

   wndclass.style =         NULL ;
   wndclass.lpfnWndProc =   WndProc ;
   wndclass.cbClsExtra =    0 ;
   wndclass.cbWndExtra =    sizeof( WORD ) ;
   wndclass.hInstance =     (HINSTANCE)hInstance ;
   wndclass.hIcon =         LoadIcon( (HINSTANCE)hInstance, MAKEINTRESOURCE( MENUTEST6DOF ) );
   wndclass.hCursor =       LoadCursor( NULL, IDC_ARROW ) ;
   wndclass.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1) ;
   wndclass.lpszMenuName =  MAKEINTRESOURCE( MENUTEST6DOF ) ;
   wndclass.lpszClassName = APPNAME;

   return( RegisterClass( &wndclass ) ) ;

} // end of InitApplication()
//---------------------------------------------------------------------------
HWND NEAR InitInstance( HANDLE hInstance, int nCmdShow )
{   
	HWND hMainWindow;
	
   	hMainWindow=CreateWindow( APPNAME, APPNAME,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 
   					CW_USEDEFAULT,500, 150,NULL, NULL,(HINSTANCE)hInstance,NULL);
   	if (NULL == hMainWindow)
      		return ( NULL ) ;

   	ShowWindow( hMainWindow, nCmdShow ) ;
   	UpdateWindow( hMainWindow ) ;

   	return ( hMainWindow ) ;
} // end of InitInstance()
//---------------------------------------------------------------------------
 LRESULT _export FAR PASCAL WndProc( HWND hWnd, UINT uMsg,WPARAM wParam, LPARAM lParam )
{   
   	switch (uMsg)
   	{
      		case WM_CREATE: 
			//  Create the device object on a port of your choice.  The object will complain
			//  if  it can't initialize the port for some reason.  Note that the object must have 
			//  a parent window handle.  This window MUST process the WM_COMMNOTIFY	
			//  using the object's special ProcessCommNotify() function.  For multiple Logitech's 
			//  you will need to allocate a separate serial port each (of course).  The  WM_COMMNOTIFY
			//  sends the device ID number in the wParam so you can direct the processing 
			//  to the proper mouse object's ProcessCommNotify().

   			VR_mouse = new Logitech(hWnd,SERIALPORT);  	

     			return(TRUE) ;  
      		break;   
      	
      		case WM_COMMNOTIFY:  
			// the Logitech class MUST process ALL the WM_COMMNOTIFY messages
			// Multiple mice will switch(wParam)  to the device port ID  and call the respective
			// mouse object's ProcessCommNotify() function.

      			VR_mouse->ProcessCommNotify();
      		break;                 
      
	 	case WM_TIMER:  
			// the timer message will ask the Logitech class object  (VR_mouse) to go get date.
			// First, lets kill the timer to allow mouse data to reach us before we ask for it again.
			// Asking for data before we are sure we've got the latest record can tangle up the data
			// stream real fast.
	  		KillTimer(hWnd,NULL);  

			// request the mouse data and tell the object what message to send (user defined message) when the data is ready
			// this message can go to any valid window handle and message type. 

	  		VR_mouse->Request6DOF(hWnd,WM_COMMAND,LOGITECH_DATA_READY);
      		break ;

      		case WM_COMMAND:
         			switch (wParam)
         			{                          
         				case LOGITECH_DATA_READY:  
					//  here we process the data ready message (user defined message)
					//  that we told the object to send us when it had the data ready.
					//  The call to the object's Request6DOF(hWnd,WM_COMMAND,LOGITECH_DATA_READY)
					//  function accomplished this request.
			
         					if( ! VR_mouse->Get6DOF(&Roll,&Pitch,&Yaw,&Xpos,&Ypos,&Zpos,&Buttons) )
         						MessageBox(hWnd,"Mouse Error","",MB_ICONEXCLAMATION);        
					
					InvalidateRect(hWnd,NULL,TRUE);   // tell window to redraw itself
	           				UpdateWindow(hWnd);
            				break;  
            
            				case START_TEST:
            					TestFlag=TRUE;
            					SetTimer(hWnd,NULL,100,NULL);   
            				break;
            
            				case END_TEST:
            					TestFlag=FALSE;
            					KillTimer(hWnd,NULL);
            				break;

            				case EXIT:
               				PostMessage( hWnd, WM_CLOSE, NULL, 0L ) ;
            				break ;
			}
		break;
      	
		case WM_DESTROY:
        			PostQuitMessage(0);         
		break;
      
		case WM_PAINT: // standard window drawing stuff to display the data
      		{
      			HDC hdc;   
      			PAINTSTRUCT ps;  
      			char buffer[101];

			hdc=BeginPaint(hWnd,&ps);
		
			if(TestFlag)  
			{
				wsprintf(buffer,"Testing ...");
				TextOut(hdc,10,10,(LPCSTR)buffer,lstrlen(buffer));
			}
			wsprintf(buffer,"Roll  =%4d        Pitch =%4d        Yaw =%4d",Roll,Pitch,Yaw);
			wsprintf(buffer,"Xpos =%4d         Ypos =%4d       Zpos =%4d   Buttons=%d",Xpos,Ypos,Zpos,Buttons); 
			TextOut(hdc,10,60,(LPCSTR)buffer,lstrlen(buffer));
		
			EndPaint(hWnd,&ps);                      
			
			if(TestFlag)  // restart timer  ... this can be done anytime after we have recieved the latest request for mouse data 
            				SetTimer(hWnd,NULL,100,NULL);
		}	
		break;

      		default:
        			return( DefWindowProc( hWnd, uMsg, wParam, lParam ) ) ;
  	 }
  	 return 0L ;
}
/************************************************************************/
RESOURCE.H

#define MENUTEST6DOF	101
#define START_TEST		102
#define END_TEST		103
#define EXIT			104


/************************************************************************/
6DOFWIN.RC

#include "resource.h"
#include "windows.h"

MENUTEST6DOF MENU DISCARDABLE 
BEGIN
    POPUP "&Test Logitech's 6DOF Mouse"
    BEGIN
        MENUITEM "&Start",                      START_TEST
        MENUITEM "&End",                        END_TEST
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       EXIT
    END
END


/************************************************************************/
6DOFWIN.DEF
// we can use a "generic" .def file since the EXPORT functions have been declaired as _export.
// _export is not supported in WIN32 / WIN32s but neither are the CommXX() functions used here.
// hence, this code is WIN 3.1 code.
NAME          	6DOFWIN
EXETYPE      	WINDOWS
DESCRIPTION  	'Logitech 6DOF C++ driver by L.Lopez [CREO ARCLAB US Army SSDC]'
STUB         	winstub.exe'

CODE         PRELOAD MOVEABLE DISCARDABLE
DATA         PRELOAD MULTIPLE MOVEABLE

HEAPSIZE     4096

/************************************************************************/
LOGITECH.H

#define POSITION_MASK 0xfff00000
#define ROTATION_MASK 0xffffc000
#define ROTATION_OFFSET 1992

class Logitech
{   
	HWND		hOwner;
	HWND		hWnd;
	UINT		Msg;
	WPARAM	wParam;
	
	char	MouseState[16];
   	                     
   	BOOL	Connected,
   		DataReady,
   		MouseError;    	
   	int     	CommDev,
		DataCount,
   		Port;
	
	public: 	
 		Logitech(HWND owner,int port); 
    
   		void	ProcessCommNotify();
    		void	Request6DOF(HWND hwnd,UINT msg, WPARAM wparam);
		BOOL	Get6DOF(int *roll, int *pitch, int *yaw, int *xpos, int *ypos, int *zpos, int *buttons);    

   	 protected:
		~Logitech();  
	
   		BOOL  	OpenConnection(HWND,int);
   		void 	CloseConnection();   
    		BOOL  	WriteCommStr(LPCSTR);
		int   	ReadCommStr(LPSTR,int);
};
/***********************************************************************/
/***********************************************************************/
LOGITECH.CPP

#include <windows.h>  
#include "logitech.h" 

//-------------------------------------------------------------
Logitech::Logitech(HWND owner,int port)
{
	Port=port;    
	hOwner=owner;
	hWnd=NULL;
	DataReady=FALSE;
	MouseError=FALSE; 
	Connected=OpenConnection(owner,port);
}
//-------------------------------------------------------------
Logitech::~Logitech()
{
	if (Connected)
    		CloseConnection();  
}
//-------------------------------------------------------------
void Logitech::CloseConnection()
{
  	EnableCommNotification( CommDev, NULL, -1, -1 ) ;
   	EscapeCommFunction( CommDev, CLRDTR ) ;
   	CloseComm( CommDev) ;
   	Connected= FALSE ;
   	MessageBeep(MB_ICONASTERISK);
} 
//-------------------------------------------------------------
BOOL  Logitech::OpenConnection(HWND hwnd, int port)
{
	char 	szPort[ 10 ];
	DCB	dcb;
   
	if(Connected)
   		CloseConnection();
                                     
	wsprintf( szPort, "COM%d", port);

	if ((CommDev = OpenComm( szPort, 1024, 1024 )) < 0) 
	{
		MessageBox(hwnd,"Logitech 6DOF Mouse Connection Failed !",szPort,MB_ICONEXCLAMATION);   
   		return ( FALSE ) ; 
	}
   
	GetCommState(CommDev,&dcb);   
	
	dcb.BaudRate = 19200;
	dcb.ByteSize = 8;
	dcb.Parity   = NOPARITY;
	dcb.StopBits = ONESTOPBIT;
	dcb.fChEvt = TRUE;   
	dcb.EvtChar = '>';

	if(SetCommState( &dcb )<0) 
	{   
		CloseComm(CommDev);
   		MessageBox(hOwner,"Settings Failed !",szPort,MB_ICONEXCLAMATION);
    		return FALSE;
	}
	else
	{    	
        		SetCommEventMask( CommDev, EV_RXFLAG ) ;
        		EnableCommNotification( CommDev, hOwner, 1, 1 ) ;
      		EscapeCommFunction( CommDev, CLRDTR ) ;
		return TRUE;
	}

} // end of OpenCon
//---------------------------------------------------------------------------
int  Logitech::ReadCommStr(LPSTR lpszBlock, int nMaxLength )
{
	int 		nLength, nError ;
	COMSTAT		ComStat ;
	
	nLength = ReadComm( CommDev, lpszBlock, nMaxLength ) ;
   
   	if (nLength < 0)
   	{
    	nLength *= -1 ;
    	nError=GetCommError(CommDev,&ComStat);
	}
    	lpszBlock[nLength]=NULL;
    	return ( nLength ) ;
} // end of ReadCommStr()
//---------------------------------------------------------------------------
BOOL Logitech::WriteCommStr(LPCSTR string )
{
	int len=lstrlen(string);
	WriteComm( CommDev,string,len) ;   
    	return ( TRUE ) ;
} 
//------------------------------------------------------
void Logitech::ProcessCommNotify()
{   
	if(!DataReady)
	{
		DataCount+=ReadCommStr(&MouseState[DataCount],16-DataCount);      		
		if(DataCount>=16)  
			DataReady=TRUE;
	}
	if(DataReady && (hWnd!=NULL) )                  
	{
		PostMessage(hWnd,Msg,wParam,NULL);		
		hWnd=NULL;
	}
}
//------------------------------------------------------
void Logitech::Request6DOF(HWND hwnd,UINT msg, WPARAM wparam)
{
	hWnd=hwnd;
	Msg=msg;
	wParam=wparam;   
	DataCount=0;
	WriteCommStr("*d");
}
//------------------------------------------------------
BOOL Logitech::Get6DOF(int*roll,int*pitch,int*yaw,int*x,int*y,int*z,int*buttons)
{
	if(MouseError || !DataReady)
	{
		MouseError=FALSE;
		return FALSE;
	}
	
 	*x=0;
 	*x|=((MouseState[1]<<14) | (MouseState[2]<<7) | MouseState[3]);
 	if(MouseState[1] & 0x40)  *x|=POSITION_MASK;

 	*y=0;
 	*y|=(MouseState[4]<<14) | (MouseState[5]<<7) | MouseState[6];
 	if(MouseState[4] & 0x40) *y|=POSITION_MASK;

 	*z=0;
 	*z|=(MouseState[7]<<14) | (MouseState[8]<<7) | MouseState[9];
 	
 	if(MouseState[7] & 0x40) 
 		*z|=POSITION_MASK;

 	*pitch= ((MouseState[10]<<7) | MouseState[11]);
 	
 	if(MouseState[10] & 0x40) 
 	{
  		*pitch|=ROTATION_MASK;
  		*pitch+=ROTATION_OFFSET;
 	}

 	*yaw=(MouseState[12]<<7) | MouseState[13];
 
 	if(MouseState[12] & 0x40) 
 	{
   		*yaw|=ROTATION_MASK;
   		*yaw+=ROTATION_OFFSET;
 	}

 	*roll=(MouseState[14]<<7) | MouseState[15];
 	
 	if(MouseState[14] & 0x40) 
 	{
   		*roll|= ROTATION_MASK;
   		*roll+=ROTATION_OFFSET;
 	}

 	*buttons=MouseState[0];

    	DataReady=FALSE;
	return TRUE;
}







