Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

LibreCAL Firmware/Docs/Test Add SCPI command to add COEFFICIENT comment(s) #13

Merged
merged 3 commits into from
Mar 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Documentation/SCPI_API.pdf
Binary file not shown.
8 changes: 7 additions & 1 deletion Documentation/SCPI_API.tex
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,13 @@ \subsubsection{:COEFFicient:CREATE}
\event{Creates a new calibration coefficient}{:COEFFicient:CREATE <set name> <coefficient name>}{<set name> Name of the coefficient set\\<coefficient name> Name of the coefficient}
If the coefficient already exists, it will be deleted first (along with all its coefficient data). Afterwards, a new and empty coefficient will be created.

This command should be used in conjunction with :COEFFicient:ADD and :COEFFicient:FINish.
This command should be used in conjunction with :COEFFicient:ADD\_COMMENT, :COEFFicient:ADD and :COEFFicient:FINish.
\subsubsection{:COEFFicient:ADD\_COMMENT}
\event{Add ASCII text comment before to add coefficient datapoint}{:COEFFicient:ADD\_COMMENT <ASCII text comment>}{<ASCII text comment> add an ASCII text comment with size up to 120chars\\if the comment exceed 120 characters it will be truncated to 120 characters}

This command should be used only after :COEFFicient:CREATE as it is intended to add comment before to add any datapoint (:COEFFicient:ADD).

There is a limitation of maximum 100 comments per calibration coefficient (:COEFFicient:CREATE)(if there is more comment the command will return an error and comment will be not added).
\subsubsection{:COEFFicient:ADD}
\event{Add a datapoint to an existing coefficient}{:COEFFicient:ADD <frequency> <S parameters>}{<frequency> Frequency of the datapoint, in GHz\\
<S parameters> S parameters, split into real and imaginary parts}
Expand Down
32 changes: 30 additions & 2 deletions Software/LibreCAL/src/SCPI.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,10 @@

using namespace SCPI;

constexpr int CommentMaxSize = 120;
constexpr int ParseLastCmdMaxSize = CommentMaxSize+1;
constexpr int ParseArgumentsMax = CommentMaxSize+1;

constexpr int NumInterfaces = 2;
constexpr int BufferSize = 256;

