Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

An unofficial (simple) Python API for the nanoVNA device line

License

NotificationsYou must be signed in to change notification settings

LC-Linkous/nanoVNA_python

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

47 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

simple non-GUI Python interfacing and data saving for the NanoVNA. Includes examples

AN UNOFFICIAL Python Library for the NanoVNA Device Series

A Non-GUI Python API class for the NanoVNA series of devices. This repository uses official resources and documentation but is NOT endorsed by the official NanoVNA product or company. See thereferences section for further reading. See theofficial NanoVNA resources and theactive user group for device features.

There also exists several officially recognized resources:

This library,nanoVNA_python, is a non-GUI based library with access to low-level interfacing. It has been written as a companion to thetinySA_python library. Even though there is a lot of similarities between the devices, the library GitHub repositories are separate in order to make it clear which examples, tips, and documentation go to which device.

This library covers most of the documented commands for the NanoVNA device series. The documentation is sorted based on the serial command, with some provided usage examples. While some error checking exists in both the device and the library, it is not exhaustive. It is strongly suggested to read the official documentation before attempting to script with your device.

Done:

  • examples for common use and functionality
  • documentation for original command usage and library functions
  • some Debian-flavored Linux testing

Working on it:

  • filling in unfinished args and any new NanoVNA features
    • Ranges and device specs need to be added (or at least changable) for error detection.
  • An argparse option + some example scripts
  • Beginner notes, vocab, and some examples for common usage

Table of Contents

The NanoVNA Series of Devices

TheNanoVNA line of devices are a series of portable and pretty user-friendly vector network analyzer devices. There are several devices with different frequency ranges, so refer toofficial documentation to select one for your needs. There are also some very convincing knock-off devices, so ensure that you are purchasing an actual device from areputable vendor.

This device is often compared to the [tinySA series of devices]https://tinysa.org/. The NanoVNA series is a handheld vector network analyzer (VNA), which measures the S-parameters (loosely: a type of response of a device or antenna) over at different frequencies, while a spectrum analyzer measures the amplitude of RF signals at different frequencies. There's a lot of overlap with the use of both devices, but the measurements are very different. A signal generator (one of the features of the tinySA) is exactly what it sounds like - it generates a signal at a specific frequency or frequencies at a specified power level.

Official documentation can be found athttps://nanovna.com/. The official Wiki is going to be more up to date than this repo with new versions and features, and they also have links to GUI-based software. Several community projects also exist on GitHub.

There is also a very active NanoVNA community athttps://groups.io/g/nanovna-users/ exploring the device capabilities and its many features.

The end of this README will have some references and links to supporting material, but it is STRONGLY suggested to do some basic research and become familiar with your device before attempting to script or write code for it.

Requirements

This project requires numpy, pandas and pyserial.

Use 'pip install -r requirements.txt' to install the following dependencies:

pyserialnumpypandas

The above dependencies are only for the API interfacing of the nanoSA_python library. Additional dependencies should be installed if you are following the examples in this README. These can be installed with 'pip install -r test_requirements.txt':

pyserialnumpypandasmatplotlibpillowpyQt5

For anyone unfamiliar with using requirements files, or having issues with the libraries, these can also be installed manually in the terminal (we recommend a Python virtual environment) with:

pipinstallpyserialnumpypandasmatplotlibpillowpyQt5

pyQt5 is used withmatplotlib to draw the figures.pyQT5 needs to be installed in Linux systems to follow the examples included in nanoVNA_python, but is not needed on all Windows machines. Install both if you have doubts; they're small packages and commonly used.

Library Usage

This library is currently only available as the NanoVNA class in 'nanoVNA_python.py' in this repository. It is very much under development and missing some key error checking and handling. HOWEVER, ‘any’ error checking is currently more than the ‘no’ error checking provided by interfacing directly with the device. The code that is included in this repository has been tested on at least one NanoVNA device and is relatively stable.

Several usage examples are provided in theExample Implementations section, including working with the hardware and plotting results with matplotlib.

Error Handling

Some error handling has been implemented for the individual functions in this library, but not for the device configuration. Most functions have a list of acceptable formats for input, which is included in the documentation and thelibrary_help function. ThenanoVNA_help function will get output from the current version of firmware running on the connected tinySA device.

Detailed error messages can be returned by toggling 'verbose' on.

Example Implementations

This library was developed on Windows and has been lightly tested on Linux. The main difference (so far) has been in the permissions for first access of the serial port, but there may be smaller bugs in format that have not been detected yet.

Finding the Serial Port

To start, a serial connection between the NanoVNA and user PC device must be created. There are several ways to list available serial ports. The library supports some rudimentary autodetection, but if that does not work instructions in this section also support manual detection.

Autoconnection with the nanoVNA_python Library

The nanoVNA_python currently has some autodetection capabilities, but these are new and not very complex. If multiple devices have the same VID, then the first one found is used. If you are connecting multiple devices to a user PC, then it is suggested to connect them manually (for now). The NanoVNA and tinySA devices have the same VID and hardware identification for the serial ports.

# import nanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNA# create a new nanoVNA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)#detailed messagesnvna.set_error_byte_return(True)#get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port found and connected, then complete task(s) and disconnectifconnected_bool==True:print("device connected")msg=nvna.get_info()print(msg)nvna.disconnect()else:print("ERROR: could not connect to port")

Manually Finding a Port on Windows

  1. OpenDevice Manager, scroll down toPorts (COM & LPT), and expand the menu. There should be aCOM# port listing "USB Serial Device(COM #)". If your NanoVNA is set up to work with Serial, this will be it.

  2. This uses the pyserial library requirement already installed for this library. It probably also works on Linux systems, but has not been tested yet.

importserial.tools.list_portsports=serial.tools.list_ports.comports()forport,desc,hwidinports:print(f"Port:{port}, Description:{desc}, Hardware ID:{hwid}")

Example output for this method (on Windows) is as follows:

Port:COM4,Description:StandardSerialoverBluetoothlink (COM4),HardwareID:BTHENUM\{00001101-0000-1000-8000-00805F9B34FB}_LOCALMFG&0000\7&D0D1EE&0&000000000000_00000000Port:COM3,Description:StandardSerialoverBluetoothlink (COM3),HardwareID:BTHENUM\{00001101-0000-1000-8000-00805F9B34FB}_LOCALMFG&0002\7&D0D1EE&0&B8B3DC31CBA8_C00000000Port:COM22,Description:USBSerialDevice (COM10),HardwareID:USBVID:PID=0483:5740SER=400LOCATION=1-3

"COM22" is the port location of the NanoVNA that is used in the examples in this README.

Manually Finding a Port on Linux

importserial.tools.list_portsports=serial.tools.list_ports.comports()forport,desc,hwidinports:print(f"Port:{port}, Description:{desc}, Hardware ID:{hwid}")
TOBEADDED

This method identified the/dev/ttyACM0. Now, when attempting to use the autoconnection feature, the following error was initially returned:

[Errno13]couldnotopenport/dev/ttyACM0: [Errno13]Permissiondenied:'/dev/ttyACM0'

This was due to not having permission to access the port. In this case, this error was solved by opening a terminal and executingsudo chmod a+rw /dev/ttyACM0. Should this issue be persistent, other solutions related to user groups and access will need to be investigated.

Serial Message Return Format

This library returns strings as cleaned byte arrays. The command and first\r\n pair are removed from the front, and thech> is removed from the end of the NanoVNA serial return.

The original message format:

bytearray(b'info\r\nModel:        NanoVNA-F_V2\r\nFrequency:    50k ~ 3GHz\r\nBuild time:   Mar  2 2021 - 09:40:50 CST\r\nch>\r\n')

Cleaned version:

bytearray(b'Model:        NanoVNA-F_V2\r\nFrequency:    50k ~ 3GHz\r\nBuild time:   Mar  2 2021 - 09:40:50 CST\r')

Connecting and Disconnecting the Device

This example shows the process for initializing, opening the serial port, getting device info, and disconnecting.

# import nanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNA# create a new nanoVNA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)#detailed messagesnvna.set_error_byte_return(True)#get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port found and connected, then complete task(s) and disconnectifconnected_bool==True:print("device connected")msg=nvna.get_info()print(msg)nvna.disconnect()else:print("ERROR: could not connect to port")

Example output for this method is as follows:

bytearray(b'Model:        NanoVNA-F_V2\r\nFrequency:    50k ~ 3GHz\r\nBuild time:   Mar  2 2021 - 09:40:50 CST\r')

Toggle Error Messages

Currently, the following can be used to turn on or off returned error messages.

  1. the 'verbose' option. When enabled, detailed messages are printed out.
# detailed messages are ONnvna.set_verbose(True)# detailed messages are OFFnvna.set_verbose(False)
  1. the 'errorByte' option. When enabled, if there is an error with the command or configuration,b'ERROR' is returned instead of the defaultb''.
# when an error occurs, b'ERROR' is returnednvna.set_error_byte_return(True)# when an error occurs, the default b'' might be returnednvna.set_error_byte_return(False)

Device and Library Help

Thehelp return can be accessed via thehelp() function call. This will interface with the device directly and return functions based on the newest firmware, not information from thenanoVNA_python.py library.

nvna.help()

Thehelp command returns bytearray in the formatbytearray(b'commands:......')

Getting Data from Active Screen

See other sections for the following examples:

The most straight forward way to get data from an active screen is with thedata command. This will pull data from an active screen. It will not adjust the range or number of points before a read. If the range needs to be adjusted prior to a read, usescan instead.

