diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d6d86a32e..d586fd2ab 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -89,7 +89,7 @@ jobs: - uses: msys2/setup-msys2@v2 with: - install: mingw-w64-x86_64-toolchain mingw-w64-x86_64-qt5-webview + install: mingw-w64-x86_64-toolchain mingw-w64-x86_64-qt5-webview mingw-w64-x86_64-protobuf mingw-w64-x86_64-abseil-cpp msystem: mingw64 release: false @@ -136,6 +136,10 @@ jobs: - name: Build run: | + #cp "C:/mingw64/bin/libabsl*.*" . + #cp "C:/mingw64/lib/libabsl*.*" . + #cp "C:/mingw64/bin/libproto*.*" . + #cp "C:/mingw64/lib/libproto*.*" . qmake make -j8 cd src/debug @@ -147,6 +151,11 @@ jobs: cp "C:/mingw64/bin/libwinpthread-1.dll" . cp "C:/mingw64/bin/libgcc_s_seh-1.dll" . cp "C:/mingw64/bin/libstdc++-6.dll" . + cp "C:/mingw64/bin/zlib1.dll" . + cp "C:/mingw64/bin/libabsl*.*" . + cp "C:/mingw64/lib/libabsl*.*" . + cp "C:/mingw64/bin/libproto*.*" . + cp "C:/mingw64/lib/libproto*.*" . cp ../../../icons/iOS/iTunesArtwork@2x.png . cp ../../AppxManifest.xml . cp ../../windows/*.py . @@ -163,6 +172,10 @@ jobs: - name: Build without python run: | + #cp "C:/mingw64/bin/libabsl*.*" . + #cp "C:/mingw64/lib/libabsl*.*" . + #cp "C:/mingw64/bin/libproto*.*" . + #cp "C:/mingw64/lib/libproto*.*" . qmake make -j8 cd src/debug @@ -174,6 +187,11 @@ jobs: cp "C:/mingw64/bin/libwinpthread-1.dll" . cp "C:/mingw64/bin/libgcc_s_seh-1.dll" . cp "C:/mingw64/bin/libstdc++-6.dll" . + cp "C:/mingw64/bin/zlib1.dll" . + cp "C:/mingw64/bin/libabsl*.*" . + cp "C:/mingw64/lib/libabsl*.*" . + cp "C:/mingw64/bin/libproto*.*" . + cp "C:/mingw64/lib/libproto*.*" . cp ../../../icons/iOS/iTunesArtwork@2x.png . cp ../../AppxManifest.xml . cp ../../../windows_openssl/*.* . @@ -407,14 +425,14 @@ jobs: path: "src/qthttpserver" - name: Install packages required to run QZ inside workflow - run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev + run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5serialbus* libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev cmake protobuf-compiler libprotobuf-dev - name: Install Qt uses: jurplel/install-qt-action@v3 with: version: '5.15.2' host: 'linux' - modules: 'qtnetworkauth qtcharts' + modules: 'qtnetworkauth qtcharts qtserial' cache: 'true' cache-key-prefix: 'install-qt-action-linux' diff --git a/src/ConsoleReader.cpp b/src/ConsoleReader.cpp new file mode 100644 index 000000000..5a0a03a97 --- /dev/null +++ b/src/ConsoleReader.cpp @@ -0,0 +1,89 @@ +//#if defined(Q_OS_LINUX) +#if 1 +#include "ConsoleReader.h" +#include +#include +#include + +static struct termios oldSettings; +static struct termios newSettings; + +/* Initialize new terminal i/o settings */ +void initTermios(int echo) { + tcgetattr(0, &oldSettings); /* grab old terminal i/o settings */ + newSettings = oldSettings; /* make new settings same as old settings */ + newSettings.c_lflag &= ~ICANON; /* disable buffered i/o */ + newSettings.c_lflag &= echo ? ECHO : ~ECHO; /* set echo mode */ + tcsetattr(0, TCSANOW, &newSettings); /* use these new terminal i/o settings now */ +} + +/* Restore old terminal i/o settings */ +void resetTermios(void) { tcsetattr(0, TCSANOW, &oldSettings); } + +/* Read 1 character without echo */ +char getch(void) { return getchar(); } + +ConsoleReader::ConsoleReader(bluetooth *bt) { + bluetoothManager = bt; + initTermios(0); +} + +ConsoleReader::~ConsoleReader() { resetTermios(); } + +void ConsoleReader::run() { + forever { + char key = getch(); + qDebug() << "key pressed" << key; + if (key == 'q') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + double speed = ((treadmill *)bluetoothManager->device())->currentSpeed().value(); + ((treadmill *)bluetoothManager->device())->changeSpeed(speed + 0.5); + } + } else if (key == 'w') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + double speed = ((treadmill *)bluetoothManager->device())->currentSpeed().value(); + ((treadmill *)bluetoothManager->device())->changeSpeed(speed - 0.5); + } + } else if (key == 'a') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + double inclination = ((treadmill *)bluetoothManager->device())->currentInclination().value(); + ((treadmill *)bluetoothManager->device())->changeInclination(inclination + 0.5, inclination + 0.5); + } + } else if (key == 's') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + double inclination = ((treadmill *)bluetoothManager->device())->currentInclination().value(); + ((treadmill *)bluetoothManager->device())->changeInclination(inclination - 0.5, inclination - 0.5); + } + } else if (key == '1') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + ((treadmill *)bluetoothManager->device())->changeSpeed(5); + } + } else if (key == '2') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + double inclination = ((treadmill *)bluetoothManager->device())->currentInclination().value(); + ((treadmill *)bluetoothManager->device())->changeInclination(inclination + 0.5, inclination + 0.5); + } + } else if (key == '3') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + ((treadmill *)bluetoothManager->device())->changeSpeed(10); + } + } else if (key == '4') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + double speed = ((treadmill *)bluetoothManager->device())->currentSpeed().value(); + ((treadmill *)bluetoothManager->device())->changeSpeed(speed - 0.5); + } + } else if (key == '5') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + double inclination = ((treadmill *)bluetoothManager->device())->currentInclination().value(); + ((treadmill *)bluetoothManager->device())->changeInclination(inclination - 0.5, inclination - 0.5); + } + } else if (key == '6') { + if (bluetoothManager->device() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) { + double speed = ((treadmill *)bluetoothManager->device())->currentSpeed().value(); + ((treadmill *)bluetoothManager->device())->changeSpeed(speed + 0.5); + } + } + emit KeyPressed(key); + } +} +#endif diff --git a/src/ConsoleReader.h b/src/ConsoleReader.h new file mode 100644 index 000000000..66cdbcf80 --- /dev/null +++ b/src/ConsoleReader.h @@ -0,0 +1,21 @@ +#ifndef CONSOLEREADER_H +#define CONSOLEREADER_H + +#include "bluetooth.h" +#include + +class ConsoleReader : public QThread { + Q_OBJECT + signals: + void KeyPressed(char ch); + + public: + ConsoleReader(bluetooth *bt); + ~ConsoleReader(); + void run(); + + private: + bluetooth *bluetoothManager; +}; + +#endif /* CONSOLEREADER_H */ diff --git a/src/Home.qml b/src/Home.qml index b7c3fa982..ed0111fec 100644 --- a/src/Home.qml +++ b/src/Home.qml @@ -23,6 +23,13 @@ HomeForm { signal plus_clicked(string name) signal minus_clicked(string name) signal largeButton_clicked(string name) + signal keyPressed(int key) + + Keys.onPressed: (event)=> { + console.log("Keys.onPressed " + event.key) + // Emit a signal with the pressed key + keyPressed(event.key) + } Settings { id: settings diff --git a/src/devices/bluetooth.cpp b/src/devices/bluetooth.cpp index 3556fcf04..f757a5df8 100644 --- a/src/devices/bluetooth.cpp +++ b/src/devices/bluetooth.cpp @@ -112,6 +112,7 @@ void bluetooth::finished() { QString nordictrack_2950_ip = settings.value(QZSettings::nordictrack_2950_ip, QZSettings::default_nordictrack_2950_ip).toString(); QString tdf_10_ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString(); + bool gpio_treadmill = settings.value(QStringLiteral("gpio_treadmill"), false).toBool(); QString proform_elliptical_ip = settings.value(QZSettings::proform_elliptical_ip, QZSettings::default_proform_elliptical_ip).toString(); bool fake_bike = settings.value(QZSettings::applewatch_fakedevice, QZSettings::default_applewatch_fakedevice).toBool(); @@ -121,7 +122,7 @@ void bluetooth::finished() { bool fakedevice_treadmill = settings.value(QZSettings::fakedevice_treadmill, QZSettings::default_fakedevice_treadmill).toBool(); // wifi devices on windows - if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty() || fake_bike || fakedevice_elliptical || fakedevice_rower || fakedevice_treadmill || !proform_elliptical_ip.isEmpty() || antbike) { + if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty() || fake_bike || fakedevice_elliptical || fakedevice_rower || fakedevice_treadmill || !proform_elliptical_ip.isEmpty() || antbike || gpio_treadmill) { // faking a bluetooth device qDebug() << "faking a bluetooth device for nordictrack_2950_ip"; deviceDiscovered(QBluetoothDeviceInfo()); @@ -434,6 +435,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) { powerSensorName.startsWith(QStringLiteral("Disabled")) || power_as_bike || power_as_treadmill; bool eliteRizerFound = eliteRizerName.startsWith(QStringLiteral("Disabled")); bool eliteSterzoSmartFound = eliteSterzoSmartName.startsWith(QStringLiteral("Disabled")); + bool gpio_treadmill = settings.value(QStringLiteral("gpio_treadmill"), false).toBool(); bool fake_bike = settings.value(QZSettings::applewatch_fakedevice, QZSettings::default_applewatch_fakedevice).toBool(); bool fakedevice_elliptical = @@ -661,6 +663,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) { emit searchingStop(); } this->signalBluetoothDeviceConnected(fakeBike); +#if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) + } else if (gpio_treadmill && !gpioTreadmill) { + discoveryAgent->stop(); + gpioTreadmill = new gpiotreadmill(pollDeviceTime, noConsole, noHeartService, 0.8, 0); + emit deviceConnected(b); + connect(gpioTreadmill, &bluetoothdevice::connectedAndDiscovered, this, + &bluetooth::connectedAndDiscovered); + connect(gpioTreadmill, &gpiotreadmill::debug, this, &bluetooth::debug); + connect(gpioTreadmill, &gpiotreadmill::inclinationChanged, this, &bluetooth::inclinationChanged); + // connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart())); + // connect(this, SIGNAL(searchingStop()), gpioTreadmill, SLOT(searchingStop())); //NOTE: Commented due + // to #358 + if (!discoveryAgent->isActive()) { + emit searchingStop(); + } + this->signalBluetoothDeviceConnected(gpioTreadmill); +#endif } else if (fakedevice_elliptical && !fakeElliptical) { this->stopDiscovery(); fakeElliptical = new fakeelliptical(noWriteResistance, noHeartService, false); @@ -2950,6 +2969,13 @@ void bluetooth::restart() { delete fakeBike; fakeBike = nullptr; } +#if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) + if (gpioTreadmill) { + + delete gpioTreadmill; + gpioTreadmill = nullptr; + } +#endif if (fakeElliptical) { delete fakeElliptical; @@ -3373,6 +3399,10 @@ bluetoothdevice *bluetooth::device() { return powerTreadmill; } else if (fakeBike) { return fakeBike; +#if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) + } else if (gpioTreadmill) { + return gpioTreadmill; +#endif } else if (fakeElliptical) { return fakeElliptical; } else if (fakeRower) { diff --git a/src/devices/bluetooth.h b/src/devices/bluetooth.h index f5df33c07..22921b2b5 100644 --- a/src/devices/bluetooth.h +++ b/src/devices/bluetooth.h @@ -96,6 +96,9 @@ #include "devices/proformwifitreadmill/proformwifitreadmill.h" #include "devices/schwinn170bike/schwinn170bike.h" #include "devices/schwinnic4bike/schwinnic4bike.h" +#if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) +#include "gpiotreadmill.h" +#endif #include "signalhandler.h" #include "devices/skandikawiribike/skandikawiribike.h" #include "devices/smartrowrower/smartrowrower.h" @@ -195,6 +198,9 @@ class bluetooth : public QObject, public SignalHandler { trxappgateusbelliptical *trxappgateusbElliptical = nullptr; echelonconnectsport *echelonConnectSport = nullptr; yesoulbike *yesoulBike = nullptr; +#if defined(Q_OS_WIN) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) + gpiotreadmill *gpioTreadmill = nullptr; +#endif flywheelbike *flywheelBike = nullptr; nordictrackelliptical *nordictrackElliptical = nullptr; nordictrackifitadbtreadmill *nordictrackifitadbTreadmill = nullptr; diff --git a/src/gpiotreadmill.cpp b/src/gpiotreadmill.cpp new file mode 100644 index 000000000..fab472d33 --- /dev/null +++ b/src/gpiotreadmill.cpp @@ -0,0 +1,366 @@ +#include "gpiotreadmill.h" +#include "ios/lockscreen.h" +#include "keepawakehelper.h" +#include "virtualdevices/virtualtreadmill.h" +#include +#include +#include +#include +#include +#include +//#define Q_OS_RASPI 0 +#ifdef Q_OS_RASPI +#include +#else +#define OUTPUT 1 +QModbusReply *gpiotreadmill::lastRequest; +QModbusClient *gpiotreadmill::modbusDevice = nullptr; +void gpiotreadmill::digitalWrite(int pin, int state) { + const int server_address = 255; + QModbusDataUnit writeUnit(QModbusDataUnit::Coils, pin, 1); + writeUnit.setValue(0, state); + if(modbusDevice) { + QModbusReply* r = nullptr; + int retry = 0; + do { + qDebug() << "modbus sending retry" << retry++; + r = modbusDevice->sendWriteRequest(writeUnit, server_address); + } while(r == nullptr); + } + else + qDebug() << "modbusDevice nullptr!"; + + qDebug() << QStringLiteral("switch pin ") + QString::number(pin) + QStringLiteral(" to ") + QString::number(state); +} + +void pinMode(int pin, int state) { + qDebug() << QStringLiteral("init pin ") + QString::number(pin) + QStringLiteral(" to ") + QString::number(state); +} + +int wiringPiSetup() { + return 0; +} +#endif +using namespace std::chrono_literals; + + +gpioWorkerThread::gpioWorkerThread(QObject *parent, QString name, uint8_t pinUp, uint8_t pinDown, double step, double currentValue, QSemaphore *semaphore): QThread(parent), + name{name}, currentValue{currentValue}, pinUp{pinUp}, pinDown{pinDown}, step{step}, semaphore{semaphore} +{ + pinMode(pinUp, OUTPUT); + pinMode(pinDown, OUTPUT); + gpiotreadmill::digitalWrite(pinUp, 0); + gpiotreadmill::digitalWrite(pinDown, 0); +} + +void gpioWorkerThread::setRequestValue(double request) +{ + this->requestValue = request; +} + +void gpioWorkerThread::run() { + if (requestValue > currentValue) { + while (requestValue > currentValue) { + qDebug() << QStringLiteral("increasing ") + name + " from " + QString::number(currentValue) + " to " + QString::number(requestValue); + semaphore->acquire(); + gpiotreadmill::digitalWrite(pinUp, 1); + QThread::msleep(GPIO_KEEP_MS); + gpiotreadmill::digitalWrite(pinUp, 0); + QThread::msleep(GPIO_REBOUND_MS); + semaphore->release(); + currentValue += step; + if(QThread::currentThread()->isInterruptionRequested()) { + qDebug() << "Interrupting set " + name; + return; + } + } + } else { + while (requestValue < currentValue) { + qDebug() << QStringLiteral("decreasing ") + name + " from " + QString::number(currentValue) + " to " + QString::number(requestValue); + semaphore->acquire(); + gpiotreadmill::digitalWrite(pinDown, 1); + QThread::msleep(GPIO_KEEP_MS); + gpiotreadmill::digitalWrite(pinDown, 0); + QThread::msleep(GPIO_REBOUND_MS); + semaphore->release(); + currentValue -= step; + if(QThread::currentThread()->isInterruptionRequested()) { + qDebug() << "Interrupting set " + name; + return; + } + } + } + QThread::msleep(50); +} + +gpiotreadmill::gpiotreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService, double forceInitSpeed, + double forceInitInclination) { + m_watt.setType(metric::METRIC_WATT); + Speed.setType(metric::METRIC_SPEED); + this->noConsole = noConsole; + this->noHeartService = noHeartService; + + if (wiringPiSetup() == -1) { + qDebug() << QStringLiteral("wiringPiSetup ERROR!"); + exit(1); + } + + modbusDevice = new QModbusRtuSerialMaster(this); + modbusDevice->setConnectionParameter(QModbusDevice::SerialPortNameParameter, + "COM4"); + modbusDevice->setConnectionParameter(QModbusDevice::SerialParityParameter, + QSerialPort::Parity::NoParity); + modbusDevice->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, + QSerialPort::Baud9600); + modbusDevice->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, + QSerialPort::Data8); + modbusDevice->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, + QSerialPort::StopBits::OneStop); + + modbusDevice->setTimeout(50); + modbusDevice->setNumberOfRetries(3); + + qDebug() << "modbus Connecting..."; + + while (!modbusDevice->connectDevice()) { + qDebug() << "modbus Connetion Error. Retrying..."; + } + + qDebug() << "modbus Connected!"; + + + pinMode(OUTPUT_START, OUTPUT); + pinMode(OUTPUT_STOP, OUTPUT); + digitalWrite(OUTPUT_START, 0); + digitalWrite(OUTPUT_STOP, 0); + + if (forceInitSpeed > 0) { + lastSpeed = forceInitSpeed; + } + + if (forceInitInclination > 0) { + lastInclination = forceInitInclination; + } + + semaphore = new QSemaphore(1); + speedThread = new gpioWorkerThread(this, "speed", OUTPUT_SPEED_UP, OUTPUT_SPEED_DOWN, SPEED_STEP, forceInitSpeed, semaphore); + inclineThread = new gpioWorkerThread(this, "incline", OUTPUT_INCLINE_UP, OUTPUT_INCLINE_DOWN, INCLINATION_STEP, forceInitInclination, semaphore); + + refresh = new QTimer(this); + initDone = false; + connect(refresh, &QTimer::timeout, this, &gpiotreadmill::update); + refresh->start(pollDeviceTime); + + Speed = 0.8; +} + +gpiotreadmill::~gpiotreadmill() { + speedThread->requestInterruption(); + speedThread->quit(); + speedThread->wait(); + delete speedThread; + inclineThread->requestInterruption(); + inclineThread->quit(); + inclineThread->wait(); + delete inclineThread; + delete semaphore; + modbusDevice->disconnectDevice(); +} + +void gpiotreadmill::onReadReady() { + +} + +void gpiotreadmill::changeInclinationRequested(double grade, double percentage) { + if (percentage < 0) + percentage = 0; + changeInclination(grade, percentage); +} + +void gpiotreadmill::forceSpeed(double requestSpeed) { + qDebug() << QStringLiteral("gpiotreadmill.cpp: request set speed ") + QString::number(Speed.value()) + QStringLiteral(" to ") + QString::number(requestSpeed); + if (speedThread->isRunning()) + { + speedThread->requestInterruption(); + speedThread->quit(); + speedThread->wait(); + + } + speedThread->setRequestValue(requestSpeed); + speedThread->start(); + + Speed = requestSpeed; /* we are on the way to the requested speed */ +} + +void gpiotreadmill::forceIncline(double requestIncline) { + qDebug() << QStringLiteral("gpiotreadmill.cpp: request set Incline ") + QString::number(Inclination.value()) + QStringLiteral(" to ") + QString::number(requestIncline); + + if (inclineThread->isRunning()) + { + inclineThread->requestInterruption(); + inclineThread->quit(); + inclineThread->wait(); + + } + inclineThread->setRequestValue(requestIncline); + inclineThread->start(); + + Inclination = requestIncline; /* we are on the way to the requested incline */ +} + +void gpiotreadmill::update() { + + QSettings settings; + // ******************************************* virtual treadmill init ************************************* + if (!firstInit && !virtualTreadMill && !virtualBike) { + bool virtual_device_enabled = settings.value("virtual_device_enabled", true).toBool(); + bool virtual_device_force_bike = settings.value("virtual_device_force_bike", false).toBool(); + + emit connectedAndDiscovered(); + + if (virtual_device_enabled) { + if (!virtual_device_force_bike) { + qDebug() << "creating virtual treadmill interface..."; + virtualTreadMill = new virtualtreadmill(this, noHeartService); + connect(virtualTreadMill, &virtualtreadmill::debug, this, &gpiotreadmill::debug); + connect(virtualTreadMill, &virtualtreadmill::changeInclination, this, + &gpiotreadmill::changeInclinationRequested); + } else { + qDebug() <<"creating virtual bike interface..."; + virtualBike = new virtualbike(this); + connect(virtualBike, &virtualbike::changeInclination, this, &gpiotreadmill::changeInclinationRequested); + } + firstInit = 1; + } + } + // ******************************************************************************************************** + + // debug("Domyos Treadmill RSSI " + QString::number(bluetoothDevice.rssi())); + + double heart = 0; + QString heartRateBeltName = + settings.value(QStringLiteral("heart_rate_belt_name"), QStringLiteral("Disabled")).toString(); + +#ifdef Q_OS_ANDROID + if (settings.value("ant_heart", false).toBool()) + Heart = (uint8_t)KeepAwakeHelper::heart(); + else +#endif + { + if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) { + + if (heart == 0) { + +#ifdef Q_OS_IOS +#ifndef IO_UNDER_QT + lockscreen h; + long appleWatchHeartRate = h.heartRate(); + h.setKcal(KCal.value()); + h.setDistance(Distance.value()); + Heart = appleWatchHeartRate; + qDebug() << "Current Heart from Apple Watch: " << QString::number(appleWatchHeartRate); +#endif +#endif + } else + + Heart = heart; + } + } + + if (!firstCharacteristicChanged) { + if (watts(settings.value(QStringLiteral("weight"), 75.0).toFloat())) + KCal += + ((((0.048 * ((double)watts(settings.value(QStringLiteral("weight"), 75.0).toFloat())) + 1.19) * + settings.value(QStringLiteral("weight"), 75.0).toFloat() * 3.5) / + 200.0) / + (60000.0 / ((double)lastTimeCharacteristicChanged.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)(lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime())))); + lastTimeCharacteristicChanged = QDateTime::currentDateTime(); + } + + qDebug() << QStringLiteral("Current speed: ") + QString::number(Speed.value()); + qDebug() << QStringLiteral("Current incline: ") + QString::number(Inclination.value()); + qDebug() << QStringLiteral("Current heart: ") + QString::number(Heart.value()); + qDebug() << QStringLiteral("Current KCal: ") + QString::number(KCal.value()); + qDebug() << QStringLiteral("Current KCal from the machine: ") + QString::number(KCal.value()); + qDebug() << QStringLiteral("Current Distance: ") + QString::number(Distance.value()); + qDebug() << QStringLiteral("Current Distance Calculated: ") + QString::number(Distance.value()); + + firstCharacteristicChanged = false; + + update_metrics(true, watts(settings.value(QStringLiteral("weight"), 75.0).toFloat())); + + // updating the treadmill console every second + if (sec1Update++ >= (1000 / refresh->interval())) { + } + + // byte 3 - 4 = elapsed time + // byte 17 = inclination + { + if (requestSpeed != -1) { + if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) { + qDebug() << QStringLiteral("writing speed ") + QString::number(requestSpeed); + + forceSpeed(requestSpeed); + } + requestSpeed = -1; + } + if (requestInclination != -1) { + // only 0.5 steps ara avaiable + requestInclination = qRound(requestInclination * 2.0) / 2.0; + if (requestInclination != currentInclination().value() && requestInclination >= 0 && + requestInclination <= 15) { + qDebug() << QStringLiteral("writing incline ") + QString::number(requestInclination); + + forceIncline(requestInclination); + } + requestInclination = -1; + } + if (requestStart != -1) { + qDebug() << QStringLiteral("starting..."); + if (lastSpeed == 0.0) { + + lastSpeed = 0.5; + } + digitalWrite(OUTPUT_START, 1); + QThread::msleep(GPIO_KEEP_MS); + digitalWrite(OUTPUT_START, 0); + requestStart = -1; + Speed = 0.8; + emit tapeStarted(); + } + if (requestStop != -1) { + qDebug() << QStringLiteral("stopping..."); + digitalWrite(OUTPUT_STOP, 1); + QThread::msleep(GPIO_KEEP_MS); + digitalWrite(OUTPUT_STOP, 0); + requestStop = -1; + } + if (requestFanSpeed != -1) { + qDebug() << QStringLiteral("changing fan speed..."); + + requestFanSpeed = -1; + } + if (requestIncreaseFan != -1) { + qDebug() << QStringLiteral("increasing fan speed..."); + + requestIncreaseFan = -1; + } else if (requestDecreaseFan != -1) { + qDebug() << QStringLiteral("decreasing fan speed..."); + + requestDecreaseFan = -1; + } + } +} + +bool gpiotreadmill::connected() { return true; } + +void *gpiotreadmill::VirtualTreadMill() { return virtualTreadMill; } + +void *gpiotreadmill::VirtualDevice() { return VirtualTreadMill(); } + +void gpiotreadmill::searchingStop() { searchStopped = true; } +double gpiotreadmill::minStepSpeed() { return 0.1; } diff --git a/src/gpiotreadmill.h b/src/gpiotreadmill.h new file mode 100644 index 000000000..c18246ba5 --- /dev/null +++ b/src/gpiotreadmill.h @@ -0,0 +1,140 @@ +#ifndef GPIOTREADMILL_H +#define GPIOTREADMILL_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#ifndef Q_OS_ANDROID +#include +#else +#include +#endif +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "devices/treadmill.h" +#include "virtualdevices/virtualbike.h" +#include "virtualdevices/virtualtreadmill.h" + +#ifdef Q_OS_IOS +#include "ios/lockscreen.h" +#endif + +class gpioWorkerThread : public QThread +{ + public: + explicit gpioWorkerThread(QObject *parent, QString name = "", uint8_t pinUp = 0, uint8_t pinDown = 0, double step = 0.0, double currentValue = 0.0, QSemaphore *semaphore = nullptr); + void run(); + void setRequestValue(double request); + private: + QString name; + double requestValue; + double currentValue; + uint8_t pinUp; + uint8_t pinDown; + double step; + const uint16_t GPIO_KEEP_MS = 1; + const uint16_t GPIO_REBOUND_MS = 175; + QSemaphore *semaphore; +}; + + +class gpiotreadmill : public treadmill { + + Q_OBJECT + public: + gpiotreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false, + double forceInitSpeed = 1.0, double forceInitInclination = 0.0); + ~gpiotreadmill(); + bool connected(); + + void *VirtualTreadMill(); + void *VirtualDevice(); + + static void digitalWrite(int pin, int state); + static QModbusReply *lastRequest; + static QModbusClient *modbusDevice; + + double minStepSpeed(); + + private: + bool noConsole = false; + bool noHeartService = false; + uint32_t pollDeviceTime = 200; + bool searchStopped = false; + uint8_t sec1Update = 0; + uint8_t firstInit = 0; + QDateTime lastTimeCharacteristicChanged; + bool firstCharacteristicChanged = true; + + QTimer *refresh; + virtualtreadmill *virtualTreadMill = nullptr; + virtualbike *virtualBike = 0; + + bool initDone = false; + bool initRequest = false; + + const uint8_t OUTPUT_SPEED_UP = 0; + const uint8_t OUTPUT_SPEED_DOWN = 1; + const uint8_t OUTPUT_INCLINE_UP = 2; + const uint8_t OUTPUT_INCLINE_DOWN = 3; + const uint8_t OUTPUT_START = 4; + const uint8_t OUTPUT_STOP = 5; + + const uint16_t GPIO_KEEP_MS = 50; + //const uint16_t GPIO_REBOUND_MS = 200; + + const double SPEED_STEP = 0.1; + const double INCLINATION_STEP = 1.0; + + void forceSpeed(double requestSpeed); + void forceIncline(double requestIncline); + gpioWorkerThread* speedThread; + gpioWorkerThread* inclineThread; + QSemaphore *semaphore; // my treadmill don't like it if the buttons will be pressed simultanly + +#ifdef Q_OS_IOS + lockscreen *h = 0; +#endif + + Q_SIGNALS: + void disconnected(); + void debug(QString string); + void speedChanged(double speed); + void packetReceived(); + + public slots: + void searchingStop(); + + private slots: + + void changeInclinationRequested(double grade, double percentage); + void onReadReady(); + + void update(); +}; + +#endif // GPIOTREADMILL_H diff --git a/src/homeform.cpp b/src/homeform.cpp index 8b85b59c0..0de21d5dd 100644 --- a/src/homeform.cpp +++ b/src/homeform.cpp @@ -2874,6 +2874,7 @@ void homeform::deviceConnected(QBluetoothDeviceInfo b) { QObject::connect(home, SIGNAL(plus_clicked(QString)), this, SLOT(Plus(QString))); QObject::connect(home, SIGNAL(minus_clicked(QString)), this, SLOT(Minus(QString))); QObject::connect(home, SIGNAL(largeButton_clicked(QString)), this, SLOT(LargeButton(QString))); + QObject::connect(home, SIGNAL(keyPressed(int)), this, SLOT(keyPressed(int))); emit workoutNameChanged(workoutName()); emit instructorNameChanged(instructorName()); @@ -2911,6 +2912,17 @@ void homeform::deviceConnected(QBluetoothDeviceInfo b) { } } +void homeform::keyPressed(int key) { + if(key == Qt::Key_A) + Plus("speed"); + else if(key == Qt::Key_S) + Minus("speed"); + else if(key == Qt::Key_D) + Plus("inclination"); + else if(key == Qt::Key_F) + Minus("inclination"); +} + void homeform::deviceFound(const QString &name) { if (name.trimmed().isEmpty()) { return; diff --git a/src/homeform.h b/src/homeform.h index 72249e167..685048a67 100644 --- a/src/homeform.h +++ b/src/homeform.h @@ -812,6 +812,7 @@ class homeform : public QObject { void StopRequested(); void Lap(); void LargeButton(const QString &); + void keyPressed(int key); void volumeDown(); void volumeUp(); void keyMediaPrevious(); diff --git a/src/main.cpp b/src/main.cpp index a81b7ba2f..d81ff01fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -67,6 +67,7 @@ bool service_changed = false; bool bike_wheel_revs = false; bool run_cadence_sensor = false; bool nordictrack_10_treadmill = false; +bool gpiotreadmill = true; bool reebok_fr30_treadmill = false; bool zwift_play = false; bool zwift_click = false; @@ -137,6 +138,8 @@ QCoreApplication *createApplication(int &argc, char *argv[]) { run_cadence_sensor = true; if (!qstrcmp(argv[i], "-nordictrack-10-treadmill")) nordictrack_10_treadmill = true; + if (!qstrcmp(argv[i], "-gpiotreadmill")) + gpiotreadmill = true; if (!qstrcmp(argv[i], "-reebok_fr30_treadmill")) reebok_fr30_treadmill = true; if (!qstrcmp(argv[i], "-zwift_play")) @@ -407,6 +410,8 @@ int main(int argc, char *argv[]) { } #endif + settings.setValue(QStringLiteral("gpio_treadmill"), gpiotreadmill); + #ifdef Q_OS_ANDROID if (settings.value(QZSettings::volume_change_gears, QZSettings::default_volume_change_gears).toBool()) { qDebug() << "handling volume keys"; diff --git a/src/qdomyos-zwift.pri b/src/qdomyos-zwift.pri index fa8d97c41..9f4b28be9 100644 --- a/src/qdomyos-zwift.pri +++ b/src/qdomyos-zwift.pri @@ -3,6 +3,9 @@ QT += bluetooth widgets xml positioning quick networkauth websockets texttospeec QTPLUGIN += qavfmediaplayer QT+= charts +win32: QT += serialport serialbus +linux:!android: QT += serialport serialbus + qtHaveModule(httpserver) { QT += httpserver DEFINES += Q_HTTPSERVER @@ -288,6 +291,10 @@ zwiftworkout.cpp macx: SOURCES += macos/lockscreen.mm !ios: SOURCES += mainwindow.cpp charts.cpp +#gpio treadmill +win32: SOURCES += gpiotreadmill.cpp +linux:!android: SOURCES += gpiotreadmill.cpp + #zwift api msvc { SOURCES += zwift-api/zwift_messages.pb.cc @@ -740,6 +747,9 @@ windows_zwift_incline_paddleocr_thread.h \ zwiftworkout.h +win32: HEADERS += gpiotreadmill.h +linux:!android: HEADERS += gpiotreadmill.h + exists(secret.h): HEADERS += secret.h !ios: HEADERS += charts.h