Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
Hi folks

We recently received a new piece of equipment for the lab, which has a network interface. Since I already have myCocoaSoftware for the analysis, why not try to extend this to include measurements as well.

I have a list of commands in the form of text strings from the supplier. These commands have to be send to the instrument over the network. I also have to receive the response. Since I have never done any network programming, I am asking where best to start?
Can I get away with high level cocoa API's, or do I have to delve into the BDS layer, with sockets and such? Which API (or tutorial) is the best starting point? Most of what I have found on the web is about networking between 2 computers and writing server and client software.

I also know the IP address.

Thanks!

PS. yes, I could use a windows computer, and the included software. But that wouldn't teach me anything about network programming, woud it?
 

Sayer

macrumors 6502a
Jan 4, 2002
981
0
Austin, TX
What kind of network-based interface does the instrument actually use? Does it use HTTP (web) interface, SOAP/RPC?

You will need to connect to a specific port, but if it uses simple text commands, it probably has some kind of high-level standards-based interface already. You need to know this info as well.

If it has a high-level interface then the complexity you have to work with (and to implement in a Cocoa app) is greatly reduced.
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
What kind of network-based interface does the instrument actually use? Does it use HTTP (web) interface, SOAP/RPC?

You will need to connect to a specific port, but if it uses simple text commands, it probably has some kind of high-level standards-based interface already. You need to know this info as well.

If it has a high-level interface then the complexity you have to work with (and to implement in a Cocoa app) is greatly reduced.

Sorry for the late reply.

It is a Keithley 2602A sourcemeter, which uses Lua (don't know this one) as a programming/scripting language.
http://www.keithley.com/products/currentvoltage/?path=2602/Documents
All commands are listed in the "Series 2600 System SourceMeter Reference Manual".

I didn't get further than this (doing other stuff at the moment).
It's only for internal use, so simple and quick (but accurate) is good enough.
 

Cromulent

macrumors 604
Oct 2, 2006
6,802
1,096
The Land of Hope and Glory
Looks like it is just a standard RS-232 serial device. Just send the commands using the same method as any serial device.

Just make sure that you use the correct line endings, some require \r at the end, others \n and some others require \r\n. I have no idea what the GPIB interface is though, although I imagine it works in a similar fashion.

All of this is language independent as long as you send the correct messages to the device and read the results correctly.
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
Looks like it is just a standard RS-232 serial device. Just send the commands using the same method as any serial device.

Just make sure that you use the correct line endings, some require \r at the end, others \n and some others require \r\n. I have no idea what the GPIB interface is though, although I imagine it works in a similar fashion.

All of this is language independent as long as you send the correct messages to the device and read the results correctly.

We want to move away from the RS232 and GPIB connections towards the network connection. GPIB can be a royal pain in the ass.
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
Looks like it is just a standard RS-232 serial device. Just send the commands using the same method as any serial device.

Just make sure that you use the correct line endings, some require \r at the end, others \n and some others require \r\n. I have no idea what the GPIB interface is though, although I imagine it works in a similar fashion.

All of this is language independent as long as you send the correct messages to the device and read the results correctly.

My apologies if my questions are simple minded. I don't know any of the networking API's or where exactly to start.

From the manual:

Raw socket (port 23): Raw socket is a basic ethernet connection that communicates similarly to
RS-232 without explicit message boundaries. The instrument will always terminate messages with
a line feed, but because binary data may include bytes that resemble line feed characters, it may
be difficult to distinguish between data and line feed characters.
VXI-11 (port 2024): VXI-11 is similar to GPIB and supports message boundaries as well as service requests
(SRQs). A VXI-11 driver or VISA software is required. Test Script Builder (TSB) uses VISA and can
be used with the VXI-11 interface.
Telnet (port 5025): Telnet is similar to raw socket and is used when the user needs to interact directly with the
instrument, typically for debugging and troubleshooting. Telnet requires a separate telnet program.
Dead socket termination port (port 5027): The dead socket termination port is used to terminate all existing
ethernet connections. A dead socket is one which is held open by the instrument because it has
not been properly closed. This most often happens when the PC is turned off or reboots without
first closing the socket. This port cannot be used for command and control functions.


Since I have no driver for Mac OS X, I have to use the raw socket on port 23 to which I send plain text commands (Lua scripting code) and receive data. Right so far?

Can I use some high level cocoa API for that?
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
NSStream

Hi everyone

NSStream seems a good place to start. So I have been pasting together some code from the internet and the documentation.

I get a response (bad request) from the server when I telnet this from the command line:
open 127.0.0.1 80
GET /index.html HTTP/1.1

When I try to do the same with "my" code, I get this:
>> : NSStreamEventOpenCompleted
<< : NSStreamEventOpenCompleted
2
<< : NSStreamEventHasSpaceAvailable
whereby 2 indicates an open connection.
No data seems to be written to the stream. When I step through the code, the program stops before the
Code:
[[aStream delegate] writeSomething];
is called.

Thanks for the responses.

Code:
- (void)open{
	NSHost * host = [NSHost hostWithAddress:@"127.0.0.1"]; 	int port = 80;
	[NSStream getStreamsToHost:host 
						  port:port
				   inputStream:&inputStream
				  outputStream:&outputStream];
	
	[inputStream setDelegate:self];
	[outputStream setDelegate:self];
	
	[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
						   forMode:NSDefaultRunLoopMode];
	[outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop]
							forMode:NSDefaultRunLoopMode];
	[inputStream open];		//from there to here
	[outputStream open];	//from here to there
}

