silderan / QTelnet

A telnet client using Qt5 framework.
28 stars 14 forks source link

telnet.canReadLine()) #3

Open sirias52 opened 4 months ago

sirias52 commented 4 months ago

in serial comunication is posible this while (m_serialPort.canReadLine()), is posible in this program?

silderan commented 4 months ago

Nop. This is an "event driven" concept. That is: "Do your staff and I (QTelnet API) will call your code when data is available". Of course, you can do it by your own. But, not sure if it worth.

What you must do is to read and store incomming data (in your slot connected to newData signal) and when a new line is present (or whatever you want) call your functions.

Your approach is fine when there are two threads: your code and socked code. So you need to call "canReadLine" on socked code. Not needed in Qt framework.

Tell me if you need help to implement this properly.

sirias52 commented 4 months ago

Hello, greetings from Nicaragua. thanks for your answer. I am not an expert programmer, but I am implementing a telnet (your library) communication between hardware and software. The program is based on sending and receiving data. in serial communication there is no problem. 1- Sending data

void frmMain::sendCommand(QString command, int tableIndex, bool showInConsole) { // if (!m_serialPort.isOpen() || !m_resetCompleted) return;

if (!telnet.isConnected() || !m_resetCompleted) return;

                  /*some code*/...

qDebug() << "sendata:" << command;

//m_serialPort.write((command + "\r").toLatin1());

telnet.sendData((command + "\r").toLatin1().trimmed());
telnet.sendData("\n"); } this part of the code works fine. I think so.

2- Recieving data

//connect(&m_serialPort, SIGNAL(readyRead()), this, SLOT(onSerialPortReadyRead()), Qt::AutoConnection); //connect(&m_serialPort, SIGNAL(error(QSerialPort::SerialPortError)), this, SLOT(onSerialPortError(QSerialPort::SerialPortError)));

connect( &telnet, SIGNAL(newData(const char,int)), this, SLOT(onTelnetPortReadyRead(const char,int)) );

//void frmMain::onSerialPortReadyRead() //{ void frmMain::onTelnetPortReadyRead(const char *msg, int count) { // while (m_serialPort.canReadLine()) { if(count>1) { ---------------------------------> (This way works well, but not if it is the best way.)

  QByteArray data(msg, count);
  data = data.trimmed();
  QString::fromStdString(data.toStdString().c_str());  -------> My problem is the way it receives the data.

// QString data = m_serialPort.readLine().trimmed();

  qDebug() << "Recieveddata:" << data ;

   /*some code*/......
   /*some code*/......
   /*some code*/......

}

In serial communication I receive formatted data: It's the right way to operate.

Recieveddata: "ok" Recieveddata: "[G54:0.000,0.000,-16.500]" Recieveddata: "[G55:0.000,0.000,0.000]" Recieveddata: "[G56:0.000,0.000,0.000]" Recieveddata: "[G57:0.000,0.000,0.000]" Recieveddata: "[G58:0.000,0.000,0.000]" Recieveddata: "[G59:0.000,0.000,0.000]" Recieveddata: "[G28:0.000,0.000,0.000]" Recieveddata: "[G30:0.000,0.000,0.000]" Recieveddata: "[G92:0.000,0.000,16.500]" Recieveddata: "[TLO:0.000]" Recieveddata: "[PRB:0.000,0.000,0.000:0]" Recieveddata: "ok" Received offsets: "[G54:0.000,0.000,-16.500]; [G55:0.000,0.000,0.000]; [G56:0.000,0.000,0.000]; [G57:0.000,0.000,0.000]; [G58:0.000,0.000,0.000]; [G59:0.000,0.000,0.000]; [G28:0.000,0.000,0.000]; [G30:0.000,0.000,0.000]; [G92:0.000,0.000,16.500]; [TLO:0.000]; [PRB:0.000,0.000,0.000:0]; ok"

In telnet communication I receive this data:

Recieveddata: "ok"

Recieveddata: "[G54:-83.285,23.397,4.872,-40.000,0.000,0.000]\n[G55:0.000,0.000,0.000,0.000,0.000,0.000]\n[G56:0.000,0.000,0.000,0.000,0.000,0.000]\n[G57:0.000,0.000,0.000,0.000,0.000,0.000]\n[G58:0.000,0.000,0.000,0.000,0.000,0.000]\n[G59:0.000,0.000,0.000,0.000,0.000,0.000]\n[G28:-334.875,52.796,0.000,0.000,-137.182,-137.182]\n[G30:-334.875,52.796,0.000,0.000,-137.182,-137.182]\n[G92:35.158,16.739,0.000,0.000,0.000,0.000]\n[TLO:0.000]\nok" Received offsets: "[G54:-83.285,23.397,4.872,-40.000,0.000,0.000]\n[G55:0.000,0.000,0.000,0.000,0.000,0.000]\n[G56:0.000,0.000,0.000,0.000,0.000,0.000]\n[G57:0.000,0.000,0.000,0.000,0.000,0.000]\n[G58:0.000,0.000,0.000,0.000,0.000,0.000]\n[G59:0.000,0.000,0.000,0.000,0.000,0.000]\n[G28:-334.875,52.796,0.000,0.000,-137.182,-137.182]\n[G30:-334.875,52.796,0.000,0.000,-137.182,-137.182]\n[G92:35.158,16.739,0.000,0.000,0.000,0.000]\n[TLO:0.000]\nok"

this generates some bugs for me.

I don't know if it's something in this part of the code

void QTelnet::onReadyRead() { qint64 readed; qint64 processed;

while( (readed = read(m_buffIncoming, IncommingBufferSize)) != 0 )
{
    switch( readed )
    {
    case -1:
        disconnectFromHost();
        break;
    default:
        processed = doTelnetInProtocol(readed);
        if( processed > 0 )
            Q_EMIT(newData(m_buffProcessed, processed));

        break;
    }
}

}

or I'm the problem in this part.

QByteArray data(msg, count); data = data.trimmed(); QString::fromStdString(data.toStdString().c_str());

thank you very much for your help.

