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.