1# NanoVNASaver 2# 3# A python program to view and export Touchstone data from a NanoVNA 4# Copyright (C) 2019, 2020 Rune B. Broberg 5# Copyright (C) 2020 NanoVNA-Saver Authors 6# 7# This program is free software: you can redistribute it and/or modify 8# it under the terms of the GNU General Public License as published by 9# the Free Software Foundation, either version 3 of the License, or 10# (at your option) any later version. 11# 12# This program is distributed in the hope that it will be useful, 13# but WITHOUT ANY WARRANTY; without even the implied warranty of 14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15# GNU General Public License for more details. 16# 17# You should have received a copy of the GNU General Public License 18# along with this program. If not, see <https://www.gnu.org/licenses/>. 19import logging 20import platform 21from collections import namedtuple 22from time import sleep 23from typing import List 24 25import serial 26from serial.tools import list_ports 27 28from NanoVNASaver.Hardware.AVNA import AVNA 29from NanoVNASaver.Hardware.NanoVNA import NanoVNA 30from NanoVNASaver.Hardware.NanoVNA_F import NanoVNA_F 31from NanoVNASaver.Hardware.NanoVNA_F_V2 import NanoVNA_F_V2 32from NanoVNASaver.Hardware.NanoVNA_H import NanoVNA_H 33from NanoVNASaver.Hardware.NanoVNA_H4 import NanoVNA_H4 34from NanoVNASaver.Hardware.NanoVNA_V2 import NanoVNA_V2 35from NanoVNASaver.Hardware.Serial import drain_serial, Interface 36from NanoVNASaver.Hardware.Sysctl import usb_vid_pid 37 38 39logger = logging.getLogger(__name__) 40 41USBDevice = namedtuple("Device", "vid pid name") 42 43USBDEVICETYPES = ( 44 USBDevice(0x0483, 0x5740, "NanoVNA"), 45 USBDevice(0x16c0, 0x0483, "AVNA"), 46 USBDevice(0x04b4, 0x0008, "S-A-A-2"), 47) 48RETRIES = 3 49TIMEOUT = 0.2 50WAIT = 0.05 51 52# The USB Driver for NanoVNA V2 seems to deliver an 53# incompatible hardware info like: 54# 'PORTS\\VID_04B4&PID_0008\\DEMO' 55# This function will fix it. 56def _fix_v2_hwinfo(dev): 57 if dev.hwid == r'PORTS\VID_04B4&PID_0008\DEMO': 58 dev.vid, dev.pid = 0x04b4, 0x0008 59 return dev 60 61 62# Get list of interfaces with VNAs connected 63def get_interfaces() -> List[Interface]: 64 interfaces = [] 65 # serial like usb interfaces 66 for d in list_ports.comports(): 67 if platform.system() == 'FreeBSD': 68 logger.debug("Found FreeBSD USB port %s", d.device) 69 vid_pid = usb_vid_pid(d.device) 70 d.vid = vid_pid[0] 71 d.pid = vid_pid[1] 72 73 for t in USBDEVICETYPES: 74 if d.vid != t.vid or d.pid != t.pid: 75 continue 76 logger.debug("Found %s USB:(%04x:%04x) on port %s", 77 t.name, d.vid, d.pid, d.device) 78 iface = Interface('serial', t.name) 79 iface.port = d.device 80 interfaces.append(iface) 81 return interfaces 82 83def get_VNA(iface: Interface) -> 'VNA': 84 # serial_port.timeout = TIMEOUT 85 86 logger.info("Finding correct VNA type...") 87 with iface.lock: 88 vna_version = detect_version(iface) 89 90 if vna_version == 'v2': 91 logger.info("Type: NanoVNA-V2") 92 return NanoVNA_V2(iface) 93 94 logger.info("Finding firmware variant...") 95 info = get_info(iface) 96 if info.find("AVNA + Teensy") >= 0: 97 logger.info("Type: AVNA") 98 return AVNA(iface) 99 if info.find("NanoVNA-H 4") >= 0: 100 logger.info("Type: NanoVNA-H4") 101 vna = NanoVNA_H4(iface) 102 return vna 103 if info.find("NanoVNA-H") >= 0: 104 logger.info("Type: NanoVNA-H") 105 vna = NanoVNA_H(iface) 106 return vna 107 if info.find("NanoVNA-F_V2") >= 0: 108 logger.info("Type: NanoVNA-F_V2") 109 return NanoVNA_F_V2(iface) 110 if info.find("NanoVNA-F") >= 0: 111 logger.info("Type: NanoVNA-F") 112 return NanoVNA_F(iface) 113 if info.find("NanoVNA") >= 0: 114 logger.info("Type: Generic NanoVNA") 115 return NanoVNA(iface) 116 logger.warning("Did not recognize NanoVNA type from firmware.") 117 return NanoVNA(iface) 118 119def detect_version(serial_port: serial.Serial) -> str: 120 data = "" 121 for i in range(RETRIES): 122 drain_serial(serial_port) 123 serial_port.write("\r".encode("ascii")) 124 sleep(0.05) 125 data = serial_port.read(128).decode("ascii") 126 if data.startswith("ch> "): 127 return "v1" 128 # -H versions 129 if data.startswith("\r\nch> "): 130 return "vh" 131 if data.startswith("2"): 132 return "v2" 133 logger.debug("Retry detection: %s", i + 1) 134 logger.error('No VNA detected. Hardware responded to CR with: %s', data) 135 return "" 136 137def get_info(serial_port: serial.Serial) -> str: 138 for _ in range(RETRIES): 139 drain_serial(serial_port) 140 serial_port.write("info\r".encode("ascii")) 141 lines = [] 142 retries = 0 143 while True: 144 line = serial_port.readline() 145 line = line.decode("ascii").strip() 146 if not line: 147 retries += 1 148 if retries > RETRIES: 149 return "" 150 sleep(WAIT) 151 continue 152 if line == "info": # suppress echo 153 continue 154 if line.startswith("ch>"): 155 logger.debug("Needed retries: %s", retries) 156 break 157 lines.append(line) 158 return "\n".join(lines) 159