instrumentkit / InstrumentKit

Python package for interacting with laboratory equipment over various buses.
248 stars 71 forks source link

Unable to perform read operation twice on Yokogawa 6370 #386

Closed slagroommarino closed 1 year ago

slagroommarino commented 1 year ago

Thanks for providing such a well thought library. Whilst trying to communicate to our Yokogawa 6370D, I encountered that I cannot read both axes from the device without throwing an error.

The device is initialized as follows, using a slightly modified open_tcip function to allow for user authorization:

osa = instruments.yokogawa.Yokogawa6370.open_tcip(host="IP_ADDRESS", port="PORT", login="USER", password="PWD")`
osa.start_sweep()

x = osa.wavelength()
y = osa.data()

the last call cannot be executed without throwing a ValueError ('' is not a valid Yokogawa6370.Traces) in utils_fns.py, which basically comes from no or empty :TRAC:ACTIVE? reply. For some reason, when the :TRAC:ACTIVE? command is executed twice, the OSA's response is empty and the ProxyList function fails. Executing the command explicitly:

x = osa.wavelength()
osa.query(':TRAC:ACTIVE?')
resp = osa.read()
y = osa.data()

solves the problem. Executing the query without the data() or wavelength function will cause the script to hang on the osa.read() command.

I'm not sure where to look for the solution here. I don't expect the OSA to be the problem, as this problem does not occur in my previous python scripts.

trappitsch commented 1 year ago

What version if ik are you using? There have been recent changes to the Yokogawa (#353) which might solve the problem if you are using the version on pip. @bmoneke: Since you just worked over this instruments, do you have any ideas here?

Also, it would be interesting to see your modified open_tcpip routine, since ik doesn't forward login and password for user authentication at the moment. Since apparently required for some instruments, these could be included as optional keyword arguments...

BenediktBurger commented 1 year ago

I'm sorry, but I never used that active trace feature.

Did you try to call the trace manually (with osa.channel['A'].data)?

slagroommarino commented 1 year ago

What version if ik are you using? There have been recent changes to the Yokogawa (#353) which might solve the problem if you are using the version on pip.

I am using the pulled version from git, and I just tried the 0.6 release as well. In v0.6 the x = osa.data() does not even work as it seems to attach a function to x instead of the result. This is not happening in the current version that is available on GitHub.

Did you try to call the trace manually (with osa.channel['A'].data)?

For some reason I get a binary block read error in binblockread() as my received message starts with '\n' instead of '#'. Regardless of version.

Also, it would be interesting to see your modified open_tcpip routine

It is a very basic addition to get the connection working:

    @classmethod
    def open_tcpip(cls, host, port, login='', password=''):
        """
        Opens an instrument, connecting via TCP/IP to a given host and TCP port.

        :param str host: Name or IP address of the instrument.
        :param int port: TCP port on which the insturment is listening.
        :param str login: Username to login to the instrument (optional)
        :param str password: Corresponding password (optional)

        :rtype: `Instrument`
        :return: Object representing the connected instrument.

        .. seealso::
            `~socket.socket.connect` for description of `host` and `port`
            parameters in the TCP/IP address family.
        """
        conn = socket.socket()
        conn.connect((host, port))

        if password:
            message = 'open "{login}"'.format(login=login) + '\r\n'
            conn.send(message.encode('utf8'))
            resp = conn.recv(2048).decode('utf8')
            message = password + '\r\n'
            conn.send(message.encode('utf8'))
            resp = conn.recv(2048).decode('utf8')

        return cls(SocketCommunicator(conn))

I'll try to experiment a bit more to find some more information.

slagroommarino commented 1 year ago

I seemed to have found the cause of the problems that I was getting!

In yokogawa6370.py, line 76, I changed self._parent._file.read_raw(1) to self._parent._file.read_raw(2).

I noticed that whilst reading data from the OSA, the first received character after a second read was always starting with \n. Inserting an additional read gave #, which is the value binblockread() is looking for. I think the \n is belonging to the CRLF termination that the OSA is apparently using.

For me it solved all problems that I described above, although I do not know why that causes the script to get a ValueError with an empty trace ID when running x = osa.data().

trappitsch commented 1 year ago

Sorry for the late reply. This is strange. According to the manual of the instrument, it will accept CRLF as a terminator, but defaults to LF only (see page 1-3, "Message Terminators"). ik by default uses \n (so LF) as the terminator. For LAN based remote control, which I think you are using, the manual (page 3-7, "Delimiter") states:

The delimiter for LAN-based remote control is fixed to CR + LF.

So if the instrument sends a \r\n but ik expects \n, this could get you into some of the problems that you describe. @bmoneke: How did you communicate with the instrument when you were testing with the hardware? Why self._parent._file.read_raw(2) works I do not know. According to the "Message Terminators" section in the manual, binary transfer should only submit an EOI as the message terminator...

As a first test to try and run this to the ground, could you try the following slightly adopted version from your initial command set and post the error that you get (if you get one):

osa = instruments.yokogawa.Yokogawa6370.open_tcip(host="IP_ADDRESS", port="PORT", login="USER", password="PWD")`

osa.terminator = "\r\n"

osa.start_sweep()

x = osa.wavelength()
y = osa.data()

@scasagrande: login authentication for TCP/IP is something we should probably include by default into ik as an option, what do you think?

BenediktBurger commented 1 year ago

I used the serial connection, so sorry, no information regarding tcp.

trappitsch commented 1 year ago

I used the serial connection, so sorry, no information regarding tcp.

Great, that might mean I'm not completely on the wrong track here... Thanks for the fast reply!

slagroommarino commented 1 year ago

As a first test to try and run this to the ground, could you try the following slightly adopted version from your initial command set and post the error that you get (if you get one):

osa = instruments.yokogawa.Yokogawa6370.open_tcip(host="IP_ADDRESS", port="PORT", login="USER", password="PWD")`

osa.terminator = "\r\n"

osa.start_sweep()

x = osa.wavelength()
y = osa.data()

Took a while, but I can confirm that on the original script (without modifications), this does work. So the device seems to be expecting a different termination depending on the connection type.

trappitsch commented 1 year ago

Cool, thanks for checking @slagroommarino. I prepared a fix for this, see #388. This would automatically set the terminator when the connection is via TCP IP to "\r\n". If you can try it with the instrument and let us know if it works, that would be great!

Will try to implement the user authentication in a separate PR. Thanks for your comments!

slagroommarino commented 1 year ago

Problem is solved using the fix! Thanks for trying to implement the authentication too, that will be a useful addition for us.

scasagrande commented 1 year ago

Not closed until the fix is merged! :)

scasagrande commented 1 year ago

merged! #388