Интро
На третьем курсе я пришел на практику в одно из предприятий отечественной ракетно-космической отрасли. Стажером я был амбициозным и довольно активным
Об этом в статье и пойдет речь.
Начало
Все приложения в отделе разрабатываются средствами C++ и библиотеки Qt. Опыт работы с данным фреймворком у меня был, поэтому с этой стороны никаких трудностей не возникло. К тому же у Qt есть обширная документация, а еще всегда можно
Поскольку все устройства подключены к одной сети, то вопрос, как к ним подключаться тоже решился очень быстро — используем сетевую часть Qt в виде QTcpSocket.
Самый интересный вопрос возник, когда пришлось решить, как именно общаться с данными устройствами, как активировать ту или иную функцию, передать то или иное значение. Тогда же оказалось, что все довольно тривиально: существует протокол стандартных команд для программируемых инструментов — SCPI. Он позволяет с помощью стандартный команд управлять любыми устройствами, поддерживающими данный стандарт.
Начинаем кодить
В заголовочном файле все стандартно и неинтересно:
#ifndef SIGGENCONTROL_H #define SIGGENCONTROL_H #include <QMainWindow> #include <QTcpSocket> #include <QString> 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";
Далее идет текстовое поле лога, ответа от устройства(туда же будут приходить ошибки, если они есть). Чуть ниже находятся кнопки соединения с устройством, отправки команды, проверки ошибок. В трех последних лайнэдитах можно либо задать свою команду и отправить ее на устройство, либо отдельно задать частоту и амплитуду. Радиокнопка справа включает/выключает ВЧ выход. Крутилка регулирует частоту, когда чекбокс снят и амплитуду, когда активирован.
Продолжаем и заканчиваем
Все самое интересное начинается в .cpp файле:
#include "siggencontrol.h" #include "ui_siggencontrol.h" #include "qdebug.h" #include <QTime> 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" ); } }
После прочтения
Понимаю, что статья может показаться оторванной от жизни и реальности для большого количества читателей, но удаленное управление контрольно-измерительной аппаратурой в сфере инженерии — достаточно распространенная тема, которая приносит много пользы и удобства (например, вам не нужно бегать к приборам и нажимать кнопочки).
В остальном это статья носит информационно-развлекательный характер и нацеленна, скорее, на энтузиастов и на людей, занимающихся разработкой и тестированием плат и другого железа. Остальным просто хотел бы рассказать о своем небольшом опыте разработки ПО для таких специфических целей.