# import NanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNAimporttimeimportserial# create a new tinySA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)#detailed messagesnvna.set_error_byte_return(True)#get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port closed, then return error messageifconnected_bool==False:print("ERROR: could not connect to port")else:# if port found and connected, then complete task(s) and disconnect# DATA gets the data on the screen# get the S11 datas11=nvna.get_s11_data()print(s11)# get the S21 datas21=nvna.get_s21_data()print(s21)nvna.resume()#resumenvna.disconnect()

Analysis of the Returned Data from the NanoVNA

This first example shows how to get measured data on the screen (usingdata) or to specify the read range and then measure withscan. Data returned will always be in a bytearray, but it will need to be converted in order to work with it.

# import NanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNAimporttimeimportserial# create a new tinySA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)#detailed messagesnvna.set_error_byte_return(True)#get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port closed, then return error messageifconnected_bool==False:print("ERROR: could not connect to port")else:# if port found and connected, then complete task(s) and disconnect# set up some parameters for the scan# NanoVNA takes freq in Hz, as intsstart=int(1e9)# 1 GHz, as an int.stop=int(3e9)# 3 GHz, as an int.# max number of points is 200, UP TO 201pts=200# SCAN can change range and number of pts# get the frequency valuess (the Y Axis of the screen)freq=nvna.get_scan_frequencies(start,stop,pts)print(freq)# get the S11 datas11=nvna.get_scan_s11(start,stop,pts)print(s11)# get the S21 datas21=nvna.get_scan_s21(start,stop,pts)print(s21)# DATA gets the data on the screen# get the S11 datas11=nvna.get_s11_data()print(s11)# get the S21 datas21=nvna.get_s21_data()print(s21)nvna.resume()#resumenvna.disconnect()

The requested frequencies are in the following format:

bytearray(b'1000000\r\n1020000\r\n1040000\r\n1060000\r\n1080000\r\n1100000\r\n1120000\r\n1140000\r\n1160000\r\n1180000\r\n1200000\r\n1220000\r\n1240000\r\n1260000\r\n1280000\r\n1300000\r\n1320000\r\n1340000\r\n1360000\r\n1380000\r\n1400000\r\n1420000\r\n1440000\r\n1460000\r\n1480000\r\n1500000\r\n1520000\r\n1540000\r\n1560000\r\n1580000\r\n1600000\r\n1620000\r\n1640000\r\n1660000\r\n1680000\r\n1700000\r\n1720000\r\n1740000\r\n1760000\r\n1780000\r\n1800000\r\n1820000\r\n1840000\r\n1860000\r\n1880000\r\n1900000\r\n1920000\r\n1940000\r\n1960000\r\n1980000\r\n2000000\r\n2020000\r\n2040000\r\n2060000\r\n2080000\r\n2100000\r\n2120000\r\n2140000\r\n2160000\r\n2180000\r\n2200000\r\n2220000\r\n2240000\r\n2260000\r\n2280000\r\n2300000\r\n2320000\r\n2340000\r\n2360000\r\n2380000\r\n2400000\r\n2420000\r\n2440000\r\n2460000\r\n2480000\r\n2500000\r\n2520000\r\n2540000\r\n2560000\r\n2580000\r\n2600000\r\n2620000\r\n2640000\r\n2660000\r\n2680000\r\n2700000\r\n2720000\r\n2740000\r\n2760000\r\n2780000\r\n2800000\r\n2820000\r\n2840000\r\n2860000\r\n2880000\r\n2900000\r\n2920000\r\n2940000\r\n2960000\r\n2980000\r\n3000000\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r\n0\r')

These frequencies represent where in the frequency range the measurements have been taken, and are returned inkHz. That is, the last reading before the padding 0-s start is '3000000'. This value is actually '3000000e3', or 3 GHz, not 3 MHz.

Returned S11 and S21 data are in the format:

bytearray(b'0.414528 0.623509\r\n0.512547 0.542835\r\n0.552637 0.489537\r\n0.602180 0.444314\r\n0.674851 0.374883\r\n0.721209 0.316875\r\n0.770192 0.216239\r\n0.790564 0.140702\r\n0.804490 0.037844\r\n0.802676 -0.086580\r\n0.784412 -0.188284\r\n0.757563 -0.260674\r\n0.718249 -0.342237\r\n0.664428 -0.416341\r\n0.617326 -0.480844\r\n0.568474 -0.533208\r\n0.492809 -0.581321\r\n0.460760 -0.616593\r\n0.384558 -0.668559\r\n0.314043 -0.682421\r\n0.245858 -0.719574\r\n0.194356 -0.714597\r\n0.118920 -0.738018\r\n0.066108 -0.737820\r\n0.010847 -0.744120\r\n-0.045027 -0.754767\r\n-0.101323 -0.761777\r\n-0.172184 -0.763956\r\n-0.248799 -0.752455\r\n-0.322770 -0.734302\r\n-0.386569 -0.712150\r\n-0.465589 -0.678363\r\n-0.538067 -0.640787\r\n-0.614912 -0.575165\r\n-0.675761 -0.518579\r\n-0.719431 -0.459212\r\n-0.762334 -0.381576\r\n-0.804731 -0.287837\r\n-0.828170 -0.205799\r\n-0.847075 -0.126603\r\n-0.857135 -0.029673\r\n-0.849052 0.066982\r\n-0.834792 0.182195\r\n-0.805081 0.263328\r\n-0.756838 0.355655\r\n-0.699059 0.442736\r\n-0.627638 0.513114\r\n-0.526945 0.592252\r\n-0.424149 0.632364\r\n-0.313906 0.655601\r\n-0.181712 0.653672\r\n-0.125724 0.615638\r\n-0.053355 0.591899\r\n0.009786 0.579131\r\n0.073450 0.570709\r\n0.166064 0.538408\r\n0.250542 0.481270\r\n0.324851 0.387106\r\n0.371485 0.280715\r\n0.392284 0.164942\r\n0.372584 0.048407\r\n0.321884 -0.054561\r\n0.248533 -0.125397\r\n0.153565 -0.178265\r\n0.061536 -0.194453\r\n-0.013059 -0.182995\r\n-0.085104 -0.155251\r\n-0.138454 -0.125033\r\n-0.184962 -0.078549\r\n-0.225650 -0.029733\r\n-0.239032 0.054426\r\n-0.229694 0.126419\r\n-0.195660 0.184412\r\n-0.150762 0.222993\r\n-0.089336 0.251350\r\n-0.039771 0.261744\r\n0.009027 0.261116\r\n0.065524 0.260008\r\n0.110400 0.234943\r\n0.140959 0.225260\r\n0.193066 0.202232\r\n0.260522 0.159201\r\n0.309473 0.106962\r\n0.343722 0.029135\r\n0.362852 -0.053262\r\n0.367468 -0.136931\r\n0.371292 -0.227675\r\n0.363786 -0.314572\r\n0.318734 -0.411707\r\n0.260611 -0.519830\r\n0.184881 -0.579277\r\n0.113375 -0.670024\r\n-0.006515 -0.709672\r\n-0.088562 -0.746757\r\n-0.209147 -0.763733\r\n-0.318236 -0.751811\r\n-0.424684 -0.736606\r\n-0.525702 -0.713903\r\n-0.608816 -0.662751\r\n-0.673664 -0.612937\r\n-0.737695 -0.554410\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r\n0.000000 0.000000\r')

The last value before the padding is -0.737695 -0.554410. The first number is thereal part of the signal, and the second number is theimaginary part of the signal. All signals are returned in two parts for each measurement.

Saving Screen Images

Thecapture() function can be used to capture the screen and output it to an image file. Note that the screen size varies by device. The library itself does not have a function for saving to an image (requires an additional library), but examples and the CLI wrapper have this functionality.

This example truncates the last hex value, so a single paddingx00 value has been added. This will eventually be investigated, but it's not hurting the output right now.

# import NanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNA# imports FOR THE EXAMPLEimportnumpyasnpfromPILimportImageimportstructdefconvert_data_to_image(data_bytes,width,height):# calculate the expected data sizeexpected_size=width*height*2# 16 bits per pixel (BGR565), 2 bytes per pixel# error checkingiflen(data_bytes)<expected_size:print(f"Data size is too small. Expected{expected_size} bytes, got{len(data_bytes)} bytes.")iflen(data_bytes)==expected_size-1:print("Data size is 1 byte smaller than expected. Adding 1 byte of padding.")data_bytes.append(0)else:returneliflen(data_bytes)>expected_size:data_bytes=data_bytes[:expected_size]print("Data is larger than the expected size. truncating. check data.")num_pixels=width*height# Unpack as little-endian 16-bit valuesx=struct.unpack(f"<{num_pixels}H",data_bytes)arr=np.array(x,dtype=np.uint32)# Convert RGB565 to RGBA# The NanoVNA uses BGR565 format.# This is a difference from the tinySA_python library which used RGB565. This is pulled out# into variables to make it clearer where/what the switch is.blue= ((arr&0xF800)>>11)*255//31# Blue in high bits (15-11)green= ((arr&0x07E0)>>5)*255//63# Green in middle bits (10-5)red= (arr&0x001F)*255//31# Red in low bits (4-0)# Combine into RGBA format (Alpha = 255 for opaque)arr_rgba=0xFF000000+ (red<<16)+ (green<<8)+blue# reshape array to match the image dimensionsarr_rgba=arr_rgba.reshape((height,width))# create and save the imageimg=Image.frombuffer('RGBA', (width,height),arr_rgba.tobytes(),'raw','RGBA',0,1)img.save("example_screen_capture_demo.png")img.show()# create a new tinySA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)#detailed messagesnvna.set_error_byte_return(True)#get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port found and connected, then complete task(s) and disconnectifconnected_bool==True:print("device connected")msg=nvna.get_info()print(msg)# get the trace datadata_bytes=nvna.capture()# Printed out for fun.# You do NOT need to print this to use itprint(data_bytes)# disconnect device since we're not using itnvna.disconnect()# processing after disconnect (just for this example)# test with 800x480 resolution for NanoVNA-F V2convert_data_to_image(data_bytes,800,480)else:print("ERROR: could not connect to port")