silderan commented 4 months ago

¿De Nicaragua? Hablas español, supongo.

Es un poco tarde aquí en España. Así que igual lo que te digo algún sinsentido ;)

La manera que implementas onTelnetPortReadyRead tienen un pequeño error (aunque creo que no te afectará) Fíjate en cómo implemento este mismo slot en el fichero QTelnetTester.cpp, en la función addText(...) La función addText lo que hace es añadir el texto que llega desde telnet a un visor de texto. Veras que, simplemente, coge el buffer, lo envía al QWidget y luego fuerza el QScrollBar a ir abajo del todo para que se muestre lo que ha llegado.

Bien, con esto quiero decirte que no necesitas comprobar la variable count. Si vas al fichero QTelnet.cpp verás que ahí dentro ya se comprueba que "count" sea válido y, por lo tanto, no necesitas comprobarlo tú. Aun así, no está de más hacer esta comprobación porque no siempre conoces/ves el código fuente de la API.

PEEEERO debe ser if( count > 0 ) Y NO if( count >1 )

No creo que esto te haya dado problemas: sería rarísimo que sólo te llegara 1 byte desde Telnet. Pero lo que está bien, está bien ;)

Luego, creo que el único problema que tienes es que debes separar lo que te ha llegado por Telnet en las diferentes líneas. Prueba esto en tu slot onTelnetPortReadyRead:

QByteArray data(msg, count);

foreach( const QByteArray &lineaByes : data.split('\n') ) { QString lineaString = data.toStdString(); qDebug() << "Receiveddata: " << lineaString << "\n"; } También, debes tener siempre presente que este código no espera a que haya líneas completas. Por lo tanto, podrías recibir un [Gxxxxxxxx] en dos invocaciones distintas de tu Slot. Cuando lees los datos del puerto serie, internamente, la función "canReadLine" sólo retornará true cuando realmente hay una línea ENTERA. Pero el código que he puesto antes, no.

Habrá varias maneras de hacerlo. Se me ocurre esta:

QByteArray data(msg, count);

static QByteArray bytesLinea;