// Both streams call this when events happen
- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
	NSError *theError = nil;
	NSString *io;
	if (aStream == inputStream)
		io = @">>";
	else
		io = @"<<";
	
	
	NSString *event;
	switch (eventCode)
	{
		case NSStreamEventNone:
			event = @"NSStreamEventNone";
			break;
		case NSStreamEventOpenCompleted:
			event = @"NSStreamEventOpenCompleted";
			break;
		case NSStreamEventHasBytesAvailable:
			event = @"NSStreamEventHasBytesAvailable";
			if (aStream == inputStream)
			{
				uint8_t buffer[128];
				int len;
				while ([inputStream hasBytesAvailable])
				{
					len = [inputStream read:buffer maxLength:sizeof(buffer)];
					if (len > 0)
					{
						NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
						if (nil != output)
						{
							NSLog(@"%@", output);
							[output release];
						}
					}
				}
			}
			break;
		case NSStreamEventHasSpaceAvailable:
			event = @"NSStreamEventHasSpaceAvailable";
			
			
			if (nbr = 0) {
				
				NSLog(@"%i",[aStream streamStatus]);
				[[aStream delegate] writeSomething];
								
				nbr++;
				NSLog(@"%i",[aStream streamStatus]); 
			} else {
			//	[aStream close];
				NSLog(@"%i",[aStream streamStatus]);
			}
			
			break;
		case NSStreamEventErrorOccurred:
			event = @"NSStreamEventErrorOccurred";
			theError = [aStream streamError];
            NSLog(@"An error occurred on the output stream. %@", [theError description]);	
			break;
		case NSStreamEventEndEncountered:
			event = @"NSStreamEventEndEncountered";
			[aStream close];
			[aStream removeFromRunLoop:[NSRunLoop currentRunLoop]
							  forMode:NSDefaultRunLoopMode];
			[aStream release];
			aStream = nil;
			break;
		default:
			event = @"** Unknown";
	}
	
	NSLog(@"%@ : %@\n", io, event);
}

-(void) writeSomething {
	NSLog(@"start");
	NSString *string = @"GET /index.html HTTP/1.1 \n";
	const char *buf = [string UTF8String];
	int len = [string length];
	int retVal;

	while (len > 0) {
		retVal = [outputStream write: buf maxLength: len];	// blocking
		if (retVal < 0)
			return;
		
		buf += retVal; len -= retVal;
	}
	printf("stop");
}

- (void)handleOutputStreamEvent:(NSStreamEvent)eventCode {
	if ([outputStream hasSpaceAvailable]) {
		NSLog(@"outputStream has bytes available");
		
	} else {
		NSLog(@"outputStream has zero bytes available");
	}
	
	NSError *theError = nil;
	switch (eventCode) {
        case NSStreamEventHasBytesAvailable:
			NSLog(@"inputStream has bytes available");
            break;
        case NSStreamEventOpenCompleted:
            // Do Something
			NSLog(@"outputStream complete");
            break;
        default:
        case NSStreamEventErrorOccurred:
			theError = [outputStream streamError];
            NSLog(@"An error occurred on the output stream. %@", [theError description]);
            break;
    }
}

- (void)handleInputStreamEvent:(NSStreamEvent)eventCode {
	if ([inputStream hasBytesAvailable]) {
		NSLog(@"inputStream has bytes");
	} else {
		NSLog(@"inputStream has zero bytes");
	}

	
    switch (eventCode) {
        case NSStreamEventHasBytesAvailable:
NSLog(@"inputStream has bytes");
            break;
        case NSStreamEventOpenCompleted:
            // Do Something
			NSLog(@"inputStream complete");
            break;
        default:
        case NSStreamEventErrorOccurred:
            NSLog(@"An error occurred on the input stream.");
            break;
    }
}