Capture of On-screen Trace Data

Capture On-Screen Trace Data from 1 GHz to 3 GHzz

Plotting Data with Matplotlib

Example 1: Plot Trace Data

This example plots the last/current sweep of data from the NanoVNA device.scan() gets the trace data.byteArrayToNumArray(byteArr) takes in the returned trace data and frequencyinformation and converts them to arrays that are then plotted usingmatplotlib

This example has 4 subplots because there is a lot of information returned with each sweep of the NanoVNA. The top, left plot shows the real and imaginary parts of the signal. This is the data as it is returned directly from the NanoVNA device. The top, right plot shows the calculated magnitude data. The bottom plots are the calculated phase response and Smith Chart, on the left and right, respectively.

# import NanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNA# imports FOR THE EXAMPLEimportnumpyasnpimportmatplotlib.pyplotaspltdefconvert_s11_data_to_arrays(start,stop,pts,data):# Convert the raw device S11 data to frequency and S11 arrays.# given the format of the data, this is assuming the data# contains PAIRS of values (real/imag or mag/phase).# Create frequency arrayfreq_arr=np.linspace(start,stop,pts)# Parse data into pairs of valueslines=data.decode('utf-8').split('\n')real_parts= []imag_parts= []forlineinlines:ifline.strip():# Skip empty linesvalues=line.split()iflen(values)>=2:try:real_val=float(values[0])imag_val=float(values[1])# Skip zero pairs (padding data)ifreal_val!=0.0orimag_val!=0.0:real_parts.append(real_val)imag_parts.append(imag_val)exceptValueError:continue# Skip malformed lines# Convert to numpy arraysreal_arr=np.array(real_parts)imag_arr=np.array(imag_parts)# Calculate derived values# If data is real/imaginary components:magnitude_db=20*np.log10(np.sqrt(real_arr**2+imag_arr**2))phase_deg=np.degrees(np.arctan2(imag_arr,real_arr))# Adjust frequency array to match actual data lengthactual_pts=len(real_arr)ifactual_pts!=pts:freq_arr=np.linspace(start,stop,actual_pts)returnfreq_arr,real_arr,imag_arr,magnitude_db,phase_deg# create a new tinySA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)# detailed messagesnvna.set_error_byte_return(True)# get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port closed, then return error messageifconnected_bool==False:print("ERROR: could not connect to port")else:# if port found and connected, then complete task(s) and disconnect# set scan valuesstart=int(1e9)# 1 GHzstop=int(3e9)# 3 GHzpts=200# sample points. MAX 201outmask=2# get measured data (y axis)# scandata_bytes=nvna.scan(start,stop,pts,outmask)print("Raw data received:")print(data_bytes)nvna.resume()# resume so screen isn't still frozennvna.disconnect()# processing after disconnect# This is typical for the examples, but does not need to be done# if you are still using the device or collecting data.# convert data to arraysfreq_arr,real_arr,imag_arr,magnitude_db,phase_deg=convert_s11_data_to_arrays(start,stop,pts,data_bytes)# Create subplots for comprehensive S11 visualization# this is different from the tinySA plots, which only showed the frequency data overlapped# because we are collecting more data with each sweep.# Data has been sorted into 4 plots# The Antenna used in data collection is a 2.4 GHz monopolefig, ((ax1,ax2), (ax3,ax4))=plt.subplots(2,2,figsize=(12,10))# Plot 1: Real and Imaginary partsax1.plot(freq_arr/1e9,real_arr,'b-',label='Real',linewidth=1.5)ax1.plot(freq_arr/1e9,imag_arr,'r-',label='Imaginary',linewidth=1.5)ax1.set_xlabel("Frequency (GHz)")ax1.set_ylabel("S11 Components")ax1.set_title("S11 Real and Imaginary Components")ax1.legend()ax1.grid(True,alpha=0.3)# Plot 2: Magnitude in dBax2.plot(freq_arr/1e9,magnitude_db,'g-',linewidth=1.5)ax2.set_xlabel("Frequency (GHz)")ax2.set_ylabel("S11 Magnitude (dB)")ax2.set_title("S1 Magnitude Response")ax2.grid(True,alpha=0.3)# Plot 3: Phaseax3.plot(freq_arr/1e9,phase_deg,'m-',linewidth=1.5)ax3.set_xlabel("Frequency (GHz)")ax3.set_ylabel("S11 Phase (degrees)")ax3.set_title("S11 Phase Response")ax3.grid(True,alpha=0.3)# Plot 4: Smith Chart representation (simplified)ax4.scatter(real_arr,imag_arr,c=freq_arr/1e9,cmap='viridis',s=20)ax4.set_xlabel("Real Part")ax4.set_ylabel("Imaginary Part")ax4.set_title("S11 Complex Plane (Simplified Smith Chart)")ax4.grid(True,alpha=0.3)ax4.axis('equal')# Add colorbar for frequency referencecbar=plt.colorbar(ax4.collections[0],ax=ax4)cbar.set_label('Frequency (GHz)')plt.tight_layout()plt.show()# Print summary statisticsprint(f"\nData Summary:")print(f"Number of valid data points:{len(real_arr)}")print(f"Frequency range:{freq_arr[0]/1e9:.3f} -{freq_arr[-1]/1e9:.3f} GHz")print(f"S_{11} Magnitude range:{np.min(magnitude_db):.2f} to{np.max(magnitude_db):.2f} dB")print(f"S_{11} Phase range:{np.min(phase_deg):.1f} to{np.max(phase_deg):.1f} degrees")

Plot of On-screen Trace Data

Plotted On-Screen Trace Data of a Frequency Sweep from 1 GHz to 3 GHz

Example 2: Plot a Static Waterfall using SCAN and Calculated Frequencies

This example uses thescan() read to get the data over a specified number of reads and then display it in the four plots described in Example 1, above. Data is exported to a specified .csv for logging. The scan can be interrupted at any time in the terminal (typically ctrl + C).

