PyUSB: SCPI communication with OWON Oscilloscope
Asked Answered
H

5

8

This is an updated and shortened question.

Communicating with a USB-device should be easy via PyUSB. So, I'm trying to read from a USB-device (oscilloscope) using PyUSB under Win10. Apparently, the USB-driver (libusb-win32 v1.2.6.0) is installed correctly since the device is found and I get some response from print(dev) (see below). From this I can see that the output endpoint address is 0x3 and the input endpoint address is 0x81

According to the Oscilloscope manual, I'm supposed to send :SDSLSCPI# to the device to set it into SCPI-mode and should get the reponse ':SCPION'. However, when sending :SDSLSCPI# the monitor of the oscilloscope reproduceably will freeze and it will restart.

If I send *IDN? I should get the response ,P1337,1842237,V2.4.0->. But only if the device is already in SCPI-mode. Apparently, it is not and I get a timeout error (see below).

So, what am I doing wrong here? What information am I missing in the PyUSB tutorial. Am I using the wrong PyUSB commands/parameters or is it about missing additional drivers or is it about the hardware, either Win10 or the device hardware? Thank you for hints on how to find out what's going wrong.

By the way, what is the second value in dev.read(0x81,7)? Number of bytes to read? Well, usually I don't know how many bytes the device will send. I was expecting a command to read until a linefeed or some other terminator character within the timeout time. Where can I find "fool-proof" documentation, tutorials and examples about PyUSB?

Code:

import usb.core
import usb.util

dev = usb.core.find(idVendor=0x5345, idProduct=0x1234)
if dev is None:
    raise ValueError('Device is not found')
# device is found :-)
print(dev)

dev.set_configuration()

msg = ':SDSLSCPI#'
print("Write:", msg, dev.write(3,msg))

print("Read:", dev.read(0x81,7))

Output from print(dev):

DEVICE ID 5345:1234 on Bus 000 Address 001 =================
 bLength                :   0x12 (18 bytes)
 bDescriptorType        :    0x1 Device
 bcdUSB                 :  0x200 USB 2.0
 bDeviceClass           :    0x0 Specified at interface
 bDeviceSubClass        :    0x0
 bDeviceProtocol        :    0x0
 bMaxPacketSize0        :   0x40 (64 bytes)
 idVendor               : 0x5345
 idProduct              : 0x1234
 bcdDevice              :  0x294 Device 2.94
 iManufacturer          :    0x1 System CPU
 iProduct               :    0x2 Oscilloscope
 iSerialNumber          :    0x3 SERIAL
 bNumConfigurations     :    0x1
  CONFIGURATION 1: 500 mA ==================================
   bLength              :    0x9 (9 bytes)
   bDescriptorType      :    0x2 Configuration
   wTotalLength         :   0x20 (32 bytes)
   bNumInterfaces       :    0x1
   bConfigurationValue  :    0x1
   iConfiguration       :    0x5 Bulk Data Configuration
   bmAttributes         :   0xc0 Self Powered
   bMaxPower            :   0xfa (500 mA)
    INTERFACE 0: Physical ==================================
     bLength            :    0x9 (9 bytes)
     bDescriptorType    :    0x4 Interface
     bInterfaceNumber   :    0x0
     bAlternateSetting  :    0x0
     bNumEndpoints      :    0x2
     bInterfaceClass    :    0x5 Physical
     bInterfaceSubClass :    0x6
     bInterfaceProtocol :   0x50
     iInterface         :    0x4 Bulk Data Interface
      ENDPOINT 0x81: Bulk IN ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :  0x200 (512 bytes)
       bInterval        :    0x0
      ENDPOINT 0x3: Bulk OUT ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :    0x3 OUT
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :  0x200 (512 bytes)
       bInterval        :    0x0

Error message:

Traceback (most recent call last):
  File "Osci.py", line 15, in <module>
    print("Read:", dev.read(0x81,7))
  File "C:\Users\Test\Programs\Python3.7.4\lib\site-packages\usb\core.py", line 988, in read
    self.__get_timeout(timeout))
  File "C:\Users\Test\Programs\Python3.7.4\lib\site-packages\usb\backend\libusb0.py", line 542, in bulk_read
    timeout)
  File "C:\Users\Test\Programs\Python3.7.4\lib\site-packages\usb\backend\libusb0.py", line 627, in __read
    timeout
  File "C:\Users\Test\Programs\Python3.7.4\lib\site-packages\usb\backend\libusb0.py", line 431, in _check
    raise USBError(errmsg, ret)
