From 253e2d35174c8cbbe8104e0d618bcf5288ace908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4berich?= Date: Fri, 21 Jun 2024 11:33:51 +0200 Subject: [PATCH] example script for using the live streaming servers --- .../SCPI_Examples/capture_live_data.py | 57 ++++++++++++++++++ .../UserManual/SCPI_Examples/libreVNA.py | 59 +++++++++++++++++++ 2 files changed, 116 insertions(+) create mode 100644 Documentation/UserManual/SCPI_Examples/capture_live_data.py diff --git a/Documentation/UserManual/SCPI_Examples/capture_live_data.py b/Documentation/UserManual/SCPI_Examples/capture_live_data.py new file mode 100644 index 00000000..88fceb59 --- /dev/null +++ b/Documentation/UserManual/SCPI_Examples/capture_live_data.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python3 + +import time +from libreVNA import libreVNA + +# Create the control instance +vna = libreVNA('localhost', 19542) + +# Quick connection check (should print "LibreVNA-GUI") +print(vna.query("*IDN?")) + +# Make sure we are connecting to a device (just to be sure, with default settings the LibreVNA-GUI auto-connects) +vna.cmd(":DEV:CONN") +dev = vna.query(":DEV:CONN?") +if dev == "Not connected": + print("Not connected to any device, aborting") + exit(-1) +else: + print("Connected to "+dev) + +# Capture live data as it is coming in. Stop acquisition for now +vna.cmd(":VNA:ACQ:STOP") + +# switch to VNA mode, set up the sweep parameters +print("Setting up the sweep...") +vna.cmd(":DEV:MODE VNA") +vna.cmd(":VNA:SWEEP FREQUENCY") +vna.cmd(":VNA:STIM:LVL -10") +vna.cmd(":VNA:ACQ:IFBW 100") +vna.cmd(":VNA:ACQ:AVG 1") +vna.cmd(":VNA:ACQ:POINTS 501") +vna.cmd(":VNA:FREQuency:START 10000000") +vna.cmd(":VNA:FREQuency:STOP 6000000000") + +sweepComplete = False + + +def callback(data): + global sweepComplete + print(data) + if data["pointNum"] == 500: + # this was the last point + vna.remove_live_callback(19000, callback) + sweepComplete = True + + +# Set up the connection for the live data +vna.add_live_callback(19000, callback) +print("Starting the sweep...") +vna.cmd(":VNA:ACQ:RUN") + +while not sweepComplete: + time.sleep(0.1) + +print("Sweep complete") + + diff --git a/Documentation/UserManual/SCPI_Examples/libreVNA.py b/Documentation/UserManual/SCPI_Examples/libreVNA.py index d14a0863..07e7df0b 100755 --- a/Documentation/UserManual/SCPI_Examples/libreVNA.py +++ b/Documentation/UserManual/SCPI_Examples/libreVNA.py @@ -2,6 +2,8 @@ import socket from asyncio import IncompleteReadError # only import the exception class import time +import threading +import json class SocketStreamReader: def __init__(self, sock: socket.socket, default_timeout=1): @@ -72,6 +74,7 @@ class libreVNA: def __init__(self, host='localhost', port=19542, check_cmds=True, timeout=1): self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.host = host try: self.sock.connect((host, port)) except: @@ -79,6 +82,8 @@ def __init__(self, host='localhost', port=19542, self.reader = SocketStreamReader(self.sock, default_timeout=timeout) self.default_check_cmds = check_cmds + self.live_threads = {} + self.live_callbacks = {} def __del__(self): self.sock.close() @@ -117,6 +122,60 @@ def get_status(self, timeout=None): if status < 0 or status > 255: raise Exception(f"*ESR? returned invalid value {status}.") return status + + def add_live_callback(self, port, callback): + # check if we already have a thread handling this connection + if not port in self.live_threads: + # needs to create the connection and thread first + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + sock.connect((self.host, port)) + except: + raise Exception("Unable to connect to streaming server at port {}. Make sure it is enabled.".format(port)) + + self.live_callbacks[port] = [callback] + self.live_threads[port] = threading.Thread(target=self.__live_thread, args=(sock, port)) + self.live_threads[port].start() + else: + # thread already existed, simply add to list + self.live_callbacks[port].append(callback) + + def remove_live_callback(self, port, callback): + if port in self.live_callbacks: + # remove all matching callbacks from the list + self.live_callbacks[port] = [cb for cb in self.live_callbacks[port] if cb != callback] + # if the list is now empty, the thread will exit + if len(self.live_callbacks) == 0: + self.live_threads[port].join() + del self.live_threads[port] + + def __live_thread(self, sock, port): + reader = SocketStreamReader(sock, default_timeout=0.1) + while len(self.live_callbacks[port]) > 0: + try: + line = reader.readline().decode().rstrip() + # determine whether this is data from the VNA or spectrum analyzer + data = json.loads(line) + if "Z0" in data: + # This is VNA data which has the imag/real parts of the S-parameters split into two float values. + # This was necessary because json does not support complex number. But python does -> convert back + # to complex + measurements = {} + for meas in data["measurements"].keys(): + if meas.endswith("_imag"): + # ignore + continue + name = meas.removesuffix("_real") + real = data["measurements"][meas] + imag = data["measurements"][name+"_imag"] + measurements[name] = complex(real, imag) + data["measurements"] = measurements + for cb in self.live_callbacks[port]: + cb(data) + except: + # ignore timeouts + pass + @staticmethod def parse_VNA_trace_data(data):