LiteVNA support (#754)

* feat: add LiteVNA64 detection
Based on https://github.com/NanoVNA-Saver/nanovna-saver/issues/534#issuecomment-1837198040 data
* chore: rename get_features
* chore: optimize disconnect method
* feat: go to idle mode after data reading
pull/764/head
Maxim Medvedev 2025-01-21 13:21:01 +01:00 zatwierdzone przez GitHub
rodzic 12f958bc94
commit 30a3dc88c4
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
23 zmienionych plików z 483 dodań i 102 usunięć

Wyświetl plik

@ -49,7 +49,7 @@ Current features
* Reading data from a NanoVNA -- Compatible devices: NanoVNA, NanoVNA-H,
NanoVNA-H4, NanoVNA-F, AVNA via Teensy
NanoVNA-H4, NanoVNA-F, AVNA via Teensy, LiteVNA
* Reading data from a TinySA
* Splitting a frequency range into multiple segments to increase resolution
(tried up to >10k points)

Wyświetl plik

@ -151,6 +151,11 @@ class SerialControl(Control):
def disconnect_device(self):
with self.interface.lock:
logger.info("Closing connection to %s", self.interface)
try:
self.app.vna.disconnect()
except IOError as exc:
logger.error("Unable to disconnect from VNA: %s", exc)
self.interface.close()
self.btn_toggle.setText("Connect to device")
self.btn_toggle.repaint()

Wyświetl plik

@ -38,6 +38,7 @@ from NanoVNASaver.Hardware.Serial import Interface, drain_serial
from NanoVNASaver.Hardware.SV4401A import SV4401A
from NanoVNASaver.Hardware.SV6301A import SV6301A
from NanoVNASaver.Hardware.TinySA import TinySA, TinySA_Ultra
from NanoVNASaver.Hardware.LiteVNA64 import LiteVNA64
from NanoVNASaver.Hardware.VNA import VNA
logger = logging.getLogger(__name__)
@ -67,6 +68,7 @@ NAME2DEVICE = {
"JNCRadio": JNCRadio_VNA_3G,
"SV4401A": SV4401A,
"SV6301A": SV6301A,
"LiteVNA64": LiteVNA64,
"Unknown": NanoVNA,
}
@ -149,6 +151,8 @@ def get_comment(iface: Interface) -> str:
vna_version = detect_version(iface)
if vna_version == "v2":
return "S-A-A-2"
elif vna_version == "lite_vna_64":
return "LiteVNA64"
logger.info("Finding firmware variant...")
info = get_info(iface)
@ -191,7 +195,7 @@ def detect_version(serial_port: serial.Serial) -> str:
if data.startswith("\r\n?\r\nch> "):
return "vh"
if data.startswith("2"):
return "v2"
return "lite_vna_64" if LiteVNA64.is_lite_vna_64(serial_port) else "v2"
logger.debug("Retry detection: %s", i + 1)
logger.error("No VNA detected. Hardware responded to CR with: %s", data)
return ""

Wyświetl plik

@ -31,7 +31,7 @@ class JNCRadio_VNA_3G(NanoVNA):
name = "JNCRadio_VNA_3G"
screenwidth = 800
screenheight = 480
valid_datapoints = (501, 11, 101, 1001)
valid_datapoints = [501, 11, 101, 1001]
sweep_points_min = 11
sweep_points_max = 1001

Wyświetl plik

@ -0,0 +1,275 @@
import logging
import platform
from struct import iter_unpack, pack, unpack
from time import sleep
from PyQt6.QtGui import QImage, QPixmap, QColor
from serial import Serial, SerialException
from NanoVNASaver.Hardware.Serial import Interface
from ..utils.version import Version
from .NanoVNA_V2 import (
_ADDR_DEVICE_VARIANT,
_ADDR_FW_MAJOR,
_ADDR_FW_MINOR,
_ADDR_HARDWARE_REVISION,
_ADDR_RAW_SAMPLES_MODE,
_ADF4350_TXPOWER_DESC_MAP,
_CMD_READ,
_CMD_READ2,
_CMD_WRITE,
WRITE_SLEEP,
NanoVNA_V2,
)
if platform.system() != "Windows":
pass
logger = logging.getLogger(__name__)
EXPECTED_HW_VERSION = Version.build(2, 2, 0)
EXPECTED_FW_VERSION = Version.build(2, 2, 0)
_ADDR_VBAT_MILIVOLTS = 0x5C
_ADDR_SCREENSHOT = 0xEE
SUPPORTED_PIXEL_FORMAT = 16
class ScreenshotData:
header_size = 2 + 2 + 1
def __init__(self, width: int, height: int, pixel_size: int):
self.width = width
self.height = height
self.pixel_size = pixel_size
self.data = bytes()
def data_size(self) -> int:
return self.width * self.height * int(self.pixel_size / 8)
def __repr__(self) -> str:
return f"{self.width}x{self.height} {self.pixel_size}bits ({self.data_size()} Bytes)"
@staticmethod
def from_header(header_data: bytes) -> "ScreenshotData":
logger.debug("Screenshot header: %s", header_data)
width, height, depth = unpack("<HHB", header_data)
return ScreenshotData(width, height, depth)
@staticmethod
def rgb565_to_888(rgb565: int) -> tuple[int, int, int]:
# Extract red, green, and blue components
r = (rgb565 & 0xF800) >> 11
g = (rgb565 & 0x07E0) >> 5
b = rgb565 & 0x001F
# Scale to 8-bit values
r = (r * 527 + 23) >> 6
g = (g * 259 + 33) >> 6
b = (b * 527 + 23) >> 6
return r, g, b
def get_rgb888_data(self) -> bytes:
result = bytearray()
for rgb565 in iter_unpack(">H", self.data):
result.extend(self.rgb565_to_888(rgb565[0]))
return bytes(result)
class LiteVNA64(NanoVNA_V2):
name = "LiteVNA-64"
valid_datapoints = [
51,
101,
201,
401,
801,
1024,
1601,
3201,
4501,
6401,
12801,
25601,
]
screenwidth = 480
screenheight = 320
sweep_points_max = 65535
sweep_max_freq_Hz = 6300e6
def __init__(self, iface: Interface):
super().__init__(iface)
self.datapoints = 1024
def read_fw_version(self) -> Version:
with self.serial.lock:
return LiteVNA64._get_fw_revision_serial(self.serial)
def init_features(self) -> None:
# VBat state will be added dynamicly in get_features()
self.features.add("Customizable data points")
self.features.add("Screenshots")
# TODO: more than one dp per freq
self.features.add("Multi data points")
# TODO review this part, which was copy-pasted from NanoVNA_V2
self.features.add("Set Average")
self.features.add("Set TX power partial")
# Can only set ADF4350 power, i.e. for >= 140MHz
# See https://groups.io/g/liteVNA/message/318 for more details
self.txPowerRanges = [
(
(140e6, self.sweep_max_freq_Hz),
[_ADF4350_TXPOWER_DESC_MAP[value] for value in (3, 2, 1, 0)],
),
]
def get_features(self) -> set[str]:
result = set(self.features)
result.add(f"Vbat: {self.read_vbat()}V")
return result
def read_vbat(self) -> str:
with self.serial.lock:
cmd = pack("<BB", _CMD_READ2, _ADDR_VBAT_MILIVOLTS)
self.serial.write(cmd)
sleep(WRITE_SLEEP)
# in a more predictive way
resp = self.serial.read(2)
vbat = int.from_bytes(resp, "little") / 1000.0
logger.debug("Vbat: %sV", vbat)
return f"{vbat}"
@staticmethod
def _get_major_minor_version_serial(
cmd_major_version: int, cmd_minor_version: int, serial: Serial
) -> Version:
cmd = pack(
"<BBBB", _CMD_READ, cmd_major_version, _CMD_READ, cmd_minor_version
)
serial.write(cmd)
sleep(WRITE_SLEEP)
# in a more predictive way
resp = serial.read(2)
if len(resp) != 2: # noqa: PLR2004
logger.error("Timeout reading version registers. Got: %s", resp)
raise IOError("Timeout reading version registers")
return Version.build(resp[0], resp[1])
@staticmethod
def _get_fw_revision_serial(serial: Serial) -> Version:
result = LiteVNA64._get_major_minor_version_serial(
_ADDR_FW_MAJOR, _ADDR_FW_MINOR, serial
)
logger.debug("Firmware version: %s", result)
return result
@staticmethod
def _get_hw_revision_serial(serial: Serial) -> Version:
result = LiteVNA64._get_major_minor_version_serial(
_ADDR_DEVICE_VARIANT, _ADDR_HARDWARE_REVISION, serial
)
logger.debug(
"Hardware version ({device_variant}.{hardware_revision}): %s",
result,
)
return result
@staticmethod
def is_lite_vna_64(serial: Serial) -> bool:
hw_version = LiteVNA64._get_hw_revision_serial(serial)
fw_version = LiteVNA64._get_fw_revision_serial(serial)
return (
hw_version == EXPECTED_HW_VERSION
and fw_version == EXPECTED_FW_VERSION
)
def disconnect(self):
self._exit_usb_mode()
super().disconnect()
def _exit_usb_mode(self) -> None:
with self.serial.lock:
self.serial.write(
pack("<BBB", _CMD_WRITE, _ADDR_RAW_SAMPLES_MODE, 2)
)
sleep(WRITE_SLEEP)
def readValues(self, value) -> list[complex]:
result = super().readValues(value)
self._exit_usb_mode()
return result
def setSweep(self, start, stop):
# Device loose these value after going to idle mode
# Do not try to cache them locally
step = (stop - start) / (self.datapoints - 1)
self.sweepStartHz = start
self.sweepStepHz = step
logger.info(
"NanoVNAV2: set sweep start %d step %d",
self.sweepStartHz,
self.sweepStepHz,
)
self._updateSweep()
def getScreenshot(self) -> QPixmap:
logger.debug("Capturing screenshot...")
self.serial.timeout = 8
if self.connected():
try:
screenshot = self._get_screenshot()
if screenshot.pixel_size != SUPPORTED_PIXEL_FORMAT:
logger.warning(
"Unsupported %d screenshot pixel format!",
screenshot.pixel_size,
)
return QPixmap()
image = QImage(
screenshot.get_rgb888_data(),
screenshot.width,
screenshot.height,
QImage.Format.Format_RGB888,
)
logger.debug("Screenshot was captured")
return QPixmap(image)
except SerialException as exc:
logger.exception(
"Exception while capturing screenshot: %s", exc
)
logger.debug("Unable to get screenshot")
return QPixmap()
def _get_screenshot(self) -> ScreenshotData:
with self.serial.lock:
self.serial.write(pack("<BBB", _CMD_WRITE, _ADDR_SCREENSHOT, 0))
sleep(WRITE_SLEEP)
result = ScreenshotData.from_header(
self.serial.read(ScreenshotData.header_size)
)
logger.debug("Screenshot format: %s. Loading data...", result)
result.data = self.serial.read(result.data_size())
return result

Wyświetl plik

@ -25,7 +25,7 @@ from PyQt6.QtGui import QImage, QPixmap
from NanoVNASaver.Hardware.Serial import Interface, drain_serial
from NanoVNASaver.Hardware.VNA import VNA
from NanoVNASaver.Version import Version
from ..utils import Version
logger = logging.getLogger(__name__)
@ -38,7 +38,7 @@ class NanoVNA(VNA):
def __init__(self, iface: Interface):
super().__init__(iface)
self.sweep_method = "sweep"
self.read_features()
self.init_features()
logger.debug("Setting initial start,stop")
self.start, self.stop = self._get_running_frequencies()
self.sweep_max_freq_Hz = 300e6
@ -111,13 +111,13 @@ class NanoVNA(VNA):
elif self.sweep_method == "scan":
list(self.exec_command(f"scan {start} {stop} {self.datapoints}"))
def read_features(self):
super().read_features()
if self.version >= Version("0.7.1"):
def init_features(self) -> None:
super().init_features()
if self.version >= Version.parse("0.7.1"):
logger.debug("Using scan mask command.")
self.features.add("Scan mask command")
self.sweep_method = "scan_mask"
elif self.version >= Version("0.2.0"):
elif self.version >= Version.parse("0.2.0"):
logger.debug("Using new scan command.")
self.features.add("Scan command")
self.sweep_method = "scan"

Wyświetl plik

@ -27,7 +27,7 @@ class NanoVNA_H4(NanoVNA_H):
name = "NanoVNA-H4"
screenwidth = 480
screenheight = 320
valid_datapoints = (101, 11, 51, 201, 401)
valid_datapoints = [101, 11, 51, 201, 401]
def __init__(self, iface: Interface):
super().__init__(iface)
@ -42,4 +42,4 @@ class NanoVNA_H4(NanoVNA_H):
# if self.readFirmware().find("DiSlord") > 0:
# self.features.add("Customizable data points")
# logger.info("VNA has 201 datapoints capability")
# self.valid_datapoints = (201, 11, 51,101)
# self.valid_datapoints = [201, 11, 51, 101]

Wyświetl plik

@ -23,7 +23,7 @@ from time import sleep
from NanoVNASaver.Hardware.Serial import Interface
from NanoVNASaver.Hardware.VNA import VNA
from NanoVNASaver.Version import Version
from ..utils import Version
if platform.system() != "Windows":
import tty
@ -69,7 +69,7 @@ _ADF4350_TXPOWER_DESC_REV_MAP = {
class NanoVNA_V2(VNA): # noqa: N801
name = "NanoVNA-V2"
valid_datapoints = (101, 11, 51, 201, 301, 501, 1023)
valid_datapoints = [101, 11, 51, 201, 301, 501, 1023]
screenwidth = 320
screenheight = 240
@ -89,7 +89,7 @@ class NanoVNA_V2(VNA): # noqa: N801
raise IOError("Device is in DFU mode")
if "S21 hack" in self.features:
self.valid_datapoints = (101, 11, 51, 201, 301, 501, 1021)
self.valid_datapoints = [101, 11, 51, 201, 301, 501, 1021]
self.sweepStartHz = 200e6
self.sweepStepHz = 1e6
@ -100,19 +100,19 @@ class NanoVNA_V2(VNA): # noqa: N801
def getCalibration(self) -> str:
return "Unknown"
def read_features(self):
def init_features(self) -> None:
self.features.add("Customizable data points")
# TODO: more than one dp per freq
self.features.add("Multi data points")
self.board_revision = self.read_board_revision()
if self.board_revision >= Version("2.0.4"):
if self.board_revision >= Version.parse("2.0.4"):
self.sweep_max_freq_Hz = 4400e6
else:
self.sweep_max_freq_Hz = 3000e6
if self.version <= Version("1.0.1"):
if self.version <= Version.parse("1.0.1"):
logger.debug("Hack for s21 oddity in first sweeppoint")
self.features.add("S21 hack")
if self.version >= Version("1.0.2"):
if self.version >= Version.parse("1.0.2"):
self.features.update({"Set TX power partial", "Set Average"})
# Can only set ADF4350 power, i.e. for >= 140MHz
self.txPowerRanges = [
@ -227,7 +227,7 @@ class NanoVNA_V2(VNA): # noqa: N801
def resetSweep(self, start: int, stop: int):
self.setSweep(start, stop)
def _read_version(self, cmd_0: int, cmd_1: int):
def _read_version(self, cmd_0: int, cmd_1: int) -> Version:
cmd = pack("<BBBB", _CMD_READ, cmd_0, _CMD_READ, cmd_1)
with self.serial.lock:
self.serial.write(cmd)
@ -238,14 +238,14 @@ class NanoVNA_V2(VNA): # noqa: N801
if len(resp) != 2: # noqa: PLR2004
logger.error("Timeout reading version registers. Got: %s", resp)
raise IOError("Timeout reading version registers")
return Version(f"{resp[0]}.0.{resp[1]}")
return Version.build(resp[0], 0, resp[1])
def readVersion(self) -> "Version":
def read_fw_version(self) -> Version:
result = self._read_version(_ADDR_FW_MAJOR, _ADDR_FW_MINOR)
logger.debug("readVersion: %s", result)
logger.debug("Firmware Version: %s", result)
return result
def read_board_revision(self) -> "Version":
def read_board_revision(self) -> Version:
result = self._read_version(
_ADDR_DEVICE_VARIANT, _ADDR_HARDWARE_REVISION
)

Wyświetl plik

@ -31,7 +31,7 @@ class SV4401A(NanoVNA):
name = "SV4401A"
screenwidth = 1024
screenheight = 600
valid_datapoints = (501, 101, 1001)
valid_datapoints = [501, 101, 1001]
sweep_points_min = 101
sweep_points_max = 1001

Wyświetl plik

@ -31,7 +31,7 @@ class SV6301A(NanoVNA):
name = "SV6301A"
screenwidth = 1024
screenheight = 600
valid_datapoints = (501, 101, 1001)
valid_datapoints = [501, 101, 1001]
sweep_points_min = 101
sweep_points_max = 1001

Wyświetl plik

@ -17,7 +17,7 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import logging
from threading import Lock
from threading import RLock
import serial
@ -47,7 +47,7 @@ class Interface(serial.Serial):
self.port = None
self.baudrate = 115200
self.timeout = 0.05
self.lock = Lock()
self.lock = RLock()
def __str__(self):
return f"{self.port} ({self.comment})"

Wyświetl plik

@ -25,7 +25,7 @@ from PyQt6.QtGui import QImage, QPixmap
from NanoVNASaver.Hardware.Serial import Interface, drain_serial
from NanoVNASaver.Hardware.VNA import VNA
from NanoVNASaver.Version import Version
from ..utils import Version
logger = logging.getLogger(__name__)
@ -34,7 +34,7 @@ class TinySA(VNA):
name = "tinySA"
screenwidth = 320
screenheight = 240
valid_datapoints = (290,)
valid_datapoints = [290]
def __init__(self, iface: Interface):
super().__init__(iface)
@ -132,7 +132,7 @@ class TinySA_Ultra(TinySA): # noqa: N801
name = "tinySA Ultra"
screenwidth = 480
screenheight = 320
valid_datapoints = (450, 51, 101, 145, 290)
valid_datapoints = [450, 51, 101, 145, 290]
hardware_revision = None
def __init__(self, iface: Interface):

Wyświetl plik

@ -23,7 +23,7 @@ from typing import Iterator
from PyQt6 import QtGui
from NanoVNASaver.Hardware.Serial import Interface, drain_serial
from NanoVNASaver.Version import Version
from ..utils import Version
logger = logging.getLogger(__name__)
@ -53,27 +53,29 @@ def _max_retries(bandwidth: int, datapoints: int) -> int:
class VNA:
name = "VNA"
valid_datapoints = (101, 51, 11)
valid_datapoints: list[int] = [101, 51, 11]
wait = 0.05
SN = "NOT SUPPORTED"
sweep_points_max = 101
sweep_points_min = 11
# Must be initilized in child classes
sweep_max_freq_Hz = 0.0
def __init__(self, iface: Interface):
self.serial = iface
self.version = Version("0.0.0")
self.features = set()
self.version = Version.parse("0.0.0")
self.features: set[str] = set()
self.validateInput = False
self.datapoints = self.valid_datapoints[0]
self.bandwidth = 1000
self.bw_method = "ttrftech"
self.sweep_max_freq_Hz = None
# [((min_freq, max_freq), [description]]. Order by increasing
# [((min_freq, max_freq), [description])]. Order by increasing
# frequency. Put default output power first.
self.txPowerRanges = []
self.txPowerRanges: list[tuple[tuple[float, float], list[str]]] = []
if self.connected():
self.version = self.readVersion()
self.read_features()
self.version = self.read_fw_version()
self.init_features()
logger.debug("Features: %s", self.features)
# cannot read current bandwidth, so set to highest
# to get initial sweep fast
@ -121,7 +123,7 @@ class VNA:
break
yield line
def read_features(self):
def init_features(self) -> None:
result = " ".join(self.exec_command("help")).split()
logger.debug("result:\n%s", result)
if "capture" in result:
@ -176,7 +178,7 @@ class VNA:
def connected(self) -> bool:
return self.serial.is_open
def getFeatures(self) -> set[str]:
def get_features(self) -> set[str]:
return self.features
def getCalibration(self) -> str:
@ -208,10 +210,10 @@ class VNA:
logger.debug("VNA done reading %s (%d values)", value, len(result))
return result
def readVersion(self) -> "Version":
def read_fw_version(self) -> Version:
result = list(self.exec_command("version"))
logger.debug("result:\n%s", result)
return Version(result[0])
logger.debug("Firmware Version:\n%s", result)
return Version.parse(result[0])
def setSweep(self, start, stop):
list(self.exec_command(f"sweep {start} {stop} {self.datapoints}"))

Wyświetl plik

@ -702,6 +702,9 @@ class NanoVNASaver(QWidget):
Defaults.cfg.gui.splitter_sizes = self.splitter.saveState()
Defaults.store(self.settings, Defaults.cfg)
# Dosconnect connected devices and release serial port
self.serial_control.disconnect_device()
a0.accept()
sys.exit()

Wyświetl plik

@ -26,7 +26,7 @@ from urllib import error, request
from PyQt6 import QtCore, QtGui, QtWidgets
from NanoVNASaver.About import INFO_URL, LATEST_URL, TAGS_KEY, TAGS_URL
from NanoVNASaver.Version import Version
from ..utils import Version
from NanoVNASaver.Windows.Defaults import make_scrollable
logger = logging.getLogger(__name__)
@ -147,7 +147,7 @@ class AboutWindow(QtWidgets.QWidget):
line = ln.decode("utf-8")
found_latest_version = TAGS_KEY in line
if found_latest_version:
latest_version = Version(
latest_version = Version.parse(
re.search(r"(\d+\.\d+\.\d+)", line).group()
)
break

Wyświetl plik

@ -150,7 +150,7 @@ class DeviceSettingsWindow(QtWidgets.QWidget):
self.label["calibration"].setText(self.app.vna.getCalibration())
self.label["SN"].setText(self.app.vna.SN)
self.featureList.clear()
features = self.app.vna.getFeatures()
features = self.app.vna.get_features()
for item in features:
self.featureList.addItem(item)

Wyświetl plik

@ -0,0 +1,3 @@
from .version import Version
__all__ = ["Version"]

Wyświetl plik

@ -34,24 +34,39 @@ _RXP = re.compile(
)
class _Version(typing.NamedTuple):
class Version(typing.NamedTuple):
"""Represents four components version: MAJOR.MAIN.REV-NOTE
Plrease note:
- `-NOTE` part is optional
- please use #parse() or #build() hepler methods to prepare right version
"""
major: int
minor: int
revision: int
note: str
@staticmethod
def parse(vstring: str = "0.0.0") -> "Version":
if (match := _RXP.search(vstring)) is None:
logger.error("Unable to parse version: %s", vstring)
return Version(0, 0, 0, "")
return Version(
int(match.group("major")),
int(match.group("minor")),
int(match.group("revision") or "0"),
match.group("note"),
)
@staticmethod
def build(
major: int, minor: int, revision: int = 0, note: str = ""
) -> "Version":
return Version(major, minor, revision, note)
def __str__(self) -> str:
return f"{self.major}.{self.minor}" f".{self.revision}{self.note}"
return f"{self.major}.{self.minor}.{self.revision}{self.note}"
def Version(vstring: str = "0.0.0") -> "_Version":
if (match := _RXP.search(vstring)) is None:
logger.error("Unable to parse version: %s", vstring)
return _Version(0, 0, 0, "")
return _Version(
int(match.group("major")),
int(match.group("minor")),
int(match.group("revision") or "0"),
match.group("note"),
)
def __repr__(self) -> str:
return f"{self.major}.{self.minor}.{self.revision}{self.note}"

Wyświetl plik

Wyświetl plik

@ -0,0 +1,41 @@
from NanoVNASaver.Hardware.LiteVNA64 import ScreenshotData
VALID_HEADER = b'\xe0\x01@\x01\x10'
class TestScreenshotData:
@staticmethod
def test_from_header() -> None:
result = ScreenshotData.from_header(VALID_HEADER)
assert result.width == 480
assert result.height == 320
assert result.pixel_size == 16
assert len(result.data) == 0
@staticmethod
def test_data_size() -> None:
assert ScreenshotData(0,0,0).data_size() == 0
assert ScreenshotData(480,320,16).data_size() == 307200
@staticmethod
def test_repr() -> None:
assert f"{ScreenshotData(0,0,0)}" == "0x0 0bits (0 Bytes)"
assert f"{ScreenshotData(480,320,16)}" == "480x320 16bits (307200 Bytes)"
@staticmethod
def test_rgb565_to_888() -> None:
assert ScreenshotData.rgb565_to_888(0x0000) == (0x00, 0x00, 0x00)
assert ScreenshotData.rgb565_to_888(0xFFE0) == (0xFF, 0xFF, 0x00)
assert ScreenshotData.rgb565_to_888(0xFFFF) == (0xFF, 0xFF, 0xFF)
@staticmethod
def test_get_rgb888_data() -> None:
img = ScreenshotData(0, 0, 0)
img.data = b"\x00\x00\xff\xe0\xff\xff"
result = img.get_rgb888_data()
assert len(result) == 3 *3
assert result == b'\x00\x00\x00\xFF\xFF\x00\xFF\xFF\xFF'

Wyświetl plik

@ -1,41 +0,0 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
import unittest
# Import targets to be tested
from NanoVNASaver.Version import Version
class TestCases(unittest.TestCase):
def test_version(self):
ver = Version("v1.2.3-test")
self.assertEqual(str(ver), "1.2.3-test")
self.assertLessEqual(ver, Version("1.2.4"))
self.assertFalse(ver > Version("1.2.4"))
self.assertFalse(ver > Version("1.2.3-u"))
self.assertTrue(Version("1.2.4") >= ver)
self.assertTrue(ver < Version("1.2.4"))
self.assertFalse(Version("0.0.0") == Version("0.0.0-rc"))
self.assertEqual(ver.major, 1)
self.assertEqual(ver.minor, 2)
self.assertEqual(ver.revision, 3)
self.assertEqual(ver.note, "-test")
Version("asdasd")
Version("1.2.invalid")

Wyświetl plik

Wyświetl plik

@ -0,0 +1,74 @@
# NanoVNASaver
#
# A python program to view and export Touchstone data from a NanoVNA
# Copyright (C) 2019, 2020 Rune B. Broberg
# Copyright (C) 2020,2021 NanoVNA-Saver Authors
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
# Import targets to be tested
from NanoVNASaver.utils import Version
class TestCases:
@staticmethod
def test_str() -> None:
assert str(Version(1, 0, 0, "")) == "1.0.0"
assert str(Version(1, 2, 0, "")) == "1.2.0"
assert str(Version(1, 2, 3, "")) == "1.2.3"
assert str(Version(1, 2, 3, "-test")) == "1.2.3-test"
@staticmethod
def test_repr() -> None:
ver = Version(1, 2, 3, "-test")
assert f"{ver}" == "1.2.3-test"
@staticmethod
def test_parse_normal_case() -> None:
# At least major and minot components must be specified
assert Version.parse("v1.2") == Version(1, 2, 0, "")
assert Version.parse("v1.2.3") == Version(1, 2, 3, "")
assert Version.parse("v1.2.3-test") == Version(1, 2, 3, "-test")
# At least major and minot components must be specified
assert Version.parse("1.2") == Version(1, 2, 0, "")
assert Version.parse("1.2.3") == Version(1, 2, 3, "")
assert Version.parse("1.2.3-test") == Version(1, 2, 3, "-test")
@staticmethod
def test_parse_invalid_values() -> None:
assert Version.parse("asdasd") == Version(0, 0, 0, "")
assert Version.parse("1.2.invalid") == Version(1, 2, 0, "invalid")
# At least major and minot components must be specified
assert Version.parse("v1") == Version(0, 0, 0, "")
assert Version.parse("1") == Version(0, 0, 0, "")
@staticmethod
def test_build_normal_case() -> None:
assert Version.build(1, 2) == Version(1, 2, 0, "")
assert Version.build(1, 2, 3) == Version(1, 2, 3, "")
assert Version.build(1, 2, 3, "test") == Version(1, 2, 3, "test")
@staticmethod
def test_comparation() -> None:
assert Version(1, 2, 3, "test") > Version(1, 2, 3, "")
assert Version(1, 2, 3, "test") < Version(1, 2, 4, "")
assert Version(1, 2, 3, "test") <= Version(1, 2, 4, "u")
assert Version(0, 0, 0, "0") != Version(0, 0, 0, "-rc")