# import nanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNA# imports FOR THE EXAMPLEimportcsvimportnumpyasnpimportmatplotlib.pyplotaspltimporttimefromdatetimeimportdatetimedefconvert_s11_data_to_arrays(start,stop,pts,data):# Convert the raw device S11 data to frequency and S11 arrays.# given the format of the data, this is assuming the data# contains PAIRS of values (real/imag or mag/phase).# Create frequency arrayfreq_arr=np.linspace(start,stop,pts)# Parse data into pairs of valueslines=data.decode('utf-8').split('\n')real_parts= []imag_parts= []forlineinlines:ifline.strip():# Skip empty linesvalues=line.split()iflen(values)>=2:try:real_val=float(values[0])imag_val=float(values[1])# Skip zero pairs (padding data)ifreal_val!=0.0orimag_val!=0.0:real_parts.append(real_val)imag_parts.append(imag_val)exceptValueError:continue# Skip malformed lines# Convert to numpy arraysreal_arr=np.array(real_parts)imag_arr=np.array(imag_parts)# Calculate derived valuesmagnitude_db=20*np.log10(np.sqrt(real_arr**2+imag_arr**2))phase_deg=np.degrees(np.arctan2(imag_arr,real_arr))# Adjust frequency array to match actual data lengthactual_pts=len(real_arr)ifactual_pts!=pts:freq_arr=np.linspace(start,stop,actual_pts)returnfreq_arr,real_arr,imag_arr,magnitude_db,phase_degdefcollect_s11_waterfall_data(nvna,start,stop,pts,outmask,num_scans,scan_interval):# collects the scans for the waterfall plotwaterfall_real= []# 2D array for real componentswaterfall_imag= []# 2D array for imaginary componentswaterfall_magnitude= []# 2D array for magnitude in dBwaterfall_phase= []# 2D array for phase in degreestimestamps= []freq_arr=Noneprint(f"Collecting{num_scans} S11 scans with{scan_interval}s intervals...")foriinrange(num_scans):print(f"Scan{i+1}/{num_scans}")# Perform scandata_bytes=nvna.scan(start,stop,pts,outmask)# Convert to arraysiffreq_arrisNone:freq_arr,real_arr,imag_arr,mag_arr,phase_arr=convert_s11_data_to_arrays(start,stop,pts,data_bytes)else:_,real_arr,imag_arr,mag_arr,phase_arr=convert_s11_data_to_arrays(start,stop,pts,data_bytes)# Store data and timestampwaterfall_real.append(real_arr)waterfall_imag.append(imag_arr)waterfall_magnitude.append(mag_arr)waterfall_phase.append(phase_arr)timestamps.append(datetime.now())# Wait before next scan (except for last scan)ifi<num_scans-1:time.sleep(scan_interval)return (freq_arr,np.array(waterfall_real),np.array(waterfall_imag),np.array(waterfall_magnitude),np.array(waterfall_phase),timestamps)defplot_s11_waterfall(freq_arr,waterfall_real,waterfall_imag,waterfall_magnitude,waterfall_phase,timestamps,start,stop):# Create figure with subplotsfig, ((ax1,ax2), (ax3,ax4))=plt.subplots(2,2,figsize=(16,12))# Create time array for y-axistime_arr=np.arange(len(timestamps))freq_mesh,time_mesh=np.meshgrid(freq_arr,time_arr)# Plot 1: S11 Magnitude waterfallim1=ax1.pcolormesh(freq_mesh/1e9,time_mesh,waterfall_magnitude,shading='nearest',cmap='viridis')ax1.set_xlabel('Frequency (GHz)')ax1.set_ylabel('Scan Number')ax1.set_title(f'S11 Magnitude Waterfall:{start/1e9:.1f} -{stop/1e9:.1f} GHz')cbar1=plt.colorbar(im1,ax=ax1)cbar1.set_label('S11 Magnitude (dB)')# Plot 2: S11 Phase waterfallim2=ax2.pcolormesh(freq_mesh/1e9,time_mesh,waterfall_phase,shading='nearest',cmap='plasma')ax2.set_xlabel('Frequency (GHz)')ax2.set_ylabel('Scan Number')ax2.set_title('S11 Phase Waterfall')cbar2=plt.colorbar(im2,ax=ax2)cbar2.set_label('S11 Phase (degrees)')# Plot 3: Latest S11 Magnitude scanax3.plot(freq_arr/1e9,waterfall_magnitude[-1],'b-',linewidth=1.5)ax3.set_xlabel('Frequency (GHz)')ax3.set_ylabel('S11 Magnitude (dB)')ax3.set_title('Latest S11 Magnitude Scan')ax3.grid(True,alpha=0.3)# Plot 4: Latest S11 Phase scanax4.plot(freq_arr/1e9,waterfall_phase[-1],'r-',linewidth=1.5)ax4.set_xlabel('Frequency (GHz)')ax4.set_ylabel('S11 Phase (degrees)')ax4.set_title('Latest S11 Phase Scan')ax4.grid(True,alpha=0.3)plt.tight_layout()returnfig# create a new nanoVNA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)# detailed messagesnvna.set_error_byte_return(True)# get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port closed, then return error messageifconnected_bool==False:print("ERROR: could not connect to port")else:# if port found and connected, then complete task(s) and disconnecttry:# set scan valuesstart=int(1e9)# 1 GHzstop=int(3e9)# 3 GHzpts=200# sample pointsoutmask=2# get measured data (y axis)# waterfall parametersnum_scans=20# number of scans to collectscan_interval=1.0# seconds between scans# collect waterfall data        (freq_arr,waterfall_real,waterfall_imag,waterfall_magnitude,waterfall_phase,timestamps)=collect_s11_waterfall_data(nvna,start,stop,pts,outmask,num_scans,scan_interval)print("S11 data collection complete!")# resume and disconnectnvna.resume()# resume so screen isn't still frozennvna.disconnect()# processing after disconnectprint("Creating S11 waterfall plots...")# create waterfall plotfig=plot_s11_waterfall(freq_arr,waterfall_real,waterfall_imag,waterfall_magnitude,waterfall_phase,timestamps,start,stop)# Save data to CSVfilename="s11_waterfall_sample.csv"# Create CSV with comprehensive S11 datawithopen(filename,'w',newline='')ascsvfile:writer=csv.writer(csvfile)# Write header rowheader= ['Scan_Number','Timestamp']forfreqinfreq_arr:header.extend([f'{freq:.0f}_Real',f'{freq:.0f}_Imag',f'{freq:.0f}_Mag_dB',f'{freq:.0f}_Phase_deg'])writer.writerow(header)# Write data rowsforiinrange(len(timestamps)):row= [i+1,timestamps[i].strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]]forjinrange(len(freq_arr)):row.extend([f'{waterfall_real[i][j]:.6f}',f'{waterfall_imag[i][j]:.6f}',f'{waterfall_magnitude[i][j]:.3f}',f'{waterfall_phase[i][j]:.2f}'                    ])writer.writerow(row)print(f"S11 waterfall data saved to{filename}")print(f"CSV contains{len(timestamps)} scans with{len(freq_arr)} frequency points each")print(f"Each frequency point includes: Real, Imaginary, Magnitude (dB), Phase (deg)")# Statisticsprint(f"\nScan Statistics:")print(f"Frequency range:{freq_arr[0]/1e9:.3f} -{freq_arr[-1]/1e9:.3f} GHz")print(f"S11 Magnitude range:{np.min(waterfall_magnitude):.2f} to{np.max(waterfall_magnitude):.2f} dB")print(f"S11 Phase range:{np.min(waterfall_phase):.1f} to{np.max(waterfall_phase):.1f} degrees")# show plotplt.show()exceptKeyboardInterrupt:print("\nScan interrupted by user")nvna.resume()nvna.disconnect()exceptExceptionase:print(f"Error occurred:{e}")nvna.resume()nvna.disconnect()

Waterfall Plot for SCAN Data Over 20 Readings

Waterfall Plot for SCAN Data Over 20 Readings

Example 3: Plot a Realtime Waterfall using SCAN and Calculated Frequencies

This example uses thescan() read to get the data directly from the NanoVNA device. After each read, the four plots on thematplotlib figure are updated. The scan can be interrupted at any time by closing the figure window.

# import nanoVNA libraryfromsrc.nanoVNA_pythonimportnanoVNA# imports FOR THE EXAMPLEimportnumpyasnpimportmatplotlib.pyplotaspltimportmatplotlib.animationasanimationfromcollectionsimportdequeimporttimefromdatetimeimportdatetimeimportthreadingimportqueuedefconvert_s11_data_to_arrays(start,stop,pts,data):# Convert the raw device S11 data to frequency and S11 arrays.# given the format of the data, this is assuming the data# contains PAIRS of values (real/imag or mag/phase).# Create frequency arrayfreq_arr=np.linspace(start,stop,pts)# Parse data into pairs of valueslines=data.decode('utf-8').split('\n')real_parts= []imag_parts= []forlineinlines:ifline.strip():values=line.split()iflen(values)>=2:try:real_val=float(values[0])imag_val=float(values[1])# Skip zero pairs (padding data)ifreal_val!=0.0orimag_val!=0.0:real_parts.append(real_val)imag_parts.append(imag_val)exceptValueError:continue# Convert to numpy arraysreal_arr=np.array(real_parts)imag_arr=np.array(imag_parts)# Calculate derived valuesmagnitude_db=20*np.log10(np.sqrt(real_arr**2+imag_arr**2))phase_deg=np.degrees(np.arctan2(imag_arr,real_arr))# Adjust frequency array to match actual data lengthactual_pts=len(real_arr)ifactual_pts!=pts:freq_arr=np.linspace(start,stop,actual_pts)returnfreq_arr,real_arr,imag_arr,magnitude_db,phase_degclassLiveS11Plotter:def__init__(self,nvna,start,stop,pts,outmask,max_history=50):self.nvna=nvnaself.start=startself.stop=stopself.pts=ptsself.outmask=outmaskself.max_history=max_history# Data storageself.freq_arr=Noneself.magnitude_history=deque(maxlen=max_history)self.phase_history=deque(maxlen=max_history)self.real_history=deque(maxlen=max_history)self.imag_history=deque(maxlen=max_history)self.timestamps=deque(maxlen=max_history)# Threading for data acquisitionself.data_queue=queue.Queue()self.running=Falseself.data_thread=None# Current data for single-trace plotsself.current_magnitude=Noneself.current_phase=Noneself.current_real=Noneself.current_imag=Nonedefdata_acquisition_thread(self):#background thread for continuous data acquisitionwhileself.running:try:# Get scan datadata_bytes=self.nvna.scan(self.start,self.stop,self.pts,self.outmask)# Convert to arraysfreq_arr,real_arr,imag_arr,mag_arr,phase_arr=convert_s11_data_to_arrays(self.start,self.stop,self.pts,data_bytes)# Put data in queue for main threadself.data_queue.put({'freq':freq_arr,'real':real_arr,'imag':imag_arr,'magnitude':mag_arr,'phase':phase_arr,'timestamp':datetime.now()                })time.sleep(0.15)# Small delay to prevent overwhelming the device# this might need to be tuned based on the device and how many points are takenexceptExceptionase:print(f"Data acquisition error:{e}")breakdefstart_acquisition(self):# start the threadself.running=Trueself.data_thread=threading.Thread(target=self.data_acquisition_thread)self.data_thread.daemon=Trueself.data_thread.start()defstop_acquisition(self):# stop the threadself.running=Falseifself.data_thread:self.data_thread.join()defupdate_plots(self,frame):# Get all available data from queuewhilenotself.data_queue.empty():try:data=self.data_queue.get_nowait()# Store frequency array (first time only)ifself.freq_arrisNone:self.freq_arr=data['freq']# Update current dataself.current_magnitude=data['magnitude']self.current_phase=data['phase']self.current_real=data['real']self.current_imag=data['imag']# Add to historyself.magnitude_history.append(data['magnitude'])self.phase_history.append(data['phase'])self.real_history.append(data['real'])self.imag_history.append(data['imag'])self.timestamps.append(data['timestamp'])exceptqueue.Empty:break# Clear all plotsforaxin [ax1,ax2,ax3,ax4]:ax.clear()ifself.freq_arrisnotNoneandself.current_magnitudeisnotNone:# Plot 1: Current S11 Magnitudeax1.plot(self.freq_arr/1e9,self.current_magnitude,'b-',linewidth=1.5)ax1.set_xlabel('Frequency (GHz)')ax1.set_ylabel('S11 Magnitude (dB)')ax1.set_title('Live S11 Magnitude')ax1.grid(True,alpha=0.3)# Plot 2: Current S11 Phaseax2.plot(self.freq_arr/1e9,self.current_phase,'r-',linewidth=1.5)ax2.set_xlabel('Frequency (GHz)')ax2.set_ylabel('S11 Phase (degrees)')ax2.set_title('Live S11 Phase')ax2.grid(True,alpha=0.3)# Plot 3: S11 Magnitude Waterfall (recent history)iflen(self.magnitude_history)>1:waterfall_mag=np.array(list(self.magnitude_history))time_arr=np.arange(len(waterfall_mag))freq_mesh,time_mesh=np.meshgrid(self.freq_arr,time_arr)im=ax3.pcolormesh(freq_mesh/1e9,time_mesh,waterfall_mag,shading='nearest',cmap='viridis')ax3.set_xlabel('Frequency (GHz)')ax3.set_ylabel('Time (scans ago)')ax3.set_title('S11 Magnitude History')# Plot 4: Complex plane (Smith chart style)ax4.scatter(self.current_real,self.current_imag,c=self.freq_arr/1e9,cmap='plasma',s=10,alpha=0.7)ax4.set_xlabel('Real Part')ax4.set_ylabel('Imaginary Part')ax4.set_title('S11 Complex Plane')ax4.grid(True,alpha=0.3)ax4.axis('equal')# Add timestampifself.timestamps:fig.suptitle(f'Live S11 Measurement -{self.timestamps[-1].strftime("%H:%M:%S")}',fontsize=14)# Main executionif__name__=="__main__":# create a new nanoVNA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)nvna.set_error_byte_return(True)# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()ifnotconnected_bool:print("ERROR: could not connect to port")else:try:print("Starting live S11 measurement...")print("Close the plot window to stop measurement")# Scan parametersstart=int(1e9)# 1 GHzstop=int(3e9)# 3 GHzpts=150# Reduced points for faster updatesoutmask=2# get measured data# Create plotterplotter=LiveS11Plotter(nvna,start,stop,pts,outmask,max_history=30)# Set up the plotfig, ((ax1,ax2), (ax3,ax4))=plt.subplots(2,2,figsize=(14,10))plt.subplots_adjust(hspace=0.3,wspace=0.3)# Start data acquisitionplotter.start_acquisition()# Create animationani=animation.FuncAnimation(fig,plotter.update_plots,interval=200,blit=False)# Show plot (this blocks until window is closed)plt.show()# Cleanupplotter.stop_acquisition()nvna.resume()nvna.disconnect()print("Live measurement stopped")exceptKeyboardInterrupt:print("\nMeasurement interrupted by user")nvna.resume()nvna.disconnect()exceptExceptionase:print(f"Error occurred:{e}")nvna.resume()nvna.disconnect()

