mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
67 Commits
android36
...
build-1052
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a23afabb6e | ||
|
|
f5fd16e0e6 | ||
|
|
0962e1a70e | ||
|
|
3d54b82428 | ||
|
|
6a04908812 | ||
|
|
1460c6190e | ||
|
|
32fcb198f8 | ||
|
|
3f04bb2ff0 | ||
|
|
fec16ee496 | ||
|
|
4f0ac7e6ea | ||
|
|
ba79162a28 | ||
|
|
93477f4008 | ||
|
|
6e0b958e56 | ||
|
|
f3f219309a | ||
|
|
860e296ae3 | ||
|
|
505cd193d3 | ||
|
|
f5ed321def | ||
|
|
490039c9a0 | ||
|
|
88eaf9d73c | ||
|
|
05979de55a | ||
|
|
fb3c34f91f | ||
|
|
a5616561d3 | ||
|
|
835a8395bf | ||
|
|
d61a55394b | ||
|
|
e2233d0a86 | ||
|
|
075a0dc368 | ||
|
|
5f18c9f81c | ||
|
|
2f2c084db3 | ||
|
|
68337ee54d | ||
|
|
e69ddb8a26 | ||
|
|
69b227373a | ||
|
|
fbb9524afd | ||
|
|
937f98c77a | ||
|
|
0ec7619408 | ||
|
|
344640fe69 | ||
|
|
26effcd6b2 | ||
|
|
206e6e6fec | ||
|
|
d2ba73b148 | ||
|
|
df8ac18a0f | ||
|
|
cfcf86adcc | ||
|
|
957d2934db | ||
|
|
7c73975c34 | ||
|
|
999ce8022a | ||
|
|
c3fa23159a | ||
|
|
6b674be421 | ||
|
|
7af98b4992 | ||
|
|
8380827494 | ||
|
|
a3a8825d48 | ||
|
|
d8b1dce8de | ||
|
|
6e141bf83e | ||
|
|
f32ad41917 | ||
|
|
e75a5bcb0a | ||
|
|
0a017ca4e6 | ||
|
|
3ceb256272 | ||
|
|
6b8bb96aad | ||
|
|
71cb562b05 | ||
|
|
de77f2aa4e | ||
|
|
316c34213d | ||
|
|
860489616c | ||
|
|
730c16f7b4 | ||
|
|
2bdbeed5f4 | ||
|
|
1946b46665 | ||
|
|
288cb3974b | ||
|
|
345b0d2f74 | ||
|
|
c8355184f2 | ||
|
|
4f7c7fa7c9 | ||
|
|
c75b6ae5f0 |
136
.github/workflows/main.yml
vendored
136
.github/workflows/main.yml
vendored
@@ -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
1
.gitignore
vendored
@@ -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
2
.gitmodules
vendored
@@ -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
374
CLAUDE.md
@@ -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
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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!")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"/>
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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!");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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!");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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; }
|
||||
@@ -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
|
||||
@@ -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(' ') +
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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...");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
@@ -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()));
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user