Compare commits

..

67 Commits

Author SHA1 Message Date
Roberto Viola
a23afabb6e Update project.pbxproj 2025-03-21 11:56:35 +01:00
Roberto Viola
f5fd16e0e6 Merge branch 'master' into victory-dircon 2025-03-21 11:46:28 +01:00
Roberto Viola
0962e1a70e Update project.pbxproj 2025-03-21 11:45:55 +01:00
Roberto Viola
3d54b82428 Merge branch 'master' into victory-dircon 2025-03-21 11:06:12 +01:00
Roberto Viola
6a04908812 Update project.pbxproj 2025-03-21 08:42:08 +01:00
Roberto Viola
1460c6190e Merge branch 'master' into victory-dircon 2025-03-21 08:34:34 +01:00
Roberto Viola
32fcb198f8 Update project.pbxproj 2025-03-20 15:14:08 +01:00
Roberto Viola
3f04bb2ff0 Merge branch 'master' into victory-dircon 2025-03-20 15:13:14 +01:00
Roberto Viola
fec16ee496 Merge branch 'master' into victory-dircon 2025-03-20 15:12:55 +01:00
Roberto Viola
4f0ac7e6ea Update project.pbxproj 2025-03-20 13:57:44 +01:00
Roberto Viola
ba79162a28 Merge branch 'master' into victory-dircon 2025-03-20 13:54:24 +01:00
Roberto Viola
93477f4008 Update project.pbxproj 2025-03-20 13:53:45 +01:00
Roberto Viola
6e0b958e56 Merge branch 'master' into victory-dircon 2025-03-20 09:39:19 +01:00
Roberto Viola
f3f219309a Update project.pbxproj 2025-03-19 11:55:38 +01:00
Roberto Viola
860e296ae3 Merge branch 'master' into victory-dircon 2025-03-19 11:48:56 +01:00
Roberto Viola
505cd193d3 Merge branch 'master' into victory-dircon 2025-03-18 16:11:23 +01:00
Roberto Viola
f5ed321def build 1043 2025-03-07 09:24:08 +01:00
Roberto Viola
490039c9a0 Merge branch 'master' into victory-dircon 2025-03-07 08:55:50 +01:00
Roberto Viola
88eaf9d73c Update project.pbxproj 2025-03-07 03:03:15 +01:00
Roberto Viola
05979de55a Merge branch 'master' into victory-dircon 2025-03-07 03:02:42 +01:00
Roberto Viola
fb3c34f91f Merge branch 'master' into victory-dircon 2025-03-07 02:54:44 +01:00
Roberto Viola
a5616561d3 Update project.pbxproj 2025-03-05 08:00:49 +01:00
Roberto Viola
835a8395bf Merge branch 'master' into victory-dircon 2025-03-05 07:58:08 +01:00
Roberto Viola
d61a55394b fixing memory leak 2025-03-04 10:07:57 +01:00
Roberto Viola
e2233d0a86 Update project.pbxproj 2025-03-04 07:58:34 +01:00
Roberto Viola
075a0dc368 Gears don't work for mid-work free ride segment (Issue #2897)
https://github.com/cagnulein/qdomyos-zwift/issues/2897#issuecomment-2692370530
2025-03-03 11:36:30 +01:00
Roberto Viola
5f18c9f81c Gears don't work for mid-work free ride segment (Issue #2897)
https://github.com/cagnulein/qdomyos-zwift/issues/2897#issuecomment-2692178928
2025-03-03 11:12:31 +01:00
Roberto Viola
2f2c084db3 Update settings.qml 2025-03-02 13:01:35 +01:00
Roberto Viola
68337ee54d Update project.pbxproj 2025-02-27 12:30:15 +01:00
Roberto Viola
e69ddb8a26 Merge branch 'master' into victory-dircon 2025-02-27 12:29:33 +01:00
Roberto Viola
69b227373a fixing build 2025-02-26 17:23:56 +01:00
Roberto Viola
fbb9524afd Merge branch 'master' into victory-dircon 2025-02-26 17:12:04 +01:00
Roberto Viola
937f98c77a fixing bluetooth with get gears on on android? not tested 2025-02-26 16:44:24 +01:00
Roberto Viola
0ec7619408 fixing bluetooth on ios with get gears from zwift enabled 2025-02-26 16:25:50 +01:00
Roberto Viola
344640fe69 Update project.pbxproj 2025-02-26 12:36:44 +01:00
Roberto Viola
26effcd6b2 Merge branch 'master' into victory-dircon 2025-02-26 12:16:14 +01:00
Roberto Viola
206e6e6fec Gears don't work for mid-work free ride segment (Issue #2897) 2025-02-24 15:50:29 +01:00
Roberto Viola
d2ba73b148 Merge branch 'master' into victory-dircon 2025-02-19 09:12:22 +01:00
Roberto Viola
df8ac18a0f Merge branch 'master' into victory-dircon 2025-02-19 09:08:25 +01:00
Roberto Viola
cfcf86adcc Update dirconmanager.cpp
https://github.com/cagnulein/qdomyos-zwift/issues/2897#issuecomment-2666126808
2025-02-18 16:58:21 +01:00
Roberto Viola
957d2934db fixing gears on startup alinged with zwift 2025-02-18 15:22:35 +01:00
Roberto Viola
7c73975c34 Merge branch 'master' into victory-dircon 2025-02-18 14:49:55 +01:00
Roberto Viola
999ce8022a Update project.pbxproj 2025-02-15 20:58:57 +01:00
Roberto Viola
c3fa23159a Log on Thread 2025-02-15 19:51:09 +00:00
Roberto Viola
6b674be421 Merge remote-tracking branch 'origin/master' into victory-dircon 2025-02-15 19:46:39 +00:00
Roberto Viola
7af98b4992 handling unhandled case 2025-02-10 10:34:26 +01:00
Roberto Viola
8380827494 Merge branch 'master' into victory-dircon 2025-02-10 10:19:55 +01:00
Roberto Viola
a3a8825d48 simulating a fake cadence randomly 2025-02-01 08:15:41 +01:00
Roberto Viola
d8b1dce8de Merge branch 'victory-dircon' of https://github.com/cagnulein/qdomyos-zwift into victory-dircon 2025-01-31 08:38:54 +01:00
Roberto Viola
6e141bf83e Update fakebike.cpp 2025-01-31 08:38:32 +01:00
Roberto Viola
f32ad41917 Merge branch 'master' into victory-dircon 2025-01-31 08:10:10 +01:00
Roberto Viola
e75a5bcb0a Merge branch 'victory-dircon' of https://github.com/cagnulein/qdomyos-zwift into victory-dircon 2025-01-31 08:10:03 +01:00
Roberto Viola
0a017ca4e6 Merge branch 'master' into victory-dircon 2025-01-31 08:09:51 +01:00
Roberto Viola
3ceb256272 Update dirconmanager.cpp 2025-01-20 15:26:35 +01:00
Roberto Viola
6b8bb96aad Merge branch 'master' into victory-dircon 2025-01-20 15:02:55 +01:00
Roberto Viola
71cb562b05 Update characteristicwriteprocessor0003.h 2025-01-07 17:31:40 +01:00
Roberto Viola
de77f2aa4e Horizon 5.0 Bike Compatibility #3001 2025-01-07 11:19:17 +01:00
Roberto Viola
316c34213d Merge branch 'master' into victory-dircon 2025-01-07 09:47:40 +01:00
Roberto Viola
860489616c improving wattage also for all bluetooth, but it's not perfect yet 2025-01-01 18:56:11 +01:00
Roberto Viola
730c16f7b4 dircon works perfectly on ios! 2025-01-01 18:41:53 +01:00
Roberto Viola
2bdbeed5f4 wahoo rgt setting is not useful anymore 2025-01-01 14:51:39 +01:00
Roberto Viola
1946b46665 it works! 2025-01-01 14:45:36 +01:00
Roberto Viola
288cb3974b kind of works (no unhandled frames) 2024-12-31 15:00:51 +01:00
Roberto Viola
345b0d2f74 adding 0004 notifier 2024-12-31 14:20:34 +01:00
Roberto Viola
c8355184f2 i'm getting the 0003 but i need to notify the 0002
it doesn't enter into the sendCharacteristicNotification loop
2024-12-30 18:12:10 +01:00
Roberto Viola
4f7c7fa7c9 it's working for asking the UUID! 2024-12-30 16:39:45 +01:00
Roberto Viola
c75b6ae5f0 starting 2024-12-30 16:29:31 +01:00
490 changed files with 5259 additions and 36753 deletions

View File

@@ -510,7 +510,7 @@ jobs:
# This workflow contains a single job called "build"
android-build:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
@@ -534,21 +534,11 @@ jobs:
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v2
with:
# This token is provided by Actions, you do not need to create your own token
token: ${{ secrets.GITHUB_TOKEN }}
submodules: 'false' # Prima disattiva il checkout automatico dei submodule
- name: Checkout submodules with specific branches
run: |
git submodule init
git submodule update --init --recursive
- name: Fix qmdnsengine submodule
run: |
cd src/qmdnsengine
git fetch
git checkout 602da51dc43c55bd9aa8a83c47ea3594a9b01b98
submodules: recursive # or 'true' if you want to check out only immediate submodules
- 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
@@ -583,7 +573,7 @@ jobs:
# waiting github.com/jurplel/install-qt-action/issues/63
- name: Install Qt Android
uses: jdpurcell/install-qt-action@v5
uses: jurplel/install-qt-action@v3
with:
version: '5.15.0'
host: 'linux'
@@ -649,92 +639,19 @@ jobs:
name: fdroid-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
android-emulator-test:
runs-on: ubuntu-latest
needs: android-build
steps:
- name: Checkout code
uses: actions/checkout@v2
# - name: Exit if not on master branch
# if: github.ref == 'refs/heads/master'
# run: exit 1
- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm
# Download the APK from the previous job
- name: Download APK Artifact
uses: actions/download-artifact@v4
with:
name: fdroid-android-trial
path: apk-debug
- name: Setup Java for Android Emulator
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '17'
# Use a smaller emulator configuration
- name: Run tests on emulator
uses: ReactiveCircus/android-emulator-runner@v2
with:
target: default # Use default instead of Google APIs
arch: x86
api-level: 29
profile: Nexus 6
disable-animations: true
script: |
# Display available space
df -h
# List available files
echo "Files in apk-debug directory:"
ls -la apk-debug/
# Install the APK
adb install apk-debug/android-debug.apk
# Grant necessary permissions for API 25
echo "Granting permissions..."
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.ACCESS_FINE_LOCATION || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.BLUETOOTH || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.BLUETOOTH_ADMIN || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.READ_EXTERNAL_STORAGE || true
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.WRITE_EXTERNAL_STORAGE || true
# Start the main activity
adb shell am start -n org.cagnulen.qdomyoszwift/org.qtproject.qt5.android.bindings.QtActivity
# Wait for app to start
sleep 40
# Verify the app is running
echo "Checking if app is running..."
adb shell "ps -A" > process_list.txt
grep -q "qdomyos" process_list.txt || (echo "App process not found in process list" && echo "TEST FAILED: App process not running" && exit 1)
echo "App is running successfully"
# Take a screenshot for verification
adb shell screencap -p /sdcard/screenshot.png
adb pull /sdcard/screenshot.png
# Check if the package is installed
adb shell pm list packages | grep org.cagnulen.qdomyoszwift
# Display logcat output for debugging (just the last 100 lines)
adb logcat -d | grep -i qdomyos | tail -n 100
- name: Upload test evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: android-emulator-test-evidence
path: |
screenshot.png
process_list.txt
if-no-files-found: warn
# - name: upload windows artifact
# uses: actions/upload-release-asset@v1
# env:
# GITHUB_TOKEN: ${{ github.token }}
# with:
# upload_url: ${{ steps.create_release.outputs.upload_url }}
# asset_path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk
# asset_name: fdroid-android-trial.zip
# asset_content_type: application/zip
ios-build:
# The type of runner that the job will run on
@@ -937,7 +854,7 @@ jobs:
- name: Install dependencies
run: |
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=${{ runner.workspace }}\vcpkg\installed
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
working-directory: ${{ runner.workspace }}
- name: Build
@@ -1125,7 +1042,7 @@ jobs:
- name: Install dependencies
run: |
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=${{ runner.workspace }}\vcpkg\installed
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
working-directory: ${{ runner.workspace }}
- name: Build
@@ -1278,12 +1195,6 @@ jobs:
- name: Rename binary
run: mv src/qdomyos-zwift src/qdomyos-zwift-64bit
- name: Archive Raspberry Pi 64bit binary
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-binary-64bit
path: src/qdomyos-zwift-64bit
window-msvc2022-build:
runs-on: windows-latest
if: github.event_name == 'schedule'
@@ -1323,7 +1234,7 @@ jobs:
with:
version: '6.8.2'
host: 'windows'
modules: 'qtnetworkauth qtcharts qtconnectivity qtspeech qtpositioning qtwebsockets qtlocation qtmultimedia qtwebengine qtwebview qthttpserver qtwebchannel'
modules: 'qtnetworkauth qtcharts qtconnectivity qtspeech qtpositioning qtwebsockets qtlocation qtmultimedia qtwebengine qtwebview qtwebchannel'
target: "desktop"
arch: win64_msvc2022_64
dir: "${{github.workspace}}/qt/"
@@ -1386,7 +1297,7 @@ jobs:
- name: Install dependencies
run: |
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=${{ runner.workspace }}\vcpkg\installed
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
working-directory: ${{ runner.workspace }}
- name: Build
@@ -1469,10 +1380,10 @@ jobs:
name: windows-msvc2022-binary-no-python
path: windows-msvc2022-binary-no-python.zip
if: ${{ ! matrix.config.python }}
upload_to_release:
permissions: write-all
runs-on: ubuntu-22.04
runs-on: ubuntu-20.04
if: github.event_name == 'schedule'
needs: [linux-x86-build, window-msvc2019-build, window-msvc2022-build, ios-build, window-build, android-build, raspberry-pi-build]
steps:
@@ -1519,4 +1430,3 @@ jobs:
windows-binary/*
fdroid-android-trial/*
raspberry-pi-binary/qdomyos-zwift-32bit
raspberry-pi-binary/qdomyos-zwift-64bit

1
.gitignore vendored
View File

@@ -50,4 +50,3 @@ src/inner_templates/googlemaps/cesium-key.js
.vscode/settings.json
/tst/Devices/.vs
src/inner_templates/googlemaps/cesium-key.js
src/qdomyos-zwift.pro.user.49de507

2
.gitmodules vendored
View File

@@ -12,7 +12,7 @@
[submodule "tst/googletest"]
path = tst/googletest
url = https://github.com/google/googletest.git
tag = release-1.12.1
branch = tags/release-1.12.1
[submodule "src/qthttpserver"]
path = src/qthttpserver
url = https://github.com/qt-labs/qthttpserver

374
CLAUDE.md
View File

@@ -1,374 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
QDomyos-Zwift is a Qt-based application that bridges fitness equipment (treadmills, bikes, ellipticals, rowers) with virtual training platforms like Zwift. It acts as a Bluetooth intermediary, connecting physical equipment to fitness apps while providing enhanced features like Peloton integration, power zone training, and workout programs.
## Build System & Commands
### Build Commands
```bash
# Build entire project (use subdirs TEMPLATE)
qmake
make
# Build specific configurations
qmake -r # Recursive build
make debug # Debug build
make release # Release build
# Clean build
make clean
make distclean
```
### Platform-Specific Builds
```bash
# Android
qmake -spec android-clang
make
# iOS
qmake -spec macx-ios-clang
make
# Windows (MinGW)
qmake -spec win32-g++
make
```
### Testing
```bash
# Build and run tests (requires main app built first)
cd tst
qmake
make
./qdomyos-zwift-tests
# Run with XML output for CI
GTEST_OUTPUT=xml:test-results/ GTEST_COLOR=1 ./qdomyos-zwift-tests
```
### No-GUI Mode
```bash
# Run application without GUI
sudo ./qdomyos-zwift -no-gui
```
## Architecture Overview
### Device Architecture
The application follows a hierarchical device architecture:
1. **Base Class**: `bluetoothdevice` - Abstract base for all fitness devices
- Manages Bluetooth connectivity via Qt's QLowEnergyController
- Defines common metrics (speed, cadence, heart rate, power, distance)
- Integrates with virtual devices for app connectivity
2. **Device Type Classes**: Inherit from `bluetoothdevice`
- `bike` - Bike-specific features (resistance, gears, power zones)
- `treadmill` - Treadmill features (speed control, inclination, pace)
- `elliptical` - Combined bike/treadmill features
- `rower` - Rowing metrics (stroke count, 500m pace)
- `stairclimber` - Step counting and climbing metrics
- `jumprope` - Jump sequence tracking
3. **Concrete Implementations**: Inherit from device type classes
- Located in `src/devices/[devicename]/` folders
- Examples: `domyosbike`, `pelotonbike`, `ftmsbike`
### Virtual Device System
- `virtualdevice` - Abstract base for virtual representations
- `virtualbike`, `virtualtreadmill`, etc. - Advertise to external apps
- Enables bidirectional communication between physical and virtual devices
### Bluetooth Management
- `bluetooth` class acts as device factory and connection manager
- `discoveryoptions` configures device discovery process
- Supports multiple connection types (Bluetooth LE, TCP, UDP)
## Key Development Areas
### Adding New Device Support
1. Create device folder in `src/devices/[devicename]/`
2. Implement device class inheriting from appropriate base type
3. Add device detection logic to `bluetooth.cpp`
4. Update `qdomyos-zwift.pri` with new source files
5. Add tests in `tst/Devices/` following existing patterns
### Characteristics & Protocols
- Bluetooth characteristics handlers in `src/characteristics/`
- FTMS (Fitness Machine Service) protocol support
- ANT+ integration for sensors
- Custom protocol implementations for specific brands
### UI & QML
- QML-based UI with Qt Quick Controls 2
- Main QML files in `src/` (main.qml, settings.qml, etc.)
- Platform-specific UI adaptations (iOS, Android, desktop)
### Integration Features
- Peloton workout/resistance integration (`peloton.cpp`)
- Zwift workout parsing (`zwiftworkout.cpp`)
- GPX file support for route following (`gpx.cpp`)
- Training program support (ZWO, XML formats)
## Platform-Specific Notes
### iOS
- Swift bridge files in `src/ios/`
- Apple Watch integration via `WatchKitConnection.swift`
- HealthKit integration for fitness data
- ConnectIQ SDK for Garmin devices
### Android
- Java bridge files in `src/android/src/`
- ANT+ integration via Android ANT SDK
- Foreground service for background operation
- USB serial support for wired connections
### Windows
- ADB integration for Nordic Track iFit devices
- PaddleOCR integration for Zwift workout detection
- Windows-specific networking features
## File Structure Patterns
### Device Files
```
src/devices/[devicename]/
├── [devicename].h # Header file
├── [devicename].cpp # Implementation
└── README.md # Device-specific documentation (optional)
```
### Test Files
```
tst/Devices/
├── DeviceTestData.h # Test data definitions
├── Test[DeviceName].h # Device-specific test cases
└── TestBluetooth.cpp # Main device detection test suite
```
## Testing Framework
- Uses Google Test (gtest) with Google Mock
- Comprehensive device detection testing
- Configuration-based test scenarios
- XML output support for CI/CD integration
- Tests must be built after main application (links against libqdomyos-zwift.a)
## Configuration & Settings
- Settings managed via `qzsettings.cpp` (QSettings wrapper)
- Platform-specific configuration paths
- Profile system for multiple users/devices
- Extensive customization options for device behavior
## External Dependencies
- Qt 5.15.2+ (Bluetooth, WebSockets, Charts, Quick, etc.)
- Google Test (submodule for testing)
- Platform SDKs (Android ANT+, iOS HealthKit, Windows ADB)
- Protocol Buffers for Zwift API integration
- MQTT client for IoT integration
- Various fitness platform APIs (Strava, Garmin Connect, etc.)
## Adding New ProForm Treadmill Models
This section provides a complete guide for adding new ProForm treadmill models to the codebase, based on the ProForm 995i implementation.
### Prerequisites
1. **Bluetooth Frame Capture File**: A file containing raw Bluetooth frames from the target treadmill
2. **Frame Analysis**: Understanding of which frames are initialization vs. sendPoll frames
3. **BLE Header Knowledge**: Each frame has an 11-byte BLE header that must be removed
### Step-by-Step Implementation Process
#### 1. Process Bluetooth Frames
First, process the raw Bluetooth frames by removing the first 11 bytes (BLE header) from each frame:
```bash
# Example: if you have "proform_model.c" with raw frames
# Process each frame by removing first 11 bytes
# Separate initialization frames from sendPoll frames
```
**Key Requirements:**
- Remove exactly 11 bytes from each frame (BLE header)
- Identify the boundary between initialization and sendPoll frames
- Initialization frames come first, sendPoll frames follow
- Document which packet number starts the sendPoll sequence
#### 2. Add Boolean Flag to Header File
Add the new model flag to `src/devices/proformtreadmill/proformtreadmill.h`:
```cpp
// Add before #ifdef Q_OS_IOS section
bool proform_treadmill_newmodel = false;
```
#### 3. Add Settings Support
Update the following files for settings integration:
**In `src/qzsettings.h`:**
```cpp
static const QString proform_treadmill_newmodel;
static constexpr bool default_proform_treadmill_newmodel = false;
```
**In `src/qzsettings.cpp`:**
```cpp
const QString QZSettings::proform_treadmill_newmodel = QStringLiteral("proform_treadmill_newmodel");
```
* Update the `allSettingsCount` in `qzsettings.cpp`
#### 4. Update QML Settings UI
**In `src/settings.qml`:**
1. Add property at the END of properties list:
```qml
property bool proform_treadmill_newmodel: false
```
2. Update ComboBox model array:
```qml
model: ["Disabled", "Proform New Model", ...]
```
3. Add case selection logic (find next available case number):
```qml
currentIndex: settings.proform_treadmill_newmodel ? XX : 0;
```
4. Add reset logic:
```qml
settings.proform_treadmill_newmodel = false;
```
5. Add switch case:
```qml
case XX: settings.proform_treadmill_newmodel = true; break;
```
#### 5. Implement Device Logic
**In `src/devices/proformtreadmill/proformtreadmill.cpp`:**
1. **Load Settings** (in constructor):
```cpp
proform_treadmill_newmodel = settings.value(QZSettings::proform_treadmill_newmodel, QZSettings::default_proform_treadmill_newmodel).toBool();
```
2. **Add Initialization Case** (in `btinit()` method):
```cpp
} else if (proform_treadmill_newmodel) {
// ALL initialization frames go here
uint8_t initData1[] = {0x00, 0xfe, 0x02, 0x08, 0x02};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
// ... continue with ALL init frames from capture file
// Use frames from beginning until sendPoll boundary
}
```
3. **Add SendPoll Case** (in `sendPoll()` method):
```cpp
} else if (proform_treadmill_newmodel) {
switch (counterPoll) {
case 0:
// First sendPoll frame
break;
case 1:
// Second sendPoll frame
break;
// ... continue with pattern from sendPoll frames
default:
// Reset counter and cycle
counterPoll = -1;
break;
}
}
```
4. **Update Force Functions** - Add flag to conditional checks in `forceIncline()` and `forceSpeed()`:
```cpp
} else if (proform_treadmill_8_0 || ... || proform_treadmill_newmodel) {
write[14] = write[11] + write[12] + 0x12;
}
```
### Implementation Requirements
#### Frame Processing Rules
- **Exactly 11 bytes** must be removed from each frame (BLE header)
- **All initialization frames** must be included in the btinit() case
- **All sendPoll frames** must be included in the sendPoll() switch statement
- **Frame order** must be preserved exactly as captured
#### Settings Integration Rules
- **Property placement**: Always add new properties at the END of the properties list in settings.qml
- **Case numbering**: Find the next available case number in the ComboBox switch statement
- **Naming convention**: Use descriptive names following existing patterns
#### Code Organization Rules
- **Initialization**: All init frames go in btinit() method
- **Communication**: All sendPoll frames go in sendPoll() method with switch/case structure
- **Force functions**: Add new model flag to existing conditional chains
### Common Pitfalls and Solutions
#### Incorrect Byte Removal
- **Problem**: Removing wrong number of bytes (12 instead of 11)
- **Solution**: Always remove exactly 11 bytes (BLE header)
#### Wrong SendPoll Boundary
- **Problem**: Using initialization frames in sendPoll logic
- **Solution**: Identify exact packet number where sendPoll starts
#### Incomplete Initialization
- **Problem**: Missing initialization frames
- **Solution**: Include ALL frames from start until sendPoll boundary
#### Settings Placement
- **Problem**: Adding property in wrong location in settings.qml
- **Solution**: Always add at END of properties list
### Verification Checklist
- [ ] All 11 bytes removed from each frame
- [ ] Initialization frames correctly identified and included
- [ ] SendPoll frames correctly identified and implemented
- [ ] Settings properly integrated in all required files
- [ ] ComboBox updated with new model option
- [ ] Force functions updated with new model flag
- [ ] Property added at END of settings.qml properties list
### Example Reference
The ProForm 995i implementation serves as the reference example:
- 25 initialization frames (pkt4658-pkt4756)
- 33 sendPoll frames (pkt4761-pkt4897)
- 6-case sendPoll switch statement with cycling logic
- Complete settings integration across all required files
## Development Tips
- Use Qt Creator for development with proper project file support
- The project uses Qt's signal/slot mechanism extensively
- Device implementations should follow existing patterns for consistency
- Add comprehensive logging using the project's logging framework
- Test device detection thoroughly using the existing test infrastructure
- Consider platform differences when adding new features
## Additional Memories
- When adding a new setting in QML (setting-tiles.qml), you must:
* Add the property at the END of the properties list

View File

@@ -299,10 +299,6 @@
8762D5132601F89500F6F049 /* scanrecordresult.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8762D5112601F89500F6F049 /* scanrecordresult.cpp */; };
87646C2027B5064600F82131 /* bhfitnesselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87646C1E27B5064500F82131 /* bhfitnesselliptical.cpp */; };
87646C2227B5065100F82131 /* moc_bhfitnesselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87646C2127B5065100F82131 /* moc_bhfitnesselliptical.cpp */; };
8767CA552DA3C1FD0003001F /* elitesquarecontroller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767CA532DA3C1FD0003001F /* elitesquarecontroller.cpp */; };
8767CA562DA3C1FD0003001F /* moc_elitesquarecontroller.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767CA542DA3C1FD0003001F /* moc_elitesquarecontroller.cpp */; };
8767CA5D2DA7F5170003001F /* ios_wahookickrsnapbike.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767CA5C2DA7F5170003001F /* ios_wahookickrsnapbike.mm */; };
8767CA602DA800590003001F /* ios_zwiftclickremote.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767CA5F2DA800590003001F /* ios_zwiftclickremote.mm */; };
8767EF1E29448D6700810C0F /* characteristicwriteprocessor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8767EF1D29448D6700810C0F /* characteristicwriteprocessor.cpp */; };
8768C8BA2BBC11C80099DBE1 /* file_sync_client.c in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8768C89C2BBC11C70099DBE1 /* file_sync_client.c */; };
8768C8BB2BBC11C80099DBE1 /* protocol.txt in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 8768C89D2BBC11C70099DBE1 /* protocol.txt */; };
@@ -396,11 +392,8 @@
8785D5442B3DD105005A2EB7 /* moc_zwift_client_auth.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */; };
87873AEE2D09A8AA005F86B4 /* moc_sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */; };
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */; };
878895DB2DD48AB100BF5162 /* moc_inclinationresistancetable.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878895DA2DD48AB100BF5162 /* moc_inclinationresistancetable.cpp */; };
878A331A25AB4FF800BD13E1 /* yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331725AB4FF800BD13E1 /* yesoulbike.cpp */; };
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */; };
878C9DC92DF01C16001114D5 /* speraxtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9DC82DF01C16001114D5 /* speraxtreadmill.cpp */; };
878C9DCA2DF01C16001114D5 /* moc_speraxtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9DC62DF01C16001114D5 /* moc_speraxtreadmill.cpp */; };
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */; };
878C9E6B28B77E9800669129 /* moc_nordictrackifitadbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9E6A28B77E9800669129 /* moc_nordictrackifitadbbike.cpp */; };
878D83742A1F33C600D7F004 /* bkoolbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878D83732A1F33C600D7F004 /* bkoolbike.cpp */; };
@@ -479,7 +472,6 @@
87BE6FDE272D2A3E00C35795 /* moc_horizongr7bike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BE6FDD272D2A3E00C35795 /* moc_horizongr7bike.cpp */; };
87BF116D298E28CA00B5B6E7 /* pelotonbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BF116C298E28CA00B5B6E7 /* pelotonbike.cpp */; };
87BF116F298E28EC00B5B6E7 /* moc_pelotonbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BF116E298E28EC00B5B6E7 /* moc_pelotonbike.cpp */; };
87BFEA2F2CEDDEEE00BDD759 /* ios_echelonconnectsport.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BFEA2E2CEDDEEE00BDD759 /* ios_echelonconnectsport.mm */; };
87C424262BC1294000503687 /* moc_treadmillErgTable.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87C424252BC1294000503687 /* moc_treadmillErgTable.cpp */; };
87C481FA26DFA7C3006211AD /* eliterizer.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87C481F926DFA7C3006211AD /* eliterizer.cpp */; };
87C481FC26DFA7D1006211AD /* moc_eliterizer.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87C481FB26DFA7D1006211AD /* moc_eliterizer.cpp */; };
@@ -546,12 +538,6 @@
87DAE16926E9FF5000B0527E /* moc_shuaa5treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16626E9FF5000B0527E /* moc_shuaa5treadmill.cpp */; };
87DAE16A26E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16726E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp */; };
87DAE16B26E9FF5000B0527E /* moc_solef80treadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DAE16826E9FF5000B0527E /* moc_solef80treadmill.cpp */; };
87DC27EA2D9BDB53007A1B9D /* echelonstairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27E72D9BDB53007A1B9D /* echelonstairclimber.cpp */; };
87DC27EB2D9BDB53007A1B9D /* stairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27E92D9BDB53007A1B9D /* stairclimber.cpp */; };
87DC27EE2D9BDB8F007A1B9D /* moc_stairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27ED2D9BDB8F007A1B9D /* moc_stairclimber.cpp */; };
87DC27EF2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27EC2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp */; };
87DC27F32D9BDC43007A1B9D /* moc_moxy5sensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27F02D9BDC43007A1B9D /* moc_moxy5sensor.cpp */; };
87DC27F42D9BDC43007A1B9D /* moxy5sensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DC27F22D9BDC43007A1B9D /* moxy5sensor.cpp */; };
87DED80627D1273900BE4FBB /* filedownloader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DED80427D1273800BE4FBB /* filedownloader.cpp */; };
87DED80827D1274600BE4FBB /* moc_filedownloader.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DED80727D1274500BE4FBB /* moc_filedownloader.cpp */; };
87DF68B825E2673B00FCDA46 /* eslinkertreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87DF68B625E2673600FCDA46 /* eslinkertreadmill.cpp */; };
@@ -580,12 +566,6 @@
87EB918A27EE5FE7002535E1 /* qdomyoszwift_qmltyperegistrations.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917F27EE5FE7002535E1 /* qdomyoszwift_qmltyperegistrations.cpp */; };
87EB918B27EE5FE7002535E1 /* moc_inappproductqmltype.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB918027EE5FE7002535E1 /* moc_inappproductqmltype.cpp */; };
87EB918C27EE5FE7002535E1 /* moc_inappproduct.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB918127EE5FE7002535E1 /* moc_inappproduct.cpp */; };
87EBB2A62D39214E00348B15 /* moc_workoutloaderworker.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EBB2A02D39214E00348B15 /* moc_workoutloaderworker.cpp */; };
87EBB2A72D39214E00348B15 /* workoutmodel.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EBB2A52D39214E00348B15 /* workoutmodel.cpp */; };
87EBB2A82D39214E00348B15 /* workoutloaderworker.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EBB2A32D39214E00348B15 /* workoutloaderworker.cpp */; };
87EBB2A92D39214E00348B15 /* moc_fitdatabaseprocessor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EBB29F2D39214E00348B15 /* moc_fitdatabaseprocessor.cpp */; };
87EBB2AA2D39214E00348B15 /* fitdatabaseprocessor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EBB29E2D39214E00348B15 /* fitdatabaseprocessor.cpp */; };
87EBB2AB2D39214E00348B15 /* moc_workoutmodel.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EBB2A12D39214E00348B15 /* moc_workoutmodel.cpp */; };
87EFB56E25BD703D0039DD5A /* proformtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFB56C25BD703C0039DD5A /* proformtreadmill.cpp */; };
87EFB57025BD704A0039DD5A /* moc_proformtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFB56F25BD704A0039DD5A /* moc_proformtreadmill.cpp */; };
87EFE45927A518F5006EA1C3 /* nautiluselliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EFE45827A518F5006EA1C3 /* nautiluselliptical.cpp */; };
@@ -593,10 +573,6 @@
87F02E4029178524000DB52C /* octaneelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F02E3E29178523000DB52C /* octaneelliptical.cpp */; };
87F02E4229178545000DB52C /* moc_octaneelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F02E4129178545000DB52C /* moc_octaneelliptical.cpp */; };
87F1179E26A5FBDE00541B3A /* libqtwebview_darwin.a in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87420DF7269D7CE1000C5EC6 /* libqtwebview_darwin.a */; };
87F1BD682DBFBCE700416506 /* moc_android_antbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F1BD672DBFBCE700416506 /* moc_android_antbike.cpp */; };
87F1BD692DBFBCE700416506 /* android_antbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F1BD662DBFBCE700416506 /* android_antbike.cpp */; };
87F1BD712DC0D59600416506 /* moc_coresensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F1BD702DC0D59600416506 /* moc_coresensor.cpp */; };
87F1BD722DC0D59600416506 /* coresensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F1BD6F2DC0D59600416506 /* coresensor.cpp */; };
87F4FB5A29D550C00061BB4A /* schwinn170bike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F4FB5829D550BF0061BB4A /* schwinn170bike.cpp */; };
87F4FB5C29D550E00061BB4A /* moc_schwinn170bike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F4FB5B29D550DF0061BB4A /* moc_schwinn170bike.cpp */; };
87F527BE28EEB5AA00A9F8D5 /* qzsettings.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87F527BC28EEB5AA00A9F8D5 /* qzsettings.cpp */; };
@@ -1229,13 +1205,6 @@
87646C1E27B5064500F82131 /* bhfitnesselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = bhfitnesselliptical.cpp; path = ../src/devices/bhfitnesselliptical/bhfitnesselliptical.cpp; sourceTree = "<group>"; };
87646C1F27B5064500F82131 /* bhfitnesselliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = bhfitnesselliptical.h; path = ../src/devices/bhfitnesselliptical/bhfitnesselliptical.h; sourceTree = "<group>"; };
87646C2127B5065100F82131 /* moc_bhfitnesselliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_bhfitnesselliptical.cpp; sourceTree = "<group>"; };
8767CA522DA3C1FD0003001F /* elitesquarecontroller.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = elitesquarecontroller.h; path = ../src/devices/elitesquarecontroller/elitesquarecontroller.h; sourceTree = SOURCE_ROOT; };
8767CA532DA3C1FD0003001F /* elitesquarecontroller.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = elitesquarecontroller.cpp; path = ../src/devices/elitesquarecontroller/elitesquarecontroller.cpp; sourceTree = SOURCE_ROOT; };
8767CA542DA3C1FD0003001F /* moc_elitesquarecontroller.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_elitesquarecontroller.cpp; sourceTree = "<group>"; };
8767CA5B2DA7F5170003001F /* ios_wahookickrsnapbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ios_wahookickrsnapbike.h; path = ../src/ios/ios_wahookickrsnapbike.h; sourceTree = SOURCE_ROOT; };
8767CA5C2DA7F5170003001F /* ios_wahookickrsnapbike.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_wahookickrsnapbike.mm; path = ../src/ios/ios_wahookickrsnapbike.mm; sourceTree = SOURCE_ROOT; };
8767CA5E2DA800590003001F /* ios_zwiftclickremote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ios_zwiftclickremote.h; path = ../src/ios/ios_zwiftclickremote.h; sourceTree = SOURCE_ROOT; };
8767CA5F2DA800590003001F /* ios_zwiftclickremote.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_zwiftclickremote.mm; path = ../src/ios/ios_zwiftclickremote.mm; sourceTree = SOURCE_ROOT; };
8767EF1D29448D6700810C0F /* characteristicwriteprocessor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicwriteprocessor.cpp; path = ../src/characteristics/characteristicwriteprocessor.cpp; sourceTree = "<group>"; };
8768C89C2BBC11C70099DBE1 /* file_sync_client.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; name = file_sync_client.c; path = ../src/ios/adb/adb/file_sync_client.c; sourceTree = "<group>"; };
8768C89D2BBC11C70099DBE1 /* protocol.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; name = protocol.txt; path = ../src/ios/adb/adb/protocol.txt; sourceTree = "<group>"; };
@@ -1384,15 +1353,10 @@
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusrower.cpp; sourceTree = "<group>"; };
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sportsplusrower.cpp; sourceTree = "<group>"; };
87873AF22D09AADF005F86B4 /* sportsplusrower.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportsplusrower.h; path = ../src/devices/sportsplusrower/sportsplusrower.h; sourceTree = SOURCE_ROOT; };
878895D92DD48AB100BF5162 /* inclinationresistancetable.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = inclinationresistancetable.h; path = ../src/inclinationresistancetable.h; sourceTree = SOURCE_ROOT; };
878895DA2DD48AB100BF5162 /* moc_inclinationresistancetable.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_inclinationresistancetable.cpp; sourceTree = "<group>"; };
8789DCDB6A4F681A76DF3F92 /* Qt5Widgets */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = Qt5Widgets; path = "/Users/cagnulein/Qt/5.15.2/ios/lib/libQt5Widgets$(QT_LIBRARY_SUFFIX).a"; sourceTree = "<absolute>"; };
878A331725AB4FF800BD13E1 /* yesoulbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = yesoulbike.cpp; path = ../src/devices/yesoulbike/yesoulbike.cpp; sourceTree = "<group>"; };
878A331825AB4FF800BD13E1 /* yesoulbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = yesoulbike.h; path = ../src/devices/yesoulbike/yesoulbike.h; sourceTree = "<group>"; };
878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_yesoulbike.cpp; sourceTree = "<group>"; };
878C9DC62DF01C16001114D5 /* moc_speraxtreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_speraxtreadmill.cpp; sourceTree = "<group>"; };
878C9DC72DF01C16001114D5 /* speraxtreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = speraxtreadmill.h; path = ../src/devices/speraxtreadmill/speraxtreadmill.h; sourceTree = SOURCE_ROOT; };
878C9DC82DF01C16001114D5 /* speraxtreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = speraxtreadmill.cpp; path = ../src/devices/speraxtreadmill/speraxtreadmill.cpp; sourceTree = SOURCE_ROOT; };
878C9E6728B77E7B00669129 /* nordictrackifitadbbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackifitadbbike.h; path = ../src/devices/nordictrackifitadbbike/nordictrackifitadbbike.h; sourceTree = "<group>"; };
878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackifitadbbike.cpp; path = ../src/devices/nordictrackifitadbbike/nordictrackifitadbbike.cpp; sourceTree = "<group>"; };
878C9E6A28B77E9800669129 /* moc_nordictrackifitadbbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackifitadbbike.cpp; sourceTree = "<group>"; };
@@ -1511,8 +1475,6 @@
87BF116B298E28CA00B5B6E7 /* pelotonbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = pelotonbike.h; path = ../src/devices/pelotonbike/pelotonbike.h; sourceTree = "<group>"; };
87BF116C298E28CA00B5B6E7 /* pelotonbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = pelotonbike.cpp; path = ../src/devices/pelotonbike/pelotonbike.cpp; sourceTree = "<group>"; };
87BF116E298E28EC00B5B6E7 /* moc_pelotonbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_pelotonbike.cpp; sourceTree = "<group>"; };
87BFEA2D2CEDDEEE00BDD759 /* ios_echelonconnectsport.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ios_echelonconnectsport.h; path = ../src/ios/ios_echelonconnectsport.h; sourceTree = SOURCE_ROOT; };
87BFEA2E2CEDDEEE00BDD759 /* ios_echelonconnectsport.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = ios_echelonconnectsport.mm; path = ../src/ios/ios_echelonconnectsport.mm; sourceTree = SOURCE_ROOT; };
87C424252BC1294000503687 /* moc_treadmillErgTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_treadmillErgTable.cpp; sourceTree = "<group>"; };
87C481F826DFA7C3006211AD /* eliterizer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = eliterizer.h; path = ../src/devices/eliterizer/eliterizer.h; sourceTree = "<group>"; };
87C481F926DFA7C3006211AD /* eliterizer.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = eliterizer.cpp; path = ../src/devices/eliterizer/eliterizer.cpp; sourceTree = "<group>"; };
@@ -1609,15 +1571,6 @@
87DAE16626E9FF5000B0527E /* moc_shuaa5treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_shuaa5treadmill.cpp; sourceTree = "<group>"; };
87DAE16726E9FF5000B0527E /* moc_kingsmithr2treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kingsmithr2treadmill.cpp; sourceTree = "<group>"; };
87DAE16826E9FF5000B0527E /* moc_solef80treadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_solef80treadmill.cpp; sourceTree = "<group>"; };
87DC27E62D9BDB53007A1B9D /* echelonstairclimber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = echelonstairclimber.h; path = ../src/devices/echelonstairclimber/echelonstairclimber.h; sourceTree = SOURCE_ROOT; };
87DC27E72D9BDB53007A1B9D /* echelonstairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = echelonstairclimber.cpp; path = ../src/devices/echelonstairclimber/echelonstairclimber.cpp; sourceTree = SOURCE_ROOT; };
87DC27E82D9BDB53007A1B9D /* stairclimber.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = stairclimber.h; path = ../src/devices/stairclimber.h; sourceTree = SOURCE_ROOT; };
87DC27E92D9BDB53007A1B9D /* stairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = stairclimber.cpp; path = ../src/devices/stairclimber.cpp; sourceTree = SOURCE_ROOT; };
87DC27EC2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_echelonstairclimber.cpp; sourceTree = "<group>"; };
87DC27ED2D9BDB8F007A1B9D /* moc_stairclimber.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_stairclimber.cpp; sourceTree = "<group>"; };
87DC27F02D9BDC43007A1B9D /* moc_moxy5sensor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_moxy5sensor.cpp; sourceTree = "<group>"; };
87DC27F12D9BDC43007A1B9D /* moxy5sensor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = moxy5sensor.h; path = ../src/devices/moxy5sensor/moxy5sensor.h; sourceTree = SOURCE_ROOT; };
87DC27F22D9BDC43007A1B9D /* moxy5sensor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = moxy5sensor.cpp; path = ../src/devices/moxy5sensor/moxy5sensor.cpp; sourceTree = SOURCE_ROOT; };
87DED80427D1273800BE4FBB /* filedownloader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = filedownloader.cpp; path = ../src/filedownloader.cpp; sourceTree = "<group>"; };
87DED80527D1273900BE4FBB /* filedownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = filedownloader.h; path = ../src/filedownloader.h; sourceTree = "<group>"; };
87DED80727D1274500BE4FBB /* moc_filedownloader.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_filedownloader.cpp; sourceTree = "<group>"; };
@@ -1659,15 +1612,6 @@
87EB917F27EE5FE7002535E1 /* qdomyoszwift_qmltyperegistrations.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = qdomyoszwift_qmltyperegistrations.cpp; sourceTree = "<group>"; };
87EB918027EE5FE7002535E1 /* moc_inappproductqmltype.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_inappproductqmltype.cpp; sourceTree = "<group>"; };
87EB918127EE5FE7002535E1 /* moc_inappproduct.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_inappproduct.cpp; sourceTree = "<group>"; };
87EBB29D2D39214E00348B15 /* fitdatabaseprocessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fitdatabaseprocessor.h; path = ../src/fitdatabaseprocessor.h; sourceTree = SOURCE_ROOT; };
87EBB29E2D39214E00348B15 /* fitdatabaseprocessor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = fitdatabaseprocessor.cpp; path = ../src/fitdatabaseprocessor.cpp; sourceTree = SOURCE_ROOT; };
87EBB29F2D39214E00348B15 /* moc_fitdatabaseprocessor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_fitdatabaseprocessor.cpp; sourceTree = "<group>"; };
87EBB2A02D39214E00348B15 /* moc_workoutloaderworker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_workoutloaderworker.cpp; sourceTree = "<group>"; };
87EBB2A12D39214E00348B15 /* moc_workoutmodel.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_workoutmodel.cpp; sourceTree = "<group>"; };
87EBB2A22D39214E00348B15 /* workoutloaderworker.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = workoutloaderworker.h; path = ../src/workoutloaderworker.h; sourceTree = SOURCE_ROOT; };
87EBB2A32D39214E00348B15 /* workoutloaderworker.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = workoutloaderworker.cpp; path = ../src/workoutloaderworker.cpp; sourceTree = SOURCE_ROOT; };
87EBB2A42D39214E00348B15 /* workoutmodel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = workoutmodel.h; path = ../src/workoutmodel.h; sourceTree = SOURCE_ROOT; };
87EBB2A52D39214E00348B15 /* workoutmodel.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = workoutmodel.cpp; path = ../src/workoutmodel.cpp; sourceTree = SOURCE_ROOT; };
87EFB56C25BD703C0039DD5A /* proformtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformtreadmill.cpp; path = ../src/devices/proformtreadmill/proformtreadmill.cpp; sourceTree = "<group>"; };
87EFB56D25BD703C0039DD5A /* proformtreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformtreadmill.h; path = ../src/devices/proformtreadmill/proformtreadmill.h; sourceTree = "<group>"; };
87EFB56F25BD704A0039DD5A /* moc_proformtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformtreadmill.cpp; sourceTree = "<group>"; };
@@ -1677,12 +1621,6 @@
87F02E3E29178523000DB52C /* octaneelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = octaneelliptical.cpp; path = ../src/devices/octaneelliptical/octaneelliptical.cpp; sourceTree = "<group>"; };
87F02E3F29178524000DB52C /* octaneelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = octaneelliptical.h; path = ../src/devices/octaneelliptical/octaneelliptical.h; sourceTree = "<group>"; };
87F02E4129178545000DB52C /* moc_octaneelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_octaneelliptical.cpp; sourceTree = "<group>"; };
87F1BD652DBFBCE700416506 /* android_antbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = android_antbike.h; path = ../src/devices/android_antbike/android_antbike.h; sourceTree = SOURCE_ROOT; };
87F1BD662DBFBCE700416506 /* android_antbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = android_antbike.cpp; path = ../src/devices/android_antbike/android_antbike.cpp; sourceTree = SOURCE_ROOT; };
87F1BD672DBFBCE700416506 /* moc_android_antbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_android_antbike.cpp; sourceTree = "<group>"; };
87F1BD6E2DC0D59600416506 /* coresensor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = coresensor.h; path = ../src/devices/coresensor/coresensor.h; sourceTree = SOURCE_ROOT; };
87F1BD6F2DC0D59600416506 /* coresensor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = coresensor.cpp; path = ../src/devices/coresensor/coresensor.cpp; sourceTree = SOURCE_ROOT; };
87F1BD702DC0D59600416506 /* moc_coresensor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_coresensor.cpp; sourceTree = "<group>"; };
87F4FB5829D550BF0061BB4A /* schwinn170bike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = schwinn170bike.cpp; path = ../src/devices/schwinn170bike/schwinn170bike.cpp; sourceTree = "<group>"; };
87F4FB5929D550BF0061BB4A /* schwinn170bike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = schwinn170bike.h; path = ../src/devices/schwinn170bike/schwinn170bike.h; sourceTree = "<group>"; };
87F4FB5B29D550DF0061BB4A /* moc_schwinn170bike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_schwinn170bike.cpp; sourceTree = "<group>"; };
@@ -2081,8 +2019,6 @@
25B08E2869634E9BCBA333A2 /* Generated Sources */ = {
isa = PBXGroup;
children = (
878895D92DD48AB100BF5162 /* inclinationresistancetable.h */,
878895DA2DD48AB100BF5162 /* moc_inclinationresistancetable.cpp */,
87C4E5BC2C1C1D2600D0750E /* moc_crossrope.cpp */,
87C424252BC1294000503687 /* moc_treadmillErgTable.cpp */,
874823FD2B935ADA006F3CA1 /* moc_ergtable.cpp */,
@@ -2255,35 +2191,6 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
87EBB29D2D39214E00348B15 /* fitdatabaseprocessor.h */,
87EBB29E2D39214E00348B15 /* fitdatabaseprocessor.cpp */,
87EBB29F2D39214E00348B15 /* moc_fitdatabaseprocessor.cpp */,
87EBB2A02D39214E00348B15 /* moc_workoutloaderworker.cpp */,
87EBB2A12D39214E00348B15 /* moc_workoutmodel.cpp */,
87EBB2A22D39214E00348B15 /* workoutloaderworker.h */,
87EBB2A32D39214E00348B15 /* workoutloaderworker.cpp */,
87EBB2A42D39214E00348B15 /* workoutmodel.h */,
87EBB2A52D39214E00348B15 /* workoutmodel.cpp */,
878C9DC62DF01C16001114D5 /* moc_speraxtreadmill.cpp */,
878C9DC72DF01C16001114D5 /* speraxtreadmill.h */,
878C9DC82DF01C16001114D5 /* speraxtreadmill.cpp */,
87F1BD6E2DC0D59600416506 /* coresensor.h */,
87F1BD6F2DC0D59600416506 /* coresensor.cpp */,
87F1BD702DC0D59600416506 /* moc_coresensor.cpp */,
8767CA5E2DA800590003001F /* ios_zwiftclickremote.h */,
8767CA5F2DA800590003001F /* ios_zwiftclickremote.mm */,
8767CA5B2DA7F5170003001F /* ios_wahookickrsnapbike.h */,
8767CA5C2DA7F5170003001F /* ios_wahookickrsnapbike.mm */,
87BFEA2D2CEDDEEE00BDD759 /* ios_echelonconnectsport.h */,
87BFEA2E2CEDDEEE00BDD759 /* ios_echelonconnectsport.mm */,
8767CA522DA3C1FD0003001F /* elitesquarecontroller.h */,
8767CA532DA3C1FD0003001F /* elitesquarecontroller.cpp */,
8767CA542DA3C1FD0003001F /* moc_elitesquarecontroller.cpp */,
87DC27F02D9BDC43007A1B9D /* moc_moxy5sensor.cpp */,
87DC27F12D9BDC43007A1B9D /* moxy5sensor.h */,
87DC27F22D9BDC43007A1B9D /* moxy5sensor.cpp */,
87DC27EC2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp */,
87DC27ED2D9BDB8F007A1B9D /* moc_stairclimber.cpp */,
8798FDC02D66075B00CF8EE8 /* osxbluetooth_p.h */,
8798FDC12D66075B00CF8EE8 /* osxbtcentralmanager_p.h */,
8798FDC22D66075B00CF8EE8 /* osxbtgcdtimer_p.h */,
@@ -2779,13 +2686,6 @@
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */,
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */,
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */,
87DC27E62D9BDB53007A1B9D /* echelonstairclimber.h */,
87DC27E72D9BDB53007A1B9D /* echelonstairclimber.cpp */,
87DC27E82D9BDB53007A1B9D /* stairclimber.h */,
87DC27E92D9BDB53007A1B9D /* stairclimber.cpp */,
87F1BD652DBFBCE700416506 /* android_antbike.h */,
87F1BD662DBFBCE700416506 /* android_antbike.cpp */,
87F1BD672DBFBCE700416506 /* moc_android_antbike.cpp */,
);
name = Sources;
sourceTree = "<group>";
@@ -3567,8 +3467,6 @@
873824C027E64707004F1B46 /* moc_dirconmanager.cpp in Compile Sources */,
2F0E70F826316F3600E11F3A /* virtualbike_zwift.swift in Compile Sources */,
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */,
87DC27EE2D9BDB8F007A1B9D /* moc_stairclimber.cpp in Compile Sources */,
87DC27EF2D9BDB8F007A1B9D /* moc_echelonstairclimber.cpp in Compile Sources */,
87062646259480B200D06586 /* ViewController.swift in Compile Sources */,
87D269A425F535340076AA48 /* moc_m3ibike.cpp in Compile Sources */,
87BAFE482B8CA7AA00065FCD /* moc_focustreadmill.cpp in Compile Sources */,
@@ -3627,12 +3525,9 @@
87F02E4229178545000DB52C /* moc_octaneelliptical.cpp in Compile Sources */,
87E2F85D291ED308002BDC65 /* lifefitnesstreadmill.cpp in Compile Sources */,
8752C0E82B15D85600C3D1A5 /* eliteariafan.cpp in Compile Sources */,
8767CA602DA800590003001F /* ios_zwiftclickremote.mm in Compile Sources */,
87917A7328E768D200F8D9AC /* Browser.swift in Compile Sources */,
873CD20B27EF8D8A000131BC /* inapptransaction.cpp in Compile Sources */,
8727C7D52B3BF1E4005429EB /* moc_proformtelnetbike.cpp in Compile Sources */,
8767CA552DA3C1FD0003001F /* elitesquarecontroller.cpp in Compile Sources */,
8767CA562DA3C1FD0003001F /* moc_elitesquarecontroller.cpp in Compile Sources */,
873824EF27E647A9004F1B46 /* query.cpp in Compile Sources */,
876F45FF279350D9003CDA5A /* moc_concept2skierg.cpp in Compile Sources */,
BE93C6EF2C2A6BFEEC9EA565 /* fit_buffered_mesg_broadcaster.cpp in Compile Sources */,
@@ -3640,7 +3535,6 @@
87DAE16426E9FF3A00B0527E /* kingsmithr2treadmill.cpp in Compile Sources */,
87F93427278E0EC00088B596 /* domyosrower.cpp in Compile Sources */,
87B617EE25F25FED0094A1CB /* snodebike.cpp in Compile Sources */,
878895DB2DD48AB100BF5162 /* moc_inclinationresistancetable.cpp in Compile Sources */,
87C5F0B526285E5F0067A1B5 /* mimemessage.cpp in Compile Sources */,
8768C8C92BBC11C80099DBE1 /* adb_client.c in Compile Sources */,
873063C0259DF2C500DA0F44 /* moc_heartratebelt.cpp in Compile Sources */,
@@ -3710,8 +3604,6 @@
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */,
87CC3BA425A0885F001EC5A8 /* elliptical.cpp in Compile Sources */,
4AD2C93A2B8FD5855E521630 /* fit_encode.cpp in Compile Sources */,
87DC27F32D9BDC43007A1B9D /* moc_moxy5sensor.cpp in Compile Sources */,
87DC27F42D9BDC43007A1B9D /* moxy5sensor.cpp in Compile Sources */,
87EB918C27EE5FE7002535E1 /* moc_inappproduct.cpp in Compile Sources */,
87E34C2D2886F99A00CEDE4B /* moc_octanetreadmill.cpp in Compile Sources */,
87D91F9A2800B9970026D43C /* proformwifibike.cpp in Compile Sources */,
@@ -3740,7 +3632,6 @@
87EFB56E25BD703D0039DD5A /* proformtreadmill.cpp in Compile Sources */,
87DA8465284933D200B550E9 /* fakeelliptical.cpp in Compile Sources */,
876E50F52B701C050080FAAF /* moc_zwiftclickremote.cpp in Compile Sources */,
8767CA5D2DA7F5170003001F /* ios_wahookickrsnapbike.mm in Compile Sources */,
87FE5BAF2692F3130056EFC8 /* tacxneo2.cpp in Compile Sources */,
8718CBAC263063CE004BF4EE /* moc_tcpclientinfosender.cpp in Compile Sources */,
873824B527E64707004F1B46 /* moc_provider_p.cpp in Compile Sources */,
@@ -3768,8 +3659,6 @@
8720890E2CE65451008C2C17 /* qmqttsubscriptionproperties.cpp in Compile Sources */,
8720890F2CE65451008C2C17 /* qmqttconnectionproperties.cpp in Compile Sources */,
872089102CE65451008C2C17 /* qmqttconnection.cpp in Compile Sources */,
878C9DC92DF01C16001114D5 /* speraxtreadmill.cpp in Compile Sources */,
878C9DCA2DF01C16001114D5 /* moc_speraxtreadmill.cpp in Compile Sources */,
872089112CE65451008C2C17 /* qmqttsubscription.cpp in Compile Sources */,
872089122CE65451008C2C17 /* qmqttclient.cpp in Compile Sources */,
872089132CE65451008C2C17 /* qmqtttopicfilter.cpp in Compile Sources */,
@@ -3829,12 +3718,6 @@
8768C9022BBC12B80099DBE1 /* socket_loopback_client.c in Compile Sources */,
87C5F0B926285E5F0067A1B5 /* mimehtml.cpp in Compile Sources */,
27E452D452B62D0948DF0755 /* sessionline.cpp in Compile Sources */,
87EBB2A62D39214E00348B15 /* moc_workoutloaderworker.cpp in Compile Sources */,
87EBB2A72D39214E00348B15 /* workoutmodel.cpp in Compile Sources */,
87EBB2A82D39214E00348B15 /* workoutloaderworker.cpp in Compile Sources */,
87EBB2A92D39214E00348B15 /* moc_fitdatabaseprocessor.cpp in Compile Sources */,
87EBB2AA2D39214E00348B15 /* fitdatabaseprocessor.cpp in Compile Sources */,
87EBB2AB2D39214E00348B15 /* moc_workoutmodel.cpp in Compile Sources */,
E40895A73216AC52D35083D9 /* signalhandler.cpp in Compile Sources */,
873CD22427EF8E18000131BC /* inappproductqmltype.cpp in Compile Sources */,
87DF68BF25E2675100FCDA46 /* moc_schwinnic4bike.cpp in Compile Sources */,
@@ -3852,7 +3735,6 @@
873824BB27E64707004F1B46 /* moc_prober_p.cpp in Compile Sources */,
877758B32C98627300BB1697 /* moc_sportstechelliptical.cpp in Compile Sources */,
8742C2B227C92C30007D3FA0 /* wahookickrsnapbike.cpp in Compile Sources */,
87BFEA2F2CEDDEEE00BDD759 /* ios_echelonconnectsport.mm in Compile Sources */,
87EB918327EE5FE7002535E1 /* moc_inappstore.cpp in Compile Sources */,
87CF516B293C87B000A7CABC /* moc_characteristicwriteprocessore005.cpp in Compile Sources */,
8780D949264FB8B800192D41 /* moc_smartspin2k.cpp in Compile Sources */,
@@ -3953,8 +3835,6 @@
87DA62A42D2305E4008ADA0F /* moc_characteristicnotifier0002.cpp in Compile Sources */,
87DA62A52D2305E4008ADA0F /* moc_characteristicwriteprocessor0003.cpp in Compile Sources */,
876ED21A25C3E9010065F3DC /* moc_material.cpp in Compile Sources */,
87DC27EA2D9BDB53007A1B9D /* echelonstairclimber.cpp in Compile Sources */,
87DC27EB2D9BDB53007A1B9D /* stairclimber.cpp in Compile Sources */,
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */,
87D10552290996EA00B3935B /* mepanelbike.cpp in Compile Sources */,
9D9484EED654597C394345DE /* moc_echelonconnectsport.cpp in Compile Sources */,
@@ -3974,8 +3854,6 @@
87A33F1A2D611D8400BFFF29 /* moc_logwriter.cpp in Compile Sources */,
87D105542909971100B3935B /* moc_mepanelbike.cpp in Compile Sources */,
87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */,
87F1BD712DC0D59600416506 /* moc_coresensor.cpp in Compile Sources */,
87F1BD722DC0D59600416506 /* coresensor.cpp in Compile Sources */,
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */,
87C5F0D726285E7E0067A1B5 /* moc_mimefile.cpp in Compile Sources */,
877FBA29276E684500F6C0C9 /* bowflextreadmill.cpp in Compile Sources */,
@@ -4019,8 +3897,6 @@
87A0771029B641D600A368BF /* wahookickrheadwind.cpp in Compile Sources */,
87EAC3D32D1D8D1D004FE975 /* moc_pitpatbike.cpp in Compile Sources */,
8791A8AB25C861BD003B50B2 /* inspirebike.cpp in Compile Sources */,
87F1BD682DBFBCE700416506 /* moc_android_antbike.cpp in Compile Sources */,
87F1BD692DBFBCE700416506 /* android_antbike.cpp in Compile Sources */,
876BFC9D27BE35C5001D7645 /* bowflext216treadmill.cpp in Compile Sources */,
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */,
87061397286D8CFE00D2446E /* PathController.cpp in Compile Sources */,
@@ -4411,7 +4287,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1115;
CURRENT_PROJECT_VERSION = 1052;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4447,7 +4323,6 @@
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/private,
../../Qt/5.15.2/ios/include/QtCore/5.15.2,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/,
../../Qt/5.15.2/ios/include/QtSql,
);
LIBRARY_SEARCH_PATHS = (
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
@@ -4494,7 +4369,7 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.18;
OTHER_CFLAGS = (
"-pipe",
"-g",
@@ -4606,7 +4481,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1115;
CURRENT_PROJECT_VERSION = 1052;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4644,7 +4519,6 @@
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/private,
../../Qt/5.15.2/ios/include/QtCore/5.15.2,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/,
../../Qt/5.15.2/ios/include/QtSql,
);
LIBRARY_SEARCH_PATHS = (
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
@@ -4691,7 +4565,7 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.18;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"-pipe",
@@ -4837,7 +4711,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1115;
CURRENT_PROJECT_VERSION = 1052;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4862,7 +4736,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.18;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -4933,7 +4807,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1115;
CURRENT_PROJECT_VERSION = 1052;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4954,7 +4828,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.18;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -5025,7 +4899,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1115;
CURRENT_PROJECT_VERSION = 1052;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5070,7 +4944,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.18;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -5141,7 +5015,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1115;
CURRENT_PROJECT_VERSION = 1052;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -5182,7 +5056,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.18;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";

View File

@@ -54,26 +54,20 @@ extension WorkoutTracking {
switch statistics.quantityType {
case HKQuantityType.quantityType(forIdentifier: .distanceCycling):
let distanceUnit = HKUnit.mile()
guard let value = statistics.mostRecentQuantity()?.doubleValue(for: distanceUnit) else {
return
}
let roundedValue = Double( round( 1 * value ) / 1 )
let value = statistics.mostRecentQuantity()?.doubleValue(for: distanceUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
delegate?.didReceiveHealthKitDistanceCycling(roundedValue)
case HKQuantityType.quantityType(forIdentifier: .activeEnergyBurned):
let energyUnit = HKUnit.kilocalorie()
guard let value = statistics.mostRecentQuantity()?.doubleValue(for: energyUnit) else {
return
}
let roundedValue = Double( round( 1 * value ) / 1 )
let value = statistics.mostRecentQuantity()?.doubleValue(for: energyUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
delegate?.didReceiveHealthKitActiveEnergyBurned(roundedValue)
case HKQuantityType.quantityType(forIdentifier: .heartRate):
let heartRateUnit = HKUnit.count().unitDivided(by: HKUnit.minute())
guard let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit) else {
return
}
let roundedValue = Double( round( 1 * value ) / 1 )
let value = statistics.mostRecentQuantity()?.doubleValue(for: heartRateUnit)
let roundedValue = Double( round( 1 * value! ) / 1 )
delegate?.didReceiveHealthKitHeartRate(roundedValue)
case HKQuantityType.quantityType(forIdentifier: .stepCount):
@@ -233,11 +227,9 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
let quantity = HKQuantity(unit: unit,
doubleValue: totalEnergyBurned)
let startDate = workoutSession.startDate ?? WorkoutTracking.lastDateMetric
let sample = HKCumulativeQuantitySeriesSample(type: quantityType,
quantity: quantity,
start: startDate,
start: workoutSession.startDate!,
end: Date())
workoutBuilder.add([sample]) {(success, error) in}
@@ -257,7 +249,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: startDate,
start: workoutSession.startDate!,
end: Date())
workoutBuilder.add([sampleDistance]) {(success, error) in
@@ -276,67 +268,6 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
}
}
} else if(sport == 4) { // Rowing
// Guard to check if steps quantity type is available
guard let quantityTypeSteps = HKQuantityType.quantityType(
forIdentifier: .stepCount) else {
return
}
let stepsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: Double(WorkoutTracking.steps))
// Create a sample for total steps
let sampleSteps = HKCumulativeQuantitySeriesSample(
type: quantityTypeSteps,
quantity: stepsQuantity,
start: startDate,
end: Date())
// Add the steps sample to workout builder
workoutBuilder.add([sampleSteps]) { (success, error) in
if let error = error {
print(error)
}
}
// Per il rowing, HealthKit utilizza un tipo specifico di distanza
// Se non esiste un tipo specifico per il rowing, possiamo usare un tipo generico di distanza
var quantityTypeDistance: HKQuantityType?
// In watchOS 10 e versioni successive, possiamo usare un tipo specifico se disponibile
if #available(watchOSApplicationExtension 10.0, *) {
// Verifica se esiste un tipo specifico per il rowing, altrimenti utilizza un tipo generico
quantityTypeDistance = HKQuantityType.quantityType(forIdentifier: .distanceSwimming)
} else {
// Nelle versioni precedenti, usa il tipo generico
quantityTypeDistance = HKQuantityType.quantityType(forIdentifier: .distanceWalkingRunning)
}
guard let typeDistance = quantityTypeDistance else {
return
}
let sampleDistance = HKCumulativeQuantitySeriesSample(type: typeDistance,
quantity: quantityMiles,
start: startDate,
end: Date())
workoutBuilder.add([sampleDistance]) {(success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
}
self.workoutBuilder.finishWorkout{ (workout, error) in
if let error = error {
print(error)
}
workout?.setValue(quantityMiles, forKey: "totalDistance")
}
}
}
} else {
// Guard to check if steps quantity type is available
@@ -351,7 +282,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
let sampleSteps = HKCumulativeQuantitySeriesSample(
type: quantityTypeSteps,
quantity: stepsQuantity, // Use your steps quantity here
start: startDate,
start: workoutSession.startDate!,
end: Date())
// Add the steps sample to workout builder
@@ -383,7 +314,7 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
let sampleDistance = HKCumulativeQuantitySeriesSample(type: quantityTypeDistance,
quantity: quantityMiles,
start: startDate,
start: workoutSession.startDate!,
end: Date())
workoutBuilder.add([sampleDistance]) {(success, error) in

View File

@@ -1,331 +0,0 @@
import sys
import logging
import asyncio
import threading
import random
import struct
import binascii
import time
from typing import Any, Union
# Verificare che siamo su macOS
if sys.platform != 'darwin':
print("Questo script è progettato specificamente per macOS!")
sys.exit(1)
# Importare bless
try:
from bless import (
BlessServer,
BlessGATTCharacteristic,
GATTCharacteristicProperties,
GATTAttributePermissions,
)
except ImportError:
print("Errore: impossibile importare bless. Installarlo con: pip install bless")
sys.exit(1)
# Configurazione logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
# Trigger per eventi
trigger = threading.Event()
# Informazioni sul dispositivo
DEVICE_NAME = "Wahoo KICKR 51A6"
# UUID dei servizi standard
CYCLING_POWER_SERVICE = "00001818-0000-1000-8000-00805f9b34fb"
USER_DATA_SERVICE = "0000181c-0000-1000-8000-00805f9b34fb"
FITNESS_MACHINE_SERVICE = "00001826-0000-1000-8000-00805f9b34fb"
# UUID dei servizi Wahoo personalizzati
WAHOO_SERVICE_1 = "a026ee01-0a7d-4ab3-97fa-f1500f9feb8b"
WAHOO_SERVICE_3 = "a026ee03-0a7d-4ab3-97fa-f1500f9feb8b"
WAHOO_SERVICE_6 = "a026ee06-0a7d-4ab3-97fa-f1500f9feb8b"
WAHOO_SERVICE_B = "a026ee0b-0a7d-4ab3-97fa-f1500f9feb8b"
# UUID delle caratteristiche standard
CYCLING_POWER_MEASUREMENT = "00002a63-0000-1000-8000-00805f9b34fb"
CYCLING_POWER_FEATURE = "00002a65-0000-1000-8000-00805f9b34fb"
SENSOR_LOCATION = "00002a5d-0000-1000-8000-00805f9b34fb"
CYCLING_POWER_CONTROL_POINT = "00002a66-0000-1000-8000-00805f9b34fb"
WEIGHT = "00002a98-0000-1000-8000-00805f9b34fb"
FITNESS_MACHINE_FEATURE = "00002acc-0000-1000-8000-00805f9b34fb"
TRAINING_STATUS = "00002ad3-0000-1000-8000-00805f9b34fb"
FITNESS_MACHINE_CONTROL_POINT = "00002ad9-0000-1000-8000-00805f9b34fb"
FITNESS_MACHINE_STATUS = "00002ada-0000-1000-8000-00805f9b34fb"
INDOOR_BIKE_DATA = "00002ad2-0000-1000-8000-00805f9b34fb"
# UUID delle caratteristiche Wahoo personalizzate
WAHOO_CUSTOM_CP_CHAR = "a026e005-0a7d-4ab3-97fa-f1500f9feb8b"
WAHOO_CHAR_1 = "a026e002-0a7d-4ab3-97fa-f1500f9feb8b"
WAHOO_CHAR_2 = "a026e004-0a7d-4ab3-97fa-f1500f9feb8b"
WAHOO_CHAR_3 = "a026e00a-0a7d-4ab3-97fa-f1500f9feb8b"
WAHOO_CHAR_4 = "a026e023-0a7d-4ab3-97fa-f1500f9feb8b"
WAHOO_CHAR_5 = "a026e037-0a7d-4ab3-97fa-f1500f9feb8b"
# Stato dispositivo - variabili globali
current_power = 120
current_cadence = 85
current_speed = 25.0
current_resistance = 5
# Funzioni di callback
def read_request(characteristic, **kwargs):
logger.debug(f"Lettura: {characteristic.value}")
return characteristic.value
def write_request(characteristic, value, **kwargs):
uuid_str = str(characteristic.uuid).lower()
logger.info(f"Scrittura su caratteristica: {uuid_str}, valore: {binascii.hexlify(value)}")
# Gestione delle richieste di scrittura
if uuid_str == FITNESS_MACHINE_CONTROL_POINT.lower():
handle_ftms_control_point(value)
elif uuid_str == CYCLING_POWER_CONTROL_POINT.lower():
handle_cp_control_point(value)
elif uuid_str in [WAHOO_CHAR_1.lower(), WAHOO_CHAR_3.lower(), WAHOO_CHAR_4.lower(), WAHOO_CHAR_5.lower()]:
handle_wahoo_char_write(uuid_str, value)
characteristic.value = value
# Gestori di richieste di scrittura
def handle_ftms_control_point(data):
global current_power, current_resistance
if not data:
return
op_code = data[0]
logger.info(f"Comando FTMS Control Point: {op_code:#x}")
if op_code == 0x05: # Set Target Power (ERG mode)
if len(data) >= 3:
target_power = int.from_bytes(data[1:3], byteorder='little')
logger.info(f"Target power impostato: {target_power}W")
current_power = target_power
def handle_cp_control_point(data):
if not data:
return
op_code = data[0]
logger.info(f"Comando CP Control Point: {op_code:#x}")
def handle_wahoo_char_write(uuid_str, data):
logger.info(f"Scrittura su caratteristica Wahoo {uuid_str}: {binascii.hexlify(data)}")
# Funzioni per generare dati
def generate_cycling_power_data():
global current_power, current_cadence
# Varia leggermente i valori
current_power += random.randint(-3, 3)
current_power = max(0, min(2000, current_power))
current_cadence += random.randint(-1, 1)
current_cadence = max(0, min(200, current_cadence))
# Crea Cycling Power Measurement
power_data = bytearray(16)
power_data[0:2] = (0x0034).to_bytes(2, byteorder='little')
power_data[2:4] = (current_power).to_bytes(2, byteorder='little')
power_data[4:8] = (int(current_power * 10)).to_bytes(4, byteorder='little')
power_data[8:12] = (0).to_bytes(4, byteorder='little')
power_data[12:14] = (current_cadence).to_bytes(2, byteorder='little')
power_data[14:16] = (0xBAD8).to_bytes(2, byteorder='little')
return bytes(power_data)
def generate_indoor_bike_data():
global current_speed, current_cadence
# Varia leggermente i valori
current_speed += random.uniform(-0.2, 0.2)
current_speed = max(0, min(60.0, current_speed))
# Crea Indoor Bike Data
bike_data = bytearray(8)
bike_data[0:2] = (0x0044).to_bytes(2, byteorder='little')
bike_data[2:4] = (int(current_speed * 100)).to_bytes(2, byteorder='little')
bike_data[4:6] = (current_cadence).to_bytes(2, byteorder='little')
bike_data[6:8] = (0).to_bytes(2, byteorder='little')
return bytes(bike_data)
async def run():
# Crea server con minimo di parametri
server = BlessServer(name=DEVICE_NAME)
server.read_request_func = read_request
server.write_request_func = write_request
logger.info(f"Configurazione del simulatore {DEVICE_NAME}...")
# 1. Servizi standard
# Aggiungi Cycling Power Service
await server.add_new_service(CYCLING_POWER_SERVICE)
await server.add_new_characteristic(
CYCLING_POWER_SERVICE,
CYCLING_POWER_MEASUREMENT,
GATTCharacteristicProperties.read | GATTCharacteristicProperties.notify,
None,
GATTAttributePermissions.readable
)
await server.add_new_characteristic(
CYCLING_POWER_SERVICE,
CYCLING_POWER_FEATURE,
GATTCharacteristicProperties.read,
None,
GATTAttributePermissions.readable
)
await server.add_new_characteristic(
CYCLING_POWER_SERVICE,
CYCLING_POWER_CONTROL_POINT,
GATTCharacteristicProperties.write | GATTCharacteristicProperties.indicate,
None,
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
)
await server.add_new_characteristic(
CYCLING_POWER_SERVICE,
WAHOO_CUSTOM_CP_CHAR,
GATTCharacteristicProperties.write | GATTCharacteristicProperties.indicate,
None,
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
)
# Aggiungi Fitness Machine Service
await server.add_new_service(FITNESS_MACHINE_SERVICE)
await server.add_new_characteristic(
FITNESS_MACHINE_SERVICE,
INDOOR_BIKE_DATA,
GATTCharacteristicProperties.read | GATTCharacteristicProperties.notify,
None,
GATTAttributePermissions.readable
)
await server.add_new_characteristic(
FITNESS_MACHINE_SERVICE,
FITNESS_MACHINE_CONTROL_POINT,
GATTCharacteristicProperties.write | GATTCharacteristicProperties.indicate,
None,
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
)
await server.add_new_characteristic(
FITNESS_MACHINE_SERVICE,
FITNESS_MACHINE_FEATURE,
GATTCharacteristicProperties.read,
None,
GATTAttributePermissions.readable
)
# 2. Servizi Wahoo personalizzati
# Wahoo Service 1
await server.add_new_service(WAHOO_SERVICE_1)
await server.add_new_characteristic(
WAHOO_SERVICE_1,
WAHOO_CHAR_1,
GATTCharacteristicProperties.write_without_response | GATTCharacteristicProperties.notify,
None,
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
)
await server.add_new_characteristic(
WAHOO_SERVICE_1,
WAHOO_CHAR_2,
GATTCharacteristicProperties.notify,
None,
GATTAttributePermissions.readable
)
# Wahoo Service 3
await server.add_new_service(WAHOO_SERVICE_3)
await server.add_new_characteristic(
WAHOO_SERVICE_3,
WAHOO_CHAR_3,
GATTCharacteristicProperties.write_without_response | GATTCharacteristicProperties.notify,
None,
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
)
# Wahoo Service 6
await server.add_new_service(WAHOO_SERVICE_6)
await server.add_new_characteristic(
WAHOO_SERVICE_6,
WAHOO_CHAR_4,
GATTCharacteristicProperties.write_without_response | GATTCharacteristicProperties.notify,
None,
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
)
# Wahoo Service B
await server.add_new_service(WAHOO_SERVICE_B)
await server.add_new_characteristic(
WAHOO_SERVICE_B,
WAHOO_CHAR_5,
GATTCharacteristicProperties.read | GATTCharacteristicProperties.write_without_response | GATTCharacteristicProperties.notify,
None,
GATTAttributePermissions.readable | GATTAttributePermissions.writeable
)
logger.info("Configurazione dei servizi completata")
# Avvia il server
await server.start()
logger.info(f"{DEVICE_NAME} è ora in fase di advertising")
# Imposta i valori iniziali DOPO l'avvio del server
# Valori per servizi standard
server.get_characteristic(CYCLING_POWER_MEASUREMENT).value = generate_cycling_power_data()
server.get_characteristic(CYCLING_POWER_FEATURE).value = (0x08).to_bytes(4, byteorder='little')
server.get_characteristic(INDOOR_BIKE_DATA).value = generate_indoor_bike_data()
server.get_characteristic(FITNESS_MACHINE_FEATURE).value = (0x02C6).to_bytes(4, byteorder='little')
# Valori per caratteristiche Wahoo
server.get_characteristic(WAHOO_CHAR_1).value = bytearray(1)
server.get_characteristic(WAHOO_CHAR_2).value = bytearray(1)
server.get_characteristic(WAHOO_CHAR_3).value = bytearray(1)
server.get_characteristic(WAHOO_CHAR_4).value = bytearray(1)
server.get_characteristic(WAHOO_CHAR_5).value = bytearray(1)
# Loop di aggiornamento
try:
counter = 0
while True:
# Aggiorna i dati principali
server.get_characteristic(INDOOR_BIKE_DATA).value = generate_indoor_bike_data()
server.get_characteristic(CYCLING_POWER_MEASUREMENT).value = generate_cycling_power_data()
# Invia notifiche
server.update_value(FITNESS_MACHINE_SERVICE, INDOOR_BIKE_DATA)
server.update_value(CYCLING_POWER_SERVICE, CYCLING_POWER_MEASUREMENT)
if counter % 10 == 0: # Log ogni 10 cicli
logger.info(f"Potenza: {current_power}W, Cadenza: {current_cadence}rpm, Velocità: {current_speed:.1f}km/h")
counter += 1
await asyncio.sleep(0.1)
except KeyboardInterrupt:
logger.info("Arresto richiesto dall'utente")
except Exception as e:
logger.error(f"Errore durante l'esecuzione: {e}")
finally:
await server.stop()
logger.info("Server arrestato")
if __name__ == "__main__":
print("=" * 80)
print(f"Wahoo KICKR 51A6 BLE Simulator per macOS (Versione completa)")
print("=" * 80)
print(f"Avvio della simulazione di {DEVICE_NAME}")
print("Premi Ctrl+C per terminare il server")
print("=" * 80)
try:
asyncio.run(run())
except KeyboardInterrupt:
print("\nSimulazione fermata dall'utente")
except Exception as e:
print(f"Errore: {e}")
print("Potrebbe essere necessario eseguire questo script con sudo")
sys.exit(1)

View File

@@ -1,51 +0,0 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.0
import Qt.labs.settings 1.0
import QtWebView 1.1
ColumnLayout {
signal popupclose()
id: column1
spacing: 10
anchors.fill: parent
Settings {
id: settings
}
WebView {
id: webView
anchors.fill: parent
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/previewchart/chart.htm"
visible: true
onLoadingChanged: {
if (loadRequest.errorString) {
console.error(loadRequest.errorString);
console.error("port " + settings.value("template_inner_QZWS_port"));
}
}
}
Timer {
id: chartJscheckStartFromWeb
interval: 200; running: true; repeat: true
onTriggered: {if(rootItem.startRequested) {rootItem.startRequested = false; rootItem.stopRequested = false; stackView.pop(); }}
}
Button {
id: closeButton
height: 50
width: parent.width
text: "Close"
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
onClicked: {
popupclose();
}
anchors {
bottom: parent.bottom
}
}
Component.onCompleted: {
headerToolbar.visible = true;
}
}

View File

@@ -60,7 +60,7 @@ Item {
anchors.horizontalCenter: parent.horizontalCenter
width: 370
height: 120
text: qsTr("Your Peloton account is now connected!")
text: qsTr("Your Peloton account is now connected!<br><br>Restart the app to apply this!")
}
}

View File

@@ -1,227 +0,0 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtCharts 2.15
Page {
id: workoutHistoryPage
// Signal for chart preview
signal fitfile_preview_clicked(var url)
// Sport type to icon mapping (using FIT_SPORT values)
function getSportIcon(sport) {
switch(parseInt(sport)) {
case 1: // FIT_SPORT_RUNNING
case 11: // FIT_SPORT_WALKING
return "🏃"; // Running/Walking
case 2: // FIT_SPORT_CYCLING
return "🚴"; // Cycling
case 4: // FIT_SPORT_FITNESS_EQUIPMENT (Elliptical)
return "⭕"; // Elliptical
case 15: // FIT_SPORT_ROWING
return "🚣"; // Rowing
case 84: // FIT_SPORT_JUMPROPE
return "🪢"; // Jump Rope
default:
return "💪"; // Generic workout
}
}
ColumnLayout {
anchors.fill: parent
spacing: 10
// Header
Rectangle {
Layout.fillWidth: true
height: 60
color: "#f5f5f5"
Text {
anchors.centerIn: parent
text: "Workout History"
font.pixelSize: 24
font.bold: true
}
}
// Loading indicator
BusyIndicator {
id: loadingIndicator
Layout.alignment: Qt.AlignHCenter
visible: workoutModel ? (workoutModel.isLoading || workoutModel.isDatabaseProcessing) : false
running: visible
}
// Database processing message
Text {
Layout.alignment: Qt.AlignHCenter
visible: workoutModel ? workoutModel.isDatabaseProcessing : false
text: "Processing workout files...\nThis may take a few moments on first startup."
horizontalAlignment: Text.AlignHCenter
color: "#666666"
font.pixelSize: 16
}
// Workout List
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: workoutModel
spacing: 8
clip: true
delegate: SwipeDelegate {
id: swipeDelegate
width: parent.width
height: 135
Component.onCompleted: {
console.log("Delegate data:", JSON.stringify({
sport: sport,
title: title,
date: date,
duration: duration,
distance: distance,
calories: calories,
id: id
}))
}
swipe.right: Rectangle {
width: parent.width
height: parent.height
color: "#FF4444"
clip: true
Row {
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
anchors.rightMargin: 20
Text {
text: "🗑️ Delete"
color: "white"
font.pixelSize: 16
anchors.verticalCenter: parent.verticalCenter
}
}
}
swipe.onCompleted: {
// Show confirmation dialog
confirmDialog.workoutId = model.id
confirmDialog.workoutTitle = model.title
confirmDialog.open()
}
// Card-like container
Rectangle {
anchors.fill: parent
anchors.margins: 8
radius: 10
color: "white"
border.color: "#e0e0e0"
RowLayout {
anchors.fill: parent
anchors.margins: 12
spacing: 16
// Sport icon
Column {
Layout.alignment: Qt.AlignVCenter
Text {
text: getSportIcon(sport)
font.pixelSize: 32
}
}
// Workout info
ColumnLayout {
Layout.fillWidth: true
spacing: 4
Text {
text: title
font.bold: true
font.pixelSize: 18
}
Text {
text: date
color: "#666666"
}
// Stats row
RowLayout {
spacing: 16
Text {
text: "⏱ " + duration
}
Text {
text: "📏 " + distance.toFixed(2) + " km"
}
}
RowLayout {
spacing: 16
Text {
text: "🔥 " + Math.round(calories) + " kcal"
}
}
}
}
}
onClicked: {
console.log("Workout clicked, ID:", model.id)
// Get workout details from the model
var details = workoutModel.getWorkoutDetails(model.id)
console.log("Workout details:", JSON.stringify(details))
// Emit signal with file URL for chart preview
console.log("Emitting fitfile_preview_clicked with path:", details.filePath)
var fileUrl = Qt.resolvedUrl("file://" + details.filePath)
console.log("Converted to URL:", fileUrl)
workoutHistoryPage.fitfile_preview_clicked(fileUrl)
// Push the ChartJsTest view
stackView.push("PreviewChart.qml")
}
}
}
}
// Confirmation Dialog
Dialog {
id: confirmDialog
property int workoutId
property string workoutTitle
title: "Delete Workout"
modal: true
standardButtons: Dialog.Ok | Dialog.Cancel
x: (parent.width - width) / 2
y: (parent.height - height) / 2
Text {
text: "Are you sure you want to delete '" + confirmDialog.workoutTitle + "'?"
}
onAccepted: {
workoutModel.deleteWorkout(confirmDialog.workoutId)
swipeDelegate.swipe.close()
}
onRejected: {
swipeDelegate.swipe.close()
}
}
}

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.19.2" android:versionCode="1114" android:installLocation="auto">
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.18.22" android:versionCode="1045" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
@@ -10,7 +10,7 @@
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:debuggable="false" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qdomyos-zwift" android:extractNativeLibs="true" android:icon="@drawable/icon" android:usesCleartextTraffic="true">
<activity android:theme="@style/Theme.AppCompat" android:exported="true" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.cagnulen.qdomyoszwift.CustomQtActivity" android:label="QZ" android:launchMode="singleTop">
<activity android:theme="@style/Theme.AppCompat" android:exported="true" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="QZ" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -120,7 +120,7 @@
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="36" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>

View File

@@ -44,7 +44,7 @@ dependencies {
def appcompat_version = "1.3.1"
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "com.android.billingclient:billing:8.0.0"
implementation "com.android.billingclient:billing:6.0.1"
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation "androidx.appcompat:appcompat:$appcompat_version"
@@ -129,7 +129,7 @@ android {
resConfig "en"
compileSdkVersion 33
minSdkVersion = 21
targetSdkVersion = 36
targetSdkVersion = 34
}
tasks.all { task ->

View File

@@ -1,4 +1,5 @@
package org.cagnulen.qdomyoszwift;
import android.annotation.SuppressLint;
import android.app.ActionBar;
import android.app.Activity;
@@ -22,145 +23,110 @@ import android.widget.Button;
import android.widget.NumberPicker;
import android.widget.TextView;
import android.widget.Toast;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.content.Intent;
public class Ant {
private ChannelService.ChannelServiceComm mChannelService = null;
private boolean mChannelServiceBound = false;
private final String TAG = "Ant";
public static Activity activity = null;
static boolean speedRequest = false;
static boolean heartRequest = false;
static boolean bikeRequest = false; // Added bike request flag
static boolean garminKey = false;
static boolean treadmill = false;
// Updated antStart method with BikeRequest parameter at the end
public void antStart(Activity a, boolean SpeedRequest, boolean HeartRequest, boolean GarminKey, boolean Treadmill, boolean BikeRequest) {
QLog.v(TAG, "antStart");
speedRequest = SpeedRequest;
heartRequest = HeartRequest;
treadmill = Treadmill;
garminKey = GarminKey;
bikeRequest = BikeRequest; // Set bike request flag
activity = a;
if(a != null)
QLog.v(TAG, "antStart activity is valid");
else
{
QLog.v(TAG, "antStart activity is invalid");
return;
}
public void antStart(Activity a, boolean SpeedRequest, boolean HeartRequest, boolean GarminKey, boolean Treadmill) {
Log.v(TAG, "antStart");
speedRequest = SpeedRequest;
heartRequest = HeartRequest;
treadmill = Treadmill;
garminKey = GarminKey;
activity = a;
if(a != null)
Log.v(TAG, "antStart activity is valid");
else
{
Log.v(TAG, "antStart activity is invalid");
return;
}
if(!mChannelServiceBound) doBindChannelService();
}
private ServiceConnection mChannelServiceConnection = new ServiceConnection()
{
@Override
public void onServiceConnected(ComponentName name, IBinder serviceBinder)
{
QLog.v(TAG, "mChannelServiceConnection.onServiceConnected...");
mChannelService = (ChannelService.ChannelServiceComm) serviceBinder;
QLog.v(TAG, "...mChannelServiceConnection.onServiceConnected");
}
@Override
public void onServiceConnected(ComponentName name, IBinder serviceBinder)
{
Log.v(TAG, "mChannelServiceConnection.onServiceConnected...");
@Override
public void onServiceDisconnected(ComponentName arg0)
{
QLog.v(TAG, "mChannelServiceConnection.onServiceDisconnected...");
// Clearing and disabling when disconnecting from ChannelService
mChannelService = null;
QLog.v(TAG, "...mChannelServiceConnection.onServiceDisconnected");
}
};
mChannelService = (ChannelService.ChannelServiceComm) serviceBinder;
Log.v(TAG, "...mChannelServiceConnection.onServiceConnected");
}
@Override
public void onServiceDisconnected(ComponentName arg0)
{
Log.v(TAG, "mChannelServiceConnection.onServiceDisconnected...");
// Clearing and disabling when disconnecting from ChannelService
mChannelService = null;
Log.v(TAG, "...mChannelServiceConnection.onServiceDisconnected");
}
};
private void doBindChannelService()
{
QLog.v(TAG, "doBindChannelService...");
// Binds to ChannelService. ChannelService binds and manages connection between the
// app and the ANT Radio Service
mChannelServiceBound = activity.bindService(new Intent(activity, ChannelService.class), mChannelServiceConnection, Context.BIND_AUTO_CREATE);
if(!mChannelServiceBound) //If the bind returns false, run the unbind method to update the GUI
doUnbindChannelService();
QLog.i(TAG, " Channel Service binding = "+ mChannelServiceBound);
QLog.v(TAG, "...doBindChannelService");
}
Log.v(TAG, "doBindChannelService...");
// Binds to ChannelService. ChannelService binds and manages connection between the
// app and the ANT Radio Service
mChannelServiceBound = activity.bindService(new Intent(activity, ChannelService.class), mChannelServiceConnection , Context.BIND_AUTO_CREATE);
if(!mChannelServiceBound) //If the bind returns false, run the unbind method to update the GUI
doUnbindChannelService();
Log.i(TAG, " Channel Service binding = "+ mChannelServiceBound);
Log.v(TAG, "...doBindChannelService");
}
public void doUnbindChannelService()
{
QLog.v(TAG, "doUnbindChannelService...");
if(mChannelServiceBound)
{
activity.unbindService(mChannelServiceConnection);
mChannelServiceBound = false;
}
QLog.v(TAG, "...doUnbindChannelService");
}
Log.v(TAG, "doUnbindChannelService...");
if(mChannelServiceBound)
{
activity.unbindService(mChannelServiceConnection);
mChannelServiceBound = false;
}
Log.v(TAG, "...doUnbindChannelService");
}
public void setCadenceSpeedPower(float speed, int power, int cadence)
{
if(mChannelService == null)
return;
QLog.v(TAG, "setCadenceSpeedPower " + speed + " " + power + " " + cadence);
mChannelService.setSpeed(speed);
mChannelService.setPower(power);
mChannelService.setCadence(cadence);
if(mChannelService == null)
return;
Log.v(TAG, "setCadenceSpeedPower " + speed + " " + power + " " + cadence);
mChannelService.setSpeed(speed);
mChannelService.setPower(power);
mChannelService.setCadence(cadence);
}
public int getHeart()
{
if(mChannelService == null)
return 0;
QLog.v(TAG, "getHeart");
return mChannelService.getHeart();
}
if(mChannelService == null)
return 0;
// Added bike-related getter methods
public int getBikeCadence() {
if(mChannelService == null)
return 0;
QLog.v(TAG, "getBikeCadence");
return mChannelService.getBikeCadence();
}
public int getBikePower() {
if(mChannelService == null)
return 0;
QLog.v(TAG, "getBikePower");
return mChannelService.getBikePower();
}
public double getBikeSpeed() {
if(mChannelService == null)
return 0.0;
QLog.v(TAG, "getBikeSpeed");
return mChannelService.getBikeSpeed();
}
public long getBikeDistance() {
if(mChannelService == null)
return 0;
QLog.v(TAG, "getBikeDistance");
return mChannelService.getBikeDistance();
}
public boolean isBikeConnected() {
if(mChannelService == null)
return false;
QLog.v(TAG, "isBikeConnected");
return mChannelService.isBikeConnected();
}
public void updateBikeTransmitterExtendedMetrics(long distanceMeters, int heartRate,
double elapsedTimeSeconds, int resistance,
double inclination) {
if(mChannelService == null)
return;
QLog.v(TAG, "updateBikeTransmitterExtendedMetrics");
mChannelService.updateBikeTransmitterExtendedMetrics(distanceMeters, heartRate,
elapsedTimeSeconds, resistance,
inclination);
Log.v(TAG, "getHeart");
return mChannelService.getHeart();
}
}

View File

@@ -1,239 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.content.Context;
import org.cagnulen.qdomyoszwift.QLog;
import android.app.Activity;
// ANT+ Plugin imports
import com.dsi.ant.plugins.antplus.pcc.AntPlusFitnessEquipmentPcc;
import com.dsi.ant.plugins.antplus.pcc.AntPlusFitnessEquipmentPcc.IFitnessEquipmentStateReceiver;
import com.dsi.ant.plugins.antplus.pcc.AntPlusFitnessEquipmentPcc.IBikeDataReceiver;
import com.dsi.ant.plugins.antplus.pcc.AntPlusFitnessEquipmentPcc.IGeneralFitnessEquipmentDataReceiver;
import com.dsi.ant.plugins.antplus.pcc.AntPlusFitnessEquipmentPcc.EquipmentState;
import com.dsi.ant.plugins.antplus.pcc.AntPlusFitnessEquipmentPcc.EquipmentType;
import com.dsi.ant.plugins.antplus.pcc.AntPlusFitnessEquipmentPcc.HeartRateDataSource;
import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState;
import com.dsi.ant.plugins.antplus.pcc.defines.EventFlag;
import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult;
import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IDeviceStateChangeReceiver;
import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IPluginAccessResultReceiver;
import com.dsi.ant.plugins.antplus.pccbase.PccReleaseHandle;
// Java imports
import java.math.BigDecimal;
import java.util.EnumSet;
public class BikeChannelController {
private static final String TAG = BikeChannelController.class.getSimpleName();
private Context context;
private AntPlusFitnessEquipmentPcc fePcc = null;
private PccReleaseHandle<AntPlusFitnessEquipmentPcc> releaseHandle = null;
private boolean isConnected = false;
// Bike data fields
public int cadence = 0; // Current cadence in RPM
public int power = 0; // Current power in watts
public BigDecimal speed = new BigDecimal(0); // Current speed in m/s
public long distance = 0; // Total distance in meters
public long calories = 0; // Total calories burned
public EquipmentType equipmentType = EquipmentType.UNKNOWN;
public EquipmentState equipmentState = EquipmentState.ASLEEP_OFF;
public int heartRate = 0; // Heart rate from equipment
public HeartRateDataSource heartRateSource = HeartRateDataSource.UNKNOWN;
public BigDecimal elapsedTime = new BigDecimal(0); // Elapsed time in seconds
// Fitness equipment state receiver
private final IFitnessEquipmentStateReceiver mFitnessEquipmentStateReceiver =
new IFitnessEquipmentStateReceiver() {
@Override
public void onNewFitnessEquipmentState(long estTimestamp,
EnumSet<EventFlag> eventFlags,
EquipmentType type,
EquipmentState state) {
equipmentType = type;
equipmentState = state;
QLog.d(TAG, "Equipment type: " + type + ", State: " + state);
// Only subscribe to bike specific data if this is actually a bike
if (type == EquipmentType.BIKE && !isSubscribedToBikeData) {
subscribeToBikeSpecificData();
isSubscribedToBikeData = true;
}
}
};
public BikeChannelController() {
this.context = Ant.activity;
openChannel();
}
public boolean openChannel() {
// Request access to first available fitness equipment device
// Using requestNewOpenAccess from the sample code
releaseHandle = AntPlusFitnessEquipmentPcc.requestNewOpenAccess(
(Activity)context,
context,
new IPluginAccessResultReceiver<AntPlusFitnessEquipmentPcc>() {
@Override
public void onResultReceived(AntPlusFitnessEquipmentPcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) {
switch(resultCode) {
case SUCCESS:
fePcc = result;
isConnected = true;
QLog.d(TAG, "Connected to fitness equipment: " + result.getDeviceName());
subscribeToBikeEvents();
break;
case CHANNEL_NOT_AVAILABLE:
QLog.e(TAG, "Channel Not Available");
break;
case ADAPTER_NOT_DETECTED:
QLog.e(TAG, "ANT Adapter Not Available");
break;
case BAD_PARAMS:
QLog.e(TAG, "Bad request parameters");
break;
case OTHER_FAILURE:
QLog.e(TAG, "RequestAccess failed");
break;
case DEPENDENCY_NOT_INSTALLED:
QLog.e(TAG, "Dependency not installed");
break;
case USER_CANCELLED:
QLog.e(TAG, "User cancelled");
break;
default:
QLog.e(TAG, "Unrecognized result: " + resultCode);
break;
}
}
},
new IDeviceStateChangeReceiver() {
@Override
public void onDeviceStateChange(DeviceState newDeviceState) {
QLog.d(TAG, "Device State Changed to: " + newDeviceState);
if (newDeviceState == DeviceState.DEAD) {
isConnected = false;
}
}
},
mFitnessEquipmentStateReceiver
);
return isConnected;
}
private void subscribeToBikeEvents() {
if (fePcc != null) {
// General fitness equipment data
fePcc.subscribeGeneralFitnessEquipmentDataEvent(new IGeneralFitnessEquipmentDataReceiver() {
@Override
public void onNewGeneralFitnessEquipmentData(long estTimestamp, EnumSet<EventFlag> eventFlags,
BigDecimal elapsedTime, long cumulativeDistance,
BigDecimal instantaneousSpeed, boolean virtualInstantaneousSpeed,
int instantaneousHeartRate, HeartRateDataSource source) {
if (elapsedTime != null && elapsedTime.intValue() != -1) {
BikeChannelController.this.elapsedTime = elapsedTime;
}
if (cumulativeDistance != -1) {
distance = cumulativeDistance;
}
if (instantaneousSpeed != null && instantaneousSpeed.intValue() != -1) {
speed = instantaneousSpeed;
}
if (instantaneousHeartRate != -1) {
heartRate = instantaneousHeartRate;
heartRateSource = source;
}
QLog.d(TAG, "General Data - Time: " + elapsedTime + "s, Distance: " +
distance + "m, Speed: " + speed + "m/s, HR: " + heartRate + "bpm");
}
});
}
}
private boolean isSubscribedToBikeData = false;
private void subscribeToBikeSpecificData() {
if (fePcc != null) {
// Subscribe to bike specific data
fePcc.getBikeMethods().subscribeBikeDataEvent(new IBikeDataReceiver() {
@Override
public void onNewBikeData(long estTimestamp, EnumSet<EventFlag> eventFlags,
int instantaneousCadence, int instantaneousPower) {
if (instantaneousCadence != -1) {
cadence = instantaneousCadence;
}
if (instantaneousPower != -1) {
power = instantaneousPower;
}
QLog.d(TAG, "Bike Data - Cadence: " + cadence + "rpm, Power: " + power + "W");
}
});
}
}
public void close() {
if (releaseHandle != null) {
releaseHandle.close();
releaseHandle = null;
}
fePcc = null;
isConnected = false;
QLog.d(TAG, "Channel Closed");
}
// Getter methods for bike data
public int getCadence() {
return cadence;
}
public int getPower() {
return power;
}
public double getSpeedKph() {
// Convert from m/s to km/h
return speed.doubleValue() * 3.6;
}
public double getSpeedMps() {
return speed.doubleValue();
}
public long getDistance() {
return distance;
}
public long getCalories() {
return calories;
}
public int getHeartRate() {
return heartRate;
}
public BigDecimal getElapsedTime() {
return elapsedTime;
}
public EquipmentState getEquipmentState() {
return equipmentState;
}
public EquipmentType getEquipmentType() {
return equipmentType;
}
public boolean isConnected() {
return isConnected;
}
}

View File

@@ -1,651 +0,0 @@
/*
* Copyright 2012 Dynastream Innovations Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.cagnulen.qdomyoszwift;
import android.os.RemoteException;
import org.cagnulen.qdomyoszwift.QLog;
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntCommandFailedException;
import com.dsi.ant.channel.IAntChannelEventHandler;
import com.dsi.ant.message.ChannelId;
import com.dsi.ant.message.ChannelType;
import com.dsi.ant.message.EventCode;
import com.dsi.ant.message.fromant.AcknowledgedDataMessage;
import com.dsi.ant.message.fromant.ChannelEventMessage;
import com.dsi.ant.message.fromant.MessageFromAntType;
import com.dsi.ant.message.ipc.AntMessageParcel;
import android.os.RemoteException;
import java.util.Locale;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.TimeUnit;
import java.util.Random;
/**
* ANT+ Bike Transmitter Controller
* Follows exactly the same pattern as PowerChannelController but for Fitness Equipment
*/
public class BikeTransmitterController {
public static final int FITNESS_EQUIPMENT_SENSOR_ID = 0x9e3d4b67; // Different from power sensor
// The device type and transmission type to be part of the channel ID message
private static final int CHANNEL_FITNESS_EQUIPMENT_DEVICE_TYPE = 17; // Fitness Equipment
private static final int CHANNEL_FITNESS_EQUIPMENT_TRANSMISSION_TYPE = 5;
// The period and frequency values the channel will be configured to
private static final int CHANNEL_FITNESS_EQUIPMENT_PERIOD = 8192; // 4 Hz for FE
private static final int CHANNEL_FITNESS_EQUIPMENT_FREQUENCY = 57;
private static final String TAG = BikeTransmitterController.class.getSimpleName();
// ANT+ Data Page IDs for Fitness Equipment
private static final byte DATA_PAGE_GENERAL_FE = 0x10;
private static final byte DATA_PAGE_BIKE_DATA = 0x19;
private static final byte DATA_PAGE_TRAINER_DATA = 0x1A;
private static final byte DATA_PAGE_GENERAL_SETTINGS = 0x11;
private static Random randGen = new Random();
// Current bike metrics to transmit
int currentCadence = 0; // Current cadence in RPM
int currentPower = 0; // Current power in watts
double currentSpeedKph = 0.0; // Current speed in km/h
long totalDistance = 0; // Total distance in meters
int currentHeartRate = 0; // Heart rate in BPM
double elapsedTimeSeconds = 0.0; // Elapsed time in seconds
int currentResistance = 0; // Current resistance level (0-100)
double currentInclination = 0.0; // Current inclination in percentage
// Control commands received from ANT+ devices
private int requestedResistance = -1; // Requested resistance from controller
private int requestedPower = -1; // Requested power from controller
private double requestedInclination = -100; // Requested inclination from controller
private AntChannel mAntChannel;
private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
private boolean mIsOpen;
// Callbacks for control commands
public interface ControlCommandListener {
void onResistanceChangeRequested(int resistance);
void onPowerChangeRequested(int power);
void onInclinationChangeRequested(double inclination);
}
private ControlCommandListener controlListener = null;
public BikeTransmitterController(AntChannel antChannel) {
mAntChannel = antChannel;
openChannel();
}
/**
* Set the listener for control commands received from ANT+ devices
*/
public void setControlCommandListener(ControlCommandListener listener) {
this.controlListener = listener;
}
boolean openChannel() {
if (null != mAntChannel) {
if (mIsOpen) {
QLog.w(TAG, "Channel was already open");
} else {
// Channel ID message contains device number, type and transmission type
ChannelId channelId = new ChannelId(FITNESS_EQUIPMENT_SENSOR_ID & 0xFFFF,
CHANNEL_FITNESS_EQUIPMENT_DEVICE_TYPE, CHANNEL_FITNESS_EQUIPMENT_TRANSMISSION_TYPE);
try {
// Setting the channel event handler so that we can receive messages from ANT
mAntChannel.setChannelEventHandler(mChannelEventCallback);
// Performs channel assignment by assigning the type to the channel
mAntChannel.assign(ChannelType.BIDIRECTIONAL_MASTER);
// Configures the channel ID, messaging period and rf frequency after assigning,
// then opening the channel.
mAntChannel.setChannelId(channelId);
mAntChannel.setPeriod(CHANNEL_FITNESS_EQUIPMENT_PERIOD);
mAntChannel.setRfFrequency(CHANNEL_FITNESS_EQUIPMENT_FREQUENCY);
mAntChannel.open();
mIsOpen = true;
QLog.d(TAG, "Opened fitness equipment channel with device number: " + FITNESS_EQUIPMENT_SENSOR_ID);
} catch (RemoteException e) {
channelError(e);
} catch (AntCommandFailedException e) {
// This will release, and therefore unassign if required
channelError("Open failed", e);
}
}
} else {
QLog.w(TAG, "No channel available");
}
return mIsOpen;
}
public boolean startTransmission() {
return openChannel();
}
public void stopTransmission() {
close();
}
void channelError(RemoteException e) {
String logString = "Remote service communication failed.";
QLog.e(TAG, logString);
}
void channelError(String error, AntCommandFailedException e) {
StringBuilder logString;
if (e.getResponseMessage() != null) {
String initiatingMessageId = "0x" + Integer.toHexString(
e.getResponseMessage().getInitiatingMessageId());
String rawResponseCode = "0x" + Integer.toHexString(
e.getResponseMessage().getRawResponseCode());
logString = new StringBuilder(error)
.append(". Command ")
.append(initiatingMessageId)
.append(" failed with code ")
.append(rawResponseCode);
} else {
String attemptedMessageId = "0x" + Integer.toHexString(
e.getAttemptedMessageType().getMessageId());
String failureReason = e.getFailureReason().toString();
logString = new StringBuilder(error)
.append(". Command ")
.append(attemptedMessageId)
.append(" failed with reason ")
.append(failureReason);
}
QLog.e(TAG, logString.toString());
mAntChannel.release();
}
public void close() {
if (null != mAntChannel) {
mIsOpen = false;
// Releasing the channel to make it available for others.
// After releasing, the AntChannel instance cannot be reused.
mAntChannel.release();
mAntChannel = null;
}
QLog.e(TAG, "Fitness Equipment Channel Closed");
}
// Setter methods for updating bike metrics from the main application
public void setCadence(int cadence) {
this.currentCadence = Math.max(0, cadence);
}
public void setPower(int power) {
this.currentPower = Math.max(0, power);
}
public void setSpeedKph(double speedKph) {
this.currentSpeedKph = Math.max(0, speedKph);
}
public void setDistance(long distance) {
this.totalDistance = Math.max(0, distance);
}
public void setHeartRate(int heartRate) {
this.currentHeartRate = Math.max(0, Math.min(255, heartRate));
}
public void setElapsedTime(double timeSeconds) {
this.elapsedTimeSeconds = Math.max(0, timeSeconds);
}
public void setResistance(int resistance) {
this.currentResistance = Math.max(0, Math.min(100, resistance));
}
public void setInclination(double inclination) {
this.currentInclination = Math.max(-100, Math.min(100, inclination));
}
// Getter methods for the last requested control values
public int getRequestedResistance() {
return requestedResistance;
}
public int getRequestedPower() {
return requestedPower;
}
public double getRequestedInclination() {
return requestedInclination;
}
public void clearControlRequests() {
requestedResistance = -1;
requestedPower = -1;
requestedInclination = -100;
}
public boolean isTransmitting() {
return mIsOpen;
}
public String getTransmissionInfo() {
if (!mIsOpen) {
return "Transmission: STOPPED";
}
return String.format("Transmission: ACTIVE - Cadence: %drpm, Power: %dW, " +
"Speed: %.1fkm/h, Resistance: %d, Inclination: %.1f%%",
currentCadence, currentPower, currentSpeedKph,
currentResistance, currentInclination);
}
/**
* Helper method to convert byte array to hex string for debugging
*/
private String bytesToHex(byte[] bytes) {
StringBuilder hex = new StringBuilder();
for (byte b : bytes) {
hex.append(String.format("%02X ", b & 0xFF));
}
return hex.toString().trim();
}
/**
* Implements the Channel Event Handler Interface following PowerChannelController pattern
*/
public class ChannelEventCallback implements IAntChannelEventHandler {
int cnt = 0;
int eventCount = 0;
int eventPowerCount = 0;
int cumulativeDistance = 0;
int cumulativeWatt = 0;
int accumulatedTorque32 = 0;
Timer carousalTimer = null;
@Override
public void onChannelDeath() {
// Display channel death message when channel dies
QLog.e(TAG, "Fitness Equipment Channel Death");
}
@Override
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
QLog.d(TAG, "Rx: " + antParcel);
QLog.d(TAG, "Message Type: " + messageType);
byte[] payload = new byte[8];
// Start unsolicited transmission timer like PowerChannelController
if(carousalTimer == null) {
carousalTimer = new Timer(); // At this line a new Thread will be created
carousalTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
QLog.d(TAG, "Tx Unsolicited Fitness Equipment Data");
byte[] payload = new byte[8];
String debugString = "";
eventCount = (eventCount + 1) & 0xFF;
cumulativeDistance = (cumulativeDistance + (int)(currentSpeedKph / 3.6)) & 0xFFFF; // rough distance calc
cnt += 1;
// Cycle through different data pages like PowerChannelController
if (cnt % 5 == 0) {
// General FE Data Page (0x10)
debugString = buildGeneralFEDataPage(payload);
} else if (cnt % 5 == 1) {
// Bike Data Page (0x19)
debugString = buildBikeDataPage(payload);
} else if (cnt % 5 == 2) {
// Trainer Data Page (0x1A)
debugString = buildBikeDataPage(payload);
} else if (cnt % 5 == 3) {
// General Settings Page (0x11)
debugString = buildGeneralSettingsPage(payload);
} else {
// Default General FE Data Page (0x10)
debugString = buildGeneralFEDataPage(payload);
}
// Log the hex data and parsed values
QLog.d(TAG, "Tx Payload HEX: " + bytesToHex(payload));
QLog.d(TAG, debugString);
if (mIsOpen) {
try {
// Setting the data to be broadcast on the next channel period
mAntChannel.setBroadcastData(payload);
} catch (RemoteException e) {
channelError(e);
}
}
}
}, 0, 250); // Every 250ms for 4Hz
}
// Switching on message type to handle different types of messages
switch (messageType) {
case BROADCAST_DATA:
// Rx Data
break;
case ACKNOWLEDGED_DATA:
// Handle control commands
payload = new AcknowledgedDataMessage(antParcel).getPayload();
QLog.d(TAG, "AcknowledgedDataMessage: " + payload);
handleControlCommand(payload);
break;
case CHANNEL_EVENT:
// Constructing channel event message from parcel
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
EventCode code = eventMessage.getEventCode();
QLog.d(TAG, "Event Code: " + code);
// Switching on event code to handle the different types of channel events
switch (code) {
case TX:
cnt += 1;
String debugString = "";
// Cycle through different data pages like PowerChannelController
if (cnt % 16 == 1) {
// General FE Data Page (0x10)
debugString = buildGeneralFEDataPage(payload);
} else if (cnt % 16 == 5) {
// Bike Data Page (0x19)
debugString = buildBikeDataPage(payload);
} else if (cnt % 16 == 9) {
// Trainer Data Page (0x1A)
debugString = buildBikeDataPage(payload);
} else if (cnt % 16 == 13) {
// General Settings Page (0x11)
debugString = buildGeneralSettingsPage(payload);
} else {
// Default General FE Data Page (0x10)
debugString = buildGeneralFEDataPage(payload);
}
// Log the hex data and parsed values
QLog.d(TAG, "Tx Payload HEX: " + bytesToHex(payload));
QLog.d(TAG, debugString);
if (mIsOpen) {
try {
// Setting the data to be broadcast on the next channel period
mAntChannel.setBroadcastData(payload);
} catch (RemoteException e) {
channelError(e);
}
}
break;
case CHANNEL_COLLISION:
cnt += 1;
break;
case RX_SEARCH_TIMEOUT:
QLog.e(TAG, "No Device Found");
break;
case CHANNEL_CLOSED:
case RX_FAIL:
case RX_FAIL_GO_TO_SEARCH:
case TRANSFER_RX_FAILED:
case TRANSFER_TX_COMPLETED:
case TRANSFER_TX_FAILED:
case TRANSFER_TX_START:
case UNKNOWN:
// TODO More complex communication will need to handle these events
break;
}
break;
case ANT_VERSION:
case BURST_TRANSFER_DATA:
case CAPABILITIES:
case CHANNEL_ID:
case CHANNEL_RESPONSE:
case CHANNEL_STATUS:
case SERIAL_NUMBER:
case OTHER:
// TODO More complex communication will need to handle these message types
break;
}
}
/**
* Build General Fitness Equipment Data Page (0x10) - Page 16
* Following Table 8-7 format exactly
* @param payload byte array to populate
* @return debug string with hex and parsed values
*/
private String buildGeneralFEDataPage(byte[] payload) {
payload[0] = 0x10; // Data Page Number = 0x10 (Page 16)
// Byte 1: Equipment Type Bit Field (Refer to Table 8-8)
payload[1] = 0x19; // Equipment type: Bike (stationary bike = 0x19)
// Byte 2: Elapsed Time (0.25 seconds resolution, rollover at 64s)
int elapsedTime025s = (int) (elapsedTimeSeconds * 4) & 0xFF;
payload[2] = (byte) elapsedTime025s;
// Byte 3: Distance Traveled (1 meter resolution, rollover at 256m)
int distanceMeters = (int) (totalDistance) & 0xFF;
payload[3] = (byte) distanceMeters;
// Bytes 4-5: Speed (0.001 m/s resolution, 0xFFFF = invalid)
int speedMms = (int) (currentSpeedKph / 3.6 * 1000);
if (speedMms > 65534) speedMms = 65534; // Max valid value
payload[4] = (byte) (speedMms & 0xFF); // Speed LSB
payload[5] = (byte) ((speedMms >> 8) & 0xFF); // Speed MSB
// Byte 6: Heart Rate (0xFF = invalid)
payload[6] = (byte) (currentHeartRate == 0 ? 0xFF : currentHeartRate);
// Byte 7: Capabilities Bit Field (4 bits 0:3) + FE State Bit Field (4 bits 4:7)
payload[7] = 0x00; // Set to 0x00 for now (refer to Tables 8-9 and 8-10)
// Create debug string
return String.format(Locale.US,
"General FE Data Page (0x10): " +
"Page=0x%02X, Equipment=0x%02X(Bike), " +
"ElapsedTime=0x%02X(%.1fs), Distance=0x%02X(%dm), " +
"Speed=0x%02X%02X(%.1fkm/h), HeartRate=0x%02X(%s), " +
"Capabilities=0x%02X",
payload[0] & 0xFF, payload[1] & 0xFF,
payload[2] & 0xFF, elapsedTimeSeconds,
payload[3] & 0xFF, distanceMeters,
payload[5] & 0xFF, payload[4] & 0xFF, currentSpeedKph,
payload[6] & 0xFF, currentHeartRate == 0 ? "Invalid" : currentHeartRate + "bpm",
payload[7] & 0xFF);
}
/**
* Build Specific Trainer/Stationary Bike Data Page (0x19) - Page 25
* Following Table 8-25 format exactly
* @param payload byte array to populate
* @return debug string with hex and parsed values
*/
private String buildBikeDataPage(byte[] payload) {
payload[0] = 0x19; // Data Page Number = 0x19 (Page 25)
// Byte 1: Update Event Count (increments with each information update)
eventPowerCount = (eventPowerCount + 1) & 0xFF;
payload[1] = (byte) eventPowerCount;
// Byte 2: Instantaneous Cadence (RPM, 0xFF = invalid)
payload[2] = (byte) (currentCadence == 0 ? 0xFF : currentCadence);
// Bytes 3-4: Accumulated Power (1 watt resolution, rollover at 65536W)
// This is cumulative power, not instantaneous
cumulativeWatt = (cumulativeWatt + currentPower);
payload[3] = (byte) (cumulativeWatt & 0xFF); // Accumulated Power LSB
payload[4] = (byte) ((cumulativeWatt >> 8) & 0xFF); // Accumulated Power MSB
// Bytes 5-6: Instantaneous Power (1.5 bytes, 0xFFF = invalid for both fields)
if (currentPower > 4094) {
// 0xFFF indicates BOTH instantaneous and accumulated power fields are invalid
payload[5] = (byte) 0xFF; // Instantaneous Power LSB
payload[6] = (byte) 0xFF; // Instantaneous Power MSB (bits 0-3) + Trainer Status (bits 4-7)
} else {
payload[5] = (byte) (currentPower & 0xFF); // Instantaneous Power LSB
payload[6] = (byte) ((currentPower >> 8) & 0x0F); // Instantaneous Power MSN (bits 0-3)
// Bits 4-7 of byte 6: Trainer Status Bit Field (refer to Table 8-27)
payload[6] |= 0x00; // Trainer status = 0 for now
}
// Byte 7: Flags Bit Field (bits 0-3) + FE State Bit Field (bits 4-7)
payload[7] = 0x00; // Set to 0x00 for now
// Create debug string
String cadenceStr = currentCadence == 0 ? "Invalid" : currentCadence + "rpm";
String powerStr = currentPower > 4094 ? "Invalid" : currentPower + "W";
return String.format(Locale.US,
"Bike Data Page (0x19): " +
"Page=0x%02X, EventCount=0x%02X(%d), " +
"Cadence=0x%02X(%s), AccumPower=0x%02X%02X(%dW), " +
"InstPower=0x%X%02X(%s), Flags=0x%02X",
payload[0] & 0xFF, payload[1] & 0xFF, eventCount,
payload[2] & 0xFF, cadenceStr,
payload[4] & 0xFF, payload[3] & 0xFF, cumulativeWatt,
(payload[6] & 0x0F), payload[5] & 0xFF, powerStr,
payload[7] & 0xFF);
}
/**
* Build General Settings Page (0x11) - Page 17
* Following Table 8-11 format exactly
* @param payload byte array to populate
* @return debug string with hex and parsed values
*/
private String buildGeneralSettingsPage(byte[] payload) {
payload[0] = 0x11; // Data Page Number = 0x11 (Page 17)
// Byte 1: Reserved (0xFF - Do not interpret)
payload[1] = (byte) 0xFF;
// Byte 2: Reserved (0xFF - Do not interpret)
payload[2] = (byte) 0xFF;
// Byte 3: Cycle length (0.01 meters resolution, 0xFF = invalid)
// Length of one 'cycle' - for bike this could be wheel circumference
int cycleLengthCm = 210; // 2.1m wheel circumference = 210cm
payload[3] = (byte) (cycleLengthCm & 0xFF);
// Bytes 4-5: Incline Percentage (signed integer, 0.01% resolution, 0x7FFF = invalid)
int inclinePercent001 = (int) (currentInclination * 100); // Convert to 0.01% units
if (inclinePercent001 < -10000) inclinePercent001 = -10000; // Min -100.00%
if (inclinePercent001 > 10000) inclinePercent001 = 10000; // Max +100.00%
payload[4] = (byte) (inclinePercent001 & 0xFF); // Incline LSB
payload[5] = (byte) ((inclinePercent001 >> 8) & 0xFF); // Incline MSB
// Byte 6: Resistance Level (0.5% resolution, percentage of maximum applicable resistance)
int resistanceLevel05 = (int) (currentResistance * 2); // Convert to 0.5% units
if (resistanceLevel05 > 200) resistanceLevel05 = 200; // Max 100% = 200 in 0.5% units
payload[6] = (byte) (resistanceLevel05 & 0xFF);
// Byte 7: Capabilities Bit Field (bits 0-3) + FE State Bit Field (bits 4-7)
payload[7] = 0x00; // Set to 0x00 for now
// Create debug string
return String.format(Locale.US,
"General Settings Page (0x11): " +
"Page=0x%02X, Reserved1=0x%02X, Reserved2=0x%02X, " +
"CycleLength=0x%02X(%.2fm), Incline=0x%02X%02X(%.2f%%), " +
"Resistance=0x%02X(%d%%), Capabilities=0x%02X",
payload[0] & 0xFF, payload[1] & 0xFF, payload[2] & 0xFF,
payload[3] & 0xFF, cycleLengthCm / 100.0,
payload[5] & 0xFF, payload[4] & 0xFF, currentInclination,
payload[6] & 0xFF, currentResistance,
payload[7] & 0xFF);
}
/**
* Handle incoming control commands
*/
private void handleControlCommand(byte[] data) {
if (data.length < 8) return;
byte pageNumber = data[0];
QLog.d(TAG, "Received control command page: 0x" + String.format("%02X", pageNumber));
QLog.d(TAG, "Control Command HEX: " + bytesToHex(data));
// Handle control command pages
switch (pageNumber) {
case 0x30: // Basic Resistance
handleBasicResistanceCommand(data);
break;
case 0x31: // Target Power
handleTargetPowerCommand(data);
break;
case 0x33: // Track Resistance
handleTrackResistanceCommand(data);
break;
default:
QLog.d(TAG, "Unknown control page: 0x" + String.format("%02X", pageNumber));
break;
}
}
private void handleBasicResistanceCommand(byte[] data) {
int resistance = data[7] & 0xFF; // Resistance in 0.5% increments
double resistancePercent = resistance * 0.5;
QLog.d(TAG, String.format(Locale.US,
"Basic Resistance Command (0x30): Resistance=0x%02X(%.1f%%)",
resistance, resistancePercent));
if (resistancePercent != requestedResistance && controlListener != null) {
requestedResistance = (int) resistancePercent;
controlListener.onResistanceChangeRequested(requestedResistance);
}
}
private void handleTargetPowerCommand(byte[] data) {
int targetPower = ((data[7] & 0xFF) << 8) | (data[6] & 0xFF);
targetPower = targetPower / 4;
QLog.d(TAG, String.format(Locale.US,
"Target Power Command (0x31): Power=0x%02X%02X(%dW)",
data[7] & 0xFF, data[6] & 0xFF, targetPower));
if (targetPower != requestedPower && controlListener != null) {
requestedPower = targetPower;
controlListener.onPowerChangeRequested(targetPower);
}
}
private void handleTrackResistanceCommand(byte[] data) {
// Grade is in 0.01% increments, signed 16-bit
int gradeRaw = ((data[6] & 0xFF) << 8) | (data[5] & 0xFF);
if (gradeRaw > 32767) gradeRaw -= 65536; // Convert to signed
double grade = (gradeRaw - 0x4E20) * 0.01;
QLog.d(TAG, String.format(Locale.US,
"Track Resistance Command (0x33): Grade=0x%02X%02X(%.2f%%)",
data[6] & 0xFF, data[5] & 0xFF, grade));
if (Math.abs(grade - requestedInclination) > 0.1 && controlListener != null) {
requestedInclination = grade;
controlListener.onInclinationChangeRequested(grade);
}
}
}
}

View File

@@ -17,7 +17,7 @@ import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.content.BroadcastReceiver;
import android.content.ContextWrapper;
import android.content.IntentFilter;
@@ -89,12 +89,12 @@ public class BleAdvertiser {
private static AdvertiseCallback advertiseCallback = new AdvertiseCallback() {
@Override
public void onStartSuccess(AdvertiseSettings settingsInEffect) {
QLog.d("BleAdvertiser", "Advertising started successfully");
Log.d("BleAdvertiser", "Advertising started successfully");
}
@Override
public void onStartFailure(int errorCode) {
QLog.e("BleAdvertiser", "Advertising failed with error code: " + errorCode);
Log.e("BleAdvertiser", "Advertising failed with error code: " + errorCode);
}
};
}

View File

@@ -25,7 +25,7 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
public class CSafeRowerUSBHID {
@@ -34,21 +34,21 @@ public class CSafeRowerUSBHID {
static int lastReadLen = 0;
public static void open(Context context) {
QLog.d("QZ","CSafeRowerUSBHID open");
Log.d("QZ","CSafeRowerUSBHID open");
hidBridge = new HidBridge(context, 0x0002, 0x17A4);
boolean ret = hidBridge.OpenDevice();
QLog.d("QZ","hidBridge.OpenDevice " + ret);
Log.d("QZ","hidBridge.OpenDevice " + ret);
if(ret == false) {
hidBridge = new HidBridge(context, 0x0001, 0x17A4);
ret = hidBridge.OpenDevice();
QLog.d("QZ","hidBridge.OpenDevice " + ret);
Log.d("QZ","hidBridge.OpenDevice " + ret);
}
hidBridge.StartReadingThread();
QLog.d("QZ","hidBridge.StartReadingThread");
Log.d("QZ","hidBridge.StartReadingThread");
}
public static void write (byte[] bytes) {
QLog.d("QZ","CSafeRowerUSBHID writing " + new String(bytes, StandardCharsets.ISO_8859_1));
Log.d("QZ","CSafeRowerUSBHID writing " + new String(bytes, StandardCharsets.ISO_8859_1));
hidBridge.WriteData(bytes);
}
@@ -60,10 +60,10 @@ public class CSafeRowerUSBHID {
if(hidBridge.IsThereAnyReceivedData()) {
receiveData = hidBridge.GetReceivedDataFromQueue();
lastReadLen = receiveData.length;
QLog.d("QZ","CSafeRowerUSBHID reading " + lastReadLen + new String(receiveData, StandardCharsets.ISO_8859_1));
Log.d("QZ","CSafeRowerUSBHID reading " + lastReadLen + new String(receiveData, StandardCharsets.ISO_8859_1));
return receiveData;
} else {
QLog.d("QZ","CSafeRowerUSBHID empty data");
Log.d("QZ","CSafeRowerUSBHID empty data");
lastReadLen = 0;
return null;
}

View File

@@ -34,7 +34,7 @@ import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.util.SparseArray;
import android.os.Build;
import androidx.core.content.ContextCompat;
@@ -49,21 +49,15 @@ public class ChannelService extends Service {
private AntChannelProvider mAntChannelProvider = null;
private boolean mAllowAddChannel = false;
public static native void nativeSetResistance(int resistance);
public static native void nativeSetPower(int power);
public static native void nativeSetInclination(double inclination);
HeartChannelController heartChannelController = null;
PowerChannelController powerChannelController = null;
SpeedChannelController speedChannelController = null;
SDMChannelController sdmChannelController = null;
BikeChannelController bikeChannelController = null; // Added BikeChannelController reference
BikeTransmitterController bikeTransmitterController = null; // Added BikeTransmitterController reference
private ServiceConnection mAntRadioServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
QLog.v(TAG, "onServiceConnected");
Log.v(TAG, "onServiceConnected");
// Must pass in the received IBinder object to correctly construct an AntService object
mAntRadioService = new AntService(service);
@@ -78,7 +72,7 @@ public class ChannelService extends Service {
// radio by attempting to acquire a channel.
boolean legacyInterfaceInUse = mAntChannelProvider.isLegacyInterfaceInUse();
QLog.v(TAG, "onServiceConnected mChannelAvailable=" + mChannelAvailable + " legacyInterfaceInUse=" + legacyInterfaceInUse);
Log.v(TAG, "onServiceConnected mChannelAvailable=" + mChannelAvailable + " legacyInterfaceInUse=" + legacyInterfaceInUse);
// If there are channels OR legacy interface in use, allow adding channels
if (mChannelAvailable || legacyInterfaceInUse) {
@@ -91,7 +85,7 @@ public class ChannelService extends Service {
try {
openAllChannels();
} catch (ChannelNotAvailableException exception) {
QLog.e(TAG, "Channel not available!!");
Log.e(TAG, "Channel not available!!");
}
} catch (RemoteException e) {
// TODO Auto-generated catch block
@@ -123,20 +117,12 @@ public class ChannelService extends Service {
if (null != sdmChannelController) {
sdmChannelController.speed = speed;
}
// Update bike transmitter with speed data (only if not treadmill)
if (!Ant.treadmill && null != bikeTransmitterController) {
bikeTransmitterController.setSpeedKph(speed);
}
}
void setPower(int power) {
if (null != powerChannelController) {
powerChannelController.power = power;
}
// Update bike transmitter with power data (only if not treadmill)
if (!Ant.treadmill && null != bikeTransmitterController) {
bikeTransmitterController.setPower(power);
}
}
void setCadence(int cadence) {
@@ -149,164 +135,16 @@ public class ChannelService extends Service {
if (null != sdmChannelController) {
sdmChannelController.cadence = cadence;
}
// Update bike transmitter with cadence data (only if not treadmill)
if (!Ant.treadmill && null != bikeTransmitterController) {
bikeTransmitterController.setCadence(cadence);
}
}
int getHeart() {
if (null != heartChannelController) {
QLog.v(TAG, "getHeart");
Log.v(TAG, "getHeart");
return heartChannelController.heart;
}
if (null != bikeChannelController) {
return bikeChannelController.getHeartRate();
}
return 0;
}
// Added getters for bike channel data
int getBikeCadence() {
if (null != bikeChannelController) {
return bikeChannelController.getCadence();
}
return 0;
}
int getBikePower() {
if (null != bikeChannelController) {
return bikeChannelController.getPower();
}
return 0;
}
double getBikeSpeed() {
if (null != bikeChannelController) {
return bikeChannelController.getSpeedKph();
}
return 0.0;
}
long getBikeDistance() {
if (null != bikeChannelController) {
return bikeChannelController.getDistance();
}
return 0;
}
boolean isBikeConnected() {
return (bikeChannelController != null && bikeChannelController.isConnected());
}
// ========== BIKE TRANSMITTER METHODS ==========
/**
* Start the bike transmitter (only available if not treadmill)
*/
boolean startBikeTransmitter() {
QLog.v(TAG, "ChannelServiceComm.startBikeTransmitter");
if (Ant.treadmill) {
QLog.w(TAG, "Bike transmitter not available in treadmill mode");
return false;
}
if (bikeTransmitterController != null) {
return bikeTransmitterController.startTransmission();
}
QLog.w(TAG, "Bike transmitter controller is null");
return false;
}
/**
* Stop the bike transmitter
*/
void stopBikeTransmitter() {
QLog.v(TAG, "ChannelServiceComm.stopBikeTransmitter");
if (bikeTransmitterController != null) {
bikeTransmitterController.stopTransmission();
}
}
/**
* Check if bike transmitter is active (only if not treadmill)
*/
boolean isBikeTransmitterActive() {
if (Ant.treadmill) {
return false;
}
return (bikeTransmitterController != null && bikeTransmitterController.isTransmitting());
}
/**
* Update bike transmitter with extended metrics (only if not treadmill)
*/
void updateBikeTransmitterExtendedMetrics(long distanceMeters, int heartRate,
double elapsedTimeSeconds, int resistance,
double inclination) {
if (!Ant.treadmill && bikeTransmitterController != null) {
bikeTransmitterController.setDistance(distanceMeters);
bikeTransmitterController.setHeartRate(heartRate);
bikeTransmitterController.setElapsedTime(elapsedTimeSeconds);
bikeTransmitterController.setResistance(resistance);
bikeTransmitterController.setInclination(inclination);
}
}
/**
* Get the last requested resistance from ANT+ controller (only if not treadmill)
*/
int getRequestedResistanceFromAnt() {
if (!Ant.treadmill && bikeTransmitterController != null) {
return bikeTransmitterController.getRequestedResistance();
}
return -1;
}
/**
* Get the last requested power from ANT+ controller (only if not treadmill)
*/
int getRequestedPowerFromAnt() {
if (!Ant.treadmill && bikeTransmitterController != null) {
return bikeTransmitterController.getRequestedPower();
}
return -1;
}
/**
* Get the last requested inclination from ANT+ controller (only if not treadmill)
*/
double getRequestedInclinationFromAnt() {
if (!Ant.treadmill && bikeTransmitterController != null) {
return bikeTransmitterController.getRequestedInclination();
}
return -100.0;
}
/**
* Clear any pending control requests (only if not treadmill)
*/
void clearAntControlRequests() {
if (!Ant.treadmill && bikeTransmitterController != null) {
bikeTransmitterController.clearControlRequests();
}
}
/**
* Get transmission info for debugging (only if not treadmill)
*/
String getBikeTransmitterInfo() {
if (Ant.treadmill) {
return "Bike transmitter disabled in treadmill mode";
}
if (bikeTransmitterController != null) {
return bikeTransmitterController.getTransmissionInfo();
}
return "Bike transmitter not initialized";
}
/**
* Closes all channels currently added.
*/
@@ -327,72 +165,6 @@ public class ChannelService extends Service {
speedChannelController = new SpeedChannelController(acquireChannel());
}
}
// Add initialization for BikeChannelController (receiver)
if (Ant.bikeRequest && bikeChannelController == null) {
bikeChannelController = new BikeChannelController();
}
// Add initialization for BikeTransmitterController (transmitter) - only when NOT treadmill
if (!Ant.treadmill && bikeTransmitterController == null) {
QLog.v(TAG, "Initializing BikeTransmitterController (not treadmill mode)");
try {
// Acquire channel like other controllers
AntChannel transmitterChannel = acquireChannel();
if (transmitterChannel != null) {
bikeTransmitterController = new BikeTransmitterController(transmitterChannel);
// Set up control command listener to handle requests from ANT+ devices
bikeTransmitterController.setControlCommandListener(new BikeTransmitterController.ControlCommandListener() {
@Override
public void onResistanceChangeRequested(int resistance) {
QLog.d(TAG, "ChannelService: ANT+ Resistance change requested: " + resistance);
// Send broadcast intent to notify the main application
Intent intent = new Intent("org.cagnulen.qdomyoszwift.ANT_RESISTANCE_CHANGE");
intent.putExtra("resistance", resistance);
nativeSetResistance(resistance);
sendBroadcast(intent);
}
@Override
public void onPowerChangeRequested(int power) {
QLog.d(TAG, "ChannelService: ANT+ Power change requested: " + power + "W");
// Send broadcast intent to notify the main application
Intent intent = new Intent("org.cagnulen.qdomyoszwift.ANT_POWER_CHANGE");
intent.putExtra("power", power);
nativeSetPower(power);
sendBroadcast(intent);
}
@Override
public void onInclinationChangeRequested(double inclination) {
QLog.d(TAG, "ChannelService: ANT+ Inclination change requested: " + inclination + "%");
// Send broadcast intent to notify the main application
Intent intent = new Intent("org.cagnulen.qdomyoszwift.ANT_INCLINATION_CHANGE");
intent.putExtra("inclination", inclination);
nativeSetInclination(inclination);
sendBroadcast(intent);
}
});
QLog.i(TAG, "BikeTransmitterController initialized successfully (bike mode)");
// Start the bike transmitter immediately after initialization
boolean transmissionStarted = bikeTransmitterController.startTransmission();
if (transmissionStarted) {
QLog.i(TAG, "BikeTransmitterController transmission started automatically");
} else {
QLog.w(TAG, "Failed to start BikeTransmitterController transmission");
}
} else {
QLog.e(TAG, "Failed to acquire channel for BikeTransmitterController");
}
} catch (Exception e) {
QLog.e(TAG, "Failed to initialize BikeTransmitterController: " + e.getMessage());
bikeTransmitterController = null;
}
}
}
private void closeAllChannels() {
@@ -404,18 +176,10 @@ public class ChannelService extends Service {
speedChannelController.close();
if (sdmChannelController != null)
sdmChannelController.close();
if (bikeChannelController != null) // Added closing bikeChannelController
bikeChannelController.close();
if (bikeTransmitterController != null) { // Added closing bikeTransmitterController
bikeTransmitterController.close(); // Use close() method like other controllers
}
heartChannelController = null;
powerChannelController = null;
speedChannelController = null;
sdmChannelController = null;
bikeChannelController = null; // Added nullifying bikeChannelController
bikeTransmitterController = null; // Added nullifying bikeTransmitterController
}
AntChannel acquireChannel() throws ChannelNotAvailableException {
@@ -436,13 +200,13 @@ public class ChannelService extends Service {
else {
NetworkKey mNK = new NetworkKey(new byte[]{(byte) 0xb9, (byte) 0xa5, (byte) 0x21, (byte) 0xfb,
(byte) 0xbd, (byte) 0x72, (byte) 0xc3, (byte) 0x45});
QLog.v(TAG, mNK.toString());
Log.v(TAG, mNK.toString());
mAntChannel = mAntChannelProvider.acquireChannelOnPrivateNetwork(this, mNK);
}
} catch (RemoteException e) {
QLog.v(TAG, "ACP Remote Ex");
Log.v(TAG, "ACP Remote Ex");
} catch (UnsupportedFeatureException e) {
QLog.v(TAG, "ACP UnsupportedFeature Ex");
Log.v(TAG, "ACP UnsupportedFeature Ex");
}
}
return mAntChannel;
@@ -459,14 +223,14 @@ public class ChannelService extends Service {
private final BroadcastReceiver mChannelProviderStateChangedReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
QLog.d(TAG, "onReceive");
Log.d(TAG, "onReceive");
if (AntChannelProvider.ACTION_CHANNEL_PROVIDER_STATE_CHANGED.equals(intent.getAction())) {
boolean update = false;
// Retrieving the data contained in the intent
int numChannels = intent.getIntExtra(AntChannelProvider.NUM_CHANNELS_AVAILABLE, 0);
boolean legacyInterfaceInUse = intent.getBooleanExtra(AntChannelProvider.LEGACY_INTERFACE_IN_USE, false);
QLog.d(TAG, "onReceive" + mAllowAddChannel + " " + numChannels + " " + legacyInterfaceInUse);
Log.d(TAG, "onReceive" + mAllowAddChannel + " " + numChannels + " " + legacyInterfaceInUse);
if (mAllowAddChannel) {
// Was a acquire channel allowed
@@ -485,7 +249,7 @@ public class ChannelService extends Service {
try {
openAllChannels();
} catch (ChannelNotAvailableException exception) {
QLog.e(TAG, "Channel not available!!");
Log.e(TAG, "Channel not available!!");
}
}
}
@@ -494,7 +258,7 @@ public class ChannelService extends Service {
};
private void doBindAntRadioService() {
if (BuildConfig.DEBUG) QLog.v(TAG, "doBindAntRadioService");
if (BuildConfig.DEBUG) Log.v(TAG, "doBindAntRadioService");
ContextCompat.registerReceiver(
this,
@@ -509,14 +273,14 @@ public class ChannelService extends Service {
}
private void doUnbindAntRadioService() {
if (BuildConfig.DEBUG) QLog.v(TAG, "doUnbindAntRadioService");
if (BuildConfig.DEBUG) Log.v(TAG, "doUnbindAntRadioService");
// Stop listing for channel available intents
try {
unregisterReceiver(mChannelProviderStateChangedReceiver);
} catch (IllegalArgumentException exception) {
if (BuildConfig.DEBUG)
QLog.d(TAG, "Attempting to unregister a never registered Channel Provider State Changed receiver.");
Log.d(TAG, "Attempting to unregister a never registered Channel Provider State Changed receiver.");
}
if (mAntRadioServiceBound) {
@@ -551,7 +315,7 @@ public class ChannelService extends Service {
}
static void die(String error) {
QLog.e(TAG, "DIE: " + error);
Log.e(TAG, "DIE: " + error);
}
}
}

View File

@@ -4,20 +4,20 @@ import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.provider.OpenableColumns;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
public class ContentHelper {
public static String getFileName(Context context, Uri uri) {
String result = null;
if (uri.getScheme().equals("content")) {
QLog.d("ContentHelper", "content");
Log.d("ContentHelper", "content");
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
QLog.d("ContentHelper", "cursor " + cursor);
Log.d("ContentHelper", "cursor " + cursor);
try {
if (cursor != null && cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
QLog.d("ContentHelper", "result " + result);
Log.d("ContentHelper", "result " + result);
}
} finally {
cursor.close();

View File

@@ -1,34 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import org.qtproject.qt5.android.bindings.QtActivity;
public class CustomQtActivity extends QtActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Handle Android 16 API 36 WindowInsetsController for fullscreen support
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11 (API 30) and above - use WindowInsetsController
getWindow().setDecorFitsSystemWindows(false);
WindowInsetsController controller = getWindow().getDecorView().getWindowInsetsController();
if (controller != null) {
controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
} else {
// Fallback for older Android versions (API < 30)
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
}

View File

@@ -17,7 +17,7 @@ import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@@ -29,26 +29,32 @@ public class FloatingHandler {
static public int _width;
static public int _height;
static public int _alpha;
static public String _htmlPage = "floating.htm";
public static void show(Context context, int port, int width, int height, int transparency, String htmlPage) {
_context = context;
_port = port;
_width = width;
_height = height;
_alpha = transparency;
_htmlPage = htmlPage;
public static void show(Context context, int port, int width, int height, int transparency) {
_context = context;
_port = port;
_width = width;
_height = height;
_alpha = transparency;
if (checkOverlayDisplayPermission()) {
if (_intent == null)
_intent = new Intent(context, FloatingWindowGFG.class);
context.startService(_intent);
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + _context.getPackageName()));
Activity a = (Activity) _context;
a.startActivityForResult(intent, -1);
}
}
// First it confirms whether the
// 'Display over other apps' permission in given
if (checkOverlayDisplayPermission()) {
if(_intent == null)
_intent = new Intent(context, FloatingWindowGFG.class);
// FloatingWindowGFG service is started
context.startService(_intent);
// The MainActivity closes here
//finish();
} else {
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + _context.getPackageName()));
// This method will start the intent. It takes two parameter, one is the Intent and the other is
// an requestCode Integer. Here it is -1.
Activity a = (Activity)_context;
a.startActivityForResult(intent, -1);
}
}
public static void hide() {
if(_intent != null)

View File

@@ -24,7 +24,7 @@ import android.widget.Toast;
import android.webkit.WebView;
import android.webkit.WebSettings;
import android.webkit.WebViewClient;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.content.SharedPreferences;
public class FloatingWindowGFG extends Service {
@@ -82,14 +82,14 @@ public class FloatingWindowGFG extends Service {
});
WebSettings settings = wv.getSettings();
settings.setJavaScriptEnabled(true);
wv.loadUrl("http://localhost:" + FloatingHandler._port + "/floating/" + FloatingHandler._htmlPage);
wv.loadUrl("http://localhost:" + FloatingHandler._port + "/floating/floating.htm");
wv.clearView();
wv.measure(100, 100);
wv.setAlpha(Float.valueOf(FloatingHandler._alpha) / 100.0f);
settings.setBuiltInZoomControls(true);
settings.setUseWideViewPort(true);
settings.setDomStorageEnabled(true);
QLog.d("QZ","loadurl");
Log.d("QZ","loadurl");
// WindowManager.LayoutParams takes a lot of parameters to set the
@@ -153,7 +153,7 @@ public class FloatingWindowGFG extends Service {
@Override
public boolean onTouch(View v, MotionEvent event) {
QLog.d("QZ","onTouch");
Log.d("QZ","onTouch");
switch (event.getAction()) {
// When the window will be touched,

View File

@@ -10,7 +10,7 @@ import android.os.Build;
import android.os.IBinder;
import androidx.core.app.NotificationCompat;
import android.content.pm.ServiceInfo;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
public class ForegroundService extends Service {
public static final String CHANNEL_ID = "ForegroundServiceChannel";
@@ -43,7 +43,7 @@ public class ForegroundService extends Service {
startForeground(1, notification);
}
} catch (Exception e) {
QLog.e("ForegroundService", "Failed to start foreground service", e);
Log.e("ForegroundService", "Failed to start foreground service", e);
return START_NOT_STICKY;
}
//do heavy work on a background thread

View File

@@ -17,7 +17,7 @@ import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import com.garmin.android.connectiq.ConnectIQ;
import com.garmin.android.connectiq.ConnectIQAdbStrategy;
import com.garmin.android.connectiq.IQApp;
@@ -53,22 +53,22 @@ public class Garmin {
private static Integer Power = 0;
public static int getHR() {
QLog.d(TAG, "getHR " + HR);
Log.d(TAG, "getHR " + HR);
return HR;
}
public static int getPower() {
QLog.d(TAG, "getPower " + Power);
Log.d(TAG, "getPower " + Power);
return Power;
}
public static double getSpeed() {
QLog.d(TAG, "getSpeed " + Speed);
Log.d(TAG, "getSpeed " + Speed);
return Speed;
}
public static int getFootCad() {
QLog.d(TAG, "getFootCad " + FootCad);
Log.d(TAG, "getFootCad " + FootCad);
return FootCad;
}
@@ -83,7 +83,7 @@ public class Garmin {
@Override
public void onInitializeError(ConnectIQ.IQSdkErrorStatus errStatus) {
QLog.e(TAG, errStatus.toString());
Log.e(TAG, errStatus.toString());
connectIqReady = false;
}
@@ -91,7 +91,7 @@ public class Garmin {
public void onSdkReady() {
connectIqInitializing = false;
connectIqReady = true;
QLog.i(TAG, " onSdkReady");
Log.i(TAG, " onSdkReady");
registerWatchMessagesReceiver();
registerDeviceStatusReceiver();
@@ -118,16 +118,16 @@ public class Garmin {
try {
List<IQDevice> devices = connectIQ.getConnectedDevices();
if (devices != null && devices.size() > 0) {
QLog.v(TAG, "getDevice connected: " + devices.get(0).toString() );
Log.v(TAG, "getDevice connected: " + devices.get(0).toString() );
deviceCache = devices.get(0);
return deviceCache;
} else {
return deviceCache;
}
} catch (InvalidStateException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
} catch (ServiceUnavailableException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
}
return null;
}
@@ -193,33 +193,33 @@ public class Garmin {
@Override
public void onApplicationInfoReceived(IQApp app) {
QLog.d(TAG, "App installed.");
Log.d(TAG, "App installed.");
}
@Override
public void onApplicationNotInstalled(String applicationId) {
if (getDevice() != null) {
Toast.makeText(context, "App not installed on your Garmin watch", Toast.LENGTH_LONG).show();
QLog.d(TAG, "watch app not installed.");
Log.d(TAG, "watch app not installed.");
}
}
});
} catch (InvalidStateException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
} catch (ServiceUnavailableException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
}
}
private static void registerDeviceStatusReceiver() {
QLog.d(TAG, "registerDeviceStatusReceiver");
Log.d(TAG, "registerDeviceStatusReceiver");
IQDevice device = getDevice();
try {
if (device != null) {
connectIQ.registerForDeviceEvents(device, new ConnectIQ.IQDeviceEventListener() {
@Override
public void onDeviceStatusChanged(IQDevice device, IQDevice.IQDeviceStatus newStatus) {
QLog.d(TAG, "Device status changed, now " + newStatus);
Log.d(TAG, "Device status changed, now " + newStatus);
}
});
}
@@ -229,7 +229,7 @@ public class Garmin {
}
private static void registerWatchMessagesReceiver(){
QLog.d(TAG, "registerWatchMessageReceiver");
Log.d(TAG, "registerWatchMessageReceiver");
IQDevice device = getDevice();
try {
if (device != null) {
@@ -238,7 +238,7 @@ public class Garmin {
public void onMessageReceived(IQDevice device, IQApp app, List<Object> message, ConnectIQ.IQMessageStatus status) {
if (status == ConnectIQ.IQMessageStatus.SUCCESS) {
//MessageHandler.getInstance().handleMessageFromWatchUsingCIQ(message, status, context);
QLog.d(TAG, "onMessageReceived, status: " + status.toString() + message.get(0));
Log.d(TAG, "onMessageReceived, status: " + status.toString() + message.get(0));
try {
String var[] = message.toArray()[0].toString().split(",");
HR = Integer.parseInt(var[0].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
@@ -249,21 +249,21 @@ public class Garmin {
Speed = Double.parseDouble(var[1].replaceAll("\\[", "").replaceAll("\\]", "").replaceAll("\\{", "").replaceAll("\\}", "").replaceAll(" ", "").split("=")[1]);
}
}
QLog.d(TAG, "HR " + HR);
QLog.d(TAG, "FootCad " + FootCad);
Log.d(TAG, "HR " + HR);
Log.d(TAG, "FootCad " + FootCad);
} catch (Exception e) {
QLog.e(TAG, "Processing error", e);
Log.e(TAG, "Processing error", e);
}
} else {
QLog.d(TAG, "onMessageReceived error, status: " + status.toString());
Log.d(TAG, "onMessageReceived error, status: " + status.toString());
}
}
});
} else {
QLog.d(TAG, "registerWatchMessagesReceiver: No device found.");
Log.d(TAG, "registerWatchMessagesReceiver: No device found.");
}
} catch (InvalidStateException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
}
}
@@ -273,19 +273,19 @@ public class Garmin {
try {
if (context != null) {
QLog.d(TAG, "Shutting down with wrapped context");
Log.d(TAG, "Shutting down with wrapped context");
connectIQ.shutdown(context);
} else {
QLog.d(TAG, "Shutting down without wrapped context");
Log.d(TAG, "Shutting down without wrapped context");
connectIQ.shutdown(applicationContext);
}
} catch (InvalidStateException e) {
// This is usually because the SDK was already shut down so no worries.
QLog.e(TAG, "This is usually because the SDK was already shut down so no worries.", e);
Log.e(TAG, "This is usually because the SDK was already shut down so no worries.", e);
} catch (IllegalArgumentException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
} catch (RuntimeException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
}
}
@@ -299,11 +299,11 @@ public class Garmin {
}
}
} catch (InvalidStateException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
} catch (IllegalArgumentException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
} catch (RuntimeException e) {
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
}
}
}

View File

@@ -1,7 +1,7 @@
package org.cagnulen.qdomyoszwift;
import android.content.Context;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.app.Activity;
// ANT+ Plugin imports
@@ -57,26 +57,26 @@ public class HeartChannelController {
case SUCCESS:
hrPcc = result;
isConnected = true;
QLog.d(TAG, "Connected to heart rate monitor: " + result.getDeviceName());
Log.d(TAG, "Connected to heart rate monitor: " + result.getDeviceName());
subscribeToHrEvents();
break;
case CHANNEL_NOT_AVAILABLE:
QLog.e(TAG, "Channel Not Available");
Log.e(TAG, "Channel Not Available");
break;
case ADAPTER_NOT_DETECTED:
QLog.e(TAG, "ANT Adapter Not Available");
Log.e(TAG, "ANT Adapter Not Available");
break;
case BAD_PARAMS:
QLog.e(TAG, "Bad request parameters");
Log.e(TAG, "Bad request parameters");
break;
case OTHER_FAILURE:
QLog.e(TAG, "RequestAccess failed");
Log.e(TAG, "RequestAccess failed");
break;
case DEPENDENCY_NOT_INSTALLED:
QLog.e(TAG, "Dependency not installed");
Log.e(TAG, "Dependency not installed");
break;
default:
QLog.e(TAG, "Unrecognized result: " + resultCode);
Log.e(TAG, "Unrecognized result: " + resultCode);
break;
}
}
@@ -84,7 +84,7 @@ public class HeartChannelController {
new IDeviceStateChangeReceiver() {
@Override
public void onDeviceStateChange(DeviceState newDeviceState) {
QLog.d(TAG, "Device State Changed to: " + newDeviceState);
Log.d(TAG, "Device State Changed to: " + newDeviceState);
if (newDeviceState == DeviceState.DEAD) {
isConnected = false;
}
@@ -104,7 +104,7 @@ public class HeartChannelController {
BigDecimal heartBeatEventTime, DataState dataState) {
heart = computedHeartRate;
QLog.d(TAG, "Heart Rate: " + heart);
Log.d(TAG, "Heart Rate: " + heart);
}
});
}
@@ -117,7 +117,7 @@ public class HeartChannelController {
}
hrPcc = null;
isConnected = false;
QLog.d(TAG, "Channel Closed");
Log.d(TAG, "Channel Closed");
}
public int getHeartRate() {

View File

@@ -16,7 +16,7 @@ import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.os.Build;
import androidx.core.content.ContextCompat;
@@ -169,7 +169,7 @@ public class HidBridge {
} catch(NullPointerException e)
{
Log("Error happened while writing. Could not connect to the device or interface is busy?");
QLog.e("HidBridge", QLog.getStackTraceString(e));
Log.e("HidBridge", Log.getStackTraceString(e));
return false;
}
return true;
@@ -289,7 +289,7 @@ public class HidBridge {
catch (NullPointerException e) {
Log("Error happened while reading. No device or the connection is busy");
QLog.e("HidBridge", QLog.getStackTraceString(e));
Log.e("HidBridge", Log.getStackTraceString(e));
}
catch (ThreadDeath e) {
if (readConnection != null) {
@@ -332,7 +332,7 @@ public class HidBridge {
}
}
else {
QLog.d("TAG", "permission denied for the device " + device);
Log.d("TAG", "permission denied for the device " + device);
}
}
}
@@ -344,7 +344,7 @@ public class HidBridge {
* @param message to log.
*/
private void Log(String message) {
QLog.d("HidBridge", message);
Log.d("HidBridge", message);
}
/**

View File

@@ -8,7 +8,7 @@ import com.garmin.android.connectiq.IQDevice;
import java.nio.BufferUnderflowException;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
public class IQMessageReceiverWrapper extends BroadcastReceiver {
private final BroadcastReceiver receiver;
@@ -20,7 +20,7 @@ public class IQMessageReceiverWrapper extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
QLog.d(TAG, "onReceive intent " + intent.getAction());
Log.d(TAG, "onReceive intent " + intent.getAction());
if ("com.garmin.android.connectiq.SEND_MESSAGE_STATUS".equals(intent.getAction())) {
replaceIQDeviceById(intent, "com.garmin.android.connectiq.EXTRA_REMOTE_DEVICE");
} else if ("com.garmin.android.connectiq.OPEN_APPLICATION".equals(intent.getAction())) {
@@ -32,7 +32,7 @@ public class IQMessageReceiverWrapper extends BroadcastReceiver {
try {
receiver.onReceive(context, intent);
} catch (IllegalArgumentException | BufferUnderflowException e) {
QLog.d(TAG, e.toString());
Log.d(TAG, e.toString());
}
}
@@ -44,7 +44,7 @@ public class IQMessageReceiverWrapper extends BroadcastReceiver {
intent.putExtra(extraName, device.getDeviceIdentifier());
}
} catch (ClassCastException e) {
QLog.d(TAG, e.toString());
Log.d(TAG, e.toString());
// It's already a long, i.e. on the simulator.
}
}

View File

@@ -5,7 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.location.LocationManager;
import android.provider.Settings;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
public class LocationHelper {
private static final String TAG = "LocationHelper";
@@ -13,7 +13,7 @@ public class LocationHelper {
private static boolean isBluetoothEnabled = false;
public static boolean start(Context context) {
QLog.d(TAG, "Starting LocationHelper check...");
Log.d(TAG, "Starting LocationHelper check...");
isLocationEnabled = isLocationEnabled(context);
isBluetoothEnabled = isBluetoothEnabled();
@@ -23,7 +23,7 @@ public class LocationHelper {
public static void requestPermissions(Context context) {
if (!isLocationEnabled || !isBluetoothEnabled) {
QLog.d(TAG, "Some services are disabled. Prompting user...");
Log.d(TAG, "Some services are disabled. Prompting user...");
if (!isLocationEnabled) {
promptEnableLocation(context);
}
@@ -31,7 +31,7 @@ public class LocationHelper {
promptEnableBluetooth(context);
}
} else {
QLog.d(TAG, "All services are already enabled.");
Log.d(TAG, "All services are already enabled.");
}
}
@@ -50,14 +50,14 @@ public class LocationHelper {
}
private static void promptEnableLocation(Context context) {
QLog.d(TAG, "Prompting to enable Location...");
Log.d(TAG, "Prompting to enable Location...");
Intent intent = new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
private static void promptEnableBluetooth(Context context) {
QLog.d(TAG, "Prompting to enable Bluetooth...");
Log.d(TAG, "Prompting to enable Bluetooth...");
Intent intent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);

View File

@@ -5,7 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.os.Build;
public class MediaButtonReceiver extends BroadcastReceiver {
@@ -13,7 +13,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
QLog.d("MediaButtonReceiver", "Received intent: " + intent.toString());
Log.d("MediaButtonReceiver", "Received intent: " + intent.toString());
String intentAction = intent.getAction();
if ("android.media.VOLUME_CHANGED_ACTION".equals(intentAction)) {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
@@ -21,7 +21,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
int currentVolume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE", -1);
int previousVolume = intent.getIntExtra("android.media.EXTRA_PREV_VOLUME_STREAM_VALUE", -1);
QLog.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Max: " + maxVolume);
Log.d("MediaButtonReceiver", "Volume changed. Current: " + currentVolume + ", Max: " + maxVolume);
nativeOnMediaButtonEvent(previousVolume, currentVolume, maxVolume);
}
}
@@ -36,7 +36,7 @@ public class MediaButtonReceiver extends BroadcastReceiver {
IntentFilter filter = new IntentFilter("android.media.VOLUME_CHANGED_ACTION");
if (context == null) {
QLog.e("MediaButtonReceiver", "Context is null, cannot register receiver");
Log.e("MediaButtonReceiver", "Context is null, cannot register receiver");
return;
}
@@ -44,21 +44,21 @@ public class MediaButtonReceiver extends BroadcastReceiver {
try {
context.registerReceiver(instance, filter, Context.RECEIVER_EXPORTED);
} catch (SecurityException se) {
QLog.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
Log.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
}
} else {
try {
context.registerReceiver(instance, filter);
} catch (SecurityException se) {
QLog.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
Log.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
}
}
QLog.d("MediaButtonReceiver", "Receiver registered successfully");
Log.d("MediaButtonReceiver", "Receiver registered successfully");
} catch (IllegalArgumentException e) {
QLog.e("MediaButtonReceiver", "Invalid arguments for receiver registration: " + e.getMessage());
Log.e("MediaButtonReceiver", "Invalid arguments for receiver registration: " + e.getMessage());
} catch (Exception e) {
QLog.e("MediaButtonReceiver", "Unexpected error while registering receiver: " + e.getMessage());
Log.e("MediaButtonReceiver", "Unexpected error while registering receiver: " + e.getMessage());
}
}

View File

@@ -12,7 +12,7 @@ import android.util.DisplayMetrics;
import android.os.Build;
import android.provider.Settings;
import android.app.AppOpsManager;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.annotation.TargetApi;
import com.rvalerio.fgchecker.AppChecker;
@@ -83,11 +83,11 @@ public class MediaProjection {
@Override
public void onForeground(String packageName) {
_packageName = packageName;
/*QLog.e("MediaProjection", packageName);
/*Log.e("MediaProjection", packageName);
if(isLandscape())
QLog.e("MediaProjection", "Landscape");
Log.e("MediaProjection", "Landscape");
else
QLog.e("MediaProjection", "Portrait");*/
Log.e("MediaProjection", "Portrait");*/
}
})
.timeout(1000)

View File

@@ -1,5 +1,5 @@
package org.cagnulen.qdomyoszwift;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
public class MyActivity extends org.qtproject.qt5.android.bindings.QtActivity {
@@ -12,6 +12,6 @@ public class MyActivity extends org.qtproject.qt5.android.bindings.QtActivity {
super.onCreate(savedInstanceState);
this.getWindow().addFlags(android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
activity_ = this;
QLog.v(TAG, "onCreate");
Log.v(TAG, "onCreate");
}
}

View File

@@ -2,7 +2,7 @@ package org.cagnulen.qdomyoszwift;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanResult;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import java.util.List;
@@ -12,7 +12,7 @@ public class NativeScanCallback extends ScanCallback {
public native void scanError(int code);
@Override
public void onScanResult(int callbackType, ScanResult result) {
QLog.i(TAG, "Res " + result);
Log.i(TAG, "Res " + result);
newScanResult(new ScanRecordResult(result));
}
@@ -24,7 +24,7 @@ public class NativeScanCallback extends ScanCallback {
@Override
public void onScanFailed(int errorCode) {
QLog.i(TAG, "onScanFailed "+errorCode);
Log.i(TAG, "onScanFailed "+errorCode);
scanError(errorCode);
}
}

View File

@@ -17,7 +17,7 @@
package org.cagnulen.qdomyoszwift;
import android.os.RemoteException;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntCommandFailedException;
@@ -61,7 +61,7 @@ public class PowerChannelController {
boolean openChannel() {
if (null != mAntChannel) {
if (mIsOpen) {
QLog.w(TAG, "Channel was already open");
Log.w(TAG, "Channel was already open");
} else {
// Channel ID message contains device number, type and transmission type. In
// order for master (TX) channels and slave (RX) channels to connect, they
@@ -92,7 +92,7 @@ public class PowerChannelController {
mAntChannel.open();
mIsOpen = true;
QLog.d(TAG, "Opened channel with device number: " + POWER_SENSOR_ID);
Log.d(TAG, "Opened channel with device number: " + POWER_SENSOR_ID);
} catch (RemoteException e) {
channelError(e);
@@ -102,7 +102,7 @@ public class PowerChannelController {
}
}
} else {
QLog.w(TAG, "No channel available");
Log.w(TAG, "No channel available");
}
return mIsOpen;
@@ -112,7 +112,7 @@ public class PowerChannelController {
void channelError(RemoteException e) {
String logString = "Remote service communication failed.";
QLog.e(TAG, logString);
Log.e(TAG, logString);
}
@@ -142,7 +142,7 @@ public class PowerChannelController {
.append(failureReason);
}
QLog.e(TAG, logString.toString());
Log.e(TAG, logString.toString());
mAntChannel.release();
}
@@ -158,7 +158,7 @@ public class PowerChannelController {
mAntChannel = null;
}
QLog.e(TAG, "Channel Closed");
Log.e(TAG, "Channel Closed");
}
/**
@@ -175,13 +175,13 @@ public class PowerChannelController {
@Override
public void onChannelDeath() {
// Display channel death message when channel dies
QLog.e(TAG, "Channel Death");
Log.e(TAG, "Channel Death");
}
@Override
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
QLog.d(TAG, "Rx: " + antParcel);
QLog.d(TAG, "Message Type: " + messageType);
Log.d(TAG, "Rx: " + antParcel);
Log.d(TAG, "Message Type: " + messageType);
byte[] payload = new byte[8];
if(carousalTimer == null) {
@@ -189,7 +189,7 @@ public class PowerChannelController {
carousalTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
QLog.d(TAG, "Tx Unsollicited");
Log.d(TAG, "Tx Unsollicited");
byte[] payload = new byte[8];
eventCount = (eventCount + 1) & 0xFF;
cumulativePower = (cumulativePower + power) & 0xFFFF;
@@ -225,7 +225,7 @@ public class PowerChannelController {
// Rx Data
//updateData(new AcknowledgedDataMessage(antParcel).getPayload());
payload = new AcknowledgedDataMessage(antParcel).getPayload();
QLog.d(TAG, "AcknowledgedDataMessage: " + payload);
Log.d(TAG, "AcknowledgedDataMessage: " + payload);
if ((payload[0] == 0) && (payload[1] == 1) && (payload[2] == (byte)0xAA)) {
payload[0] = (byte) 0x01;
@@ -268,7 +268,7 @@ public class PowerChannelController {
// Constructing channel event message from parcel
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
EventCode code = eventMessage.getEventCode();
QLog.d(TAG, "Event Code: " + code);
Log.d(TAG, "Event Code: " + code);
// Switching on event code to handle the different types of channel events
switch (code) {
@@ -320,7 +320,7 @@ public class PowerChannelController {
break;
case RX_SEARCH_TIMEOUT:
// TODO May want to keep searching
QLog.e(TAG, "No Device Found");
Log.e(TAG, "No Device Found");
break;
case CHANNEL_CLOSED:
case RX_FAIL:

View File

@@ -1,121 +0,0 @@
package org.cagnulen.qdomyoszwift;
import android.util.Log;
/**
* QLog - Wrapper for Android's Log class that redirects logs to Qt's logging system
* Usage: import org.cagnulen.qdomyoszwift.Log;
*/
public class QLog {
public static native void sendToQt(int level, String tag, String message);
static {
try {
// Try to load the native library if needed
System.loadLibrary("qtlogging_native");
} catch (UnsatisfiedLinkError e) {
// Library might be loaded elsewhere, or will be loaded later
Log.w("QLog", "Native library not loaded yet: " + e.getMessage());
}
}
// Debug level methods
public static int d(String tag, String msg) {
sendToQt(3, tag, msg);
return Log.d(tag, msg);
}
public static int d(String tag, String msg, Throwable tr) {
sendToQt(3, tag, msg + '\n' + Log.getStackTraceString(tr));
return Log.d(tag, msg, tr);
}
// Error level methods
public static int e(String tag, String msg) {
sendToQt(6, tag, msg);
return Log.e(tag, msg);
}
public static int e(String tag, String msg, Throwable tr) {
sendToQt(6, tag, msg + '\n' + Log.getStackTraceString(tr));
return Log.e(tag, msg, tr);
}
// Info level methods
public static int i(String tag, String msg) {
sendToQt(4, tag, msg);
return Log.i(tag, msg);
}
public static int i(String tag, String msg, Throwable tr) {
sendToQt(4, tag, msg + '\n' + Log.getStackTraceString(tr));
return Log.i(tag, msg, tr);
}
// Verbose level methods
public static int v(String tag, String msg) {
sendToQt(2, tag, msg);
return Log.v(tag, msg);
}
public static int v(String tag, String msg, Throwable tr) {
sendToQt(2, tag, msg + '\n' + Log.getStackTraceString(tr));
return Log.v(tag, msg, tr);
}
// Warning level methods
public static int w(String tag, String msg) {
sendToQt(5, tag, msg);
return Log.w(tag, msg);
}
public static int w(String tag, String msg, Throwable tr) {
sendToQt(5, tag, msg + '\n' + Log.getStackTraceString(tr));
return Log.w(tag, msg, tr);
}
public static int w(String tag, Throwable tr) {
sendToQt(5, tag, Log.getStackTraceString(tr));
return Log.w(tag, tr);
}
// What a Terrible Failure: Report an exception that should never happen
public static int wtf(String tag, String msg) {
sendToQt(7, tag, "WTF: " + msg);
return Log.wtf(tag, msg);
}
public static int wtf(String tag, Throwable tr) {
sendToQt(7, tag, "WTF: " + Log.getStackTraceString(tr));
return Log.wtf(tag, tr);
}
public static int wtf(String tag, String msg, Throwable tr) {
sendToQt(7, tag, "WTF: " + msg + '\n' + Log.getStackTraceString(tr));
return Log.wtf(tag, msg, tr);
}
// Utility methods
public static String getStackTraceString(Throwable tr) {
return Log.getStackTraceString(tr);
}
public static boolean isLoggable(String tag, int level) {
return Log.isLoggable(tag, level);
}
// Additional utility methods
public static int println(int priority, String tag, String msg) {
sendToQt(priority, tag, msg);
return Log.println(priority, tag, msg);
}
// API Level 28+ (Android 9+) methods
public static RuntimeException getStackTraceElement() {
try {
return (RuntimeException) Log.class.getMethod("getStackTraceElement").invoke(null);
} catch (Exception e) {
return new RuntimeException("QLog: Failed to get stack trace element");
}
}
}

View File

@@ -17,7 +17,7 @@ import android.os.Environment;
import android.os.IBinder;
import android.os.PowerManager;
import android.provider.Settings;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.RadioButton;
@@ -43,8 +43,6 @@ public class QZAdbRemote implements DeviceConnectionListener {
private static final String LOG_TAG = "QZ:AdbRemote";
private static String lastCommand = "";
private static boolean ADBConnected = false;
private static boolean cryptoReady = false;
private static final Object cryptoLock = new Object();
private static String _address = "127.0.0.1";
private static Context _context;
@@ -64,46 +62,31 @@ public class QZAdbRemote implements DeviceConnectionListener {
@Override
public void notifyConnectionEstablished(DeviceConnection devConn) {
QLog.d(LOG_TAG, "notifyConnectionEstablished - START: devConn=" + devConn + ", host=" + (devConn != null ? devConn.getHost() : "null") + ", port=" + (devConn != null ? devConn.getPort() : "null"));
ADBConnected = true;
QLog.i(LOG_TAG, "notifyConnectionEstablished - CONNECTED=true, lastCommand=" + lastCommand);
QLog.d(LOG_TAG, "notifyConnectionEstablished - END: ADBConnected=" + ADBConnected);
Log.i(LOG_TAG, "notifyConnectionEstablished" + lastCommand);
}
@Override
public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
QLog.d(LOG_TAG, "notifyConnectionFailed - START: devConn=" + devConn + ", host=" + (devConn != null ? devConn.getHost() : "null") + ", port=" + (devConn != null ? devConn.getPort() : "null"));
ADBConnected = false;
QLog.e(LOG_TAG, "notifyConnectionFailed - ERROR: " + (e != null ? e.getMessage() : "null exception") + ", ADBConnected=" + ADBConnected);
if (e != null) {
QLog.e(LOG_TAG, "notifyConnectionFailed - STACK_TRACE: ", e);
}
Log.e(LOG_TAG, e.getMessage());
}
@Override
public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
QLog.d(LOG_TAG, "notifyStreamFailed - START: devConn=" + devConn + ", host=" + (devConn != null ? devConn.getHost() : "null") + ", port=" + (devConn != null ? devConn.getPort() : "null"));
ADBConnected = false;
QLog.e(LOG_TAG, "notifyStreamFailed - ERROR: " + (e != null ? e.getMessage() : "null exception") + ", ADBConnected=" + ADBConnected);
if (e != null) {
QLog.e(LOG_TAG, "notifyStreamFailed - STACK_TRACE: ", e);
}
Log.e(LOG_TAG, e.getMessage());
}
@Override
public void notifyStreamClosed(DeviceConnection devConn) {
QLog.d(LOG_TAG, "notifyStreamClosed - START: devConn=" + devConn + ", host=" + (devConn != null ? devConn.getHost() : "null") + ", port=" + (devConn != null ? devConn.getPort() : "null"));
ADBConnected = false;
QLog.e(LOG_TAG, "notifyStreamClosed - ADBConnected=" + ADBConnected);
Log.e(LOG_TAG, "notifyStreamClosed");
}
@Override
public AdbCrypto loadAdbCrypto(DeviceConnection devConn) {
QLog.d(LOG_TAG, "loadAdbCrypto - START: devConn=" + devConn + ", context=" + _context);
AdbCrypto crypto = AdbUtils.readCryptoConfig(_context.getFilesDir());
QLog.d(LOG_TAG, "loadAdbCrypto - RESULT: crypto=" + (crypto != null ? "valid" : "null"));
return crypto;
return AdbUtils.readCryptoConfig(_context.getFilesDir());
}
@Override
@@ -113,7 +96,7 @@ public class QZAdbRemote implements DeviceConnectionListener {
@Override
public void receivedData(DeviceConnection devConn, byte[] data, int offset, int length) {
QLog.i(LOG_TAG, data.toString());
Log.i(LOG_TAG, data.toString());
}
@Override
@@ -128,132 +111,96 @@ public class QZAdbRemote implements DeviceConnectionListener {
private DeviceConnection startConnection(String host, int port) {
QLog.d(LOG_TAG, "startConnection - START: host=" + host + ", port=" + port + ", binder=" + binder);
/* Create the connection object */
DeviceConnection conn = binder.createConnection(host, port);
QLog.d(LOG_TAG, "startConnection - CONNECTION_CREATED: conn=" + conn);
/* Add this activity as a connection listener */
binder.addListener(conn, this);
QLog.d(LOG_TAG, "startConnection - LISTENER_ADDED: this=" + this);
/* Begin the async connection process */
QLog.d(LOG_TAG, "startConnection - STARTING_CONNECT: about to call conn.startConnect()");
conn.startConnect();
QLog.d(LOG_TAG, "startConnection - END: startConnect() called, returning conn=" + conn);
return conn;
}
private DeviceConnection connectOrLookupConnection(String host, int port) {
QLog.d(LOG_TAG, "connectOrLookupConnection - START: host=" + host + ", port=" + port + ", binder=" + binder);
DeviceConnection conn = binder.findConnection(host, port);
QLog.d(LOG_TAG, "connectOrLookupConnection - EXISTING_CONN: conn=" + (conn != null ? "found" : "null"));
if (conn == null) {
/* No existing connection, so start the connection process */
QLog.d(LOG_TAG, "connectOrLookupConnection - NEW_CONNECTION: starting new connection");
conn = startConnection(host, port);
}
else {
/* Add ourselves as a new listener of this connection */
QLog.d(LOG_TAG, "connectOrLookupConnection - REUSE_CONNECTION: adding listener to existing connection");
binder.addListener(conn, this);
}
QLog.d(LOG_TAG, "connectOrLookupConnection - END: returning conn=" + conn);
return conn;
}
public ServiceConnection serviceConn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName arg0, IBinder arg1) {
QLog.d(LOG_TAG, "onServiceConnected - START: componentName=" + arg0 + ", binder=" + arg1 + ", _address=" + _address);
binder = (ShellService.ShellServiceBinder)arg1;
QLog.d(LOG_TAG, "onServiceConnected - BINDER_SET: binder=" + binder + ", existing_connection=" + connection);
if (connection != null) {
QLog.d(LOG_TAG, "onServiceConnected - REMOVING_OLD_LISTENER: connection=" + connection);
binder.removeListener(connection, QZAdbRemote.getInstance());
}
QLog.d(LOG_TAG, "onServiceConnected - CONNECTING: about to call connectOrLookupConnection");
connection = connectOrLookupConnection(_address, 5555);
QLog.d(LOG_TAG, "onServiceConnected - END: connection=" + connection);
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
QLog.d(LOG_TAG, "onServiceDisconnected - START: componentName=" + arg0 + ", old_binder=" + binder);
binder = null;
QLog.d(LOG_TAG, "onServiceDisconnected - END: binder set to null");
}
};
static public void createConnection(String ip, Context context) {
QLog.d(LOG_TAG, "createConnection - START: ip=" + ip + ", context=" + context + ", existing_binder=" + binder);
_address = ip;
_context = context;
QLog.d(LOG_TAG, "createConnection - PARAMS_SET: _address=" + _address + ", _context=" + _context);
/* If we have old RSA keys, just use them */
QLog.d(LOG_TAG, "createConnection - CHECKING_CRYPTO: reading existing crypto config");
AdbCrypto crypto = AdbUtils.readCryptoConfig(_context.getFilesDir());
QLog.d(LOG_TAG, "createConnection - CRYPTO_CHECK: crypto=" + (crypto != null ? "exists" : "null"));
if (crypto == null)
{
/* We need to make a new pair */
QLog.i(LOG_TAG,
Log.i(LOG_TAG,
"This will only be done once.");
QLog.d(LOG_TAG, "createConnection - GENERATING_CRYPTO: synchronously generating crypto keys");
crypto = AdbUtils.writeNewCryptoConfig(_context.getFilesDir());
if (crypto == null) {
QLog.e(LOG_TAG, "Unable to generate and save RSA key pair");
cryptoReady = false;
return;
}
QLog.d(LOG_TAG, "createConnection - CRYPTO_GENERATED: crypto keys generated successfully");
synchronized (cryptoLock) {
cryptoReady = true;
}
} else {
QLog.d(LOG_TAG, "createConnection - CRYPTO_EXISTS: marking crypto as ready");
synchronized (cryptoLock) {
cryptoReady = true;
}
new Thread(new Runnable() {
@Override
public void run() {
AdbCrypto crypto;
crypto = AdbUtils.writeNewCryptoConfig(_context.getFilesDir());
if (crypto == null)
{
Log.e(LOG_TAG,
"Unable to generate and save RSA key pair");
return;
}
}
}).start();
}
QLog.d(LOG_TAG, "createConnection - SERVICE_CHECK: binder=" + (binder != null ? "exists" : "null"));
if (binder == null) {
QLog.i(LOG_TAG, "createConnection - STARTING_SERVICE: Starting ShellService.class");
service = new Intent(_context, ShellService.class);
service.putExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
QLog.d(LOG_TAG, "createConnection - SERVICE_INTENT: service=" + service);
/* Bind the service if we're not bound already. After binding, the callback will
* perform the initial connection. */
QLog.d(LOG_TAG, "createConnection - BINDING_SERVICE: about to bind service");
_context.bindService(service, QZAdbRemote.getInstance().serviceConn, Service.BIND_AUTO_CREATE);
QLog.d(LOG_TAG, "createConnection - SERVICE_BOUND: bindService called");
QLog.d(LOG_TAG, "createConnection - STARTING_SERVICE: SDK_INT=" + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
QLog.d(LOG_TAG, "createConnection - FOREGROUND_SERVICE: starting foreground service");
_context.startForegroundService(service);
}
else {
QLog.d(LOG_TAG, "createConnection - REGULAR_SERVICE: starting regular service");
_context.startService(service);
}
QLog.d(LOG_TAG, "createConnection - SERVICE_STARTED: service start completed");
} else {
QLog.d(LOG_TAG, "createConnection - SKIP_SERVICE: binder already exists, skipping service creation");
}
QLog.d(LOG_TAG, "createConnection - END: method completed");
}
static public void sendCommand(String command) {
QLog.d(LOG_TAG, "sendCommand " + ADBConnected + " " + command);
Log.d(LOG_TAG, "sendCommand " + ADBConnected + " " + command);
if(ADBConnected) {
StringBuilder commandBuffer = new StringBuilder();
@@ -265,7 +212,7 @@ public class QZAdbRemote implements DeviceConnectionListener {
/* Send it to the device */
connection.queueCommand(commandBuffer.toString());
} else {
QLog.e(LOG_TAG, "sendCommand ADB is not connected!");
Log.e(LOG_TAG, "sendCommand ADB is not connected!");
}
}

View File

@@ -17,7 +17,7 @@ package org.cagnulen.qdomyoszwift;
import android.os.RemoteException;
import android.os.SystemClock;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntCommandFailedException;
@@ -68,7 +68,7 @@ public class SDMChannelController {
boolean openChannel() {
if (null != mAntChannel) {
if (mIsOpen) {
QLog.w(TAG, "Channel was already open");
Log.w(TAG, "Channel was already open");
} else {
// Channel ID message contains device number, type and transmission type. In
// order for master (TX) channels and slave (RX) channels to connect, they
@@ -99,7 +99,7 @@ public class SDMChannelController {
mAntChannel.open();
mIsOpen = true;
QLog.d(TAG, "Opened channel with device number: " + SPEED_SENSOR_ID);
Log.d(TAG, "Opened channel with device number: " + SPEED_SENSOR_ID);
} catch (RemoteException e) {
channelError(e);
} catch (AntCommandFailedException e) {
@@ -108,7 +108,7 @@ public class SDMChannelController {
}
}
} else {
QLog.w(TAG, "No channel available");
Log.w(TAG, "No channel available");
}
return mIsOpen;
@@ -117,7 +117,7 @@ public class SDMChannelController {
void channelError(RemoteException e) {
String logString = "Remote service communication failed.";
QLog.e(TAG, logString);
Log.e(TAG, logString);
}
void channelError(String error, AntCommandFailedException e) {
@@ -146,11 +146,11 @@ public class SDMChannelController {
.append(failureReason);
}
QLog.e(TAG, logString.toString());
Log.e(TAG, logString.toString());
mAntChannel.release();
QLog.e(TAG, "ANT Command Failed");
Log.e(TAG, "ANT Command Failed");
}
public void close() {
@@ -164,7 +164,7 @@ public class SDMChannelController {
mAntChannel = null;
}
QLog.e(TAG, "Channel Closed");
Log.e(TAG, "Channel Closed");
}
/**
@@ -186,20 +186,20 @@ public class SDMChannelController {
@Override
public void onChannelDeath() {
// Display channel death message when channel dies
QLog.e(TAG, "Channel Death");
Log.e(TAG, "Channel Death");
}
@Override
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
QLog.d(TAG, "Rx: " + antParcel);
QLog.d(TAG, "Message Type: " + messageType);
Log.d(TAG, "Rx: " + antParcel);
Log.d(TAG, "Message Type: " + messageType);
if(carousalTimer == null) {
carousalTimer = new Timer(); // At this line a new Thread will be created
carousalTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
QLog.d(TAG, "Tx Unsollicited");
Log.d(TAG, "Tx Unsollicited");
long realtimeMillis = SystemClock.elapsedRealtime();
double speedM_s = speed / 3.6;
long deltaTime = (realtimeMillis - lastTime);
@@ -243,7 +243,7 @@ public class SDMChannelController {
// Constructing channel event message from parcel
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
EventCode code = eventMessage.getEventCode();
QLog.d(TAG, "Event Code: " + code);
Log.d(TAG, "Event Code: " + code);
// Switching on event code to handle the different types of channel events
switch (code) {
@@ -278,7 +278,7 @@ public class SDMChannelController {
break;
case RX_SEARCH_TIMEOUT:
// TODO May want to keep searching
QLog.e(TAG, "No Device Found");
Log.e(TAG, "No Device Found");
break;
case CHANNEL_CLOSED:
case RX_FAIL:

View File

@@ -18,7 +18,7 @@ import android.media.projection.MediaProjectionManager;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.view.Display;
import android.view.OrientationEventListener;
import android.view.WindowManager;
@@ -43,7 +43,7 @@ import android.graphics.Rect;
import android.graphics.Point;
import androidx.core.util.Pair;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.os.Build;
public class ScreenCaptureService extends Service {
@@ -137,7 +137,7 @@ public class ScreenCaptureService extends Service {
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mWidth;
//QLog.e(TAG, "Image reviewing");
//Log.e(TAG, "Image reviewing");
isRunning = true;
@@ -152,7 +152,7 @@ public class ScreenCaptureService extends Service {
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos);
IMAGES_PRODUCED++;
QLog.e(TAG, "captured image: " + IMAGES_PRODUCED);
Log.e(TAG, "captured image: " + IMAGES_PRODUCED);
*/
InputImage inputImage = InputImage.fromBitmap(bitmap, 0);
@@ -169,7 +169,7 @@ public class ScreenCaptureService extends Service {
public void onSuccess(Text result) {
// Task completed successfully
//QLog.e(TAG, "Image done!");
//Log.e(TAG, "Image done!");
String resultText = result.getText();
lastText = resultText;
@@ -204,12 +204,12 @@ public class ScreenCaptureService extends Service {
@Override
public void onFailure(Exception e) {
// Task failed with an exception
//QLog.e(TAG, "Image fail");
//Log.e(TAG, "Image fail");
isRunning = false;
}
});
} else {
//QLog.e(TAG, "Image ignored");
//Log.e(TAG, "Image ignored");
}
}
} catch (Exception e) {
@@ -246,7 +246,7 @@ public class ScreenCaptureService extends Service {
private class MediaProjectionStopCallback extends MediaProjection.Callback {
@Override
public void onStop() {
QLog.e(TAG, "stopping projection.");
Log.e(TAG, "stopping projection.");
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -276,12 +276,12 @@ public class ScreenCaptureService extends Service {
if (!storeDirectory.exists()) {
boolean success = storeDirectory.mkdirs();
if (!success) {
QLog.e(TAG, "failed to create file storage directory.");
Log.e(TAG, "failed to create file storage directory.");
stopSelf();
}
}
} else {
QLog.e(TAG, "failed to create file storage directory, getExternalFilesDir is null.");
Log.e(TAG, "failed to create file storage directory, getExternalFilesDir is null.");
stopSelf();
}
@@ -310,7 +310,7 @@ public class ScreenCaptureService extends Service {
startForeground(notification.first, notification.second);
}
} catch (Exception e) {
QLog.e("ForegroundService", "Failed to start foreground service", e);
Log.e("ForegroundService", "Failed to start foreground service", e);
return START_NOT_STICKY;
}
// start projection

View File

@@ -6,7 +6,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.content.IntentFilter;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.app.Service;
import android.media.RingtoneManager;
import android.net.Uri;
@@ -35,7 +35,7 @@ public class Shortcuts {
List<ShortcutInfo> shortcuts = new ArrayList<>();
QLog.d("Shortcuts", folder);
Log.d("Shortcuts", folder);
File[] files = new File(folder, "profiles").listFiles();
if (files != null) {
for (int i = 0; i < files.length && i < 5; i++) { // Limit to 5 shortcuts
@@ -45,7 +45,7 @@ public class Shortcuts {
if (dotIndex > 0) { // Check if there is a dot, indicating an extension exists
fileNameWithoutExtension = fileNameWithoutExtension.substring(0, dotIndex);
}
QLog.d("Shortcuts", file.getAbsolutePath());
Log.d("Shortcuts", file.getAbsolutePath());
Intent intent = new Intent(context, context.getClass());
intent.setAction(Intent.ACTION_VIEW);
intent.putExtra("profile_path", file.getAbsolutePath());
@@ -74,7 +74,7 @@ public class Shortcuts {
for (String key : extras.keySet()) {
Object value = extras.get(key);
if("profile_path".equals(key)) {
QLog.d("Shortcuts", "profile_path: " + value.toString());
Log.d("Shortcuts", "profile_path: " + value.toString());
return value.toString();
}
}
@@ -88,7 +88,7 @@ public class Shortcuts {
if (extras != null) {
for (String key : extras.keySet()) {
Object value = extras.get(key);
QLog.d("Shortcuts", "Key: " + key + ", Value: " + value.toString());
Log.d("Shortcuts", "Key: " + key + ", Value: " + value.toString());
}
}
}

View File

@@ -17,7 +17,7 @@ package org.cagnulen.qdomyoszwift;
import android.os.RemoteException;
import android.os.SystemClock;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntCommandFailedException;
@@ -67,7 +67,7 @@ public class SpeedChannelController {
boolean openChannel() {
if (null != mAntChannel) {
if (mIsOpen) {
QLog.w(TAG, "Channel was already open");
Log.w(TAG, "Channel was already open");
} else {
// Channel ID message contains device number, type and transmission type. In
// order for master (TX) channels and slave (RX) channels to connect, they
@@ -98,7 +98,7 @@ public class SpeedChannelController {
mAntChannel.open();
mIsOpen = true;
QLog.d(TAG, "Opened channel with device number: " + SPEED_SENSOR_ID);
Log.d(TAG, "Opened channel with device number: " + SPEED_SENSOR_ID);
} catch (RemoteException e) {
channelError(e);
} catch (AntCommandFailedException e) {
@@ -107,7 +107,7 @@ public class SpeedChannelController {
}
}
} else {
QLog.w(TAG, "No channel available");
Log.w(TAG, "No channel available");
}
return mIsOpen;
@@ -116,7 +116,7 @@ public class SpeedChannelController {
void channelError(RemoteException e) {
String logString = "Remote service communication failed.";
QLog.e(TAG, logString);
Log.e(TAG, logString);
}
void channelError(String error, AntCommandFailedException e) {
@@ -145,11 +145,11 @@ public class SpeedChannelController {
.append(failureReason);
}
QLog.e(TAG, logString.toString());
Log.e(TAG, logString.toString());
mAntChannel.release();
QLog.e(TAG, "ANT Command Failed");
Log.e(TAG, "ANT Command Failed");
}
public void close() {
@@ -163,7 +163,7 @@ public class SpeedChannelController {
mAntChannel = null;
}
QLog.e(TAG, "Channel Closed");
Log.e(TAG, "Channel Closed");
}
/**
@@ -185,20 +185,20 @@ public class SpeedChannelController {
@Override
public void onChannelDeath() {
// Display channel death message when channel dies
QLog.e(TAG, "Channel Death");
Log.e(TAG, "Channel Death");
}
@Override
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
QLog.d(TAG, "Rx: " + antParcel);
QLog.d(TAG, "Message Type: " + messageType);
Log.d(TAG, "Rx: " + antParcel);
Log.d(TAG, "Message Type: " + messageType);
if(carousalTimer == null) {
carousalTimer = new Timer(); // At this line a new Thread will be created
carousalTimer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
QLog.d(TAG, "Tx Unsollicited");
Log.d(TAG, "Tx Unsollicited");
long realtimeMillis = SystemClock.elapsedRealtime();
if (lastTime != 0) {
@@ -252,7 +252,7 @@ public class SpeedChannelController {
// Constructing channel event message from parcel
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
EventCode code = eventMessage.getEventCode();
QLog.d(TAG, "Event Code: " + code);
Log.d(TAG, "Event Code: " + code);
// Switching on event code to handle the different types of channel events
switch (code) {
@@ -296,7 +296,7 @@ public class SpeedChannelController {
break;
case RX_SEARCH_TIMEOUT:
// TODO May want to keep searching
QLog.e(TAG, "No Device Found");
Log.e(TAG, "No Device Found");
break;
case CHANNEL_CLOSED:
case RX_FAIL:

View File

@@ -8,7 +8,7 @@ import android.content.IntentFilter;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbDeviceConnection;
import android.hardware.usb.UsbManager;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.app.Service;
import android.media.RingtoneManager;
import android.net.Uri;
@@ -43,12 +43,12 @@ public class Usbserial {
static int lastReadLen = 0;
public static void open(Context context) {
QLog.d("QZ","UsbSerial open");
Log.d("QZ","UsbSerial open");
// Find all available drivers from attached devices.
UsbManager manager = (UsbManager) context.getSystemService(Context.USB_SERVICE);
List<UsbSerialDriver> availableDrivers = UsbSerialProber.getDefaultProber().findAllDrivers(manager);
if (availableDrivers.isEmpty()) {
QLog.d("QZ","UsbSerial no available drivers");
Log.d("QZ","UsbSerial no available drivers");
return;
}
@@ -58,7 +58,7 @@ public class Usbserial {
Uri notification = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
RingtoneManager.getRingtone(context, notification).play();
QLog.d("QZ","USB permission ...");
Log.d("QZ","USB permission ...");
final Boolean[] granted = {null};
BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@Override
@@ -85,12 +85,12 @@ public class Usbserial {
// Do something here
}
}
QLog.d("QZ","USB permission "+granted[0]);
Log.d("QZ","USB permission "+granted[0]);
}
UsbDeviceConnection connection = manager.openDevice(driver.getDevice());
if (connection == null) {
QLog.d("QZ","UsbSerial no permissions");
Log.d("QZ","UsbSerial no permissions");
// add UsbManager.requestPermission(driver.getDevice(), ..) handling here
return;
}
@@ -104,14 +104,14 @@ public class Usbserial {
// Do something here
}
QLog.d("QZ","UsbSerial port opened");
Log.d("QZ","UsbSerial port opened");
}
public static void write (byte[] bytes) {
if(port == null)
return;
QLog.d("QZ","UsbSerial writing " + new String(bytes, StandardCharsets.UTF_8));
Log.d("QZ","UsbSerial writing " + new String(bytes, StandardCharsets.UTF_8));
try {
port.write(bytes, 2000);
}
@@ -132,7 +132,7 @@ public class Usbserial {
try {
lastReadLen = port.read(receiveData, 2000);
QLog.d("QZ","UsbSerial reading " + lastReadLen + new String(receiveData, StandardCharsets.UTF_8));
Log.d("QZ","UsbSerial reading " + lastReadLen + new String(receiveData, StandardCharsets.UTF_8));
}
catch (IOException e) {
// Do something here

View File

@@ -17,7 +17,7 @@ import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
@@ -33,7 +33,7 @@ public class WearableController {
_intent = new Intent(context, WearableMessageListenerService.class);
// FloatingWindowGFG service is started
context.startService(_intent);
QLog.v("WearableController", "started");
Log.v("WearableController", "started");
}
public static int getHeart() {

View File

@@ -15,7 +15,7 @@ import com.google.android.gms.wearable.Wearable;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.wearable.DataItemBuffer;
import com.google.android.gms.wearable.DataMap;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.os.Bundle;
import com.google.android.gms.common.api.Status;
import java.io.InputStream;
@@ -31,7 +31,7 @@ public class WearableMessageListenerService extends Service implements
@Override
public void onCreate() {
super.onCreate();
QLog.v("WearableMessageListenerService","onCreate");
Log.v("WearableMessageListenerService","onCreate");
}
public static int getHeart() {
@@ -55,7 +55,7 @@ public class WearableMessageListenerService extends Service implements
mWearableClient.addListener(this);
Wearable.getDataClient(this).addListener(this);
QLog.v("WearableMessageListenerService","onStartCommand");
Log.v("WearableMessageListenerService","onStartCommand");
// Return START_STICKY to restart the service if it's killed by the system
return START_STICKY;
@@ -65,9 +65,9 @@ public class WearableMessageListenerService extends Service implements
public void onDataChanged(DataEventBuffer dataEvents) {
for (DataEvent event : dataEvents) {
if (event.getType() == DataEvent.TYPE_DELETED) {
QLog.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
Log.d(TAG, "DataItem deleted: " + event.getDataItem().getUri());
} else if (event.getType() == DataEvent.TYPE_CHANGED) {
QLog.d(TAG, "DataItem changed: " + event.getDataItem().getUri() + " " + event.getDataItem().getUri().getPath());
Log.d(TAG, "DataItem changed: " + event.getDataItem().getUri() + " " + event.getDataItem().getUri().getPath());
if(event.getDataItem().getUri().getPath().equals("/qz")) {
new Thread(new Runnable() {
@Override
@@ -78,14 +78,14 @@ public class WearableMessageListenerService extends Service implements
heart_rate = DataMap.fromByteArray(result.get(0).getData())
.getInt("heart_rate", 0);
} else {
QLog.e(TAG, "Unexpected number of DataItems found.\n"
Log.e(TAG, "Unexpected number of DataItems found.\n"
+ "\tExpected: 1\n"
+ "\tActual: " + result.getCount());
}
} else {
QLog.d(TAG, "onHandleIntent: failed to get current alarm state");
} else if (Log.isLoggable(TAG, Log.DEBUG)) {
Log.d(TAG, "onHandleIntent: failed to get current alarm state");
}
QLog.d(TAG, "Heart: " + heart_rate);
Log.d(TAG, "Heart: " + heart_rate);
}
}).start();
}
@@ -96,17 +96,17 @@ public class WearableMessageListenerService extends Service implements
@Override
public void onConnected(Bundle bundle) {
QLog.v("WearableMessageListenerService","onConnected");
Log.v("WearableMessageListenerService","onConnected");
}
@Override
public void onConnectionSuspended(int i) {
QLog.v("WearableMessageListenerService","onConnectionSuspended");
Log.v("WearableMessageListenerService","onConnectionSuspended");
}
@Override
public void onConnectionFailed(ConnectionResult connectionResult) {
QLog.v("WearableMessageListenerService","onConnectionFailed");
Log.v("WearableMessageListenerService","onConnectionFailed");
}
@Override
@@ -117,8 +117,8 @@ public class WearableMessageListenerService extends Service implements
// Handle the received message data here
String messageData = new String(data); // Assuming it's a simple string message
QLog.v("Wearable", path);
QLog.v("Wearable", messageData);
Log.v("Wearable", path);
Log.v("Wearable", messageData);
// You can then perform actions or update data in your service based on the received message
}

View File

@@ -17,7 +17,7 @@ import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import android.content.BroadcastReceiver;
import android.content.ContextWrapper;
import android.content.IntentFilter;
@@ -44,12 +44,12 @@ public class ZapClickLayer {
}
public static int processCharacteristic(byte[] value) {
QLog.d(TAG, "processCharacteristic");
Log.d(TAG, "processCharacteristic");
return device.processCharacteristic("QZ", value);
}
public static byte[] buildHandshakeStart() {
QLog.d(TAG, "buildHandshakeStart");
Log.d(TAG, "buildHandshakeStart");
return device.buildHandshakeStart();
}
}

View File

@@ -17,7 +17,7 @@ import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import com.garmin.android.connectiq.ConnectIQ;
import com.garmin.android.connectiq.ConnectIQAdbStrategy;
import com.garmin.android.connectiq.IQApp;
@@ -49,17 +49,17 @@ public class ZwiftAPI {
// Ora puoi usare 'message' come un oggetto normale
} catch (InvalidProtocolBufferException e) {
// Gestisci l'eccezione se il messaggio non può essere parsato
QLog.e(TAG, e.toString());
Log.e(TAG, e.toString());
}
}
public static float getAltitude() {
QLog.d(TAG, "getAltitude " + playerState.getAltitude());
Log.d(TAG, "getAltitude " + playerState.getAltitude());
return playerState.getAltitude();
}
public static float getDistance() {
QLog.d(TAG, "getDistance " + playerState.getDistance());
Log.d(TAG, "getDistance " + playerState.getDistance());
return playerState.getDistance();
}
}

View File

@@ -17,7 +17,7 @@ import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import com.garmin.android.connectiq.ConnectIQ;
import com.garmin.android.connectiq.ConnectIQAdbStrategy;
import com.garmin.android.connectiq.IQApp;

View File

@@ -9,7 +9,6 @@ import java.io.UnsupportedEncodingException;
import java.net.ConnectException;
import java.net.Socket;
import java.util.HashMap;
import org.cagnulen.qdomyoszwift.QLog;
/**
* This class represents an ADB connection.
@@ -125,13 +124,10 @@ public class AdbConnection implements Closeable {
try {
/* Read and parse a message off the socket's input stream */
AdbProtocol.AdbMessage msg = AdbProtocol.AdbMessage.parseAdbMessage(inputStream);
QLog.d("AdbConnection", "connectionThread - Received packet: command=0x" + Integer.toHexString(msg.command) + ", arg0=" + msg.arg0 + ", arg1=" + msg.arg1);
/* Verify magic and checksum */
if (!AdbProtocol.validateMessage(msg)) {
QLog.w("AdbConnection", "connectionThread - Invalid message, dropping packet");
if (!AdbProtocol.validateMessage(msg))
continue;
}
switch (msg.command)
{
@@ -179,25 +175,21 @@ public class AdbConnection implements Closeable {
break;
case AdbProtocol.CMD_AUTH:
QLog.d("AdbConnection", "connectionThread - Received AUTH packet, type=" + msg.arg0);
byte[] packet;
if (msg.arg0 == AdbProtocol.AUTH_TYPE_TOKEN)
{
/* This is an authentication challenge */
QLog.d("AdbConnection", "connectionThread - AUTH_TYPE_TOKEN challenge, sentSignature=" + conn.sentSignature);
if (conn.sentSignature)
{
/* We've already tried our signature, so send our public key */
QLog.d("AdbConnection", "connectionThread - Sending RSA public key");
packet = AdbProtocol.generateAuth(AdbProtocol.AUTH_TYPE_RSA_PUBLIC,
conn.crypto.getAdbPublicKeyPayload());
}
else
{
/* We'll sign the token */
QLog.d("AdbConnection", "connectionThread - Signing token with private key");
packet = AdbProtocol.generateAuth(AdbProtocol.AUTH_TYPE_SIGNATURE,
conn.crypto.signAdbTokenPayload(msg.payload));
conn.sentSignature = true;
@@ -206,22 +198,16 @@ public class AdbConnection implements Closeable {
/* Write the AUTH reply */
conn.outputStream.write(packet);
conn.outputStream.flush();
QLog.d("AdbConnection", "connectionThread - AUTH response sent");
}
else {
QLog.w("AdbConnection", "connectionThread - Unhandled AUTH type: " + msg.arg0);
}
break;
case AdbProtocol.CMD_CNXN:
QLog.d("AdbConnection", "connectionThread - Received CNXN packet! maxData=" + msg.arg1);
synchronized (conn) {
/* We need to store the max data size */
conn.maxData = msg.arg1;
/* Mark us as connected and unwait anyone waiting on the connection */
conn.connected = true;
QLog.d("AdbConnection", "connectionThread - Connection established! Notifying waiting threads");
conn.notifyAll();
}
break;
@@ -233,7 +219,6 @@ public class AdbConnection implements Closeable {
} catch (Exception e) {
/* The cleanup is taken care of by a combination of this thread
* and close() */
QLog.e("AdbConnection", "connectionThread - Exception in connection thread: " + e.getClass().getSimpleName() + ": " + e.getMessage(), e);
break;
}
}
@@ -285,32 +270,23 @@ public class AdbConnection implements Closeable {
if (connected)
throw new IllegalStateException("Already connected");
QLog.d("AdbConnection", "connect() - Starting ADB connection");
/* Write the CONNECT packet */
outputStream.write(AdbProtocol.generateConnect());
outputStream.flush();
QLog.d("AdbConnection", "connect() - CONNECT packet sent, starting connection thread");
/* Start the connection thread to respond to the peer */
connectAttempted = true;
connectionThread.start();
QLog.d("AdbConnection", "connect() - Connection thread started, waiting for connection...");
/* Wait for the connection to go live */
synchronized (this) {
if (!connected) {
QLog.d("AdbConnection", "connect() - Waiting for connection to complete...");
if (!connected)
wait();
QLog.d("AdbConnection", "connect() - Wait completed, connected=" + connected);
}
if (!connected) {
QLog.e("AdbConnection", "connect() - Connection failed after wait");
throw new IOException("Connection failed");
}
}
QLog.d("AdbConnection", "connect() - Successfully connected!");
}
/**

View File

@@ -11,7 +11,6 @@ import com.cgutman.adblib.AdbConnection;
import com.cgutman.adblib.AdbCrypto;
import com.cgutman.adblib.AdbStream;
import com.cgutman.androidremotedebugger.AdbUtils;
import org.cagnulen.qdomyoszwift.QLog;
public class DeviceConnection implements Closeable {
private static final int CONN_TIMEOUT = 5000;
@@ -60,58 +59,42 @@ public class DeviceConnection implements Closeable {
}
public void startConnect() {
QLog.d("DeviceConnection", "startConnect - START: host=" + host + ", port=" + port + ", listener=" + listener);
new Thread(new Runnable() {
@Override
public void run() {
QLog.d("DeviceConnection", "startConnect.run - THREAD_START: host=" + host + ", port=" + port);
boolean connected = false;
Socket socket = new Socket();
AdbCrypto crypto;
/* Load the crypto config */
QLog.d("DeviceConnection", "startConnect.run - LOADING_CRYPTO: calling loadAdbCrypto");
crypto = listener.loadAdbCrypto(DeviceConnection.this);
if (crypto == null) {
QLog.e("DeviceConnection", "startConnect.run - CRYPTO_FAILED: crypto is null, returning");
return;
}
QLog.d("DeviceConnection", "startConnect.run - CRYPTO_LOADED: crypto=" + crypto);
try {
/* Establish a connect to the remote host */
QLog.d("DeviceConnection", "startConnect.run - SOCKET_CONNECT: connecting to " + host + ":" + port + " with timeout=" + CONN_TIMEOUT);
socket.connect(new InetSocketAddress(host, port), CONN_TIMEOUT);
QLog.d("DeviceConnection", "startConnect.run - SOCKET_CONNECTED: socket connected successfully");
} catch (IOException e) {
QLog.e("DeviceConnection", "startConnect.run - SOCKET_FAILED: connection failed", e);
listener.notifyConnectionFailed(DeviceConnection.this, e);
return;
}
try {
/* Establish the application layer connection */
QLog.d("DeviceConnection", "startConnect.run - ADB_CONNECTION: creating AdbConnection");
connection = AdbConnection.create(socket, crypto);
QLog.d("DeviceConnection", "startConnect.run - ADB_CONNECT: calling connection.connect()");
connection.connect();
QLog.d("DeviceConnection", "startConnect.run - ADB_CONNECTED: ADB connection established");
/* Open the shell stream */
QLog.d("DeviceConnection", "startConnect.run - SHELL_STREAM: opening shell stream");
shellStream = connection.open("shell:");
QLog.d("DeviceConnection", "startConnect.run - SHELL_OPENED: shell stream opened successfully");
connected = true;
} catch (IOException e) {
QLog.e("DeviceConnection", "startConnect.run - ADB_IO_ERROR: IOException during ADB connection", e);
listener.notifyConnectionFailed(DeviceConnection.this, e);
} catch (InterruptedException e) {
QLog.e("DeviceConnection", "startConnect.run - ADB_INTERRUPTED: InterruptedException during ADB connection", e);
listener.notifyConnectionFailed(DeviceConnection.this, e);
} finally {
/* Cleanup if the connection failed */
if (!connected) {
QLog.d("DeviceConnection", "startConnect.run - CLEANUP: connection failed, cleaning up");
AdbUtils.safeClose(shellStream);
/* The AdbConnection object will close the underlying socket
@@ -129,16 +112,12 @@ public class DeviceConnection implements Closeable {
}
/* Notify the listener that the connection is complete */
QLog.d("DeviceConnection", "startConnect.run - NOTIFY_SUCCESS: calling listener.notifyConnectionEstablished");
listener.notifyConnectionEstablished(DeviceConnection.this);
QLog.d("DeviceConnection", "startConnect.run - NOTIFIED: notifyConnectionEstablished called");
/* Start the receive thread */
QLog.d("DeviceConnection", "startConnect.run - START_RECEIVE: starting receive thread");
startReceiveThread();
/* Enter the blocking send loop */
QLog.d("DeviceConnection", "startConnect.run - SEND_LOOP: entering send loop");
sendLoop();
}
}).start();
@@ -169,32 +148,23 @@ public class DeviceConnection implements Closeable {
}
private void startReceiveThread() {
QLog.d("DeviceConnection", "startReceiveThread - START: creating receive thread");
new Thread(new Runnable() {
@Override
public void run() {
QLog.d("DeviceConnection", "startReceiveThread.run - THREAD_START: receive thread started");
try {
while (!shellStream.isClosed()) {
QLog.d("DeviceConnection", "startReceiveThread.run - READING: waiting for data from shellStream");
byte[] data = shellStream.read();
QLog.d("DeviceConnection", "startReceiveThread.run - DATA_RECEIVED: " + data.length + " bytes received");
listener.receivedData(DeviceConnection.this, data, 0, data.length);
}
QLog.d("DeviceConnection", "startReceiveThread.run - STREAM_CLOSED: shellStream is closed");
listener.notifyStreamClosed(DeviceConnection.this);
} catch (IOException e) {
QLog.e("DeviceConnection", "startReceiveThread.run - IO_ERROR: IOException in receive thread", e);
listener.notifyStreamFailed(DeviceConnection.this, e);
} catch (InterruptedException e) {
QLog.d("DeviceConnection", "startReceiveThread.run - INTERRUPTED: receive thread interrupted");
} finally {
QLog.d("DeviceConnection", "startReceiveThread.run - CLEANUP: cleaning up receive thread");
AdbUtils.safeClose(DeviceConnection.this);
}
}
}).start();
QLog.d("DeviceConnection", "startReceiveThread - END: receive thread started");
}
public boolean isClosed() {

View File

@@ -23,7 +23,7 @@ import android.os.IBinder;
import android.os.PowerManager;
import android.os.PowerManager.WakeLock;
import androidx.core.app.NotificationCompat;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
public class ShellService extends Service implements DeviceConnectionListener {
@@ -48,20 +48,14 @@ public class ShellService extends Service implements DeviceConnectionListener {
public class ShellServiceBinder extends Binder {
public DeviceConnection createConnection(String host, int port) {
QLog.d("ShellService", "createConnection - START: host=" + host + ", port=" + port + ", listener=" + listener);
DeviceConnection conn = new DeviceConnection(listener, host, port);
QLog.d("ShellService", "createConnection - CONNECTION_CREATED: conn=" + conn);
listener.addListener(conn, ShellService.this);
QLog.d("ShellService", "createConnection - LISTENER_ADDED: returning conn=" + conn);
return conn;
}
public DeviceConnection findConnection(String host, int port) {
String connStr = host+":"+port;
QLog.d("ShellService", "findConnection - SEARCH: connStr=" + connStr + ", mapSize=" + currentConnectionMap.size());
DeviceConnection found = currentConnectionMap.get(connStr);
QLog.d("ShellService", "findConnection - RESULT: found=" + (found != null ? "exists" : "null"));
return found;
return currentConnectionMap.get(connStr);
}
public void notifyPausingActivity(DeviceConnection devConn) {
@@ -82,95 +76,68 @@ public class ShellService extends Service implements DeviceConnectionListener {
}
public void addListener(DeviceConnection conn, DeviceConnectionListener listener) {
QLog.d("ShellService", "addListener - START: conn=" + conn + ", listener=" + listener);
ShellService.this.listener.addListener(conn, listener);
QLog.d("ShellService", "addListener - END: listener added");
}
public void removeListener(DeviceConnection conn, DeviceConnectionListener listener) {
QLog.d("ShellService", "removeListener - START: conn=" + conn + ", listener=" + listener);
ShellService.this.listener.removeListener(conn, listener);
QLog.d("ShellService", "removeListener - END: listener removed");
}
}
@Override
public IBinder onBind(Intent arg0) {
QLog.d("ShellService", "onBind - START: intent=" + arg0 + ", binder=" + binder);
QLog.d("ShellService", "onBind - END: returning binder");
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
QLog.d("ShellService", "onUnbind - START: intent=" + intent + ", connections=" + currentConnectionMap.size());
/* Stop the service if no connections remain */
if (currentConnectionMap.isEmpty()) {
QLog.d("ShellService", "onUnbind - STOPPING_SERVICE: no connections remain");
stopSelf();
} else {
QLog.d("ShellService", "onUnbind - KEEPING_SERVICE: " + currentConnectionMap.size() + " connections remain");
}
QLog.d("ShellService", "onUnbind - END: returning false");
return false;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
QLog.d("ShellService", "onStartCommand - START: intent=" + intent + ", flags=" + flags + ", startId=" + startId + ", foregroundId=" + foregroundId);
if (foregroundId == 0) {
try {
int serviceType = intent.getIntExtra(EXTRA_FOREGROUND_SERVICE_TYPE, FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE);
// If we're not already running in the foreground, use a placeholder
// notification until a real connection is established. After connection
// establishment, the real notification will replace this one.
QLog.d("ShellService", "onStartCommand - FOREGROUND_START: serviceType=" + serviceType + ", SDK_INT=" + Build.VERSION.SDK_INT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
QLog.d("ShellService", "onStartCommand - FOREGROUND_Q+: starting with service type");
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification(), serviceType);
} else {
QLog.d("ShellService", "onStartCommand - FOREGROUND_LEGACY: starting without service type");
startForeground(FOREGROUND_PLACEHOLDER_ID, createForegroundPlaceholderNotification());
}
QLog.d("ShellService", "onStartCommand - FOREGROUND_SUCCESS: foreground service started");
} catch (Exception e) {
QLog.e("ForegroundService", "Failed to start foreground service", e);
Log.e("ForegroundService", "Failed to start foreground service", e);
return START_NOT_STICKY;
}
} else {
QLog.d("ShellService", "onStartCommand - SKIP_FOREGROUND: already running in foreground with id=" + foregroundId);
}
// Don't restart if we've been killed. We will have already lost our connections
// when we died, so we'll just be running doing nothing if the OS restarted us.
QLog.d("ShellService", "onStartCommand - END: returning START_NOT_STICKY");
return Service.START_NOT_STICKY;
}
@Override
public void onCreate() {
QLog.d("ShellService", "onCreate - START: initializing service");
super.onCreate();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
QLog.d("ShellService", "onCreate - NOTIFICATION_CHANNEL: creating notification channel");
NotificationChannel channel = new NotificationChannel(CHANNEL_ID, "Connection Info", NotificationManager.IMPORTANCE_DEFAULT);
NotificationManager notificationManager = getSystemService(NotificationManager.class);
notificationManager.createNotificationChannel(channel);
QLog.d("ShellService", "onCreate - NOTIFICATION_CHANNEL: channel created");
}
QLog.d("ShellService", "onCreate - WIFI_LOCK: creating wifi lock");
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
wlanLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL, "RemoteADBShell:ShellService");
QLog.d("ShellService", "onCreate - WIFI_LOCK: wlanLock=" + wlanLock);
QLog.d("ShellService", "onCreate - WAKE_LOCK: creating wake lock");
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "RemoteADBShell:ShellService");
QLog.d("ShellService", "onCreate - WAKE_LOCK: wakeLock=" + wakeLock);
QLog.d("ShellService", "onCreate - END: service initialization complete");
}
@Override
@@ -281,76 +248,44 @@ public class ShellService extends Service implements DeviceConnectionListener {
}
private synchronized void addNewConnection(DeviceConnection devConn) {
QLog.d("ShellService", "addNewConnection - START: devConn=" + devConn + ", currentSize=" + currentConnectionMap.size());
if (currentConnectionMap.isEmpty()) {
QLog.d("ShellService", "addNewConnection - ACQUIRING_LOCKS: first connection, acquiring locks");
wakeLock.acquire();
wlanLock.acquire();
QLog.d("ShellService", "addNewConnection - LOCKS_ACQUIRED: wakeLock and wlanLock acquired");
}
String connString = getConnectionString(devConn);
QLog.d("ShellService", "addNewConnection - ADDING: connString=" + connString);
currentConnectionMap.put(connString, devConn);
QLog.d("ShellService", "addNewConnection - END: connection added, newSize=" + currentConnectionMap.size());
currentConnectionMap.put(getConnectionString(devConn), devConn);
}
private synchronized void removeConnection(DeviceConnection devConn) {
String connString = getConnectionString(devConn);
QLog.d("ShellService", "removeConnection - START: devConn=" + devConn + ", connString=" + connString + ", currentSize=" + currentConnectionMap.size());
currentConnectionMap.remove(connString);
QLog.d("ShellService", "removeConnection - REMOVED: newSize=" + currentConnectionMap.size());
currentConnectionMap.remove(getConnectionString(devConn));
/* Stop the service if no connections remain */
if (currentConnectionMap.isEmpty()) {
QLog.d("ShellService", "removeConnection - STOPPING_SERVICE: no connections remain");
stopSelf();
} else {
QLog.d("ShellService", "removeConnection - KEEPING_SERVICE: " + currentConnectionMap.size() + " connections remain");
}
QLog.d("ShellService", "removeConnection - END");
}
@Override
public void notifyConnectionEstablished(DeviceConnection devConn) {
QLog.d("ShellService", "notifyConnectionEstablished - START: devConn=" + devConn + ", host=" + (devConn != null ? devConn.getHost() : "null") + ", port=" + (devConn != null ? devConn.getPort() : "null"));
addNewConnection(devConn);
QLog.d("ShellService", "notifyConnectionEstablished - CONNECTION_ADDED: updating notification");
updateNotification(devConn, true);
QLog.d("ShellService", "notifyConnectionEstablished - END: connection established successfully");
}
@Override
public void notifyConnectionFailed(DeviceConnection devConn, Exception e) {
QLog.d("ShellService", "notifyConnectionFailed - START: devConn=" + devConn + ", host=" + (devConn != null ? devConn.getHost() : "null") + ", port=" + (devConn != null ? devConn.getPort() : "null"));
QLog.e("ShellService", "notifyConnectionFailed - ERROR: " + (e != null ? e.getMessage() : "null exception"));
if (e != null) {
QLog.e("ShellService", "notifyConnectionFailed - STACK_TRACE: ", e);
}
/* No notification is displaying here */
QLog.d("ShellService", "notifyConnectionFailed - END");
}
@Override
public void notifyStreamFailed(DeviceConnection devConn, Exception e) {
QLog.d("ShellService", "notifyStreamFailed - START: devConn=" + devConn + ", host=" + (devConn != null ? devConn.getHost() : "null") + ", port=" + (devConn != null ? devConn.getPort() : "null"));
QLog.e("ShellService", "notifyStreamFailed - ERROR: " + (e != null ? e.getMessage() : "null exception"));
if (e != null) {
QLog.e("ShellService", "notifyStreamFailed - STACK_TRACE: ", e);
}
updateNotification(devConn, false);
QLog.d("ShellService", "notifyStreamFailed - NOTIFICATION_UPDATED: removing connection");
removeConnection(devConn);
QLog.d("ShellService", "notifyStreamFailed - END");
}
@Override
public void notifyStreamClosed(DeviceConnection devConn) {
QLog.d("ShellService", "notifyStreamClosed - START: devConn=" + devConn + ", host=" + (devConn != null ? devConn.getHost() : "null") + ", port=" + (devConn != null ? devConn.getPort() : "null"));
updateNotification(devConn, false);
QLog.d("ShellService", "notifyStreamClosed - NOTIFICATION_UPDATED: removing connection");
removeConnection(devConn);
QLog.d("ShellService", "notifyStreamClosed - END");
}
@Override

View File

@@ -55,7 +55,7 @@ import java.util.List;
import android.app.Activity;
import android.content.Context;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import com.android.billingclient.api.AcknowledgePurchaseParams;
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
@@ -65,16 +65,13 @@ import com.android.billingclient.api.BillingFlowParams;
import com.android.billingclient.api.BillingResult;
import com.android.billingclient.api.ConsumeParams;
import com.android.billingclient.api.ConsumeResponseListener;
import com.android.billingclient.api.PendingPurchasesParams;
import com.android.billingclient.api.ProductDetails;
import com.android.billingclient.api.ProductDetailsResponseListener;
import com.android.billingclient.api.Purchase;
import com.android.billingclient.api.Purchase.PurchaseState;
import com.android.billingclient.api.PurchasesResponseListener;
import com.android.billingclient.api.PurchasesUpdatedListener;
import com.android.billingclient.api.QueryProductDetailsParams;
import com.android.billingclient.api.QueryPurchasesParams;
import com.android.billingclient.api.QueryProductDetailsResult;
import com.android.billingclient.api.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
/***********************************************************************
@@ -100,8 +97,8 @@ public class InAppPurchase implements PurchasesUpdatedListener
public static final int RESULT_OK = BillingClient.BillingResponseCode.OK;
public static final int RESULT_USER_CANCELED = BillingClient.BillingResponseCode.USER_CANCELED;
public static final String TYPE_INAPP = BillingClient.ProductType.INAPP;
public static final String TYPE_SUBS = BillingClient.ProductType.SUBS;
public static final String TYPE_INAPP = BillingClient.SkuType.INAPP;
public static final String TYPE_SUBS = BillingClient.SkuType.SUBS;
public static final String TAG = "InAppPurchase";
// Should be in sync with InAppTransaction::FailureReason
@@ -122,28 +119,25 @@ public class InAppPurchase implements PurchasesUpdatedListener
}
public void initializeConnection(){
QLog.w(TAG, "initializeConnection start");
PendingPurchasesParams pendingPurchasesParams = PendingPurchasesParams.newBuilder()
.enableOneTimeProducts()
.build();
Log.w(TAG, "initializeConnection start");
billingClient = BillingClient.newBuilder(m_context)
.enablePendingPurchases(pendingPurchasesParams)
.enablePendingPurchases()
.setListener(this)
.build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
QLog.w(TAG, "onBillingSetupFinished");
Log.w(TAG, "onBillingSetupFinished");
if (billingResult.getResponseCode() == RESULT_OK) {
purchasedProductsQueried(m_nativePointer);
} else {
QLog.w(TAG, "onBillingSetupFinished error!" + billingResult.getResponseCode());
Log.w(TAG, "onBillingSetupFinished error!" + billingResult.getResponseCode());
}
}
@Override
public void onBillingServiceDisconnected() {
QLog.w(TAG, "Billing service disconnected");
Log.w(TAG, "Billing service disconnected");
}
});
}
@@ -197,7 +191,7 @@ public class InAppPurchase implements PurchasesUpdatedListener
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult)
{
QLog.d(TAG, "Purchase acknowledged ");
Log.d(TAG, "Purchase acknowledged ");
}
}
);
@@ -205,9 +199,9 @@ public class InAppPurchase implements PurchasesUpdatedListener
}
public void queryDetails(final String[] productIds) {
QLog.d(TAG, "queryDetails: start");
Log.d(TAG, "queryDetails: start");
int index = 0;
QLog.d(TAG, "queryDetails: productIds.length " + productIds.length);
Log.d(TAG, "queryDetails: productIds.length " + productIds.length);
while (index < productIds.length) {
List<String> productIdList = new ArrayList<>();
for (int i = index; i < Math.min(index + 20, productIds.length); ++i) {
@@ -215,44 +209,31 @@ public class InAppPurchase implements PurchasesUpdatedListener
}
index += productIdList.size();
List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
for (String productId : productIdList) {
productList.add(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(productId)
.setProductType(TYPE_SUBS)
.build());
}
QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
.setProductList(productList)
.build();
billingClient.queryProductDetailsAsync(params,
(billingResult, productDetailsResult) -> {
List<ProductDetails> productDetailsList = productDetailsResult.getProductDetailsList();
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(productIdList).setType(TYPE_SUBS);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
int responseCode = billingResult.getResponseCode();
QLog.d(TAG, "onProductDetailsResponse: responseCode " + responseCode);
Log.d(TAG, "onSkuDetailsResponse: responseCode " + responseCode);
if (responseCode != RESULT_OK) {
QLog.e(TAG, "queryDetails: Couldn't retrieve product details.");
Log.e(TAG, "queryDetails: Couldn't retrieve sku details.");
return;
}
if (productDetailsList == null || productDetailsList.isEmpty()) {
QLog.e(TAG, "queryDetails: No details list in response.");
if (skuDetailsList == null) {
Log.e(TAG, "queryDetails: No details list in response.");
return;
}
QLog.d(TAG, "onProductDetailsResponse: productDetailsList " + productDetailsList);
for (ProductDetails productDetails : productDetailsList) {
Log.d(TAG, "onSkuDetailsResponse: skuDetailsList " + skuDetailsList);
for (SkuDetails skuDetails : skuDetailsList) {
try {
String queriedProductId = productDetails.getProductId();
String queriedPrice = "";
String queriedTitle = productDetails.getTitle();
String queriedDescription = productDetails.getDescription();
// Get price from subscription offer details
if (productDetails.getSubscriptionOfferDetails() != null && !productDetails.getSubscriptionOfferDetails().isEmpty()) {
queriedPrice = productDetails.getSubscriptionOfferDetails().get(0).getPricingPhases().getPricingPhaseList().get(0).getFormattedPrice();
}
String queriedProductId = skuDetails.getSku();
String queriedPrice = skuDetails.getPrice();
String queriedTitle = skuDetails.getTitle();
String queriedDescription = skuDetails.getDescription();
registerProduct(m_nativePointer,
queriedProductId,
queriedPrice,
@@ -262,6 +243,7 @@ public class InAppPurchase implements PurchasesUpdatedListener
e.printStackTrace();
}
}
}
});
@@ -273,41 +255,33 @@ public class InAppPurchase implements PurchasesUpdatedListener
public void launchBillingFlow(String identifier, final int requestCode){
purchaseRequestCode = requestCode;
List<QueryProductDetailsParams.Product> productList = new ArrayList<>();
productList.add(
QueryProductDetailsParams.Product.newBuilder()
.setProductId(identifier)
.setProductType(TYPE_SUBS)
.build());
QueryProductDetailsParams params = QueryProductDetailsParams.newBuilder()
.setProductList(productList)
.build();
billingClient.queryProductDetailsAsync(params,
(billingResult, productDetailsResult) -> {
List<ProductDetails> productDetailsList = productDetailsResult.getProductDetailsList();
List<String> skuList = new ArrayList<>();
skuList.add(identifier);
SkuDetailsParams.Builder params = SkuDetailsParams.newBuilder();
params.setSkusList(skuList).setType(TYPE_SUBS);
billingClient.querySkuDetailsAsync(params.build(),
new SkuDetailsResponseListener() {
@Override
public void onSkuDetailsResponse(BillingResult billingResult, List<SkuDetails> skuDetailsList) {
if (billingResult.getResponseCode() != RESULT_OK) {
QLog.e(TAG, "Unable to launch Google Play purchase screen");
Log.e(TAG, "Unable to launch Google Play purchase screen");
String errorString = getErrorString(requestCode);
purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString);
return;
}
else if (productDetailsList == null || productDetailsList.isEmpty()){
else if (skuDetailsList == null){
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Data missing from result");
return;
}
ProductDetails productDetails = productDetailsList.get(0);
BillingFlowParams.ProductDetailsParams productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build();
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(java.util.Arrays.asList(productDetailsParams))
.setSkuDetails(skuDetailsList.get(0))
.build();
//Results will be delivered to onPurchasesUpdated
billingClient.launchBillingFlow((Activity) m_context, purchaseParams);
}
});
}
@@ -317,7 +291,7 @@ public class InAppPurchase implements PurchasesUpdatedListener
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() != RESULT_OK) {
QLog.e(TAG, "Unable to consume purchase. Response code: " + billingResult.getResponseCode());
Log.e(TAG, "Unable to consume purchase. Response code: " + billingResult.getResponseCode());
}
}
};
@@ -338,7 +312,7 @@ public class InAppPurchase implements PurchasesUpdatedListener
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
if (billingResult.getResponseCode() != RESULT_OK){
QLog.e(TAG, "Unable to acknowledge purchase. Response code: " + billingResult.getResponseCode());
Log.e(TAG, "Unable to acknowledge purchase. Response code: " + billingResult.getResponseCode());
}
}
};
@@ -347,21 +321,18 @@ public class InAppPurchase implements PurchasesUpdatedListener
public void queryPurchasedProducts(final List<String> productIdList) {
QueryPurchasesParams queryPurchasesParams = QueryPurchasesParams.newBuilder()
.setProductType(TYPE_SUBS)
.build();
billingClient.queryPurchasesAsync(queryPurchasesParams, new PurchasesResponseListener() {
billingClient.queryPurchasesAsync(TYPE_INAPP, new PurchasesResponseListener() {
@Override
public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> list) {
for (Purchase purchase : list) {
if (productIdList.contains(purchase.getProducts().get(0))) {
if (productIdList.contains(purchase.getSkus().get(0))) {
registerPurchased(m_nativePointer,
purchase.getProducts().get(0),
purchase.getSkus().get(0),
purchase.getSignature(),
purchase.getOriginalJson(),
purchase.getPurchaseToken(),
"", // getDeveloperPayload() is deprecated
purchase.getDeveloperPayload(),
purchase.getPurchaseTime());
}
}

View File

@@ -17,7 +17,7 @@
package org.qtproject.qt.android.purchasing;
import android.text.TextUtils;
import org.cagnulen.qdomyoszwift.QLog;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
@@ -58,7 +58,7 @@ public class Security {
*/
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (signedData == null) {
QLog.e(TAG, "data is null");
Log.e(TAG, "data is null");
return false;
}
@@ -67,7 +67,7 @@ public class Security {
PublicKey key = Security.generatePublicKey(base64PublicKey);
verified = Security.verify(key, signedData, signature);
if (!verified) {
QLog.w(TAG, "signature does not match data.");
Log.w(TAG, "signature does not match data.");
return false;
}
}
@@ -89,10 +89,10 @@ public class Security {
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
QLog.e(TAG, "Invalid key specification.");
Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Base64DecoderException e) {
QLog.e(TAG, "Base64 decoding failed.");
Log.e(TAG, "Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
@@ -113,18 +113,18 @@ public class Security {
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
QLog.e(TAG, "Signature verification failed.");
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
QLog.e(TAG, "NoSuchAlgorithmException.");
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
QLog.e(TAG, "Invalid key specification.");
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
QLog.e(TAG, "Signature exception.");
Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
QLog.e(TAG, "Base64 decoding failed.");
Log.e(TAG, "Base64 decoding failed.");
}
return false;
}

View File

@@ -1,42 +0,0 @@
#include <QDebug>
#ifdef Q_OS_ANDROID
#include <QAndroidJniObject>
#include <jni.h>
extern "C" JNIEXPORT void JNICALL
Java_org_cagnulen_qdomyoszwift_QLog_sendToQt(JNIEnv *env, jclass clazz,
jint level, jstring tag, jstring message) {
const char *tagChars = env->GetStringUTFChars(tag, nullptr);
const char *msgChars = env->GetStringUTFChars(message, nullptr);
QString tagStr = QString::fromUtf8(tagChars);
QString msgStr = QString::fromUtf8(msgChars);
// Converti i livelli di log Android in livelli Qt
switch (level) {
case 2: // VERBOSE
qDebug() << "[VERBOSE:" << tagStr << "]" << msgStr;
break;
case 3: // DEBUG
qDebug() << "[DEBUG:" << tagStr << "]" << msgStr;
break;
case 4: // INFO
qInfo() << "[INFO:" << tagStr << "]" << msgStr;
break;
case 5: // WARN
qWarning() << "[WARN:" << tagStr << "]" << msgStr;
break;
case 6: // ERROR
qCritical() << "[ERROR:" << tagStr << "]" << msgStr;
break;
case 7: // ASSERT/WTF
qCritical() << "[ASSERT:" << tagStr << "]" << msgStr;
break;
default:
qDebug() << "[LOG:" << tagStr << "(" << level << ")]" << msgStr;
}
env->ReleaseStringUTFChars(tag, tagChars);
env->ReleaseStringUTFChars(message, msgChars);
}
#endif

View File

@@ -1,7 +1,6 @@
#include "characteristicnotifier2acd.h"
#include "devices/treadmill.h"
#include <qmath.h>
#include <QTime> // Include QTime for Bike->elapsedTime()
CharacteristicNotifier2ACD::CharacteristicNotifier2ACD(bluetoothdevice *Bike, QObject *parent)
: CharacteristicNotifier(0x2acd, parent), Bike(Bike) {}
@@ -10,8 +9,7 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
if (dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL) {
value.append(0x0C); // Inclination available and distance for peloton
//value.append((char)0x01); // heart rate available
value.append((char)0x05); // HeartRate(8) | ElapsedTime(10)
value.append((char)0x01); // heart rate available
uint16_t normalizeSpeed = (uint16_t)qRound(Bike->currentSpeed().value() * 100);
char a = (normalizeSpeed >> 8) & 0XFF;
@@ -63,18 +61,6 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
rampBytes.append(b);
rampBytes.append(a);
// Get session elapsed time - makes Runna calculations work
QTime sessionElapsedTime = Bike->elapsedTime();
double elapsed_time_seconds =
(double)sessionElapsedTime.hour() * 3600.0 +
(double)sessionElapsedTime.minute() * 60.0 +
(double)sessionElapsedTime.second() +
(double)sessionElapsedTime.msec() / 1000.0;
uint16_t ftms_elapsed_time_field = (uint16_t)qRound(elapsed_time_seconds);
QByteArray elapsedBytes;
elapsedBytes.append(static_cast<char>(ftms_elapsed_time_field & 0xFF));
elapsedBytes.append(static_cast<char>((ftms_elapsed_time_field >> 8) & 0xFF));
value.append(speedBytes); // Actual value.
value.append(distanceBytes); // Actual value.
@@ -84,9 +70,6 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
value.append(rampBytes); // ramp angle
value.append(Bike->currentHeart().value()); // current heart rate
value.append(elapsedBytes); // Elapsed Time
return CN_OK;
} else
return CN_INVALID;

View File

@@ -1,6 +1,6 @@
#include "bike.h"
#include "characteristicwriteprocessor0003.h"
#include <QDebug>
#include "bike.h"
CharacteristicWriteProcessor0003::CharacteristicWriteProcessor0003(double bikeResistanceGain,
int8_t bikeResistanceOffset,
@@ -30,13 +30,6 @@ CharacteristicWriteProcessor0003::VarintResult CharacteristicWriteProcessor0003:
return {result, bytesRead};
}
double CharacteristicWriteProcessor0003::currentGear() {
if(zwiftGearReceived)
return currentZwiftGear;
else
return ((bike*)Bike)->gears();
}
qint32 CharacteristicWriteProcessor0003::decodeSInt(const QByteArray& bytes) {
if (static_cast<quint8>(bytes.at(0)) != 0x22) {
qFatal("Invalid field header");
@@ -109,10 +102,9 @@ void CharacteristicWriteProcessor0003::handleZwiftGear(const QByteArray &array)
for (int i = 0; i < g - currentZwiftGear; ++i) {
((bike*)Bike)->gearUp();
}
}
}
currentZwiftGear = g;
}
currentZwiftGear = g;
zwiftGearReceived = true;
}
QByteArray CharacteristicWriteProcessor0003::encodeHubRidingData(

View File

@@ -23,8 +23,6 @@ public:
uint32_t unknown1,
uint32_t unknown2);
static uint32_t calculateUnknown1(uint16_t power);
void handleZwiftGear(const QByteArray &array);
double currentGear();
private:
@@ -34,9 +32,9 @@ private:
};
VarintResult decodeVarint(const QByteArray& bytes, int startIndex);
qint32 decodeSInt(const QByteArray& bytes);
qint32 decodeSInt(const QByteArray& bytes);
void handleZwiftGear(const QByteArray &array);
int currentZwiftGear = 8;
bool zwiftGearReceived = false;
signals:
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);

View File

@@ -1,187 +0,0 @@
#include "android_antbike.h"
#include "virtualdevices/virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
#include <math.h>
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#include <QLowEnergyConnectionParameters>
#endif
#include <chrono>
using namespace std::chrono_literals;
android_antbike::android_antbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->noVirtualDevice = noVirtualDevice;
initDone = false;
connect(refresh, &QTimer::timeout, this, &android_antbike::update);
refresh->start(200ms);
}
void android_antbike::update() {
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
#ifdef Q_OS_ANDROID
Heart = KeepAwakeHelper::antObject(true)->callMethod<int>("getHeart", "()I");
Cadence = KeepAwakeHelper::antObject(true)->callMethod<int>("getBikeCadence", "()I");
m_watt = KeepAwakeHelper::antObject(true)->callMethod<int>("getBikePower", "()I");
if (settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = metric::calculateSpeedFromPower(
m_watt.value(), 0, Speed.value(), fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0),
speedLimit());
} else {
Speed = KeepAwakeHelper::antObject(true)->callMethod<double>("getBikeSpeed", "()D");
}
bool bikeConnected = KeepAwakeHelper::antObject(true)->callMethod<jboolean>("isBikeConnected", "()Z");
qDebug() << QStringLiteral("Current ANT Cadence: ") << QString::number(Cadence.value());
qDebug() << QStringLiteral("Current ANT Speed: ") << QString::number(Speed.value());
qDebug() << QStringLiteral("Current ANT Power: ") << QString::number(m_watt.value());
qDebug() << QStringLiteral("Current ANT Heart: ") << QString::number(Heart.value());
qDebug() << QStringLiteral("ANT Bike Connected: ") << bikeConnected;
#endif
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
if (requestInclination != -100) {
Inclination = requestInclination;
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
requestInclination = -100;
}
update_metrics(false, watts());
Distance += ((Speed.value() / (double)3600.0) /
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !this->hasVirtualDevice() && !noVirtualDevice
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
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);
connect(virtualBike, &virtualbike::changeInclination, this, &android_antbike::changeInclinationRequested);
connect(virtualBike, &virtualbike::ftmsCharacteristicChanged, this, &android_antbike::ftmsCharacteristicChanged);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
}
}
if (!firstStateChanged)
emit connectedAndDiscovered();
firstStateChanged = 1;
// ********************************************************************************************************
if (!noVirtualDevice) {
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
Heart = (uint8_t)KeepAwakeHelper::heart();
debug("Current Heart: " + QString::number(Heart.value()));
}
#endif
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
update_hr_from_external();
}
#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
}
if (Heart.value()) {
static double lastKcal = 0;
if (KCal.value() < 0) // if the user pressed stop, the KCAL resets the accumulator
lastKcal = abs(KCal.value());
KCal = metric::calculateKCalfromHR(Heart.average(), elapsed.value()) + lastKcal;
}
if (requestResistance != -1 && requestResistance != currentResistance().value()) {
Resistance = requestResistance;
m_pelotonResistance = requestResistance;
}
}
void android_antbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
QByteArray b = newValue;
qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
}
void android_antbike::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)
percentage = 0;
changeInclination(grade, percentage);
}
uint16_t android_antbike::wattsFromResistance(double resistance) {
return _ergTable.estimateWattage(Cadence.value(), resistance);
}
resistance_t android_antbike::resistanceFromPowerRequest(uint16_t power) {
//QSettings settings;
//bool toorx_srx_3500 = settings.value(QZSettings::toorx_srx_3500, QZSettings::default_toorx_srx_3500).toBool();
/*if(toorx_srx_3500)*/ {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();
if (Cadence.value() == 0)
return 1;
for (resistance_t i = 1; i < maxResistance(); i++) {
if (wattsFromResistance(i) <= power && wattsFromResistance(i + 1) >= power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << wattsFromResistance(i)
<< wattsFromResistance(i + 1) << power;
return i;
}
}
if (power < wattsFromResistance(1))
return 1;
else
return maxResistance();
} /*else {
return power / 10;
}*/
}
uint16_t android_antbike::watts() { return m_watt.value(); }
bool android_antbike::connected() { return true; }

View File

@@ -1,81 +0,0 @@
#ifndef ANDROID_ANTBIKE_H
#define ANDROID_ANTBIKE_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#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 <QObject>
#include <QString>
#include "devices/bike.h"
#include "ergtable.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class android_antbike : public bike {
Q_OBJECT
public:
android_antbike(bool noWriteResistance, bool noHeartService, bool noVirtualDevice);
bool connected() override;
uint16_t watts() override;
resistance_t maxResistance() override { return 100; }
resistance_t resistanceFromPowerRequest(uint16_t power) override;
private:
QTimer *refresh;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
QDateTime lastGoodCadence = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
bool noVirtualDevice = false;
uint16_t oldLastCrankEventTime = 0;
uint16_t oldCrankRevs = 0;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
uint16_t wattsFromResistance(double resistance);
signals:
void disconnected();
void debug(QString string);
private slots:
void changeInclinationRequested(double grade, double percentage);
void update();
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
};
#endif // ANDROID_ANTBIKE_H

View File

@@ -57,13 +57,7 @@ void apexbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
}
writeBuffer = new QByteArray((const char *)data, data_len);
if (gattWriteCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer,
QLowEnergyService::WriteWithoutResponse);
} else {
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
}
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
if (!disable_log) {
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') +

View File

@@ -62,28 +62,17 @@ void bike::changePower(int32_t power) {
return;
}
QSettings settings;
bool power_sensor = !settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"));
double erg_filter_upper =
settings.value(QZSettings::zwift_erg_filter, QZSettings::default_zwift_erg_filter).toDouble();
double erg_filter_lower =
settings.value(QZSettings::zwift_erg_filter_down, QZSettings::default_zwift_erg_filter_down).toDouble();
requestPower = power; // used by some bikes that have ERG mode builtin
if(power_sensor && ergModeSupported && m_rawWatt.value() > 0 && m_watt.value() > 0 && fabs(requestPower - m_watt.average5s()) < qMax(erg_filter_upper, erg_filter_lower)) {
qDebug() << "applying delta watt to power request m_rawWatt" << m_rawWatt.average5s() << "watt" << m_watt.average5s() << "req" << requestPower;
// the concept here is to trying to add or decrease the delta from the power sensor
requestPower += (requestPower - m_watt.average5s());
}
QSettings settings;
bool force_resistance =
settings.value(QZSettings::virtualbike_forceresistance, QZSettings::default_virtualbike_forceresistance)
.toBool();
// bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool(); //Not used
// anywhere in code
double erg_filter_upper =
settings.value(QZSettings::zwift_erg_filter, QZSettings::default_zwift_erg_filter).toDouble();
double erg_filter_lower =
settings.value(QZSettings::zwift_erg_filter_down, QZSettings::default_zwift_erg_filter_down).toDouble();
double deltaDown = wattsMetric().value() - ((double)power);
double deltaUp = ((double)power) - wattsMetric().value();
qDebug() << QStringLiteral("filter ") + QString::number(deltaUp) + " " + QString::number(deltaDown) + " " +
@@ -187,7 +176,6 @@ void bike::clearStats() {
m_jouls.clear(true);
elevationAcc = 0;
m_watt.clear(false);
m_rawWatt.clear(false);
WeightLoss.clear(false);
RequestedPelotonResistance.clear(false);
@@ -215,7 +203,6 @@ void bike::setPaused(bool p) {
Heart.setPaused(p);
m_jouls.setPaused(p);
m_watt.setPaused(p);
m_rawWatt.setPaused(p);
WeightLoss.setPaused(p);
m_pelotonResistance.setPaused(p);
Cadence.setPaused(p);
@@ -241,7 +228,6 @@ void bike::setLap() {
Heart.setLap(false);
m_jouls.setLap(true);
m_watt.setLap(false);
m_rawWatt.setLap(false);
WeightLoss.setLap(false);
WattKg.setLap(false);

View File

@@ -109,8 +109,6 @@ void bluetooth::finished() {
QSettings settings;
bool antbike =
settings.value(QZSettings::antbike, QZSettings::default_antbike).toBool();
bool android_antbike =
settings.value(QZSettings::android_antbike, QZSettings::default_android_antbike).toBool();
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();
@@ -123,7 +121,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 || android_antbike) {
if (!nordictrack_2950_ip.isEmpty() || !tdf_10_ip.isEmpty() || fake_bike || fakedevice_elliptical || fakedevice_rower || fakedevice_treadmill || !proform_elliptical_ip.isEmpty() || antbike) {
// faking a bluetooth device
qDebug() << "faking a bluetooth device for nordictrack_2950_ip";
deviceDiscovered(QBluetoothDeviceInfo());
@@ -464,8 +462,6 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
settings.value(QZSettings::proformtreadmillip, QZSettings::default_proformtreadmillip).toString();
bool antbike_setting =
settings.value(QZSettings::antbike, QZSettings::default_antbike).toBool();
bool android_antbike_setting =
settings.value(QZSettings::android_antbike, QZSettings::default_android_antbike).toBool();
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();
@@ -821,19 +817,6 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(antBike);
} else if (android_antbike_setting && !android_antBike) {
this->stopDiscovery();
android_antBike = new android_antbike(noWriteResistance, noHeartService, false);
emit deviceConnected(b);
connect(android_antBike, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
connect(android_antBike, &android_antbike::debug, this, &bluetooth::debug);
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(android_antBike);
} else if (!proformtreadmillip.isEmpty() && !proformWifiTreadmill) {
this->stopDiscovery();
proformWifiTreadmill = new proformwifitreadmill(noWriteResistance, noHeartService, bikeResistanceOffset,
@@ -1351,27 +1334,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(soleF80);
} else if (b.name().toUpper().startsWith(QStringLiteral("SPERAX_RM01")) && !speraXTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
speraXTreadmill = new speraxtreadmill(this->pollDeviceTime, noConsole, noHeartService);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
stateFileRead();
#endif
emit deviceConnected(b);
connect(speraXTreadmill, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(speraXTreadmill, SIGNAL(disconnected()), this, SLOT(restart()));
connect(speraXTreadmill, &speraxtreadmill::debug, this, &bluetooth::debug);
connect(speraXTreadmill, &speraxtreadmill::speedChanged, this, &bluetooth::speedChanged);
connect(speraXTreadmill, &speraxtreadmill::inclinationChanged, this, &bluetooth::inclinationChanged);
speraXTreadmill->deviceDiscovered(b);
connect(this, &bluetooth::searchingStop, speraXTreadmill, &speraxtreadmill::searchingStop);
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(speraXTreadmill);
} else if ((b.name().toUpper().startsWith(QStringLiteral("LF")) && (b.name().length() == 18 || b.name().length() == 15)) &&
} else if ((b.name().toUpper().startsWith(QStringLiteral("LF")) && b.name().length() == 18) &&
!lifefitnessTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1433,27 +1396,22 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("YS_T1MPLUST")) ||
b.name().toUpper().startsWith(QStringLiteral("YPOO-MINI PRO-")) ||
b.name().toUpper().startsWith(QStringLiteral("BFX_T9_")) ||
(b.name().toUpper().startsWith("3G PRO ")) ||
(b.name().toUpper().startsWith("3G ELITE ")) ||
b.name().toUpper().startsWith(QStringLiteral("AB300S-")) ||
b.name().toUpper().startsWith(QStringLiteral("TF04-")) || // Sport Synology Z5 Treadmill #2415
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && !b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) || // FIT-1596 and sports tech f37s treadmill #2412
b.name().toUpper().startsWith(QStringLiteral("LJJ-")) || // LJJ-02351A
b.name().toUpper().startsWith(QStringLiteral("WLT-EP-")) || // Flow elliptical
(b.name().toUpper().startsWith("SCHWINN 810")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-MC")) ||
b.name().toUpper().startsWith(QStringLiteral("ANPIUS-")) ||
b.name().toUpper().startsWith(QStringLiteral("SPERAX_RM-01")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-MC")) ||
b.name().toUpper().startsWith(QStringLiteral("ANPIUS-")) ||
(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) || // Kingsmith WalkingPad Z1
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("TT8")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith(QStringLiteral("ST90")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith(QStringLiteral("XT485")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
b.name().toUpper().startsWith(QStringLiteral("MOBVOI TM")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("MOBVOI WMTP")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("LB600")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("TUNTURI T80-")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("TUNTURI T90-")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("ASSAULTRUNNER")) || // FTMS
@@ -1560,8 +1518,6 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((b.name().toUpper().startsWith("TACX ") ||
b.name().toUpper().startsWith(QStringLiteral("THINK X")) ||
b.name().toUpper().startsWith(QStringLiteral("THINK-")) ||
b.name().toUpper().startsWith(QStringLiteral("THINK_")) ||
b.name().toUpper().startsWith(QStringLiteral("53997-")) ||
(b.name().toUpper().startsWith("VANRYSEL-HT")) ||
b.address() == QBluetoothAddress("C1:14:D9:9C:FB:01") || // specific TACX NEO 2 #1707
(b.name().toUpper().startsWith("TACX SMART BIKE"))) &&
@@ -1580,8 +1536,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// connect(tacxneo2Bike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
tacxneo2Bike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(tacxneo2Bike);
} else if (((b.name().toUpper().startsWith("INDOORCYCLE")) ||
((b.name().toUpper().startsWith("MAGNUS ")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)))) &&
} else if ((b.name().toUpper().startsWith("INDOORCYCLE")) &&
!cycleopsphantomBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1627,8 +1582,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("DS25-")) || // Bodytone DS25
(b.name().toUpper().startsWith("SCHWINN 510T")) ||
(b.name().toUpper().startsWith("3G CARDIO ")) ||
(b.name().toUpper().startsWith("ZWIFT HUB")) ||
((b.name().toUpper().startsWith("MAGNUS ")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith("ZWIFT HUB")) || (b.name().toUpper().startsWith("MAGNUS ")) ||
(b.name().toUpper().startsWith("HAMMER ") && !power_as_bike && !saris_trainer) || // HAMMER 64123
(b.name().toUpper().startsWith("FLXCY-")) || // Pro FlexBike
(b.name().toUpper().startsWith("QB-WC01")) || // Nexgim QB-C01 smart bike
@@ -1674,7 +1628,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("FELVON V2")) ||
(b.name().toUpper().startsWith("JUSTO")) ||
(b.name().toUpper().startsWith("MYCYCLE ")) ||
(b.name().toUpper().startsWith("T2 ")) ||
(b.name().toUpper().startsWith("T2 ")) ||
(b.name().toUpper().startsWith("DR") && b.name().length() == 2) ||
(b.name().toUpper().startsWith("RC-MAX-")) ||
(b.name().toUpper().startsWith("TPS-SPBIKE-2.0")) ||
(b.name().toUpper().startsWith("NEO BIKE SMART")) ||
@@ -1688,23 +1643,17 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("POOBOO")) ||
(b.name().toUpper().startsWith("ZYCLE ZPRO")) ||
(b.name().toUpper().startsWith("SM720I")) ||
(b.name().toUpper().startsWith("H9115 LYON")) ||
(b.name().toUpper().startsWith("AVANTI")) ||
(b.name().toUpper().startsWith("T300P_")) ||
(b.name().toUpper().startsWith("T200_")) ||
(b.name().toUpper().startsWith("BZ9110 ")) ||
(b.name().toUpper().startsWith("CFC") && b.name().length() == 14) || // CFC31231004349
(b.name().toUpper().startsWith("TITAN 7000")) ||
(b.name().toUpper().startsWith("LYDSTO")) ||
(b.name().toUpper().startsWith("CYCLO_")) ||
(b.name().toUpper().startsWith("SL010-")) ||
(b.name().toUpper().startsWith("EXPERT-SX9")) ||
(b.name().toUpper().startsWith("LCR")) ||
(b.name().toUpper().startsWith("MRK-S26S-")) ||
(b.name().toUpper().startsWith("ROBX")) ||
(b.name().toUpper().startsWith("XCX-")) ||
(b.name().toUpper().startsWith("NEO BIKE PLUS ")) ||
(b.name().toUpper().startsWith(QStringLiteral("PM5")) && !b.name().toUpper().endsWith(QStringLiteral("SKI")) && !b.name().toUpper().endsWith(QStringLiteral("ROW"))) ||
(b.name().toUpper().startsWith("L-") && b.name().length() == 11) ||
(b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) ||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
@@ -1716,8 +1665,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("DIRETO X")) || (b.name().toUpper().startsWith("MERACH-667-")) ||
!b.name().compare(ftms_bike, Qt::CaseInsensitive) || (b.name().toUpper().startsWith("SMB1")) ||
(b.name().toUpper().startsWith("UBIKE FTMS")) || (b.name().toUpper().startsWith("INRIDE"))) &&
ftms_rower.contains(QZSettings::default_ftms_rower) &&
!ftmsBike && !ftmsRower && !snodeBike && !fitPlusBike && !stagesBike && filter) {
!ftmsBike && !snodeBike && !fitPlusBike && !stagesBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
ftmsBike = new ftmsbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
@@ -1792,10 +1740,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
((b.name().toUpper().startsWith("KU")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith("ELITETRAINER")) ||
(b.name().toUpper().startsWith("TOUR 600")) ||
(b.name().toUpper().startsWith("SMART+ #")) ||
(b.name().toUpper().startsWith(QStringLiteral("QD")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith(QStringLiteral("RM")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith(QStringLiteral("DR")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith(QStringLiteral("DFC")) && b.name().length() == 3) ||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
powerSensorName.startsWith(QStringLiteral("Disabled")))) &&
@@ -1828,7 +1773,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
smartrowRower->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(smartrowRower);
} else if ((b.name().toUpper().startsWith(QStringLiteral("PM5")) &&
b.name().toUpper().endsWith(QStringLiteral("SKI"))) &&
!b.name().toUpper().endsWith(QStringLiteral("ROW"))) &&
!concept2Skierg && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1871,28 +1816,13 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// connect(ftmsRower, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
ftmsRower->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(ftmsRower);
} else if ((b.name().toUpper().startsWith(QLatin1String("ECH-EC-SPT"))) &&
!echelonStairclimber && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
echelonStairclimber = new echelonstairclimber(this->pollDeviceTime, noConsole, noHeartService);
// stateFileRead();
emit deviceConnected(b);
connect(echelonStairclimber, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(echelonRower, SIGNAL(disconnected()), this, SLOT(restart())); connect(echelonStride,
connect(echelonStairclimber, &echelonstairclimber::debug, this, &bluetooth::debug);
connect(echelonStairclimber, &echelonstairclimber::speedChanged, this, &bluetooth::speedChanged);
connect(echelonStairclimber, &echelonstairclimber::inclinationChanged, this, &bluetooth::inclinationChanged);
echelonStairclimber->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(echelonStairclimber);
} else if ((b.name().toUpper().startsWith(QLatin1String("ECH-STRIDE")) ||
b.name().toUpper().startsWith(QLatin1String("ECH-UK-")) ||
b.name().toUpper().startsWith(QLatin1String("ECH-FR-")) ||
b.name().toUpper().startsWith(QLatin1String("STRIDE")) ||
b.name().toUpper().startsWith(QLatin1String("STRIDE6S-")) ||
b.name().toUpper().startsWith(QLatin1String("ECH-SD-SPT"))) &&
!echelonStride && !echelonStairclimber && filter) {
!echelonStride && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
echelonStride = new echelonstride(this->pollDeviceTime, noConsole, noHeartService);
@@ -1982,7 +1912,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// connect(echelonRower, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
echelonRower->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(echelonRower);
} else if (b.name().startsWith(QStringLiteral("ECH")) && !echelonRower && !echelonStride && !echelonStairclimber && !ftmsBike &&
} else if (b.name().startsWith(QStringLiteral("ECH")) && !echelonRower && !echelonStride && !ftmsBike &&
!echelonConnectSport && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -2228,21 +2158,6 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// SLOT(inclinationChanged(double)));
pafersTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(pafersTreadmill);
} else if (b.name().toUpper().startsWith(QStringLiteral("MOXY6")) && !moxy5Sensor) {
// *** SPECIAL DEVICE ****
moxy5Sensor = new moxy5sensor();
connect(moxy5Sensor, &moxy5sensor::debug, this, &bluetooth::debug);
moxy5Sensor->deviceDiscovered(b);
} else if (b.name().toUpper().startsWith(QStringLiteral("CORE ")) && !coreSensor) {
// *** SPECIAL DEVICE ****
coreSensor = new coresensor();
connect(coreSensor, &coresensor::debug, this, &bluetooth::debug);
coreSensor->deviceDiscovered(b);
connect(coreSensor, &coresensor::coreBodyTemperatureChanged, this->device(), &bluetoothdevice::coreBodyTemperature);
connect(coreSensor, &coresensor::skinTemperatureChanged, this->device(), &bluetoothdevice::skinTemperature);
connect(coreSensor, &coresensor::heatStrainIndexChanged, this->device(), &bluetoothdevice::heatStrainIndex);
} else if (b.name().toUpper().startsWith(QStringLiteral("BOWFLEX T")) && !bowflexT216Treadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -2415,10 +2330,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("BIKZU_")) ||
b.name().toUpper().startsWith(QStringLiteral("PASYOU-")) ||
b.name().toUpper().startsWith(QStringLiteral("VIRTUFIT")) ||
b.name().toUpper().startsWith(QStringLiteral("IBIKING+")) ||
((b.name().startsWith(QStringLiteral("TOORX")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOIE+")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) ||
b.name().toUpper().startsWith(QStringLiteral("IBIKING+")) ||
b.name().toUpper().startsWith(QStringLiteral("ICONSOLE+")) ||
b.name().toUpper().startsWith(QStringLiteral("VIFHTR2.1")) ||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK"))) ||
@@ -2853,8 +2768,6 @@ void bluetooth::connectedAndDiscovered() {
connect(powerSensor, &stagesbike::debug, this, &bluetooth::debug);
connect(powerSensor, &bluetoothdevice::powerChanged, this->device(), &bluetoothdevice::powerSensor);
connect(powerSensor, &bluetoothdevice::cadenceChanged, this->device(),
&bluetoothdevice::cadenceSensor);
powerSensor->deviceDiscovered(b);
} else if (device() && device()->deviceType() == bluetoothdevice::TREADMILL) {
powerSensorRun = new strydrunpowersensor(false, false, true);
@@ -2976,25 +2889,6 @@ void bluetooth::connectedAndDiscovered() {
}
}
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("SQUARE"))) && !eliteSquareController && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
eliteSquareController = new elitesquarecontroller(this->device());
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
connect(eliteSquareController, &elitesquarecontroller::debug, this, &bluetooth::debug);
connect(eliteSquareController, &elitesquarecontroller::plus, (bike*)this->device(), &bike::gearUp);
connect(eliteSquareController, &elitesquarecontroller::minus, (bike*)this->device(), &bike::gearDown);
eliteSquareController->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Elite Square Connected!");
break;
}
}
}
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
@@ -3036,13 +2930,12 @@ void bluetooth::connectedAndDiscovered() {
QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
"activity", "()Landroid/app/Activity;");
KeepAwakeHelper::antObject(true)->callMethod<void>(
"antStart", "(Landroid/app/Activity;ZZZZZ)V", activity.object<jobject>(),
"antStart", "(Landroid/app/Activity;ZZZZ)V", activity.object<jobject>(),
settings.value(QZSettings::ant_cadence, QZSettings::default_ant_cadence).toBool(),
settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool(),
settings.value(QZSettings::ant_garmin, QZSettings::default_ant_garmin).toBool(),
device()->deviceType() == bluetoothdevice::TREADMILL ||
device()->deviceType() == bluetoothdevice::ELLIPTICAL,
settings.value(QZSettings::android_antbike, QZSettings::default_android_antbike).toBool());
device()->deviceType() == bluetoothdevice::ELLIPTICAL);
}
if (settings.value(QZSettings::android_notification, QZSettings::default_android_notification).toBool()) {
@@ -3190,11 +3083,6 @@ void bluetooth::restart() {
delete horizonTreadmill;
horizonTreadmill = nullptr;
}
if (speraXTreadmill) {
delete speraXTreadmill;
speraXTreadmill = nullptr;
}
if (lifefitnessTreadmill) {
delete lifefitnessTreadmill;
@@ -3300,11 +3188,6 @@ void bluetooth::restart() {
delete antBike;
antBike = nullptr;
}
if (android_antBike) {
delete android_antBike;
android_antBike = nullptr;
}
if (proformTelnetBike) {
delete proformTelnetBike;
@@ -3461,11 +3344,6 @@ void bluetooth::restart() {
delete echelonStride;
echelonStride = nullptr;
}
if (echelonStairclimber) {
delete echelonStairclimber;
echelonStairclimber = nullptr;
}
if (octaneTreadmill) {
delete octaneTreadmill;
@@ -3800,8 +3678,6 @@ bluetoothdevice *bluetooth::device() {
return proformWifiTreadmill;
} else if (antBike) {
return antBike;
} else if (android_antBike) {
return android_antBike;
} else if (nordictrackifitadbTreadmill) {
return nordictrackifitadbTreadmill;
} else if (nordictrackifitadbBike) {
@@ -3860,8 +3736,6 @@ bluetoothdevice *bluetooth::device() {
return horizonTreadmill;
} else if (lifefitnessTreadmill) {
return lifefitnessTreadmill;
} else if (speraXTreadmill) {
return speraXTreadmill;
} else if (technogymmyrunTreadmill) {
return technogymmyrunTreadmill;
#ifndef Q_OS_IOS
@@ -3886,8 +3760,6 @@ bluetoothdevice *bluetooth::device() {
return echelonRower;
} else if (echelonStride) {
return echelonStride;
} else if (echelonStairclimber) {
return echelonStairclimber;
} else if (octaneTreadmill) {
return octaneTreadmill;
} else if (ziproTreadmill) {

View File

@@ -21,9 +21,7 @@
#include "qzsettings.h"
#include "devices/activiotreadmill/activiotreadmill.h"
#include "devices/speraxtreadmill/speraxtreadmill.h"
#include "devices/antbike/antbike.h"
#include "devices/android_antbike/android_antbike.h"
#include "devices/apexbike/apexbike.h"
#include "devices/bhfitnesselliptical/bhfitnesselliptical.h"
#include "devices/bkoolbike/bkoolbike.h"
@@ -31,7 +29,6 @@
#include "devices/bowflext216treadmill/bowflext216treadmill.h"
#include "devices/bowflextreadmill/bowflextreadmill.h"
#include "devices/chronobike/chronobike.h"
#include "devices/coresensor/coresensor.h"
#ifndef Q_OS_IOS
#include "devices/computrainerbike/computrainerbike.h"
#include "devices/csaferower/csaferower.h"
@@ -49,10 +46,8 @@
#include "devices/echelonconnectsport/echelonconnectsport.h"
#include "devices/echelonrower/echelonrower.h"
#include "devices/echelonstairclimber/echelonstairclimber.h"
#include "devices/eliteariafan/eliteariafan.h"
#include "devices/eliterizer/eliterizer.h"
#include "devices/elitesquarecontroller/elitesquarecontroller.h"
#include "devices/elitesterzosmart/elitesterzosmart.h"
#include "devices/eslinkertreadmill/eslinkertreadmill.h"
#include "devices/fakebike/fakebike.h"
@@ -82,7 +77,6 @@
#include "devices/m3ibike/m3ibike.h"
#include "devices/mcfbike/mcfbike.h"
#include "devices/mepanelbike/mepanelbike.h"
#include "devices/moxy5sensor/moxy5sensor.h"
#include "devices/nautilusbike/nautilusbike.h"
#include "devices/nautiluselliptical/nautiluselliptical.h"
#include "devices/nautilustreadmill/nautilustreadmill.h"
@@ -179,13 +173,11 @@ class bluetooth : public QObject, public SignalHandler {
QFile *debugCommsLog = nullptr;
QBluetoothDeviceDiscoveryAgent *discoveryAgent = nullptr;
antbike *antBike = nullptr;
android_antbike *android_antBike = nullptr;
apexbike *apexBike = nullptr;
bkoolbike *bkoolBike = nullptr;
bhfitnesselliptical *bhFitnessElliptical = nullptr;
bowflextreadmill *bowflexTreadmill = nullptr;
bowflext216treadmill *bowflexT216Treadmill = nullptr;
coresensor* coreSensor = nullptr;
crossrope *crossRope = nullptr;
fitshowtreadmill *fitshowTreadmill = nullptr;
focustreadmill *focusTreadmill = nullptr;
@@ -207,7 +199,6 @@ class bluetooth : public QObject, public SignalHandler {
trxappgateusbtreadmill *trxappgateusb = nullptr;
spirittreadmill *spiritTreadmill = nullptr;
activiotreadmill *activioTreadmill = nullptr;
speraxtreadmill *speraXTreadmill = nullptr;
nautilusbike *nautilusBike = nullptr;
nautiluselliptical *nautilusElliptical = nullptr;
nautilustreadmill *nautilusTreadmill = nullptr;
@@ -217,7 +208,6 @@ class bluetooth : public QObject, public SignalHandler {
echelonconnectsport *echelonConnectSport = nullptr;
yesoulbike *yesoulBike = nullptr;
flywheelbike *flywheelBike = nullptr;
moxy5sensor *moxy5Sensor = nullptr;
nordictrackelliptical *nordictrackElliptical = nullptr;
nordictrackifitadbtreadmill *nordictrackifitadbTreadmill = nullptr;
nordictrackifitadbbike *nordictrackifitadbBike = nullptr;
@@ -266,7 +256,6 @@ class bluetooth : public QObject, public SignalHandler {
ftmsrower *ftmsRower = nullptr;
smartrowrower *smartrowRower = nullptr;
echelonstride *echelonStride = nullptr;
echelonstairclimber *echelonStairclimber = nullptr;
lifefitnesstreadmill *lifefitnessTreadmill = nullptr;
lifespantreadmill *lifespanTreadmill = nullptr;
keepbike *keepBike = nullptr;
@@ -303,7 +292,6 @@ class bluetooth : public QObject, public SignalHandler {
QList<zwiftclickremote* > zwiftPlayDevice;
zwiftclickremote* zwiftClickRemote = nullptr;
sramaxscontroller* sramAXSController = nullptr;
elitesquarecontroller* eliteSquareController = nullptr;
QString filterDevice = QLatin1String("");
bool testResistance = false;

View File

@@ -108,7 +108,6 @@ QTime bluetoothdevice::maxPace() {
double bluetoothdevice::odometerFromStartup() { return Distance.valueRaw(); }
double bluetoothdevice::odometer() { return Distance.value(); }
double bluetoothdevice::lapOdometer() { return Distance.lapValue(); }
metric bluetoothdevice::calories() { return KCal; }
metric bluetoothdevice::jouls() { return m_jouls; }
uint8_t bluetoothdevice::fanSpeed() { return FanSpeed; };
@@ -133,9 +132,6 @@ bool bluetoothdevice::changeFanSpeed(uint8_t speed) {
bool bluetoothdevice::connected() { return false; }
metric bluetoothdevice::elevationGain() { return elevationAcc; }
void bluetoothdevice::heartRate(uint8_t heart) { Heart.setValue(heart); }
void bluetoothdevice::coreBodyTemperature(double coreBodyTemperature) { CoreBodyTemperature.setValue(coreBodyTemperature); }
void bluetoothdevice::skinTemperature(double skinTemperature) { SkinTemperature.setValue(skinTemperature); }
void bluetoothdevice::heatStrainIndex(double heatStrainIndex) { HeatStrainIndex.setValue(heatStrainIndex); }
void bluetoothdevice::disconnectBluetooth() {
if (m_control) {
m_control->disconnectFromDevice();
@@ -285,15 +281,11 @@ void bluetoothdevice::clearStats() {
m_jouls.clear(true);
elevationAcc = 0;
m_watt.clear(false);
m_rawWatt.clear(false);
WeightLoss.clear(false);
WattKg.clear(false);
Cadence.clear(false);
for(int i=0; i<maxHeartZone(); i++) {
hrZonesSeconds[i].clear(false);
}
for(int i=0; i<maxHeatZone(); i++) {
heatZonesSeconds[i].clear(false);
}
}
@@ -309,15 +301,11 @@ void bluetoothdevice::setPaused(bool p) {
Heart.setPaused(p);
m_jouls.setPaused(p);
m_watt.setPaused(p);
m_rawWatt.setPaused(p);
WeightLoss.setPaused(p);
WattKg.setPaused(p);
Cadence.setPaused(p);
for(int i=0; i<maxHeartZone(); i++) {
hrZonesSeconds[i].setPaused(p);
}
for(int i=0; i<maxHeatZone(); i++) {
heatZonesSeconds[i].setPaused(p);
}
}
@@ -332,15 +320,11 @@ void bluetoothdevice::setLap() {
Heart.setLap(false);
m_jouls.setLap(true);
m_watt.setLap(false);
m_rawWatt.setLap(false);
WeightLoss.setLap(false);
WattKg.setLap(false);
Cadence.setLap(false);
for(int i=0; i<maxHeartZone(); i++) {
hrZonesSeconds[i].setLap(false);
}
for(int i=0; i<maxHeatZone(); i++) {
heatZonesSeconds[i].setLap(false);
}
}
@@ -505,39 +489,9 @@ void bluetoothdevice::setHeartZone(double hz) {
}
}
void bluetoothdevice::setHeatZone(double heatStrainIndex) {
// Determine heat zone based on Heat Strain Index values
uint8_t zone;
if (heatStrainIndex >= 0 && heatStrainIndex <= 1.99) {
zone = 1;
} else if (heatStrainIndex < 3.0) {
zone = 2;
} else if (heatStrainIndex < 7.0) {
zone = 3;
} else {
zone = 4;
}
HeatZone = zone;
if(isPaused() == false && heatStrainIndex > 0) {
// Convert to 0-based index for array access
uint8_t zoneIndex = zone - 1;
if(zoneIndex < maxHeatZone()) {
heatZonesSeconds[zoneIndex].setValue(heatZonesSeconds[zoneIndex].value() + 1);
}
}
}
uint32_t bluetoothdevice::secondsForHeartZone(uint8_t zone) {
if(zone < maxHeartZone()) {
return hrZonesSeconds[zone].value();
}
return 0;
}
uint32_t bluetoothdevice::secondsForHeatZone(uint8_t zone) {
if(zone < maxHeatZone()) {
return heatZonesSeconds[zone].value();
}
return 0;
}

View File

@@ -146,11 +146,6 @@ class bluetoothdevice : public QObject {
*/
virtual QTime lapElapsedTime();
/**
* @brief lapOdometer Gets the distance elapsed on the current lap.
*/
virtual double lapOdometer();
/**
* @brief connected Gets a value to indicate if the device is connected.
*/
@@ -370,24 +365,6 @@ class bluetoothdevice : public QObject {
*/
uint32_t secondsForHeartZone(uint8_t zone);
/**
* @brief currentHeatZone Gets a metric object to get or set the current heat zone. Units: depends on
* implementation (based on Heat Strain Index: Zone 1: 0-1.99, Zone 2: 2-2.99, Zone 3: 3-6.99, Zone 4: 7+)
*/
metric currentHeatZone() { return HeatZone; }
/**
* @brief maxHeatZone Gets the maximum number of heat zones.
*/
uint8_t maxHeatZone() { return maxheatzone; }
/**
* @brief secondsForHeatZone Gets the number of seconds in the current heat zone.
*
* @param zone The heat zone.
*/
uint32_t secondsForHeatZone(uint8_t zone);
/**
* @brief currentPowerZone Gets a metric object to get or set the current power zome. Units: depends on
* implementation.
@@ -422,13 +399,6 @@ class bluetoothdevice : public QObject {
*/
void setHeartZone(double hz);
/**
* @brief setHeatZone Set the current heat zone based on Heat Strain Index.
* This is equivalent to currentHeatZone().setvalue(hz)
* @param heatStrainIndex The heat strain index to determine zone. Unit: depends on implementation.
*/
void setHeatZone(double heatStrainIndex);
/**
* @brief setPowerZone Set the current power zone.
* This is equivalent to currentPowerZone().setvalue(pz)
@@ -443,7 +413,7 @@ class bluetoothdevice : public QObject {
*/
void setTargetPowerZone(double pz) { TargetPowerZone = pz; }
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL, JUMPROPE, STAIRCLIMBER };
enum BLUETOOTH_TYPE { UNKNOWN = 0, TREADMILL, BIKE, ROWING, ELLIPTICAL, JUMPROPE };
enum WORKOUT_EVENT_STATE { STARTED = 0, PAUSED = 1, RESUMED = 2, STOPPED = 3 };
/**
@@ -468,11 +438,6 @@ class bluetoothdevice : public QObject {
*/
virtual resistance_t maxResistance();
// Metrics for core temperature data
metric CoreBodyTemperature; // Core body temperature in °C or °F
metric SkinTemperature; // Skin temperature in °C or °F
metric HeatStrainIndex; // Heat Strain Index (0-25.4, scaled by 10)
public Q_SLOTS:
virtual void start();
virtual void stop(bool pause);
@@ -480,9 +445,6 @@ class bluetoothdevice : public QObject {
virtual void cadenceSensor(uint8_t cadence);
virtual void powerSensor(uint16_t power);
virtual void speedSensor(double speed);
virtual void coreBodyTemperature(double coreBodyTemperature);
virtual void skinTemperature(double skinTemperature);
virtual void heatStrainIndex(double heatStrainIndex);
virtual void inclinationSensor(double grade, double inclination);
virtual void changeResistance(resistance_t res);
virtual void changePower(int32_t power);
@@ -619,14 +581,9 @@ class bluetoothdevice : public QObject {
metric elevationAcc;
/**
* @brief m_watt Metric to get and set the power read from the trainer or from the power sensor Unit: watts
* @brief m_watt Metric to get and set the power expended in the session. Unit: watts
*/
metric m_watt;
/**
* @brief m_rawWatt Metric to get and set the power from the trainer only. Unit: watts
*/
metric m_rawWatt;
/**
* @brief WattKg Metric to get and set the watt kg for the session (what's this?). Unit: watt kg
@@ -706,11 +663,6 @@ class bluetoothdevice : public QObject {
*/
metric HeartZone;
/**
* @brief HeatZone A metric to get and set the current heat zone. Unit: depends on implementation
*/
metric HeatZone;
/**
* @brief PowerZone A metric to get and set the current power zone. Unit: depends on implementation
*/
@@ -737,12 +689,6 @@ class bluetoothdevice : public QObject {
static const uint8_t maxhrzone = 5;
metric hrZonesSeconds[maxhrzone];
/**
* @brief Collect the number of seconds in each zone for the current heat strain index
*/
static const uint8_t maxheatzone = 4;
metric heatZonesSeconds[maxheatzone];
bluetoothdevice::WORKOUT_EVENT_STATE lastState;
/**

View File

@@ -199,20 +199,10 @@ void bowflext216treadmill::characteristicChanged(const QLowEnergyCharacteristic
else if ((newValue.length() != 12) && bowflex_t8j == true)
return;
if(bowflex_T128 && characteristic.uuid() != QBluetoothUuid(QStringLiteral("a46a4a80-9803-11e3-8f3c-0002a5d5c51b"))) {
double incline = GetInclinationFromPacket(value);
qDebug() << QStringLiteral("Current incline: ") << incline;
if (Inclination.value() != incline) {
emit inclinationChanged(0.0, incline);
}
Inclination = incline;
return;
}
if ((bowflex_t6 == true || bowflex_T128 == true) && characteristic.uuid() != QBluetoothUuid(QStringLiteral("a46a4a80-9803-11e3-8f3c-0002a5d5c51b")))
if (bowflex_t6 == true && characteristic.uuid() != QBluetoothUuid(QStringLiteral("a46a4a80-9803-11e3-8f3c-0002a5d5c51b")))
return;
double speed = GetSpeedFromPacket(value);
double speed = GetSpeedFromPacket(value);
double incline = GetInclinationFromPacket(value);
// double kcal = GetKcalFromPacket(value);
// double distance = GetDistanceFromPacket(value);
@@ -236,13 +226,10 @@ void bowflext216treadmill::characteristicChanged(const QLowEnergyCharacteristic
emit speedChanged(speed);
}
Speed = speed;
if(bowflex_T128 == false) {
if (Inclination.value() != incline) {
emit inclinationChanged(0.0, incline);
}
Inclination = incline;
if (Inclination.value() != incline) {
emit inclinationChanged(0.0, incline);
}
Inclination = incline;
// KCal = kcal;
// Distance = distance;
@@ -284,10 +271,6 @@ double bowflext216treadmill::GetSpeedFromPacket(const QByteArray &packet) {
uint16_t convertedData = (uint16_t)((uint8_t)packet.at(3)) + ((uint16_t)((uint8_t)packet.at(4)) << 8);
double data = (double)convertedData / 100.0f;
return data * 1.60934;
} else if(bowflex_T128) {
uint16_t convertedData = (uint16_t)((uint8_t)packet.at(9)) + ((uint16_t)((uint8_t)packet.at(10)) << 8);
double data = (double)convertedData / 100.0f;
return data * 1.60934;
} else if (bowflex_t6 == false) {
uint16_t convertedData = (uint16_t)((uint8_t)packet.at(6)) + (((uint16_t)((uint8_t)packet.at(7)) << 8) & 0xFF00);
double data = (double)convertedData / 100.0f;
@@ -311,12 +294,6 @@ double bowflext216treadmill::GetDistanceFromPacket(const QByteArray &packet) {
}
double bowflext216treadmill::GetInclinationFromPacket(const QByteArray &packet) {
if (bowflex_T128) {
uint16_t convertedData = packet.at(7);
double data = convertedData;
return data;
}
if (bowflex_t8j) {
uint16_t convertedData = packet.at(6);
double data = convertedData;
@@ -470,12 +447,6 @@ void bowflext216treadmill::deviceDiscovered(const QBluetoothDeviceInfo &device)
device.address().toString() + ')');
{
bluetoothDevice = device;
if(bluetoothDevice.name().toUpper().startsWith("BOWFLEX T128")) {
qDebug() << "BOWFLEX T128 workaround found!";
bowflex_T128 = true;
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &bowflext216treadmill::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &bowflext216treadmill::serviceScanDone);

View File

@@ -75,7 +75,6 @@ class bowflext216treadmill : public treadmill {
bool bowflex_t6 = false;
bool bowflex_btx116 = false;
bool bowflex_t8j = false;
bool bowflex_T128 = false;
Q_SIGNALS:
void disconnected();

View File

@@ -1,456 +0,0 @@
#include "coresensor.h"
#include "homeform.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QEventLoop>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
coresensor::coresensor() :
m_sensorHeartRate(0),
m_dataQuality(0),
m_heartRateState(0),
m_isCelsius(true) {
}
coresensor::~coresensor() {
disconnectBluetooth();
}
bool coresensor::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
metric coresensor::currentHeart() {
// If we have a valid value from the sensor, create a metric with that value
if (m_sensorHeartRate > 0) {
metric heartMetric;
heartMetric.setValue(m_sensorHeartRate);
return heartMetric;
}
// Otherwise return the value from the base class
return bluetoothdevice::currentHeart();
}
void coresensor::update() {
// This method can be used for periodic tasks or calculations
QSettings settings;
// Future implementation if needed
}
void coresensor::disconnectBluetooth() {
qDebug() << QStringLiteral("coresensor::disconnect") << m_control;
if (m_control) {
m_control->disconnectFromDevice();
}
}
void coresensor::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QSettings settings;
qDebug() << QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')';
if(homeform::singleton())
homeform::singleton()->setToastRequested(device.name() + QStringLiteral(" connected!"));
// We might filter the device name if needed
// For example: if (device.name().contains("CORE") || device.name().contains("CoreTemp"))
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &coresensor::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &coresensor::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &coresensor::handleError);
connect(m_control, &QLowEnergyController::stateChanged, this, &coresensor::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
qDebug() << QStringLiteral("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
void coresensor::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
}
void coresensor::serviceScanDone() {
qDebug() << QStringLiteral("serviceScanDone");
auto services_list = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services_list)) {
qDebug() << QStringLiteral("coresensor services ") << s.toString();
// Look for the specific CORE sensor service
if (s == QBluetoothUuid(QString(CORE_SERVICE_UUID))) {
QBluetoothUuid coreServiceId(QString(CORE_SERVICE_UUID));
m_coreService = m_control->createServiceObject(coreServiceId);
connect(m_coreService, &QLowEnergyService::stateChanged, this,
&coresensor::serviceStateChanged);
connect(m_coreService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &coresensor::handleServiceError);
m_coreService->discoverDetails();
return;
}
}
// If we reach here, we didn't find the CORE service
qDebug() << QStringLiteral("CORE service not found!");
}
void coresensor::serviceStateChanged(QLowEnergyService::ServiceState state) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (state == QLowEnergyService::ServiceDiscovered) {
auto characteristics_list = m_coreService->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("characteristic ") + c.uuid().toString();
}
// Find the Core Body Temperature characteristic
m_coreTemperatureCharacteristic =
m_coreService->characteristic(QBluetoothUuid(QString(CORE_TEMPERATURE_CHAR_UUID)));
if (!m_coreTemperatureCharacteristic.isValid()) {
qDebug() << "Core Body Temperature characteristic not valid";
return;
}
// Find the Control Point characteristic
m_coreControlPointCharacteristic =
m_coreService->characteristic(QBluetoothUuid(QString(CORE_CONTROL_POINT_UUID)));
if (!m_coreControlPointCharacteristic.isValid()) {
qDebug() << "Core Control Point characteristic not valid";
// We can continue without control point if only reading temperature
}
// Set up notifications and handle characteristic changes
connect(m_coreService, &QLowEnergyService::characteristicChanged, this,
&coresensor::characteristicChanged);
connect(m_coreService, &QLowEnergyService::characteristicWritten, this,
&coresensor::characteristicWritten);
connect(m_coreService, &QLowEnergyService::descriptorWritten, this,
&coresensor::descriptorWritten);
// Enable notifications for Core Body Temperature characteristic
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
m_coreService->writeDescriptor(
m_coreTemperatureCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration),
descriptor);
// For Control Point, we need to enable indications (not notifications)
if (m_coreControlPointCharacteristic.isValid()) {
QByteArray cpDescriptor;
cpDescriptor.append((char)0x02); // 0x02 for indications (different from notifications)
cpDescriptor.append((char)0x00);
m_coreService->writeDescriptor(
m_coreControlPointCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration),
cpDescriptor);
}
}
}
void coresensor::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
emit packetReceived();
qDebug() << QStringLiteral(" << ") + newValue.toHex(' ');
// Handle Core Body Temperature characteristic updates
if (characteristic.uuid() == QBluetoothUuid(QString(CORE_TEMPERATURE_CHAR_UUID))) {
if (newValue.length() < 1) {
qDebug() << "Received invalid data packet (too short)";
return;
}
// Parse flags field (first byte)
uint8_t flags = newValue.at(0);
bool hasSkinTemp = (flags & 0x01);
bool hasCoreReserved = (flags & 0x02);
bool hasQualityState = (flags & 0x04);
bool isFahrenheit = (flags & 0x08);
bool hasHeartRate = (flags & 0x10);
bool hasHeatStrainIndex = (flags & 0x20);
// Update temperature unit flag
bool newIsCelsius = !isFahrenheit;
if (m_isCelsius != newIsCelsius) {
m_isCelsius = newIsCelsius;
emit isCelsiusChanged(m_isCelsius);
}
int offset = 1; // Start after flags byte
// Core body temperature (mandatory)
if (newValue.length() >= (offset + 2)) {
int16_t tempRaw = (int16_t)((uint8_t)newValue[offset] | ((uint8_t)newValue[offset+1] << 8));
// Special value for 'Data not available'
if (tempRaw == 0x7FFF) {
qDebug() << "Core body temperature: Data not available";
} else {
double temp = tempRaw / 100.0; // Convert from 0.01°C/F to °C/F
CoreBodyTemperature.setValue(temp);
emit coreBodyTemperatureChanged(temp);
}
offset += 2;
}
// Skin temperature (optional)
if (hasSkinTemp && newValue.length() >= (offset + 2)) {
int16_t tempRaw = (int16_t)((uint8_t)newValue[offset] | ((uint8_t)newValue[offset+1] << 8));
double temp = tempRaw / 100.0; // Convert from 0.01°C/F to °C/F
SkinTemperature.setValue(temp);
emit skinTemperatureChanged(temp);
offset += 2;
}
// Core reserved (optional)
if (hasCoreReserved && newValue.length() >= (offset + 2)) {
int16_t tempRaw = (int16_t)((uint8_t)newValue[offset] | ((uint8_t)newValue[offset+1] << 8));
double temp = tempRaw / 100.0; // Convert from 0.01°C/F to °C/F
emit coreReservedChanged(temp);
offset += 2;
}
// Quality and state (optional)
if (hasQualityState && newValue.length() >= (offset + 1)) {
uint8_t qualityState = newValue.at(offset);
int newDataQuality = qualityState & 0x0F; // Lower 4 bits, actually using only 3 bits
int newHeartRateState = (qualityState >> 4) & 0x03; // Bits 4-5
if (m_dataQuality != newDataQuality) {
m_dataQuality = newDataQuality;
emit dataQualityChanged(m_dataQuality);
}
if (m_heartRateState != newHeartRateState) {
m_heartRateState = newHeartRateState;
emit heartRateStateChanged(m_heartRateState);
}
offset += 1;
}
// Heart rate (optional)
if (hasHeartRate && newValue.length() >= (offset + 1)) {
uint8_t hr = newValue.at(offset);
if (m_sensorHeartRate != hr) {
m_sensorHeartRate = hr;
emit heartRateChanged(m_sensorHeartRate);
// Forward heart rate to base class if it's a valid value
if (hr > 0) {
bluetoothdevice::heartRate(hr);
}
}
offset += 1;
}
// Heat Strain Index (optional)
if (hasHeatStrainIndex && newValue.length() >= (offset + 1)) {
uint8_t hsiRaw = newValue.at(offset);
double hsi = hsiRaw / 10.0; // Convert from 0.1 units to actual value
if (HeatStrainIndex.value() != hsi) {
HeatStrainIndex = hsi;
emit heatStrainIndexChanged(HeatStrainIndex.value());
}
offset += 1;
}
// Log data for debugging
QString logMsg = QStringLiteral("CORE Sensor: ");
logMsg += QStringLiteral("Temperature: ") + QString::number(CoreBodyTemperature.value(), 'f', 2) +
(m_isCelsius ? "°C" : "°F");
if (hasSkinTemp) {
logMsg += QStringLiteral(" Skin: ") + QString::number(SkinTemperature.value(), 'f', 2) +
(m_isCelsius ? "°C" : "°F");
}
if (hasHeartRate) {
logMsg += QStringLiteral(" HR: ") + QString::number(m_sensorHeartRate) + QStringLiteral(" BPM");
}
if (hasHeatStrainIndex) {
logMsg += QStringLiteral(" HSI: ") + QString::number(HeatStrainIndex.value(), 'f', 1);
}
qDebug() << logMsg;
} else if (characteristic.uuid() == QBluetoothUuid(QString(CORE_CONTROL_POINT_UUID))) {
// Handle Control Point responses
handleControlPointResponse(newValue);
}
}
void coresensor::handleControlPointResponse(const QByteArray &value) {
if (value.length() < 3) {
qDebug() << "Invalid Control Point response (too short)";
return;
}
// First byte should be 0x80 (response code)
if ((uint8_t)value.at(0) != 0x80) {
qDebug() << "Invalid Control Point response (expected 0x80 response code)";
return;
}
uint8_t requestOpCode = value.at(1);
uint8_t resultCode = value.at(2);
QString result;
switch (resultCode) {
case 0x01: result = "Success"; break;
case 0x02: result = "Op Code not supported"; break;
case 0x03: result = "Invalid Parameter"; break;
case 0x04: result = "Operation Failed"; break;
default: result = "Unknown result code"; break;
}
qDebug() << "Control Point response for OpCode" << QString("0x%1").arg(requestOpCode, 2, 16, QChar('0'))
<< "Result:" << result;
m_operationInProgress = false;
}
void coresensor::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
Q_UNUSED(characteristic);
qDebug() << QStringLiteral("characteristicWritten ") + newValue.toHex(' ');
}
void coresensor::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' ');
}
void coresensor::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
m_control->connectToDevice();
}
}
void coresensor::handleError(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
qDebug() << QStringLiteral("coresensor::error ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void coresensor::handleServiceError(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
qDebug() << QStringLiteral("coresensor::serviceError ") + QString::fromLocal8Bit(metaEnum.valueToKey(err));
}
bool coresensor::setExternalHeartRate(uint8_t bpm) {
if (!m_coreControlPointCharacteristic.isValid() || m_operationInProgress) {
return false;
}
QByteArray request;
request.append((char)0x13); // OpCode for external heart rate
request.append((char)bpm); // Heart rate value
m_operationInProgress = true;
m_coreService->writeCharacteristic(m_coreControlPointCharacteristic, request, QLowEnergyService::WriteWithResponse);
return true;
}
bool coresensor::disableExternalHeartRate() {
if (!m_coreControlPointCharacteristic.isValid() || m_operationInProgress) {
return false;
}
QByteArray request;
request.append((char)0x13); // OpCode for external heart rate
// No additional data means disable external heart rate
m_operationInProgress = true;
m_coreService->writeCharacteristic(m_coreControlPointCharacteristic, request, QLowEnergyService::WriteWithResponse);
return true;
}
bool coresensor::scanForBLEHeartRateMonitors() {
if (!m_coreControlPointCharacteristic.isValid() || m_operationInProgress) {
return false;
}
QByteArray request;
request.append((char)0x0D); // OpCode for scan BLE HRMs
request.append((char)0xFF); // Parameter for starting scan
m_operationInProgress = true;
m_coreService->writeCharacteristic(m_coreControlPointCharacteristic, request, QLowEnergyService::WriteWithResponse);
return true;
}
bool coresensor::scanForANTHeartRateMonitors() {
if (!m_coreControlPointCharacteristic.isValid() || m_operationInProgress) {
return false;
}
QByteArray request;
request.append((char)0x0A); // OpCode for scan ANT+ HRMs
request.append((char)0xFF); // Parameter for starting scan
m_operationInProgress = true;
m_coreService->writeCharacteristic(m_coreControlPointCharacteristic, request, QLowEnergyService::WriteWithResponse);
return true;
}
int coresensor::getNumberOfPairedHeartRateMonitors() {
// This is a simple implementation that requests the number of paired BLE HRMs
// In a complete implementation, we would make an async request and store the result
if (!m_coreControlPointCharacteristic.isValid() || m_operationInProgress) {
return -1; // Error condition
}
// Request total number of paired BLE heart rate monitors
QByteArray request;
request.append((char)0x08); // OpCode for get total number of paired BLE HRMs
m_operationInProgress = true;
m_coreService->writeCharacteristic(m_coreControlPointCharacteristic, request, QLowEnergyService::WriteWithResponse);
// Note: in a real implementation, we would need to handle the response asynchronously
// and store the result for a getter to access. This would require additional state tracking.
return 0; // Placeholder value - in reality, would return cached value from last request
}

View File

@@ -1,196 +0,0 @@
#ifndef CORESENSOR_H
#define CORESENSOR_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#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 <QObject>
#include <QTime>
#include <QDateTime>
#include "bluetoothdevice.h"
// UUID costanti per CORE Sensor secondo le specifiche
#define CORE_SERVICE_UUID "00002100-5B1E-4347-B07C-97B514DAE121"
#define CORE_TEMPERATURE_CHAR_UUID "00002101-5B1E-4347-B07C-97B514DAE121"
#define CORE_CONTROL_POINT_UUID "00002102-5B1E-4347-B07C-97B514DAE121"
// Flag bit definitions for Core Body Temperature characteristic
namespace CoreSensorFlags {
const uint8_t SKIN_TEMPERATURE_PRESENT = 0x01;
const uint8_t CORE_RESERVED_PRESENT = 0x02;
const uint8_t QUALITY_STATE_PRESENT = 0x04;
const uint8_t TEMPERATURE_UNIT_FAHRENHEIT = 0x08;
const uint8_t HEART_RATE_PRESENT = 0x10;
const uint8_t HEAT_STRAIN_INDEX_PRESENT = 0x20;
}
// Data quality values
namespace CoreSensorQuality {
const uint8_t INVALID = 0;
const uint8_t POOR = 1;
const uint8_t FAIR = 2;
const uint8_t GOOD = 3;
const uint8_t EXCELLENT = 4;
const uint8_t NOT_AVAILABLE = 7;
}
// Heart rate state values
namespace CoreSensorHRState {
const uint8_t NOT_SUPPORTED = 0;
const uint8_t SUPPORTED_NOT_RECEIVING = 1;
const uint8_t SUPPORTED_RECEIVING = 2;
const uint8_t NOT_AVAILABLE = 3;
}
// Control Point OpCodes
namespace CoreControlPoint {
// ANT+ Related OpCodes
const uint8_t CLEAR_ANT_PAIRED_HRM_LIST = 0x01;
const uint8_t ADD_ANT_HRM = 0x02;
const uint8_t REMOVE_ANT_HRM = 0x03;
const uint8_t GET_TOTAL_ANT_PAIRED_HRMS = 0x04;
const uint8_t GET_ANT_HRM_ID_AT_INDEX = 0x05;
const uint8_t SCAN_ANT_HRMS = 0x0A;
const uint8_t GET_TOTAL_SCANNED_ANT_HRMS = 0x0B;
const uint8_t GET_SCANNED_ANT_HRM_ID = 0x0C;
// BLE Related OpCodes
const uint8_t ADD_BLE_HRM = 0x06;
const uint8_t REMOVE_BLE_HRM = 0x07;
const uint8_t GET_TOTAL_BLE_PAIRED_HRMS = 0x08;
const uint8_t GET_BLE_HRM_NAME_STATE = 0x09;
const uint8_t SCAN_BLE_HRMS = 0x0D;
const uint8_t GET_TOTAL_SCANNED_BLE_HRMS = 0x0E;
const uint8_t GET_SCANNED_BLE_HRM_NAME = 0x0F;
const uint8_t GET_SCANNED_BLE_HRM_MAC = 0x10;
const uint8_t CLEAR_BLE_PAIRED_HRM_LIST = 0x11;
const uint8_t GET_BLE_HRM_MAC_STATE = 0x12;
// External heart rate input
const uint8_t EXTERNAL_HEART_RATE = 0x13;
// Response code (sent by sensor)
const uint8_t RESPONSE_CODE = 0x80;
// Scan parameters
const uint8_t START_SCAN = 0xFF;
const uint8_t START_PROXIMITY_PAIRING = 0xFE;
// Result codes
const uint8_t SUCCESS = 0x01;
const uint8_t OP_CODE_NOT_SUPPORTED = 0x02;
const uint8_t INVALID_PARAMETER = 0x03;
const uint8_t OPERATION_FAILED = 0x04;
}
// Struttura per la qualità e lo stato
struct QualityAndState {
enum DataQuality {
INVALID = 0,
POOR = 1,
FAIR = 2,
GOOD = 3,
EXCELLENT = 4,
NOT_AVAILABLE = 7
};
enum HeartRateState {
NOT_SUPPORTED = 0,
SUPPORTED_NOT_RECEIVING = 1,
SUPPORTED_RECEIVING = 2,
NOT_AVAILABLE_STATE = 3
};
DataQuality quality;
HeartRateState hrState;
};
class coresensor : public bluetoothdevice {
Q_OBJECT
public:
coresensor();
~coresensor();
bool connected() override;
// Override from bluetoothdevice
metric currentHeart() override;
private:
QLowEnergyService *m_coreService = nullptr;
QLowEnergyCharacteristic m_coreTemperatureCharacteristic;
QLowEnergyCharacteristic m_coreControlPointCharacteristic;
// Other data from Core sensor
uint8_t m_sensorHeartRate; // Heart rate in BPM
int m_dataQuality; // Quality level (0=Invalid, 1=Poor, 2=Fair, 3=Good, 4=Excellent)
int m_heartRateState; // Heart rate state
bool m_isCelsius; // Temperature unit (true=°C, false=°F)
// Control point state tracking
bool m_operationInProgress = false;
signals:
void disconnected();
void debug(QString string);
void packetReceived();
// Signals for property change notifications
void coreBodyTemperatureChanged(double value);
void skinTemperatureChanged(double value);
void coreReservedChanged(double value);
void heartRateChanged(double value);
void heatStrainIndexChanged(double value);
void dataQualityChanged(int value);
void heartRateStateChanged(int value);
void isCelsiusChanged(bool value);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void disconnectBluetooth();
// Control point operations
bool setExternalHeartRate(uint8_t bpm);
bool disableExternalHeartRate();
// Heart rate monitor operations - these depend on implementation
bool scanForBLEHeartRateMonitors();
bool scanForANTHeartRateMonitors();
int getNumberOfPairedHeartRateMonitors();
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void serviceStateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void handleError(QLowEnergyController::Error err);
void handleServiceError(QLowEnergyService::ServiceError err);
// Handle control point responses
void handleControlPointResponse(const QByteArray &value);
};
#endif // CORESENSOR_H

View File

@@ -72,14 +72,7 @@ void cscbike::update() {
}
}
bool rogue_echo_bike = settings.value(QZSettings::rogue_echo_bike, QZSettings::default_rogue_echo_bike).toBool();
if (rogue_echo_bike) {
double rpm = currentCadence().value();
m_watt = 0.000602337 * pow(rpm, 3.11762) + 32.6404;
} else {
m_watt = wattFromHR(false);
}
m_watt = wattFromHR(false);
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
if (m_control->state() == QLowEnergyController::UnconnectedState) {

View File

@@ -1,5 +1,4 @@
#include "devices/dircon/dirconmanager.h"
#include "devices/bike.h"
#include <QNetworkInterface>
#include <QSettings>
#include <chrono>
@@ -9,17 +8,14 @@ using namespace std::chrono_literals;
#define DM_MACHINE_TYPE_BIKE 1
#define DM_MACHINE_TYPE_TREADMILL 2
#define DM_SERV_OP(OP, P1, P2, P3, ZWIFT_ENABLED) \
#define DM_SERV_OP(OP, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0x1826, WAHOO_KICKR, P1, P2, P3) \
OP(FITNESS_MACHINE_TREADMILL, 0x1826, WAHOO_TREADMILL, P1, P2, P3) \
OP(CYCLING_POWER, 0x1818, WAHOO_KICKR, P1, P2, P3) \
OP(CYCLING_SPEED_AND_CADENCE, 0x1816, WAHOO_KICKR, P1, P2, P3) \
OP(RUNNING_SPEED_AND_CADENCE, 0x1814, WAHOO_TREADMILL, P1, P2, P3) \
OP(HEART_RATE, 0x180D, WAHOO_BLUEHR, P1, P2, P3) \
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, ZWIFT_PLAY_ENUM_VALUE, WAHOO_KICKR, P1, P2, P3)
#define ZWIFT_ENABLED_OP(OP, DESC, UUID, MACHINE, P1, P2, P3) OP(DESC, UUID, MACHINE, P1, P2, P3)
#define ZWIFT_DISABLED_OP(OP, DESC, UUID, MACHINE, P1, P2, P3)
OP(ZWIFT_PLAY_EMULATOR, ZWIFT_PLAY_ENUM_VALUE, WAHOO_KICKR, P1, P2, P3)
#define DM_MACHINE_OP(OP, P1, P2, P3) \
OP(WAHOO_KICKR, "Wahoo KICKR $uuid_hex$", DM_MACHINE_TYPE_TREADMILL | DM_MACHINE_TYPE_BIKE, P1, P2, P3) \
@@ -27,7 +23,7 @@ using namespace std::chrono_literals;
OP(WAHOO_RPM_SPEED, "Wahoo SPEED $uuid_hex$", DM_MACHINE_TYPE_BIKE, P1, P2, P3) \
OP(WAHOO_TREADMILL, "Wahoo TREAD $uuid_hex$", DM_MACHINE_TYPE_TREADMILL, P1, P2, P3)
#define DP_PROCESS_WRITE_0003() (zwift_play_emulator ? writeP0003 : 0)
#define DP_PROCESS_WRITE_0003() writeP0003
#define DP_PROCESS_WRITE_2AD9() writeP2AD9
#define DP_PROCESS_WRITE_2AD9T() writeP2AD9
#define DP_PROCESS_WRITE_E005() writePE005
@@ -38,7 +34,7 @@ using namespace std::chrono_literals;
#define DM_BT(A) QByteArrayLiteral(A)
#define DM_CHAR_OP(OP, P1, P2, P3, ZWIFT_ENABLED) \
#define DM_CHAR_OP(OP, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0x2ACC, DPKT_CHAR_PROP_FLAG_READ, DM_BT("\x83\x14\x00\x00\x0C\xE0\x00\x00"), \
DP_PROCESS_WRITE_NULL, P1, P2, P3) \
OP(FITNESS_MACHINE_CYCLE, 0x2AD6, DPKT_CHAR_PROP_FLAG_READ, DM_BT("\x0A\x00\x96\x00\x0A\x00"), \
@@ -78,14 +74,16 @@ using namespace std::chrono_literals;
OP(RUNNING_SPEED_AND_CADENCE, 0x2A53, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, \
P3) \
OP(HEART_RATE, 0x2A37, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0003, DPKT_CHAR_PROP_FLAG_WRITE, DM_BT("\x00"), DP_PROCESS_WRITE_0003, P1, P2, P3) \
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0002, DPKT_CHAR_PROP_FLAG_NOTIFY, DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
ZWIFT_ENABLED(OP, ZWIFT_PLAY_EMULATOR, 0x0004, DPKT_CHAR_PROP_FLAG_INDICATE, DM_BT("\x02\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3)
#define ZWIFT_CHAR_ENABLED_OP(OP, DESC, UUID, TYPE, READV, WRITEP, P1, P2, P3) \
OP(DESC, UUID, TYPE, READV, WRITEP, P1, P2, P3)
#define ZWIFT_CHAR_DISABLED_OP(OP, DESC, UUID, TYPE, READV, WRITEP, P1, P2, P3)
OP(ZWIFT_PLAY_EMULATOR, 0x0003, \
DPKT_CHAR_PROP_FLAG_WRITE, \
DM_BT("\x00"), DP_PROCESS_WRITE_0003, P1, P2, P3) \
OP(ZWIFT_PLAY_EMULATOR, 0x0002, \
DPKT_CHAR_PROP_FLAG_NOTIFY, \
DM_BT("\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3) \
OP(ZWIFT_PLAY_EMULATOR, 0x0004, \
/* CHECK THE INDICATE*/ \
DPKT_CHAR_PROP_FLAG_INDICATE, \
DM_BT("\x02\x00"), DP_PROCESS_WRITE_NULL, P1, P2, P3)
#define DM_MACHINE_ENUM_OP(DESC, NAME, TYPE, P1, P2, P3) DM_MACHINE_##DESC,
@@ -93,22 +91,15 @@ enum { DM_MACHINE_OP(DM_MACHINE_ENUM_OP, 0, 0, 0) };
#define DM_SERV_ENUMU_OP(DESC, UUID, MACHINE, P1, P2, P3) DM_SERV_U_##DESC = UUID,
enum {
DM_SERV_OP(DM_SERV_ENUMU_OP, 0, 0, 0, ZWIFT_ENABLED_OP)
};
enum { DM_SERV_OP(DM_SERV_ENUMU_OP, 0, 0, 0) };
#define DM_SERV_ENUMM_OP(DESC, UUID, MACHINE, P1, P2, P3) DM_SERV_M_##DESC = DM_MACHINE_##MACHINE,
enum {
DM_SERV_OP(DM_SERV_ENUMM_OP, 0, 0, 0, ZWIFT_ENABLED_OP)
};
enum { DM_SERV_OP(DM_SERV_ENUMM_OP, 0, 0, 0) };
#define DM_SERV_ENUMI_OP(DESC, UUID, MACHINE, P1, P2, P3) DM_SERV_I_##DESC,
enum {
DM_SERV_OP(DM_SERV_ENUMI_OP, 0, 0, 0, ZWIFT_ENABLED_OP)
DM_SERV_I_NUM
};
enum { DM_SERV_OP(DM_SERV_ENUMI_OP, 0, 0, 0) DM_SERV_I_NUM };
#define DM_CHAR_INIT_OP(SDESC, UUID, TYPE, READV, WRITEP, P1, P2, P3) \
if (P1.size() <= DM_SERV_I_##SDESC) { \
@@ -171,47 +162,31 @@ DirconManager::DirconManager(bluetoothdevice *Bike, int8_t bikeResistanceOffset,
QSettings settings;
DirconProcessorService *service;
QList<DirconProcessorService *> services, proc_services;
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
bt = Bike;
bluetoothdevice::BLUETOOTH_TYPE dt = Bike->deviceType();
uint8_t type = dt == bluetoothdevice::TREADMILL || dt == bluetoothdevice::ELLIPTICAL ? DM_MACHINE_TYPE_TREADMILL
: DM_MACHINE_TYPE_BIKE;
qDebug() << "Building Dircom Manager";
uint16_t server_base_port =
settings.value(QZSettings::dircon_server_base_port, QZSettings::default_dircon_server_base_port).toUInt();
bool bike_wheel_revs = settings.value(QZSettings::bike_wheel_revs, QZSettings::default_bike_wheel_revs).toBool();
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
DM_CHAR_NOTIF_OP(DM_CHAR_NOTIF_BUILD_OP, Bike, 0, 0)
writeP2AD9 = new CharacteristicWriteProcessor2AD9(bikeResistanceGain, bikeResistanceOffset, Bike, notif2AD9, this);
writePE005 = new CharacteristicWriteProcessorE005(bikeResistanceGain, bikeResistanceOffset, Bike, this);
writeP0003 = new CharacteristicWriteProcessor0003(bikeResistanceGain, bikeResistanceOffset, Bike, notif0002, notif0004, this);
if (zwift_play_emulator) {
DM_CHAR_OP(DM_CHAR_INIT_OP, services, service, 0, ZWIFT_CHAR_ENABLED_OP);
} else {
DM_CHAR_OP(DM_CHAR_INIT_OP, services, service, 0, ZWIFT_CHAR_DISABLED_OP);
}
DM_CHAR_OP(DM_CHAR_INIT_OP, services, service, 0)
connect(writeP2AD9, SIGNAL(changeInclination(double, double)), this, SIGNAL(changeInclination(double, double)));
connect(writeP2AD9, SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)), this,
SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)));
connect(writePE005, SIGNAL(changeInclination(double, double)), this, SIGNAL(changeInclination(double, double)));
connect(writePE005, SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)), this,
SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)));
if (zwift_play_emulator && writeP0003) {
connect(writeP0003, SIGNAL(changeInclination(double, double)), this, SIGNAL(changeInclination(double, double)));
connect(writeP0003, SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)), this,
SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)));
}
connect(writeP0003, SIGNAL(changeInclination(double, double)), this, SIGNAL(changeInclination(double, double)));
connect(writeP0003, SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)), this,
SIGNAL(ftmsCharacteristicChanged(QLowEnergyCharacteristic, QByteArray)));
QObject::connect(&bikeTimer, &QTimer::timeout, this, &DirconManager::bikeProvider);
QString mac = getMacAddress();
DM_MACHINE_OP(DM_MACHINE_INIT_OP, services, proc_services, type)
if (zwift_play_emulator || settings.value(QZSettings::race_mode, QZSettings::default_race_mode).toBool())
if (settings.value(QZSettings::race_mode, QZSettings::default_race_mode).toBool() || settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool())
bikeTimer.start(50ms);
else
bikeTimer.start(1s);
@@ -219,7 +194,7 @@ DirconManager::DirconManager(bluetoothdevice *Bike, int8_t bikeResistanceOffset,
#define DM_CHAR_NOTIF_NOTIF1_OP(UUID, P1, P2, P3) \
QByteArray all##UUID; \
int rv##UUID = notif##UUID ? notif##UUID->notify(all##UUID) : 0;
int rv##UUID = notif##UUID->notify(all##UUID);
#define DM_CHAR_NOTIF_NOTIF2_OP(UUID, P1, P2, P3) \
if (rv##UUID == CN_OK) \
@@ -227,16 +202,5 @@ DirconManager::DirconManager(bluetoothdevice *Bike, int8_t bikeResistanceOffset,
void DirconManager::bikeProvider() {
DM_CHAR_NOTIF_OP(DM_CHAR_NOTIF_NOTIF1_OP, 0, 0, 0)
foreach (DirconProcessor *processor, processors) {
DM_CHAR_NOTIF_OP(DM_CHAR_NOTIF_NOTIF2_OP, processor, 0, 0)
}
}
double DirconManager::currentGear() {
QSettings settings;
if(settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool() && writeP0003)
return writeP0003->currentGear();
else if(bt && bt->deviceType() == bluetoothdevice::BIKE)
return ((bike*)bt)->gears();
return 0;
foreach (DirconProcessor *processor, processors) { DM_CHAR_NOTIF_OP(DM_CHAR_NOTIF_NOTIF2_OP, processor, 0, 0) }
}

View File

@@ -35,10 +35,8 @@ class DirconManager : public QObject {
DM_CHAR_NOTIF_OP(DM_CHAR_NOTIF_DEFINE_OP, 0, 0, 0)
QList<DirconProcessor *> processors;
static QString getMacAddress();
bluetoothdevice* bt = 0;
public:
double currentGear();
explicit DirconManager(bluetoothdevice *t, int8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0,
QObject *parent = nullptr);
private slots:

View File

@@ -268,7 +268,7 @@ void DirconProcessor::tcpDataAvailable() {
qDebug() << "Sending resp for uuid" << serverName << ":" << resp;
if (resp.Identifier != DPKT_MSGID_ERROR) {
QByteArray byteout = resp.encode(pkt.SequenceNumber);
if (byteout.size() && client && client->sock)
if (byteout.size())
client->sock->write(byteout);
}
} else if (rembuf >= 0) {
@@ -277,7 +277,7 @@ void DirconProcessor::tcpDataAvailable() {
resp.ResponseCode = DPKT_RESPCODE_UNEXPECTED_ERROR;
resp.Identifier = pkt.Identifier;
QByteArray byteout = resp.encode(pkt.SequenceNumber);
if (byteout.size() && client && client->sock)
if (byteout.size())
client->sock->write(byteout);
} else
break;

View File

@@ -353,16 +353,20 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
and speed status return;*/
double speed = GetSpeedFromPacket(value);
double kcal;
if (!settings.value(QZSettings::kcal_ignore_builtin, QZSettings::default_kcal_ignore_builtin).toBool())
KCal = GetKcalFromPacket(value);
kcal = GetKcalFromPacket(value);
else {
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) *
kcal =
KCal.value() + ((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body
// weight in kg * 3.5) / 200 ) / 60
else
kcal = KCal.value();
}
if(!firstCharacteristicChanged) {
@@ -435,7 +439,7 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
qDebug() << QStringLiteral("Current cadence: ") + QString::number(Cadence.value());
qDebug() << QStringLiteral("Current resistance: ") + QString::number(Resistance.value());
qDebug() << QStringLiteral("Current heart: ") + QString::number(Heart.value());
qDebug() << QStringLiteral("Current KCal: ") + QString::number(KCal.value());
qDebug() << QStringLiteral("Current KCal: ") + QString::number(kcal);
qDebug() << QStringLiteral("Current Distance: ") + QString::number(distance);
qDebug() << QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs);
qDebug() << QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime);
@@ -452,6 +456,7 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
watts(), Inclination.value(), Speed.value(),
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
KCal = kcal;
firstCharacteristicChanged = false;
}

View File

@@ -330,7 +330,6 @@ void domyostreadmill::update() {
if (requestInclination != -100 && lastInclinationChanged.secsTo(QDateTime::currentDateTime()) > inclination_delay_seconds) {
lastInclinationChanged = QDateTime::currentDateTime();
// only 0.5 steps ara available
requestInclination = treadmillInclinationOverrideReverse(requestInclination);
requestInclination = qRound(requestInclination * 2.0) / 2.0;
inc = requestInclination;
requestInclination = -100;
@@ -642,7 +641,7 @@ void domyostreadmill::characteristicChanged(const QLowEnergyCharacteristic &char
and speed status return;*/
double speed = GetSpeedFromPacket(value);
double incline = treadmillInclinationOverride(GetInclinationFromPacket(value));
double incline = GetInclinationFromPacket(value);
double kcal = GetKcalFromPacket(value);
double distance = GetDistanceFromPacket(value);
bool disable_hr_frommachinery =

View File

@@ -37,37 +37,28 @@ echelonconnectsport::echelonconnectsport(bool noWriteResistance, bool noHeartSer
void echelonconnectsport::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QSettings settings;
bool useNativeIOS = false;
#ifdef Q_OS_IOS
useNativeIOS = settings.value(QZSettings::ios_btdevice_native, QZSettings::default_ios_btdevice_native).toBool();
#endif
QEventLoop loop;
QTimer timeout;
if (!useNativeIOS) {
// if there are some crash here, maybe it's better to use 2 separate event for the characteristicChanged.
// one for the resistance changed event (spontaneous), and one for the other ones.
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);
}
// if there are some crash here, maybe it's better to use 2 separate event for the characteristicChanged.
// one for the resistance changed event (spontaneous), and one for the other ones.
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);
}
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
m_control->state() == QLowEnergyController::UnconnectedState) {
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
return;
}
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
m_control->state() == QLowEnergyController::UnconnectedState) {
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
return;
}
if (!gattWriteCharacteristic.isValid()) {
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
return;
}
if (!gattWriteCharacteristic.isValid()) {
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
return;
}
if (writeBuffer) {
@@ -75,25 +66,14 @@ void echelonconnectsport::writeCharacteristic(uint8_t *data, uint8_t data_len, c
}
writeBuffer = new QByteArray((const char *)data, data_len);
#ifdef Q_OS_IOS
if (useNativeIOS) {
#ifndef IO_UNDER_QT
iOS_echelonConnectSport->echelonConnectSport_WriteCharacteristic((unsigned char*)writeBuffer->data(), data_len);
#endif
} else
#endif
{
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
}
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
if (!disable_log) {
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
QStringLiteral(" // ") + info;
}
if (!useNativeIOS) {
loop.exec();
}
loop.exec();
}
void echelonconnectsport::forceResistance(resistance_t requestResistance) {
@@ -125,14 +105,7 @@ void echelonconnectsport::sendPoll() {
}
void echelonconnectsport::update() {
QSettings settings;
bool useNativeIOS = false;
#ifdef Q_OS_IOS
useNativeIOS = settings.value(QZSettings::ios_btdevice_native, QZSettings::default_ios_btdevice_native).toBool();
#endif
if (!useNativeIOS && m_control->state() == QLowEnergyController::UnconnectedState) {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
@@ -140,17 +113,12 @@ void echelonconnectsport::update() {
if (initRequest) {
initRequest = false;
btinit();
} else if ((useNativeIOS ||
(bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() &&
gattNotify1Characteristic.isValid() &&
gattNotify2Characteristic.isValid())) &&
initDone) {
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
gattNotify1Characteristic.isValid() && gattNotify2Characteristic.isValid() && initDone) {
update_metrics(true, watts());
// sending poll every 2 seconds
// sending poll every 2 seconds
if (sec1Update++ >= (2000 / refresh->interval())) {
sec1Update = 0;
sendPoll();
@@ -172,7 +140,7 @@ void echelonconnectsport::update() {
if (requestStart != -1) {
qDebug() << QStringLiteral("starting...");
// btinit();
// btinit();
requestStart = -1;
emit bikeStarted();
@@ -238,24 +206,19 @@ void echelonconnectsport::characteristicChanged(const QLowEnergyCharacteristic &
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool useNativeIOS = false;
#ifdef Q_OS_IOS
useNativeIOS = settings.value(QZSettings::ios_btdevice_native, QZSettings::default_ios_btdevice_native).toBool();
#endif
qDebug() << " << " + newValue.toHex(' ');
lastPacket = newValue;
// resistance value is in another frame
// resistance value is in another frame
if (newValue.length() == 5 && ((unsigned char)newValue.at(0)) == 0xf0 && ((unsigned char)newValue.at(1)) == 0xd2) {
resistance_t res = newValue.at(3);
if (settings.value(QZSettings::gears_from_bike, QZSettings::default_gears_from_bike).toBool()) {
qDebug() << QStringLiteral("gears_from_bike") << res << Resistance.value() << gears()
<< lastRawRequestedResistanceValue << lastRequestedResistance().value();
if (
// if the resistance is different from the previous one
// if the resistance is different from the previous one
res != qRound(Resistance.value()) &&
// and the last target resistance is different from the current one or there is no any pending last
// requested resistance
@@ -331,18 +294,18 @@ void echelonconnectsport::characteristicChanged(const QLowEnergyCharacteristic &
#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());
}
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
// these useless lines are needed to calculate the AVG resistance and AVG peloton resistance since
// echelon just send the resistance values when it changes
// these useless lines are needed to calculate the AVG resistance and AVG peloton resistance since
// echelon just send the resistance values when it changes
Resistance = Resistance.value();
m_pelotonResistance = m_pelotonResistance.value();
@@ -355,7 +318,7 @@ void echelonconnectsport::characteristicChanged(const QLowEnergyCharacteristic &
qDebug() << QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime);
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
if (!useNativeIOS && m_control && m_control->error() != QLowEnergyController::NoError) {
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
}
@@ -403,90 +366,79 @@ void echelonconnectsport::btinit() {
}
void echelonconnectsport::stateChanged(QLowEnergyService::ServiceState state) {
QSettings settings;
bool useNativeIOS = false;
QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("0bf669f2-45f2-11e7-9598-0800200c9a66"));
QBluetoothUuid _gattNotify1CharacteristicId(QStringLiteral("0bf669f3-45f2-11e7-9598-0800200c9a66"));
QBluetoothUuid _gattNotify2CharacteristicId(QStringLiteral("0bf669f4-45f2-11e7-9598-0800200c9a66"));
#ifdef Q_OS_IOS
useNativeIOS = settings.value(QZSettings::ios_btdevice_native, QZSettings::default_ios_btdevice_native).toBool();
#endif
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (!useNativeIOS) {
QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("0bf669f2-45f2-11e7-9598-0800200c9a66"));
QBluetoothUuid _gattNotify1CharacteristicId(QStringLiteral("0bf669f3-45f2-11e7-9598-0800200c9a66"));
QBluetoothUuid _gattNotify2CharacteristicId(QStringLiteral("0bf669f4-45f2-11e7-9598-0800200c9a66"));
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
gattNotify2Characteristic = gattCommunicationChannelService->characteristic(_gattNotify2CharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotify1Characteristic.isValid());
Q_ASSERT(gattNotify2Characteristic.isValid());
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&echelonconnectsport::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&echelonconnectsport::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &echelonconnectsport::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&echelonconnectsport::descriptorWritten);
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
gattNotify2Characteristic = gattCommunicationChannelService->characteristic(_gattNotify2CharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotify1Characteristic.isValid());
Q_ASSERT(gattNotify2Characteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&echelonconnectsport::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&echelonconnectsport::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &echelonconnectsport::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&echelonconnectsport::descriptorWritten);
}
}
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !this->hasVirtualDevice()
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !this->hasVirtualDevice()
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_rower =
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_rower =
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).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
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) {
if (virtual_device_rower) {
qDebug() << QStringLiteral("creating virtual rower interface...");
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
} else {
qDebug() << QStringLiteral("creating virtual bike interface...");
auto virtualBike =
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
// connect(virtualBike,&virtualbike::debug ,this,&echelonconnectsport::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &echelonconnectsport::changeInclination);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
}
if (virtual_device_enabled) {
if (virtual_device_rower) {
qDebug() << QStringLiteral("creating virtual rower interface...");
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
} else {
qDebug() << QStringLiteral("creating virtual bike interface...");
auto virtualBike =
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
// connect(virtualBike,&virtualbike::debug ,this,&echelonconnectsport::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &echelonconnectsport::changeInclination);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
}
}
}
firstStateChanged = 1;
// ********************************************************************************************************
}
firstStateChanged = 1;
// ********************************************************************************************************
if (!useNativeIOS && state == QLowEnergyService::ServiceDiscovered) {
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
@@ -513,26 +465,17 @@ void echelonconnectsport::characteristicWritten(const QLowEnergyCharacteristic &
void echelonconnectsport::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
QSettings settings;
bool useNativeIOS = false;
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("0bf669f1-45f2-11e7-9598-0800200c9a66"));
#ifdef Q_OS_IOS
useNativeIOS = settings.value(QZSettings::ios_btdevice_native, QZSettings::default_ios_btdevice_native).toBool();
#endif
if (!useNativeIOS) {
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("0bf669f1-45f2-11e7-9598-0800200c9a66"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&echelonconnectsport::stateChanged);
if(gattCommunicationChannelService != nullptr) {
gattCommunicationChannelService->discoverDetails();
} else {
if(homeform::singleton())
homeform::singleton()->setToastRequested("Bluetooth Service Error! Restart the bike!");
m_control->disconnectFromDevice();
}
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&echelonconnectsport::stateChanged);
if(gattCommunicationChannelService != nullptr) {
gattCommunicationChannelService->discoverDetails();
} else {
if(homeform::singleton())
homeform::singleton()->setToastRequested("Bluetooth Service Error! Restart the bike!");
m_control->disconnectFromDevice();
}
}
@@ -551,21 +494,6 @@ void echelonconnectsport::error(QLowEnergyController::Error err) {
void echelonconnectsport::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')';
QSettings settings;
bool useNativeIOS = false;
#ifdef Q_OS_IOS
useNativeIOS = settings.value(QZSettings::ios_btdevice_native, QZSettings::default_ios_btdevice_native).toBool();
if (useNativeIOS) {
#ifndef IO_UNDER_QT
iOS_echelonConnectSport = new lockscreen();
iOS_echelonConnectSport->echelonConnectSport(device.name().toStdString().c_str(), this);
return;
#endif
}
#endif
if (device.name().startsWith(QStringLiteral("ECH"))) {
bluetoothDevice = device;
@@ -596,23 +524,13 @@ void echelonconnectsport::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit disconnected();
});
// Connect
// Connect
m_control->connectToDevice();
return;
}
}
bool echelonconnectsport::connected() {
QSettings settings;
bool useNativeIOS = false;
#ifdef Q_OS_IOS
useNativeIOS = settings.value(QZSettings::ios_btdevice_native, QZSettings::default_ios_btdevice_native).toBool();
if (useNativeIOS) {
return true;
}
#endif
if (!m_control) {
return false;
}
@@ -739,21 +657,10 @@ uint16_t echelonconnectsport::wattsFromResistance(double resistance) {
void echelonconnectsport::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
QSettings settings;
bool useNativeIOS = false;
#ifdef Q_OS_IOS
useNativeIOS = settings.value(QZSettings::ios_btdevice_native, QZSettings::default_ios_btdevice_native).toBool();
#endif
if (state == QLowEnergyController::UnconnectedState
&& (m_control || useNativeIOS)
) {
if (state == QLowEnergyController::UnconnectedState && m_control) {
lastResistanceBeforeDisconnection = Resistance.value();
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
if(!useNativeIOS)
m_control->connectToDevice();
m_control->connectToDevice();
}
}

View File

@@ -82,7 +82,6 @@ class echelonconnectsport : public bike {
#ifdef Q_OS_IOS
lockscreen *h = 0;
lockscreen* iOS_echelonConnectSport = nullptr;
#endif
Q_SIGNALS:
@@ -90,14 +89,14 @@ class echelonconnectsport : public bike {
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void controllerStateChanged(QLowEnergyController::ControllerState state);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);

View File

@@ -1,474 +0,0 @@
#include "echelonstairclimber.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualtreadmill.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
using namespace std::chrono_literals;
echelonstairclimber::echelonstairclimber(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;
refresh = new QTimer(this);
initDone = false;
connect(refresh, &QTimer::timeout, this, &echelonstairclimber::update);
refresh->start(pollDeviceTime);
}
void echelonstairclimber::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(this, &echelonstairclimber::packetReceived, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
} else {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
}
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
m_control->state() == QLowEnergyController::UnconnectedState) {
emit debug(QStringLiteral("writeCharacteristic error because the connection is closed"));
return;
}
if (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray((const char *)data, data_len);
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
QStringLiteral(" // ") + info);
}
loop.exec();
if (timeout.isActive() == false) {
emit debug(QStringLiteral(" exit for timeout"));
}
}
void echelonstairclimber::updateDisplay(uint16_t elapsed) {}
void echelonstairclimber::forceSpeed(double requestSpeed) {
uint8_t noOpData[] = {0xf0, 0xb2, 0x02, 0x00, 0x00, 0x00};
noOpData[3] = (uint8_t)(((uint16_t)(requestSpeed * 1000.0)) >> 8);
noOpData[4] = ((uint8_t)(requestSpeed * 1000.0));
for (uint8_t i = 0; i < sizeof(noOpData) - 1; i++) {
noOpData[5] += noOpData[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("force speed"), false, true);
}
void echelonstairclimber::forceIncline(double requestIncline) {
uint8_t noOpData[] = {0xf0, 0xb1, 0x01, 0x00, 0x00};
noOpData[3] = requestIncline;
for (uint8_t i = 0; i < sizeof(noOpData) - 1; i++) {
noOpData[4] += noOpData[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("force incline"), false, true);
}
void echelonstairclimber::sendPoll() {
uint8_t noOpData[] = {0xf0, 0xa0, 0x01, 0x00, 0x00};
noOpData[3] = counterPoll;
for (uint8_t i = 0; i < sizeof(noOpData) - 1; i++) {
noOpData[4] += noOpData[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
counterPoll++;
if (!counterPoll) {
counterPoll = 1;
}
}
void echelonstairclimber::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)
percentage = 0;
changeInclination(grade, percentage);
}
void echelonstairclimber::update() {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
if (initRequest) {
initRequest = false;
btinit();
} else if (/*bluetoothDevice.isValid() &&*/
m_control->state() == QLowEnergyController::DiscoveredState && gattCommunicationChannelService &&
gattWriteCharacteristic.isValid() && gattNotify1Characteristic.isValid() &&
gattNotify2Characteristic.isValid() && initDone) {
QSettings settings;
// ******************************************* virtual treadmill init *************************************
if (!firstInit && !this->hasVirtualDevice()) {
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_force_bike =
settings.value(QZSettings::virtual_device_force_bike, QZSettings::default_virtual_device_force_bike)
.toBool();
if (virtual_device_enabled) {
if (!virtual_device_force_bike) {
debug("creating virtual treadmill interface...");
auto virtualTreadMill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadMill, &virtualtreadmill::debug, this, &echelonstairclimber::debug);
connect(virtualTreadMill, &virtualtreadmill::changeInclination, this,
&echelonstairclimber::changeInclinationRequested);
this->setVirtualDevice(virtualTreadMill, VIRTUAL_DEVICE_MODE::PRIMARY);
} else {
debug("creating virtual bike interface...");
auto virtualBike = new virtualbike(this);
connect(virtualBike, &virtualbike::changeInclination, this,
&echelonstairclimber::changeInclinationRequested);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
}
firstInit = 1;
}
}
// ********************************************************************************************************
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
// updating the treadmill console every second
if (sec1Update++ >= (2000 / refresh->interval())) {
sec1Update = 0;
sendPoll();
}
/*
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
forceSpeed(requestSpeed);
}
requestSpeed = -1;
}
if (requestInclination != -100) {
if (requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
forceIncline(requestInclination);
}
requestInclination = -100;
}
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
if (lastSpeed == 0.0) {
lastSpeed = 0.5;
}
uint8_t initData3[] = {0xf0, 0xb0, 0x01, 0x01, 0xa2};
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("start"), false, true);
if(stride4) {
uint8_t initData0[] = {0xf0, 0xa5, 0x00, 0x95};
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("start"), false, false);
}
uint8_t initData4[] = {0xf0, 0xd0, 0x01, 0x00, 0xc1};
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("start"), false, false);
uint8_t initData5[] = {0xf0, 0xd0, 0x01, 0x11, 0xd2};
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("start"), false, false);
if(stride4) {
uint8_t initData0[] = {0xf0, 0xd3, 0x02, 0x01, 0xf4, 0xba};
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("start"), false, false);
}
lastStart = QDateTime::currentMSecsSinceEpoch();
requestStart = -1;
emit tapeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
uint8_t initData3[] = {0xf0, 0xb0, 0x01, 0x00, 0xa1};
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("stop"), false, true);
requestStop = -1;
lastStop = QDateTime::currentMSecsSinceEpoch();
}
if (requestFanSpeed != -1) {
emit debug(QStringLiteral("changing fan speed..."));
// sendChangeFanSpeed(requestFanSpeed);
requestFanSpeed = -1;
}
if (requestIncreaseFan != -1) {
emit debug(QStringLiteral("increasing fan speed..."));
// sendChangeFanSpeed(FanSpeed + 1);
requestIncreaseFan = -1;
} else if (requestDecreaseFan != -1) {
emit debug(QStringLiteral("decreasing fan speed..."));
// sendChangeFanSpeed(FanSpeed - 1);
requestDecreaseFan = -1;
}*/
}
}
void echelonstairclimber::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
void echelonstairclimber::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
QDateTime now = QDateTime::currentDateTime();
// 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();
Q_UNUSED(characteristic);
QByteArray value = newValue;
qDebug() << QStringLiteral(" << ") + newValue.toHex(' ');
lastPacket = newValue;
if (((unsigned char)newValue.at(0)) == 0xf0 && ((unsigned char)newValue.at(1)) == 0xd1) {
uint16_t convertedData = (newValue.at(5) << 8) | ((uint8_t)newValue.at(6));
StepCount = convertedData;
convertedData = (newValue.at(7) << 8) | ((uint8_t)newValue.at(8));
elevationAcc = convertedData;
Cadence = (uint8_t)newValue.at(9);
Speed = Cadence.value() / 3.2;
qDebug() << QStringLiteral("Current Floors: ") + QString::number(StepCount.value() / 10.0);
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
qDebug() << QStringLiteral("Current StepCount: ") + QString::number(StepCount.value());
qDebug() << QStringLiteral("Current elevationAcc: ") + QString::number(elevationAcc.value());
return;
}
/*if (newValue.length() != 21)
return;*/
/*if ((uint8_t)(newValue.at(0)) != 0xf0 && (uint8_t)(newValue.at(1)) != 0xd1)
return;*/
if (!firstCharacteristicChanged) {
if (watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
KCal +=
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600.0) /
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(now))));
}
if ((uint8_t)newValue.at(1) == 0xD1 && newValue.length() > 11)
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
{
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
uint8_t heart = ((uint8_t)newValue.at(11));
if (heart == 0) {
update_hr_from_external();
} else {
Heart = heart;
}
}
}
qDebug() << QStringLiteral("Current Heart: ") + QString::number(Heart.value());
qDebug() << QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value());
qDebug() << QStringLiteral("Current Watt: ") +
QString::number(watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
if (m_control->error() != QLowEnergyController::NoError)
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
lastTimeCharacteristicChanged = now;
firstCharacteristicChanged = false;
}
void echelonstairclimber::btinit() {
uint8_t initData0[] = {0xf0, 0xa4, 0x00, 0x94};
uint8_t initData1[] = {0xf0, 0xe0, 0x85, 0xf8, 0x03, 0x68, 0xd2, 0x8a};
uint8_t initData2[] = {0xf0, 0xe0, 0x38, 0x04, 0x03, 0xb5, 0x14, 0xd8};
uint8_t initData3[] = {0xf0, 0xa1, 0x00, 0x91};
uint8_t initData4[] = {0xf0, 0xa3, 0x00, 0x93};
uint8_t initData5[] = {0xf0, 0xb0, 0x01, 0x01, 0xa2};
uint8_t initData6[] = {0xf0, 0xa5, 0x00, 0x95};
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData0, sizeof(initData0), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, true);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, true);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, true);
initDone = true;
}
void echelonstairclimber::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("0bf669f2-45f2-11e7-9598-0800200c9a66"));
QBluetoothUuid _gattNotify1CharacteristicId(QStringLiteral("0bf669f3-45f2-11e7-9598-0800200c9a66"));
QBluetoothUuid _gattNotify2CharacteristicId(QStringLiteral("0bf669f4-45f2-11e7-9598-0800200c9a66"));
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
gattNotify2Characteristic = gattCommunicationChannelService->characteristic(_gattNotify2CharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotify1Characteristic.isValid());
Q_ASSERT(gattNotify2Characteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&echelonstairclimber::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&echelonstairclimber::characteristicWritten);
connect(gattCommunicationChannelService, SIGNAL(error(QLowEnergyService::ServiceError)), this,
SLOT(errorService(QLowEnergyService::ServiceError)));
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&echelonstairclimber::descriptorWritten);
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
gattCommunicationChannelService->writeDescriptor(
gattNotify2Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void echelonstairclimber::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
initRequest = true;
emit connectedAndDiscovered();
}
void echelonstairclimber::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
}
void echelonstairclimber::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
auto services_list = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services_list)) {
qDebug() << s << "service found!";
}
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("0bf669f1-45f2-11e7-9598-0800200c9a66"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if(gattCommunicationChannelService == nullptr) {
qDebug() << "invalid service";
return;
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &echelonstairclimber::stateChanged);
gattCommunicationChannelService->discoverDetails();
}
void echelonstairclimber::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("echelonstairclimber::errorService ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void echelonstairclimber::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("echelonstairclimber::error ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void echelonstairclimber::deviceDiscovered(const QBluetoothDeviceInfo &device) {
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &echelonstairclimber::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &echelonstairclimber::serviceScanDone);
connect(m_control, SIGNAL(error(QLowEnergyController::Error)), this, SLOT(error(QLowEnergyController::Error)));
connect(m_control, &QLowEnergyController::stateChanged, this, &echelonstairclimber::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
emit debug(QStringLiteral("Cannot connect to remote device."));
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("Controller connected. Search services..."));
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("LowEnergy controller disconnected"));
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
void echelonstairclimber::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
m_control->connectToDevice();
}
}
bool echelonstairclimber::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}

View File

@@ -1,104 +0,0 @@
#ifndef ECHELONSTAIRCLIMBER_H
#define ECHELONSTAIRCLIMBER_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#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 <QObject>
#include <QString>
#include "stairclimber.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class echelonstairclimber : public stairclimber {
Q_OBJECT
public:
echelonstairclimber(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false,
double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
bool connected() override;
private:
double GetSpeedFromPacket(QByteArray packet);
double GetInclinationFromPacket(QByteArray packet);
double GetKcalFromPacket(QByteArray packet);
double GetDistanceFromPacket(QByteArray packet);
void forceSpeed(double requestSpeed);
void forceIncline(double requestIncline);
void updateDisplay(uint16_t elapsed);
void btinit();
void sendPoll();
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
bool noConsole = false;
bool noHeartService = false;
uint32_t pollDeviceTime = 200;
uint8_t sec1Update = 0;
uint8_t firstInit = 0;
uint8_t counterPoll = 1;
QByteArray lastPacket;
QDateTime lastTimeCharacteristicChanged;
bool firstCharacteristicChanged = true;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotify2Characteristic;
bool initDone = false;
bool initRequest = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
signals:
void disconnected();
void debug(QString string);
void speedChanged(double speed);
void packetReceived();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void changeInclinationRequested(double grade, double percentage);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // ECHELONSTAIRCLIMBER_H

View File

@@ -1,304 +0,0 @@
#include "homeform.h"
#include "elitesquarecontroller.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QEventLoop>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
using namespace std::chrono_literals;
#ifdef Q_OS_IOS
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
// Define static constants
const QString elitesquarecontroller::DEVICE_NAME = "SQUARE";
const QBluetoothUuid elitesquarecontroller::SERVICE_UUID = QBluetoothUuid(QStringLiteral("347b0001-7635-408b-8918-8ff3949ce592"));
const QBluetoothUuid elitesquarecontroller::CHARACTERISTIC_UUID = QBluetoothUuid(QStringLiteral("347b0045-7635-408b-8918-8ff3949ce592"));
elitesquarecontroller::elitesquarecontroller(bluetoothdevice *parentDevice) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
this->parentDevice = parentDevice;
// Initialize button state vector (24 buttons from 0-23)
buttonState.resize(24);
buttonState.fill(0);
// Set up refresh timer
refresh = new QTimer(this);
connect(refresh, &QTimer::timeout, this, &elitesquarecontroller::update);
refresh->start(1000ms);
}
void elitesquarecontroller::update() {
// Just a simple heartbeat check - no handshake needed for Elite Square
if (m_control && m_control->state() == QLowEnergyController::UnconnectedState) {
// Try to reconnect if disconnected
qDebug() << QStringLiteral("Elite Square disconnected, attempting to reconnect...");
m_control->connectToDevice();
}
}
void elitesquarecontroller::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
// Check if the discovered service is the Elite Square service
if (gatt == SERVICE_UUID) {
qDebug() << QStringLiteral("Elite Square service discovered");
}
}
void elitesquarecontroller::disconnectBluetooth() {
qDebug() << QStringLiteral("elitesquarecontroller::disconnect") << m_control;
if (m_control) {
m_control->disconnectFromDevice();
}
}
void elitesquarecontroller::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit packetReceived();
qDebug() << QStringLiteral(" << ") << newValue.toHex(' ') << QString(newValue);
// Process the Elite Square button data
if (characteristic.uuid() == CHARACTERISTIC_UUID) {
// Process the raw bytes directly
parseButtonData(newValue);
}
}
void elitesquarecontroller::parseButtonData(const QByteArray &data) {
// The data comes as raw bytes, with 11 bytes total
if (data.size() < 11) {
qDebug() << QStringLiteral("Invalid button data size: ") << data.size();
return;
}
// Log the hex data for better debugging
qDebug() << QStringLiteral("Processing button data: ") << data.toHex(' ');
// Extract the important bytes (based on the provided Android code)
uint8_t leftByte = data[5];
uint8_t rightByte = data[10];
// Extract the nibbles from these bytes
uint8_t leftLargeValue = (leftByte >> 4) & 0xF; // Left Shift Button 1
uint8_t leftSmallValue = leftByte & 0xF; // Left Shift Button 2
uint8_t rightLargeValue = (rightByte >> 4) & 0xF; // Right Shift Button 1
uint8_t rightSmallValue = rightByte & 0xF; // Right Shift Button 2
qDebug() << QStringLiteral("Button values: leftLarge=") << leftLargeValue
<< QStringLiteral(" leftSmall=") << leftSmallValue
<< QStringLiteral(" rightLarge=") << rightLargeValue
<< QStringLiteral(" rightSmall=") << rightSmallValue;
// Check if the left large shift button (Left Shift Button 1) is pressed
// According to the code, odd values indicate press events
if (leftLargeValue != buttonState[BUTTON_LEFT_SHIFT_1] && leftLargeValue % 2 == 1) {
qDebug() << QStringLiteral("Left Shift Button 1 pressed (shift down 1)");
emit minus();
}
buttonState[BUTTON_LEFT_SHIFT_1] = leftLargeValue;
// Check if the left small shift button (Left Shift Button 2) is pressed
if (leftSmallValue != buttonState[BUTTON_LEFT_SHIFT_2] && leftSmallValue % 2 == 1) {
qDebug() << QStringLiteral("Left Shift Button 2 pressed (shift down 3)");
// Emit minus three times for triple downshift
emit minus();
emit minus();
emit minus();
}
buttonState[BUTTON_LEFT_SHIFT_2] = leftSmallValue;
// Check if the right large shift button (Right Shift Button 1) is pressed
if (rightLargeValue != buttonState[BUTTON_RIGHT_SHIFT_1] && rightLargeValue % 2 == 1) {
qDebug() << QStringLiteral("Right Shift Button 1 pressed (shift up 1)");
emit plus();
}
buttonState[BUTTON_RIGHT_SHIFT_1] = rightLargeValue;
// Check if the right small shift button (Right Shift Button 2) is pressed
if (rightSmallValue != buttonState[BUTTON_RIGHT_SHIFT_2] && rightSmallValue % 2 == 1) {
qDebug() << QStringLiteral("Right Shift Button 2 pressed (shift up 3)");
// Emit plus three times for triple upshift
emit plus();
emit plus();
emit plus();
}
buttonState[BUTTON_RIGHT_SHIFT_2] = rightSmallValue;
// Check for steering buttons and other controls
// Assuming byte 3 might contain steering information as in our previous implementation
uint8_t controlByte = data[3];
// These conditions would need to be adjusted based on actual behavior
if (controlByte == 0x60 && data[3] != buttonState[BUTTON_X]) {
emit steeringLeft(true);
buttonState[BUTTON_X] = controlByte;
} else if (controlByte == 0x20 && data[3] != buttonState[BUTTON_CIRCLE]) {
// Need to make sure this doesn't conflict with shift button detection
emit steeringRight(true);
buttonState[BUTTON_CIRCLE] = controlByte;
} else if (controlByte == 0x00) {
emit steeringLeft(false);
emit steeringRight(false);
buttonState[BUTTON_X] = 0;
buttonState[BUTTON_CIRCLE] = 0;
}
}
void elitesquarecontroller::stateChanged(QLowEnergyService::ServiceState state) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
qDebug() << QStringLiteral("stateChanged") << s->serviceUuid() << s->state();
if (s->state() != QLowEnergyService::ServiceDiscovered && s->state() != QLowEnergyService::InvalidService) {
qDebug() << QStringLiteral("not all services discovered");
return;
}
}
if (state != QLowEnergyService::ServiceState::ServiceDiscovered) {
qDebug() << QStringLiteral("ignoring this state");
return;
}
qDebug() << QStringLiteral("all services discovered!");
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
if (s->state() == QLowEnergyService::ServiceDiscovered) {
// establish hook into notifications
connect(s, &QLowEnergyService::characteristicChanged, this, &elitesquarecontroller::characteristicChanged);
connect(
s, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &elitesquarecontroller::errorService);
connect(s, &QLowEnergyService::descriptorWritten, this, &elitesquarecontroller::descriptorWritten);
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
// Check if this is the Elite Square service
if (s->serviceUuid() == SERVICE_UUID) {
gattService = s;
// Find the notification characteristic
auto characteristics_list = s->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
if (c.uuid() == CHARACTERISTIC_UUID) {
gattNotifyCharacteristic = c;
// Subscribe to notifications
if ((c.properties() & QLowEnergyCharacteristic::Notify) == QLowEnergyCharacteristic::Notify) {
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
connectionEstablished = true;
qDebug() << QStringLiteral("Elite Square notification subscribed!");
} else {
qDebug() << QStringLiteral("ClientCharacteristicConfiguration is not valid");
}
}
}
}
}
}
}
}
void elitesquarecontroller::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
}
void elitesquarecontroller::serviceScanDone(void) {
emit debug(QStringLiteral("serviceScanDone"));
auto services_list = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services_list)) {
gattCommunicationChannelService.append(m_control->createServiceObject(s));
if (gattCommunicationChannelService.constLast()) {
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
&elitesquarecontroller::stateChanged);
gattCommunicationChannelService.constLast()->discoverDetails();
} else {
m_control->disconnectFromDevice();
}
}
}
void elitesquarecontroller::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("elitesquarecontroller::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void elitesquarecontroller::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("elitesquarecontroller::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void elitesquarecontroller::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
// Check if this is the Elite Square device
if (device.name() == DEVICE_NAME) {
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &elitesquarecontroller::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &elitesquarecontroller::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &elitesquarecontroller::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &elitesquarecontroller::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
emit debug(QStringLiteral("Cannot connect to Elite Square device."));
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("Elite Square controller connected. Searching services..."));
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("Elite Square controller disconnected"));
connectionEstablished = false;
emit disconnected();
});
// Connect to the device
m_control->connectToDevice();
}
}
bool elitesquarecontroller::connected() {
if (!m_control) {
return false;
}
return connectionEstablished && (m_control->state() == QLowEnergyController::DiscoveredState);
}
void elitesquarecontroller::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
connectionEstablished = false;
m_control->connectToDevice();
}
}

View File

@@ -1,120 +0,0 @@
#ifndef ELITESQUARECONTROLLER_H
#define ELITESQUARECONTROLLER_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#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 <QObject>
#include <QTime>
#include "devices/bluetoothdevice.h"
#include "zwift_play/abstractZapDevice.h"
// Button positions on Elite Square controller
enum EliteSquareButton {
BUTTON_NONE = 0,
// 1 and 2 are N/A
BUTTON_UP = 3,
BUTTON_LEFT = 4,
BUTTON_DOWN = 5,
BUTTON_RIGHT = 6,
BUTTON_X = 7,
BUTTON_SQUARE = 8,
BUTTON_LEFT_CAMPAGNOLO = 9,
BUTTON_LEFT_BRAKE = 10,
BUTTON_LEFT_SHIFT_1 = 11,
BUTTON_LEFT_SHIFT_2 = 12,
BUTTON_Y = 13,
BUTTON_A = 14,
BUTTON_B = 15,
BUTTON_Z = 16,
BUTTON_CIRCLE = 17,
BUTTON_TRIANGLE = 18,
// 19 is N/A
BUTTON_RIGHT_CAMPAGNOLO = 20,
BUTTON_RIGHT_BRAKE = 21,
BUTTON_RIGHT_SHIFT_1 = 22,
BUTTON_RIGHT_SHIFT_2 = 23
};
// Button state
enum EliteSquareButtonState {
RELEASED = 0,
PRESSED = 1
};
class elitesquarecontroller : public bluetoothdevice {
Q_OBJECT
public:
// Constants for Elite Square device
static const QString DEVICE_NAME;
static const QBluetoothUuid SERVICE_UUID;
static const QBluetoothUuid CHARACTERISTIC_UUID;
elitesquarecontroller(bluetoothdevice *parentDevice);
bool connected() override;
private:
QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattNotifyCharacteristic;
QLowEnergyService *gattService;
bluetoothdevice *parentDevice = nullptr;
bool connectionEstablished = false;
QTimer *refresh;
// Last known state of each button (0-23)
QVector<int> buttonState;
// Helper function to parse the button data
void parseButtonData(const QByteArray &data);
signals:
void disconnected();
void debug(QString string);
void packetReceived();
// Match AbstractZapDevice signals
void plus(); // Gear up/increase
void minus(); // Gear down/decrease
void steeringLeft(bool active); // X button and Left Campagnolo
void steeringRight(bool active); // Circle button and Right Campagnolo
void buttonActivated(int buttonId, bool pressed); // General button signal
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void disconnectBluetooth();
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
};
#endif // ELITESQUARECONTROLLER_H

View File

@@ -106,12 +106,8 @@ void eslinkertreadmill::writeCharacteristic(uint8_t *data, uint8_t data_len, con
}
writeBuffer = new QByteArray((const char *)data, data_len);
if (gattWriteCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer,
QLowEnergyService::WriteWithoutResponse);
} else {
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
}
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer,
QLowEnergyService::WriteWithoutResponse);
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
@@ -708,8 +704,6 @@ void eslinkertreadmill::stateChanged(QLowEnergyService::ServiceState state) {
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotifyCharacteristic.isValid());
qDebug() << (gattWriteCharacteristic.properties() & QLowEnergyService::WriteWithoutResponse);
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&eslinkertreadmill::characteristicChanged);

View File

@@ -210,7 +210,7 @@ void fitshowtreadmill::update() {
if (requestInclination != inc) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
inc = requestInclination;
double speed = currentSpeed().valueRaw();
double speed = currentSpeed().value();
if (requestSpeed != -1) {
speed = requestSpeed;
requestSpeed = -1;

View File

@@ -34,7 +34,6 @@ ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResis
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
ergModeSupported = true; // by default ftms devices SHOULD have ergMode supported
connect(refresh, &QTimer::timeout, this, &ftmsbike::update);
refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt());
wheelCircumference::GearTable g;
@@ -194,7 +193,7 @@ void ftmsbike::zwiftPlayInit() {
}
void ftmsbike::forcePower(int16_t requestPower) {
if(resistance_lvl_mode || TITAN_7000) {
if(resistance_lvl_mode) {
forceResistance(resistanceFromPowerRequest(requestPower));
} else {
uint8_t write[] = {FTMS_SET_TARGET_POWER, 0x00, 0x00};
@@ -232,31 +231,18 @@ resistance_t ftmsbike::resistanceFromPowerRequest(uint16_t power) {
if (power < wattsFromResistance(1))
return 1;
else
if(DU30_bike)
return max_resistance;
else
return _ergTable.getMaxResistance();
return max_resistance;
}
void ftmsbike::forceResistance(resistance_t requestResistance) {
QSettings settings;
bool ergModeNotSupported = (requestPower > 0 && !ergModeSupported);
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
resistance_lvl_mode == false && _3G_Cardio_RB == false && JFBK5_0 == false) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x28, 0x19};
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
if(ergModeNotSupported) {
requestResistance = _inclinationResistanceTable.estimateInclination(requestResistance) * 10.0;
qDebug() << "ergMode Not Supported so the resistance will be" << requestResistance;
} else {
requestResistance = fr;
}
if(TITAN_7000)
Resistance = requestResistance;
requestResistance = fr;
write[3] = ((uint16_t)requestResistance * 10) & 0xFF;
write[4] = ((uint16_t)requestResistance * 10) >> 8;
@@ -336,18 +322,14 @@ void ftmsbike::update() {
requestResistance = 1;
}
double gearMultiplier = 5;
if(REEBOK)
gearMultiplier = 1;
resistance_t rR = requestResistance + (gears() * gearMultiplier);
resistance_t rR = requestResistance + (gears() * 5);
if (rR != currentResistance().value() || lastGearValue != gears()) {
bool ergModeNotSupported = (requestPower > 0 && !ergModeSupported);
qDebug() << QStringLiteral("writing resistance ") << requestResistance << ergModeNotSupported << requestPower << resistance_lvl_mode;
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
// if the FTMS is connected, the ftmsCharacteristicChanged event will do all the stuff because it's a
// FTMS bike. This condition handles the peloton requests
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike || resistance_lvl_mode || ergModeNotSupported) &&
(requestPower == 0 || requestPower == -1 || resistance_lvl_mode || ergModeNotSupported)) {
// FTMS bike. This condition handles the peloton requests
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike || resistance_lvl_mode) &&
(requestPower == 0 || requestPower == -1)) {
init();
forceResistance(rR);
@@ -497,26 +479,6 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
return;
}
if(T2 && characteristic.uuid() == QBluetoothUuid(QStringLiteral("6e400003-b5a3-f393-e0a9-e50e24dcca9e")) && newValue.length() == 62) {
int16_t gears = ((int16_t)(((int16_t)((uint8_t)newValue.at(55)) << 8) |
(int16_t)((uint8_t)newValue.at(54))));
qDebug() << QStringLiteral("T2 gears event, actual gear") << gears << QStringLiteral("previous value") << T2_lastGear;
if (gears < T2_lastGear) {
for (int i = 0; i < T2_lastGear - gears; ++i) {
gearDown();
}
} else if (gears > T2_lastGear) {
for (int i = 0; i < gears - T2_lastGear; ++i) {
gearUp();
}
}
T2_lastGear = gears;
return;
}
// Wattbike Atom First Generation - Display Gears
if(WATTBIKE && characteristic.uuid() == QBluetoothUuid(QStringLiteral("b4cc1224-bc02-4cae-adb9-1217ad2860d1")) &&
newValue.length() > 3 && newValue.at(1) == 0x03 && (uint8_t)newValue.at(2) == 0xb6) {
@@ -624,11 +586,11 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
double d = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
if(d > 0) {
if(Resistance.value() > 0) {
if(BIKE_)
d = d / 10.0;
// for this bike, i will use the resistance that I set directly because the bike sends a different ratio.
if(!SL010 && !TITAN_7000)
if(!SL010)
Resistance = d;
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
emit resistanceRead(Resistance.value());
@@ -644,18 +606,14 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
double cr = 97.62165482;
if (Cadence.value() && m_watt.value()) {
if(YS_G1MPLUS) {
m_pelotonResistance = Resistance.value(); // 1:1 ratio
} else {
m_pelotonResistance =
(((sqrt(pow(br, 2.0) - 4.0 * ar *
(cr - (m_watt.value() * 132.0 /
(ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
br) /
(2.0 * ar)) *
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
}
m_pelotonResistance =
(((sqrt(pow(br, 2.0) - 4.0 * ar *
(cr - (m_watt.value() * 132.0 /
(ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
br) /
(2.0 * ar)) *
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
if (!resistance_received && !DU30_bike && !SL010) {
Resistance = m_pelotonResistance;
emit resistanceRead(Resistance.value());
@@ -745,181 +703,6 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
lastRefreshCharacteristicChanged2AD2 = now;
ftmsFrameReceived = true;
} else if (characteristic.uuid() == QBluetoothUuid::CyclingPowerMeasurement && !ftmsFrameReceived) {
uint16_t flags = (((uint16_t)((uint8_t)newValue.at(1)) << 8) | (uint16_t)((uint8_t)newValue.at(0)));
bool cadence_present = false;
bool wheel_revs = false;
bool crank_rev_present = false;
uint16_t time_division = 1024;
uint8_t index = 4;
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
if (newValue.length() > 3) {
m_watt = (((uint16_t)((uint8_t)newValue.at(3)) << 8) | (uint16_t)((uint8_t)newValue.at(2)));
}
emit powerChanged(m_watt.value());
emit debug(QStringLiteral("Current watt: ") + QString::number(m_watt.value()));
}
if(THINK_X) {
if ((flags & 0x1) == 0x01) // Pedal Power Balance Present
{
index += 1;
}
if ((flags & 0x2) == 0x02) // Pedal Power Balance Reference
{
}
if ((flags & 0x4) == 0x04) // Accumulated Torque Present
{
index += 2;
}
if ((flags & 0x8) == 0x08) // Accumulated Torque Source
{
}
if ((flags & 0x10) == 0x10) // Wheel Revolution Data Present
{
cadence_present = true;
wheel_revs = true;
}
if ((flags & 0x20) == 0x20) // Crank Revolution Data Present
{
cadence_present = true;
crank_rev_present = true;
}
if (cadence_present) {
if (wheel_revs && !crank_rev_present) {
time_division = 2048;
CrankRevs =
(((uint32_t)((uint8_t)newValue.at(index + 3)) << 24) |
((uint32_t)((uint8_t)newValue.at(index + 2)) << 16) |
((uint32_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint32_t)((uint8_t)newValue.at(index)));
index += 4;
LastCrankEventTime =
(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)));
index += 2; // wheel event time
} else if (wheel_revs && crank_rev_present) {
index += 4; // wheel revs
index += 2; // wheel event time
}
if (crank_rev_present) {
CrankRevs =
(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)));
index += 2;
LastCrankEventTime =
(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)));
index += 2;
}
int16_t deltaT = LastCrankEventTime - oldLastCrankEventTime;
if (deltaT < 0) {
deltaT = LastCrankEventTime + time_division - oldLastCrankEventTime;
}
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
if (CrankRevs != oldCrankRevs && deltaT) {
double cadence = ((CrankRevs - oldCrankRevs) / deltaT) * time_division * 60;
if (!crank_rev_present)
cadence =
cadence /
2; // I really don't like this, there is no relationship between wheel rev and crank rev
if (cadence >= 0) {
Cadence = cadence;
}
lastGoodCadence = now;
} else if (lastGoodCadence.msecsTo(now) > 2000) {
Cadence = 0;
}
}
qDebug() << QStringLiteral("Current Cadence: ") << Cadence.value() << CrankRevs << oldCrankRevs << deltaT
<< time_division << LastCrankEventTime << oldLastCrankEventTime;
oldLastCrankEventTime = LastCrankEventTime;
oldCrankRevs = CrankRevs;
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = Cadence.value() * settings
.value(QZSettings::cadence_sensor_speed_ratio,
QZSettings::default_cadence_sensor_speed_ratio)
.toDouble();
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChangedPower.msecsTo(now)));
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
// if we change this, also change the wattsFromResistance function. We can create a standard function in
// order to have all the costants in one place (I WANT MORE TIME!!!)
double ac = 0.01243107769;
double bc = 1.145964912;
double cc = -23.50977444;
double ar = 0.1469553975;
double br = -5.841344538;
double cr = 97.62165482;
double res =
(((sqrt(pow(br, 2.0) - 4.0 * ar *
(cr - (m_watt.value() * 132.0 /
(ac * pow(Cadence.value(), 2.0) + bc * Cadence.value() + cc)))) -
br) /
(2.0 * ar)) *
settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
if (isnan(res)) {
if (Cadence.value() > 0) {
// let's keep the last good value
} else {
m_pelotonResistance = 0;
}
} else {
m_pelotonResistance = res;
}
qDebug() << QStringLiteral("Current Peloton Resistance: ") + QString::number(m_pelotonResistance.value());
if (settings.value(QZSettings::schwinn_bike_resistance, QZSettings::default_schwinn_bike_resistance)
.toBool())
Resistance = pelotonToBikeResistance(m_pelotonResistance.value());
else
Resistance = m_pelotonResistance;
emit resistanceRead(Resistance.value());
qDebug() << QStringLiteral("Current Resistance Calculated: ") + QString::number(Resistance.value());
if (watts())
KCal +=
((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChangedPower.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight
// in kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
lastRefreshCharacteristicChangedPower = now;
}
}
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACE)) {
union flags {
struct {
@@ -1014,12 +797,9 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
if (Flags.resistanceLvl) {
if(!TITAN_7000) {
Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
emit resistanceRead(Resistance.value());
resistance_received = true;
}
Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
emit resistanceRead(Resistance.value());
index += 2;
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
} else if(!DU30_bike) {
@@ -1129,9 +909,6 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
update_hr_from_external();
}
if(resistance_received && requestPower == -1)
_inclinationResistanceTable.collectData(Inclination.value(), Resistance.value(), m_watt.value());
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
@@ -1197,7 +974,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
}
}
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SCH_190U || DOMYOS || SMB1 || FIT_BK) {
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SCH_190U || DOMYOS || SMB1) {
QBluetoothUuid ftmsService((quint16)0x1826);
if (s->serviceUuid() != ftmsService) {
qDebug() << QStringLiteral("hammer racer bike wants to be subscribed only to FTMS service in order "
@@ -1270,14 +1047,10 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
settings.setValue(QZSettings::domyosbike_notfmts, true);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Domyos bike presents itself like a FTMS but it's not. Restart QZ to apply the fix, thanks.");
} else if(gattFTMSService == nullptr && PM5) {
settings.setValue(QZSettings::ftms_rower, bluetoothDevice.name());
if(homeform::singleton())
homeform::singleton()->setToastRequested("PM5 rower found. Restart QZ to apply the fix, thanks.");
}
if (gattFTMSService && gattWriteCharControlPointId.isValid() &&
(settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SMB1 || FIT_BK)) {
(settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SMB1)) {
init();
}
@@ -1321,10 +1094,9 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
bool ergModeNotSupported = (requestPower > 0 && !ergModeSupported);
if (!autoResistance() || resistance_lvl_mode || ergModeNotSupported) {
qDebug() << "ignoring routing FTMS packet to the bike from virtualbike because of auto resistance OFF or resistance lvl mode is on or ergModeNotSupported"
<< characteristic.uuid() << newValue.toHex(' ') << ergModeNotSupported << resistance_lvl_mode;
if (!autoResistance() || resistance_lvl_mode) {
qDebug() << "ignoring routing FTMS packet to the bike from virtualbike because of auto resistance OFF or resistance lvl mode is on"
<< characteristic.uuid() << newValue.toHex(' ');
return;
}
@@ -1402,7 +1174,7 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
#endif
writeCharacteristicZwiftPlay((uint8_t*)message.data(), message.length(), "gearInclination", false, false);
return;
} else if(b.at(0) == FTMS_SET_TARGET_POWER && ((zwiftPlayService != nullptr && gears_zwift_ratio) || !ergModeSupported)) {
} else if(b.at(0) == FTMS_SET_TARGET_POWER && zwiftPlayService != nullptr && gears_zwift_ratio) {
qDebug() << "discarding";
return;
}
@@ -1522,7 +1294,6 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if((bluetoothDevice.name().toUpper().startsWith("SCH_190U"))) {
qDebug() << QStringLiteral("SCH_190U found");
SCH_190U = true;
max_resistance = 100;
} else if(bluetoothDevice.name().toUpper().startsWith("D2RIDE")) {
qDebug() << QStringLiteral("D2RIDE found");
D2RIDE = true;
@@ -1556,35 +1327,6 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
SL010 = true;
max_resistance = 25;
resistance_lvl_mode = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("REEBOK"))) {
qDebug() << QStringLiteral("REEBOK found");
REEBOK = true;
max_resistance = 32;
resistance_lvl_mode = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("TITAN 7000"))) {
qDebug() << QStringLiteral("Titan 7000 found");
TITAN_7000 = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("T2 "))) {
qDebug() << QStringLiteral("T2 found");
T2 = true;
} else if ((bluetoothDevice.name().toUpper().startsWith(QStringLiteral("FIT-BK-")))) {
qDebug() << QStringLiteral("FIT-BK found");
FIT_BK = true;
ergModeSupported = false; // this bike doesn't have ERG mode natively
} else if ((bluetoothDevice.name().toUpper().startsWith(QStringLiteral("EXPERT-SX9")))) {
qDebug() << QStringLiteral("EXPERT-SX9 found");
EXPERT_SX9 = true;
ergModeSupported = false; // this bike doesn't have ERG mode natively
} else if (((bluetoothDevice.name().toUpper().startsWith("YS_G1MPLUS")))) {
qDebug() << QStringLiteral("YS_G1MPLUS found");
YS_G1MPLUS = true;
max_resistance = 100;
} else if (bluetoothDevice.name().toUpper().startsWith(QStringLiteral("PM5"))) {
PM5 = true;
qDebug() << QStringLiteral("PM5 found");
} else if(device.name().toUpper().startsWith(QStringLiteral("THINK X")) || device.name().toUpper().startsWith(QStringLiteral("THINK-"))) {
THINK_X = true;
qDebug() << "THINK X workaround enabled!";
}
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {

View File

@@ -28,7 +28,6 @@
#include "wheelcircumference.h"
#include "devices/bike.h"
#include "inclinationresistancetable.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
@@ -105,10 +104,8 @@ class ftmsbike : public bike {
uint8_t sec1Update = 0;
QByteArray lastPacket;
QByteArray lastPacketFromFTMS;
QDateTime lastRefreshCharacteristicChangedPower = QDateTime::currentDateTime();
QDateTime lastRefreshCharacteristicChanged2AD2 = QDateTime::currentDateTime();
QDateTime lastRefreshCharacteristicChanged2ACE = QDateTime::currentDateTime();
bool ftmsFrameReceived = false;
uint8_t firstStateChanged = 0;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
@@ -125,7 +122,6 @@ class ftmsbike : public bike {
bool resistance_lvl_mode = false;
bool resistance_received = false;
inclinationResistanceTable _inclinationResistanceTable;
bool DU30_bike = false;
bool ICSE = false;
@@ -142,24 +138,9 @@ class ftmsbike : public bike {
bool SMB1 = false;
bool LYDSTO = false;
bool SL010 = false;
bool REEBOK = false;
bool TITAN_7000 = false;
bool T2 = false;
bool FIT_BK = false;
bool YS_G1MPLUS = false;
bool EXPERT_SX9 = false;
bool PM5 = false;
bool THINK_X = false;
int16_t T2_lastGear = 0;
uint8_t battery_level = 0;
uint16_t oldLastCrankEventTime = 0;
uint16_t oldCrankRevs = 0;
QDateTime lastGoodCadence = QDateTime::currentDateTime();
double lastRawRequestedInclinationValue = -100;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif

View File

@@ -927,10 +927,6 @@ void horizontreadmill::update() {
// updateDisplay(elapsed);
}
// this treadmill can't go below 1
if(mobvoi_tmp_treadmill && requestSpeed < 1)
requestSpeed = -1;
if (requestSpeed != -1) {
bool minSpeed =
fabs(requestSpeed - float_one_point_round(currentSpeed().value())) >= (minStepSpeed() - 0.09);
@@ -1220,7 +1216,7 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
if(!anplus_treadmill && !trx3500_treadmill && !wellfit_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill && !YPOO_MINI_PRO && !T3G_PRO && !T3G_ELITE) {
if(!anplus_treadmill && !trx3500_treadmill && !wellfit_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill && !YPOO_MINI_PRO) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
@@ -1290,7 +1286,7 @@ void horizontreadmill::forceIncline(double requestIncline) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill && !YPOO_MINI_PRO && !T3G_PRO && !T3G_ELITE) {
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill && !YPOO_MINI_PRO) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
@@ -1436,44 +1432,6 @@ void horizontreadmill::forceIncline(double requestIncline) {
writeS[1] = 0x00;
writeS[2] = 0x00;
}
} else if(T3G_PRO || T3G_ELITE) {
if(requestInclination > 0 && requestInclination < 1) {
writeS[1] = 0x10;
writeS[2] = 0x00;
} else if(requestInclination >= 1 && requestInclination < 2) {
writeS[1] = 0x1a;
writeS[2] = 0x00;
} else if(requestInclination >= 2 && requestInclination < 3) {
writeS[1] = 0x24;
writeS[2] = 0x00;
} else if(requestInclination >= 3 && requestInclination < 4) {
writeS[1] = 0x2e;
writeS[2] = 0x00;
} else if(requestInclination >= 4 && requestInclination < 5) {
writeS[1] = 0x38;
writeS[2] = 0x00;
} else if(requestInclination >= 5 && requestInclination < 6) {
writeS[1] = 0x42;
writeS[2] = 0x00;
} else if(requestInclination >= 6 && requestInclination < 7) {
writeS[1] = 0x4c;
writeS[2] = 0x00;
} else if(requestInclination >= 7 && requestInclination < 8) {
writeS[1] = 0x56;
writeS[2] = 0x00;
} else if(requestInclination >= 8 && requestInclination < 9) {
writeS[1] = 0x60;
writeS[2] = 0x00;
} else if(requestInclination >= 9 && requestInclination < 10) {
writeS[1] = 0x6A;
writeS[2] = 0x00;
} else if(requestInclination >= 10 && requestInclination < 11) {
writeS[1] = 0x74;
writeS[2] = 0x00;
} else {
writeS[1] = 0x00;
writeS[2] = 0x00;
}
} else {
if(HORIZON_78AT_treadmill)
requestIncline = requestIncline / 2.0;
@@ -1783,7 +1741,6 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACD)) {
bool horizon_treadmill_7_0_at_24 = settings.value(QZSettings::horizon_treadmill_7_0_at_24, QZSettings::default_horizon_treadmill_7_0_at_24).toBool();
lastPacket = newValue;
// default flags for this treadmill is 84 04
@@ -1824,8 +1781,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// this treadmill sends the speed in miles!
speed *= miles_conversion;
}
if(!mobvoi_tmp_treadmill || (mobvoi_tmp_treadmill && !horizonPaused))
parseSpeed(speed);
parseSpeed(speed);
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
}
@@ -1958,37 +1914,16 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
} else {
emit debug(QStringLiteral("Error on parsing heart!"));
}
index += 1;
// index += 1; //NOTE: clang-analyzer-deadcode.DeadStores
}
}
if (Flags.metabolic) {
index += 1;
// todo
}
if (Flags.elapsedTime) {
if (index + 1 < newValue.length()) {
static uint16_t old_local_elapsed = 0;
static QDateTime last_local_elapsed_change = QDateTime::currentDateTime();
uint16_t local_elapsed = ((((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
qDebug() << QStringLiteral("Local Time Elapsed") << local_elapsed;
// this treadmill sends here if the user is really running
if(old_local_elapsed != local_elapsed) {
last_local_elapsed_change = QDateTime::currentDateTime();
horizonPaused = false;
}
QDateTime current_time = QDateTime::currentDateTime();
// Only if more than 2 seconds have passed since the last update
if(mobvoi_tmp_treadmill && last_local_elapsed_change.secsTo(current_time) >= 2) {
Speed = 0;
horizonPaused = true;
qDebug() << "Forcing Speed to 0 since the treadmill saws that the user is not really running";
}
old_local_elapsed = local_elapsed;
}
index += 2;
// todo
}
if (Flags.remainingTime) {
@@ -2554,12 +2489,6 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (device.name().toUpper().startsWith(QStringLiteral("FIT-"))) {
qDebug() << QStringLiteral("FIT- found");
FIT = true;
} else if (device.name().toUpper().startsWith(QStringLiteral("3G PRO "))) {
qDebug() << QStringLiteral("3G PRO");
T3G_PRO = true;
} else if (device.name().toUpper().startsWith(QStringLiteral("3G ELITE "))) {
qDebug() << QStringLiteral("3G ELITE");
T3G_ELITE = true;
}
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
@@ -2680,7 +2609,7 @@ bool horizontreadmill::autoStartWhenSpeedIsGreaterThenZero() {
// the horizon starts with a strange speed, since that i can auto start (maybe the best way to solve this
// is to understand why it's starting with this strange speed)
if (!horizon_paragon_x && !horizon_treadmill_7_8 && !mobvoi_tmp_treadmill)
if (!horizon_paragon_x && !horizon_treadmill_7_8)
return false;
if ((lastStop == 0 || QDateTime::currentMSecsSinceEpoch() > (lastStop + 25000)) && requestStop == -1)
@@ -3284,7 +3213,7 @@ void horizontreadmill::testProfileCRC() {
double horizontreadmill::minStepInclination() {
QSettings settings;
bool toorx_ftms_treadmill = settings.value(QZSettings::toorx_ftms_treadmill, QZSettings::default_toorx_ftms_treadmill).toBool();
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill || FIT || T3G_PRO || T3G_ELITE)
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill || FIT)
return 1.0;
else
return 0.5;

View File

@@ -108,8 +108,6 @@ class horizontreadmill : public treadmill {
bool YPOO_MINI_PRO = false;
bool MX_TM = false;
bool FIT = false;
bool T3G_PRO = false;
bool T3G_ELITE = false;
void testProfileCRC();
void updateProfileCRC();

View File

@@ -77,13 +77,15 @@ void kineticinroadbike::writeCharacteristic(uint8_t *data, uint8_t data_len, con
}
void kineticinroadbike::forceResistance(resistance_t requestResistance) {
if (noWriteResistance) {
return;
}
// Use Smart Control brake mode for direct resistance control
smart_control_set_mode_brake_data cmd = smart_control_set_mode_brake_command(requestResistance);
writeCharacteristic(cmd.bytes, sizeof(cmd.bytes), QStringLiteral("set brake resistance"), false, true);
/*uint8_t noOpData[] = {0xf0, 0xb1, 0x01, 0x00, 0x00};
noOpData[3] = requestResistance;
for (uint8_t i = 0; i < sizeof(noOpData) - 1; i++) {
noOpData[4] += noOpData[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("force resistance"), false, true);*/
}
void kineticinroadbike::update() {
@@ -106,23 +108,6 @@ void kineticinroadbike::update() {
// updateDisplay(elapsed);
}
if (requestInclination != -100) {
qDebug() << QStringLiteral("writing inclination ") + QString::number(requestInclination);
forceInclination(requestInclination);
if(!VirtualBike() || (VirtualBike() && !VirtualBike()->ftmsDeviceConnected())) {
Inclination = requestInclination;
}
requestInclination = -100;
requestResistance = -1; // Clear resistance request when handling inclination
}
if (requestPower != -1) {
qDebug() << QStringLiteral("writing power ") + QString::number(requestPower);
changePower(requestPower);
requestPower = -1;
requestResistance = -1; // Clear resistance request when handling power
}
if (requestResistance != -1) {
if (requestResistance > max_resistance)
requestResistance = max_resistance;
@@ -135,7 +120,6 @@ void kineticinroadbike::update() {
}
requestResistance = -1;
}
if (requestStart != -1) {
qDebug() << QStringLiteral("starting...");
@@ -152,42 +136,6 @@ void kineticinroadbike::update() {
}
}
void kineticinroadbike::changePower(int32_t power) {
if (noWriteResistance) {
return;
}
RequestedPower = power;
if (power < 0)
power = 0;
// Use Smart Control ERG mode for power-based training
smart_control_set_mode_erg_data cmd = smart_control_set_mode_erg_command((uint16_t)power);
writeCharacteristic(cmd.bytes, sizeof(cmd.bytes), QStringLiteral("set ERG power"), false, true);
}
void kineticinroadbike::forceInclination(double inclination) {
if (noWriteResistance) {
return;
}
// Update resistance to match inclination (for UI consistency)
Resistance = inclination;
// Smart Control simulation parameters
QSettings settings;
float weightKG = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
float rollingCoeff = 0.004f; // Standard rolling resistance for asphalt
float windCoeff = 0.6f; // Standard wind resistance coefficient
float windSpeedMPS = 0.0f; // No headwind/tailwind
// Use Smart Control simulation mode for inclination control
smart_control_set_mode_simulation_data cmd = smart_control_set_mode_simulation_command(
weightKG, rollingCoeff, windCoeff, (float)inclination, windSpeedMPS);
writeCharacteristic(cmd.bytes, sizeof(cmd.bytes), QStringLiteral("set simulation inclination"), false, true);
}
void kineticinroadbike::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
}

View File

@@ -44,11 +44,10 @@ class kineticinroadbike : public bike {
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
resistance_t resistanceFromPowerRequest(uint16_t power) override;
void changePower(int32_t power) override;
bool connected() override;
private:
const resistance_t max_resistance = 999;
const resistance_t max_resistance = 32;
double bikeResistanceToPeloton(double resistance);
double GetDistanceFromPacket(const QByteArray &packet);
QTime GetElapsedFromPacket(const QByteArray &packet);
@@ -57,7 +56,6 @@ class kineticinroadbike : public bike {
bool wait_for_response = false);
void startDiscover();
void forceResistance(resistance_t requestResistance);
void forceInclination(double inclination);
uint16_t watts() override;
QTimer *refresh;

View File

@@ -146,8 +146,6 @@ void kingsmithr2treadmill::update() {
debug("creating virtual treadmill interface...");
auto virtualTreadMill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadMill, &virtualtreadmill::debug, this, &kingsmithr2treadmill::debug);
connect(virtualTreadMill, &virtualtreadmill::changeInclination, this,
&kingsmithr2treadmill::changeInclinationRequested);
this->setVirtualDevice(virtualTreadMill, VIRTUAL_DEVICE_MODE::PRIMARY);
} else {
debug("creating virtual bike interface...");

View File

@@ -82,15 +82,15 @@ void lifefitnesstreadmill::waitForAPacket() {
}
void lifefitnesstreadmill::btinit() {
if (gattWriteChar4CustomService2.isValid()) {
uint8_t initData1[1] = {0x01};
uint8_t initData2a[20] = {0x38, 0x66, 0x65, 0x64, 0x61, 0x38, 0x38, 0x39, 0x31, 0x64,
0x62, 0x61, 0x34, 0x30, 0x31, 0x66, 0x38, 0x39, 0x39, 0x30};
uint8_t initData2b[12] = {0x30, 0x32, 0x33, 0x30, 0x37, 0x39, 0x35, 0x66, 0x30, 0x38, 0x36, 0x30};
uint8_t initData3[1] = {0x00};
uint8_t initData4[7] = {0x00, 0x00, 0x00, 0x01, 0xb8, 0x5b, 0x5d};
uint8_t initData5[1] = {0x02};
uint8_t initData1[1] = {0x01};
uint8_t initData2a[20] = {0x38, 0x66, 0x65, 0x64, 0x61, 0x38, 0x38, 0x39, 0x31, 0x64,
0x62, 0x61, 0x34, 0x30, 0x31, 0x66, 0x38, 0x39, 0x39, 0x30};
uint8_t initData2b[12] = {0x30, 0x32, 0x33, 0x30, 0x37, 0x39, 0x35, 0x66, 0x30, 0x38, 0x36, 0x30};
uint8_t initData3[1] = {0x00};
uint8_t initData4[7] = {0x00, 0x00, 0x00, 0x01, 0xb8, 0x5b, 0x5d};
uint8_t initData5[1] = {0x02};
if (gattWriteChar4CustomService2.isValid()) {
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData1, sizeof(initData1),
QStringLiteral("init"), false, false);
@@ -104,101 +104,9 @@ void lifefitnesstreadmill::btinit() {
QStringLiteral("init"), false, false);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData5, sizeof(initData5),
QStringLiteral("init"), false, false);
} else if(lifet5) {
// From pkt5841 (after first 12 bytes)
uint8_t initData1[5] = {0xf1, 0x00, 0x01, 0x11, 0xee}; // |.@....|
// From pkt5849 (after first 12 bytes)
uint8_t initData2[7] = {0xf1, 0x00, 0x03, 0x02, 0x00, 0x08, 0xf3}; // |.@....|
// From pkt5851 (after first 12 bytes)
uint8_t initData3[21] = {0xf1, 0x00, 0x11, 0x04, 0x00, 0x00, 0x4e, 0x6f, 0x72, 0x77, 0x65,
0x67, 0x69, 0x61, 0x6e, 0x20, 0x34, 0x78, 0x34, 0x00, 0x41}; // |.@....Norweg|, |ian 4x4.A|
// From pkt5854 (after first 12 bytes)
uint8_t initData4[28] = {0xf1, 0x00, 0x18, 0x04, 0x00, 0x01, 0x54, 0x72, 0x65, 0x61, 0x64,
0x6d, 0x69, 0x6c, 0x6c, 0x20, 0x30, 0x33, 0x2f, 0x30, 0x38, 0x2f,
0x32, 0x30, 0x32, 0x34, 0x00, 0x34}; // |.@....Treadm|, |ill 03/0|, |8/2024.4|
// From pkt5856 (after first 12 bytes)
uint8_t initData5[20] = {0xf1, 0x00, 0x10, 0x04, 0x00, 0x02, 0x35, 0x20, 0x78, 0x20, 0x31,
0x6b, 0x6d, 0x20, 0x61, 0x74, 0x20, 0x39, 0x00, 0xa6}; // |.@....5 x 1k|, |m at 9..|
// From pkt5859 (after first 12 bytes)
uint8_t initData6[19] = {0xf1, 0x00, 0x0f, 0x04, 0x00, 0x03, 0x35, 0x20, 0x78, 0x20, 0x31,
0x6b, 0x6d, 0x20, 0x40, 0x20, 0x39, 0x00, 0x3b}; // |.@....5 x 1k|, |m @ 9.;|
// From pkt5861 (after first 12 bytes)
uint8_t initData7[21] = {0xf1, 0x00, 0x11, 0x04, 0x00, 0x04, 0x36, 0x20, 0x78, 0x20, 0x35,
0x30, 0x30, 0x6d, 0x20, 0x40, 0x20, 0x31, 0x30, 0x00, 0x16}; // |.@....6 x 50|, |0m @ 10.|
// From pkt5864 (after first 12 bytes)
uint8_t initData8[20] = {0xf1, 0x00, 0x10, 0x04, 0x00, 0x05, 0x36, 0x20, 0x78, 0x20, 0x38,
0x30, 0x30, 0x6d, 0x20, 0x40, 0x20, 0x39, 0x00, 0x3b}; // |.@....6 x 80|, |0m @ 9.;|
// From pkt5866 (after first 12 bytes)
uint8_t initData9[33] = {0xf1, 0x00, 0x1d, 0x04, 0x00, 0x06, 0x32, 0x30, 0x6d, 0x69, 0x6e,
0x20, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 0x6c, 0x73, 0x20,
0x28, 0x31, 0x30, 0x20, 0x72, 0x75, 0x6e, 0x73, 0x29, 0x00, 0x81}; // |.@....20min |, |intervals|, |s (10 ru|, |ns)..|
// From pkt5869 (after first 12 bytes)
uint8_t initData10[20] = {0xf1, 0x00, 0x10, 0x04, 0x00, 0x07, 0x36, 0x20, 0x78, 0x20, 0x32,
0x20, 0x6d, 0x69, 0x6e, 0x20, 0x40, 0x39, 0x00, 0xc8}; // |.@....6 x 2 |, |min @9..|
// From pkt5876 (after first 12 bytes)
uint8_t initData11[5] = {0x6e, 0x64, 0x65, 0x72, 0xa6}; // |.@nder.|
// From pkt5888 (after first 12 bytes)
uint8_t initData12[5] = {0x4c, 0x49, 0x42, 0x52, 0x8d}; // |.@LIBR.|
// From pkt5894 (after first 12 bytes)
uint8_t initData13[5] = {0x5f, 0x4c, 0x49, 0x42, 0x22}; // |.@_LIB"|
// From pkt5895 (after first 12 bytes)
uint8_t initData14[202] = {0xf1, 0x00, 0xc6, 0x27, 0x02, 0x03, 0x52, 0x41, 0x52, 0x59, 0x22, 0x7d,
0x2c, 0x7b, 0x22, 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x49, 0x64,
0x22, 0x3a, 0x37, 0x2c, 0x22, 0x66, 0x69, 0x6c, 0x65, 0x4e, 0x61, 0x6d,
0x65, 0x22, 0x3a, 0x22, 0x36, 0x20, 0x78, 0x20, 0x32, 0x20, 0x6d, 0x69,
0x6e, 0x20, 0x40, 0x39, 0x2e, 0x35, 0x6d, 0x70, 0x68, 0x20, 0x31, 0x20,
0x6d, 0x69, 0x6e, 0x20, 0x77, 0x61, 0x6c, 0x6b, 0x20, 0x72, 0x65, 0x73,
0x74, 0x22, 0x2c, 0x22, 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x4e,
0x61, 0x6d, 0x65, 0x22, 0x3a, 0x22, 0x36, 0x20, 0x78, 0x20, 0x32, 0x20,
0x6d, 0x69, 0x6e, 0x20, 0x40, 0x39, 0x2e, 0x35, 0x6d, 0x70, 0x68, 0x20,
0x31, 0x20, 0x6d, 0x69, 0x6e, 0x20, 0x77, 0x61, 0x6c, 0x6b, 0x20, 0x72,
0x65, 0x73, 0x74, 0x22, 0x2c, 0x22, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22,
0x3a, 0x37, 0x2c, 0x22, 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x54,
0x79, 0x70, 0x65, 0x22, 0x3a, 0x22, 0x57, 0x4f, 0x52, 0x4b, 0x4f, 0x55,
0x54, 0x5f, 0x4c, 0x49, 0x42, 0x52, 0x41, 0x52, 0x59, 0x22, 0x7d, 0x5d,
0x7d, 0x3c, 0x2f, 0x77, 0x6f, 0x72, 0x6b, 0x6f, 0x75, 0x74, 0x53, 0x75,
0x6d, 0x6d, 0x61, 0x72, 0x69, 0x65, 0x73, 0x3e, 0x0a, 0x3c, 0x2f, 0x64,
0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x3e, 0x0a, 0xe6}; // |.R.@...'|, |..RARY"}|, |,{"worko|, and many more lines
// From pkt6713 (after first 12 bytes)
//uint8_t initData15[5] = {0xf1, 0x00, 0x01, 0x0a, 0xf5}; // |.R.@....|
// From pkt6929 (after first 12 bytes)
//uint8_t initData16[6] = {0xf1, 0x00, 0x02, 0x0c, 0x3e, 0xb4}; // |.R.@....|
// Write all the initialization data sequentially
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData1, sizeof(initData1), QStringLiteral("init lifet5 1"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData2, sizeof(initData2), QStringLiteral("init lifet5 2"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData3, sizeof(initData3), QStringLiteral("init lifet5 3"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData4, sizeof(initData4), QStringLiteral("init lifet5 4"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData5, sizeof(initData5), QStringLiteral("init lifet5 5"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData6, sizeof(initData6), QStringLiteral("init lifet5 6"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData7, sizeof(initData7), QStringLiteral("init lifet5 7"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData8, sizeof(initData8), QStringLiteral("init lifet5 8"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData9, sizeof(initData9), QStringLiteral("init lifet5 9"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData10, sizeof(initData10), QStringLiteral("init lifet5 10"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData11, sizeof(initData11), QStringLiteral("init lifet5 11"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData12, sizeof(initData12), QStringLiteral("init lifet5 12"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData13, sizeof(initData13), QStringLiteral("init lifet5 13"), false, true);
writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData14, sizeof(initData14), QStringLiteral("init lifet5 14"), false, true);
//writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData15, sizeof(initData15), QStringLiteral("init lifet5 15"), false, true);
//writeCharacteristic(gattCustomService1, gattWriteChar1CustomService1, initData16, sizeof(initData16), QStringLiteral("init lifet5 16"), false, true);
}
QByteArray descriptor;
QBluetoothUuid _gattTreadmillDataId((quint16)0x2ACD);
QBluetoothUuid _gattTrainingStatusId((quint16)0x2AD3);
QBluetoothUuid _gattCrossTrainerDataId((quint16)0x2ACE);
@@ -206,13 +114,11 @@ void lifefitnesstreadmill::btinit() {
qDebug() << "gattFTMSService is empty!";
return;
}
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
QLowEnergyCharacteristic gattTreadmillData = gattFTMSService->characteristic(_gattTreadmillDataId);
QLowEnergyCharacteristic gattTrainingStatus = gattFTMSService->characteristic(_gattTrainingStatusId);
QLowEnergyCharacteristic gattCrossTrainerData = gattFTMSService->characteristic(_gattCrossTrainerDataId);
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattFTMSService->writeDescriptor(gattTrainingStatus.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration),
descriptor);
if (gattTreadmillData.isValid()) {
@@ -361,7 +267,7 @@ void lifefitnesstreadmill::characteristicChanged(const QLowEnergyCharacteristic
double heart = 0; // NOTE : Should be initialized with a value to shut clang-analyzer's
// UndefinedBinaryOperatorResult
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
bool distanceEval = false;
QSettings settings;
// bool horizon_paragon_x = settings.value(QZSettings::horizon_paragon_x,
@@ -374,32 +280,7 @@ void lifefitnesstreadmill::characteristicChanged(const QLowEnergyCharacteristic
emit debug(QStringLiteral(" << ") + characteristic.uuid().toString() + " " + QString::number(newValue.length()) +
" " + newValue.toHex(' '));
if (characteristic.uuid() == QBluetoothUuid(QStringLiteral("4a8ff3f1-c933-11e3-9c1a-0800200c9a66")) && newValue.length() == 40) {
Speed = ((double)newValue.at(32)) / 10.0;
Inclination = ((double)newValue.at(27)) / 10.0;
distanceEval = true;
if (firstDistanceCalculated) {
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
if(watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
KCal +=
((((0.048 *
((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
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
}
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
#endif
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACD)) {
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACD)) {
lastPacket = newValue;
// default flags for this treadmill is 84 04
@@ -748,7 +629,6 @@ void lifefitnesstreadmill::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteChar2CustomService1(QStringLiteral("5da569e2-9cb2-11e5-8994-feff819cdc9f"));
QBluetoothUuid _gattWriteChar3CustomService2(QStringLiteral("5da54cf2-9cb2-11e5-8994-feff819cdc9f"));
QBluetoothUuid _gattWriteChar4CustomService2(QStringLiteral("ce78e85e-9cb6-11e5-8994-feff819cdc9f"));
QBluetoothUuid _gattWriteChar5CustomService3(QStringLiteral("c52d3161-d1a1-11e3-9c1a-0800200c9a66"));
QBluetoothUuid _gattWriteCharControlPointId((quint16)0x2AD9);
QBluetoothUuid _gattTreadmillDataId((quint16)0x2ACD);
QBluetoothUuid _gattCrossTrainerDataId((quint16)0x2ACE);
@@ -816,12 +696,6 @@ void lifefitnesstreadmill::stateChanged(QLowEnergyService::ServiceState state) {
qDebug() << QStringLiteral("Custom service and Control Point 4 found");
gattWriteChar4CustomService2 = c;
gattCustomService2 = s;
} else if (c.uuid() == _gattWriteChar5CustomService3) {
qDebug() << QStringLiteral("Custom service and Control Point found");
gattWriteChar1CustomService1 = c;
gattCustomService1 = s;
lifet5 = true;
qDebug() << "Life Fitness T5 workaround enabled!";
}
}
}
@@ -862,23 +736,6 @@ void lifefitnesstreadmill::stateChanged(QLowEnergyService::ServiceState state) {
// ********************************************************************************************************
}
if(lifet5) {
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
QLowEnergyCharacteristic gattTreadmillData1 = gattCustomService1->characteristic(QBluetoothUuid(QStringLiteral("4a8ff3f1-c933-11e3-9c1a-0800200c9a66")));
qDebug() << gattTreadmillData1.isValid();
foreach(QLowEnergyCharacteristic c, gattCustomService1->characteristics()) {
qDebug() << c.uuid();
}
if (gattTreadmillData1.isValid()) {
qDebug() << "writing descriptor";
gattCustomService1->writeDescriptor(gattTreadmillData1.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
initRequest = true;
emit connectedAndDiscovered();
}

View File

@@ -84,8 +84,6 @@ class lifefitnesstreadmill : public treadmill {
bool noWriteResistance = false;
bool noHeartService = false;
bool lifet5 = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif

View File

@@ -1,221 +0,0 @@
#include "moxy5sensor.h"
#include "homeform.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QEventLoop>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
moxy5sensor::moxy5sensor() :
m_currentSaturatedHemoglobin(0.0),
m_previousSaturatedHemoglobin(0.0),
m_totalHemoglobinConcentration(0.0) {
}
moxy5sensor::~moxy5sensor() {
}
void moxy5sensor::update() {
QSettings settings;
}
void moxy5sensor::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
}
void moxy5sensor::disconnectBluetooth() {
qDebug() << QStringLiteral("moxy5sensor::disconnect") << m_control;
if (m_control) {
m_control->disconnectFromDevice();
}
}
void moxy5sensor::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit packetReceived();
qDebug() << QStringLiteral(" << ") + newValue.toHex(' ');
// Check if there's enough data in the packet
if (newValue.length() >= 8) {
// Parse data according to Moxy 5 format
// Assuming data is organized as percentages or scaled values
// Extract values from packet bytes
// Note: this parsing is an assumption and should be adapted to the actual Moxy 5 data structure
double currentSaturation = (double)((uint16_t)((uint8_t)newValue[2] | ((uint8_t)newValue[3] << 8))) / 100.0;
double previousSaturation = (double)((uint16_t)((uint8_t)newValue[4] | ((uint8_t)newValue[5] << 8))) / 100.0;
double totalHemoglobin = (double)((uint16_t)((uint8_t)newValue[6] | ((uint8_t)newValue[7] << 8))) / 10.0;
// Update internal variables
if (m_currentSaturatedHemoglobin != currentSaturation) {
m_currentSaturatedHemoglobin = currentSaturation;
emit currentSaturatedHemoglobinChanged(m_currentSaturatedHemoglobin);
}
if (m_previousSaturatedHemoglobin != previousSaturation) {
m_previousSaturatedHemoglobin = previousSaturation;
emit previousSaturatedHemoglobinChanged(m_previousSaturatedHemoglobin);
}
if (m_totalHemoglobinConcentration != totalHemoglobin) {
m_totalHemoglobinConcentration = totalHemoglobin;
emit totalHemoglobinConcentrationChanged(m_totalHemoglobinConcentration);
}
// Debug log
qDebug() << QStringLiteral("Current SmO2: ") + QString::number(m_currentSaturatedHemoglobin, 'f', 2) + "% " +
QStringLiteral("Previous SmO2: ") + QString::number(m_previousSaturatedHemoglobin, 'f', 2) + "% " +
QStringLiteral("THb: ") + QString::number(m_totalHemoglobinConcentration, 'f', 2) + " g/dL";
homeform::singleton()->setToastRequested(QStringLiteral("Current SmO2: ") + QString::number(m_currentSaturatedHemoglobin, 'f', 2) + "% " +
QStringLiteral("Previous SmO2: ") + QString::number(m_previousSaturatedHemoglobin, 'f', 2) + "% " +
QStringLiteral("THb: ") + QString::number(m_totalHemoglobinConcentration, 'f', 2) + " g/dL");
}
}
void moxy5sensor::stateChanged(QLowEnergyService::ServiceState state) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (state == QLowEnergyService::ServiceDiscovered) {
auto characteristics_list = gattCommunicationChannelService->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("characteristic ") + c.uuid().toString();
}
// Trova la caratteristica del sensore Moxy
gattNotifyCharacteristic =
gattCommunicationChannelService->characteristic(QBluetoothUuid(QString(MOXY5_CHARACTERISTIC_UUID)));
if(!gattNotifyCharacteristic.isValid()) {
qDebug() << "gattNotifyCharacteristic not valid for Moxy5";
return;
}
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&moxy5sensor::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&moxy5sensor::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &moxy5sensor::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&moxy5sensor::descriptorWritten);
// Abilita le notifiche per questa caratteristica
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotifyCharacteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void moxy5sensor::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' ');
}
void moxy5sensor::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
Q_UNUSED(characteristic);
qDebug() << QStringLiteral("characteristicWritten ") + newValue.toHex(' ');
}
void moxy5sensor::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
auto services_list = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services_list)) {
qDebug() << QStringLiteral("moxy5sensor services ") << s.toString();
// Cerca il servizio specifico del Moxy 5
if (s == QBluetoothUuid(QString(MOXY5_SERVICE_UUID))) {
QBluetoothUuid _gattCommunicationChannelServiceId(QString(MOXY5_SERVICE_UUID));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&moxy5sensor::stateChanged);
gattCommunicationChannelService->discoverDetails();
return;
}
}
}
void moxy5sensor::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
qDebug() << QStringLiteral("moxy5sensor::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void moxy5sensor::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
qDebug() << QStringLiteral("moxy5sensor::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void moxy5sensor::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QSettings settings;
qDebug() << QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')';
if(homeform::singleton())
homeform::singleton()->setToastRequested(device.name() + QStringLiteral(" connected!"));
// Controlla se il dispositivo è un Moxy 5 o semplicemente collegati a qualsiasi dispositivo che trovi
// (qui puoi aggiungere un filtro per il nome se necessario)
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &moxy5sensor::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &moxy5sensor::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &moxy5sensor::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &moxy5sensor::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
qDebug() << QStringLiteral("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
}
bool moxy5sensor::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void moxy5sensor::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
m_control->connectToDevice();
}
}

View File

@@ -1,90 +0,0 @@
#ifndef MOXY5SENSOR_H
#define MOXY5SENSOR_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#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 <QObject>
#include <QTime>
#include <QDateTime>
#include "treadmill.h"
// UUID costanti per Moxy 5
#define MOXY5_SERVICE_UUID "6404D801-4CB9-11E8-B566-0800200C9A66"
#define MOXY5_CHARACTERISTIC_UUID "6404D804-4CB9-11E8-B566-0800200C9A66"
class moxy5sensor : public treadmill {
Q_OBJECT
// Properties exposed for data access
Q_PROPERTY(double currentSaturatedHemoglobin READ getCurrentSaturatedHemoglobin NOTIFY currentSaturatedHemoglobinChanged)
Q_PROPERTY(double previousSaturatedHemoglobin READ getPreviousSaturatedHemoglobin NOTIFY previousSaturatedHemoglobinChanged)
Q_PROPERTY(double totalHemoglobinConcentration READ getTotalHemoglobinConcentration NOTIFY totalHemoglobinConcentrationChanged)
public:
moxy5sensor();
~moxy5sensor();
bool connected() override;
// Getters for properties
double getCurrentSaturatedHemoglobin() const { return m_currentSaturatedHemoglobin; }
double getPreviousSaturatedHemoglobin() const { return m_previousSaturatedHemoglobin; }
double getTotalHemoglobinConcentration() const { return m_totalHemoglobinConcentration; }
private:
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattNotifyCharacteristic;
// Variables for Moxy sensor data
double m_currentSaturatedHemoglobin;
double m_previousSaturatedHemoglobin;
double m_totalHemoglobinConcentration;
signals:
void disconnected();
void debug(QString string);
void packetReceived();
void heartRate(uint8_t heart) override; // Kept for compatibility with treadmill
// Signals for property change notifications
void currentSaturatedHemoglobinChanged(double value);
void previousSaturatedHemoglobinChanged(double value);
void totalHemoglobinConcentrationChanged(double value);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void disconnectBluetooth();
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // MOXY5SENSOR_H

View File

@@ -251,7 +251,7 @@ void nautiluselliptical::characteristicChanged(const QLowEnergyCharacteristic &c
return;
}
if ((newValue.length() != 14 && (bt_variant == 0 || bt_variant == 2)) || (newValue.length() != 12 && bt_variant == 1)) {
if ((newValue.length() != 14 && bt_variant == 0) || (newValue.length() != 12 && bt_variant == 1)) {
return;
}
@@ -273,11 +273,7 @@ void nautiluselliptical::characteristicChanged(const QLowEnergyCharacteristic &c
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
if(bt_variant == 2) {
Cadence = ((uint8_t)newValue.at(1));
} else {
Cadence = ((uint8_t)newValue.at(5));
}
Cadence = ((uint8_t)newValue.at(5));
}
// m_watt = watt;
Speed = speed;
@@ -308,14 +304,11 @@ double nautiluselliptical::GetSpeedFromPacket(const QByteArray &packet) {
uint16_t convertedData = 0;
double data = 0;
if (bt_variant == 0 || bt_variant == 2) {
convertedData = (packet.at(4) << 8) | ((uint8_t)packet.at(3));
if (bt_variant == 0) {
convertedData = (packet.at(4) << 8) | packet.at(3);
data = (double)convertedData / 100.0f;
if(bt_variant == 2) {
data = data * 1.60934f;
}
} else {
convertedData = (packet.at(8) << 8) | ((uint8_t)packet.at(7));
} else {
convertedData = (packet.at(8) << 8) | packet.at(7);
data = (double)convertedData / 10.0f;
}
@@ -439,17 +432,8 @@ void nautiluselliptical::serviceScanDone(void) {
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService == nullptr) {
if (gattCommunicationChannelService == nullptr) {
qDebug() << QStringLiteral("backup UUID not found, trying the 4th fallback...");
bt_variant = 2;
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("7DF7A3F7-F013-492F-A58E-68A8F078AB96"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService == nullptr) {
qDebug() << QStringLiteral("neither the fallback worked, exiting...");
return;
}
}
qDebug() << QStringLiteral("neither the fallback worked, exiting...");
return;
}
}
}

View File

@@ -300,7 +300,6 @@ void nordictrackifitadbbike::processPendingDatagrams() {
settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote)
.toBool();
double inclination_delay_seconds = settings.value(QZSettings::inclination_delay_seconds, QZSettings::default_inclination_delay_seconds).toDouble();
bool proform_tdf_10_0 = settings.value(QZSettings::proform_tdf_10_0, QZSettings::default_proform_tdf_10_0).toBool();
// only resistance
if(proform_studio_NTEX71021 || nordictrackadbbike_resistance) {
@@ -311,21 +310,14 @@ void nordictrackifitadbbike::processPendingDatagrams() {
int y2 = (int)(493 - (13.57 * (requestResistance - 1)));
int y1Resistance = (int)(493 - (13.57 * currentResistance().value()));
if(proform_tdf_10_0) {
x1 = 1175;
y2 = (int)(590 - (15.91 * requestResistance));
y1Resistance = (int)(590 - (15.91 * currentResistance().value()));
Resistance = requestResistance;
emit resistanceRead(Resistance.value());
}
else if(!proform_studio_NTEX71021) { // s22i default
if(!proform_studio_NTEX71021) { // s22i default
x1 = 1920 - 75;
y2 = (int)(803 - (23.777 * requestResistance));
y1Resistance = (int)(803 - (23.777 * currentResistance().value()));
Resistance = requestResistance;
emit resistanceRead(Resistance.value());
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
@@ -366,6 +358,7 @@ void nordictrackifitadbbike::processPendingDatagrams() {
double inc = qRound(requestInclination / 0.5) * 0.5;
if (inc != currentInclination().value()) {
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_tdf_10_0 = settings.value(QZSettings::proform_tdf_10_0, QZSettings::default_proform_tdf_10_0).toBool();
int x1 = 75;
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));

View File

@@ -18,7 +18,7 @@ nordictrackifitadbellipticalLogcatAdbThread::nordictrackifitadbellipticalLogcatA
void nordictrackifitadbellipticalLogcatAdbThread::run() {
QSettings settings;
QString ip = settings.value(QZSettings::proform_elliptical_ip, QZSettings::default_proform_elliptical_ip).toString();
QString ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
runAdbCommand("connect " + ip);
while (1) {
@@ -66,27 +66,17 @@ void nordictrackifitadbellipticalLogcatAdbThread::runAdbTailCommand(QString comm
QStringList lines = output.split('\n', Qt::SplitBehaviorFlags::SkipEmptyParts);
bool wattFound = false;
bool hrmFound = false;
bool cadenceFound = false;
bool resistanceFound = false;
foreach (QString line, lines) {
if (line.contains("Changed KPH") || line.contains("Changed Actual KPH")) {
emit debug(line);
if (line.contains("Changed KPH")) {
emit debug(line);
speed = line.split(' ').last().toDouble();
} else if (line.contains("Changed Grade")) {
emit debug(line);
inclination = line.split(' ').last().toDouble();
} else if (line.contains("Changed RPM")) {
emit debug(line);
cadence = line.split(' ').last().toDouble();
cadenceFound = true;
} else if (line.contains("Changed Watts")) {
emit debug(line);
watt = line.split(' ').last().toDouble();
wattFound = true;
} else if (line.contains("Resistance changed")) {
emit debug(line);
resistance = line.split(' ').last().toDouble();
resistanceFound = true;
} else if (line.contains("HeartRateDataUpdate")) {
emit debug(line);
QStringList splitted = line.split(' ', Qt::SkipEmptyParts);
@@ -97,14 +87,10 @@ void nordictrackifitadbellipticalLogcatAdbThread::runAdbTailCommand(QString comm
}
}
emit onSpeedInclination(speed, inclination);
if (cadenceFound)
emit onCadence(cadence);
if (wattFound)
emit onWatt(watt);
if (hrmFound)
emit onHRM(hrm);
if (resistanceFound)
emit onResistance(resistance);
#ifdef Q_OS_WINDOWS
if(adbCommandPending.length() != 0) {
runAdbCommand(adbCommandPending);
@@ -135,7 +121,7 @@ nordictrackifitadbelliptical::nordictrackifitadbelliptical(bool noWriteResistanc
this->noHeartService = noHeartService;
initDone = false;
connect(refresh, &QTimer::timeout, this, &nordictrackifitadbelliptical::update);
ip = settings.value(QZSettings::proform_elliptical_ip, QZSettings::default_proform_elliptical_ip).toString();
ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
refresh->start(200ms);
socket = new QUdpSocket(this);
@@ -187,14 +173,6 @@ nordictrackifitadbelliptical::nordictrackifitadbelliptical(bool noWriteResistanc
&nordictrackifitadbelliptical::onSpeedInclination);
connect(logcatAdbThread, &nordictrackifitadbellipticalLogcatAdbThread::onWatt, this,
&nordictrackifitadbelliptical::onWatt);*/
connect(logcatAdbThread, &nordictrackifitadbellipticalLogcatAdbThread::onCadence, this,
&nordictrackifitadbelliptical::onCadence);
connect(logcatAdbThread, &nordictrackifitadbellipticalLogcatAdbThread::onSpeedInclination, this,
&nordictrackifitadbelliptical::onSpeedInclination);
connect(logcatAdbThread, &nordictrackifitadbellipticalLogcatAdbThread::onWatt, this,
&nordictrackifitadbelliptical::onWatt);
connect(logcatAdbThread, &nordictrackifitadbellipticalLogcatAdbThread::onResistance, this,
&nordictrackifitadbelliptical::onResistance);
connect(logcatAdbThread, &nordictrackifitadbellipticalLogcatAdbThread::onHRM, this, &nordictrackifitadbelliptical::onHRM);
connect(logcatAdbThread, &nordictrackifitadbellipticalLogcatAdbThread::debug, this, &nordictrackifitadbelliptical::debug);
logcatAdbThread->start();
@@ -206,28 +184,6 @@ nordictrackifitadbelliptical::nordictrackifitadbelliptical(bool noWriteResistanc
}
}
void nordictrackifitadbelliptical::onSpeedInclination(double speed, double inclination) {
if(speed > 0)
speedReadFromTM = true;
Speed = speed;
Inclination = inclination;
}
void nordictrackifitadbelliptical::onWatt(double watt) {
m_watt = watt;
wattReadFromTM = true;
}
void nordictrackifitadbelliptical::onCadence(double cadence) {
Cadence = cadence;
cadenceReadFromTM = true;
}
void nordictrackifitadbelliptical::onResistance(double resistance) {
Resistance = resistance;
resistanceReadFromTM = true;
}
bool nordictrackifitadbelliptical::inclinationAvailableByHardware() {
QSettings settings;
bool proform_studio_NTEX71021 =
@@ -263,7 +219,7 @@ void nordictrackifitadbelliptical::processPendingDatagrams() {
qDebug() << "Port From :: " << port;
qDebug() << "Message :: " << datagram;
QString ip = settings.value(QZSettings::proform_elliptical_ip, QZSettings::default_proform_elliptical_ip).toString();
QString ip = settings.value(QZSettings::tdf_10_ip, QZSettings::default_tdf_10_ip).toString();
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
@@ -282,7 +238,6 @@ void nordictrackifitadbelliptical::processPendingDatagrams() {
if (line.contains(QStringLiteral("Changed KPH")) && !settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
QStringList aValues = line.split(" ");
if (aValues.length()) {
speedReadFromTM = true;
speed = getDouble(aValues.last());
Speed = speed;
}
@@ -291,11 +246,9 @@ void nordictrackifitadbelliptical::processPendingDatagrams() {
if (aValues.length()) {
cadence = getDouble(aValues.last());
Cadence = cadence;
if(!speedReadFromTM) {
Speed = Cadence.value() *
settings.value(QZSettings::cadence_sensor_speed_ratio, QZSettings::default_cadence_sensor_speed_ratio)
.toDouble();
}
Speed = Cadence.value() *
settings.value(QZSettings::cadence_sensor_speed_ratio, QZSettings::default_cadence_sensor_speed_ratio)
.toDouble();
}
} else if (line.contains(QStringLiteral("Changed CurrentGear"))) {
QStringList aValues = line.split(" ");
@@ -337,73 +290,27 @@ void nordictrackifitadbelliptical::processPendingDatagrams() {
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), 20);
}
bool proform_studio_NTEX71021 =
settings.value(QZSettings::proform_studio_NTEX71021, QZSettings::default_proform_studio_NTEX71021).toBool();
bool nordictrack_ifit_adb_remote =
settings.value(QZSettings::nordictrack_ifit_adb_remote, QZSettings::default_nordictrack_ifit_adb_remote)
.toBool();
double inclination_delay_seconds = settings.value(QZSettings::inclination_delay_seconds, QZSettings::default_inclination_delay_seconds).toDouble();
// resistance
if (nordictrack_ifit_adb_remote) {
if (requestResistance != -1) {
if (requestResistance != currentResistance().value()) {
bool nordictrack_fs10i = true; //settings.value(QZSettings::nordictrack_fs10i, QZSettings::default_nordictrack_fs10i).toBool();
int x1 = 1205; // Estimated x-coordinate of the resistance slider (right side)
int y2 = (int)(590 - (15.65 * requestResistance));
int y1Resistance = (int)(590 - (15.65 * currentResistance().value()));
// For resistance slider on NordicTrackFS10i
if(nordictrack_fs10i) {
x1 = 1205; // Estimated x-coordinate of the resistance slider (right side)
y2 = (int)(590 - (15.65 * requestResistance));
y1Resistance = (int)(590 - (15.65 * currentResistance().value()));
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
#ifdef Q_OS_ANDROID
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
"sendCommand", "(Ljava/lang/String;)V",
command.object<jstring>());
#elif defined(Q_OS_WIN)
if (logcatAdbThread)
logcatAdbThread->runCommand("shell " + lastCommand);
#elif defined Q_OS_IOS
#ifndef IO_UNDER_QT
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
}
}
requestResistance = -1;
} else {
QByteArray message = (QString::number(requestResistance).toLocal8Bit()) + ";";
requestResistance = -1;
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
qDebug() << QString::number(ret) + " >> " + message;
}
// since the motor of the bike is slow, let's filter the inclination changes to more than 4 seconds
if (lastInclinationChanged.secsTo(QDateTime::currentDateTime()) > inclination_delay_seconds) {
lastInclinationChanged = QDateTime::currentDateTime();
// only resistance
if(proform_studio_NTEX71021 || nordictrackadbbike_resistance) {
if (nordictrack_ifit_adb_remote) {
if (requestInclination != -100) {
double inc = qRound(requestInclination / 0.5) * 0.5;
if (inc != currentInclination().value()) {
bool nordictrack_fs10i = true; //settings.value(QZSettings::nordictrack_fs10i, QZSettings::default_nordictrack_fs10i).toBool();
int x1 = 75;
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
if (requestResistance != -1) {
if (requestResistance != currentResistance().value()) {
int x1 = 950;
int y2 = (int)(493 - (13.57 * (requestResistance - 1)));
int y1Resistance = (int)(493 - (13.57 * currentResistance().value()));
if(nordictrack_fs10i) {
x1 = 75;
int y_bottom = 585; // y-coordinate for 0% incline
double coefficient = 35.5; // (585-230)/10 pixels per 1% of inclination
y2 = (int)(y_bottom - (coefficient * (inc + gears())));
y1Resistance = (int)(y_bottom - (coefficient * currentInclination().value()));
if(!proform_studio_NTEX71021) { // s22i default
x1 = 1920 - 75;
y2 = (int)(803 - (23.777 * requestResistance));
y1Resistance = (int)(803 - (23.777 * currentResistance().value()));
Resistance = requestResistance;
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
@@ -425,14 +332,95 @@ void nordictrackifitadbelliptical::processPendingDatagrams() {
}
}
requestResistance = -1;
}
QByteArray message = (QString::number(requestResistance).toLocal8Bit()) + ";";
requestResistance = -1;
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
qDebug() << QString::number(ret) + " >> " + message;
}
// since the motor of the bike is slow, let's filter the inclination changes to more than 4 seconds
else if (lastInclinationChanged.secsTo(QDateTime::currentDateTime()) > inclination_delay_seconds) {
lastInclinationChanged = QDateTime::currentDateTime();
if (nordictrack_ifit_adb_remote) {
bool erg_mode = settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool();
if (requestInclination != -100 && erg_mode && requestResistance != -100) {
qDebug() << "forcing inclination based on the erg mode resistance request of" << requestResistance;
requestInclination = requestResistance;
requestResistance = -100;
}
if (requestInclination != -100) {
double inc = qRound(requestInclination / 0.5) * 0.5;
if (inc != currentInclination().value()) {
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_tdf_10_0 = settings.value(QZSettings::proform_tdf_10_0, QZSettings::default_proform_tdf_10_0).toBool();
int x1 = 75;
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
if(proform_studio) {
x1 = 1827;
y2 = (int)(806 - (21.375 * (inc + gears())));
y1Resistance = (int)(806 - (21.375 * currentInclination().value()));
} else if(proform_tdf_10_0) {
x1 = 75;
y2 = (int)(477 - (12.5 * (inc + gears())));
y1Resistance = (int)(477 - (12.5 * currentInclination().value()));
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
#ifdef Q_OS_ANDROID
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
"sendCommand", "(Ljava/lang/String;)V",
command.object<jstring>());
#elif defined(Q_OS_WIN)
if (logcatAdbThread)
logcatAdbThread->runCommand("shell " + lastCommand);
#elif defined Q_OS_IOS
#ifndef IO_UNDER_QT
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
// this bike has both inclination and resistance, let's try to handle both
// the Original Poster doesn't want anymore, but maybe it will be useful in the future
/*
if(freemotion_coachbike_b22_7) {
int x1 = 75;
int y2 = (int)(616.18 - (17.223 * (inc + gears())));
int y1Resistance = (int)(616.18 - (17.223 * currentInclination().value()));
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Resistance) + " " +
QString::number(x1) + " " + QString::number(y2) + " 200";
qDebug() << " >> " + lastCommand;
#ifdef Q_OS_ANDROID
QAndroidJniObject command = QAndroidJniObject::fromString(lastCommand).object<jstring>();
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/QZAdbRemote",
"sendCommand", "(Ljava/lang/String;)V",
command.object<jstring>());
#elif defined(Q_OS_WIN)
if (logcatAdbThread)
logcatAdbThread->runCommand("shell " + lastCommand);
#elif defined Q_OS_IOS
#ifndef IO_UNDER_QT
h->adb_sendcommand(lastCommand.toStdString().c_str());
#endif
#endif
}
*/
}
}
requestInclination = -100;
} else {
double r = currentResistance().value() + difficult() + gears(); // the inclination here is like the resistance for the other bikes
QByteArray message = (QString::number(requestInclination).toLocal8Bit()) + ";" + QString::number(r).toLocal8Bit();
requestInclination = -100;
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
qDebug() << QString::number(ret) + " >> " + message;
}
double r = currentResistance().value() + difficult() + gears(); // the inclination here is like the resistance for the other bikes
QByteArray message = (QString::number(requestInclination).toLocal8Bit()) + ";" + QString::number(r).toLocal8Bit();
requestInclination = -100;
int ret = socket->writeDatagram(message, message.size(), sender, 8003);
qDebug() << QString::number(ret) + " >> " + message;
}
if (watts())
@@ -476,7 +464,6 @@ void nordictrackifitadbelliptical::processPendingDatagrams() {
#endif
#endif
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
emit debug(QStringLiteral("Current Gear: ") + QString::number(gear));

View File

@@ -40,17 +40,13 @@ class nordictrackifitadbellipticalLogcatAdbThread : public QThread {
void debug(QString message);
void onWatt(double watt);
void onHRM(int hrm);
void onCadence(double cadence);
void onResistance(double resistance);
private:
QString adbCommandPending = "";
QString runAdbCommand(QString command);
double speed = 0;
double inclination = 0;
double cadence = 0;
double watt = 0;
double resistance = 0;
int hrm = 0;
QString name;
struct adbfile {
@@ -83,10 +79,6 @@ class nordictrackifitadbelliptical : public elliptical {
QDateTime lastInclinationChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
uint16_t m_watts = 0;
bool cadenceReadFromTM = false;
bool resistanceReadFromTM = false;
bool wattReadFromTM = false;
bool speedReadFromTM = false;
bool initDone = false;
bool initRequest = false;
@@ -118,10 +110,6 @@ class nordictrackifitadbelliptical : public elliptical {
void processPendingDatagrams();
void changeInclinationRequested(double grade, double percentage);
void onHRM(int hrm);
void onWatt(double watt);
void onResistance(double resistance);
void onCadence(double cadence);
void onSpeedInclination(double speed, double inclination);
void update();
};

View File

@@ -75,18 +75,13 @@ void nordictrackifitadbtreadmillLogcatAdbThread::runAdbTailCommand(QString comma
qDebug() << "adbLogCat STDOUT << " << output;
QStringList lines = output.split('\n', Qt::SplitBehaviorFlags::SkipEmptyParts);
bool wattFound = false;
bool cadenceFound = false;
foreach (QString line, lines) {
if (line.contains("Changed KPH") || line.contains("Changed Actual KPH")) {
if (line.contains("Changed KPH")) {
emit debug(line);
speed = line.split(' ').last().toDouble();
} else if (line.contains("Changed Grade")) {
emit debug(line);
inclination = line.split(' ').last().toDouble();
} else if (line.contains("Changed RPM")) {
emit debug(line);
cadence = line.split(' ').last().toDouble();
cadenceFound = true;
} else if (line.contains("Changed Watts")) {
emit debug(line);
watt = line.split(' ').last().toDouble();
@@ -96,9 +91,7 @@ void nordictrackifitadbtreadmillLogcatAdbThread::runAdbTailCommand(QString comma
emit onSpeedInclination(speed, inclination);
if (wattFound)
emit onWatt(watt);
if (cadenceFound)
emit onCadence(cadence);
#ifdef Q_OS_WINDOWS
#ifdef Q_OS_WINDOWS
if(adbCommandPending.length() != 0) {
runAdbCommand(adbCommandPending);
adbCommandPending = "";
@@ -154,8 +147,6 @@ nordictrackifitadbtreadmill::nordictrackifitadbtreadmill(bool noWriteResistance,
&nordictrackifitadbtreadmill::onSpeedInclination);
connect(logcatAdbThread, &nordictrackifitadbtreadmillLogcatAdbThread::onWatt, this,
&nordictrackifitadbtreadmill::onWatt);
connect(logcatAdbThread, &nordictrackifitadbtreadmillLogcatAdbThread::onCadence, this,
&nordictrackifitadbtreadmill::onCadence);
connect(logcatAdbThread, &nordictrackifitadbtreadmillLogcatAdbThread::debug, this,
&nordictrackifitadbtreadmill::debug);
logcatAdbThread->start();
@@ -386,8 +377,7 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
}
}
// sending only if there is a real command in order to don't send too much commands when on the companion there is the debug log enabled.
if(!nordictrack_ifit_adb_remote && (requestSpeed != -1 || currentRequestInclination != -100)) {
if(!nordictrack_ifit_adb_remote) {
QByteArray message = (QString::number(requestSpeed) + ";" + QString::number(currentRequestInclination)).toLocal8Bit();
// we have to separate the 2 commands
if (requestSpeed == -1)
@@ -461,11 +451,6 @@ void nordictrackifitadbtreadmill::onWatt(double watt) {
wattReadFromTM = true;
}
void nordictrackifitadbtreadmill::onCadence(double cadence) {
Cadence = cadence;
cadenceReadFromTM = true;
}
void nordictrackifitadbtreadmill::onSpeedInclination(double speed, double inclination) {
parseSpeed(speed);
@@ -485,7 +470,6 @@ void nordictrackifitadbtreadmill::onSpeedInclination(double speed, double inclin
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
#ifdef Q_OS_ANDROID
@@ -499,7 +483,6 @@ void nordictrackifitadbtreadmill::onSpeedInclination(double speed, double inclin
}
}
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
emit debug(QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value()));

View File

@@ -39,14 +39,12 @@ class nordictrackifitadbtreadmillLogcatAdbThread : public QThread {
void onSpeedInclination(double speed, double inclination);
void debug(QString message);
void onWatt(double watt);
void onCadence(double cadence);
private:
QString adbCommandPending = "";
double speed = 0;
double inclination = 0;
double watt = 0;
double cadence = 0;
QString name;
struct adbfile {
QDateTime date;
@@ -79,7 +77,6 @@ class nordictrackifitadbtreadmill : public treadmill {
uint8_t firstStateChanged = 0;
uint16_t m_watts = 0;
bool wattReadFromTM = false;
bool cadenceReadFromTM = false;
bool initDone = false;
bool initRequest = false;
@@ -110,7 +107,6 @@ class nordictrackifitadbtreadmill : public treadmill {
void onSpeedInclination(double speed, double inclination);
void onWatt(double watt);
void onCadence(double cadence);
void processPendingDatagrams();
void changeInclinationRequested(double grade, double percentage);

View File

@@ -83,9 +83,6 @@ uint16_t proformbike::wattsFromResistance(resistance_t resistance) {
if (currentCadence().value() == 0)
return 0;
if (proform_225_csx_PFEX32925_INT_0)
return _ergTable.estimateWattage(Cadence.value(), resistance);
switch (resistance) {
case 0:
case 1:
@@ -1277,94 +1274,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
if (m_watts > 3000) {
m_watts = 0;
} else {
if(proform_225_csx_PFEX32925_INT_0) {
switch ((uint8_t)newValue.at(11)) {
case 0:
case 1:
Resistance = 1;
m_pelotonResistance = 1;
break;
case 3:
Resistance = 2;
m_pelotonResistance = 4;
break;
case 5:
Resistance = 3;
m_pelotonResistance = 9;
break;
case 7:
Resistance = 4;
m_pelotonResistance = 13;
break;
case 9:
Resistance = 5;
m_pelotonResistance = 18;
break;
case 0x0b:
Resistance = 6;
m_pelotonResistance = 23;
break;
case 0x0d:
Resistance = 7;
m_pelotonResistance = 27;
break;
case 0x0f:
Resistance = 8;
m_pelotonResistance = 32;
break;
case 0x11:
Resistance = 9;
m_pelotonResistance = 37;
break;
case 0x13:
Resistance = 10;
m_pelotonResistance = 42;
break;
case 0x15:
Resistance = 11;
m_pelotonResistance = 46;
break;
case 0x17:
Resistance = 12;
m_pelotonResistance = 50;
break;
case 0x19:
Resistance = 13;
m_pelotonResistance = 55;
break;
case 0x1b:
Resistance = 14;
m_pelotonResistance = 59;
break;
case 0x1d:
Resistance = 15;
m_pelotonResistance = 63;
break;
case 0x1f:
Resistance = 16;
m_pelotonResistance = 68;
break;
case 0x21:
Resistance = 17;
m_pelotonResistance = 73;
break;
case 0x22:
case 0x23:
Resistance = 18;
m_pelotonResistance = 77;
break;
case 0x24:
case 0x25:
Resistance = 19;
m_pelotonResistance = 82;
break;
case 0x26:
case 0x27:
Resistance = 20;
m_pelotonResistance = 86;
break;
}
} else if(nordictrack_GX4_5_bike || nordictrack_gx_44_pro) {
if(nordictrack_GX4_5_bike || nordictrack_gx_44_pro) {
switch ((uint8_t)newValue.at(11)) {
case 0:
case 1:

View File

@@ -18,7 +18,6 @@ proformtelnetbike::proformtelnetbike(bool noWriteResistance, bool noHeartService
double bikeResistanceGain) {
QSettings settings;
m_watt.setType(metric::METRIC_WATT);
m_rawWatt.setType(metric::METRIC_WATT);
target_watts.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
@@ -299,11 +298,11 @@ void proformtelnetbike::characteristicChanged(const char *buff, int len) {
QStringList packet = QString::fromLocal8Bit(newValue).split(" ");
qDebug() << packet;
if (newValue.contains("Current Watts")) {
m_rawWatt = packet[3].toDouble();
double watt = packet[3].toDouble();
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled")))
m_watt = m_rawWatt.value();
m_watt = watt;
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
} else if (newValue.contains("Cur RPM")) {
double RPM = packet[3].toDouble();

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More