Waterfall Plot for SCAN Data in Realtime

Waterfall Plot for SCAN Data in Realtime

Saving SCAN Data to CSV

# import NanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNA# imports FOR THE EXAMPLEimportcsvimportnumpyasnpdefconvert_s11_data_to_arrays(start,stop,pts,data):# Convert the raw data so that the frequency, real, and imaginary are all stored.# Create frequency arrayfreq_arr=np.linspace(start,stop,pts)# Parse data into pairs of values (real/imaginary)lines=data.decode('utf-8').split('\n')real_parts= []imag_parts= []forlineinlines:ifline.strip():values=line.split()iflen(values)>=2:try:real_val=float(values[0])imag_val=float(values[1])real_parts.append(real_val)imag_parts.append(imag_val)exceptValueError:continue# Convert to numpy arraysreal_arr=np.array(real_parts)imag_arr=np.array(imag_parts)# Adjust frequency array to match actual data lengthactual_pts=len(real_arr)ifactual_pts!=pts:freq_arr=np.linspace(start,stop,actual_pts)returnfreq_arr,real_arr,imag_arr# create a new nanoVNA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)#detailed messagesnvna.set_error_byte_return(True)#get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port closed, then return error messageifconnected_bool==False:print("ERROR: could not connect to port")else:# if port found and connected, then complete task(s) and disconnect# the S11 (return loss) data is the default collection for this tutorialprint("Connected to nanoVNA - collecting S11 data...")# set scan valuesstart=int(1e9)# 1 GHzstop=int(3e9)# 3 GHzpts=200# sample pointsoutmask=2# get measured data# scan for S11 datadata_bytes=nvna.scan(start,stop,pts,outmask)print(f"Received{len(data_bytes)} bytes of S11 data")nvna.resume()#resume so screen isn't still frozen# disconnect because in this example we're done reading from devicenvna.disconnect()# processing after disconnect# convert data to 3 arrays: frequency, real, imaginaryfreq_arr,real_arr,imag_arr=convert_s11_data_to_arrays(start,stop,pts,data_bytes)# Save the RAW data to CSVfilename="s11_raw_data.csv"# Write out to csv: frequency, real, imaginarywithopen(filename,'w',newline='')ascsvfile:writer=csv.writer(csvfile)# Write header rowwriter.writerow(['Frequency_Hz','S11_Real','S11_Imaginary'])# Write data rows (frequency, real, imaginary triplets)forfreq,real,imaginzip(freq_arr,real_arr,imag_arr):writer.writerow([f'{freq:.0f}',f'{real:.6f}',f'{imag:.6f}'])print(f"RAW S11 data saved to{filename}")print(f"Total:{len(freq_arr)} data points saved")

Accessing the NanoVNA Directly

In some cases, this library may not cover all possible command versions, or new features might not be included yet. The NanoVNA can be accessed directly using thecommand() function. There is NO ERROR CHECKING on this function. It takes the full argument, just as if arguments were entered on the command line.

# import NanoVNA library# (NOTE: check library path relative to script path)fromsrc.nanoVNA_pythonimportnanoVNA# create a new tinySA objectnvna=nanoVNA()# set the return message preferencesnvna.set_verbose(True)#detailed messagesnvna.set_error_byte_return(True)#get explicit b'ERROR' if error thrown# attempt to autoconnectfound_bool,connected_bool=nvna.autoconnect()# if port closed, then return error messageifconnected_bool==False:print("ERROR: could not connect to port")else:# if port found and connected, then complete task(s) and disconnect# get device infomsg=nvna.command("info")print(msg)# get device Idmsg=nvna.command("SN")print(msg)# scan example data# NOTE: scan REQUIRES integers,#  so the 1e9 fo 1 GHz notation does not work in a stringdata_bytes=nvna.command("scan 1000000000 2500000000 200 2")print(data_bytes)nvna.resume()#resumenvna.disconnect()

List of NanoVNA Commands and their Library Commands

Library functions are organized based on the command passed to the device. For example, any functions with shortcuts for using thesweep command will be grouped undersweep. This list and the following list in theAdditional Library Commands section describe the functions in this library.

This section is sorted by the NanoVNA commands, and includes:

  • A brief description of what the command does
  • What the original usage looked like
  • The nanoVNA_python function call, or calls if multiple options exist
  • Example return, or example format of return
  • Any additional notes about the usage

All of the listed commands are included in this API to some degree, but error checking may be incomplete.

Quick Link Table:

beepcalcaptureclearconfigcwfreqdataedelay
frequencieshelpinfoLCDLCD_IDlcdmarker
pauseportpwmrecallresetresolutionrestart
resumesavesaveconfigscanSNsweeptouchcal
touchtesttraceversion

beep

  • Description: Turn the beep on or off.
  • Original Usage:beep [on|off]
  • Direct Library Function Call:beep()
  • Example Return:b''
  • Alias Functions:
    • beep_on()
    • beep_off()
    • beep_time(val=Int)
  • CLI Wrapper Usage:
  • Notes: Beep plays a continious tone until it is turned off.

cal

  • Description: Work through the calibration process. Requires physical interaction with the device
  • Original Usage:cal [load|open|short|thru|done|reset|on|off]
  • Direct Library Function Call:cal(val=load|open|short|thru|done|reset|on|off|in)
  • Example Return: ``
  • Alias Functions:
    • cal_load() - calibrate with the load connector
    • cal_open() - calibrate with the open connector
    • cal_short()- calibrate with the short connector
    • cal_thru() - calibrate with cable connected to both ports
    • cal_done() - done with calibration
    • cal_reset() - reset calibration data. Do this BEFORE calibrating
    • cal_on() - start measuring with calibration, apply it to device
    • cal_off() - stop messing with calibration being applied to device
  • CLI Wrapper Usage:
  • Notes:
    • cal - no argument gets the calibration status
    • cal load - calibrate with the load connector. Hardware must be attached before calibration
    • cal open - calibrate with the open connector. Hardware must be attached before calibration
    • cal short - calibrate with the open connector. Hardware must be attached before calibration
    • cal thru - calibrate with cable connected to both ports. Hardware must be attached before calibration
    • cal done - complete the calibration
    • cal reset - reset calibration data. Do this BEFORE calibrating
    • cal on - start measuring with calibration, apply it to device
    • cal off - stop measuring with calibration being applied to device
    • cal in - this is in the documentation, but has no button on the NanoVNA-F V2. Might be a later feature.

capture

  • Description: Requests a screen dump to be sent in binary format of HEIGHTxWIDTH pixels of each 2 bytes
  • Original Usage:capture
  • Direct Library Function Call:capture()
  • Example Return:format:'\x00\x00\x00\x00\x00\x00\x00\...x00\x00\x00'
  • Alias Functions:
    • capture_screen()
  • CLI Wrapper Usage:
  • Notes: Data is in little-endian mode. Screen resolution is 800*480 for NanoVNA-F V2 and V3

clearconfig

  • Description: Resets the configuration data to factory defaults
  • Original Usage:clearconfig
  • Direct Library Function Call:clear_config()
  • Example Return:b'Config and all calibration data cleared. \r\n Do reset manually to take effect. Then do touch calibration and save.\r'
  • Alias Functions:
    • clear_and_reset()
  • CLI Wrapper Usage:
  • Notes: Requires password '1234'. Hardcoded. Other functions need to be used with this to complete the process. This causes the deletion of ALL settings and calibration. USE WITH CAUTION.