usb.core.USBError: [Errno None] b'libusb0-dll:err [_usb_reap_async] timeout error\n'

Update:

I got a reply from the vendor. And he confirms that the oscilloscope (or at least this specific series) crashes when sending the command :SDSLSCPI#. He will contact the developers which will back next week. OK, it seems so far no chance for me to get it to run with this specific device and the available documentation :-(.

Helsinki answered 29/11, 2019 at 12:49 Comment(4)
I think you mixed the endpoints. Try to write to 0x81 and read from 0x03.Cattier
well, I'm pretty sure that the addresses are correct. In the meantime I could get the SerialNumber via sending *IDN?,however, other SCPI-commands still create a timeout error. And my suspicion is that sending :SDSLSCPI# is actually not needed. The whole thing is pretty annoying and strange...Helsinki
If you are getting any response, than the communication channel is set properly, and it is not a problem with pyUSB. So it is finding common language with the device ;) Are you sure it supports the SCPI mode? BC in the docs written if the device does not support SCPI, it will not return anything on your message.Cattier
I was told that the device would understand SCPI. At least it understands *IDN?. But according to documentation also the other listed commands should give some response. I need to check again next week.Helsinki
H
9

I guess there was no chance to answer this question unless somebody already went through the very same problems. I'm sorry for all of you (@Alex P., @Turbo J, @igrinis, @2xB) who took your time to make suggestions to help.

My findings: (I hope they will be useful to others):

  1. Everything seems to be OK with PyUSB.
  2. the vendor has provided outdated and wrong documentation. I hope very much that they will soon update the documentation on their homepage.
  3. Sending the command :SDSLSCPI# is not necessary to enter SCPI-mode (but actually leads to a crash/restart)
  4. For example: :CHAN1:SCAL 10v is wrong, it has to be :CH1:SCALe 10v (commands apparenty can't be abbreviated, although mentioned in the documentation that :CH1:SCAL 10v should also work.)
  5. the essential command to get data :DATA:WAVE:SCREen:CH1? was missing in the manual.

The way it is working for me (so far):

The following would have been the minimal code I expected from the vendor/manufacturer. But instead I wasted a lot of time debugging their documentation. However, still some strange things are going on, e.g. it seems you get data only if you ask for the header beforehand. But, well, this is not the topic of the original question.

Code:

### read data from a Peaktech 1337 Oscilloscope (OWON)
import usb.core
import usb.util

dev = usb.core.find(idVendor=0x5345, idProduct=0x1234)

if dev is None:
    raise ValueError('Device not found')
else:
    print(dev)
    dev.set_configuration()

def send(cmd):
    # address taken from results of print(dev):   ENDPOINT 0x3: Bulk OUT
    dev.write(3,cmd)
    # address taken from results of print(dev):   ENDPOINT 0x81: Bulk IN
    result = (dev.read(0x81,100000,1000))
    return result

def get_id():
    return send('*IDN?').tobytes().decode('utf-8')

def get_data(ch):
    # first 4 bytes indicate the number of data bytes following
    rawdata = send(':DATA:WAVE:SCREen:CH{}?'.format(ch))
    data = []
    for idx in range(4,len(rawdata),2):
        # take 2 bytes and convert them to signed integer using "little-endian"
        point = int().from_bytes([rawdata[idx], rawdata[idx+1]],'little',signed=True)
        data.append(point/4096)  # data as 12 bit
    return data

def get_header():
    # first 4 bytes indicate the number of data bytes following
    header = send(':DATA:WAVE:SCREen:HEAD?')
    header = header[4:].tobytes().decode('utf-8')
    return header

def save_data(ffname,data):
    f = open(ffname,'w')
    f.write('\n'.join(map(str, data)))
    f.close()

print(get_id())
header = get_header()
data = get_data(1)
save_data('Osci.dat',data)
### end of code

Result: (using gnuplot)

enter image description here

Helsinki answered 5/2, 2020 at 9:46 Comment(2)
Well done for solving this. I know a LOT of SCPI instruments have really crummy bugs like what you describe.Hersh
Where did you find info about the :DATA command? Where can I learn more about this?Political
M
2

First note that @igrinis posted a video showing what you want to reach.

(As stated by @igrinis:) For the second value in read(...), you are right in theory. Good thing is that practically often enough you can request way longer answers. So try e.g. requesting 256 bytes and look if that fixes your current code.

If that does not solve your issue:

You can try to have a second PC/Laptop around with software from e.g. the manufacturer that is capable of communicating with the device, and use Wireshark (with USBPcap installed) to read the device communication. The USB bulk data transmitted and received is written in Wiresharks "Leftover Capture Data" field. By looking at that you can compare what your script sends and how it should look like to spot mistakes. You can add it as a column to the list of packets by right-clicking and selecting "Apply as column". Your problem might e.g. be the encoding of your command to big or little endian.

Documentation for PyUSB:

[Update] Added hints to a great comment that already gave some of the answers and more.

Milkmaid answered 3/2, 2020 at 14:32 Comment(4)
thank you for your reply. I got a reply from the vendor. I guess the problem is a different one. I will update the question.Helsinki
@Helsinki The vendor replying sounds great. I would btw. not constantly edit one question, since there are already answers for your current/old question(s) here that other people might want to search and read in context. Just create a new question if a new question arises for you. That is the advantage of a Q&A system like Stackoverflow over forum-thread-based systems ^^Milkmaid
@Helsinki Also, don't forget to vote for answers that are helpful and comment your criticism to answers that are not helpful, and if your original question is answered, vote for the best answer as the correct one. Good luck!Milkmaid
seems that I had to debug the vendor's documentation. That's why the given and well-intentioned answers actually could not help solving the problem.Helsinki
S
1

msg = '*IDN?'

This is not a complete SCPI command: It is missing the newline \n character at the end.

That is also why the device was not able to send an answer via USB.

Stelliform answered 29/11, 2019 at 16:59 Comment(3)
Thank you for your hint. Argh!!! So stupid, in the documentation there is an example with msg='test', which gave the impression to me that the PyUSB module takes care about the line-end character. I can only test next week and will upvote & accept if it works.Helsinki
Sorry, no. Simply adding \n doesn't change anything with the timeout error. The documentation of the device manufacturer about communication is also pretty poor.Helsinki
Apparently, PyUSB takes care about end characters.Helsinki
C
1

Once you get the response from the device on *IDN? query you should be good to go. This is SCPI ;)