Expand Down Expand Up @@ -317,6 +321,30 @@ static const Command commands[] = {
// file deleted
tx_string("\r\n", interface);
}, nullptr, 2),
Command("COEFFicient:ADD_COMMENT", [](char *argv[], int argc, int interface){
char comment[CommentMaxSize+1];
memset(comment, 0, sizeof(comment));
// Concatenate all argv(words) adding a space between each word to create the full comment
for(uint8_t i=0;i<argc - 1;i++) {
strncat(comment, argv[i+1], (CommentMaxSize-strlen(comment)));
if(strlen(comment) == CommentMaxSize-1)
break;
strncat(comment, " ", (CommentMaxSize-strlen(comment)));
}
// Remove last space at end if it exist
int len = strlen(comment);
if(comment[len-1] == ' ')
comment[len-1] = 0;
if(comment[len] == ' ')
comment[len] = 0;
if(!Touchstone::AddComment(comment)) {
// failed to add comment
tx_string("ERROR\r\n", interface);
return;
}
// comment added
tx_string("\r\n", interface);
}, nullptr, 1),
Command("COEFFicient:ADD", [](char *argv[], int argc, int interface){
double freq = strtod(argv[1], NULL);
double values[argc - 2];
Expand Down Expand Up @@ -420,9 +448,9 @@ static void scpi_lst(char *argv[], int argc, int interface) {
}

static void parse(char *s, uint8_t interface) {
static char last_cmd[50] = ":";
static char last_cmd[ParseLastCmdMaxSize] = ":";
// split strings into args
char *argv[20];
char *argv[ParseArgumentsMax];
int argc = 0;
argv[argc] = s;
while(*s) {
Expand Down
39 changes: 34 additions & 5 deletions Software/LibreCAL/src/Touchstone.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ static bool readFileOpen = false;
static uint32_t nextReadPoint;
static char readFileFolder[50];
static char readFileName[50];
static bool write_init_lines = false;
const uint16_t add_comment_limit_per_file = 100;
static uint16_t add_comment_nb = 0;

static bool extract_double_values(const char *line, double *values, uint8_t expected_values) {
while(expected_values) {
Expand Down Expand Up @@ -112,23 +115,49 @@ bool Touchstone::StartNewFile(const char *folder, const char *filename) {
return false;
}
writeFileOpen = true;
// write initial lines
auto res = f_printf(&writeFile, "! Automatically created by LibreCAL firmware\r\n");
if(res < 0) {
write_init_lines = false;
add_comment_nb = 0;

return true;
}

bool Touchstone::AddComment(const char* comment) {
if(!writeFileOpen) {
return false;
}
if(write_init_lines == true) {
// Add comment only allowed just after call of Touchstone::StartNewFile
return false;
}
// write the option line (only these options are supported)
res = f_printf(&writeFile, "# GHz S RI R 50.0\r\n");
if(add_comment_nb == add_comment_limit_per_file) {
// Add comment reached max limit per file allowed
return false;
}
auto res = f_printf(&writeFile, "! %s\r\n", comment);
if(res < 0) {
return false;
}
add_comment_nb++;
return true;
}

bool Touchstone::AddPoint(double frequency, double *values, uint8_t num_values) {
if(!writeFileOpen) {
return false;
}
if(write_init_lines == false) {
// write initial lines
auto res = f_printf(&writeFile, "! Automatically created by LibreCAL firmware\r\n");
if(res < 0) {
return false;
}
// write the option line (only these options are supported)
res = f_printf(&writeFile, "# GHz S RI R 50.0\r\n");
if(res < 0) {
return false;
}
write_init_lines = true;
}
auto res = f_printf(&writeFile, "%f", frequency);
if(res < 0) {
return false;
Expand Down
1 change: 1 addition & 0 deletions Software/LibreCAL/src/Touchstone.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ namespace Touchstone {

uint32_t GetPointNum(const char *folder, const char *filename);
bool StartNewFile(const char *folder, const char *filename);
bool AddComment(const char* comment);
bool AddPoint(double frequency, double *values, uint8_t num_values);
bool FinishFile();
bool DeleteFile(const char *folder, const char *filename);
Expand Down
112 changes: 112 additions & 0 deletions Software/Scripts/FactoryCoefficients/VNA_Example_Test/Test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import socket
from asyncio import IncompleteReadError # only import the exception class

class SocketStreamReader:
def __init__(self, sock: socket.socket):
self._sock = sock
# self._recv_buffer = bytearray()

def read(self, num_bytes: int = -1) -> bytes:
raise NotImplementedError

def readexactly(self, num_bytes: int) -> bytes:
buf = bytearray(num_bytes)
# pos = 0
# while pos < num_bytes:
# n = self._recv_into(memoryview(buf)[pos:])
# if n == 0:
# raise IncompleteReadError(bytes(buf[:pos]), num_bytes)
# pos += n
return bytes(buf)

def readline(self) -> bytes:
# return self.readuntil(b"\n")
buf = bytearray(1)
return bytes(buf)

def readuntil(self, separator: bytes = b"\n") -> bytes:
# if len(separator) != 1:
# raise ValueError("Only separators of length 1 are supported.")

# chunk = bytearray(4096)
# start = 0
# buf = bytearray(len(self._recv_buffer))
# bytes_read = self._recv_into(memoryview(buf))
# assert bytes_read == len(buf)

# while True:
# idx = buf.find(separator, start)
# if idx != -1:
# break

# start = len(self._recv_buffer)
# bytes_read = self._recv_into(memoryview(chunk))
# buf += memoryview(chunk)[:bytes_read]

# result = bytes(buf[: idx + 1])
# self._recv_buffer = b"".join(
# (memoryview(buf)[idx + 1 :], self._recv_buffer)
# )
# return result
buf = bytearray(1)
return bytes(buf)

def _recv_into(self, view: memoryview) -> int:
# bytes_read = min(len(view), len(self._recv_buffer))
# view[:bytes_read] = self._recv_buffer[:bytes_read]
# self._recv_buffer = self._recv_buffer[bytes_read:]
# if bytes_read == len(view):
# return bytes_read
# bytes_read += self._sock.recv_into(view[bytes_read:])
# return bytes_read
return 0

class Test:
def __init__(self, host='localhost', port=19542):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# try:
# self.sock.connect((host, port))
# except:
# raise Exception("Unable to connect to HP8753D. Make sure it is running and the TCP server is enabled.")
# self.reader = SocketStreamReader(self.sock)

def __del__(self):
# self.sock.close()
return

def __read_response(self):
# return self.reader.readline().decode().rstrip()
return


def cmd(self, cmd):
# self.sock.sendall(cmd.encode())
# self.sock.send(b"\n")
# self.__read_response()
return

def query(self, query):
# self.sock.sendall(query.encode())
# self.sock.send(b"\n")
# return self.__read_response()
return ""

@staticmethod
def parse_trace_data(data):
ret = []
# Remove brackets (order of data implicitly known)
# data = data.replace(']','').replace('[','')
# values = data.split(',')
# if int(len(values) / 3) * 3 != len(values):
# number of values must be a multiple of three (frequency, real, imaginary)
# raise Exception("Invalid input data: expected tuples of three values each")
# for i in range(0, len(values), 3):
# freq = float(values[i])
# real = float(values[i+1])
# imag = float(values[i+2])
# ret.append((freq, complex(real, imag)))
freq = float(0.1)
real = float(0.2)
imag = float(0.3)
ret.append((freq, complex(real, imag)))
return ret
73 changes: 73 additions & 0 deletions Software/Scripts/FactoryCoefficients/VNA_Example_Test/VNA.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# A VNA is needed to measure and create the factory coefficients.
# This file contains the necessary function to extract data from this VNA.
# Please implement all functions according to the VNA you are using.

from VNA_Example_Test.Test import Test
import time

vna = None

def checkIfReady() -> bool:
"""
checkIfReady checks if the VNA is connected and ready to be used

This function can also be used for initiliazing the VNA. It is called only
once at the beginning

:return: True if VNA is ready. False otherwise
"""
global vna
try:
vna = Test('localhost', 19542)
except:
# Test-GUI not detected
print("Unable to connect to Test-GUI")
return False
"""
checkIfReady checks if the VNA is connected and ready to be used

This function can also be used for initiliazing the VNA. It is called only
once at the beginning

:return: True if VNA is ready. False otherwise
"""
return True

def getPorts() -> int:
"""
getPorts returns the number of ports the VNA has

Creation of factory coefficients if faster if more ports are available.
The VNA needs at least 2 ports and at most 4 ports can be utilized.

:return: Number of ports on the VNA
"""
return 2

def measure():
"""
measure starts a measurement and returns the S parameter data

This function should start a new measurement and block until the data
is available. No previous measurements must be returned.

Measurements are returned as a dictionary:
Key: S parameter name (e.g. "S11")
Value: List of tuples: [frequency, complex]

:return: Measurements
"""

# Trigger the sweep
vna.cmd(":VNA:ACQ:SINGLE TRUE")
# Wait for the sweep to finish
# while vna.query(":VNA:ACQ:FIN?") == "FALSE":
# time.sleep(0.1)

ret = {}
ret["S11"] = vna.parse_trace_data(vna.query(":VNA:TRACE:DATA? S11"))
ret["S12"] = vna.parse_trace_data(vna.query(":VNA:TRACE:DATA? S12"))
ret["S21"] = vna.parse_trace_data(vna.query(":VNA:TRACE:DATA? S21"))
ret["S22"] = vna.parse_trace_data(vna.query(":VNA:TRACE:DATA? S22"))

return ret
Loading