view rsib.py @ 79:84f96c5fe791

Use different message ID that does not result in "Query UNTERMINATE" messages in the error log. Fix testsrq. Rename queryrsb to querystb as that matches the operating manual.
author Daniel O'Connor <doconnor@gsoft.com.au>
date Fri, 27 Sep 2024 16:53:43 +0930
parents 576f112e0aba
children
line wrap: on
line source

#!/usr/bin/env python

# Copyright (c) 2024
#      Daniel O'Connor <darius@dons.net.au>.  All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# 1. Redistributions of source code must retain the above copyright
#    notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
#    notice, this list of conditions and the following disclaimer in the
#    documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY AUTHOR AND CONTRIBUTORS ``AS IS'' AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
# SUCH DAMAGE.
#

# Helper functions to talk to Rohde & Schwarz test equipment over the
# RSIB protocol.
#
# This is for older test equipment (tested on an FSP7 Windows NT 3.51..)
#
# Reverse engineered from the Linux library & example program at
# http://epsrv.astro.umk.pl/~ep/irbene/rsib/library/RSIB-Linux.zip
#
# There are 2 socket connections for some reason.
# The first seems to do [nearly] all the work
#
# Packets to the instrument
# On connection:
# -> 00 00 00 40
# <- 00 00 00 40 a0 04 00 00
#
# We need to echo back the last 4 bytes of the reply to a second
# socket opened to the same port. I don't know what that socket is for
# though.
#
# Send a command:
# -> 00 00 00 05 90 00 01 2a  49 44 4e 3f             .......* IDN?
#
# Offs	Value	Meaning
# 00	00	?
# 01	00	?
# 02	00	?
# 03	05	Length
# 04	94	MsgID
# 05	00	?
# 06	03	Seq number
# 07	2a (*)	Cmd byte 0
# 08	49 (I)	Cmd byte 1
# 09	44 (D)	Cmd byte 2
# 10	4a (N)	Cmd byte 3
# 10	3f (?)	Cmd byte 4
#
# Interactive program seems to cap length at 0x99 but perhaps the
# first 4 bytes are length.
#
# MsgID is 0x94 but 0x90 works but then we get "Query UNTERMINATED"
# from SYS:ERR? Could be EoM bit.
#
# Reply to command:
# <- 00 00 00 23 80 00 01 52 6f 68 ...
#
# Offs	Value	Meaning
# 00	00	Length 31..24
# 01	00	of     23..16
# 02	00	block  15..8
# 03	23	        7..0
# 04	00/80	Is 0x80 if this is the last block in a sequence
# 05	00	?
# 06	01	Seq number

MSG_HELLO = 0x40   # We send this on connect
MSG_SRQ = 0x91	   # Query SRQ

import socket
import time

class RSIBDevice(object):
    hello = '\x00\x00\x00\x40'
    docmd = '\x00\x00\x00\x05\x90\x00\x01'

    def __init__(self, host, port = None):
        if port == None:
            port = 2525
        self.host = host
        self.port = port

        s1 = socket.socket()
        s1.settimeout(5)
        s1.connect((self.host, self.port))
        s1.send(b'\x00\x00\x00\x40')
        rx = b''
        while len(rx) < 8:
            rx = rx + s1.recv(8)
            if rx == b'':
                raise IOError("EOF from device")

        s2 = socket.socket()
        s2.settimeout(5)
        s2.connect((self.host, self.port))
        s2.send(rx[4:])

        self.s1 = s1
        self.s2 = s2
        self.tag = 0

    def write(self, cmd, timeout = 0.5):
        """Send data (string) to the instrument"""
        self.s1.settimeout(timeout)

        if len(cmd) > 0x99:
            raise ValueError("Command too long")

        # Pre-increment for easy comparison in read
        self.tag = (self.tag + 1) & 0xff
        msg = b'\x00\x00\x00' + bytes([len(cmd)]) + b'\x94\x00' + bytes([self.tag]) + cmd.encode('ascii')
        self.s1.send(msg)

    def read(self, timeout = 0.5):
        """Read data from the device, waits for up to timeout seconds for each TCP read"""

        if timeout is not None:
            timeout = time.clock_gettime(time.CLOCK_MONOTONIC) + timeout
        self.s1.settimeout(timeout)

        reply = b''
        last = False
        while not last:
            # Fetch the header
            hdr = b''
            remain = 7
            while len(hdr) < 7:
                rx = self.s1.recv(remain)
                if len(rx) == 0:
                    if timeout is not None and time.clock_gettime(time.CLOCK_MONOTONIC) >= timeout:
                        raise IOError("EOF from device")
                    else:
                        time.sleep(0.1)
                remain -= len(rx)
                hdr += rx
            rxlen = rx[0] << 24 | rx[1] << 16 | rx[2] << 8 | rx[3]
            #print(rx, rxlen)
            if self.tag != rx[6]:
                raise IOError("Reply out of order, got 0x%02x expected 0x%02x" % (rx[6], self.tag))
            if rx[4] == 0x80:
                #print('EOM')
                last = True
            if rx[5] != 0:
                print("Mystery byte %d != 0" % (rx[5]))

            # Fetch the actual data block now we know the length
            remain = rxlen
            while remain > 0:
                rx = self.s1.recv(remain)
                if len(rx) == 0:
                    if timeout is not None and time.clock_gettime(time.CLOCK_MONOTONIC) >= timeout:
                        raise IOError("EOF from device")
                    else:
                        time.sleep(0.1)

                reply += rx
                remain -= len(rx)

        return(reply)

    def ask(self, s, timeout = None):
        '''Wrapper to send a command and wait for a reply'''
        self.write(s)
        return self.read(timeout = timeout)

    def querystb(self):
        msg = b'\x00\x00\x00\x00\xd1\x18\x00'
        self.s2.send(msg)

        reply = b''
        while len(reply) < 7:
            rx = self.s2.recv(7)
            if rx == b'':
                raise IOError("EOF from device")
            reply += rx

        # '\x00\x00\x00\x00\x80\x04\x01' => STB = 4
        if rx[4] != 0x80:
            raise IOError("Incorrect Msg ID in response to STB query")

        return rx[5]

    def waitsrq(self):
        msg = b'\x00\x00\x00\x00\xb1\x00\x00'

    def testsrq(self):
        msg = b'\x00\x00\x00\x00\x91\x00\x00'
        self.s2.send(msg)
        reply = b''
        while len(reply) < 7:
            rx = self.s2.recv(7)
            if rx == b'':
                raise IOError("EOF from device")
            reply += rx

        # 00 00 00 00 80 14 08 - SRQ = 0
        # 00 00 00 00 a0 64 07 - SRQ = 1
        if rx == b'\x00\x00\x00\x00\x80\x14\x08':
            return False
        elif rx == b'\x00\x00\x00\x00\xa0\x64\x07':
            return True
        else:
            raise IOError("Unknown SRQ byte sequence - " + str(rx))