cwfreq

  • Description: Set the continuous wave (CW) pulse frequency
  • Original Usage:cwfreq {frequency in Hz}
  • Direct Library Function Call:cwfreq(val=Int|Freq in Hz)
  • Example Return: ``
  • Alias Functions:
    • set_cwfreq(val=Int|Freq in Hz)
  • CLI Wrapper Usage:
  • Notes:

data

  • Description: Gets the trace data for either S11 or S21, or the calibration.
  • Original Usage:data {0..6}
  • Direct Library Function Call:data(val=None|0|1|2|3|4|5|6)
  • Example Return:
    • data 0: format bytearray(b'-0.086151 0.957274\r\n1.013057 -0.197761\r\n0.944041 -0.348532\r\n0.858225 -0....\r\n-0.588183 -0.481691\r\n-0.646600 -0.426130\r')
    • data 7: out of bounds.bytearray(b'usage: data [array]\r')
  • Alias Functions:
    • get_s11_data()
    • get_s21_data()
    • get_load_cal_data()
    • get_open_cal_data()
    • get_short_cal_data()
    • get_thru_cal_data()
    • get_isolation_cal_data()
  • CLI Wrapper Usage:
  • Notes: S11 data is printed by default, but can be selected with input0 for S11 and input1 for S21. Higher values are returns for the calibration, according to some documenation online (see references).
    • data 0 - S11
    • data 1 - S21
    • data 2 - cal load
    • data 3 - cal open
    • data 4 - cal short
    • data 5 - cal thru
    • data 6 - cal isolation

edelay

  • Description: electrical delay. This lets users compensate for time delay caused by components attached to the port, such as cables, adapters, etc.
  • Original Usage:edelay id
  • Direct Library Function Call:edelay(val=None|Int|Float)
  • Example Return: empty bytearray
  • Alias Functions:
    • get_edelay()
    • set_edelay(val=Int|Float)
  • CLI Wrapper Usage:
  • Notes: No params should get the current edelay value. If there is 1 parameter, the delay is in nanoseconds.

frequencies

  • Description: Gets the frequencies used by the last sweep
  • Original Usage:frequencies
  • Direct Library Function Call:frequencies()
  • Example Return:b'1500000000\r\n... \r\n3000000000\r'
  • Alias Functions:
    • get_last_freqs()
  • CLI Wrapper Usage:
  • Notes:

help

  • Description: Gets a list of the available commands. Can be used to call NanoVNA help directly.
  • Original Usage:help
  • Direct Library Function Call:help(val=None|0|1)
  • Example Return:
bytearray(b'There are all commands\r\n    help:                lists all the registered commands\r\n    reset:               usage: reset\r\ncwfreq:usage:cwfreq {frequency(Hz)}\r\n    saveconfig:          usage: saveconfig\r\n    clearconfig:         usage: clearconfig {protection key}\r\ndata:usage:data [array]\r\n    frequencies:         usage: frequencies\r\n    port:                usage: port {1:S11 2:S21}\r\nscan:usage:scan {start(Hz)} [stop] [points] [outmask]\r\n    sweep:               usage: sweep {start(Hz)} [stop] [points]\r\n    touchcal:            usage: touchcal\r\n    touchtest:           usage: touchtest\r\n    pause:               usage: pause\r\n    resume:              usage: resume\r\ncal:usage:cal [load|open|short|thru|done|reset|on|off|in]\r\n    save:                usage: save {id}\r\n    recall:              usage: recall {id}\r\n    trace:               usage: trace {id}\r\n    marker:              usage: marker [n] [off|{index}]\r\n    edelay:              usage: edelay {id}\r\npwm:usage:pwm {0.0-1.0}\r\n    beep:                usage: beep on/off\r\n    lcd:                 usage: lcd X Y WIDTH HEIGHT FFFF\r\ncapture:usage:capture\r\n    version:             usage: Show NanoVNA version\r\n    info:                usage: NanoVNA-F info\r\n    SN:                  usage: NanoVNA-F ID\r\n    resolution:          usage: LCD resolution\r\n    LCD_ID:              usage: LCD ID\r')
  • Alias Functions:
    • NanoVNA_Help()
  • CLI Wrapper Usage:
  • Notes:

info

  • Description: Displays various software/firmware and hardware information
  • Original Usage:info
  • Direct Library Function Call:info()
  • Example Return:bytearray(b'Model: NanoVNA-F_V2\r\nFrequency: 50k ~ 3GHz\r\nBuild time: Mar 2 2021 - 09:40:50 CST\r')
  • Alias Functions:
    • get_info()
  • CLI Wrapper Usage:
  • Notes:

lcd

  • Description: Draw rectangles on the screen
  • Original Usage:lcd {X} {Y} {WIDTH} {HEIGHT} {FFFF}
  • Direct Library Function Call:lcd()
  • Example Return: empty bytearray
  • Alias Functions:
    • draw_rect(X=Int, Y=Int, W=Int, H=Int, COL=4 digit hex)
  • CLI Wrapper Usage:
  • Notes: Pause the screen first, and then draw. When the screen refreshes, the rectangle will be erased from left to right.

LCD_ID

  • Description: Get the ID of the LCD screen
  • Original Usage:LCD_ID
  • Direct Library Function Call:LCD_ID()
  • Example Return:bytearray(b'118200\r')
  • Alias Functions:
    • get_LCD_ID()
  • CLI Wrapper Usage:
  • Notes:

marker

  • Description: sets or dumps marker info
  • Original Usage:
    • marker [n] [on|off|{index}]
    • marker [n] [off|{index}]
    • marker [n] peak
  • Direct Library Function Call:marker(ID=Int|1..4, val="on"|"off"|"peak", idx=None|Int)
  • Example Return:
    • marker with no active markers:
      • bytearray(b'') - no active markers
      • bytearray(b'1 0 50\r\n2 40 0\r') - 2 active markers
    • marker 1 25 - marker 1, data reading point 25
      • bytearray(b'')
    • marker 1 - information about location
      • bytearray(b'1 25 2940000\r')
    • marker 1 peak - moves marker 1 to peak
      • bytearray(b'')
  • Alias Functions:
    • get_all_marker_positions()
    • get_marker_position(ID=Int)
    • set_marker_position(ID=Int, idx=Int) - idx is a point between 0-201, or whatever the limits of the reading for the device is if it's higher.
    • marker_peak(ID=Int)
    • marker_on(ID=Int)
    • marker_off(ID=Int)
  • CLI Wrapper Usage:
  • Notes:
    • Marker indexes depend on what the device lists. 0 i
    • marker no argument gets the attributes of the active markers.
    • marker {ID=integer} gets the attributes of that marker
    • The frequency must be within the selected sweep range mode.
    • Alias functions need error checking.

pause

  • Description: Pauses the sweep
  • Original Usage:pause
  • Direct Library Function Call:pause()
  • Example Return:bytearray(b'')
  • Alias Functions:
    • None
  • CLI Wrapper Usage:
  • Notes:

pwm

  • Description: Adjusts the PWM of the screen. This is screen brightness in this application.
  • Original Usage:pwm
  • Direct Library Function Call:pwm(val=Float|0.0-1.0)
  • Example Return:bytearray(b'')
  • Alias Functions:
    • set_screen_brightness(val=Float|0.0-1.0)
  • CLI Wrapper Usage:
  • Notes:
    • 0.1 is 10% brightness, etc.

recall

  • Description: Loads a previously stored calibration from the device
  • Original Usage: recall 0..4...6
  • Direct Library Function Call:recall(val=0|1|2|3|4|5|6)
  • Example Return: empty bytearray
  • Alias Functions:
    • None
  • CLI Wrapper Usage:
  • Notes: where 0 is the startup preset. No arguments prints the frequency range of the save results. Appears to be the same assave()

reset

  • Description: Resets the NanoVNA device.
  • Original Usage:reset
  • Direct Library Function Call:reset()
  • Example Return: empty bytearray, serial error message. depends on the system.
  • Alias Functions:
    • reset_device()
  • CLI Wrapper Usage:
  • Notes: Disconnects the serial too, so will need to reconnect to continue using.

restart

  • Description: Restarts the tinySA after the specified number of seconds
  • Original Usage:restart {seconds}
  • Direct Library Function Call:restart(val=0...)
  • Example Return: empty bytearray
  • Alias Functions:
    • restart_device()
    • cancel_restart()
  • CLI Wrapper Usage:
  • Notes:
    • Has not worked in testing on development DUT, but appears to work on some devices online.
    • 0 seconds stops the restarting process.

resolution

  • Description: Get the resolution of the LCD screen in pixels
  • Original Usage:resolution
  • Direct Library Function Call:resolution()
  • Example Return:bytearray(b'800,480\r')
  • Alias Functions:
    • get_resolution()
    • lcd_resolution()
  • CLI Wrapper Usage:
  • Notes: The screen resolution for the NanoVNA-F V2 and V3 is 800x480 pixels (width x height)

resume

  • Description: Resumes the sweep
  • Original Usage:resume
  • Direct Library Function Call:resume()
  • Example Return: empty bytearray
  • Alias Functions:
    • None
  • CLI Wrapper Usage:
  • Notes:

save

  • Description: Saves the current calibration data. Might save the current trace settings and marker position.
  • Original Usage:save 0..4...6
  • Direct Library Function Call:save(val=None|0..4..6)
  • Example Return: empty bytearray
  • Alias Functions:
    • None
  • CLI Wrapper Usage:
  • Notes: where 0 is the startup preset. No arguments prints the frequency range of the save results.

