Об опыте общения с генератором сигнала через QTcpSocket и SCPI
Интро
На третьем курсе я пришел на практику в одно из предприятий отечественной ракетно-космической отрасли. Стажером я был амбициозным и довольно активным пока не повидал рутинной работы на российском госпредприятии и по своей просьбе был распределен в отдел, занимающийся разработкой контрольно-проверочной аппаратуры(далее КПА). К слову, большой частью разработки КПА является написание программной части для оборудования. В том числе, для проверки разрабатываемой аппаратной части в отделе имеются различные контрольно-измерительные устройства, все они подключены к общей сети. В итоге, в качестве одного из первых моих заданий стало написание приложения для управления генератором сигнала.
Об этом в статье и пойдет речь.
Начало
Все приложения в отделе разрабатываются средствами C++ и библиотеки Qt. Опыт работы с данным фреймворком у меня был, поэтому с этой стороны никаких трудностей не возникло. К тому же у Qt есть обширная документация, а еще всегда можно копипастнуть код со StackOverflow проконсультироваться у куратора.
Поскольку все устройства подключены к одной сети, то вопрос, как к ним подключаться тоже решился очень быстро — используем сетевую часть Qt в виде QTcpSocket.
Самый интересный вопрос возник, когда пришлось решить, как именно общаться с данными устройствами, как активировать ту или иную функцию, передать то или иное значение. Тогда же оказалось, что все довольно тривиально: существует протокол стандартных команд для программируемых инструментов — SCPI. Он позволяет с помощью стандартный команд управлять любыми устройствами, поддерживающими данный стандарт.
Начинаем кодить
#ifndef SIGGENCONTROL_H
#define SIGGENCONTROL_H
#include
#include
#include
namespace Ui {
class sigGenControl;
}
class sigGenControl : public QMainWindow
{
Q_OBJECT
public:
explicit sigGenControl(QWidget *parent = 0);
~sigGenControl();
//изначально хост и порт были заданы статически, но теперь задаются через интерфейс
QString host; //= "192.168.1.109"; ip нашего устройства
int port;// = 5025; порт устройства
void clearErr(); //процедура очистки ошибок
bool rfOn; //включен ли вч выход
bool pset = false;
bool hset = false;
private:
Ui::sigGenControl *ui;
QTcpSocket* socket;
private slots:
//слоты, подробно будут описаны в .cpp файле
void connectToHostSlot();
void sendToHostSlot();
void readyReadSlot();
void setFreq();
void setPow();
void activateRF();
void checkErrSlot();
void setPort();
void setHost();
void setDefault();
void dialValChangedSlot();
};
#endif // SIGGENCONTROL_H
Интерфейс решено было сделать таким:
Он довольно прост и интуитивно понятен. В двух лайнэдитах вверху задаются хост и порт устройства. Так же имеется возможность выбрать стандартные значения, тогда они примут следующий вид:
port = 5025;
host = "192.168.1.109";
Далее идет текстовое поле лога, ответа от устройства (туда же будут приходить ошибки, если они есть). Чуть ниже находятся кнопки соединения с устройством, отправки команды, проверки ошибок. В трех последних лайнэдитах можно либо задать свою команду и отправить ее на устройство, либо отдельно задать частоту и амплитуду. Радиокнопка справа включает/выключает ВЧ выход. Крутилка регулирует частоту, когда чекбокс снят и амплитуду, когда активирован.
Продолжаем и заканчиваем
#include "siggencontrol.h"
#include "ui_siggencontrol.h"
#include "qdebug.h"
#include
sigGenControl::sigGenControl(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::sigGenControl)
{
ui->setupUi(this);
ui->history->setReadOnly(true);
ui->history->setText("host : not set\nport : not set");
ui->history->append(QTime::currentTime().toString() + " : No connection");
socket = new QTcpSocket(this); //создаем новый экземпляр класса сокета
//через него будем осуществлять все операции по передаче-приему данных
//соединим нужные сигналы со слотами, большая часть не нуждается в комментариях
//в прицнипе ни один из коннектов не нуждается в подробном описании
connect(ui->connPb,QPushButton::clicked,this,sigGenControl::connectToHostSlot);
connect(ui->sendToHostPb,QPushButton::clicked,this,sigGenControl::sendToHostSlot);
connect(ui->input,QLineEdit::returnPressed,this,sigGenControl::sendToHostSlot);
connect(socket,QTcpSocket::readyRead,this,sigGenControl::readyReadSlot);
connect(ui->freqEdit,QLineEdit::returnPressed,this,sigGenControl::setFreq);
connect(ui->amptdEdit,QLineEdit::returnPressed,this,sigGenControl::setPow);
connect(ui->radioButton,QRadioButton::clicked,this,sigGenControl::activateRF);
connect(ui->errPb,QPushButton::clicked,this,sigGenControl::clearErr);
connect(ui->hostEdit,QLineEdit::returnPressed,this,sigGenControl::setHost);
connect(ui->portEdit,QLineEdit::returnPressed,this,sigGenControl::setPort);
connect(ui->checkBox,QCheckBox::clicked,this,sigGenControl::setDefault);
connect(ui->dial, QDial::valueChanged,this,sigGenControl::dialValChangedSlot);
ui->hist->setReadOnly(true);
QString deactRF = ":OUTP 0\n"; //та самая SCPI команда, в данном случае на выкл ВЧ выхода
socket->write(deactRF.toLocal8Bit()); //записываем команду в сокет и отправляем
this->rfOn = false; //снимаем флаг активности
ui->input->setReadOnly(true);
ui->freqEdit->setReadOnly(true);
ui->amptdEdit->setReadOnly(true);
ui->radioButton->setEnabled(false);
ui->sendToHostPb->setEnabled(false);
ui->errPb->setEnabled(false);
ui->connPb->setDisabled(true);
}
sigGenControl::~sigGenControl()
{
delete ui;
}
void sigGenControl::connectToHostSlot(){
socket->connectToHost(host,port); //соединяемся с хостом
//ждем соединения и выводим в qDebug() в случае успеха
if (socket->waitForConnected(1000))
qDebug("Connected!");
ui->history->append(QTime::currentTime().toString() + " : Connected"); //добавляем в лог
ui->input->setReadOnly(false);
ui->freqEdit->setReadOnly(false);
ui->amptdEdit->setReadOnly(false);
ui->radioButton->setEnabled(true);
ui->sendToHostPb->setEnabled(true);
ui->errPb->setEnabled(true);
ui->input->setText("*IDN?"); //запрос на идентификацию устройства
}
void sigGenControl::sendToHostSlot(){
//слот отправляет содержимое лайнэдита хосту
socket->write(ui->input->text().toLocal8Bit()+"\n");
}
void sigGenControl::readyReadSlot(){
//если есть готовность к считыванию
qDebug("ready read!");
QByteArray dataArr;
//считываем все с сокета
dataArr = socket->readAll();
//добавляем в лог
ui->hist->append(QTime::currentTime().toString() + " " + dataArr);
}
void sigGenControl::clearErr(){
//считываем ошибки, находящиеся в очереди, тем самым, очищая ее
QString errTxt = ":SYST:ERR?\n";
socket->write(errTxt.toLocal8Bit());
}
void sigGenControl::setFreq(){
QString fr = " kHz";
QString cmd = ":FREQ "; //команда для установки частоты
QString command = cmd+ui->freqEdit->text()+fr+"\n"; //формируем запрос
qDebug() << command;
socket->write(command.toLocal8Bit()); //записываем в сокет
}
void sigGenControl::setPow(){
QString amp = " dBm";
QString cmd = ":POW "; //команда для установки амплитуды
socket->write(cmd.toLocal8Bit() + ui->amptdEdit->text().toLocal8Bit() + amp.toLocal8Bit() + "\n"); //формируем запрос
}
void sigGenControl::activateRF(){
//команда для установки выхода в положение ВКЛ
//включает ВЧ выход
QString actRF = ":OUTP 1\n"; //команда на ВКЛ
QString deactRF = ":OUTP 0\n"; //команда на ВЫКЛ
if(this->rfOn == false){
socket->write(actRF.toLocal8Bit());
rfOn = true;
}else{
socket->write(deactRF.toLocal8Bit());
rfOn = false;
}
}
void sigGenControl::checkErrSlot(){
clearErr();
}
void sigGenControl::setHost(){
//устанавливаем значение хоста
this->host = ui->hostEdit->text();
ui->history->append("host: " + host);
ui->checkBox->setEnabled(true);
hset = true; //ставим флаг
if((pset && hset) == true){
ui->connPb->setEnabled(true);
}
}
void sigGenControl::setPort(){
//устанавливаем значение порта
this->port = ui->portEdit->text().toInt();
ui->history->append("port: " + QString::number(port));
ui->checkBox->setEnabled(true);
pset = true; //ставим флаг
if((pset && hset) == true){
ui->connPb->setEnabled(true);
}
}
void sigGenControl::setDefault(){
//установка дефолтного хоста и порта
port = 5025;
host = "192.168.1.109";
ui->history->append(QTime::currentTime().toString() + " " + "host: " + host);
ui->history->append(QTime::currentTime().toString() + " " + "port: " + QString::number(port));
ui->checkBox->setDisabled(true);
ui->connPb->setEnabled(true);
ui->hostEdit->setText(host);
ui->portEdit->setText(QString::number(port));
}
void sigGenControl::dialValChangedSlot(){
//нужно только для отладки, выдает значение дайала при его изменении
qDebug()<< "value : " << ui->dial->value();
if(ui->amplCheckBox->isChecked() == false){
//если это частота, установим пределы значений для крутилки
ui->dial->setMinimum(100);
ui->dial->setMaximum(20000000);
QString fr = " kHz";
QString cmd = ":FREQ "; //команда на изменение частоты
QString command = cmd+QString::number(ui->dial->value())+fr+"\n";
qDebug() << command;
socket->write(command.toLocal8Bit());
ui->label->setText("FREQUENCY :" + QString::number(ui->dial->value()) + " kHz" );
}else if(ui->amplCheckBox->isChecked() == true){
//если это амплитуда, установим другие пределы значений для крутилки
ui->dial->setMinimum(-130);
ui->dial->setMaximum(15);
QString pw = " dBm";
QString cmd = ":POW "; //команда на изменение амплитуды
QString command = cmd+QString::number(ui->dial->value())+pw+"\n";
qDebug() << command;
socket->write(command.toLocal8Bit());
ui->label->setText("AMPLITUDE :" + QString::number(ui->dial->value()) + " dBm" );
}
}
После прочтения
Понимаю, что статья может показаться оторванной от жизни и реальности для большого количества читателей, но удаленное управление контрольно-измерительной аппаратурой в сфере инженерии — достаточно распространенная тема, которая приносит много пользы и удобства (например, вам не нужно бегать к приборам и нажимать кнопочки).
В остальном это статья носит информационно-развлекательный характер и нацеленна, скорее, на энтузиастов и на людей, занимающихся разработкой и тестированием плат и другого железа. Остальным просто хотел бы рассказать о своем небольшом опыте разработки ПО для таких специфических целей.