PDA

View Full Version : networking with a lab instrument




MrFusion
Jun 23, 2009, 02:00 AM
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?



Cromulent
Jun 23, 2009, 02:17 AM
Your best bet is most likely using BSD sockets. Really there is no difference between communicating between 2 computers and 1 computer and 1 device as long as you know what the protocol is.

This guide got me going with network programming:

http://www.beej.us/guide/bgnet/output/html/singlepage/bgnet.html

Sayer
Jun 23, 2009, 02:13 PM
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
Jun 25, 2009, 03:39 PM
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
Jun 25, 2009, 03:51 PM
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
Jun 25, 2009, 04:09 PM
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
Jun 26, 2009, 03:55 AM
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
Jun 26, 2009, 03:59 AM
Your best bet is most likely using BSD sockets. Really there is no difference between communicating between 2 computers and 1 computer and 1 device as long as you know what the protocol is.

This guide got me going with network programming:

http://www.beej.us/guide/bgnet/output/html/singlepage/bgnet.html

Thanks!

MrFusion
Jun 27, 2009, 01:55 AM
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 [[aStream delegate] writeSomething]; is called.

Thanks for the responses.



- (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
Jun 27, 2009, 02:00 AM
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
Jun 27, 2009, 03:12 AM
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.

Cromulent
Jun 27, 2009, 04:27 AM
Try just sending GET without the /index.html HTTP/1.1 stuff added on top.

MrFusion
Jun 27, 2009, 07:47 AM
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:

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
Jun 28, 2009, 11:49 AM
If anyone is interested, this code works on my computer (with websharing enabled).
I don't understand how this part of sendCommand is working.

void * marker = (void *)[dataToSend bytes];
while (0 < remainingToWrite) {
int actuallyWritten = 0;
actuallyWritten = [outputStream write:marker maxLength:remainingToWrite];
remainingToWrite -= actuallyWritten;
marker += actuallyWritten;
}





-(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;
}