saveconfig

  • Description: Saves the device configuration data. This includes language and touch calibration.
  • Original Usage:saveconfig
  • Direct Library Function Call:save_config()
  • Example Return: empty bytearray
  • Alias Functions:
    • None
  • CLI Wrapper Usage:
  • Notes:

scan

  • Description: Performs a scan and optionally outputs the measured data
  • Original Usage:scan {start(Hz)} {stop(Hz)} [points] [outmask]
  • Direct Library Function Call:scan(start, stop, pts, outmask)
  • Example Return:
    • scan 1000000 2000000
      • No return. Sets the screen to scan between 1 MHz and 2 MHz.
    • scan 1000000 2000000 200
      • bytearray(b''). The outmask is0 by default, so there's no printout.
    • scan 1000000 2000000 200 1
      • bytearray(b'1000 \r\n1010 \r\n1020 \r\n1030 ... \r\n1980 \r\n1990 \r\n2000 ... \r\n0 \r'
      • The freuency points are returned, including a buffer of\r\n0
      • Values are returned inkHz
    • scan 1000000 2000000 200 1
      • bytearray(b'-1.134857 0.890570 \r\n-1.143237 0.889276... \r\n-1.411501 1.746581 \r\n-1.400607 1.754247 ... \r\n0.000000 0.000000 \r\n0.000000 0.000000 \r')
      • The S11 data is complex, with real and imaginary values. The padding is also complex.
    • scan 1000000 2000000 200 3
      • bytearray(b'1000 -1.124556 0.885832 \r\n1010 -1.133325 0.882054....\r\n0 0.000000 0.000000 \r\n0 0.000000 0.000000 \r')
      • When the frequency and S11 are returned, the data is thefreq in KHz and then the 2 parts of the complex signal. Thepadding is has 3 blank float values.
    • scan 1000000 2000000 200 7
      • bytearray(b'1000 -1.133184 0.885893 -0.000045 -0.000008 \r\n....\r\n0 0.000000 0.000000 0.000000 0.000000 \r'))
      • When the frequency, S11, and S21 are returned, the data is thefreq in KHz and then the 2 parts of the EACH complex signal, 4 signal parts in total. Thepadding has 5 blank float values.
    • Returns for invalid input:
      • bytearray(b'sweep points exceeds range 51 -201\r')
      • bytearray(b'frequency range is invalid\r')
  • Alias Functions:
    • scan_range(start=Int, stop=Int) - Scans. sets boundaries, does not return data
    • get_scan_frequencies(start=Int, stop=Int, pts=Int) - returns frequency data
    • get_scan_s11(start=Int, stop=Int, pts=Int) - returns S11 data
    • get_scan_freqs_s11(start=Int, stop=Int, pts=Int) - returns frequency and S11 data
    • get_scan_s21(start=Int, stop=Int, pts=Int) - returns S21 data
    • get_scan_freqs_s21(start=Int, stop=Int, pts=Int) - returns frequency and S21 data
    • get_scan_s11_s21(start=Int, stop=Int, pts=Int) - returns S11 and S21 data
    • get_scan_freqs_s11_s21(start=Int, stop=Int, pts=Int) - returns frequency, S11, and S21 data
  • CLI Wrapper Usage:
  • Notes:
    • start andstop are required values of frequencies are in Hz. Frequency returns are in kHz.
    • [points] is the number of points in the scan. The MAX points is device dependent. 201 is a common max, end not inclusive.
    • [outmask]
    • 0 = no printout
    • 1 = frequency vals
    • 2 = S11 of sweep points
    • 3 = frequency values & S11 of sweep pts
    • 4 = S21 of sweep pts
    • 5 = frequency values and & S21 data of sweep pts
    • 6 = S11 and S21 data of sweep points
    • 7 = frequency values, S11 and S21 data of sweep points

SN

  • Description: Get the unique serial number of the NanoVNA.
  • Original Usage:SN
  • Direct Library Function Call:SN(None)
  • Example Return:bytearray(b'63507468C\r')
  • Alias Functions:
    • get_SN()
  • CLI Wrapper Usage:
  • Notes:
    • NanoVNA-F ID (hint returned by help for DUT)
    • Example number changed from actual return. This is a 16-Bit serial number.

sweep

  • Description: Set sweep mode, frequency and points
  • Original Usage:
    • sweep {start(Hz)} {stop(Hz)} {points}
    • sweep {start|stop|center|span|cw|points} {freq(Hz)}
  • Direct Library Function Call:config_sweep(argName=start|stop|center|span|cw, val=Int|Float) ANDpreform_sweep(start, stop, pts)
  • Example Return:
    • empty bytearrayb''
    • bytearray(b'0 800000000 450\r')
  • Alias Functions:
    • get_sweep_params()
    • set_sweep_start(val=Int) - val is frequency in Hz
    • set_sweep_stop(val=Int) - val is frequency in Hz
    • set_sweep_center(val=Int) - val is frequency in Hz
    • set_sweep_span(val=Int) - val is frequency in Hz
    • set_sweep_cw(val=Int) - val is frequency in Hz
    • run_sweep(start=Int, stop=Int, pts=Int)
  • CLI Wrapper Usage:
  • Notes:
  • sweep with no arguments lists the current sweep settings, the frequencies specified should be within the permissible range.
  • sweep {integer} is interpreted as start frequency value.
  • sweep {integer} {integer} is interpreted as start and stop frequencies.
  • sweep {integer} {integer} {integer} is interpreted as start and stop frequencies, and the number of points.
  • sweep start {integer}: sets the start frequency of the sweep.
  • sweep stop {integer}: sets the stop frequency of the sweep.
  • sweep center {integer}: sets the center frequency of the sweep.
  • sweep span {integer}: sets the span of the sweep.
  • sweep cw {integer}: sets the continuous wave frequency (zero span sweep).

touchcal

  • Description: starts the touch calibration. Physical interaction with the device screen is required.
  • Original Usage:touchcal
  • Direct Library Function Call:touch_cal()
  • Example Return: empty bytearray
  • Alias Functions:
    • start_touch_cal()
  • CLI Wrapper Usage:
  • Notes: To save this,saveconfig must be used.

touchtest

  • Description: starts the touch test. When this command is used, the screen can be drawn on to check responsiveness.
  • Original Usage:touchtest
  • Direct Library Function Call:touch_test()
  • Example Return: empty bytearray
  • Alias Functions:
    • start_touch_test()
  • CLI Wrapper Usage:
  • Notes: There may be instructions on screen. Pause the screen first to see the marks made on the screen.

trace

  • Description: displays all or one trace information or sets trace related information. INCOMPLETE due to how many combinations are possible.
  • Original Usage:
    • trace [0|1|2|3|all] [off|logmag|linear|phase|smith|swr|polar|delay|refpos|channel] [value]
    • read the above astrace {ID} {format/action} {value/channel}
  • Direct Library Function Call:trace(ID=None|Int, trace_format=None|String, val=None|Int)
  • Example Return:
    • empty bytearrayb''
    • trace
      • bytearray(b'0 LOGMAG S11 1.000000 7.000000\r\n1 LOGMAG S21 1.000000 7.000000\r\n2 SMITH S11 1.000000 0.000000\r')
      • summary of all active traces
    • trace 0 - Information on Trace 0, S11
      • bytearray(b'0 LOGMAG S11\r')
    • trace 1 - Information on Trace 1, S21
      • bytearray(b'1 LOGMAG S21\r')
    • trace 0 linear 1 - Set trace 0 to linear, and set the input from chanel 1 (port 2)
      • bytearray(b'')
    • trace 0 linear - set the format of trace 0 to linear. This trace is by default on channel 0 (port 1)
      • bytearray(b'')
  • Alias Functions:
    • get_all_trace_attr()
    • get_trace_attr(ID=Int|"all")
    • trace_off(ID=Int|"all")
    • set_trace_logmag(ID=Int|"all")
    • set_trace_linear(ID=Int|"all")
    • set_trace_phase(ID=Int|"all")
    • set_trace_smith(ID=Int|"all")
    • set_trace_polar(ID=Int|"all")
    • set_trace_swr(ID=Int|"all",val=Float|Int)
    • set_trace_refposition(ID=Int|"all",val=Float|Int)
    • set_trace_delay(ID=Int|"all",val=Float|Int)
    • set_trace_channel(ID=Int|"all", val=Int)
  • CLI Wrapper Usage:
  • Notes:
    • NOTE: Traces can be turned OFF programatically, but not ON.
    • trace no args returns characteristics of active traces
    • trace {ID=integer} gets characteristics of that trace. using 'all' returns information for all traces.
    • trace {ID=integer} {str=logmag|phase|smith|linear|delay|swr} The ID sets the trace ID, and the second argument indicates what trace data format is returned.
    • trace {ID=integer} {off} turn the trace off. using 'all' will toggle all traces off. Traces cannot be turned 'on' with this method (conflicting documentation)
    • trace {ID=integer} {str=scale|refpos|channel} {val=int} the first argument is the ID of the trace. The second argument is an action toscale the trace by a numeric value, to set the reference position (refpos), or to set the channel. The third value specifies the value for the action.

version

  • Description: returns the firmware version
  • Original Usage:version
  • Direct Library Function Call:version()
  • Example Return:bytearray(b'0.2.1\r')
  • Alias Functions:
    • get_version()
  • CLI Wrapper Usage:
  • Notes:

Additional Library Functions for Advanced Use