Try to send :CHAN1:SCAL 10v, and watch the display. It should change the vertical scale of channel 1 to 10V/div.

Watch this video, it will help you get a grip.

On your question about read() parameters. Quoting the PyUSB source:

def read(self, endpoint, size_or_buffer, timeout = None):
    r"""Read data from the endpoint.
    This method is used to receive data from the device. The endpoint
    parameter corresponds to the bEndpointAddress member whose endpoint
    you want to communicate with. The size_or_buffer parameter either
    tells how many bytes you want to read or supplies the buffer to
    receive the data (it *must* be an object of the type array).
    The timeout is specified in miliseconds.
    If the size_or_buffer parameter is the number of bytes to read, the
    method returns an array object with the data read. If the
    size_or_buffer parameter is an array object, it returns the number
    of bytes actually read.
    """

When the timeout is omitted, it is used the Device.default_timeout property as the operation timeout. Values are in milliseconds.

If you set the buffer size big enough, you will get only the bytes actually read. So your expectations are correct.

Cattier answered 31/1, 2020 at 20:26 Comment(2)
thank your for your suggestion. No, nothing will happen by sending :CHAN1:SCAL 10v or any other value. I got a response from the vendor. I will update the question.Helsinki
Sorry, outdated documentation rpovided by the vendor was the problem. See my answer. By the way, I don't understand why people are creating (or watching) a 1 h video for something which should fit onto 2 pages of paper.Helsinki
S
0

SCPI is not enabled by default on OWON devices. According to the page#3 of the manual you need to send :SDSLSCPI# command to switch to the SCPI mode.

Sainfoin answered 4/12, 2019 at 21:5 Comment(2)
this doesn't change anything. The problem is in the line ret = dev.read(0x81, 23, 1000). It gives usb.core.USBError: [Errno None] b'libusb0-dll:err [_usb_reap_async] timeout error\n'. Seems to be no data to read.Helsinki
sorry, outdated/wrong documentation provided by the vendor was the problem. See my answer.Helsinki

© 2022 - 2024 — McMap. All rights reserved.