for( int i = 0; i < count; ++i ) { if( ((msg[i] == '\n') || (msg[i] == '\r')) && !bytesLinea.isEmpty() ) { qDebug() << "Receiveddata: " << bytesLinea.toStdString() << "\n" ; // DO your stuff here. bytesLinea.clear(); } }

No he probado nada y llevo mucho tiempo sin tocar las librerías Qt. Así que revisa que no haya puesto una tontería.

¡Ya me cuentas qué tal!

Un saludo, Rafa.

sirias52 commented 4 months ago

Hola, gracias por su respuesta y amabilidad, si hablo español jeje. La verdad estoy tratando de hacer funcionar este proyecto https://github.com/Denvi/Candle, pero con comunicion telnet. No se, si usted ha escuchado de grbl. Simplemente es un proyecto para menejo de equipos CNC de codigo abierto. Pero hasta el momento solo hay una opción por telnet o websocket, pero no es de mi agrado. Así que decidí trabajar este proyecto que para mí es muy comodo y agradable visualmente. Entonces encontré su librería para telnet y la implementé en el candle. Funciona bien, pero mi problema es a la hora de recibir datos, no me interpreta las "\n" como su debido siguiente línea.

cuando la aplicación se conecta por telnet (con lo que he modificado el codigo) recibo exactamente la respuesta a como debería estar en serial.

grbl reset Recieveddata: "GRBL 3.7 [CNC v3.7.12 (wifi) '$' for help]" reseting filter: "GRBL 3.7 [CNC v3.7.12 (wifi) '$' for help]" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0|WCO:-83.285,23.397,4.872,-40.000,0.000,0.000>" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0|Ov:100,100,100>" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0>" sendata: "$G" Recieveddata: "[GC:G0 G54 G17 G21 G90 G94 M5 M9 T0 F0 S0]" Recieveddata: "ok" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0>" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0>" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0>" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0>" sendata: "$G" Recieveddata: "[GC:G0 G54 G17 G21 G90 G94 M5 M9 T0 F0 S0]" Recieveddata: "ok" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0>" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0>" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0>" Recieveddata: "<Idle|MPos:416.267,13.398,-6.000,0.000,0.000,0.000|FS:0,0|WCO:-83.285,23.397,4.872,-40.000,0.000,0.000>" sendata: "$G"

y asi esta repitiendo esta secuencia una vez se envia "$G". hasta por aca todo espectacular, el problema ocurré si yo envio un seteo de posición.

sendata: "G92X0Y0" sendata: "$#" Recieveddata: "ok" Recieveddata: "[G54:-83.285,23.397,4.872,-40.000,0.000,0.000]\n[G55:0.000,0.000,0.000,0.000,0.000,0.000]\n[G56:0.000,0.000,0.000,0.000,0.000,0.000]\n[G57:0.000,0.000,0.000,0.000,0.000,0.000]\n[G58:0.000,0.000,0.000,0.000,0.000,0.000]\n[G59:0.000,0.000,0.000,0.000,0.000,0.000]\n[G28:-334.875,52.796,0.000,0.000,-137.182,-137.182]\n[G30:-334.875,52.796,0.000,0.000,-137.182,-137.182]\n[G92:499.552,-10.000,0.000,0.000,0.000,0.000]\n[TLO:0.000]\nok" Received offsets: "[G54:-83.285,23.397,4.872,-40.000,0.000,0.000]\n[G55:0.000,0.000,0.000,0.000,0.000,0.000]\n[G56:0.000,0.000,0.000,0.000,0.000,0.000]\n[G57:0.000,0.000,0.000,0.000,0.000,0.000]\n[G58:0.000,0.000,0.000,0.000,0.000,0.000]\n[G59:0.000,0.000,0.000,0.000,0.000,0.000]\n[G28:-334.875,52.796,0.000,0.000,-137.182,-137.182]\n[G30:-334.875,52.796,0.000,0.000,-137.182,-137.182]\n[G92:499.552,-10.000,0.000,0.000,0.000,0.000]\n[TLO:0.000]\nok"

el envio de dato esta a como debería ser, pero al recibir el dato recibo el mensaje sin su debido espacio, como que no interpreta el \n. y esto genera que no se setee o que la tarjeta al comparar estos valores no lo haga adecuadamente. Pero si yo envio un archivo para que lo procese, se procesa muy bien, aunque a veces recibe ese \n y crashea.

Ayer estuve probando sus lineas.

QByteArray data(msg, count);

foreach( const QByteArray &lineaByes : data.split('\n') ) { QString lineaString = data.toStdString(); qDebug() << "Receiveddata: " << lineaString << "\n"; }

En esta parte el compildor me da un error, no sabe quien es &lineaByes.

QByteArray data(msg, count);

static QByteArray bytesLinea;

for( int i = 0; i < count; ++i ) { if( ((msg[i] == '\n') || (msg[i] == '\r')) && !bytesLinea.isEmpty() ) { qDebug() << "Receiveddata: " << bytesLinea.toStdString() << "\n" ; // DO your stuff here. bytesLinea.clear(); } }

en este otro funciona, pero lo recibe sin comillas y a la hora de poner el resto de codigo en // DO your stuff here// no funciona.

Con respecto a esto: "PEEEERO debe ser if( count > 0 ) Y NO if( count >1 )", si pongo cero no funciona. Es extraño, pero no funciona.

Aclaro, no creo sea un problema con su librería, mas bien parece un problema de que no he podido procesar bien lo que recibo.

silderan commented 4 months ago

No me dedico a la programación a nivel profesional. Aunque tengo algunos programas hechos para mi trabajo. No me importa ayudar. Aunque, lamentablemente, no tengo el hardware necesario para simular el CNC en Candle.

¿Lo que pretendes es modificar Candle para que use mi librería en la comunicación con el CNC en vez del "serial port"?

Cuando dices "lo recibe sin comillas"... ¿Podrías explicarme más detalles? La verdad es que me parece rarísimo ver el texto "\n". Y sólo cuando le indicas al CNC que vaya a una posición y no con el reset. ¿Te he entendido bien?

El error es que "foreach" no se usa como lo puse yo... es " foreach( const QByteArray &lineaByes, data.split('\n') )" O mejor (más estándar): for( const QByteArray &lineaByes : data.split('\n') )"Llevo mucho sin tocar C++ ni Qt :P

Por otro lado, podrías crear un fichero binario con los datos recibidos, me lo pasas y lo abro. Con ese fichero quizá pueda echarte una mano. Para crear un fichero que se cree solo una vez por sesión del programa, es algo así:

QFile static f("out.data"); static bool Abierto = false; if( !Abierto ) Abierto = f.open(QIODevice::WriteOnly); f.write(data, count); f.flush();

Luego (o mañana) le echaré un vistazo al código del Candle. A ver si descubro algo más.

sirias52 commented 4 months ago

Si , con el harware es de evidente ayuda. Si estoy modificando el candle, y a como le dije funciona bien, solo con los inconvenientes mencionados. De hecho ya está agregada su libreria al proyecto... Asi es, con el reset esta funcionando bien, pero a la hora de dar algún comando en el que el grbl retorna posiciones , es ahi los problemas de los \n, de ahi con otros comando que no retorne posiciones no hay problema. Voy a revisar para crear el fichero. De todas formas el hardaware es un ESP32 (https://www.ebay.es/itm/364306536290?itmmeta=01HZDT4RR5GKXP67YXDRX6KZ9R&hash=item54d25c9762:g:2~YAAOSw1ZVkjWEQ&itmprp=enc%3AAQAJAAAA4JFj9dxzyXUzYlC0rrtMedok4%2BebVZMv6G9rZSUlx%2BK3kCW6SIJ4eAOEv%2FA1P6OPYz3eT%2BRbj0rAhNzwqpC5f--L56xnPUrJFU%2B1DfNqt5pJs08N3auznox615DSj7emcKzCu%2BM3gnBW%2F9%2BvCZm%2FBjUb2UVRqrKsYmZ9cZ4jbgIRXpumm9DNMdQXxYbXK6TrrQuG8RFQyib72Iz0uBRcmkPvl4WGGkxD3Uk4BIsbmZUvnF4aO1Kerx6VNrEgAyLUyioFNCeVO78q0yTuAWqodcpAIM8hB3WyUCLUbsaDu5Si%7Ctkp%3ABk9SR5aMk7r7Yw) con el firmware Fluid cnc https://github.com/bdring/FluidNC

Por otro lado, con su mismo programa Qtelnettester se puede estar probando, ya que el debug retorna los mismo errores. El debug debe estar sin <<\n, ya que deberia recibir los datos ya con su respectivo "enter".

WhatsApp Image 2024-06-02 at 19 13 00

sirias52 commented 4 months ago

Muchas gracias. Funcionaaa... for(const QByteArray &lineaByes : data.split('\n') ), ayudo mucho, ya no tiene ningun bug, ni con altas velocidades. Su librería esta genial y ahora con su ayuda ya se tiene un controlador CNC por telnet. Asi que su libreria andará gestionando placas CNC por telnet. Igual seguiré depurando, pero muchas gracias.

Ahora solo necesito probarla con otro firmware y estaría completo.

silderan commented 4 months ago

Me alegro ;)

Si quieres ayuda para hacer un "fork" del proyecto original y añadir la posibilidad de comunicación a través de Telnet que sea elegible desde la GUI del programa, en vez de las modificaciones que has hecho "hardcodeadas", dímelo. Aunque ando un poco oxidado en Qt.... :P

Da la casualidad que tengo un par de proyectos con ESP32 XD. Pero bueno, no me vale la pena meterme en ello. Y menos sabiendo que ahora te funciona.

Por cierto, aunque te haya funcionado hacer el "split" de los datos que han llegado, deberías buscar por qué no va el último código que te pase. Si por el motivo que sea recibes media línea, tendrás dos medias líneas, en dos invocaciones del slot y, por lo tanto, datos corruptos. Los paquetes de datos por la red son de 1500 bytes. Quitando la cabecera IP y el protocolo Telnet, igual te quedan 1400 bytes "usables" (me lo invento). En esos 1400 bytes el CNC tiene de sobra para enviar las respuestas. Es más. Creo que cada vez que se invoca el slot de newData, tendrá una sola línea, ya que, por muy rápida que sea la máquina de control numérico, no lo será tanto como para hacer tantas acciones que sobrepasen la capacidad de los paquetes Ethernet. Aun así, llámalo purismo de programador, es mejor no asumir nada y dejarlo todo atado.

He estado mirando el código de Candle. Justamente donde está el slot onSerialPortReadyRead es un código muy obscuro y poco mantenible: mezcla la parte de comunicación con la parte de análisis del texto que llega por el puerto serie. Esto hace que añadir la funcionalidad de Telnet no sea limpio. Por eso has tenido que modificar los parámetros que pasas a las funciones para adaptarlas a Telnet. Lo ideal sería, como te comenté, mantener las dos opciones de comunicación; que el usuario decida cómo conectarse. Prueba esto:

void frmMain::onSerialPortReadyRead() { while( m_serialPort.canReadLine() ) parseReceivedLine(m_serialPort.readLine().trimmed()); }

define USE_SPLIT_TEXT

void frmMain::onTelnetPortReadyRead(const char *data, int count) {

ifdef USE_SPLIT_TEXT

 for( const QByteArray &linea : QByteArray(data, count).split('\n') )
    parseReceivedLine(linea.toStdString()).trimmed();

else

static QByteArray bytesLinea;
for( int i = 0; i < count; ++i )
{
    if( ((msg[i] == '\n') || (msg[i] == '\r'))  && !bytesLinea.isEmpty()

) { parseReceivedLine(linea.toStdString.trimmed()); bytesLinea.clear(); } }

endif

}

Añade la función parseReceivedLine(QString data) en la definición de la clase frmMain y modifica la actual onSerialPortReadyRead quedando así:

void frmMain::parseReceivedLine(QString data) { // Filter prereset responses if (m_reseting) { qDebug() << "reseting filter:" << data; if (!dataIsReset(data)) continue; else { m_reseting = false; m_timerStateQuery.setInterval(m_settings->queryStateTime()); } }

// Status response
if (data[0] == '<') {
    int status = -1;

    m_statusReceived = true;

    // Update machine coordinates
    static QRegExp mpx("MPos:([^,]*),([^,]*),([^,^>^|]*)");
    if (mpx.indexIn(data) != -1) {
        ui->txtMPosX->setText(mpx.cap(1));
        ui->txtMPosY->setText(mpx.cap(2));
        ui->txtMPosZ->setText(mpx.cap(3));
    }

    // Status
    static QRegExp stx("<([^,^>^|]*)");
    if (stx.indexIn(data) != -1) {
        status = m_status.indexOf(stx.cap(1));

        // Undetermined status
        if (status == -1) status = 0;

        // Update status
        if (status != m_lastGrblStatus) {
            ui->txtStatus->setText(m_statusCaptions[status]);
            ui->txtStatus->setStyleSheet(QString("background-color: %1;

color: %2;") .arg(m_statusBackColors [status]).arg(m_statusForeColors[status])); }

        // Update controls
        ui->cmdRestoreOrigin->setEnabled(status == IDLE);
        ui->cmdSafePosition->setEnabled(status == IDLE);
        ui->cmdZeroXY->setEnabled(status == IDLE);
        ui->cmdZeroZ->setEnabled(status == IDLE);
        ui->chkTestMode->setEnabled(status != RUN && !m_processingFile);
        ui->chkTestMode->setChecked(status == CHECK);
        ui->cmdFilePause->setChecked(status == HOLD0 || status == HOLD1

|| status == QUEUE); ui->cmdSpindle->setEnabled(!m_processingFile || status == HOLD0);

ifdef WINDOWS

        if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS7) {
            if (m_taskBarProgress) m_taskBarProgress->setPaused(status

== HOLD0 || status == HOLD1 || status == QUEUE); }

endif

        // Update "elapsed time" timer
        if (m_processingFile) {
            QTime time(0, 0, 0);
            int elapsed = m_startTime.elapsed();
            ui->glwVisualizer->setSpendTime(time.addMSecs(elapsed));
        }

        // Test for job complete
        if (m_processingFile && m_transferCompleted &&
                ((status == IDLE && m_lastGrblStatus == RUN) || status

== CHECK)) { qDebug() << "job completed:" << m_fileCommandIndex << m_currentModel->rowCount() - 1;

            // Shadow last segment
            GcodeViewParse *parser = m_currentDrawer->viewParser();
            QList<LineSegment*> list = parser->getLineSegmentList();
            if (m_lastDrawnLineIndex < list.count()) {
                list[m_lastDrawnLineIndex]->setDrawn(true);
                m_currentDrawer->update(QList<int>() <<

m_lastDrawnLineIndex); }

            // Update state
            m_processingFile = false;
            m_fileProcessedCommandIndex = 0;
            m_lastDrawnLineIndex = 0;
            m_storedParserStatus.clear();

            updateControlsState();

            qApp->beep();

            m_timerStateQuery.stop();
            m_timerConnection.stop();

            QMessageBox::information(this, qApp->applicationDisplayName(),

tr("Job done.\nTime elapsed: %1") .arg(ui->glwVisualizer-> spendTime().toString("hh:mm:ss")));

            m_timerStateQuery.setInterval(m_settings->queryStateTime());
            m_timerConnection.start();
            m_timerStateQuery.start();
        }

        // Store status
        if (status != m_lastGrblStatus) m_lastGrblStatus = status;

        // Abort
        static double x = sNan;
        static double y = sNan;
        static double z = sNan;

        if (m_aborting) {
            switch (status) {
            case IDLE: // Idle
                if (!m_processingFile && m_resetCompleted) {
                    m_aborting = false;
                    restoreOffsets();
                    restoreParserState();
                    return;
                }
                break;
            case HOLD0: // Hold
            case HOLD1:
            case QUEUE:
                if (!m_reseting && compareCoordinates(x, y, z)) {
                    x = sNan;
                    y = sNan;
                    z = sNan;
                    grblReset();
                } else {
                    x = ui->txtMPosX->text().toDouble();
                    y = ui->txtMPosY->text().toDouble();
                    z = ui->txtMPosZ->text().toDouble();
                }
                break;
            }
        }
    }

    // Store work offset
    static QVector3D workOffset;
    static QRegExp wpx("WCO:([^,]*),([^,]*),([^,^>^|]*)");

    if (wpx.indexIn(data) != -1)
    {
        workOffset = QVector3D(wpx.cap(1).toDouble(), wpx.cap(2).

toDouble(), wpx.cap(3).toDouble()); }

    // Update work coordinates
    int prec = m_settings->units() == 0 ? 3 : 4;
    ui->txtWPosX->setText(QString::number(ui->txtMPosX->text().toDouble()

Esto separa la parte del "parsing" de "data receiving" quedando más claro todo.

¡Ya me vas contando!

Message ID: @.***>

sirias52 commented 4 months ago

Gracias por la ayuda. De hecho me gustaría poder hacer el fork y dejar elegible el método de comunicación . Por el momento trabajaré de forma hardcodeada, pero te comento que hay un proyecto de candle2 donde realiza exactamente lo que has dicho, de separar el parseo de texto de la comunicación y de hacer elegible el método de comunicación. https://github.com/Schildkroet/Candle2.

Pero él decidió trabajar con un protocolo llamado Grip ethernet , que ni idea de que sea. Así que lo dejo cerrado a ese protocolo. Yo igual me iba a basar luego con su código para modificar el que tengo y dejar elegible, separado, y todo eso para que no sea obscuro el código de candle. Lo malo que candle2 me ha dado algunos bug en modo serial y no quise estar corrigiendo esos bug, mientras que el otro es más estáble.

El otro que hay es iosender https://github.com/terjeio/iosender, pero me ha dado muchos problemas, y no es fluido. Este maneja websocket y telnet. En fin tengo muchos planes ahora que funciona con telnet el candle. Al igual que tu no soy un experto programador y prácticamente soy nuevo en Qt. Me gusta mucho y por eso seguiré aprendiendo.

Ya con fluid cnc funciona (toca probar muchas cosas) y debo probarlo con grblhall. Así ya habrá un sender confiable para estos firmware.

Si gustas y tiene tiempo podemos ver lo del fork o las mejoras del programa. Igual te puedo ayudar con lo que vayas hacer con el esp32.

On Mon, Jun 3, 2024, 7:04 AM Rafael Dellà Bort @.***> wrote:

Me alegro ;)

Si quieres ayuda para hacer un "fork" del proyecto original y añadir la posibilidad de comunicación a través de Telnet que sea elegible desde la GUI del programa, en vez de las modificaciones que has hecho "hardcodeadas", dímelo. Aunque ando un poco oxidado en Qt.... :P

Da la casualidad que tengo un par de proyectos con ESP32 XD. Pero bueno, no me vale la pena meterme en ello. Y menos sabiendo que ahora te funciona.

Por cierto, aunque te haya funcionado hacer el "split" de los datos que han llegado, deberías buscar por qué no va el último código que te pase. Si por el motivo que sea recibes media línea, tendrás dos medias líneas, en dos invocaciones del slot y, por lo tanto, datos corruptos. Los paquetes de datos por la red son de 1500 bytes. Quitando la cabecera IP y el protocolo Telnet, igual te quedan 1400 bytes "usables" (me lo invento). En esos 1400 bytes el CNC tiene de sobra para enviar las respuestas. Es más. Creo que cada vez que se invoca el slot de newData, tendrá una sola línea, ya que, por muy rápida que sea la máquina de control numérico, no lo será tanto como para hacer tantas acciones que sobrepasen la capacidad de los paquetes Ethernet. Aun así, llámalo purismo de programador, es mejor no asumir nada y dejarlo todo atado.

He estado mirando el código de Candle. Justamente donde está el slot onSerialPortReadyRead es un código muy obscuro y poco mantenible: mezcla la parte de comunicación con la parte de análisis del texto que llega por el puerto serie. Esto hace que añadir la funcionalidad de Telnet no sea limpio. Por eso has tenido que modificar los parámetros que pasas a las funciones para adaptarlas a Telnet. Lo ideal sería, como te comenté, mantener las dos opciones de comunicación; que el usuario decida cómo conectarse. Prueba esto:

void frmMain::onSerialPortReadyRead() { while( m_serialPort.canReadLine() ) parseReceivedLine(m_serialPort.readLine().trimmed()); }

define USE_SPLIT_TEXT

void frmMain::onTelnetPortReadyRead(const char *data, int count) {

ifdef USE_SPLIT_TEXT

for( const QByteArray &linea : QByteArray(data, count).split('\n') ) parseReceivedLine(linea.toStdString()).trimmed();

else

static QByteArray bytesLinea; for( int i = 0; i < count; ++i ) { if( ((msg[i] == '\n') || (msg[i] == '\r')) && !bytesLinea.isEmpty() ) { parseReceivedLine(linea.toStdString.trimmed()); bytesLinea.clear(); } }

endif

}

Añade la función parseReceivedLine(QString data) en la definición de la clase frmMain y modifica la actual onSerialPortReadyRead quedando así:

void frmMain::parseReceivedLine(QString data) { // Filter prereset responses if (m_reseting) { qDebug() << "reseting filter:" << data; if (!dataIsReset(data)) continue; else { m_reseting = false; m_timerStateQuery.setInterval(m_settings->queryStateTime()); } }

// Status response if (data[0] == '<') { int status = -1;

m_statusReceived = true;

// Update machine coordinates static QRegExp mpx("MPos:([^,]),([^,]),([^,^>^|]*)"); if (mpx.indexIn(data) != -1) { ui->txtMPosX->setText(mpx.cap(1)); ui->txtMPosY->setText(mpx.cap(2)); ui->txtMPosZ->setText(mpx.cap(3)); }

// Status static QRegExp stx("<([^,^>^|]*)"); if (stx.indexIn(data) != -1) { status = m_status.indexOf(stx.cap(1));

// Undetermined status if (status == -1) status = 0;

// Update status if (status != m_lastGrblStatus) { ui->txtStatus->setText(m_statusCaptions[status]); ui->txtStatus->setStyleSheet(QString("background-color: %1; color: %2;") .arg(m_statusBackColors [status]).arg(m_statusForeColors[status])); }

// Update controls ui->cmdRestoreOrigin->setEnabled(status == IDLE); ui->cmdSafePosition->setEnabled(status == IDLE); ui->cmdZeroXY->setEnabled(status == IDLE); ui->cmdZeroZ->setEnabled(status == IDLE); ui->chkTestMode->setEnabled(status != RUN && !m_processingFile); ui->chkTestMode->setChecked(status == CHECK); ui->cmdFilePause->setChecked(status == HOLD0 || status == HOLD1 || status == QUEUE); ui->cmdSpindle->setEnabled(!m_processingFile || status == HOLD0);

ifdef WINDOWS

if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS7) { if (m_taskBarProgress) m_taskBarProgress->setPaused(status == HOLD0 || status == HOLD1 || status == QUEUE); }

endif

// Update "elapsed time" timer if (m_processingFile) { QTime time(0, 0, 0); int elapsed = m_startTime.elapsed(); ui->glwVisualizer->setSpendTime(time.addMSecs(elapsed)); }

// Test for job complete if (m_processingFile && m_transferCompleted && ((status == IDLE && m_lastGrblStatus == RUN) || status == CHECK)) { qDebug() << "job completed:" << m_fileCommandIndex << m_currentModel->rowCount() - 1;

// Shadow last segment GcodeViewParse parser = m_currentDrawer->viewParser(); QList<LineSegment> list = parser->getLineSegmentList(); if (m_lastDrawnLineIndex < list.count()) { list[m_lastDrawnLineIndex]->setDrawn(true); m_currentDrawer->update(QList() << m_lastDrawnLineIndex); }

// Update state m_processingFile = false; m_fileProcessedCommandIndex = 0; m_lastDrawnLineIndex = 0; m_storedParserStatus.clear();

updateControlsState();

qApp->beep();

m_timerStateQuery.stop(); m_timerConnection.stop();

QMessageBox::information(this, qApp->applicationDisplayName(), tr("Job done.\nTime elapsed: %1") .arg(ui->glwVisualizer-> spendTime().toString("hh:mm:ss")));

m_timerStateQuery.setInterval(m_settings->queryStateTime()); m_timerConnection.start(); m_timerStateQuery.start(); }

// Store status if (status != m_lastGrblStatus) m_lastGrblStatus = status;

// Abort static double x = sNan; static double y = sNan; static double z = sNan;

if (m_aborting) { switch (status) { case IDLE: // Idle if (!m_processingFile && m_resetCompleted) { m_aborting = false; restoreOffsets(); restoreParserState(); return; } break; case HOLD0: // Hold case HOLD1: case QUEUE: if (!m_reseting && compareCoordinates(x, y, z)) { x = sNan; y = sNan; z = sNan; grblReset(); } else { x = ui->txtMPosX->text().toDouble(); y = ui->txtMPosY->text().toDouble(); z = ui->txtMPosZ->text().toDouble(); } break; } } }

// Store work offset static QVector3D workOffset; static QRegExp wpx("WCO:([^,]),([^,]),([^,^>^|]*)");

if (wpx.indexIn(data) != -1) { workOffset = QVector3D(wpx.cap(1).toDouble(), wpx.cap(2). toDouble(), wpx.cap(3).toDouble()); }

// Update work coordinates int prec = m_settings->units() == 0 ? 3 : 4; ui->txtWPosX->setText(QString::number(ui->txtMPosX->text().toDouble()

  • workOffset.x(), 'f', prec)); ui->txtWPosY->setText(QString::number(ui->txtMPosY->text().toDouble()
  • workOffset.y(), 'f', prec)); ui->txtWPosZ->setText(QString::number(ui->txtMPosZ->text().toDouble()
  • workOffset.z(), 'f', prec));

// Update tool position QVector3D toolPosition; if (!(status == CHECK && m_fileProcessedCommandIndex < m_currentModel->rowCount() - 1)) { toolPosition = QVector3D(toMetric(ui->txtWPosX->text().toDouble ()), toMetric(ui->txtWPosY->text(). toDouble()), toMetric(ui->txtWPosZ->text(). toDouble())); m_toolDrawer.setToolPosition(m_codeDrawer->getIgnoreZ() ? QVector3D(toolPosition.x(), toolPosition.y(), 0) : toolPosition); }

// toolpath shadowing if (m_processingFile && status != CHECK) { GcodeViewParse *parser = m_currentDrawer->viewParser();

bool toolOntoolpath = false;

QList drawnLines; QList<LineSegment*> list = parser->getLineSegmentList();

for (int i = m_lastDrawnLineIndex; i < list.count() && list.at(i)->getLineNumber() <= (m_currentModel->data(m_currentModel->index(m_fileProcessedCommandIndex, 4)).toInt() + 1); i++) { if (list.at(i)->contains(toolPosition)) { toolOntoolpath = true; m_lastDrawnLineIndex = i; break; } drawnLines << i; }

if (toolOntoolpath) { foreach (int i, drawnLines) { list.at(i)->setDrawn(true); } if (!drawnLines.isEmpty()) m_currentDrawer->update (drawnLines); } else if (m_lastDrawnLineIndex < list.count()) { qDebug() << "tool missed:" << list.at (m_lastDrawnLineIndex)->getLineNumber() << m_currentModel->data(m_currentModel->index(m_fileProcessedCommandIndex, 4)).toInt() << m_fileProcessedCommandIndex; } }

// Get overridings static QRegExp ov("Ov:([^,]),([^,]),([^,^>^|]*)"); if (ov.indexIn(data) != -1) { updateOverride(ui->slbFeedOverride, ov.cap(1).toInt(), 0x91); updateOverride(ui->slbSpindleOverride, ov.cap(3).toInt(), 0x9a);

int rapid = ov.cap(2).toInt(); ui->slbRapidOverride->setCurrentValue(rapid);

int target = ui->slbRapidOverride->isChecked() ? ui-> slbRapidOverride->value() : 100;

if (rapid != target) switch (target) { case 25: m_serialPort.write(QByteArray(1, char(0x97))); break; case 50: m_serialPort.write(QByteArray(1, char(0x96))); break; case 100: m_serialPort.write(QByteArray(1, char(0x95))); break; }

// Update pins state QString pinState; static QRegExp pn("Pn:([^|^>]*)"); if (pn.indexIn(data) != -1) { pinState.append(QString(tr("PS: %1")).arg(pn.cap(1))); }

// Process spindle state static QRegExp as("A:([^,^>^|]+)"); if (as.indexIn(data) != -1) { QString state = as.cap(1); m_spindleCW = state.contains("S"); if (state.contains("S") || state.contains("C")) { m_timerToolAnimation.start(25, this); ui->cmdSpindle->setChecked(true); } else { m_timerToolAnimation.stop(); ui->cmdSpindle->setChecked(false); }

if (!pinState.isEmpty()) pinState.append(" / "); pinState.append(QString(tr("AS: %1")).arg(as.cap(1))); } else { m_timerToolAnimation.stop(); ui->cmdSpindle->setChecked(false); } ui->glwVisualizer->setPinState(pinState); }

// Get feed/spindle values static QRegExp fs("FS:([^,]),([^,^|^>])"); if (fs.indexIn(data) != -1) { ui->glwVisualizer->setSpeedState((QString(tr("F/S: %1 / %2")). arg(fs.cap(1)).arg(fs.cap(2)))); }

} else if (data.length() > 0) {

// Processed commands if (m_commands.length() > 0 && !dataIsFloating(data) && !(m_commands[0].command != "[CTRL+X]" && dataIsReset(data))) {

static QString response; // Full response string

if ((m_commands[0].command != "[CTRL+X]" && dataIsEnd(data)) || (m_commands[0].command == "[CTRL+X]" && dataIsReset(data))) {

response.append(data);

// Take command from buffer CommandAttributes ca = m_commands.takeFirst(); QTextBlock tb = ui->txtConsole->document()-> findBlockByNumber(ca.consoleIndex); QTextCursor tc(tb);

// Restore absolute/relative coordinate system after jog if (ca.command.toUpper() == "$G" && ca.tableIndex == -2) { if (ui->chkKeyboardControl->isChecked()) m_absoluteCoordinates = response.contains("G90"); else if (response.contains("G90")) sendCommand("G90", -1, m_settings->showUICommands()); }

// Jog if (ca.command.toUpper().contains("$J=") && ca.tableIndex == -2) { jogStep(); }

// Process parser status if (ca.command.toUpper() == "$G" && ca.tableIndex == -3) { // Update status in visualizer window ui->glwVisualizer->setParserStatus(response.left( response.indexOf("; ")));

// Store parser status if (m_processingFile) storeParserState();

// Spindle speed QRegExp rx(".*S([\d\.]+)"); if (rx.indexIn(response) != -1) { double speed = toMetric(rx.cap(1).toDouble()); //RPM in imperial? ui->slbSpindle->setCurrentValue(speed); }

m_updateParserStatus = true; }

// Store origin if (ca.command == "$#" && ca.tableIndex == -2) { qDebug() << "Received offsets:" << response; QRegExp rx(".G92:([^,]),([^,]),([^\]])");

if (rx.indexIn(response) != -1) { if (m_settingZeroXY) { m_settingZeroXY = false; m_storedX = toMetric(rx.cap(1).toDouble()); m_storedY = toMetric(rx.cap(2).toDouble()); } else if (m_settingZeroZ) { m_settingZeroZ = false; m_storedZ = toMetric(rx.cap(3).toDouble()); } ui->cmdRestoreOrigin->setToolTip(QString(tr("Restore origin:\n%1, %2, %3")).arg(m_storedX).arg(m_storedY).arg(m_storedZ)); } }

// Homing response if ((ca.command.toUpper() == "$H" || ca.command.toUpper() == "$T") && m_homing) m_homing = false;

// Reset complete if (ca.command == "[CTRL+X]") { m_resetCompleted = true; m_updateParserStatus = true; }

// Clear command buffer on "M2" & "M30" command (old firmwares) if ((ca.command.contains("M2") || ca.command.contains("M30")) && response.contains("ok") && !response.contains("[Pgm End]")) { m_commands.clear(); m_queue.clear(); }

// Process probing on heightmap mode only from table commands if (ca.command.contains("G38.2") && m_heightMapMode && ca. tableIndex > -1) { // Get probe Z coordinate // "[PRB:0.000,0.000,0.000:0];ok" QRegExp rx(".PRB:([^,]),([^,]),([^]^:])"); double z = qQNaN(); if (rx.indexIn(response) != -1) { qDebug() << "probing coordinates:" << rx.cap(1) << rx.cap(2) << rx.cap(3); z = toMetric(rx.cap(3).toDouble()); }

static double firstZ; if (m_probeIndex == -1) { firstZ = z; z = 0; } else { // Calculate delta Z z -= firstZ;

// Calculate table indexes int row = trunc(m_probeIndex / m_heightMapModel. columnCount()); int column = m_probeIndex - row * m_heightMapModel. columnCount(); if (row % 2) column = m_heightMapModel.columnCount()

  • 1 - column;

// Store Z in table m_heightMapModel.setData(m_heightMapModel.index(row, column), z, Qt::UserRole); ui->tblHeightMap->update(m_heightMapModel.index( m_heightMapModel.rowCount() - 1 - row, column)); updateHeightMapInterpolationDrawer(); }

m_probeIndex++; }

// Change state query time on check mode on if (ca.command.contains(QRegExp("$[cC]"))) { m_timerStateQuery.setInterval(response.contains("Enable") ? 1000 : m_settings->queryStateTime()); }

// Add response to console if (tb.isValid() && tb.text() == ca.command) {

bool scrolledDown = ui->txtConsole->verticalScrollBar ()->value() == ui->txtConsole->verticalScrollBar()->maximum();

// Update text block numbers int blocksAdded = response.count("; ");

if (blocksAdded > 0) for (int i = 0; i < m_commands. count(); i++) { if (m_commands[i].consoleIndex != -1) m_commands[i]. consoleIndex += blocksAdded; }

tc.beginEditBlock(); tc.movePosition(QTextCursor::EndOfBlock);

tc.insertText(" < " + QString(response).replace("; ", " \r\n")); tc.endEditBlock();

if (scrolledDown) ui->txtConsole->verticalScrollBar()-> setValue(ui->txtConsole->verticalScrollBar()->maximum()); }

// Check queue if (m_queue.length() > 0) { CommandQueue cq = m_queue.takeFirst(); while ((bufferLength() + cq.command.length() + 1) <= BUFFERLENGTH) { sendCommand(cq.command, cq.tableIndex, cq. showInConsole); if (m_queue.isEmpty()) break; else cq = m_queue. takeFirst(); } }

// Add response to table, send next program commands if (m_processingFile) {

// Only if command from table if (ca.tableIndex > -1) { m_currentModel->setData(m_currentModel->index(ca. tableIndex, 2), GCodeItem::Processed); m_currentModel->setData(m_currentModel->index(ca. tableIndex, 3), response);

m_fileProcessedCommandIndex = ca.tableIndex;

if (ui->chkAutoScroll->isChecked() && ca.tableIndex != -1) { ui->tblProgram->scrollTo(m_currentModel->index( ca.tableIndex + 1, 0)); // TODO: Update by timer ui->tblProgram->setCurrentIndex(m_currentModel-> index(ca.tableIndex, 1)); } }

// Update taskbar progress

ifdef WINDOWS

if (QSysInfo::windowsVersion() >= QSysInfo::WV_WINDOWS7) { if (m_taskBarProgress) m_taskBarProgress->setValue (m_fileProcessedCommandIndex); }

endif

// Process error messages static bool holding = false; static QString errors;

if (ca.tableIndex > -1 && response.toUpper().contains( "ERROR") && !m_settings->ignoreErrors()) { errors.append(QString::number(ca.tableIndex + 1) + ": " + ca.command

  • " < " + response + "\n");

m_senderErrorBox->setText(tr("Error message(s) received:\n") + errors);

if (!holding) { holding = true; // Hold transmit while messagebox is visible response.clear();

m_serialPort.write("!"); m_senderErrorBox->checkBox()->setChecked(false); qApp->beep(); int result = m_senderErrorBox->exec();

holding = false; errors.clear(); if (m_senderErrorBox->checkBox()->isChecked()) m_settings->setIgnoreErrors(true); if (result == QMessageBox::Ignore) m_serialPort. write("~"); else on_cmdFileAbort_clicked(); } }

// Check transfer complete (last row always blank, last command row = rowcount - 2) if (m_fileProcessedCommandIndex == m_currentModel-> rowCount() - 2 || ca.command.contains(QRegExp("M0*2|M30"))) m_transferCompleted = true; // Send next program commands else if (!m_fileEndSent && (m_fileCommandIndex < m_currentModel->rowCount()) && !holding) sendNextFileCommands(); }

// Scroll to first line on "M30" command if (ca.command.contains("M30")) ui->tblProgram-> setCurrentIndex(m_currentModel->index(0, 1));

// Toolpath shadowing on check mode if (m_statusCaptions.indexOf(ui->txtStatus->text()) == CHECK) { GcodeViewParse parser = m_currentDrawer->viewParser(); QList<LineSegment> list = parser->getLineSegmentList();

if (!m_transferCompleted && m_fileProcessedCommandIndex < m_currentModel->rowCount() - 1) { int i; QList drawnLines;

for (i = m_lastDrawnLineIndex; i < list.count() && list.at(i)->getLineNumber() <= (m_currentModel->data(m_currentModel-> index(m_fileProcessedCommandIndex, 4)).toInt()); i++) { drawnLines << i; }

if (!drawnLines.isEmpty() && (i < list.count())) { m_lastDrawnLineIndex = i; QVector3D vec = list.at(i)->getEnd(); m_toolDrawer.setToolPosition(vec); }

foreach (int i, drawnLines) { list.at(i)->setDrawn(true); } if (!drawnLines.isEmpty()) m_currentDrawer->update (drawnLines); } else { foreach (LineSegment* s, list) { if (!qIsNaN(s->getEnd().length())) { m_toolDrawer.setToolPosition(s->getEnd()); break; } } } }

response.clear(); } else { response.append(data + "; "); }

} else { // Unprocessed responses qDebug() << "floating response:" << data;

// Handle hardware reset if (dataIsReset(data)) { qDebug() << "hardware reset";

m_processingFile = false; m_transferCompleted = true; m_fileCommandIndex = 0;

m_reseting = false; m_homing = false; m_lastGrblStatus = -1;

m_updateParserStatus = true; m_statusReceived = true;

m_commands.clear(); m_queue.clear();

updateControlsState(); } ui->txtConsole->appendPlainText(data); } } else { // Blank response // ui->txtConsole->appendPlainText(data); } }

Esto separa la parte del "parsing" de "data receiving" quedando más claro todo.

¡Ya me vas contando!

Message ID: @.***>

— Reply to this email directly, view it on GitHub https://github.com/silderan/QTelnet/issues/3#issuecomment-2145152918, or unsubscribe https://github.com/notifications/unsubscribe-auth/ACPACMXJGW3WJQZW4W4KX43ZFRSUTAVCNFSM6AAAAABIL7ZJJSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDCNBVGE2TEOJRHA . You are receiving this because you authored the thread.Message ID: @.***>