command

  • Description: override library functions to run commands on the NanoVNA device directly.
  • Original Usage: None.
  • Direct Library Function Call:command(val=Str)
  • Example Usage::
    • example:command("version")
    • return:b'tinySA4_v1.4-143-g864bb27\r\nHW Version:V0.4.5.1.1 \r'
    • example:command("trace 1")
    • return:b'1: dBm 0.000000000 10.000000000 \r'
    • example:command("scan 150e6 200e6 5 2")
    • return:b'5.750000e+00 0.000000000 \r\n6.250000e+00 0.000000000 \r\n6.750000e+00 0.000000000 \r\n6.250000e+00 0.000000000 \r\n6.750000e+00 0.000000000 \r'
  • Example Return: command dependent
  • Alias Functions:
    • None
  • CLI Wrapper Usage:
  • Notes: If unfamiliar with device and operation, DO NOT USE THIS. There is no error checking and you will be interfacing with the NanoVNA device directly.

Unrecognized Commands that Appear in Documentation

These commands return the error messageCommand not recognised. from the device, not the library. They may appear in some versions of the firmware, but have not done anything to the DUT (NanoVNA-F V2).

  • bandwidth
  • color
  • dump
  • freq (frequency command is fine, but not shorter version)
  • power
  • scan_bin
  • smooth
  • tcxo
  • threshold
  • time
  • transform
  • vbat
  • zero
  • s21offset

Notes for Beginners

This is a brief section for anyone that might have jumped in with a bit too much ambition. It is highly suggested toread the manual.

Very useful, important documentation can be found at:

This library was modified from thetinySA_python library, and much of the material orignated there. These ARE NOT the same device, and have very different functionality, but some of the menus and commands are in the same format.

Vocab Check

Running list of words and acronyms that get tossed around with little to no explanation. Googling is recommended if you are not familiar with these terms as they're essential to understanding device usage.

  • AGC - Automatic Gain Control. This controls the overall dynamic range of the output when the input level(s) changes.
  • Baud - Baud, or baud rate. The rate that information is transferred in a communication channel. A baud rate of 9600 means a max of 9600 bits per second is transmitted.
  • DANL - Displayed Average Noise Level (DANL) refers to the average noise level displayed on a spectrum analyzer.
  • dB - dB (decibel) and dBm (decibel-milliwatts). dB (unitless) quantifies the ratio between two values, whereas dBm expresses the absolute power level (always relative to 1mW).
  • DUT - Device Under Test. Used here to refer to the singular device used while initially writing the API.
  • IF - Intermediate Frequency. A frequency to which a carrier wave is shifted as an intermediate step in transmission or reception -Wikipedia
  • LNA - Low Noise Amplifier. An electronic component that amplifies a very low-power signal without significantly degrading its signal-to-noise ratio -Wikipedia
  • Outmask - "outmask" refers to a setting that determines additional formatting or optional features that are not a core argument for a command.
    • For example, with thehop command, this value controls whether the device's output is a frequency or a level (power) signal. When the outmask is set to "1", the tinySA will output a frequency signal. When set to "2", the outmask will cause the tinySA to output a level signal, which is a measure of the signal's power or intensity
  • RBW - Resolution Bandwidth. Frequency span of the final filter (IF filter) that is applied to the input signal. Determines the fast-Fourier transform (FFT) bin size.
  • SDR* - Software Defined Radio. This is a software (computer) controlled radio system capable of sending and receiving RF signals. This type of device uses software to control functions such as modulation, demodulation, filtering, and other signal processing tasks. Messages (packets) can be sent and received with this device.
  • Signal Generator - used to create various types of repeating or non-repeating electronic signals for testing and evaluating electronic devices and systems.
  • S-parameters - are a way to characterize the behavior of radio frequency (RF) networks and components. They describe how much of a signal is reflected, transmitted or transferred between PORTS. In case of s11 (s-one-one), the return loss of a single antenna or port is measured. In s12 (s-one-two) or s21 (s-two-one), the interaction between ports is measured.
  • SA - Spectrum Analyzer. A device that measures the (power) magnitude of an input signal vs frequency. It shows signal as a spectrum.* This is what the 'SA' in 'tinySA' is!
  • SA - Signal Analyzer. A device that measures the properties of a single frequency signal. This can include power, magnitude, phase, and other features such as modulation.
  • SNA - Scalar Network Analyzer. A device that measures amplitude as it passes through the device. It can be used to determine gain, attenuation, or frequency response.
  • SWR - Standing Wave Ratio. SWR is an indication of how well an antenna is matched to a transmission line. A low SWR (close to 1:1) means there is minimal signal reflection and the power is being transmitted down the line eddiciently. A high SWR indicats and impedance mismatch between a DUT (or antenna, or network) and the transmission line, which in this case is internal to the NanoVNA.
  • VNA - Vector Network Analyzer. A device that measures the network parameters of electrical networks (typically, s-parameters). Can measure both measures both amplitude and phase properties. Thewiki article on network analyzers covers the topic in detail.

VNA vs. SA vs. LNA vs. SNA vs. SDR vs Signal Generator

aka “what am I looking at and did I buy the right thing?”

**tinySA Vs. NanoVNA **: The tinySA and NanoVNA look a lot alike, and have some similar code, but they are NOT the same device. They are designed to measure different things. The tinySA is a spectrum analyzer (SA) while the NanoVNA is a vector network analyzer (VNA). Both have signal generation capabilities (to an extent, as OUTPUT), but the tinySA (currently) has expanded features for generating signals. This library was made for the NanoVNA line of devices. There is some overlap with the tinySA, but there is a seperate library for that device attinySA_python.

SA - This one is context dependent. SA can mean either 'Spectrum Analyzer' (multiple frequencies) or 'Signal Analyzer' (single frequency). In the case of the tinySA it is 'Spectrum Analyzer' because multiple frequencies are being measured. A spectrum analyzer measures the magnitude of an external input signal vs frequency. It shows signal as a spectrum. The signal source does not need to be directly, physically connected to the SA, which allows for analysis of the wireless spectrum. This is the primary functionality of the tinySA, but it does have other features (such as signal generation).

VNA – a vector network analyzer (VNA) measures parameters such as s-parameters, impedance and reflection coefficient of a radio frequency (RF) device under test (DUT). A VNA is used to characterize the transmission and reflection properties of the DUT by generating a stimulus signal and then measuring the device's response. This can be used to characterize and measure the behavior of RF devices and individual components.*"What is a Vector Network Analyzer and How Does it Work?" - Tektronix*NanoVNA @ https://nanovna.com/

Signal Generator - A signal generator is used to create various types of repeating or non-repeating electronic signals for testing and evaluating electronic devices and systems. These can be used for calibration, design, or testing. Some signal generators will only have sine, square, or pulses, while others allow for AM and FM modulation (which begins to crossover into SDR territory)

SNA – a scalar network analyzer (SNA) measures amplitude as it passes through the device. It can be used to determine gain, attenuation, or frequency response. scalar network analyzers are less expensive than VNAs because they only measure the magnitude of the signal, not the phase.

SDR - a software defined radio (SDR) is a software (computer) controlled radio system capable of sending and receiving RF signals. This type of device uses software to control functions such as modulation, demodulation, filtering, and other signal processing tasks. Messages can be sent and received with this device.

LNA - an electronic component designed to amplify weak incoming signals with minimal noise addition, thus improving the signal-to-noise ratio (SNR). This hardware is often attached (or built in) to the devices above. It is not a stand-alone device for signal generation or analysis.

Calibration Setup

Some tips:

  • The open, sort, and load pieces should be finger tight. If the piece will not turn, there's a high risk of cross threading if it's forced.
  • The thru calibration should be done with the included cable. The cable impedance needs to match the impedance of the calibration kit. These are usually 50 ohms, but may be 75 ohms.
  • When the cable is attatched to port 1 and port 2, both connectors should be finger tight.

Some General NanoVNA Notes

These are notes collected from various references as this README documentation is built out. Some are obvious, some were not at the time.

FAQs

How should I be using this?

Right now, this library is set up as a class that can be added to a Python program. I recommend adding the contents of the./src folder on the same level (or lower) than the main program you're writing. If that doesn't make a lot of sense, check out thehello_world.py file in this repo. Because that example file is at the same level as the./src folder, we aren't dealing with path imports or checking. This works well for beginners, which is whom the bulk of the documentation is intended for.

Will this be made into a REAL Python library I can import into my project?

That's the plan! Right now, the core library is made of functions for directly interfacing with the NanoVNA series of devices. There are several examples in this README, which will be integrated into the core library as the error checking and features are stabilized. We're probably 3-6 months of development and testing away from an official release or library creation.

How often is this library updated?

This library is updated in spurts. June-August are going to be the most active development months, but it will get monthly-ish updates otherwise. Development is pretty constant on the backend, but only stable code is released publicly. Bug fixes will be addressed as they happen.

References

The original documentation for this project comes from the relatedtinySA_python library. That library was taken and applied to the NanoVNA to get a baseline of what commands were shared, and what might be new (to the library) for the NanoVNA. These ARE NOT the same device, and have very different functionality, but some of the menus and commands are in the same format.

The NanoVNA main site:

Licensing

The code in this repository has been released under GPL-2.0 for right now (and to have something in place rather than nothing). This licensing does NOT take priority over the official releases and the decisions of the NanoVNA team. This licensing does NOT take priority for any of their products, including the devices that can be used with this software.

This software is released AS-IS, meaning that there may be bugs (especially as it is under development).

This software is UNOFFICIAL, meaning that the NanoVNA team does not offer tech support for it, does not maintain it, and has no responsibility for any of the contents.

About

An unofficial (simple) Python API for the nanoVNA device line

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages


[8]ページ先頭

©2009-2025 Movatter.jp