This is MiscSpaceMouseDriver.m in view mode; [Download] [Up]
/* MiscSpaceMouseDriver.m * * This is a 3DMouse adaptor that will handle the SpaceMouse (produced by * SpaceControl & DLR Germany). * * For more interface-info see the header file. More in depth information * can be found in the source-code. * * Written by: Thomas Engel * Created: 23.03.1994 (Copyleft) * Last modified: 18.04.1994 */ #import "MiscSpaceMouseDriver.h" #import "MiscSpaceMouseFrontend.h" #import <misckit/MiscSerialPort.h> // Here we have some supporting funktions that will help us with lowlevel // Error-correction. // This code is almost the same as in the SGI driver. static char smNibbleTable[] = {'0','A','B','3','D','5','6','G','H','9',':','K','<','M','N','?' }; static int smLowNibble( char ch ) { // if( ch != smNibbleTable[ ch & 0x0F ] ) // [self setErrorFlag:1]; return( ch & 0x0F ); } // Here is our real OO-based driver part. // Basically I have tried to keep it as OO as possible. But some parts // are more runtime optimized. @implementation MiscSpaceMouseDriver void *SpaceMouseActivityEntry( DPSTimedEntry tag, double now, id myDriver ) { [myDriver checkActivity]; return myDriver; } - setTarget:anObject { // When becoming inactive we will reset all the activity checks. // The timeentry settings are save because it will only work when we are // connected to a device! So tentry does reflect the right conditions. if( [self isConnectedToDevice] ) { // We will disable events here to ensure that they won't interfere // while we're trying to 'shut them down'. sendEvents = NO; if( anObject == nil ) { if( tentry ) DPSRemoveTimedEntry( tentry ); if( isActive ) [target transformationDidEnd:self]; isActive = NO; newActions = NO; tentry = 0; } else { if( !tentry ) { tentry = DPSAddTimedEntry( 1, (DPSTimedEntryProc)SpaceMouseActivityEntry, self, NX_MODALRESPTHRESHOLD ); } isActive = NO; newActions = NO; } } return [super setTarget:anObject]; } - setFrontend:anObject { frontend = anObject; return self; } - checkActivity { // Here we will handle the activitv control. // If we are still isActive we might check the conditions. if( !isActive ) return self; // If there has been a new activity in the last time-period we will // assume that the transformation did not end yet. But we will reset the // flag. // Otherwise we will infor our target that no new action did take place. if( newActions ) newActions = NO; else { [target transformationDidEnd:self]; isActive = NO; } return self; } - (BOOL)connectToDevice:(const char *)device { BOOL suc; // Init a new port and don't forget to reset our buffers! if( [self isConnectedToDevice] ) return NO; smEventBuffer[0] = 0; port = [MiscSerialPort new]; [port setBaud:9600]; [port setDelegate:self]; [port setDeviceName:device]; suc = [port connect]; sleep( 1 ); if( suc ) { // The first direct write is just to set the mouse the 9600 Baud. // Then we will set every value to a default. This way all our values // get initialized. [port transmitChars:"z\r\r" length:3]; sleep( 1 ); [self zeroMouseData]; [self setMouseInDominantMode:YES withTranslationEnabled:YES andRotationEnabled:YES]; [self setRotScale:100]; [self setTransScale:500]; [self setQualityForTranslation:2 andRotation:4]; [self setDataRateMin:10 max:15]; [self setNullRadius:15]; // Now lets init the new timed entry. We will check the start rotation // stuff. isActive = NO; newActions = NO; tentry = DPSAddTimedEntry( 1, (DPSTimedEntryProc)SpaceMouseActivityEntry, self, NX_MODALRESPTHRESHOLD ); } else port = nil; return suc; } - disconnectFromDevice { if( [self isConnectedToDevice] ) { [port disconnect]; port = nil; isActive = NO; newActions = NO; if( tentry ) DPSRemoveTimedEntry(tentry); tentry = 0; } return self; } - (BOOL)isConnectedToDevice { if( port ) return YES; else return NO; } - receiveChars:(char *)buffer length:(int)length { // Here we'll get notified of arriving characters. // We are looking for the final \r CR character. // The first step is to add the chars from the buffer to our // parsing Line. This will continue until we have a full event. // // Our CommandBuffer is 200 chars long. This should be long enough // because the longest command is the Version command an this will deliver // about 50 chars. So missing one \r will cause no problems. int i; int eventLength; int maxLength; int newLength; char aChar; for( eventLength=0; eventLength<198; eventLength++) if( smEventBuffer[eventLength] == 0 ) break; maxLength = 198 - eventLength; if( maxLength > length ) maxLength = length; aChar = 0; for( i=0; i<maxLength; i++ ) { aChar = buffer[i]; if( aChar == '\r' ) break; smEventBuffer[eventLength] = aChar; eventLength++; } // We will terminate the new eventString and we will calculate the length // of the remaining buffer. // This is only interesting when we ended with a \r !! So we can // savely do (i+1) here! smEventBuffer[eventLength] = 0; newLength = length - (i+1); // If we did not end with a CR we'll have to wait for some more input. // If we did. We will copy the rest of the buffer to the eventBuffer, // after we did parse the current event. // printf( "%d %d %d %s\r\n" , length, newLength, eventLength, // smEventBuffer); if( aChar != '\r' ) return self; [self parseCommand:smEventBuffer]; // If there is some code left inside the buffer we call ourself // recursivly again after erasing the current eventBuffer. smEventBuffer[0] = 0; if( newLength > 0 ) [self receiveChars:&buffer[length-newLength] length:newLength]; return self; } - setMouseInDominantMode:(BOOL)domFlag withTranslationEnabled:(BOOL)transFlag andRotationEnabled:(BOOL)rotFlag { rotationOn = rotFlag; translationOn = transFlag; dominantModeOn = domFlag; smSendBuffer[0] = 'm'; smSendBuffer[1] = smNibbleTable[ rotFlag * 1 + transFlag * 2 + domFlag * 4 ]; smSendBuffer[2] = '\r'; // We don't need to ask for the result here. The mouse echos the // setting on its own. [port transmitChars:smSendBuffer length:3]; return self; } - (BOOL)hasTranslationEnabled { return translationOn; } - (BOOL)hasRotationEnabled { return rotationOn; } - (BOOL)isInDominantMode { return dominantModeOn; } - setQualityForTranslation:(int)transInt andRotation:(int)rotInt { if( rotInt < 0 ) rotInt = 0; if( rotInt > 15 ) rotInt = 15; if( transInt < 0 ) transInt = 0; if( transInt > 15 ) transInt = 15; rotationQuality = rotInt; translationQuality = transInt; smSendBuffer[0] = 'q'; smSendBuffer[1] = smNibbleTable[transInt]; smSendBuffer[2] = smNibbleTable[rotInt]; smSendBuffer[3] = '\r'; [port transmitChars:smSendBuffer length:4]; // [port transmitChars:"qQ\r" length:3]; return self; } - (int)translationQuality { return translationQuality; } - (int)rotationQuality { return rotationQuality; } - setNullRadius:(int)anInt { if( anInt < 0 ) anInt = 0; if( anInt > 15 ) anInt = 15; nullRadius = anInt; smSendBuffer[0] = 'n'; smSendBuffer[1] = smNibbleTable[anInt]; smSendBuffer[2] = '\r'; [port transmitChars:smSendBuffer length:3]; // [port transmitChars:"nQ\r" length:3]; return self; } - (int)nullRadius { return nullRadius; } - setDataRateMin:(int)minRate max:(int)maxRate { if( minRate < 2 ) minRate = 2; if( minRate > 15 ) minRate = 15; if( maxRate < 2 ) maxRate = 2; if( maxRate > 15 ) maxRate = 15; if( minRate > maxRate ) minRate = maxRate; if( maxRate < minRate ) maxRate = minRate; minDataRate = minRate; maxDataRate = maxRate; smSendBuffer[0] = 'p'; smSendBuffer[1] = smNibbleTable[maxRate]; smSendBuffer[2] = smNibbleTable[minRate]; smSendBuffer[3] = '\r'; [port transmitChars:smSendBuffer length:4]; // [port transmitChars:"pQ\r" length:3]; return self; } - (int)minDataRate { return minDataRate; } - (int)maxDataRate { return maxDataRate; } - setRotScale:(float)aFloat { rotScale = aFloat; [frontend takeScaleFrom:self]; return self; } - (float)rotScale { return rotScale; } - setTransScale:(float)aFloat { transScale = aFloat; [frontend takeScaleRationFrom:self]; return self; } - (float)transScale { return transScale; } - queryDeviceVersion { [port transmitChars:"vQ\r" length:3]; return self; } - beepWithDuration:(int)anInt { // Well we will always turn the beeper on. (=> + 15) // I don't know what turning the beeper off is good for. // If you can tell me.. I will add a new method for that. if( anInt < 0 ) anInt = 0; if( anInt > 7 ) anInt = 7; smSendBuffer[0] = 'b'; smSendBuffer[1] = smNibbleTable[anInt + 15]; smSendBuffer[2] = '\r'; [port transmitChars:smSendBuffer length:3]; return self; } - zeroMouseData { [port transmitChars:"z\r" length:2]; return self; } - setErrorFlag:(int)flag { errorFlag = flag; return self; } - parseCommand:(const char *)buffer { if( buffer[0] == 'd' ) [self parseTransformationEvent:buffer]; if( buffer[0] == 'k' ) [self parseKeyEvent:buffer]; if( buffer[0] == 'n' ) [self parseNullRadiusEvent:buffer]; if( buffer[0] == 'q' ) [self parseQualityEvent:buffer]; if( buffer[0] == 'p' ) [self parseDataRateEvent:buffer]; if( buffer[0] == 'e' ) [self parseErrorEvent:buffer]; if( buffer[0] == 'v' ) [self parseVersionEvent:buffer]; if( buffer[0] == 'm' ) [self parseModeEvent:buffer]; return self; } - parseModeEvent:(const char *)buffer { int mode; mode = smLowNibble( buffer[1] ); if( mode & 2 ) translationOn = YES; else translationOn = NO; if( mode & 1 ) rotationOn = YES; else rotationOn = NO; if( mode & 4 ) dominantModeOn = YES; else dominantModeOn = NO; [frontend takeTransModeFrom:self]; [frontend takeRotModeFrom:self]; [frontend takeDomModeFrom:self]; return self; } - parseVersionEvent:(const char *)buffer { return self; } - parseErrorEvent:(const char *)buffer { return self; } - parseDataRateEvent:(const char *)buffer { maxDataRate = smLowNibble( buffer[1] ); minDataRate = smLowNibble( buffer[2] ); [frontend takeMinDataRateFrom:self]; [frontend takeMaxDataRateFrom:self]; return self; } - parseQualityEvent:(const char *)buffer { translationQuality = smLowNibble( buffer[1] ); rotationQuality = smLowNibble( buffer[2] ); [frontend takeTransQualityFrom:self]; [frontend takeRotQualityFrom:self]; return self; } - parseNullRadiusEvent:(const char *)buffer { nullRadius = smLowNibble( buffer[1] ); [frontend takeNullRadiusFrom:self]; return self; } - parseKeyEvent:(const char *)buffer { [self deliverKeyPress:"12"]; return self; } - parseTransformationEvent:(const char *)buffer { float x, y, z, a, b, c; if( !sendEvents ) return self; x = smLowNibble( buffer[ 1] ) * 4096 + smLowNibble( buffer[ 2] ) * 256 + smLowNibble( buffer[ 3] ) * 16 + smLowNibble( buffer[ 4] ) - 32768; y = smLowNibble( buffer[ 5] ) * 4096 + smLowNibble( buffer[ 6] ) * 256 + smLowNibble( buffer[ 7] ) * 16 + smLowNibble( buffer[ 8] ) - 32768; z = smLowNibble( buffer[ 9] ) * 4096 + smLowNibble( buffer[10] ) * 256 + smLowNibble( buffer[11] ) * 16 + smLowNibble( buffer[12] ) - 32768; a = smLowNibble( buffer[13] ) * 4096 + smLowNibble( buffer[14] ) * 256 + smLowNibble( buffer[15] ) * 16 + smLowNibble( buffer[16] ) - 32768; b = smLowNibble( buffer[17] ) * 4096 + smLowNibble( buffer[18] ) * 256 + smLowNibble( buffer[19] ) * 16 + smLowNibble( buffer[20] ) - 32768; c = smLowNibble( buffer[21] ) * 4096 + smLowNibble( buffer[22] ) * 256 + smLowNibble( buffer[23] ) * 16 + smLowNibble( buffer[24] ) - 32768; x = x / transScale; y = y / transScale; z = z / transScale; a = a / rotScale; b = b / rotScale; c = c / rotScale; // If this is our first action after a long time of silence we will tell // our target about that. every new action will be registered here. newActions = YES; if( !isActive ) { isActive = YES; [target transformationWillStart:self]; } [self deliverTranslation:x :y :z andRotation:a :b :c]; return self; } @end /* * History: 18.04.94 Added the timed entry to find start and end of actions. * * 15.04.94 Included two seperate scales and frontend support. * * 11.04.94 added support for the general Misc3DMouseDriver. * * 04.04.94 Got the first data from the SpaceMouse. Now let the * party begin. * * 23.03.94 First code written. * * * Bugs: - Does not read the mouse version. It will need a MiscString someday. * * - The zeroing return msg is ignored. Well should not matter. */
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.