-(void) awakeFromNib {
	nbr = 0;
	[self open];
}
 

Cromulent

macrumors 604
Oct 2, 2006
6,802
1,096
The Land of Hope and Glory
The IP address you are using is the localhost address which means you are actually just trying to communicate with your computer and not the device. You need the actual IP address of the device on the network.
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
The IP address you are using is the localhost address which means you are actually just trying to communicate with your computer and not the device. You need the actual IP address of the device on the network.

I know. :)

I have enabled web sharing on my mac at home to figure out NSStream. If I can't connect to my own web server, I can't connect to this device. First order of business is being able to connect to any server. Afterwards I can figure out how to control the lab instrument via Lua.

For some reason, I can't "GET" the html page of my webserver with the code I posted above. With telnet on the command line, I do get a response.
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
Try just sending GET without the /index.html HTTP/1.1 stuff added on top.

Thanks for the suggestion, but I don't think that is going to change much.

In the function "writeSomething", the first two lines are:
Code:
	NSLog(@"start");
	NSString *string = @"GET /index.html HTTP/1.1 \n";

In the output I never see "start" being printed. The function is never called.
I am opening the stream, but not sending any requests. I don't know why the function is never called.
 

MrFusion

macrumors 6502a
Original poster
Jun 8, 2005
613
0
West-Europe
Functional code for NSStream

If anyone is interested, this code works on my computer (with websharing enabled).
I don't understand how this part of sendCommand is working.
Code:
        void * marker = (void *)[dataToSend bytes];
        while (0 < remainingToWrite) {
            int actuallyWritten = 0;
            actuallyWritten = [outputStream write:marker maxLength:remainingToWrite];
            remainingToWrite -= actuallyWritten;
            marker += actuallyWritten;
        }



Code:
-(void)awakeFromNib {
	[self connectToHost];
	[self sendCommand:[NSString stringWithFormat:@"GET \n"]];
	[self closeStream:outputStream];
}
-(void) connectToHost{
	NSHost * host = [NSHost hostWithAddress:@"127.0.0.1"]; 	
	int port = 80;
	[NSStream getStreamsToHost:host 
						  port:port
				   inputStream:&inputStream
				  outputStream:&outputStream];
	[self openStream:inputStream];
	[self openStream:outputStream];
}
-(void) sendCommand:(NSString *) stringToSend {
    NSData * dataToSend = [stringToSend dataUsingEncoding:NSUTF8StringEncoding];
    if (outputStream) {
        int remainingToWrite = [dataToSend length];
        void * marker = (void *)[dataToSend bytes];
        while (0 < remainingToWrite) {
            int actuallyWritten = 0;
            actuallyWritten = [outputStream write:marker maxLength:remainingToWrite];
            remainingToWrite -= actuallyWritten;
            marker += actuallyWritten;
        }
    }	
}

- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)streamEvent {
	NSError *theError = nil;
	NSString *io;
	if (aStream == inputStream)
		io = @">>";
	else
		io = @"<<";
	
	
	NSString *event;
	switch (streamEvent) {
		case NSStreamEventNone:
			event = @"NSStreamEventNone";
			break;
		case NSStreamEventOpenCompleted:
			event = @"NSStreamEventOpenCompleted";
			break;
		case NSStreamEventHasBytesAvailable:
			event = @"NSStreamEventHasBytesAvailable";
			if (aStream == inputStream) {
				uint8_t buffer[128];
				int len;
				while ([inputStream hasBytesAvailable]) {
					len = [inputStream read:buffer maxLength:sizeof(buffer)];
					if (len > 0) {
						NSString *output = [[NSString alloc] initWithBytes:buffer length:len encoding:NSASCIIStringEncoding];
						if (nil != output) {
							NSLog(@"%@", output);
							[output release];
						}
					}
				}
			}
			break;
		case NSStreamEventHasSpaceAvailable:
			event = @"NSStreamEventHasSpaceAvailable";
			break;
		case NSStreamEventErrorOccurred:
			event = @"NSStreamEventErrorOccurred";
			theError = [aStream streamError];
            NSLog(@"An error occurred on the output stream. %@", [theError description]);	
			break;
		case NSStreamEventEndEncountered:
			event = @"NSStreamEventEndEncountered";
			[self closeStream:aStream];
			break;
		default:
			event = @"** Unknown";
	}
	
	NSLog(@"%@ : %@\n", io, event);
}


-(void) openStream:(NSStream *) myStream {
	[myStream retain];
    [myStream setDelegate:self];
    [myStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [myStream open];
}
-(void) closeStream:(NSStream *) myStream  {
    [myStream close];
    [myStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [myStream setDelegate:nil];
    [myStream release];
    myStream = nil;
}
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.