mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
26 Commits
fixing_tra
...
ProForm-Ge
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e2b4a7883 | ||
|
|
31fe6eedb7 | ||
|
|
af7d797642 | ||
|
|
723f8d0588 | ||
|
|
b618a85995 | ||
|
|
31272e66ee | ||
|
|
19ece0e7e5 | ||
|
|
372c5d3711 | ||
|
|
75ca498f2d | ||
|
|
1bbbba1283 | ||
|
|
7448e8ec4b | ||
|
|
8fdf541c75 | ||
|
|
1248a610ac | ||
|
|
add8fd7041 | ||
|
|
318e8842ba | ||
|
|
d157dbc385 | ||
|
|
8933a5b139 | ||
|
|
1b31b116b4 | ||
|
|
5b863b8d13 | ||
|
|
8d2178a129 | ||
|
|
030c6fdeeb | ||
|
|
62ceb953b8 | ||
|
|
4ae80ab492 | ||
|
|
c6fbdc4773 | ||
|
|
5951650e27 | ||
|
|
33ca78f302 |
522
src/QTelnet.cpp
Normal file
522
src/QTelnet.cpp
Normal file
@@ -0,0 +1,522 @@
|
||||
#include "QTelnet.h"
|
||||
|
||||
#include "QTelnet.h"
|
||||
#include <QHostAddress>
|
||||
|
||||
const char QTelnet::IACWILL[2] = { IAC, WILL };
|
||||
const char QTelnet::IACWONT[2] = { IAC, WONT };
|
||||
const char QTelnet::IACDO[2] = { IAC, DO };
|
||||
const char QTelnet::IACDONT[2] = { IAC, DONT };
|
||||
const char QTelnet::IACSB[2] = { IAC, SB };
|
||||
const char QTelnet::IACSE[2] = { IAC, SE };
|
||||
|
||||
char QTelnet::_sendCodeArray[2] = { IAC, 0 };
|
||||
char QTelnet::_arrCRLF[2] = { 13, 10 };
|
||||
char QTelnet::_arrCR[2] = { 13, 0 };
|
||||
|
||||
QTelnet::QTelnet(QObject *parent) :
|
||||
QTcpSocket(parent), m_actualSB(0)
|
||||
{
|
||||
connect( this, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(socketError(QAbstractSocket::SocketError)) );
|
||||
connect( this, SIGNAL(readyRead()), this, SLOT(onReadyRead()) );
|
||||
}
|
||||
|
||||
QString QTelnet::peerInfo() const
|
||||
{
|
||||
return QString("%1 (%2):%3").arg(peerName()).arg(peerAddress().toString()).arg(peerPort());
|
||||
}
|
||||
|
||||
bool QTelnet::isConnected() const
|
||||
{
|
||||
return state() == QAbstractSocket::ConnectedState;
|
||||
}
|
||||
|
||||
bool QTelnet::testBinaryMode() const
|
||||
{
|
||||
return m_receivedDX[(unsigned char)TELOPT_BINARY] == DO;
|
||||
}
|
||||
|
||||
void QTelnet::connectToHost(const QString &host, quint16 port)
|
||||
{
|
||||
if( !isConnected() )
|
||||
{
|
||||
resetProtocol();
|
||||
abort();
|
||||
QTcpSocket::connectToHost(host, port);
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::sendData(const QByteArray &ba)
|
||||
{
|
||||
if( isConnected() )
|
||||
transpose( ba.constData(), ba.count() );
|
||||
}
|
||||
|
||||
void QTelnet::socketError(QAbstractSocket::SocketError err)
|
||||
{
|
||||
Q_UNUSED(err);
|
||||
disconnectFromHost();
|
||||
}
|
||||
|
||||
void QTelnet::write(const char c)
|
||||
{
|
||||
QTcpSocket::write( (char*)&c, 1 );
|
||||
}
|
||||
|
||||
void QTelnet::setCustomCR(char cr, char cr2)
|
||||
{
|
||||
_arrCR[0] = cr;
|
||||
_arrCR[1] = cr2;
|
||||
}
|
||||
|
||||
void QTelnet::setCustomCRLF(char lf, char cr)
|
||||
{
|
||||
_arrCR[0] = lf;
|
||||
_arrCR[1] = cr;
|
||||
}
|
||||
|
||||
// Envia el codigo de control al servidor.
|
||||
void QTelnet::sendTelnetControl(char codigo)
|
||||
{
|
||||
_sendCodeArray[1] = codigo;
|
||||
QTcpSocket::write(_sendCodeArray, 2);
|
||||
}
|
||||
|
||||
void QTelnet::writeCustomCRLF()
|
||||
{
|
||||
QTcpSocket::write(_arrCRLF, 2);
|
||||
}
|
||||
|
||||
void QTelnet::writeCustomCR()
|
||||
{
|
||||
QTcpSocket::write(_arrCR, 2);
|
||||
}
|
||||
|
||||
/// Resetea los datos del protocolo. Debe llamarse cada vez que se inicia una conexión nueva.
|
||||
void QTelnet::resetProtocol()
|
||||
{
|
||||
for( int i = 0; i < 256; i++ )
|
||||
{
|
||||
m_receivedDX[i] =
|
||||
m_receivedWX[i] =
|
||||
m_sentDX[i] =
|
||||
m_sentWX[i] = 0;
|
||||
m_negotiationState = STATE_DATA;
|
||||
m_buffSB.clear();
|
||||
m_actualSB = 0;
|
||||
}
|
||||
m_oldWinSize.setHeight(-1);
|
||||
m_oldWinSize.setWidth(-1);
|
||||
}
|
||||
|
||||
void QTelnet::sendSB(char code, char *arr, int iLen)
|
||||
{
|
||||
write(IAC);
|
||||
write(SB);
|
||||
write(code);
|
||||
|
||||
QTcpSocket::write(arr, iLen);
|
||||
|
||||
write(IAC);
|
||||
write(SE);
|
||||
}
|
||||
void QTelnet::sendWindowSize()
|
||||
{
|
||||
if( isConnected() && (m_receivedDX[TELOPT_NAWS] == DO) && (m_oldWinSize != m_winSize) )
|
||||
{
|
||||
char size[4];
|
||||
|
||||
m_oldWinSize = m_winSize;
|
||||
size[0] = (m_winSize.width()>>8) & 0xFF;
|
||||
size[1] = m_winSize.width() & 0xFF;
|
||||
size[2] = (m_winSize.height()>>8) & 0xFF;
|
||||
size[3] = m_winSize.height() & 0xFF;
|
||||
sendSB(TELOPT_NAWS, size, 4);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle an incoming IAC SB type chars IAC SE
|
||||
void QTelnet::handleSB()
|
||||
{
|
||||
switch( m_actualSB )
|
||||
{
|
||||
case TELOPT_TTYPE:
|
||||
if( (m_buffSB.count() > 0) && ((unsigned char)m_buffSB[0] == (unsigned char)TELQUAL_SEND) )
|
||||
{
|
||||
QTcpSocket::write(IACSB, 2);
|
||||
write(TELOPT_TTYPE);
|
||||
write(TELQUAL_IS);
|
||||
/* FIXME: need more logic here if we use
|
||||
* more than one terminal type
|
||||
*/
|
||||
QTcpSocket::write("SiraggaTerminal", 15);
|
||||
QTcpSocket::write(IACSE, 2);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Analiza el texto saliente para que cumpla las normas del protocolo.
|
||||
// Además ya lo escribe en el socket.
|
||||
void QTelnet::transpose(const char *buf, int iLen)
|
||||
{
|
||||
for( int i = 0; i < iLen; i++ )
|
||||
{
|
||||
switch( buf[i] )
|
||||
{
|
||||
case IAC:
|
||||
// Escape IAC twice in stream ... to be telnet protocol compliant
|
||||
// this is there in binary and non-binary mode.
|
||||
write(IAC);
|
||||
write(IAC);
|
||||
break;
|
||||
case 10: // \n
|
||||
// We need to heed RFC 854. LF (\n) is 10, CR (\r) is 13
|
||||
// we assume that the Terminal sends \n for lf+cr and \r for just cr
|
||||
// linefeed+carriage return is CR LF
|
||||
|
||||
// En modo binario no se traduce nada.
|
||||
if( testBinaryMode() )
|
||||
write(buf[i]);
|
||||
else
|
||||
writeCustomCRLF();
|
||||
break;
|
||||
case 13: // \r
|
||||
// carriage return is CR NUL */
|
||||
|
||||
// En modo binario no se traduce nada.
|
||||
if( testBinaryMode() )
|
||||
write(buf[i]);
|
||||
else
|
||||
writeCustomCR();
|
||||
break;
|
||||
default:
|
||||
// all other characters are just copied
|
||||
write(buf[i]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::willsReply(char action, char reply)
|
||||
{
|
||||
if( (reply != m_sentDX[(unsigned char)action]) || (WILL != m_receivedWX[(unsigned char)action]) )
|
||||
{
|
||||
write(IAC);
|
||||
write(reply);
|
||||
write(action);
|
||||
|
||||
m_sentDX[(unsigned char)action] = reply;
|
||||
m_receivedWX[(unsigned char)action] = WILL;
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::wontsReply(char action, char reply)
|
||||
{
|
||||
if( (reply != m_sentDX[(unsigned char)action]) || (WONT != m_receivedWX[(unsigned char)action]) )
|
||||
{
|
||||
write(IAC);
|
||||
write(reply);
|
||||
write(action);
|
||||
|
||||
m_sentDX[(unsigned char)action] = reply;
|
||||
m_receivedWX[(unsigned char)action] = WONT;
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::doesReply(char action, char reply)
|
||||
{
|
||||
if( (reply != m_sentWX[(unsigned char)action]) || (DO != m_receivedDX[(unsigned char)action]) )
|
||||
{
|
||||
write(IAC);
|
||||
write(reply);
|
||||
write(action);
|
||||
|
||||
m_sentWX[(unsigned char)action] = reply;
|
||||
m_receivedDX[(unsigned char)action] = DO;
|
||||
}
|
||||
}
|
||||
|
||||
void QTelnet::dontsReply(char action, char reply)
|
||||
{
|
||||
if( (reply != m_sentWX[(unsigned char)action]) || (DONT != m_receivedDX[(unsigned char)action]) )
|
||||
{
|
||||
write(IAC);
|
||||
write(reply);
|
||||
write(action);
|
||||
|
||||
m_sentWX[(unsigned char)action] = reply;
|
||||
m_receivedDX[(unsigned char)action] = DONT;
|
||||
}
|
||||
}
|
||||
|
||||
// Analiza el buffer de entrada colocá ndolo en el buffer de procesado usando el protocolo telnet.
|
||||
qint64 QTelnet::doTelnetInProtocol(qint64 buffSize)
|
||||
{
|
||||
qint64 iIn, iOut;
|
||||
char b;
|
||||
|
||||
for( iIn = 0, iOut = 0; iIn < buffSize; iIn++ )
|
||||
{
|
||||
b = m_buffIncoming[iIn];
|
||||
|
||||
switch( m_negotiationState )
|
||||
{
|
||||
case STATE_DATA:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
m_negotiationState = STATE_IAC;
|
||||
break;
|
||||
case '\r':
|
||||
m_negotiationState = STATE_DATAR;
|
||||
break;
|
||||
case '\n':
|
||||
m_negotiationState = STATE_DATAN;
|
||||
break;
|
||||
default:
|
||||
m_buffProcessed[iOut++] = b;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_DATAN:
|
||||
case STATE_DATAR:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
m_negotiationState = STATE_IAC;
|
||||
break;
|
||||
case '\r':
|
||||
case '\n':
|
||||
m_buffProcessed[iOut++] = '\n';
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
default:
|
||||
m_buffProcessed[iOut++] = b;
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_IAC:
|
||||
switch( b )
|
||||
{
|
||||
case IAC: // Dos IAC seguidos, se intenta enviar un caracter con el valor IAC.
|
||||
m_negotiationState = STATE_DATA;
|
||||
m_buffProcessed[iOut++] = IAC;
|
||||
break;
|
||||
case WILL:
|
||||
m_negotiationState = STATE_IACWILL;
|
||||
break;
|
||||
case WONT:
|
||||
m_negotiationState = STATE_IACWONT;
|
||||
break;
|
||||
case DONT:
|
||||
m_negotiationState = STATE_IACDONT;
|
||||
break;
|
||||
case DO:
|
||||
m_negotiationState = STATE_IACDO;
|
||||
break;
|
||||
case EOR:
|
||||
emitEndOfRecord();
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case SB:
|
||||
m_negotiationState = STATE_IACSB;
|
||||
m_buffSB.clear();
|
||||
break;
|
||||
default:
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_IACWILL:
|
||||
switch( b )
|
||||
{
|
||||
case TELOPT_ECHO:
|
||||
emitEchoLocal(false);
|
||||
willsReply(b, DO);
|
||||
break;
|
||||
case TELOPT_SGA:
|
||||
willsReply(b, DO);
|
||||
break;
|
||||
case TELOPT_EOR:
|
||||
willsReply(b, DO);
|
||||
break;
|
||||
case TELOPT_BINARY:
|
||||
willsReply(b, DO);
|
||||
break;
|
||||
default:
|
||||
willsReply(b, DONT);
|
||||
break;
|
||||
}
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case STATE_IACWONT:
|
||||
switch(b)
|
||||
{
|
||||
case TELOPT_ECHO:
|
||||
emitEchoLocal(true);
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
case TELOPT_SGA:
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
case TELOPT_EOR:
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
case TELOPT_BINARY:
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
default:
|
||||
wontsReply(b, DONT);
|
||||
break;
|
||||
}
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case STATE_IACDO:
|
||||
switch( b )
|
||||
{
|
||||
case TELOPT_ECHO:
|
||||
doesReply(b, WILL);
|
||||
emitEchoLocal(true);
|
||||
break;
|
||||
case TELOPT_SGA:
|
||||
doesReply(b, WILL);
|
||||
break;
|
||||
case TELOPT_TTYPE:
|
||||
doesReply(b, WILL);
|
||||
break;
|
||||
case TELOPT_BINARY:
|
||||
doesReply(b, WILL);
|
||||
break;
|
||||
case TELOPT_NAWS:
|
||||
m_receivedDX[(unsigned char)b] = (unsigned char)DO;
|
||||
m_sentWX[(unsigned char)b] = (unsigned char)WILL;
|
||||
write(IAC);
|
||||
write(WILL);
|
||||
write(b);
|
||||
|
||||
// Enviamos el tamaño de la pantalla.
|
||||
sendWindowSize();
|
||||
break;
|
||||
default:
|
||||
doesReply(b, WONT);
|
||||
break;
|
||||
}
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case STATE_IACDONT:
|
||||
switch (b)
|
||||
{
|
||||
case TELOPT_ECHO:
|
||||
dontsReply(b, WONT);
|
||||
emitEchoLocal(false);
|
||||
break;
|
||||
case TELOPT_SGA:
|
||||
dontsReply(b, WONT);
|
||||
break;
|
||||
case TELOPT_NAWS:
|
||||
dontsReply(b, WONT);
|
||||
break;
|
||||
case TELOPT_BINARY:
|
||||
dontsReply(b, WONT);
|
||||
break;
|
||||
default:
|
||||
dontsReply(b, WONT);
|
||||
break;
|
||||
}
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case STATE_IACSB:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
// Entramos en estado IAC en la sub-negociación.
|
||||
m_negotiationState = STATE_IACSBIAC;
|
||||
break;
|
||||
default:
|
||||
// Iniciamos la sub-negociación.
|
||||
m_buffSB.clear();
|
||||
m_actualSB = b;
|
||||
m_negotiationState = STATE_IACSBDATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_IACSBDATA: // Estamos en datos de la subnegociación.
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
m_negotiationState = STATE_IACSBDATAIAC;
|
||||
break;
|
||||
default:
|
||||
m_buffSB.append(b);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case STATE_IACSBIAC:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
// Reiniciamos la sub-negociación.
|
||||
m_buffSB.clear();
|
||||
m_actualSB = b;
|
||||
m_negotiationState = STATE_IACSBDATA;
|
||||
default:
|
||||
// Salimos de la sub-negociación.
|
||||
m_negotiationState = STATE_DATA;
|
||||
}
|
||||
break;
|
||||
case STATE_IACSBDATAIAC:
|
||||
switch( b )
|
||||
{
|
||||
case IAC:
|
||||
m_negotiationState = STATE_IACSBDATA;
|
||||
m_buffSB.append(IAC);
|
||||
break;
|
||||
case SE:
|
||||
handleSB();
|
||||
m_actualSB = 0;
|
||||
m_buffSB.clear();
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
case SB:
|
||||
handleSB();
|
||||
m_buffSB.clear();
|
||||
m_negotiationState = STATE_IACSB;
|
||||
break;
|
||||
default:
|
||||
m_buffSB.clear();
|
||||
m_actualSB = 0;
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
m_negotiationState = STATE_DATA;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return iOut;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
136
src/QTelnet.h
Normal file
136
src/QTelnet.h
Normal file
@@ -0,0 +1,136 @@
|
||||
#ifndef QTELNET_H
|
||||
#define QTELNET_H
|
||||
|
||||
#include <QObject>
|
||||
#include <qtcpsocket.h>
|
||||
#include <qsize.h>
|
||||
#include <QString>
|
||||
|
||||
#define IncommingBufferSize (1500)
|
||||
|
||||
class QTelnet : public QTcpSocket
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum SocketStatus
|
||||
{
|
||||
Disconnected,
|
||||
Resolving, // Resolving host
|
||||
Connecting, // Connecting to host.
|
||||
Connected // Connected to host.
|
||||
};
|
||||
|
||||
protected:
|
||||
enum TelnetStateCodes
|
||||
{
|
||||
STATE_DATA = (char)0,
|
||||
STATE_IAC = (char)1,
|
||||
STATE_IACSB = (char)2,
|
||||
STATE_IACWILL = (char)3,
|
||||
STATE_IACDO = (char)4,
|
||||
STATE_IACWONT = (char)5,
|
||||
STATE_IACDONT = (char)6,
|
||||
STATE_IACSBIAC = (char)7,
|
||||
STATE_IACSBDATA = (char)8,
|
||||
STATE_IACSBDATAIAC = (char)9,
|
||||
STATE_DATAR = (char)10,
|
||||
STATE_DATAN = (char)11
|
||||
};
|
||||
enum TelnetCodes
|
||||
{
|
||||
// Negociación entrada/salida (cliente<->servidor)
|
||||
IAC = (char)255, // Inicia la secuencia para la negociación telnet.
|
||||
EOR = (char)239, // Estando en la negociación, End Of Record.
|
||||
WILL = (char)251, // Estando en la negociación, Acepta el protocolo?
|
||||
WONT = (char)252, // Estando en la negociación, Acepta el protocolo?
|
||||
DO = (char)253, // Estando en la negociación, Protocolo aceptado.
|
||||
DONT = (char)254, // Estando en la negociación, Protocolo denegado.
|
||||
SB = (char)250, // Estando en la negociación, inicia secuencia de sub-negociación.
|
||||
SE = (char)240, // Estando en la sub-negociación, fin de sub-negociación.
|
||||
|
||||
// Negociación de salida (cliente->servidor)
|
||||
TELOPT_BINARY = (char)0, // Estando en la negociación, pide modo binario.
|
||||
TELOPT_ECHO = (char)1, // Estando en la negociación, pide echo local.
|
||||
TELOPT_SGA = (char)2, // Estando en la negociación, pide Supress Go Ahead.
|
||||
TELOPT_EOR = (char)25, // Estando en la negociación, informa End Of Record.
|
||||
TELOPT_NAWS = (char)31, // Estando en la negociación, Negotiate Abaut Window Size.
|
||||
TELOPT_TTYPE = (char)24 // Estando en la negociación, Terminal Type.
|
||||
};
|
||||
enum TelnetQualifiers
|
||||
{
|
||||
TELQUAL_IS = (char)0,
|
||||
TELQUAL_SEND = (char)1
|
||||
};
|
||||
|
||||
private:
|
||||
static const char IACWILL[2];
|
||||
static const char IACWONT[2];
|
||||
static const char IACDO[2];
|
||||
static const char IACDONT[2];
|
||||
static const char IACSB[2];
|
||||
static const char IACSE[2];
|
||||
static char _sendCodeArray[2];
|
||||
static char _arrCRLF[2];
|
||||
static char _arrCR[2];
|
||||
|
||||
QSize m_winSize; // Tamaño de la pantalla en caracteres.
|
||||
QSize m_oldWinSize; // Tamaño de la pantalla que se envió por última vez al server. Para no enviar el mismo dato.
|
||||
enum TelnetStateCodes m_negotiationState;
|
||||
char m_receivedDX[256]; // What IAC DO(NT) request do we have received already ?
|
||||
char m_receivedWX[256]; // What IAC WILL/WONT request do we have received already ?
|
||||
char m_sentDX[256]; // What IAC DO/DONT request do we have sent already ?
|
||||
char m_sentWX[256]; // What IAC WILL/WONT request do we have sent already ?
|
||||
void resetProtocol();
|
||||
|
||||
char m_buffIncoming[IncommingBufferSize];
|
||||
char m_buffProcessed[IncommingBufferSize];
|
||||
QByteArray m_buffSB;
|
||||
int m_actualSB;
|
||||
|
||||
void emitEndOfRecord() { Q_EMIT(endOfRecord()); }
|
||||
void emitEchoLocal(bool bEcho) { Q_EMIT(echoLocal(bEcho)); }
|
||||
|
||||
void sendTelnetControl(char codigo);
|
||||
void handleSB(void);
|
||||
void transpose(const char *buf, int iLen);
|
||||
|
||||
void willsReply(char action, char reply);
|
||||
void wontsReply(char action, char reply);
|
||||
void doesReply(char action, char reply);
|
||||
void dontsReply(char action, char reply);
|
||||
|
||||
void sendSB(char code, char *arr, int iLen);
|
||||
qint64 doTelnetInProtocol(qint64 buffSize);
|
||||
|
||||
public:
|
||||
explicit QTelnet(QObject *parent = 0);
|
||||
|
||||
virtual void connectToHost(const QString &host, quint16 port);
|
||||
void sendData(const QByteArray &ba);
|
||||
void setCustomCRLF(char lf = 13, char cr = 10);
|
||||
void setCustomCR(char cr = 10, char cr2 = 0);
|
||||
|
||||
void writeCustomCRLF();
|
||||
void writeCustomCR();
|
||||
|
||||
void write(const char c);
|
||||
|
||||
bool isConnected() const;
|
||||
bool testBinaryMode() const;
|
||||
void setWindSize(QSize s) {m_winSize = s;}
|
||||
void sendWindowSize();
|
||||
|
||||
QString peerInfo()const;
|
||||
|
||||
signals:
|
||||
void newData(const char *buff, int len);
|
||||
void endOfRecord();
|
||||
void echoLocal(bool echo);
|
||||
|
||||
private slots:
|
||||
void socketError(QAbstractSocket::SocketError err);
|
||||
void onReadyRead();
|
||||
};
|
||||
|
||||
#endif // QTELNET_H
|
||||
@@ -399,6 +399,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
settings.value(QZSettings::fakedevice_treadmill, QZSettings::default_fakedevice_treadmill).toBool();
|
||||
bool pafers_treadmill = settings.value(QZSettings::pafers_treadmill, QZSettings::default_pafers_treadmill).toBool();
|
||||
QString proformtdf4ip = settings.value(QZSettings::proformtdf4ip, QZSettings::default_proformtdf4ip).toString();
|
||||
QString proformtdf1ip = settings.value(QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip).toString();
|
||||
QString proformtreadmillip =
|
||||
settings.value(QZSettings::proformtreadmillip, QZSettings::default_proformtreadmillip).toString();
|
||||
QString nordictrack_2950_ip =
|
||||
@@ -656,6 +657,21 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(proformWifiBike);
|
||||
} else if (!proformtdf1ip.isEmpty() && !proformTelnetBike) {
|
||||
this->stopDiscovery();
|
||||
proformTelnetBike =
|
||||
new proformtelnetbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
emit deviceConnected(b);
|
||||
connect(proformTelnetBike, &bluetoothdevice::connectedAndDiscovered, this,
|
||||
&bluetooth::connectedAndDiscovered);
|
||||
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
|
||||
connect(proformTelnetBike, &proformtelnetbike::debug, this, &bluetooth::debug);
|
||||
proformTelnetBike->deviceDiscovered(b);
|
||||
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
|
||||
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
|
||||
emit searchingStop();
|
||||
}
|
||||
this->signalBluetoothDeviceConnected(proformTelnetBike);
|
||||
#ifndef Q_OS_IOS
|
||||
} else if (!computrainerSerialPort.isEmpty() && !computrainerBike) {
|
||||
this->stopDiscovery();
|
||||
@@ -2552,6 +2568,11 @@ void bluetooth::restart() {
|
||||
delete proformWifiBike;
|
||||
proformWifiBike = nullptr;
|
||||
}
|
||||
if (proformTelnetBike) {
|
||||
|
||||
delete proformTelnetBike;
|
||||
proformTelnetBike = nullptr;
|
||||
}
|
||||
if (proformWifiTreadmill) {
|
||||
|
||||
delete proformWifiTreadmill;
|
||||
@@ -2965,6 +2986,8 @@ bluetoothdevice *bluetooth::device() {
|
||||
return cscBike;
|
||||
} else if (proformWifiBike) {
|
||||
return proformWifiBike;
|
||||
} else if (proformTelnetBike) {
|
||||
return proformTelnetBike;
|
||||
} else if (proformWifiTreadmill) {
|
||||
return proformWifiTreadmill;
|
||||
} else if (nordictrackifitadbTreadmill) {
|
||||
|
||||
@@ -86,6 +86,7 @@
|
||||
#include "proformellipticaltrainer.h"
|
||||
#include "proformrower.h"
|
||||
#include "proformtreadmill.h"
|
||||
#include "proformtelnetbike.h"
|
||||
#include "proformwifibike.h"
|
||||
#include "proformwifitreadmill.h"
|
||||
#include "schwinn170bike.h"
|
||||
@@ -187,6 +188,7 @@ class bluetooth : public QObject, public SignalHandler {
|
||||
pelotonbike *pelotonBike = nullptr;
|
||||
proformrower *proformRower = nullptr;
|
||||
proformbike *proformBike = nullptr;
|
||||
proformtelnetbike *proformTelnetBike = nullptr;
|
||||
proformwifibike *proformWifiBike = nullptr;
|
||||
proformwifitreadmill *proformWifiTreadmill = nullptr;
|
||||
proformelliptical *proformElliptical = nullptr;
|
||||
|
||||
487
src/proformtelnetbike.cpp
Normal file
487
src/proformtelnetbike.cpp
Normal file
@@ -0,0 +1,487 @@
|
||||
#include "proformtelnetbike.h"
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include "keepawakehelper.h"
|
||||
#endif
|
||||
#include "virtualbike.h"
|
||||
#include <QDateTime>
|
||||
#include <QFile>
|
||||
#include <QMetaEnum>
|
||||
#include <QSettings>
|
||||
#include <QThread>
|
||||
#include <QtXml>
|
||||
#include <chrono>
|
||||
#include <math.h>
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
proformtelnetbike::proformtelnetbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain) {
|
||||
QSettings settings;
|
||||
m_watt.setType(metric::METRIC_WATT);
|
||||
target_watts.setType(metric::METRIC_WATT);
|
||||
Speed.setType(metric::METRIC_SPEED);
|
||||
refresh = new QTimer(this);
|
||||
this->noWriteResistance = noWriteResistance;
|
||||
this->noHeartService = noHeartService;
|
||||
this->bikeResistanceGain = bikeResistanceGain;
|
||||
this->bikeResistanceOffset = bikeResistanceOffset;
|
||||
initDone = false;
|
||||
connect(refresh, &QTimer::timeout, this, &proformtelnetbike::update);
|
||||
refresh->start(200ms);
|
||||
|
||||
bool ok = connect(&telnet, &QTelnet::newData, this, &proformtelnetbike::characteristicChanged);
|
||||
|
||||
ergModeSupported = true; // IMPORTANT, only for this bike
|
||||
|
||||
connectToDevice();
|
||||
|
||||
initRequest = true;
|
||||
|
||||
// ******************************************* virtual bike init *************************************
|
||||
if (!firstStateChanged && !this->hasVirtualDevice()
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
&& !h
|
||||
#endif
|
||||
#endif
|
||||
) {
|
||||
QSettings settings;
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence =
|
||||
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence) {
|
||||
qDebug() << "ios_peloton_workaround activated!";
|
||||
h = new lockscreen();
|
||||
h->virtualbike_ios();
|
||||
} else
|
||||
#endif
|
||||
#endif
|
||||
if (virtual_device_enabled) {
|
||||
emit debug(QStringLiteral("creating virtual bike interface..."));
|
||||
auto virtualBike =
|
||||
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
|
||||
// connect(virtualBike,&virtualbike::debug ,this,& proformtelnetbike::debug);
|
||||
connect(virtualBike, &virtualbike::changeInclination, this, &proformtelnetbike::changeInclination);
|
||||
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
|
||||
}
|
||||
}
|
||||
firstStateChanged = 1;
|
||||
// ********************************************************************************************************
|
||||
}
|
||||
|
||||
void proformtelnetbike::connectToDevice() {
|
||||
QSettings settings;
|
||||
// https://github.com/dawsontoth/zwifit/blob/e846501149a6c8fbb03af8d7b9eab20474624883/src/ifit.js
|
||||
telnet.connectToHost(settings.value(QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip).toString(), 23);
|
||||
telnet.waitForConnected();
|
||||
telnet.sendData("./utconfig\n");
|
||||
QThread::sleep(1);
|
||||
telnet.sendData("2\n"); // modify variables
|
||||
|
||||
}
|
||||
|
||||
/*
|
||||
void proformtelnetbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
|
||||
bool wait_for_response) {
|
||||
QEventLoop loop;
|
||||
QTimer timeout;
|
||||
if (wait_for_response) {
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
} else {
|
||||
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
|
||||
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
|
||||
}
|
||||
|
||||
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic,
|
||||
QByteArray((const char *)data, data_len));
|
||||
|
||||
if (!disable_log) {
|
||||
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
|
||||
QStringLiteral(" // ") + info);
|
||||
}
|
||||
|
||||
loop.exec();
|
||||
}*/
|
||||
|
||||
resistance_t proformtelnetbike::resistanceFromPowerRequest(uint16_t power) {
|
||||
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();
|
||||
|
||||
QSettings settings;
|
||||
|
||||
double watt_gain = settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
|
||||
double watt_offset = settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
|
||||
|
||||
for (resistance_t i = 1; i < max_resistance; i++) {
|
||||
if (((wattsFromResistance(i) * watt_gain) + watt_offset) <= power &&
|
||||
((wattsFromResistance(i + 1) * watt_gain) + watt_offset) >= power) {
|
||||
qDebug() << QStringLiteral("resistanceFromPowerRequest")
|
||||
<< ((wattsFromResistance(i) * watt_gain) + watt_offset)
|
||||
<< ((wattsFromResistance(i + 1) * watt_gain) + watt_offset) << power;
|
||||
return i;
|
||||
}
|
||||
}
|
||||
if (power < ((wattsFromResistance(1) * watt_gain) + watt_offset))
|
||||
return 1;
|
||||
else
|
||||
return max_resistance;
|
||||
}
|
||||
|
||||
uint16_t proformtelnetbike::wattsFromResistance(resistance_t resistance) {
|
||||
|
||||
if (currentCadence().value() == 0)
|
||||
return 0;
|
||||
|
||||
switch (resistance) {
|
||||
case 0:
|
||||
case 1:
|
||||
// -13.5 + 0.999x + 0.00993x²
|
||||
return (-13.5 + (0.999 * currentCadence().value()) + (0.00993 * pow(currentCadence().value(), 2)));
|
||||
case 2:
|
||||
// -17.7 + 1.2x + 0.0116x²
|
||||
return (-17.7 + (1.2 * currentCadence().value()) + (0.0116 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 3:
|
||||
// -17.5 + 1.24x + 0.014x²
|
||||
return (-17.5 + (1.24 * currentCadence().value()) + (0.014 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 4:
|
||||
// -20.9 + 1.43x + 0.016x²
|
||||
return (-20.9 + (1.43 * currentCadence().value()) + (0.016 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 5:
|
||||
// -27.9 + 1.75x+0.0172x²
|
||||
return (-27.9 + (1.75 * currentCadence().value()) + (0.0172 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 6:
|
||||
// -26.7 + 1.9x + 0.0201x²
|
||||
return (-26.7 + (1.9 * currentCadence().value()) + (0.0201 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 7:
|
||||
// -33.5 + 2.23x + 0.0225x²
|
||||
return (-33.5 + (2.23 * currentCadence().value()) + (0.0225 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 8:
|
||||
// -36.5+2.5x+0.0262x²
|
||||
return (-36.5 + (2.5 * currentCadence().value()) + (0.0262 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 9:
|
||||
// -38+2.62x+0.0305x²
|
||||
return (-38.0 + (2.62 * currentCadence().value()) + (0.0305 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 10:
|
||||
// -41.2+2.85x+0.0327x²
|
||||
return (-41.2 + (2.85 * currentCadence().value()) + (0.0327 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 11:
|
||||
// -43.4+3.01x+0.0359x²
|
||||
return (-43.4 + (3.01 * currentCadence().value()) + (0.0359 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 12:
|
||||
// -46.8+3.23x+0.0364x²
|
||||
return (-46.8 + (3.23 * currentCadence().value()) + (0.0364 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 13:
|
||||
// -49+3.39x+0.0371x²
|
||||
return (-49.0 + (3.39 * currentCadence().value()) + (0.0371 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 14:
|
||||
// -53.4+3.55x+0.0383x²
|
||||
return (-53.4 + (3.55 * currentCadence().value()) + (0.0383 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 15:
|
||||
// -49.9+3.37x+0.0429x²
|
||||
return (-49.9 + (3.37 * currentCadence().value()) + (0.0429 * pow(currentCadence().value(), 2)));
|
||||
|
||||
case 16:
|
||||
default:
|
||||
// -47.1+3.25x+0.0464x²
|
||||
return (-47.1 + (3.25 * currentCadence().value()) + (0.0464 * pow(currentCadence().value(), 2)));
|
||||
}
|
||||
}
|
||||
|
||||
void proformtelnetbike::sendFrame(QByteArray frame) {
|
||||
telnet.sendData(frame);
|
||||
qDebug() << " >> " << frame;
|
||||
}
|
||||
|
||||
void proformtelnetbike::update() {
|
||||
qDebug() << "websocket.state()" << telnet.isConnected();
|
||||
|
||||
if (initRequest) {
|
||||
initRequest = false;
|
||||
btinit();
|
||||
emit connectedAndDiscovered();
|
||||
} else if (telnet.isConnected()) {
|
||||
update_metrics(false, watts());
|
||||
|
||||
// updating the treadmill console every second
|
||||
if (sec1Update++ == (500 / refresh->interval())) {
|
||||
sec1Update = 0;
|
||||
// updateDisplay(elapsed);
|
||||
}
|
||||
|
||||
if (requestStart != -1) {
|
||||
emit debug(QStringLiteral("starting..."));
|
||||
|
||||
// btinit();
|
||||
|
||||
requestStart = -1;
|
||||
emit bikeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
|
||||
requestStop = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool proformtelnetbike::inclinationAvailableByHardware() { return true; }
|
||||
|
||||
resistance_t proformtelnetbike::pelotonToBikeResistance(int pelotonResistance) {
|
||||
if (pelotonResistance <= 10) {
|
||||
return 1;
|
||||
}
|
||||
if (pelotonResistance <= 20) {
|
||||
return 2;
|
||||
}
|
||||
if (pelotonResistance <= 25) {
|
||||
return 3;
|
||||
}
|
||||
if (pelotonResistance <= 30) {
|
||||
return 4;
|
||||
}
|
||||
if (pelotonResistance <= 35) {
|
||||
return 5;
|
||||
}
|
||||
if (pelotonResistance <= 40) {
|
||||
return 6;
|
||||
}
|
||||
if (pelotonResistance <= 45) {
|
||||
return 7;
|
||||
}
|
||||
if (pelotonResistance <= 50) {
|
||||
return 8;
|
||||
}
|
||||
if (pelotonResistance <= 55) {
|
||||
return 9;
|
||||
}
|
||||
if (pelotonResistance <= 60) {
|
||||
return 10;
|
||||
}
|
||||
if (pelotonResistance <= 65) {
|
||||
return 11;
|
||||
}
|
||||
if (pelotonResistance <= 70) {
|
||||
return 12;
|
||||
}
|
||||
if (pelotonResistance <= 75) {
|
||||
return 13;
|
||||
}
|
||||
if (pelotonResistance <= 80) {
|
||||
return 14;
|
||||
}
|
||||
if (pelotonResistance <= 85) {
|
||||
return 15;
|
||||
}
|
||||
if (pelotonResistance <= 100) {
|
||||
return 16;
|
||||
}
|
||||
return Resistance.value();
|
||||
}
|
||||
|
||||
void proformtelnetbike::serviceDiscovered(const QBluetoothUuid &gatt) {
|
||||
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
|
||||
}
|
||||
|
||||
void proformtelnetbike::characteristicChanged(const char *buff, int len) {
|
||||
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
|
||||
QSettings settings;
|
||||
QString heartRateBeltName =
|
||||
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
|
||||
bool disable_hr_frommachinery =
|
||||
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
|
||||
bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();
|
||||
|
||||
QByteArray newValue = QByteArray::fromRawData(buff, len);
|
||||
emit debug(QStringLiteral(" << ") + newValue);
|
||||
|
||||
if(newValue.contains("Shared Memory Management Utility")) {
|
||||
emit debug(QStringLiteral("Ready to start the poll"));
|
||||
sendFrame("2\n"); // current watt
|
||||
} else if(newValue.contains("Enter New Value")) {
|
||||
if(poolIndex >= 4) {
|
||||
if(!erg_mode) {
|
||||
sendFrame((QString::number(requestInclination) + "\n").toLocal8Bit()); // target incline
|
||||
qDebug() << "forceInclination" << requestInclination;
|
||||
requestInclination = -100;
|
||||
} else {
|
||||
double r = requestPower;
|
||||
if (settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble() <= 2.00) {
|
||||
if (settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble() != 1.0) {
|
||||
qDebug() << QStringLiteral("request watt value was ") << r
|
||||
<< QStringLiteral("but it will be transformed to")
|
||||
<< r / settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
|
||||
}
|
||||
r /= settings.value(QZSettings::watt_gain, QZSettings::default_watt_gain).toDouble();
|
||||
}
|
||||
if (settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble() < 0) {
|
||||
if (settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble() != 0.0) {
|
||||
qDebug() << QStringLiteral("request watt value was ") << r
|
||||
<< QStringLiteral("but it will be transformed to")
|
||||
<< r - settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
|
||||
}
|
||||
r -= settings.value(QZSettings::watt_offset, QZSettings::default_watt_offset).toDouble();
|
||||
}
|
||||
sendFrame((QString::number(r) + "\n").toLocal8Bit()); // target watt
|
||||
qDebug() << "forceWatt" << r;
|
||||
requestPower = -1;
|
||||
}
|
||||
poolIndex = 0;
|
||||
} else {
|
||||
sendFrame("q\n"); // quit
|
||||
}
|
||||
} else if(newValue.contains("Enter Variable Offset")) {
|
||||
qDebug() << "poolIndex" << poolIndex;
|
||||
bool done = false;
|
||||
do {
|
||||
switch (poolIndex)
|
||||
{
|
||||
case 0:
|
||||
sendFrame("124\n"); // current watt
|
||||
done = true;
|
||||
break;
|
||||
case 1:
|
||||
sendFrame("40\n"); // current rpm
|
||||
done = true;
|
||||
break;
|
||||
case 2:
|
||||
sendFrame("34\n"); // current speed
|
||||
done = true;
|
||||
break;
|
||||
case 3:
|
||||
if(!erg_mode) {
|
||||
if(requestInclination != -100) {
|
||||
sendFrame("45\n"); // target incline
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
poolIndex = 99;
|
||||
} else {
|
||||
if(requestPower != -1) {
|
||||
sendFrame("125\n"); // target watt
|
||||
done = true;
|
||||
}
|
||||
else
|
||||
poolIndex = 99;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
poolIndex++;
|
||||
if(poolIndex > 4)
|
||||
poolIndex = 0;
|
||||
} while(!done);
|
||||
}
|
||||
|
||||
QStringList packet = QString::fromLocal8Bit(newValue).split(" ");
|
||||
qDebug() << packet;
|
||||
if (newValue.contains("Current Watts")) {
|
||||
double watt = packet[3].toDouble();
|
||||
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
|
||||
.toString()
|
||||
.startsWith(QStringLiteral("Disabled")))
|
||||
m_watt = watt;
|
||||
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
|
||||
} else if (newValue.contains("Cur RPM")) {
|
||||
double RPM = packet[3].toDouble();
|
||||
Cadence = RPM;
|
||||
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
|
||||
|
||||
if (Cadence.value() > 0) {
|
||||
CrankRevs++;
|
||||
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
|
||||
}
|
||||
} else if (newValue.contains("Cur KPH")) {
|
||||
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
|
||||
double kph = packet[3].toDouble();
|
||||
Speed = kph;
|
||||
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
|
||||
} else {
|
||||
Speed = metric::calculateSpeedFromPower(
|
||||
watts(), Inclination.value(), Speed.value(),
|
||||
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
|
||||
}
|
||||
}
|
||||
|
||||
if (watts()) {
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts()) + 1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
|
||||
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
|
||||
//* 3.5) / 200 ) / 60
|
||||
Distance += ((Speed.value() / (double)3600.0) /
|
||||
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
|
||||
}
|
||||
/*
|
||||
Resistance = resistance;
|
||||
m_pelotonResistance = (100 / 32) * Resistance.value();
|
||||
emit resistanceRead(Resistance.value()); */
|
||||
|
||||
/*
|
||||
if (!disable_hr_frommachinery && !values[QStringLiteral("Chest Pulse")].isUndefined()) {
|
||||
Heart = values[QStringLiteral("Chest Pulse")].toString().toDouble();
|
||||
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
|
||||
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
|
||||
}*/
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
else
|
||||
#endif
|
||||
{
|
||||
if (disable_hr_frommachinery && heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
|
||||
update_hr_from_external();
|
||||
}
|
||||
}
|
||||
|
||||
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#ifndef IO_UNDER_QT
|
||||
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
|
||||
bool ios_peloton_workaround =
|
||||
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
|
||||
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
|
||||
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
|
||||
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
|
||||
emit debug(QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value()));
|
||||
emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
|
||||
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime)); */
|
||||
}
|
||||
|
||||
void proformtelnetbike::btinit() { initDone = true; }
|
||||
|
||||
void proformtelnetbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
|
||||
}
|
||||
|
||||
bool proformtelnetbike::connected() { return telnet.isConnected(); }
|
||||
|
||||
uint16_t proformtelnetbike::watts() { return m_watt.value(); }
|
||||
110
src/proformtelnetbike.h
Normal file
110
src/proformtelnetbike.h
Normal file
@@ -0,0 +1,110 @@
|
||||
#ifndef PROFORMTELNETBIKE_H
|
||||
#define PROFORMTELNETBIKE_H
|
||||
|
||||
|
||||
#include <QAbstractOAuth2>
|
||||
#include <QObject>
|
||||
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
#include <QDesktopServices>
|
||||
#include <QHttpMultiPart>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QOAuth2AuthorizationCodeFlow>
|
||||
#include <QOAuthHttpServerReplyHandler>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QUrlQuery>
|
||||
#include <QtCore/qbytearray.h>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QtCore/qcoreapplication.h>
|
||||
#else
|
||||
#include <QtGui/qguiapplication.h>
|
||||
#endif
|
||||
#include <QtCore/qlist.h>
|
||||
#include <QtCore/qmutex.h>
|
||||
#include <QtCore/qscopedpointer.h>
|
||||
#include <QtCore/qtimer.h>
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
|
||||
#include "bike.h"
|
||||
|
||||
#include "QTelnet.h"
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
#include "ios/lockscreen.h"
|
||||
#endif
|
||||
|
||||
class proformtelnetbike : public bike {
|
||||
Q_OBJECT
|
||||
public:
|
||||
proformtelnetbike(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
|
||||
double bikeResistanceGain);
|
||||
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
|
||||
resistance_t resistanceFromPowerRequest(uint16_t power) override;
|
||||
resistance_t maxResistance() override { return max_resistance; }
|
||||
bool inclinationAvailableByHardware() override;
|
||||
bool connected() override;
|
||||
|
||||
private:
|
||||
QTelnet telnet;
|
||||
resistance_t max_resistance = 100;
|
||||
resistance_t min_resistance = -20;
|
||||
double max_incline_supported = 20;
|
||||
void connectToDevice();
|
||||
uint16_t wattsFromResistance(resistance_t resistance);
|
||||
double GetDistanceFromPacket(QByteArray packet);
|
||||
QTime GetElapsedFromPacket(QByteArray packet);
|
||||
void btinit();
|
||||
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
|
||||
bool wait_for_response = false);
|
||||
void startDiscover();
|
||||
void sendPoll();
|
||||
uint16_t watts() override;
|
||||
void sendFrame(QByteArray frame);
|
||||
|
||||
QTimer *refresh;
|
||||
uint8_t counterPoll = 0;
|
||||
uint8_t bikeResistanceOffset = 4;
|
||||
double bikeResistanceGain = 1.0;
|
||||
|
||||
uint8_t sec1Update = 0;
|
||||
QString lastPacket;
|
||||
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
|
||||
uint8_t firstStateChanged = 0;
|
||||
metric target_watts;
|
||||
|
||||
bool initDone = false;
|
||||
bool initRequest = false;
|
||||
|
||||
bool noWriteResistance = false;
|
||||
bool noHeartService = false;
|
||||
|
||||
uint8_t poolIndex = 0;
|
||||
|
||||
#ifdef Q_OS_IOS
|
||||
lockscreen *h = 0;
|
||||
#endif
|
||||
|
||||
signals:
|
||||
void disconnected();
|
||||
void debug(QString string);
|
||||
|
||||
public slots:
|
||||
void deviceDiscovered(const QBluetoothDeviceInfo &device);
|
||||
|
||||
private slots:
|
||||
|
||||
void characteristicChanged(const char *buff, int len);
|
||||
|
||||
void serviceDiscovered(const QBluetoothUuid &gatt);
|
||||
void update();
|
||||
};
|
||||
#endif // PROFORMTELNETBIKE_H
|
||||
@@ -72,11 +72,13 @@ DEFINES += QT_DEPRECATED_WARNINGS IO_UNDER_QT SMTP_BUILD NOMINMAX
|
||||
# include(../qtzeroconf/qtzeroconf.pri)
|
||||
|
||||
SOURCES += \
|
||||
$$PWD/QTelnet.cpp \
|
||||
$$PWD/bkoolbike.cpp \
|
||||
$$PWD/csafe.cpp \
|
||||
$$PWD/csaferower.cpp \
|
||||
$$PWD/eliteariafan.cpp \
|
||||
$$PWD/fakerower.cpp \
|
||||
$$PWD/proformtelnetbike.cpp \
|
||||
$$PWD/virtualdevice.cpp \
|
||||
$$PWD/androidactivityresultreceiver.cpp \
|
||||
$$PWD/androidadblog.cpp \
|
||||
@@ -281,10 +283,12 @@ else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
INCLUDEPATH += fit-sdk/
|
||||
|
||||
HEADERS += \
|
||||
$$PWD/QTelnet.h \
|
||||
$$PWD/bkoolbike.h \
|
||||
$$PWD/csafe.h \
|
||||
$$PWD/csaferower.h \
|
||||
$$PWD/eliteariafan.h \
|
||||
$$PWD/proformtelnetbike.h \
|
||||
$$PWD/windows_zwift_workout_paddleocr_thread.h \
|
||||
$$PWD/fakerower.h \
|
||||
virtualdevice.h \
|
||||
|
||||
@@ -686,10 +686,12 @@ const QString QZSettings::nordictrack_s20_treadmill = QStringLiteral("nordictrac
|
||||
const QString QZSettings::freemotion_coachbike_b22_7 = QStringLiteral("freemotion_coachbike_b22_7");
|
||||
const QString QZSettings::proform_cycle_trainer_300_ci = QStringLiteral("proform_cycle_trainer_300_ci");
|
||||
const QString QZSettings::kingsmith_encrypt_g1_walking_pad = QStringLiteral("kingsmith_encrypt_g1_walking_pad");
|
||||
const QString QZSettings::proformtdf1ip = QStringLiteral("proformtdf1ip");
|
||||
const QString QZSettings::default_proformtdf1ip = QStringLiteral("");
|
||||
const QString QZSettings::proform_bike_225_csx = QStringLiteral("proform_bike_225_csx");
|
||||
const QString QZSettings::proform_treadmill_l6_0s = QStringLiteral("proform_treadmill_l6_0s");
|
||||
|
||||
const uint32_t allSettingsCount = 578;
|
||||
const uint32_t allSettingsCount = 579;
|
||||
|
||||
QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
|
||||
@@ -1271,9 +1273,10 @@ QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::nordictrack_s20_treadmill, QZSettings::default_nordictrack_s20_treadmill},
|
||||
{QZSettings::freemotion_coachbike_b22_7, QZSettings::default_freemotion_coachbike_b22_7},
|
||||
{QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci},
|
||||
{QZSettings::kingsmith_encrypt_g1_walking_pad, QZSettings::default_kingsmith_encrypt_g1_walking_pad},
|
||||
{QZSettings::kingsmith_encrypt_g1_walking_pad, QZSettings::default_kingsmith_encrypt_g1_walking_pad},
|
||||
{QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx},
|
||||
{QZSettings::proform_treadmill_l6_0s, QZSettings::default_proform_treadmill_l6_0s},
|
||||
{QZSettings::proformtdf1ip, QZSettings::default_proformtdf1ip},
|
||||
};
|
||||
|
||||
void QZSettings::qDebugAllSettings(bool showDefaults) {
|
||||
|
||||
@@ -1935,6 +1935,9 @@ class QZSettings {
|
||||
static const QString proform_treadmill_l6_0s;
|
||||
static constexpr bool default_proform_treadmill_l6_0s = false;
|
||||
|
||||
static const QString proformtdf1ip;
|
||||
static const QString default_proformtdf1ip;
|
||||
|
||||
/**
|
||||
* @brief Write the QSettings values using the constants from this namespace.
|
||||
* @param showDefaults Optionally indicates if the default should be shown with the key.
|
||||
|
||||
@@ -858,6 +858,7 @@ import QtQuick.Dialogs 1.0
|
||||
|
||||
// from version 2.16.30
|
||||
property bool proform_treadmill_l6_0s: false
|
||||
property string proformtdf1ip: ""
|
||||
}
|
||||
|
||||
function paddingZeros(text, limit) {
|
||||
@@ -3049,6 +3050,29 @@ import QtQuick.Dialogs 1.0
|
||||
onClicked: { settings.proform_bike_sb = checked; window.settings_restart_to_apply = true; }
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
text: qsTr("TDF1 IP:")
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
TextField {
|
||||
id: proformTDF1IPTextField
|
||||
text: settings.proformtdf1ip
|
||||
horizontalAlignment: Text.AlignRight
|
||||
Layout.fillHeight: false
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
//inputMethodHints: Qt.ImhFormattedNumbersOnly
|
||||
onAccepted: settings.proformtdf1ip = text
|
||||
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
|
||||
}
|
||||
Button {
|
||||
text: "OK"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: { settings.proformtdf1ip = proformTDF1IPTextField.text; window.settings_restart_to_apply = true; toast.show("Setting saved!"); }
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
|
||||
Reference in New Issue
Block a user