Compare commits

..

4 Commits

633 changed files with 70387 additions and 151637 deletions

17
.github/stale.yml vendored
View File

@@ -1,17 +0,0 @@
# Number of days of inactivity before an issue becomes stale
daysUntilStale: 15
# Number of days of inactivity before a stale issue is closed
daysUntilClose: 7
# Issues with these labels will never be considered stale
exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: wontfix
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you
for your contributions.
# Comment to post when closing a stale issue. Set to `false` to disable
#closeComment: false

View File

@@ -18,86 +18,6 @@ on:
# A workflow run is made up of one or more jobs that can run sequentially or in parallel
jobs:
window-build:
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: bluetiger9/SmtpClient-for-Qt
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: microsoft/MSIX-Toolkit
path: "src/MSIX-Toolkit/"
ref: b82af826d29e93e4c85d34fad8a405b6c49905e7
- uses: msys2/setup-msys2@v2
with:
install: mingw-w64-x86_64-toolchain
msystem: mingw64
release: false
- name: Setup cmake
uses: jwlawson/actions-setup-cmake@v1.9
with:
cmake-version: '3.20.x'
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: '5.15.2'
host: 'windows'
modules: 'qtnetworkauth qtcharts'
target: "desktop"
arch: win64_mingw81
dir: "${{github.workspace}}/qt/"
install-deps: "true"
- name: Build
run: |
cd src
qmake
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
make -j8
cd debug
mkdir output
mkdir appx
cp qdomyos-zwift.exe output/
cd output
windeployqt --qmldir ../../ qdomyos-zwift.exe
cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libwinpthread-1.dll" .
cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libgcc_s_seh-1.dll" .
cp "${{github.workspace}}/qt/Qt/5.15.2/mingw81_64/bin/libstdc++-6.dll" .
cp ../../../icons/iOS/iTunesArtwork@2x.png .
cp ../../AppxManifest.xml .
cd ..
cd appx
#../../MSIX-Toolkit/WindowsSDK/10/10.0.20348.0/x64/makeappx.exe pack /d ../output/ /p qz
- name: Archive windows binary
uses: actions/upload-artifact@v2
with:
name: windows-binary
path: src/debug/output
# This workflow contains a single job called "build"
build:
# The type of runner that the job will run on
@@ -131,25 +51,9 @@ jobs:
with:
repository: bluetiger9/SmtpClient-for-Qt
path: "src/smtpclient/"
ref: 3fa4a0fe5797070339422cf18b5e9ed8dcb91f9c
- uses: actions/checkout@v2
- name: Checkout submodule repo
uses: actions/checkout@v2
with:
repository: cagnulein/qmdnsengine
path: "src/qmdnsengine/"
ref: "zwift"
- name: Install packages required to run QZ inside workflow
run: sudo apt update -y && sudo apt-get install -y qt5-default qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
- name: Install Qt
uses: jurplel/install-qt-action@v2
with:
version: '5.15.2'
host: 'linux'
modules: 'qtnetworkauth qtcharts'
run: sudo apt update -y && sudo apt-get install -y qt5-default libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
- name: Compile Linux Desktop
run: cd src; qmake; make -j8

12
.gitignore vendored
View File

@@ -18,15 +18,9 @@ src/build/*
src/debug-*
src/secret.h
*.swo
*.swp
build-qdomyos-zwift-Android_Qt_5_15_2_Clang_Multi_Abi-Debug/*
**/node_modules/*
*.pro.user
template-examples/youtube-viewer/node_modules/*
template-examples/youtube-viewer/*.json
template-examples/youtube-viewer/.eslintrc.js
@@ -39,9 +33,3 @@ template-examples/train-program-saver/*.json
template-examples/train-program-saver/.eslintrc.js
template-examples/train-program-saver/.jshintrc
template-examples/train-program-saver/debug.js
# Qt-es
*.pro.user
*build-*
!build-qdomyos-zwift-Qt_*_for_iOS-Debug # Needed for Apple Watch
src/inner_templates/googlemaps/cesium-key.js

7
.gitmodules vendored
View File

@@ -3,9 +3,4 @@
url = https://github.com/KDAB/android_openssl.git
[submodule "src/smtpclient"]
path = src/smtpclient
url = https://github.com/cagnulein/SmtpClient-for-Qt.git
branch = cagnulein-patch-2
[submodule "src/qmdnsengine"]
path = src/qmdnsengine
url = https://github.com/cagnulein/qmdnsengine.git
branch = zwift
url = https://github.com/bluetiger9/SmtpClient-for-Qt.git

View File

@@ -54,10 +54,8 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/qdomyoszwift">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "876E4E102594747F00BD5714"
@@ -65,7 +63,7 @@
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</RemoteRunnable>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
@@ -73,10 +71,8 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<RemoteRunnable
runnableDebuggingMode = "2"
BundleIdentifier = "com.apple.Carousel"
RemotePath = "/qdomyoszwift">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "876E4E102594747F00BD5714"
@@ -84,16 +80,7 @@
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</RemoteRunnable>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "876E4E102594747F00BD5714"
BuildableName = "watchkit.app"
BlueprintName = "watchkit"
ReferencedContainer = "container:qdomyoszwift.xcodeproj">
</BuildableReference>
</MacroExpansion>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">

View File

@@ -193,38 +193,6 @@
endingLineNumber = "57"
landmarkName = "BLEPeripheralManager"
landmarkType = "3">
<Locations>
<Location
uuid = "16D24B27-D0FB-4EC3-BAE8-56101FE7949B - 1c798ec95ff8d4b7"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "qdomyoszwift.BLEPeripheralManager.crankRevolutions.modify : Swift.Optional&lt;Swift.UInt16&gt;"
moduleName = "qdomyoszwift"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/%3Ccompiler-generated%3E"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "0"
endingLineNumber = "0"
offsetFromSymbolStart = "16">
</Location>
<Location
uuid = "16D24B27-D0FB-4EC3-BAE8-56101FE7949B - 5ebbef0dc9913f07"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
symbolName = "qdomyoszwift.BLEPeripheralManager.init() -&gt; qdomyoszwift.BLEPeripheralManager"
moduleName = "qdomyoszwift"
usesParentBreakpointCondition = "Yes"
urlString = "file:///Users/cagnulein/qdomyos-zwift/src/ios/BLEPeripheralManager.swift"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "57"
endingLineNumber = "57"
offsetFromSymbolStart = "132">
</Location>
</Locations>
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
@@ -367,7 +335,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "38"
endingLineNumber = "38"
landmarkName = "lockscreen::stepCadence()"
landmarkName = "lockscreen::virtualbike_setHeartRate(heartRate)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@@ -375,7 +343,7 @@
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "FE5697FF-F44C-43C2-A98D-C400EE56F047"
shouldBeEnabled = "No"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../src/ios/lockscreen.mm"
@@ -383,8 +351,8 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "44"
endingLineNumber = "44"
landmarkName = "unknown"
landmarkType = "0">
landmarkName = "lockscreen::virtualbike_setCadence(crankRevolutions, lastCrankEventTime)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
@@ -399,7 +367,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "37"
endingLineNumber = "37"
landmarkName = "lockscreen::stepCadence()"
landmarkName = "lockscreen::virtualbike_setHeartRate(heartRate)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@@ -407,7 +375,7 @@
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
uuid = "3DBE0495-050A-4979-85D4-28B78676F212"
shouldBeEnabled = "No"
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../src/ios/lockscreen.mm"
@@ -415,7 +383,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "43"
endingLineNumber = "43"
landmarkName = "lockscreen::setKcal(kcal)"
landmarkName = "lockscreen::virtualbike_setCadence(crankRevolutions, lastCrankEventTime)"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@@ -431,7 +399,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "32"
endingLineNumber = "32"
landmarkName = "lockscreen::heartRate()"
landmarkName = "lockscreen::virtualbike_ios()"
landmarkType = "7">
</BreakpointContent>
</BreakpointProxy>
@@ -463,7 +431,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "35"
endingLineNumber = "35"
offsetFromSymbolStart = "32">
offsetFromSymbolStart = "22">
</Location>
<Location
uuid = "18F27065-9FB2-44A2-99D0-7D41061141A3 - 4daffae51fb2d733"
@@ -478,7 +446,7 @@
endingColumnNumber = "9223372036854775807"
startingLineNumber = "35"
endingLineNumber = "35"
offsetFromSymbolStart = "36">
offsetFromSymbolStart = "28">
</Location>
</Locations>
</BreakpointContent>

View File

@@ -1,62 +0,0 @@
{
"identifier" : "2816EB89",
"nonRenewingSubscriptions" : [
],
"products" : [
],
"settings" : {
},
"subscriptionGroups" : [
{
"id" : "F012E388",
"localizations" : [
],
"name" : "Swag Bag",
"subscriptions" : [
{
"adHocOffers" : [
],
"codeOffers" : [
],
"displayPrice" : "1.99",
"familyShareable" : false,
"groupNumber" : 1,
"internalID" : "F108BD35",
"introductoryOffer" : null,
"localizations" : [
{
"description" : "Swag Bag",
"displayName" : "Swag Bag",
"locale" : "en_US"
},
{
"description" : "Swag Bag",
"displayName" : "Swag Bag",
"locale" : "en_GB"
},
{
"description" : "Swag Bag",
"displayName" : "Swag Bag",
"locale" : "it"
}
],
"productID" : "org.cagnulein.qdomyoszwift.swagbag",
"recurringSubscriptionPeriod" : "P1M",
"referenceName" : "SwagBag",
"subscriptionGroupID" : "F012E388",
"type" : "RecurringSubscription"
}
]
}
],
"version" : {
"major" : 1,
"minor" : 2
}
}

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "circular38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "circular40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "circular42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "circular44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "extra-large38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "extra-large40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "extra-large42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "extra-large44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,13 +1,11 @@
{
"images" : [
{
"filename" : "graphic-bezel40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-bezel44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,13 +1,11 @@
{
"images" : [
{
"filename" : "graphic-circular40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-circular44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,13 +1,11 @@
{
"images" : [
{
"filename" : "graphic-corner40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-corner44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "graphic-extra-large38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "graphic-extra-large40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-extra-large42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "graphic-extra-large44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,13 +1,11 @@
{
"images" : [
{
"filename" : "graphic-large-rectangular40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "graphic-large-rectangular44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "modular38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "modular40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "modular42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "modular44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -1,25 +1,21 @@
{
"images" : [
{
"filename" : "utility38mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : "<=145"
},
{
"filename" : "utility40mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">161"
},
{
"filename" : "utility42mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">145"
},
{
"filename" : "utility44mm@2x.png",
"idiom" : "watch",
"scale" : "2x",
"screen-width" : ">183"

View File

@@ -13,59 +13,6 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
// MARK: - Timeline Configuration
private func templateForComplication(complication: CLKComplication) -> CLKComplicationTemplate? {
// Init default output:
var template: CLKComplicationTemplate? = nil
// Graphic Complications are only availably since watchOS 5.0:
if #available(watchOSApplicationExtension 5.0, *) {
// NOTE: Watch faces that support graphic templates are available only on Apple Watch Series 4 or later. So the binary on older devices (e.g. Watch Series 3) will not contain the images.
if complication.family == .graphicCircular {
let imageTemplate = CLKComplicationTemplateGraphicCircularImage()
// Check if asset exists, to prevent crash on non-supported devices:
if let fullColorImage = UIImage(named: "Complication/Graphic Circular") {
let imageProvider = CLKFullColorImageProvider.init(fullColorImage: fullColorImage)
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
}
else if complication.family == .graphicCorner {
let imageTemplate = CLKComplicationTemplateGraphicCornerCircularImage()
// Check if asset exists, to prevent crash on non-supported devices:
if let fullColorImage = UIImage(named: "Complication/Graphic Corner") {
let imageProvider = CLKFullColorImageProvider.init(fullColorImage: fullColorImage)
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
}
}
// For all watchOS versions:
if complication.family == .circularSmall {
let imageTemplate = CLKComplicationTemplateCircularSmallSimpleImage()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Circular")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
else if complication.family == .modularSmall {
let imageTemplate = CLKComplicationTemplateModularSmallSimpleImage()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Modular")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
else if complication.family == .utilitarianSmall {
let imageTemplate = CLKComplicationTemplateUtilitarianSmallSquare()
let imageProvider = CLKImageProvider(onePieceImage: UIImage(named: "Complication/Utilitarian")!)
imageProvider.tintColor = UIColor.blue
imageTemplate.imageProvider = imageProvider
template = imageTemplate
}
return template
}
func getSupportedTimeTravelDirections(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimeTravelDirections) -> Void) {
handler([.forward, .backward])
}
@@ -86,9 +33,7 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
func getCurrentTimelineEntry(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTimelineEntry?) -> Void) {
// Call the handler with the current timeline entry
let template = templateForComplication(complication: complication)
let timelineEntry = CLKComplicationTimelineEntry(date: Date(), complicationTemplate: template!)
handler(timelineEntry)
handler(nil)
}
func getTimelineEntries(for complication: CLKComplication, before date: Date, limit: Int, withHandler handler: @escaping ([CLKComplicationTimelineEntry]?) -> Void) {
@@ -101,15 +46,11 @@ class ComplicationController: NSObject, CLKComplicationDataSource {
handler(nil)
}
func getPlaceholderTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(templateForComplication(complication: complication))
}
// MARK: - Placeholder Templates
func getLocalizableSampleTemplate(for complication: CLKComplication, withHandler handler: @escaping (CLKComplicationTemplate?) -> Void) {
// This method will be called once per supported complication, and the results will be cached
handler(templateForComplication(complication: complication))
handler(nil)
}
}

View File

@@ -8,8 +8,6 @@
<string>watchkit Extension</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>NSMotionUsageDescription</key>
<string>access to step cadence in order to show it in the application</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
@@ -24,21 +22,6 @@
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>CLKComplicationPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).ComplicationController</string>
<key>CLKComplicationSupportedFamilies</key>
<array>
<string>CLKComplicationFamilyModularSmall</string>
<string>CLKComplicationFamilyModularLarge</string>
<string>CLKComplicationFamilyUtilitarianSmall</string>
<string>CLKComplicationFamilyUtilitarianSmallFlat</string>
<string>CLKComplicationFamilyUtilitarianLarge</string>
<string>CLKComplicationFamilyCircularSmall</string>
<string>CLKComplicationFamilyExtraLarge</string>
<string>CLKComplicationFamilyGraphicCorner</string>
<string>CLKComplicationFamilyGraphicBezel</string>
<string>CLKComplicationFamilyGraphicCircular</string>
<string>CLKComplicationFamilyGraphicRectangular</string>
<string>CLKComplicationFamilyGraphicExtraLarge</string>
</array>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>

View File

@@ -8,17 +8,13 @@
import WatchKit
import HealthKit
import CoreMotion
class MainController: WKInterfaceController {
@IBOutlet weak var userNameLabel: WKInterfaceLabel!
@IBOutlet weak var stepCountsLabel: WKInterfaceLabel!
@IBOutlet weak var caloriesLabel: WKInterfaceLabel!
@IBOutlet weak var distanceLabel: WKInterfaceLabel!
@IBOutlet weak var heartRateLabel: WKInterfaceLabel!
@IBOutlet weak var startButton: WKInterfaceButton!
static var start: Bool! = false
let pedometer = CMPedometer()
override func awake(withContext context: Any?) {
super.awake(withContext: context)
@@ -32,15 +28,6 @@ class MainController: WKInterfaceController {
super.willActivate()
print("WILL ACTIVE")
WorkoutTracking.shared.fetchStepCounts()
if CMPedometer.isStepCountingAvailable() {
pedometer.startUpdates(from: Date()) { pedometerData, error in
guard let pedometerData = pedometerData, error == nil else { return }
self.stepCountsLabel.setText("\(Int(((pedometerData.currentCadence?.doubleValue ?? 0) * 60.0 / 2.0))) STEP CAD.")
WatchKitConnection.stepCadence = Int(((pedometerData.currentCadence?.doubleValue ?? 0) * 60.0 / 2.0))
WatchKitConnection.shared.sendMessage(message: ["stepCadence":
"\(WatchKitConnection.stepCadence)" as AnyObject])
}
}
}
override func didDeactivate() {
@@ -72,7 +59,6 @@ extension MainController {
}
extension MainController: WorkoutTrackingDelegate {
func didReceiveHealthKitDistanceCycling(_ distanceCycling: Double) {
}
@@ -86,17 +72,10 @@ extension MainController: WorkoutTrackingDelegate {
"\(heartRate)" as AnyObject])
WorkoutTracking.distance = WatchKitConnection.distance
WorkoutTracking.kcal = WatchKitConnection.kcal
self.distanceLabel.setText("Distance \(Double(WorkoutTracking.distance))")
self.caloriesLabel.setText("KCal \(Int(WorkoutTracking.kcal))")
//WorkoutTracking.cadenceSteps = pedometer.
}
func didReceiveHealthKitStepCounts(_ stepCounts: Double) {
//stepCountsLabel.setText("\(stepCounts) STEPS")
}
func didReceiveHealthKitStepCadence(_ stepCadence: Double) {
stepCountsLabel.setText("\(stepCounts) STEPS")
}
}

View File

@@ -23,7 +23,6 @@ class WatchKitConnection: NSObject {
static let shared = WatchKitConnection()
public static var distance = 0.0
public static var kcal = 0.0
public static var stepCadence = 0
weak var delegate: WatchKitConnectionDelegate?
private override init() {

View File

@@ -12,7 +12,6 @@ import HealthKit
protocol WorkoutTrackingDelegate: class {
func didReceiveHealthKitHeartRate(_ heartRate: Double)
func didReceiveHealthKitStepCounts(_ stepCounts: Double)
func didReceiveHealthKitStepCadence(_ stepCadence: Double)
func didReceiveHealthKitDistanceCycling(_ distanceCycling: Double)
func didReceiveHealthKitActiveEnergyBurned(_ activeEnergyBurned: Double)
}
@@ -28,9 +27,6 @@ class WorkoutTracking: NSObject {
static let shared = WorkoutTracking()
public static var distance = Double()
public static var kcal = Double()
public static var cadenceTimeStamp = NSDate().timeIntervalSince1970
public static var cadenceLastSteps = Double()
public static var cadenceSteps = 0
let healthStore = HKHealthStore()
let configuration = HKWorkoutConfiguration()
var workoutSession: HKWorkoutSession!
@@ -83,13 +79,7 @@ extension WorkoutTracking {
if let sum = result.sumQuantity() {
resultCount = sum.doubleValue(for: HKUnit.count())
let now = NSDate().timeIntervalSince1970
let deltaT = now - WorkoutTracking.cadenceTimeStamp
let deltaC = resultCount - WorkoutTracking.cadenceLastSteps
WorkoutTracking.cadenceLastSteps = resultCount
WorkoutTracking.cadenceTimeStamp = now
weakSelf.delegate?.didReceiveHealthKitStepCounts(resultCount)
weakSelf.delegate?.didReceiveHealthKitStepCadence((deltaC / deltaT) * 60)
} else {
print("Failed to fetch steps rate 2")
}

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="19529" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Tpn-rd-UUX">
<document type="com.apple.InterfaceBuilder.WatchKit.Storyboard" version="3.0" toolsVersion="17506" targetRuntime="watchKit" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Tpn-rd-UUX">
<device id="watch38"/>
<dependencies>
<deployment identifier="watchOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="19519"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="19514"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17505"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBWatchKitPlugin" version="17500"/>
</dependencies>
<scenes>
<!--Main-->
@@ -12,20 +12,16 @@
<objects>
<controller identifier="Main" hidesWhenLoading="NO" id="Tpn-rd-UUX" customClass="MainController" customModule="watchkit_Extension">
<items>
<label width="136" alignment="left" text="QZ Fitness" textAlignment="center" id="SlU-M7-WGB"/>
<label width="136" alignment="left" text="qdomyos-zwift" textAlignment="center" id="SlU-M7-WGB"/>
<label width="136" alignment="left" text="Heart Rate" id="Nda-m1-XRw"/>
<label width="136" alignment="left" text="Step Counts" id="HpA-e9-6YV"/>
<button width="1" alignment="left" title="Start" id="vZg-X8-uY5">
<connections>
<action selector="startWorkout" destination="Tpn-rd-UUX" id="UaW-pR-tn6"/>
</connections>
</button>
<label width="136" alignment="left" text="Heart Rate" id="Nda-m1-XRw"/>
<label width="136" alignment="left" text="Step Counts" id="HpA-e9-6YV"/>
<label width="136" alignment="left" text="Calories" id="Szi-Jp-J3S"/>
<label width="136" alignment="left" text="Distance" id="eRf-NJ-6If"/>
</items>
<connections>
<outlet property="caloriesLabel" destination="Szi-Jp-J3S" id="trd-YS-bJy"/>
<outlet property="distanceLabel" destination="eRf-NJ-6If" id="ZE2-OB-jqN"/>
<outlet property="heartRateLabel" destination="Nda-m1-XRw" id="1la-8R-3jG"/>
<outlet property="startButton" destination="vZg-X8-uY5" id="pJc-09-kfV"/>
<outlet property="stepCountsLabel" destination="HpA-e9-6YV" id="Z88-ej-6oG"/>

View File

@@ -1,24 +1,10 @@
FROM ubuntu:latest
FROM debian:stable
MAINTAINER cagnulein
ARG DEBIAN_FRONTEND=noninteractive
ENV TZ=Europe/Moscow
ENV MAKEFLAGS -j8
WORKDIR /usr/local/src
RUN apt-get update && apt-get install -y tzdata
# utils
RUN apt -y update
RUN apt -y upgrade
RUN apt update -y && apt-get install -y git qt5-default libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev build-essential
RUN git clone https://github.com/cagnulein/qdomyos-zwift.git
WORKDIR /usr/local/src/qdomyos-zwift
RUN git submodule update --init src/smtpclient/
RUN git submodule update --init src/qmdnsengine/
WORKDIR /usr/local/src/qdomyos-zwift/src
RUN qmake
RUN make -j4
WORKDIR /usr/local/src/qdomyos-zwift/src
CMD ["./qdomyos-zwift","-no-gui"]
RUN apt -y install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-default

View File

@@ -8,7 +8,7 @@ Once you've installed QDomyos-Zwift, you can access the [operation guide](30_usa
```buildoutcfg
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5
$ sudo sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
$ cd qdomyos-zwift
$ git submodule update --init src/smtpclient/
@@ -102,7 +102,7 @@ This operation takes a moment to complete.
#### Install qdomyos-zwift from sources
`sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5`
`sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev`
`git clone https://github.com/cagnulein/qdomyos-zwift.git`
`cd qdomyos-zwift`

View File

@@ -11,12 +11,26 @@ This list is not exhaustive. Please report any application known to be working w
|[Fulgaz](21_applications_detail.md#fulgaz)|![bike](img/20_bike.png)|![IOS](img/20_apple.png) ![Android](img/20_android.png) ![PC](img/20_windows.png)|Yes|Yes|Yes|Yes, no FTMS support (see note)|Yes (see note) |
# Supported devices
Check the full list https://github.com/cagnulein/qdomyos-zwift/wiki/Equipment-Compatibility
This list is not exhaustive.
Try the qdomyos app with your fitness appliance and report how it is going.
If it's not working, you can [ask for your device to be supported](#ask-for-device-support)
## Supported bikes
|Manufacturer|Model|Speed|RPM|Power|HRM|Resistence Control|
|------------|-----|------------|---|-----|---|------------------|
|[Echelon](22_devices_detail.md#echelon)|Connect Sport|Yes|Yes|Yes|Yes|N/A|
|[Sportstech](22_devices_detail.md#sportstech)|ESX500|Yes|Yes|Yes|Yes|Yes|
## Supported treadmills
|Manufacturer|Model|Speed|HRM|Inclinaison Control| Speed control|
|------------|-----|------------|---|-------------------|--------------|
|Domyos|Intense Run|Yes|Yes|Yes|Yes|
|Domyos|T900c|Yes|Yes|Yes|Yes|
|Toorx|TRX Route Key|Yes|Yes|Yes|Yes|
# Ask for device support
You can ask for supporting a device by opening an issue and following these steps.

View File

@@ -21,33 +21,32 @@ Please refer to this article for more information under [QML Operations](https:/
This is the list of settings available in the application. These settings needs to be appended to the binary command line.
*Example :* `sudo ./qdomyos-zwift -no-gui` for disabling any graphical interface.
| **Option** | **Type** | **Default** | **Function** |
|:------------------------------|:---------|:------------|:-----------------------------------------------------------------------------|
| -no-gui | Boolean | False | Disable GUI |
| -qml | Boolean | False | Enables the QML interface |
| -miles | Boolean | False | Swithes to Imperial Units System |
| -no-console | Boolean | False | Not in use |
| -test-resistance | Boolean | False | |
| -no-log | Boolean | False | Disable Logging |
| -no-write-resistance | Boolean | False | Disable resistance instructions from QZ to your fitness equipment |
| -no-heart-service | Boolean | False | Do not simulate external HR monitor, use only FTMS |
| -heart-service | Boolean | True | Simulate HR service (required for applications not reading FTMS) |
| -only-virtualbike | Boolean | False | |
| -only-virtualtreadmill | Boolean | False | |
| -no-reconnection | Boolean | False | QZ will not try to reconnect your fitness equipement if enabled |
| -bluetooth-relaxed | Boolean | False | In case of deconnections from QZ to your fitness equipement |
| -bike-cadence-sensor | Boolean | False | |
| -bike-power-sensor | Boolean | False | |
| -battery-service | Boolean | False | |
| -service-changed | Boolean | False | |
| -bike-wheel-revs | Boolean | False | |
| -run-cadence-sensor | Boolean | False | |
| -nordictrack-10-treadmill | Boolean | False | Enable NordicTrack compatibility mode |
| -train | String | | Force training program |
| -name | String | | Force bluetooth device name (if QZ struggles finding your fitness equipment) |
| -poll-device-time | Int | 200 (ms) | Frequency to refresh informations from QZ to Fitness equipment |
| -bike-resistance-gain | Int | | Adjust resistance from the fitness application |
| -bike-resistance-offset | Int | | Set another resistance point than default |
| **Option** | **Type** | **Default** | **Function** |
|:------------------------|:---------|:------------|:-----------------------------------------------------------------------------|
| -no-gui | Boolean | False | Disable GUI |
| -qml | Boolean | False | Enables the QML interface |
| -miles | Boolean | False | Swithes to Imperial Units System |
| -no-console | Boolean | False | Not in use |
| -test-resistance | Boolean | False | |
| -no-log | Boolean | False | Disable Logging |
| -no-write-resistance | Boolean | False | Disable resistance instructions from QZ to your fitness equipment |
| -no-heart-service | Boolean | False | Do not simulate external HR monitor, use only FTMS |
| -heart-service | Boolean | True | Simulate HR service (required for applications not reading FTMS) |
| -only-virtualbike | Boolean | False | |
| -only-virtualtreadmill | Boolean | False | |
| -no-reconnection | Boolean | False | QZ will not try to reconnect your fitness equipement if enabled |
| -bluetooth-relaxed | Boolean | False | In case of deconnections from QZ to your fitness equipement |
| -bike-cadence-sensor | Boolean | False | |
| -bike-power-sensor | Boolean | False | |
| -battery-service | Boolean | False | |
| -service-changed | Boolean | False | |
| -bike-wheel-revs | Boolean | False | |
| -run-cadence-sensor | Boolean | False | |
| -train | String | | Force training program |
| -name | String | | Force bluetooth device name (if QZ struggles finding your fitness equipment) |
| -poll-device-time | Int | 200 (ms) | Frequency to refresh informations from QZ to Fitness equipment |
| -bike-resistance-gain | Int | | Adjust resistance from the fitness application |
| -bike-resistance-offset | Int | | Set another resistance point than default |

View File

@@ -1,365 +0,0 @@
# QDomyos-Zwift WebSocket API Installation & Operation guide
# Installation
## About
The QDomyos-Zwift WebSocket API can be installed from source on Linux, Raspberry Pi (4, 3, zero W), macOS, Android and IOS.
However, this guide will only focus on the Linux (Debian 11) Installation and Raspberry Pi cause there are the most useful case in headless control.
If you already install the Web Socket, feel free to [skip to the Usage section](#usage).
## Requirement
To Install QDomyos-Zwift with WebSocket API you will need Qt 5.12.2+ and the following modules :
- Qt Bluetooth
- Qt Widgets
- Qt Positioning
- Qt XML
- Qt Charts
- Qt Network
- Qt Network Authorization
- Qt WebSockets
- Qt Assistant
Unfortunately under Debian 11 (or Raspbian 11) the Qt 5 packages are not recent enough for compilation however this guide will explain how to manually compile the latest version of Qt (5.12.12)
If you already had Qt 5.12.2 or more, feel free to [skip to Install Qt Httpserver](#install-qt-httpserver).
## Install Qt 5.12.2
*If you compile for a Raspberry Pi Zero, it's* ***faster and easy*** *to do all the Raspberry Pi task on a Raspberry Pi 4 and after copy compiled binary files toe the Raspberry Pi Zero*
For more info on the steps [please refer to the source](#source)
Before do anything. Make sure all your packages are updated :
```bash
apt update && apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
```
After download last version of Qt Source and extract them :
```bash
wget https://download.qt.io/official_releases/qt/5.12/5.12.12/single/qt-everywhere-src-5.12.12.tar.xz
```
If you compile for a Raspberry Pi you will need the Raspberry Pi Qt Configuration for raspberry pi and install it in the source :
```bash
git clone https://github.com/oniongarlic/qt-raspberrypi-configuration.git
cd qt-raspberrypi-configuration && make install DESTDIR=../qt-everywhere-src-5.12.12
```
Install the bare minimum required development packages for building Qt 5 with apt :
```bash
apt install build-essential libfontconfig1-dev libdbus-1-dev libfreetype6-dev libicu-dev libinput-dev libxkbcommon-dev libsqlite3-dev libssl-dev libpng-dev libjpeg-dev libglib2.0-dev libraspberrypi-dev
```
*For raspberry Pi install `libraspberrypi-dev` package* :
```bash
apt install libraspberrypi-dev
```
Now install all required development packages for building all Qt 5 modules:
```bash
apt install bluez libgbm-dev
apt install libudev-dev libinput-dev libts-dev libxcb-xinerama0-dev libxcb-xinerama0 gdbserver
apt install libegl1-mesa libegl1-mesa-dev libgles2-mesa libgles2-mesa-dev
apt install wiringpi libnfc-bin libnfc-dev fonts-texgyre libts-dev
apt install libbluetooth-dev bluez-tools gstreamer1.0-plugins* libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libopenal-data libopenal1 libopenal-dev pulseaudio
apt install libgstreamer*-dev
apt install gstreamer*-dev
apt install libasound2-dev libavcodec-dev libavformat-dev libswscale-dev libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev gstreamer-tools libgstreamer-plugins-*
apt install qtdeclarative5-dev
apt install libvlc-dev
```
On Raspbian Stretch/Buster/Bullseye the OpenGL library files have been renamed so that they wouldn't conflict with Mesa installed ones. Unfortunately Qt configure script is still looking for the old names.
So ***on your target Raspberry Pi*** you need to symlink those file to make sure Qt run correctly.
```bash
ln -s /usr/lib/arm-linux-gnueabihf/libGLESv2.so /usr/lib/libbrcmGLESv2.so
ln -s /usr/lib/arm-linux-gnueabihf/libEGL.so /usr/lib/libbrcmEGL.so
```
Now all dependency are installed. It's time to create build folder and compiled.
```bash
mkdir build
cd build
# For Raspberry Pi Zero or 3
PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig ../qt-everywhere-src-5.12.12/configure -platform linux-rpi-g++ -v -opengl es2 -eglfs -no-gtk -opensource -confirm-license -release -reduce-exports -force-pkg-config -nomake examples -no-compile-examples -skip qtwayland -skip qtwebengine -no-feature-geoservices_mapboxgl -qt-pcre -no-pch -ssl -evdev -system-freetype -fontconfig -glib -prefix /opt/Qt/5.12.12 -qpa eglfs
CFLAGS="-march=armv6zk -mtune=arm1176jzf-s -mfpu=vfp" make -j3 # Remove -j3 if you compiled directly on Raspberry Pi Zero
# For Raspberry Pi 4
PKG_CONFIG_LIBDIR=/usr/lib/arm-linux-gnueabihf/pkgconfig:/usr/share/pkgconfig ../qt-everywhere-src-5.12.12/configure -platform linux-rpi4-v3d-g++ -v -opengl es2 -eglfs -no-gtk -opensource -confirm-license -release -reduce-exports -force-pkg-config -nomake examples -no-compile-examples -skip qtwayland -skip qtwebengine -no-feature-geoservices_mapboxgl -qt-pcre -no-pch -ssl -evdev -system-freetype -fontconfig -glib -prefix /opt/Qt/5.12.12 -qpa eglfs
CFLAGS="-march=armv8-a -mtune=cortex-a72 -mfpu=crypto-neon-fp-armv8" make -j3
# For Debian 11 x64 (Not tested)
../qt-everywhere-src-5.12.12/configure -v -opengl es2 -eglfs -no-gtk -opensource -confirm-license -release -reduce-exports -force-pkg-config -nomake examples -no-compile-examples -skip qtwayland -skip qtwebengine -no-feature-geoservices_mapboxgl -qt-pcre -no-pch -ssl -evdev -system-freetype -fontconfig -glib -prefix /opt/Qt/5.12.12 -qpa eglfs
make
```
Finally, if you cross compiled you can transfer the build folder to other machine and then just run as root in the build folder :
```bash
make install
```
# Install Qt Httpserver
Like explain in PR #252, to make work the Http Server you will need to manually compile `qthttpserver` module.
For that just run following commands in your home directory :
```bash
cd ~
git clone https://github.com/qt-labs/qthttpserver
cd ~/qthttpserver/src/3rdparty/http-parser
wget https://raw.githubusercontent.com/nodejs/http-parser/main/http_parser.h
wget https://raw.githubusercontent.com/nodejs/http-parser/main/http_parser.c
cd ~/qthttpserver/src
qmake # Please note if you compiled Qt you need to specify /opt/Qt/5.12.12/bin/qmake
make
# Wait...
sudo make install
```
***You have successfully installed Qt Httpserver***
# Install QDomyos-Zwift
If you already compile QDomyos-Zwift and you just compiled a new version of Qt.
Please delete the whole QDomyos-Zwift folder and restart from scratch to prevent linking issues.
```bash
cd ~
git clone https://github.com/cagnulein/qdomyos-zwift.git
cd ~/qdomyos-zwift
git submodule update --init ~/qdomyos-zwift/src/smtpclient/
cd ~/qdomyos-zwift/src
qmake # Please note if you compiled Qt you need to specify /opt/Qt/5.12.12/bin/qmake
make -j4 # Remove -j4 if you compiled on Raspberry Pi Zero
```
Now installed you need to compile like say in PR #252 and issue #572 template/debug in the same directory of source file of QDomyos-Zwift.
```bash
cp -r ~/qdomyos-zwift/src/templates/debug ~/qdomyos-zwift/src/.
cp -r ~/qdomyos-zwift/src/templates/debug/* ~/qdomyos-zwift/src/.
```
Last if you can't run QML version (probably because you don't had a X11 Server.) you need to manually edit the configuration file in `/root/.config/Roberto Viola/qDomyos-Zwift.conf` and add :
```
template_inner_QZWS_enabled=true
template_inner_QZWS_folders=:/inner_templates//chartjs
template_inner_QZWS_ips=192.168.1.42
template_inner_QZWS_port=34107
template_inner_QZWS_type=WebServer
```
In this config file we open an HTTP Server on port 34107 with bind to 192.168.1.42 but feel free to change these values.
Finally, ***do not move `qdomyos-zwift` from src folder*** and run it as Root
# Usage
The way that [WebSocket](https://developer.mozilla.org/docs/Web/API/WebSockets_API) work in QDomyos-Zwift is by sending commands and listen events.
## Workout Event
The workout Event is the default message send almost every second by QDomyos-Zwift to inform you which state is your equipment.
Here what is look like :
```json
{
"BIKE_TYPE": 2,
"ELLIPTICAL_TYPE": 4,
"ROWING_TYPE": 3,
"TREADMILL_TYPE": 1,
"UNKNOWN_TYPE": 0,
"deviceId": "0B:54:49:D1:BC:DA",
"deviceName": "Domyos-TC-0314",
"deviceRSSI": 0,
"deviceType": 1,
"deviceConnected": false,
"devicePaused": false,
"elapsed_s": 0,
"elapsed_m": 0,
"elapsed_h": 0,
"pace_s": 0,
"pace_m": 0,
"pace_h": 0,
"moving_s": 0,
"moving_m": 0,
"moving_h": 0,
"speed": 0,
"speed_avg": 0,
"calories": 0,
"distance": 0,
"heart": 0,
"heart_avg": 0,
"heart_max": 0,
"jouls": 0,
"elevation": 0,
"difficult": 1,
"watts": 0,
"watts_avg": 0,
"watts_max": 0,
"kgwatts": 0,
"kgwatts_avg": 0,
"kgwatts_max": 0,
"workoutName": "",
"workoutStartDate": "",
"instructorName": "",
"latitude": null,
"longitude": null,
"nickName": "N/A",
"inclination": 0,
"inclination_avg": 0
}
```
## Commands
To send commands you will need to send a socket message in JSON format like :
```json
{
"msg": "pause"
}
```
which `msg` is always the name of the command. Command also return on WebSocket message like to acknowledge command :
```json
{
"msg": "R_pause"
}
```
Here is a list of the most "useful" commands
### Start
#### Description :
Allows you to start the bike / treadmill (Reset Timer if bike / treadmill is stopped)
#### Send :
```json
{
"msg": "start"
}
```
#### Response :
```json
{
"msg": "R_start"
}
```
### Pause
#### Description :
Allows you to stop (pause) the bike / treadmill without reset timer.
#### Send :
```json
{
"msg": "pause"
}
```
#### Response :
```json
{
"msg": "R_pause"
}
```
### Stop
#### Description :
Allows you to stop the bike / treadmill and reset timer.
#### Send :
```json
{
"msg": "stop"
}
```
#### Response :
```json
{
"msg": "R_stop"
}
```
### SetSpeed
#### Description :
Allows you to control the treadmill speed.
#### Send :
```json
{
"msg": "setspeed",
"content": {
"value": 8.0
}
}
```
#### Response :
```json
{
"msg": "R_setspeed",
"content": {
"value": 8.0
}
}
```
### SetResistance
#### Description :
Allows you to control the resistance bike or the treadmill incline.
#### Send :
```json
{
"msg": "setresistance",
"content": {
"value": 8.0
}
}
```
#### Response :
```json
{
"msg": "R_setresistance",
"content": {
"value": 8.0
}
}
```
### SetFanSpeed
#### Description :
Allows you to control the fan bike / treadmill speed.
#### Send :
```json
{
"msg": "setfanspeed",
"content": {
"value": 8.0
}
}
```
#### Response :
```json
{
"msg": "R_setfanspeed",
"content": {
"value": 8.0
}
}
```
# Source
How compile Qt 5.12.10 on Raspberry Pi : https://www.tal.org/tutorials/building-qt-512-raspberry-pi
How cross compile Qt 5.12.5 on Raspberry Pi (in French) : https://wiki.logre.eu/index.php/Cross-compilation_Qt_5.12.5_pour_Raspberry_Pi
Issue [REQ] Add to qdomyos an API for remote access to treadmill #572
PR "Templated" connections and Web server #252

View File

@@ -1,6 +0,0 @@
copy icons\iOS\iTunesArtwork@2x.png build-qdomyos-zwift-Qt_5_15_2_for_UWP_64bit_MSVC_2019-Release\release
del build-qdomyos-zwift-Qt_5_15_2_for_UWP_64bit_MSVC_2019-Release\release\qz.appx
cd build-qdomyos-zwift-Qt_5_15_2_for_UWP_64bit_MSVC_2019-Release\release
"C:\Program Files (x86)\Windows Kits\10\bin\10.0.19041.0\x64\makeappx.exe" pack /d . /p qz
explorer build-qdomyos-zwift-Qt_5_15_2_for_UWP_64bit_MSVC_2019-Release\release
pause

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -3,5 +3,5 @@ QMAKE_PRO_INPUT = httpserver.pro
QMAKE_PRL_TARGET = libQt5HttpServer_arm64-v8a.so
QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl android_install unversioned_soname unversioned_libname plugin_with_soname android_deployment_settings clang_pch_style android-21 shared cross_compile shared release android linux unix posix gcc clang llvm copy_dir_files cross_compile compile_examples enable_new_dtags neon precompile_header prefix_build force_independent force_bootstrap builtin_testdata utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib arm64-v8a Arm64-v8aBuild Arm64-v8a build_pass qtquickcompiler arm64-v8a Arm64-v8aBuild Arm64-v8a build_pass relative_qt_rpath git_build target_qt c++11 strict_c++ c++14 c++1z c99 c11 hide_symbols qt_install_headers need_fwd_pri qt_install_module create_cmake compiler_supports_fpmath qt_android_deps no_linker_version_script create_pc create_libtool arm64-v8a Arm64-v8aBuild Arm64-v8a build_pass have_target dll armeabi-v7a_and_arm64-v8a_and_x86_and_x86_64 build_all exclusive_builds multi_android_abi no_autoqmake thread moc resources
QMAKE_PRL_VERSION = 5.12.0
QMAKE_PRL_LIBS = C:/Qt/5.15.2/android/lib/libQt5SslServer_arm64-v8a.so C:/Qt/5.15.2/android/lib/libQt5WebSockets_arm64-v8a.so C:/Qt/5.15.2/android/lib/libQt5Network_arm64-v8a.so C:/Qt/5.15.2/android/lib/libQt5Concurrent_arm64-v8a.so C:/Qt/5.15.2/android/lib/libQt5Core_arm64-v8a.so
QMAKE_PRL_LIBS_FOR_CMAKE = C:/Qt/5.15.2/android/lib/libQt5SslServer_arm64-v8a.so;C:/Qt/5.15.2/android/lib/libQt5WebSockets_arm64-v8a.so;C:/Qt/5.15.2/android/lib/libQt5Network_arm64-v8a.so;C:/Qt/5.15.2/android/lib/libQt5Concurrent_arm64-v8a.so;C:/Qt/5.15.2/android/lib/libQt5Core_arm64-v8a.so;
QMAKE_PRL_LIBS = D:/Dati/GoogleDrive/cpp/build-qthttpserver-Android_Qt_5_15_2_Clang_Multi_Abi-Release/lib/libQt5SslServer_arm64-v8a.so C:/Qt/5.15.2/android/lib/libQt5WebSockets_arm64-v8a.so C:/Qt/5.15.2/android/lib/libQt5Network_arm64-v8a.so C:/Qt/5.15.2/android/lib/libQt5Concurrent_arm64-v8a.so C:/Qt/5.15.2/android/lib/libQt5Core_arm64-v8a.so
QMAKE_PRL_LIBS_FOR_CMAKE = D:/Dati/GoogleDrive/cpp/build-qthttpserver-Android_Qt_5_15_2_Clang_Multi_Abi-Release/lib/libQt5SslServer_arm64-v8a.so;C:/Qt/5.15.2/android/lib/libQt5WebSockets_arm64-v8a.so;C:/Qt/5.15.2/android/lib/libQt5Network_arm64-v8a.so;C:/Qt/5.15.2/android/lib/libQt5Concurrent_arm64-v8a.so;C:/Qt/5.15.2/android/lib/libQt5Core_arm64-v8a.so;

View File

@@ -3,5 +3,5 @@ QMAKE_PRO_INPUT = httpserver.pro
QMAKE_PRL_TARGET = libQt5HttpServer_armeabi-v7a.so
QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl android_install unversioned_soname unversioned_libname plugin_with_soname android_deployment_settings clang_pch_style android-21 shared cross_compile shared release android linux unix posix gcc clang llvm copy_dir_files cross_compile compile_examples enable_new_dtags neon precompile_header prefix_build force_independent force_bootstrap builtin_testdata utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib armeabi-v7a Armeabi-v7aBuild Armeabi-v7a build_pass optimize_size qtquickcompiler armeabi-v7a Armeabi-v7aBuild Armeabi-v7a build_pass relative_qt_rpath git_build target_qt c++11 strict_c++ c++14 c++1z c99 c11 hide_symbols qt_install_headers need_fwd_pri qt_install_module create_cmake compiler_supports_fpmath qt_android_deps no_linker_version_script create_pc create_libtool armeabi-v7a Armeabi-v7aBuild Armeabi-v7a build_pass have_target dll armeabi-v7a_and_arm64-v8a_and_x86_and_x86_64 build_all exclusive_builds multi_android_abi no_autoqmake thread moc resources
QMAKE_PRL_VERSION = 5.12.0
QMAKE_PRL_LIBS = C:/Qt/5.15.2/android/lib/libQt5SslServer_armeabi-v7a.so C:/Qt/5.15.2/android/lib/libQt5WebSockets_armeabi-v7a.so C:/Qt/5.15.2/android/lib/libQt5Network_armeabi-v7a.so C:/Qt/5.15.2/android/lib/libQt5Concurrent_armeabi-v7a.so C:/Qt/5.15.2/android/lib/libQt5Core_armeabi-v7a.so
QMAKE_PRL_LIBS_FOR_CMAKE = C:/Qt/5.15.2/android/lib/libQt5SslServer_armeabi-v7a.so;C:/Qt/5.15.2/android/lib/libQt5WebSockets_armeabi-v7a.so;C:/Qt/5.15.2/android/lib/libQt5Network_armeabi-v7a.so;C:/Qt/5.15.2/android/lib/libQt5Concurrent_armeabi-v7a.so;C:/Qt/5.15.2/android/lib/libQt5Core_armeabi-v7a.so;
QMAKE_PRL_LIBS = D:/Dati/GoogleDrive/cpp/build-qthttpserver-Android_Qt_5_15_2_Clang_Multi_Abi-Release/lib/libQt5SslServer_armeabi-v7a.so C:/Qt/5.15.2/android/lib/libQt5WebSockets_armeabi-v7a.so C:/Qt/5.15.2/android/lib/libQt5Network_armeabi-v7a.so C:/Qt/5.15.2/android/lib/libQt5Concurrent_armeabi-v7a.so C:/Qt/5.15.2/android/lib/libQt5Core_armeabi-v7a.so
QMAKE_PRL_LIBS_FOR_CMAKE = D:/Dati/GoogleDrive/cpp/build-qthttpserver-Android_Qt_5_15_2_Clang_Multi_Abi-Release/lib/libQt5SslServer_armeabi-v7a.so;C:/Qt/5.15.2/android/lib/libQt5WebSockets_armeabi-v7a.so;C:/Qt/5.15.2/android/lib/libQt5Network_armeabi-v7a.so;C:/Qt/5.15.2/android/lib/libQt5Concurrent_armeabi-v7a.so;C:/Qt/5.15.2/android/lib/libQt5Core_armeabi-v7a.so;

View File

@@ -3,5 +3,5 @@ QMAKE_PRO_INPUT = httpserver.pro
QMAKE_PRL_TARGET = libQt5HttpServer_x86.so
QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl android_install unversioned_soname unversioned_libname plugin_with_soname android_deployment_settings clang_pch_style android-21 shared cross_compile shared release android linux unix posix gcc clang llvm copy_dir_files cross_compile compile_examples enable_new_dtags neon precompile_header prefix_build force_independent force_bootstrap builtin_testdata utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib x86 X86Build X86 build_pass qtquickcompiler x86 X86Build X86 build_pass relative_qt_rpath git_build target_qt c++11 strict_c++ c++14 c++1z c99 c11 hide_symbols qt_install_headers need_fwd_pri qt_install_module create_cmake compiler_supports_fpmath qt_android_deps no_linker_version_script create_pc create_libtool x86 X86Build X86 build_pass have_target dll armeabi-v7a_and_arm64-v8a_and_x86_and_x86_64 build_all exclusive_builds multi_android_abi no_autoqmake thread moc resources
QMAKE_PRL_VERSION = 5.12.0
QMAKE_PRL_LIBS = C:/Qt/5.15.2/android/lib/libQt5SslServer_x86.so C:/Qt/5.15.2/android/lib/libQt5WebSockets_x86.so C:/Qt/5.15.2/android/lib/libQt5Network_x86.so C:/Qt/5.15.2/android/lib/libQt5Concurrent_x86.so C:/Qt/5.15.2/android/lib/libQt5Core_x86.so
QMAKE_PRL_LIBS_FOR_CMAKE = C:/Qt/5.15.2/android/lib/libQt5SslServer_x86.so;C:/Qt/5.15.2/android/lib/libQt5WebSockets_x86.so;C:/Qt/5.15.2/android/lib/libQt5Network_x86.so;C:/Qt/5.15.2/android/lib/libQt5Concurrent_x86.so;C:/Qt/5.15.2/android/lib/libQt5Core_x86.so;
QMAKE_PRL_LIBS = D:/Dati/GoogleDrive/cpp/build-qthttpserver-Android_Qt_5_15_2_Clang_Multi_Abi-Release/lib/libQt5SslServer_x86.so C:/Qt/5.15.2/android/lib/libQt5WebSockets_x86.so C:/Qt/5.15.2/android/lib/libQt5Network_x86.so C:/Qt/5.15.2/android/lib/libQt5Concurrent_x86.so C:/Qt/5.15.2/android/lib/libQt5Core_x86.so
QMAKE_PRL_LIBS_FOR_CMAKE = D:/Dati/GoogleDrive/cpp/build-qthttpserver-Android_Qt_5_15_2_Clang_Multi_Abi-Release/lib/libQt5SslServer_x86.so;C:/Qt/5.15.2/android/lib/libQt5WebSockets_x86.so;C:/Qt/5.15.2/android/lib/libQt5Network_x86.so;C:/Qt/5.15.2/android/lib/libQt5Concurrent_x86.so;C:/Qt/5.15.2/android/lib/libQt5Core_x86.so;

View File

@@ -3,5 +3,5 @@ QMAKE_PRO_INPUT = httpserver.pro
QMAKE_PRL_TARGET = libQt5HttpServer_x86_64.so
QMAKE_PRL_CONFIG = lex yacc depend_includepath testcase_targets import_plugins import_qpa_plugin prepare_docs qt_docs_targets qt_build_extra file_copies qmake_use qt warn_on release link_prl android_install unversioned_soname unversioned_libname plugin_with_soname android_deployment_settings clang_pch_style android-21 shared cross_compile shared release android linux unix posix gcc clang llvm copy_dir_files cross_compile compile_examples enable_new_dtags neon precompile_header prefix_build force_independent force_bootstrap builtin_testdata utf8_source create_prl link_prl no_private_qt_headers_warning QTDIR_build qt_example_installs exceptions_off testcase_exceptions explicitlib x86_64 X86_64Build X86_64 build_pass qtquickcompiler x86_64 X86_64Build X86_64 build_pass relative_qt_rpath git_build target_qt c++11 strict_c++ c++14 c++1z c99 c11 hide_symbols qt_install_headers need_fwd_pri qt_install_module create_cmake compiler_supports_fpmath qt_android_deps no_linker_version_script create_pc create_libtool x86_64 X86_64Build X86_64 build_pass have_target dll armeabi-v7a_and_arm64-v8a_and_x86_and_x86_64 build_all exclusive_builds multi_android_abi no_autoqmake thread moc resources
QMAKE_PRL_VERSION = 5.12.0
QMAKE_PRL_LIBS = C:/Qt/5.15.2/android/lib/libQt5SslServer_x86_64.so C:/Qt/5.15.2/android/lib/libQt5WebSockets_x86_64.so C:/Qt/5.15.2/android/lib/libQt5Network_x86_64.so C:/Qt/5.15.2/android/lib/libQt5Concurrent_x86_64.so C:/Qt/5.15.2/android/lib/libQt5Core_x86_64.so
QMAKE_PRL_LIBS_FOR_CMAKE = C:/Qt/5.15.2/android/lib/libQt5SslServer_x86_64.so;C:/Qt/5.15.2/android/lib/libQt5WebSockets_x86_64.so;C:/Qt/5.15.2/android/lib/libQt5Network_x86_64.so;C:/Qt/5.15.2/android/lib/libQt5Concurrent_x86_64.so;C:/Qt/5.15.2/android/lib/libQt5Core_x86_64.so;
QMAKE_PRL_LIBS = D:/Dati/GoogleDrive/cpp/build-qthttpserver-Android_Qt_5_15_2_Clang_Multi_Abi-Release/lib/libQt5SslServer_x86_64.so C:/Qt/5.15.2/android/lib/libQt5WebSockets_x86_64.so C:/Qt/5.15.2/android/lib/libQt5Network_x86_64.so C:/Qt/5.15.2/android/lib/libQt5Concurrent_x86_64.so C:/Qt/5.15.2/android/lib/libQt5Core_x86_64.so
QMAKE_PRL_LIBS_FOR_CMAKE = D:/Dati/GoogleDrive/cpp/build-qthttpserver-Android_Qt_5_15_2_Clang_Multi_Abi-Release/lib/libQt5SslServer_x86_64.so;C:/Qt/5.15.2/android/lib/libQt5WebSockets_x86_64.so;C:/Qt/5.15.2/android/lib/libQt5Network_x86_64.so;C:/Qt/5.15.2/android/lib/libQt5Concurrent_x86_64.so;C:/Qt/5.15.2/android/lib/libQt5Core_x86_64.so;

View File

@@ -1,30 +0,0 @@
<Package xmlns=\"http://schemas.microsoft.com/appx/manifest/foundation/windows10\" xmlns:mp=\"http://schemas.microsoft.com/appx/2014/phone/manifest\" xmlns:uap=\"http://schemas.microsoft.com/appx/manifest/uap/windows10\" xmlns:uap3=\"http://schemas.microsoft.com/appx/manifest/uap/windows10/3\" xmlns:mobile=\"http://schemas.microsoft.com/appx/manifest/mobile/windows10\" xmlns:iot=\"http://schemas.microsoft.com/appx/manifest/iot/windows10\" IgnorableNamespaces=\"uap uap3 mp mobile iot\">
<Identity Name=\"35433QZdiRobertoViola.QZFitness\" Publisher=\"CN=CA24F902-6882-40DF-B1E3-2E1B81CD730C\" Version=\"2.10.83.0\" ProcessorArchitecture=\"x64\"/>
<Properties>
<DisplayName>QZ Fitness</DisplayName>
<PublisherDisplayName>QZ di Roberto Viola</PublisherDisplayName>
<Description>QZ Fitness syncs fitness bikes, ellipticals, rowers, and treadmills to training apps like Echelon, Peloton, Zwift and Strava providing you with an unparalleled level of connectivity that keeps you informed and in control of your workouts</Description>
<Logo>50.png</Logo>
</Properties>
<Dependencies>
<TargetDeviceFamily Name=\"Windows.Universal\" MinVersion=\"10.0.19041.0\" MaxVersionTested=\"10.0.19041.0\"/>
<PackageDependency Name=\"Microsoft.VCLibs.140.00\" MinVersion=\"14.0.0.0\" Publisher=\"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US\"/>
</Dependencies>
<Resources>
<Resource Language=\"en\"/>
</Resources>
<Applications>
<Application Id=\"App\" Executable=\"qdomyos-zwift.exe\" EntryPoint=\"qdomyos-zwift.App\">
<uap:VisualElements DisplayName=\"qdomyos-zwift\" Description=\"Default package description\" BackgroundColor=\"green\" Square150x150Logo=\"150.png\" Square44x44Logo=\"44.png\">
</uap:VisualElements>
</Application>
</Applications>
<Capabilities>
<Capability Name=\"internetClient\"/>
<Capability Name=\"internetClientServer\"/>
<Capability Name=\"privateNetworkClientServer\"/>
<DeviceCapability Name=\"bluetooth.genericAttributeProfile\"/>
<DeviceCapability Name=\"bluetooth.rfcomm\"/>
<DeviceCapability Name=\"location\"/>
</Capabilities>
</Package>

View File

@@ -19,10 +19,8 @@ ColumnLayout {
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/chartjs/chart.htm"
visible: true
onLoadingChanged: {
if (loadRequest.errorString) {
if (loadRequest.errorString)
console.error(loadRequest.errorString);
console.error("port " + settings.value("template_inner_QZWS_port"));
}
}
}

View File

@@ -1,257 +0,0 @@
import QtQuick 2.7
import Qt.labs.folderlistmodel 2.15
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.0
import QtQuick.Dialogs 1.0
import QtCharts 2.2
import Qt.labs.settings 1.0
import QtPositioning 5.5
import QtLocation 5.6
ColumnLayout {
signal trainprogram_open_clicked(url name)
signal trainprogram_preview(url name)
FileDialog {
id: fileDialogTrainProgram
title: "Please choose a file"
folder: shortcuts.home
onAccepted: {
console.log("You chose: " + fileDialogTrainProgram.fileUrl)
trainprogram_open_clicked(fileDialogTrainProgram.fileUrl)
fileDialogTrainProgram.close()
}
onRejected: {
console.log("Canceled")
fileDialogTrainProgram.close()
}
}
RowLayout{
spacing: 2
anchors.top: parent.top
anchors.fill: parent
ColumnLayout {
spacing: 0
anchors.top: parent.top
anchors.fill: parent
Row
{
spacing: 5
Text
{
text:"Filter"
color: "white"
verticalAlignment: Text.AlignVCenter
}
TextField
{
function updateFilter()
{
var text = filterField.text
var filter = "*"
for(var i = 0; i<text.length; i++)
filter+= "[%1%2]".arg(text[i].toUpperCase()).arg(text[i].toLowerCase())
filter+="*"
print(filter)
folderModel.nameFilters = [filter + ".gpx"]
}
id: filterField
onTextChanged: updateFilter()
}
}
ListView {
Layout.fillWidth: true
Layout.minimumWidth: 50
Layout.preferredWidth: 100
Layout.maximumWidth: row.left
Layout.minimumHeight: 150
Layout.preferredHeight: parent.height
ScrollBar.vertical: ScrollBar {}
id: list
FolderListModel {
id: folderModel
nameFilters: ["*.gpx"]
folder: "file://" + rootItem.getWritableAppDir() + 'gpx'
showDotAndDotDot: false
showDirs: true
}
model: folderModel
delegate: Component {
Rectangle {
property alias textColor: fileTextBox.color
width: parent.width
height: 40
color: Material.backgroundColor
z: 1
Item {
id: root
property alias text: fileTextBox.text
property int spacing: 30
width: fileTextBox.width + spacing
height: fileTextBox.height
clip: true
Text {
id: fileTextBox
color: Material.color(Material.Grey)
font.pixelSize: Qt.application.font.pixelSize * 1.6
text: fileName.substring(0, fileName.length-4)
NumberAnimation on x {
Component.onCompleted: {
if(fileName.length > 30) {
running: true;
} else {
stop();
}
}
from: 0; to: -root.width; duration: 20000; loops: Animation.Infinite
}
Text {
x: root.width
text: fileTextBox.text
color: Material.color(Material.Grey)
font.pixelSize: Qt.application.font.pixelSize * 1.6
}
}
}
MouseArea {
anchors.fill: parent
z: 100
onClicked: {
console.log('onclicked ' + index+ " count "+list.count);
if (index == list.currentIndex) {
let fileUrl = folderModel.get(list.currentIndex, 'fileUrl') || folderModel.get(list.currentIndex, 'fileURL');
if (fileUrl) {
trainprogram_open_clicked(fileUrl);
popup.open()
}
}
else {
if (list.currentItem)
list.currentItem.textColor = Material.color(Material.Grey)
list.currentIndex = index
}
}
}
}
}
highlight: Rectangle {
color: Material.color(Material.Green)
z:3
radius: 5
opacity: 0.4
focus: true
}
focus: true
onCurrentItemChanged: {
let fileUrl = folderModel.get(list.currentIndex, 'fileUrl') || folderModel.get(list.currentIndex, 'fileURL');
if (fileUrl) {
list.currentItem.textColor = Material.color(Material.Yellow)
console.log(fileUrl + ' selected');
trainprogram_preview(fileUrl)
}
}
Component.onCompleted: {
}
}
}
ScrollView {
anchors.top: parent.top
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
//contentHeight: map.height
Layout.preferredHeight: parent.height
Layout.fillWidth: true
Layout.fillHeight: true
Layout.minimumWidth: 100
Layout.preferredWidth: 200
Row {
id: row
anchors.fill: parent
Text {
id: distance
width: parent.width
text: rootItem.previewWorkoutDescription
font.pixelSize: 16
color: "white"
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Plugin {
id: osmMapPlugin
name: "osm"
PluginParameter { name: "osm.useragent"; value: "QZ Fitness" }
}
Map {
height: parent.height - distance.height
width: parent.width
id: map
anchors.top: distance.bottom
plugin: osmMapPlugin
zoomLevel: 14
center: pathController.center
visible: true
MapPolyline {
id: pl
line.width: 3
line.color: 'red'
}
Component.onCompleted: {
console.log("Dimensions: ", width, height)
}
}
function loadPath(){
var lines = []
var elevationGain = 0
var offsetElevation = 0
for(var i = 0; i < pathController.geopath.size(); i++){
if(i > 0 && pathController.geopath.coordinateAt(i).altitude > pathController.geopath.coordinateAt(i-1).altitude)
elevationGain = elevationGain + (pathController.geopath.coordinateAt(i).altitude - pathController.geopath.coordinateAt(i-1).altitude)
lines[i] = pathController.geopath.coordinateAt(i)
}
distance.text = "Distance " + (pathController.geopath.length() / 1000.0).toFixed(1) + " km Elevation Gain: " + elevationGain.toFixed(1) + " meters"
return lines;
}
Connections{
target: pathController
onGeopathChanged: {
pl.path = row.loadPath();
}
onCenterChanged: {
map.center = pathController.center;
}
}
Component.onCompleted: pl.path = loadPath()
}
}
}
Button {
id: searchButton
height: 50
width: parent.width
text: "Other folders"
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
onClicked: {
console.log("folder is " + rootItem.getWritableAppDir() + 'gpx')
fileDialogTrainProgram.visible = true
}
anchors {
bottom: parent.bottom
}
}
}

View File

@@ -1,44 +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
property string maps_type: "3D"
}
WebView {
id: webView
anchors.fill: parent
url: "http://localhost:" + settings.value("template_inner_QZWS_port") + "/" + (settings.value("maps_type") === "3D" ? "googlemaps" : "maps2d") + "/maps.htm"
visible: true
onLoadingChanged: {
if (loadRequest.errorString)
console.error(loadRequest.errorString);
}
}
Button {
id: closeButton
height: 50
width: parent.width
text: "Close"
Layout.alignment: Qt.AlignCenter | Qt.AlignVCenter
onClicked: {
popupclose();
}
anchors {
bottom: parent.bottom
}
}
Component.onCompleted: {
headerToolbar.visible = true;
}
}

View File

@@ -12,7 +12,6 @@ HomeForm{
signal stop_clicked;
signal lap_clicked;
signal peloton_start_workout;
signal peloton_abort_workout;
signal plus_clicked(string name)
signal minus_clicked(string name)
@@ -27,7 +26,7 @@ HomeForm{
informativeText: "Do you want to follow the resistance? " + rootItem.pelotonProvider
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: {rootItem.pelotonAskStart = false; peloton_start_workout();}
onNoClicked: {rootItem.pelotonAskStart = false; peloton_abort_workout();}
onNoClicked: rootItem.pelotonAskStart = false;
visible: rootItem.pelotonAskStart
}
@@ -158,12 +157,6 @@ HomeForm{
source: rect
}
Timer {
id: toggleIconTimer
interval: 500; running: true; repeat: true
onTriggered: { if(identificator === "inclination" && rootItem.autoInclinationEnabled()) myIcon.visible = !myIcon.visible; else myIcon.visible = true; }
}
Image {
id: myIcon
x: 5

View File

@@ -5,7 +5,7 @@ import QtGraphicalEffects 1.12
Page {
title: qsTr("QZ Fitness")
title: qsTr("qDomyos-Zwift")
id: page
property alias start: start
@@ -157,7 +157,7 @@ Page {
width: parent.width
anchors.top: row1.bottom
anchors.topMargin: 30
text: "This app should automatically connects to your bike/treadmill. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) bluetooth and bluetooth permission MUST be on<br>3) your bike/treadmill should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issue, please, feel free to contact me to roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br/ ><i>QZ specifically disclaims liability for<br>incidental or consequential damages and assumes<br>no responsibility or liability for any loss<br>or damage suffered by any person as a result of<br>the use or misuse of the app.</i><br><br>Roberto Viola"
text: "This app should automatically connects to your bike/treadmill. <b>If it doesn't, please check</b>:<br>1) your Echelon/Domyos App MUST be closed while qdomyos-zwift is running;<br>2) bluetooth and bluetooth permission MUST be on<br>3) your bike/treadmill should be turned on BEFORE starting this app<br>4) try to restart your device<br><br>If your bike/treadmill disconnects every 30 seconds try to disable the 'virtual device' setting on the left bar.<br><br>In case of issue, please, feel free to contact me to roberto.viola83@gmail.com.<br><br><b>Have a nice ride!</b><br>Roberto Viola"
wrapMode: Label.WordWrap
visible: rootItem.labelHelp
}

View File

@@ -1,57 +0,0 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
ColumnLayout {
id: rootElement
property bool isOpen: false
property string title: ""
property alias color: accordionHeader.color
property alias textColor: accordionText.color
property alias textFont: accordionText.font.family
property alias textFontSize: accordionText.font.pixelSize
property alias indicatRectColor: indicatRect.color
property string accordionContent: ""
spacing: 0
Layout.fillWidth: true;
Rectangle {
id: accordionHeader
color: "red"
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true;
height: 48
Rectangle{
id:indicatRect
x: 16; y: 20
width: 8; height: 8
radius: 8
color: "white"
}
Text {
id: accordionText
x:34;y:13
color: "#FFFFFF"
text: rootElement.title
}
Text {
y:13
anchors.right: parent.right
anchors.rightMargin: 20
width: 30; height: 30
id: indicatImg
text: ">"
font.pixelSize: 24
color: "white"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
stackView.push(accordionContent)
}
}
}
}

View File

@@ -1,39 +0,0 @@
#include <wobjectimpl.h>
#include "PathController.h"
#include <QtDebug>
W_OBJECT_IMPL(PathController)
PathController::PathController(QObject *parent)
: QObject(parent)
{
QGeoPositionInfoSource *source = QGeoPositionInfoSource::createDefaultSource(this);
if (source) {
auto sourceName = source->sourceName();
auto objectName = source->objectName();
qDebug() << "Found QGeoPositionInfoSource: sourceName:" << sourceName
<< ", objectName:"<<objectName;
QGeoCoordinate const coordinate = source->lastKnownPosition().coordinate();
if(coordinate.isValid()) {
this->setCenter(coordinate);
} else {
// This has been known to happen.
// The Trixter X-Dream V1 bike using a Prolific PL2303HXA Serial to USB converter
// is identified by QGeoPositionInfoSource in Qt 5.15.2 as a source, but it delivers
// invalid data.
qDebug() << "Last known coordinate was not valid. Ignoring device.";
delete source;
}
}
}

View File

@@ -1,52 +0,0 @@
#ifndef APPLICATION_PATHCONTROLLER_H
#define APPLICATION_PATHCONTROLLER_H
#include <wobjectdefs.h>
#include <QGeoPath>
#include <QGeoPositionInfoSource>
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
class PathController : public QObject {
// Q_OBJECT
W_OBJECT(PathController)
public:
PathController(QObject *parent = 0);
QGeoPath geoPath() const { return mGeoPath; }
void setGeoPath(const QGeoPath &geoPath) {
if (geoPath == mGeoPath) {
return;
}
mGeoPath = geoPath;
emit geopathChanged();
}
void geopathChanged() W_SIGNAL(geopathChanged)
QGeoCoordinate center() const {
return mCenter;
}
void setCenter(const QGeoCoordinate &center) {
if (center == mCenter) {
return;
}
mCenter = center;
emit centerChanged();
}
void centerChanged() W_SIGNAL(centerChanged)
private : QGeoPath mGeoPath;
QGeoCoordinate mCenter;
W_PROPERTY(QGeoPath, geopath READ geoPath WRITE setGeoPath NOTIFY geopathChanged)
W_PROPERTY(QGeoCoordinate, center READ center WRITE setCenter NOTIFY centerChanged)
};
#endif // APPLICATION_PATHCONTROLLER_H

View File

@@ -37,7 +37,6 @@ ColumnLayout {
folder: "file://" + rootItem.getWritableAppDir() + 'settings'
showDotAndDotDot: false
showDirs: true
sortReversed: true
}
model: folderModel
delegate: Component {

View File

@@ -1,129 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.7
Item {
id: button
property string text: ""
property color buttonColor: "white"
property color textColor: "black"
property bool available: true
property alias fontPointSize: buttonText.font.pointSize
signal clicked()
state: "NORMAL"
Rectangle {
id: buttonRect
anchors.fill: parent
radius: 10
color: buttonColor
visible: button.available
Text {
id: buttonText
anchors.fill: parent
anchors.rightMargin: parent.width * 0.05
anchors.leftMargin: parent.width * 0.05
anchors.bottomMargin: parent.height * 0.20
anchors.topMargin: parent.height * 0.20
text: button.text
color: textColor
fontSizeMode: Text.Fit
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
minimumPointSize: 8
font.pointSize: 64
font.family: "Helvetica"
font.weight: Font.Light
}
}
MouseArea {
anchors.fill: parent
onClicked: {
button.clicked();
}
onPressed: {
button.state = "PRESSED";
}
onReleased: {
button.state = "NORMAL";
}
}
states: [
State {
name: "NORMAL"
PropertyChanges {
target: buttonRect
color: button.buttonColor
border.color: "transparent"
}
PropertyChanges {
target: buttonText
color: button.textColor
}
},
State {
name: "PRESSED"
PropertyChanges {
target: buttonRect
color: "transparent"
border.color: button.buttonColor
}
PropertyChanges {
target: buttonText
color: button.buttonColor
}
}
]
}

View File

@@ -1,185 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.7
import QtQuick.Controls 2.15
import org.cagnulein.qdomyoszwift 1.0
Rectangle {
id: storeItem
property Product product: undefined
state: "NORMAL"
visible: product.status == Product.Registered
radius: 10
color: "white"
height: titleText.contentHeight + descriptionText.contentHeight + 2
// ![0]
Text {
id: titleText
text: product.title
font.bold: true
anchors.right: priceText.left
anchors.top: parent.top
anchors.left: parent.left
}
Text {
id: descriptionText
text: product.description
anchors.right: priceText.left
anchors.left: parent.left
anchors.top: titleText.bottom
wrapMode: Text.WordWrap
}
Text {
id: priceText
text: product.price
anchors.right: parent.right
anchors.verticalCenter: parent.verticalCenter
}
MouseArea {
anchors.fill: parent
onClicked: {
pendingRect.visible = true;
spinBox.visible = true;
statusText.text = "Purchasing...";
storeItem.state = "PURCHASING";
product.purchase();
}
onPressed: {
storeItem.state = "PRESSED";
}
onReleased: {
storeItem.state = "NORMAL";
}
}
// ![0]
Rectangle {
id: pendingRect
anchors.fill: parent
opacity: 0.0
color: "white"
radius: parent.radius
Text {
id: statusText
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.left: parent.left
anchors.right: spinBox.left
verticalAlignment: Text.AlignVCenter
}
BusyIndicator {
id: spinBox
anchors.top: parent.top
anchors.bottom: parent.bottom
anchors.right: parent.right
width: height
}
Connections {
target: product
function onPurchaseSucceeded() {
statusText.text = "Purchase Succeeded";
spinBox.visible = false;
}
function onPurchaseFailed() {
statusText.text = "Purchase Failed";
spinBox.visible = false;
storeItem.state = "NORMAL";
}
}
}
states: [
State {
name: "NORMAL"
PropertyChanges {
target: storeItem
color: "white"
border.color: "transparent"
}
PropertyChanges {
target: pendingRect
opacity: 0.0
}
},
State {
name: "PRESSED"
PropertyChanges {
target: storeItem
color: "transparent"
border.color: "white"
}
},
State {
name: "PURCHASING"
PropertyChanges {
target: pendingRect
opacity: 1.0
}
}
]
transitions: [
Transition {
from: "PURCHASING"
to: "NORMAL"
NumberAnimation { target: pendingRect; property: "opacity"; duration: 2000; easing.type: Easing.InExpo }
}
]
}

View File

@@ -1,93 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.7
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.3
import org.cagnulein.qdomyoszwift 1.0
Item {
Text {
padding: 5
id: description
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
color: "white"
font.pointSize: 22
wrapMode: TextArea.Wrap
text: qsTr("Hi! Do you know that QZ is just an Open Souce Indie App?<br><br>No Big Companies are running this!<br>The \"Swag Bag\" is a way to support the ongoing development, maintenance and support of QZ Fitness!<br><br>Thanks to Rungap App to give me the idea of the name!")
}
Column {
//anchors.top: description.bottom + 10
anchors.top: description.bottom
//anchors.bottom: restoreButton.top
anchors.right: parent.right
anchors.left: parent.left
SwagBagItem {
product: productUnlockVowels
width: parent.width
}
}
/*Button {
id: restoreButton
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width * .5
text: "Restore Purchases"
onClicked: {
console.log("restoring...");
iapStore.restorePurchases();
}
}*/
}

View File

@@ -32,7 +32,7 @@ ColumnLayout {
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
//inputMethodHints: Qt.ImhFormattedNumbersOnly
inputMethodHints: Qt.ImhFormattedNumbersOnly
onAccepted: hostRow.doSaveHost(text)
}
Button {

View File

@@ -4,12 +4,9 @@ import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.0
import QtQuick.Dialogs 1.0
import QtCharts 2.2
import Qt.labs.settings 1.0
ColumnLayout {
signal trainprogram_open_clicked(url name)
signal trainprogram_preview(url name)
FileDialog {
id: fileDialogTrainProgram
title: "Please choose a file"
@@ -25,51 +22,15 @@ ColumnLayout {
}
}
RowLayout{
spacing: 2
anchors.top: parent.top
anchors.fill: parent
ColumnLayout {
spacing: 0
anchors.top: parent.top
anchors.fill: parent
Row
{
spacing: 5
Text
{
text:"Filter"
color: "white"
verticalAlignment: Text.AlignVCenter
}
TextField
{
function updateFilter()
{
var text = filterField.text
var filter = "*"
for(var i = 0; i<text.length; i++)
filter+= "[%1%2]".arg(text[i].toUpperCase()).arg(text[i].toLowerCase())
filter+="*"
print(filter)
folderModel.nameFilters = [filter + ".zwo", filter + ".xml"]
}
id: filterField
onTextChanged: updateFilter()
}
}
AccordionElement {
title: qsTr("Application Training folder")
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Grey)
color: Material.backgroundColor
accordionContent: ColumnLayout {
ListView {
Layout.fillWidth: true
Layout.minimumWidth: 50
Layout.preferredWidth: 100
Layout.maximumWidth: row.left
Layout.minimumHeight: 150
Layout.preferredHeight: parent.height
ScrollBar.vertical: ScrollBar {}
id: list
anchors.fill: parent
FolderListModel {
id: folderModel
nameFilters: ["*.xml", "*.zwo"]
@@ -85,35 +46,11 @@ ColumnLayout {
height: 40
color: Material.backgroundColor
z: 1
Item {
id: root
property alias text: fileTextBox.text
property int spacing: 30
width: fileTextBox.width + spacing
height: fileTextBox.height
clip: true
Text {
id: fileTextBox
color: Material.color(Material.Grey)
font.pixelSize: Qt.application.font.pixelSize * 1.6
text: fileName.substring(0, fileName.length-4)
NumberAnimation on x {
Component.onCompleted: {
if(fileName.length > 30) {
running: true;
} else {
stop();
}
}
from: 0; to: -root.width; duration: 20000; loops: Animation.Infinite
}
Text {
x: root.width
text: fileTextBox.text
color: Material.color(Material.Grey)
font.pixelSize: Qt.application.font.pixelSize * 1.6
}
}
Text {
id: fileTextBox
color: Material.color(Material.Grey)
font.pixelSize: Qt.application.font.pixelSize * 1.6
text: fileName.substring(0, fileName.length-4)
}
MouseArea {
anchors.fill: parent
@@ -154,124 +91,14 @@ ColumnLayout {
if (fileUrl) {
list.currentItem.textColor = Material.color(Material.Yellow)
console.log(fileUrl + ' selected');
trainprogram_preview(fileUrl)
powerSeries.clear();
for(var i=0;i<rootItem.preview_workout_points;i+=10)
{
powerSeries.append(i * 1000, rootItem.preview_workout_watt[i]);
}
rootItem.update_chart_power(powerChart);
//trainprogram_open_clicked(fileUrl);
//popup.open()
}
}
Component.onCompleted: {
}
}
}
ScrollView {
anchors.top: parent.top
ScrollBar.vertical.policy: ScrollBar.AlwaysOn
contentHeight: date.height + description.height + powerChart.height
Layout.preferredHeight: parent.height
Layout.fillWidth: true
Layout.minimumWidth: 100
Layout.preferredWidth: 200
property alias powerSeries: powerSeries
property alias powerChart: powerChart
Settings {
id: settings
property real ftp: 200.0
}
Row {
id: row
anchors.fill: parent
Text {
id: date
width: parent.width
text: rootItem.previewWorkoutDescription
font.pixelSize: 14
color: "white"
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Text {
anchors.top: date.bottom
id: description
width: parent.width
text: rootItem.previewWorkoutTags
font.pixelSize: 10
wrapMode: Text.WordWrap
color: "white"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
anchors.horizontalCenter: parent.horizontalCenter
}
Item {
anchors.left: parent.left
anchors.right: parent.right
anchors.top: description.bottom
anchors.bottom: parent.bottom
ChartView {
id: powerChart
objectName: "powerChart"
antialiasing: true
legend.visible: false
height: 400
width: parent.width
title: "Power"
titleFont.pixelSize: 20
DateTimeAxis {
id: valueAxisX
tickCount: 7
min: new Date(0)
max: new Date(rootItem.preview_workout_points * 1000)
format: "mm:ss"
//labelsVisible: false
gridVisible: false
//lineVisible: false
labelsFont.pixelSize: 10
}
ValueAxis {
id: valueAxisY
min: 0
max: rootItem.wattMaxChart
//tickCount: 60
tickCount: 8
labelFormat: "%.0f"
//labelsVisible: false
//gridVisible: false
//lineVisible: false
labelsFont.pixelSize: 10
}
LineSeries {
//name: "Power"
id: powerSeries
visible: true
axisX: valueAxisX
axisY: valueAxisY
color: "black"
width: 1
}
}
}
}
}
}
spacing: 10
Button {
id: searchButton

View File

@@ -1,205 +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
Item {
signal trainprogram_zwo_loaded(string s)
id: column1
// vedi trainprogram_open_clicked
Settings {
id: settings
}
Button {
id: loadButton
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
enabled: false
text: "Load"
height: Math.max(parent.height * 0.1, 50)
onClicked: {
console.log(webView.rr);
trainprogram_zwo_loaded(webView.rr);
//popupclose();
}
}
WebView {
id: webView
property var rr;
anchors.top: loadButton.bottom
anchors.bottom: parent.bottom
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
url: "https://whatsonzwift.com/workouts"
visible: true
onLoadingChanged: {
if (loadRequest.errorString)
console.error(loadRequest.errorString);
if (loadRequest.status == WebView.LoadSucceededStatus) {
console.error("Procedo");
let loadScr = `
let parsePace = function(s) {
let pace = 2;
if (s=='5k') pace = 1;
else if (s == 'HM') pace = 3;
else if (s == 'M') pace = 4;
return pace;
};
let parseDuration = function(txt) {
let re;
let objout = {
dur: 60,
repeat: 0,
durationType: null,
txt: txt
};
let dur = 60;
let repeat = 0;
if ((txt.indexOf('min') > 0 || txt.indexOf('sec') > 0) && (re = /([0-9]+x +)?([0-9]+min)? *([0-9]+sec)? +/.exec(txt.trim()))) {
let dd = 0;
objout.durationType = 'time';
objout.d_re = re;
for (let i = 1; i<re.length; i++) {
let trm1 = re[i];
if (!trm1) continue;
else if ((trm1 = trm1.trim()).endsWith('x')) {
objout.repeat = parseInt(re[i].substring(0, re[i].length - 1));
}
else if (trm1.endsWith('sec')) {
dd += parseInt(re[i].substring(0, re[i].length - 3));
}
else if (trm1.endsWith('min')) {
dd += parseInt(re[i].substring(0, re[i].length - 3)) * 60;
}
}
if (dd) objout.dur = dd;
objout.txt = txt.substring(re[0].length);
}
else if (re = /(?:([0-9]+)x +)?([0-9]+) +m +/.exec(txt)) {
objout.durationType = 'distance';
if (re[1]) {
objout.repeat = parseInt(re[1]);
}
objout.d_re = re;
objout.dur = parseInt(re[2]);
objout.txt = txt.substring(re[0].length);
}
return objout;
};
let processDOM = function() {
let outobj = {};
let div = document.querySelector('div.overview');
outobj.description = 'N/A';
if (div) {
let nextSibling = div.nextSibling;
while(nextSibling && (nextSibling.nodeType != 1 || nextSibling.tagName != 'P')) {
nextSibling = nextSibling.nextSibling;
}
if (nextSibling) outobj.description = nextSibling.innerText;
}
let durationType = null;
let gli = document.querySelector('h4.glyph-icon');
outobj.name = gli?gli.innerText:'N/A';
outobj.sportType = gli && gli.classList.contains('flaticon-run')?'run':'bike';
outobj.workout = [];
outobj.author = "whatsonzwift.com";
let wll = document.querySelector('.workoutlist');
let pace = null;
if (wll) {
let rexp = /(?:from +([0-9]+) +to +|@ +|)(?:[0-9]+rpm, +)?(?:([0-9]+)% +(?:of +(5k|10k|HM|M) +pace|FTP)|No Incline Walk)/; // fine idx = 2, pace idx = 3 inizio idx = 1
let tbs = wll.querySelectorAll('.textbar');
for (let i = 0; tbs && i<tbs.length; i++) {
let txt = tbs[i].innerText;
let elem = {};
elem.d_pretxt = txt;
let o = parseDuration(txt);
if (o.durationType) {
let dur = o.dur;
let repeat = o.repeat;
let OffDuration = -1;
let OffPower = -1;
let o2;
let re, re2;
elem.d_dur = o;
elem.d_posttxt = o.txt;
txt = o.txt;
if (re = rexp.exec(txt.trim())) {
elem.d_re = re;
let ln = re[0].length;
if (txt.length > ln && txt.charAt(ln) == ',' && (o2 = parseDuration(txt.substring(ln + 1))) && o2.durationType && (re2 = rexp.exec(o2.txt))) {
OffPower = parseInt(re2[2]);
OffDuration = o2.dur;
}
if (re[1]) {
if (i == 0) {
elem.type = "Warmup";
}
else if (i == tbs.length - 1) {
elem.type = "Cooldown";
}
else {
elem.type = "Ramp";
}
elem.Duration = o.dur;
elem.PowerLow = parseInt(re[1]) / 100.0;
elem.PowerHigh = parseInt(re[2]) / 100.0;
}
else if (OffPower >= 0 && OffDuration >= 0) {
elem.type = 'IntervalsT';
if (o.repeat) elem.Repeat = o.repeat; else elem.Repeat = 0;
elem.OnDuration = o.dur;
elem.OnPower = parseInt(re[2]) / 100.0;
elem.OffDuration = OffDuration;
elem.OffPower = OffPower / 100.0;
}
else {
elem.type = "SteadyState";
if (o.repeat) elem.Repeat = o.repeat; else elem.Repeat = 0;
elem.Duration = o.dur;
elem.Power = re[2]?parseInt(re[2]) / 100.0:0.5;
}
if (re[3]) pace = parsePace(re[3]);
if (pace) elem.pace = pace;
}
else if (txt == "free run" || txt == "free ride") {
elem.type = "FreeRide";
elem.Duration = o.dur;
}
if (durationType === null) {
outobj.durationType = o.durationType;
durationType = o.durationType;
}
}
if (elem) outobj.workout.push(elem);
}
}
return outobj;
};
let o = processDOM();
let res = JSON.stringify(o);
res
`;
webView.runJavaScript(loadScr, function(res) {
console.log("AHO1 " + res);
let ro = JSON.parse(res);
if (ro.name && ro.workout && ro.workout.length) {
console.log("AHO2 " + ro);
webView.rr = res;
loadButton.text = 'Load ' + ro.name;
loadButton.enabled = true;
}
});
}
}
}
Component.onCompleted: {
headerToolbar.visible = true;
webView.rr = 'ciao';
}
}

View File

@@ -70,66 +70,11 @@ void activiotreadmill::writeCharacteristic(const QLowEnergyCharacteristic charac
}
void activiotreadmill::forceSpeed(double requestSpeed) {
QSettings settings;
uint8_t writeSpeed[] = {0x03, 0x00, 0x00, 0x00, 0x00, 0x28, 0x00};
writeSpeed[1] = (requestSpeed * 10);
writeSpeed[5] += writeSpeed[1];
if(!settings.value(QStringLiteral("fitfiu_mc_v460"), false).toBool())
writeSpeed[6] = writeSpeed[1] + 1;
else {
switch(writeSpeed[1] & 0x0F) {
case 0x00:
writeSpeed[6] = writeSpeed[1] + 5;
break;
case 0x01:
writeSpeed[6] = writeSpeed[1] + 3;
break;
case 0x02:
writeSpeed[6] = writeSpeed[1] + 1;
break;
case 0x03:
writeSpeed[6] = writeSpeed[1] - 1;
break;
case 0x04:
writeSpeed[6] = writeSpeed[1] + 5;
break;
case 0x05:
writeSpeed[6] = writeSpeed[1] + 3;
break;
case 0x06:
writeSpeed[6] = writeSpeed[1] + 1;
break;
case 0x07:
writeSpeed[6] = writeSpeed[1] - 1;
break;
case 0x08:
writeSpeed[6] = writeSpeed[1] + 5;
break;
case 0x09:
writeSpeed[6] = writeSpeed[1] + 3;
break;
case 0x0A:
writeSpeed[6] = writeSpeed[1] + 1;
break;
case 0x0B:
writeSpeed[6] = writeSpeed[1] - 1;
break;
case 0x0C:
writeSpeed[6] = writeSpeed[1] + 5;
break;
case 0x0D:
writeSpeed[6] = writeSpeed[1] + 3;
break;
case 0x0E:
writeSpeed[6] = writeSpeed[1] + 1;
break;
case 0x0F:
writeSpeed[6] = writeSpeed[1] - 1;
break;
}
}
writeSpeed[6] = writeSpeed[1] + 1;
writeCharacteristic(gattWriteCharacteristic, writeSpeed, sizeof(writeSpeed),
QStringLiteral("forceSpeed speed=") + QString::number(requestSpeed), false, false);
@@ -201,15 +146,13 @@ void activiotreadmill::update() {
}
requestSpeed = -1;
}
if (requestInclination != -100) {
if(requestInclination < 0)
requestInclination = 0;
if (requestInclination != -1) {
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
forceIncline(requestInclination);
}
requestInclination = -100;
requestInclination = -1;
}
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
@@ -297,9 +240,7 @@ void activiotreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// lastState = value.at(0);
double speed = GetSpeedFromPacket(value);
double incline = 1.0; // "fitfiu_mc_v460" has 1.0 fixed inclination
if(!settings.value(QStringLiteral("fitfiu_mc_v460"), false).toBool())
incline = GetInclinationFromPacket(value);
double incline = GetInclinationFromPacket(value);
// double kcal = GetKcalFromPacket(value);
// double distance = GetDistanceFromPacket(value);

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.11.40" android:versionCode="381" android:installLocation="auto">
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.8.142" android:versionCode="239" 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: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="qdomyos-zwift" android:launchMode="singleTop">
<activity 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="qdomyos-zwift" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
@@ -74,12 +74,10 @@
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="33" />
<uses-sdk android:minSdkVersion="21" android:targetSdkVersion="30" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_CHECKIN_PROPERTIES"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE"/>
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT"/>
<uses-permission android:name="com.android.vending.BILLING"/>
<uses-permission android:name="android.permission.BLUETOOTH"/>
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
</manifest>

View File

@@ -18,7 +18,6 @@ apply plugin: 'com.android.application'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
implementation "com.android.billingclient:billing:4.0.0"
}
android {
@@ -64,8 +63,7 @@ android {
defaultConfig {
resConfig "en"
compileSdkVersion 33
minSdkVersion = 21
targetSdkVersion = 33
targetSdkVersion = 30
}
}

View File

@@ -1,572 +0,0 @@
/* Copyright (c) 2012 Google Inc.
* Copyright (c) 2015 The Qt Company Ltd.
*
* 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.qtproject.qt.android.purchasing;
// This code was converted from code at http://iharder.sourceforge.net/base64/
// Lots of extraneous features were removed.
/* The original code said:
* <p>
* I am placing this code in the Public Domain. Do with it as you will.
* This software comes with no guarantees or warranties but with
* plenty of well-wishing instead!
* Please visit
* <a href="http://iharder.net/xmlizable">http://iharder.net/xmlizable</a>
* periodically to check for updates or to contribute improvements.
* </p>
*
* @author Robert Harder
* @author rharder@usa.net
* @version 1.3
*/
/**
* Base64 converter class. This code is not a complete MIME encoder;
* it simply converts binary data to base64 data and back.
*
* <p>Note {@link CharBase64} is a GWT-compatible implementation of this
* class.
*/
public class Base64 {
/** Specify encoding (value is {@code true}). */
public final static boolean ENCODE = true;
/** Specify decoding (value is {@code false}). */
public final static boolean DECODE = false;
/** The equals sign (=) as a byte. */
private final static byte EQUALS_SIGN = (byte) '=';
/** The new line character (\n) as a byte. */
private final static byte NEW_LINE = (byte) '\n';
/**
* The 64 valid Base64 values.
*/
private final static byte[] ALPHABET =
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
(byte) '9', (byte) '+', (byte) '/'};
/**
* The 64 valid web safe Base64 values.
*/
private final static byte[] WEBSAFE_ALPHABET =
{(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D', (byte) 'E', (byte) 'F',
(byte) 'G', (byte) 'H', (byte) 'I', (byte) 'J', (byte) 'K',
(byte) 'L', (byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T', (byte) 'U',
(byte) 'V', (byte) 'W', (byte) 'X', (byte) 'Y', (byte) 'Z',
(byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e',
(byte) 'f', (byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n', (byte) 'o',
(byte) 'p', (byte) 'q', (byte) 'r', (byte) 's', (byte) 't',
(byte) 'u', (byte) 'v', (byte) 'w', (byte) 'x', (byte) 'y',
(byte) 'z', (byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7', (byte) '8',
(byte) '9', (byte) '-', (byte) '_'};
/**
* Translates a Base64 value to either its 6-bit reconstruction value
* or a negative number indicating some other meaning.
**/
private final static byte[] DECODABET = {-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
-5, -5, // Whitespace: Tab and Linefeed
-9, -9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
-9, -9, -9, -9, -9, // Decimal 27 - 31
-5, // Whitespace: Space
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 42
62, // Plus sign at decimal 43
-9, -9, -9, // Decimal 44 - 46
63, // Slash at decimal 47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
-9, -9, -9, -9, -9, -9, // Decimal 91 - 96
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};
/** The web safe decodabet */
private final static byte[] WEBSAFE_DECODABET =
{-9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 0 - 8
-5, -5, // Whitespace: Tab and Linefeed
-9, -9, // Decimal 11 - 12
-5, // Whitespace: Carriage Return
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 14 - 26
-9, -9, -9, -9, -9, // Decimal 27 - 31
-5, // Whitespace: Space
-9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, -9, // Decimal 33 - 44
62, // Dash '-' sign at decimal 45
-9, -9, // Decimal 46-47
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, // Numbers zero through nine
-9, -9, -9, // Decimal 58 - 60
-1, // Equals sign at decimal 61
-9, -9, -9, // Decimal 62 - 64
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, // Letters 'A' through 'N'
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, // Letters 'O' through 'Z'
-9, -9, -9, -9, // Decimal 91-94
63, // Underscore '_' at decimal 95
-9, // Decimal 96
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, // Letters 'a' through 'm'
39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, // Letters 'n' through 'z'
-9, -9, -9, -9, -9 // Decimal 123 - 127
/* ,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 128 - 139
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 140 - 152
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 153 - 165
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 166 - 178
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 179 - 191
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 192 - 204
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 205 - 217
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 218 - 230
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9, // Decimal 231 - 243
-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9,-9 // Decimal 244 - 255 */
};
// Indicates white space in encoding
private final static byte WHITE_SPACE_ENC = -5;
// Indicates equals sign in encoding
private final static byte EQUALS_SIGN_ENC = -1;
/** Defeats instantiation. */
private Base64() {
}
/* ******** E N C O D I N G M E T H O D S ******** */
/**
* Encodes up to three bytes of the array <var>source</var>
* and writes the resulting four Base64 bytes to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accommodate <var>srcOffset</var> + 3 for
* the <var>source</var> array or <var>destOffset</var> + 4 for
* the <var>destination</var> array.
* The actual number of significant bytes in your array is
* given by <var>numSigBytes</var>.
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param numSigBytes the number of significant bytes in your array
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param alphabet is the encoding alphabet
* @return the <var>destination</var> array
* @since 1.3
*/
private static byte[] encode3to4(byte[] source, int srcOffset,
int numSigBytes, byte[] destination, int destOffset, byte[] alphabet) {
// 1 2 3
// 01234567890123456789012345678901 Bit position
// --------000000001111111122222222 Array position from threeBytes
// --------| || || || | Six bit groups to index alphabet
// >>18 >>12 >> 6 >> 0 Right shift necessary
// 0x3f 0x3f 0x3f Additional AND
// Create buffer with zero-padding if there are only one or two
// significant bytes passed in the array.
// We have to shift left 24 in order to flush out the 1's that appear
// when Java treats a value as negative that is cast from a byte to an int.
int inBuff =
(numSigBytes > 0 ? ((source[srcOffset] << 24) >>> 8) : 0)
| (numSigBytes > 1 ? ((source[srcOffset + 1] << 24) >>> 16) : 0)
| (numSigBytes > 2 ? ((source[srcOffset + 2] << 24) >>> 24) : 0);
switch (numSigBytes) {
case 3:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
destination[destOffset + 3] = alphabet[(inBuff) & 0x3f];
return destination;
case 2:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = alphabet[(inBuff >>> 6) & 0x3f];
destination[destOffset + 3] = EQUALS_SIGN;
return destination;
case 1:
destination[destOffset] = alphabet[(inBuff >>> 18)];
destination[destOffset + 1] = alphabet[(inBuff >>> 12) & 0x3f];
destination[destOffset + 2] = EQUALS_SIGN;
destination[destOffset + 3] = EQUALS_SIGN;
return destination;
default:
return destination;
} // end switch
} // end encode3to4
/**
* Encodes a byte array into Base64 notation.
* Equivalent to calling
* {@code encodeBytes(source, 0, source.length)}
*
* @param source The data to convert
* @since 1.4
*/
public static String encode(byte[] source) {
return encode(source, 0, source.length, ALPHABET, true);
}
/**
* Encodes a byte array into web safe Base64 notation.
*
* @param source The data to convert
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
*/
public static String encodeWebSafe(byte[] source, boolean doPadding) {
return encode(source, 0, source.length, WEBSAFE_ALPHABET, doPadding);
}
/**
* Encodes a byte array into Base64 notation.
*
* @param source the data to convert
* @param off offset in array where conversion should begin
* @param len length of data to convert
* @param alphabet the encoding alphabet
* @param doPadding is {@code true} to pad result with '=' chars
* if it does not fall on 3 byte boundaries
* @since 1.4
*/
public static String encode(byte[] source, int off, int len, byte[] alphabet,
boolean doPadding) {
byte[] outBuff = encode(source, off, len, alphabet, Integer.MAX_VALUE);
int outLen = outBuff.length;
// If doPadding is false, set length to truncate '='
// padding characters
while (doPadding == false && outLen > 0) {
if (outBuff[outLen - 1] != '=') {
break;
}
outLen -= 1;
}
return new String(outBuff, 0, outLen);
}
/**
* Encodes a byte array into Base64 notation.
*
* @param source the data to convert
* @param off offset in array where conversion should begin
* @param len length of data to convert
* @param alphabet is the encoding alphabet
* @param maxLineLength maximum length of one line.
* @return the BASE64-encoded byte array
*/
public static byte[] encode(byte[] source, int off, int len, byte[] alphabet,
int maxLineLength) {
int lenDiv3 = (len + 2) / 3; // ceil(len / 3)
int len43 = lenDiv3 * 4;
byte[] outBuff = new byte[len43 // Main 4:3
+ (len43 / maxLineLength)]; // New lines
int d = 0;
int e = 0;
int len2 = len - 2;
int lineLength = 0;
for (; d < len2; d += 3, e += 4) {
// The following block of code is the same as
// encode3to4( source, d + off, 3, outBuff, e, alphabet );
// but inlined for faster encoding (~20% improvement)
int inBuff =
((source[d + off] << 24) >>> 8)
| ((source[d + 1 + off] << 24) >>> 16)
| ((source[d + 2 + off] << 24) >>> 24);
outBuff[e] = alphabet[(inBuff >>> 18)];
outBuff[e + 1] = alphabet[(inBuff >>> 12) & 0x3f];
outBuff[e + 2] = alphabet[(inBuff >>> 6) & 0x3f];
outBuff[e + 3] = alphabet[(inBuff) & 0x3f];
lineLength += 4;
if (lineLength == maxLineLength) {
outBuff[e + 4] = NEW_LINE;
e++;
lineLength = 0;
} // end if: end of line
} // end for: each piece of array
if (d < len) {
encode3to4(source, d + off, len - d, outBuff, e, alphabet);
lineLength += 4;
if (lineLength == maxLineLength) {
// Add a last newline
outBuff[e + 4] = NEW_LINE;
e++;
}
e += 4;
}
assert (e == outBuff.length);
return outBuff;
}
/* ******** D E C O D I N G M E T H O D S ******** */
/**
* Decodes four bytes from array <var>source</var>
* and writes the resulting bytes (up to three of them)
* to <var>destination</var>.
* The source and destination arrays can be manipulated
* anywhere along their length by specifying
* <var>srcOffset</var> and <var>destOffset</var>.
* This method does not check to make sure your arrays
* are large enough to accommodate <var>srcOffset</var> + 4 for
* the <var>source</var> array or <var>destOffset</var> + 3 for
* the <var>destination</var> array.
* This method returns the actual number of bytes that
* were converted from the Base64 encoding.
*
*
* @param source the array to convert
* @param srcOffset the index where conversion begins
* @param destination the array to hold the conversion
* @param destOffset the index where output will be put
* @param decodabet the decodabet for decoding Base64 content
* @return the number of decoded bytes converted
* @since 1.3
*/
private static int decode4to3(byte[] source, int srcOffset,
byte[] destination, int destOffset, byte[] decodabet) {
// Example: Dk==
if (source[srcOffset + 2] == EQUALS_SIGN) {
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12);
destination[destOffset] = (byte) (outBuff >>> 16);
return 1;
} else if (source[srcOffset + 3] == EQUALS_SIGN) {
// Example: DkL=
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18);
destination[destOffset] = (byte) (outBuff >>> 16);
destination[destOffset + 1] = (byte) (outBuff >>> 8);
return 2;
} else {
// Example: DkLE
int outBuff =
((decodabet[source[srcOffset]] << 24) >>> 6)
| ((decodabet[source[srcOffset + 1]] << 24) >>> 12)
| ((decodabet[source[srcOffset + 2]] << 24) >>> 18)
| ((decodabet[source[srcOffset + 3]] << 24) >>> 24);
destination[destOffset] = (byte) (outBuff >> 16);
destination[destOffset + 1] = (byte) (outBuff >> 8);
destination[destOffset + 2] = (byte) (outBuff);
return 3;
}
} // end decodeToBytes
/**
* Decodes data from Base64 notation.
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
* @since 1.4
*/
public static byte[] decode(String s) throws Base64DecoderException {
byte[] bytes = s.getBytes();
return decode(bytes, 0, bytes.length);
}
/**
* Decodes data from web safe Base64 notation.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param s the string to decode (decoded in default encoding)
* @return the decoded data
*/
public static byte[] decodeWebSafe(String s) throws Base64DecoderException {
byte[] bytes = s.getBytes();
return decodeWebSafe(bytes, 0, bytes.length);
}
/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source The Base64 encoded data
* @return decoded data
* @since 1.3
* @throws Base64DecoderException
*/
public static byte[] decode(byte[] source) throws Base64DecoderException {
return decode(source, 0, source.length);
}
/**
* Decodes web safe Base64 content in byte array format and returns
* the decoded data.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param source the string to decode (decoded in default encoding)
* @return the decoded data
*/
public static byte[] decodeWebSafe(byte[] source)
throws Base64DecoderException {
return decodeWebSafe(source, 0, source.length);
}
/**
* Decodes Base64 content in byte array format and returns
* the decoded byte array.
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @return decoded data
* @since 1.3
* @throws Base64DecoderException
*/
public static byte[] decode(byte[] source, int off, int len)
throws Base64DecoderException {
return decode(source, off, len, DECODABET);
}
/**
* Decodes web safe Base64 content in byte array format and returns
* the decoded byte array.
* Web safe encoding uses '-' instead of '+', '_' instead of '/'
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @return decoded data
*/
public static byte[] decodeWebSafe(byte[] source, int off, int len)
throws Base64DecoderException {
return decode(source, off, len, WEBSAFE_DECODABET);
}
/**
* Decodes Base64 content using the supplied decodabet and returns
* the decoded byte array.
*
* @param source the Base64 encoded data
* @param off the offset of where to begin decoding
* @param len the length of characters to decode
* @param decodabet the decodabet for decoding Base64 content
* @return decoded data
*/
public static byte[] decode(byte[] source, int off, int len, byte[] decodabet)
throws Base64DecoderException {
int len34 = len * 3 / 4;
byte[] outBuff = new byte[2 + len34]; // Upper limit on size of output
int outBuffPosn = 0;
byte[] b4 = new byte[4];
int b4Posn = 0;
int i = 0;
byte sbiCrop = 0;
byte sbiDecode = 0;
for (i = 0; i < len; i++) {
sbiCrop = (byte) (source[i + off] & 0x7f); // Only the low seven bits
sbiDecode = decodabet[sbiCrop];
if (sbiDecode >= WHITE_SPACE_ENC) { // White space Equals sign or better
if (sbiDecode >= EQUALS_SIGN_ENC) {
// An equals sign (for padding) must not occur at position 0 or 1
// and must be the last byte[s] in the encoded value
if (sbiCrop == EQUALS_SIGN) {
int bytesLeft = len - i;
byte lastByte = (byte) (source[len - 1 + off] & 0x7f);
if (b4Posn == 0 || b4Posn == 1) {
throw new Base64DecoderException(
"invalid padding byte '=' at byte offset " + i);
} else if ((b4Posn == 3 && bytesLeft > 2)
|| (b4Posn == 4 && bytesLeft > 1)) {
throw new Base64DecoderException(
"padding byte '=' falsely signals end of encoded value "
+ "at offset " + i);
} else if (lastByte != EQUALS_SIGN && lastByte != NEW_LINE) {
throw new Base64DecoderException(
"encoded value has invalid trailing byte");
}
break;
}
b4[b4Posn++] = sbiCrop;
if (b4Posn == 4) {
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
b4Posn = 0;
}
}
} else {
throw new Base64DecoderException("Bad Base64 input character at " + i
+ ": " + source[i + off] + "(decimal)");
}
}
// Because web safe encoding allows non padding base64 encodes, we
// need to pad the rest of the b4 buffer with equal signs when
// b4Posn != 0. There can be at most 2 equal signs at the end of
// four characters, so the b4 buffer must have two or three
// characters. This also catches the case where the input is
// padded with EQUALS_SIGN
if (b4Posn != 0) {
if (b4Posn == 1) {
throw new Base64DecoderException("single trailing character at offset "
+ (len - 1));
}
b4[b4Posn++] = EQUALS_SIGN;
outBuffPosn += decode4to3(b4, 0, outBuff, outBuffPosn, decodabet);
}
byte[] out = new byte[outBuffPosn];
System.arraycopy(outBuff, 0, out, 0, outBuffPosn);
return out;
}
}

View File

@@ -1,34 +0,0 @@
/* Copyright (c) 2012 Google Inc.
* Copyright (c) 2015 The Qt Company Ltd.
*
* 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.qtproject.qt.android.purchasing;
/**
* Exception thrown when encountering an invalid Base64 input character.
*
* @author nelson
*/
public class Base64DecoderException extends Exception {
public Base64DecoderException() {
super();
}
public Base64DecoderException(String s) {
super(s);
}
private static final long serialVersionUID = 1L;
}

View File

@@ -1,389 +0,0 @@
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the examples of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
package org.qtproject.qt.android.purchasing;
import java.util.ArrayList;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import com.android.billingclient.api.AcknowledgePurchaseParams;
import com.android.billingclient.api.AcknowledgePurchaseResponseListener;
import com.android.billingclient.api.BillingClient;
import com.android.billingclient.api.BillingClientStateListener;
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.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.SkuDetails;
import com.android.billingclient.api.SkuDetailsParams;
import com.android.billingclient.api.SkuDetailsResponseListener;
/***********************************************************************
** More info: https://developer.android.com/google/play/billing
** Add Dependencies below to build.gradle file:
dependencies {
def billing_version = "4.0.0"
implementation "com.android.billingclient:billing:$billing_version"
}
***********************************************************************/
public class InAppPurchase implements PurchasesUpdatedListener
{
private Context m_context = null;
private long m_nativePointer;
private String m_publicKey = null;
private int purchaseRequestCode;
private BillingClient billingClient;
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.SkuType.INAPP;
public static final String TYPE_SUBS = BillingClient.SkuType.SUBS;
public static final String TAG = "InAppPurchase";
// Should be in sync with InAppTransaction::FailureReason
public static final int FAILUREREASON_NOFAILURE = 0;
public static final int FAILUREREASON_USERCANCELED = 1;
public static final int FAILUREREASON_ERROR = 2;
public InAppPurchase()
{
//m_context = context;
//m_nativePointer = nativePointer;
}
public void initPointer(Context context, long nativePointer)
{
m_context = context;
m_nativePointer = nativePointer;
}
public void initializeConnection(){
Log.w(TAG, "initializeConnection start");
billingClient = BillingClient.newBuilder(m_context)
.enablePendingPurchases()
.setListener(this)
.build();
billingClient.startConnection(new BillingClientStateListener() {
@Override
public void onBillingSetupFinished(BillingResult billingResult) {
Log.w(TAG, "onBillingSetupFinished");
if (billingResult.getResponseCode() == RESULT_OK) {
purchasedProductsQueried(m_nativePointer);
} else {
Log.w(TAG, "onBillingSetupFinished error!" + billingResult.getResponseCode());
}
}
@Override
public void onBillingServiceDisconnected() {
Log.w(TAG, "Billing service disconnected");
}
});
}
@Override
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
int responseCode = billingResult.getResponseCode();
if (purchases == null) {
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Data missing from result");
return;
}
if (billingResult.getResponseCode() == RESULT_OK) {
handlePurchase(purchases);
} else if (responseCode == RESULT_USER_CANCELED) {
purchaseFailed(purchaseRequestCode, FAILUREREASON_USERCANCELED, "");
} else {
String errorString = getErrorString(responseCode);
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, errorString);
}
}
//Get list of purchases from onPurchasesUpdated
private void handlePurchase(List<Purchase> purchases) {
for (Purchase purchase : purchases) {
try {
if (m_publicKey != null && !Security.verifyPurchase(m_publicKey, purchase.getOriginalJson(), purchase.getSignature())) {
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Signature could not be verified");
return;
}
int purchaseState = purchase.getPurchaseState();
if (purchaseState != PurchaseState.PURCHASED) {
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Unexpected purchase state in result");
return;
}
} catch (Exception e) {
e.printStackTrace();
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, e.getMessage());
}
purchaseSucceeded(purchaseRequestCode, purchase.getSignature(), purchase.getOriginalJson(), purchase.getPurchaseToken(), purchase.getOrderId(), purchase.getPurchaseTime());
}
}
public void queryDetails(final String[] productIds) {
Log.d(TAG, "queryDetails: start");
int index = 0;
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) {
productIdList.add(productIds[i]);
}
index += productIdList.size();
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();
Log.d(TAG, "onSkuDetailsResponse: responseCode " + responseCode);
if (responseCode != RESULT_OK) {
Log.e(TAG, "queryDetails: Couldn't retrieve sku details.");
return;
}
if (skuDetailsList == null) {
Log.e(TAG, "queryDetails: No details list in response.");
return;
}
Log.d(TAG, "onSkuDetailsResponse: skuDetailsList " + skuDetailsList);
for (SkuDetails skuDetails : skuDetailsList) {
try {
String queriedProductId = skuDetails.getSku();
String queriedPrice = skuDetails.getPrice();
String queriedTitle = skuDetails.getTitle();
String queriedDescription = skuDetails.getDescription();
registerProduct(m_nativePointer,
queriedProductId,
queriedPrice,
queriedTitle,
queriedDescription);
} catch (Exception e) {
e.printStackTrace();
}
}
}
});
queryPurchasedProducts(productIdList);
}
}
//Launch Google purchasing screen
public void launchBillingFlow(String identifier, final int requestCode){
purchaseRequestCode = requestCode;
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) {
Log.e(TAG, "Unable to launch Google Play purchase screen");
String errorString = getErrorString(requestCode);
purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString);
return;
}
else if (skuDetailsList == null){
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Data missing from result");
return;
}
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
.setSkuDetails(skuDetailsList.get(0))
.build();
//Results will be delivered to onPurchasesUpdated
billingClient.launchBillingFlow((Activity) m_context, purchaseParams);
}
});
}
public void consumePurchase(String purchaseToken){
ConsumeResponseListener listener = new ConsumeResponseListener() {
@Override
public void onConsumeResponse(BillingResult billingResult, String purchaseToken) {
if (billingResult.getResponseCode() != RESULT_OK) {
Log.e(TAG, "Unable to consume purchase. Response code: " + billingResult.getResponseCode());
}
}
};
ConsumeParams consumeParams =
ConsumeParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build();
billingClient.consumeAsync(consumeParams, listener);
}
public void acknowledgeUnlockablePurchase(String purchaseToken){
AcknowledgePurchaseParams acknowledgePurchaseParams = AcknowledgePurchaseParams.newBuilder()
.setPurchaseToken(purchaseToken)
.build();
AcknowledgePurchaseResponseListener acknowledgePurchaseResponseListener = new AcknowledgePurchaseResponseListener() {
@Override
public void onAcknowledgePurchaseResponse(BillingResult billingResult) {
if (billingResult.getResponseCode() != RESULT_OK){
Log.e(TAG, "Unable to acknowledge purchase. Response code: " + billingResult.getResponseCode());
}
}
};
billingClient.acknowledgePurchase(acknowledgePurchaseParams, acknowledgePurchaseResponseListener);
}
public void queryPurchasedProducts(final List<String> productIdList) {
billingClient.queryPurchasesAsync(TYPE_INAPP, new PurchasesResponseListener() {
@Override
public void onQueryPurchasesResponse(BillingResult billingResult, List<Purchase> list) {
for (Purchase purchase : list) {
if (productIdList.contains(purchase.getSkus().get(0))) {
registerPurchased(m_nativePointer,
purchase.getSkus().get(0),
purchase.getSignature(),
purchase.getOriginalJson(),
purchase.getPurchaseToken(),
purchase.getDeveloperPayload(),
purchase.getPurchaseTime());
}
}
}
});
}
private String getErrorString(int responseCode){
String errorString;
switch (responseCode) {
case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE: errorString = "Billing unavailable"; break;
case BillingClient.BillingResponseCode.ITEM_UNAVAILABLE: errorString = "Item unavailable"; break;
case BillingClient.BillingResponseCode.DEVELOPER_ERROR: errorString = "Developer error"; break;
case BillingClient.BillingResponseCode.ERROR: errorString = "Fatal error occurred"; break;
case BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED: errorString = "Item already owned"; break;
case BillingClient.BillingResponseCode.ITEM_NOT_OWNED: errorString = "Item not owned"; break;
default: errorString = "Unknown billing error " + responseCode; break;
};
return errorString;
}
public void setPublicKey(String publicKey)
{
m_publicKey = publicKey;
}
private void purchaseFailed(int requestCode, int failureReason, String errorString)
{
purchaseFailed(m_nativePointer, requestCode, failureReason, errorString);
}
private void purchaseSucceeded(int requestCode,
String signature,
String purchaseData,
String purchaseToken,
String orderId,
long timestamp)
{
purchaseSucceeded(m_nativePointer, requestCode, signature, purchaseData, purchaseToken, orderId, timestamp);
}
private native static void queryFailed(long nativePointer, String productId);
private native static void purchasedProductsQueried(long nativePointer);
private native static void registerProduct(long nativePointer,
String productId,
String price,
String title,
String description);
private native static void purchaseFailed(long nativePointer,
int requestCode,
int failureReason,
String errorString);
private native static void purchaseSucceeded(long nativePointer,
int requestCode,
String signature,
String data,
String purchaseToken,
String orderId,
long timestamp);
private native static void registerPurchased(long nativePointer,
String identifier,
String signature,
String data,
String purchaseToken,
String orderId,
long timestamp);
}

View File

@@ -1,131 +0,0 @@
/* Copyright (c) 2012 Google Inc.
* Copyright (c) 2015 The Qt Company Ltd.
*
* 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.qtproject.qt.android.purchasing;
import android.text.TextUtils;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
/**
* Security-related methods. For a secure implementation, all of this code
* should be implemented on a server that communicates with the
* application on the device. For the sake of simplicity and clarity of this
* example, this code is included here and is executed on the device. If you
* must verify the purchases on the phone, you should obfuscate this code to
* make it harder for an attacker to replace the code with stubs that treat all
* purchases as verified.
*/
public class Security {
private static final String TAG = "IABUtil/Security";
private static final String KEY_FACTORY_ALGORITHM = "RSA";
private static final String SIGNATURE_ALGORITHM = "SHA1withRSA";
/**
* Verifies that the data was signed with the given signature, and returns
* the verified purchase. The data is in JSON format and signed
* with a private key. The data also contains the {@link PurchaseState}
* and product ID of the purchase.
* @param base64PublicKey the base64-encoded public key to use for verifying.
* @param signedData the signed JSON string (signed, not encrypted)
* @param signature the signature for the data, signed with the private key
*/
public static boolean verifyPurchase(String base64PublicKey, String signedData, String signature) {
if (signedData == null) {
Log.e(TAG, "data is null");
return false;
}
boolean verified = false;
if (!TextUtils.isEmpty(signature)) {
PublicKey key = Security.generatePublicKey(base64PublicKey);
verified = Security.verify(key, signedData, signature);
if (!verified) {
Log.w(TAG, "signature does not match data.");
return false;
}
}
return true;
}
/**
* Generates a PublicKey instance from a string containing the
* Base64-encoded public key.
*
* @param encodedPublicKey Base64-encoded public key
* @throws IllegalArgumentException if encodedPublicKey is invalid
*/
public static PublicKey generatePublicKey(String encodedPublicKey) {
try {
byte[] decodedKey = Base64.decode(encodedPublicKey);
KeyFactory keyFactory = KeyFactory.getInstance(KEY_FACTORY_ALGORITHM);
return keyFactory.generatePublic(new X509EncodedKeySpec(decodedKey));
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeySpecException e) {
Log.e(TAG, "Invalid key specification.");
throw new IllegalArgumentException(e);
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
throw new IllegalArgumentException(e);
}
}
/**
* Verifies that the signature from the server matches the computed
* signature on the data. Returns true if the data is correctly signed.
*
* @param publicKey public key associated with the developer account
* @param signedData signed data from server
* @param signature server signature
* @return true if the data and signature match
*/
public static boolean verify(PublicKey publicKey, String signedData, String signature) {
Signature sig;
try {
sig = Signature.getInstance(SIGNATURE_ALGORITHM);
sig.initVerify(publicKey);
sig.update(signedData.getBytes());
if (!sig.verify(Base64.decode(signature))) {
Log.e(TAG, "Signature verification failed.");
return false;
}
return true;
} catch (NoSuchAlgorithmException e) {
Log.e(TAG, "NoSuchAlgorithmException.");
} catch (InvalidKeyException e) {
Log.e(TAG, "Invalid key specification.");
} catch (SignatureException e) {
Log.e(TAG, "Signature exception.");
} catch (Base64DecoderException e) {
Log.e(TAG, "Base64 decoding failed.");
}
return false;
}
}

View File

@@ -1,582 +0,0 @@
#include "bhfitnesselliptical.h"
#include "ftmsbike.h"
#include "ios/lockscreen.h"
#include "virtualtreadmill.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
#include <math.h>
#ifdef Q_OS_ANDROID
#include <QLowEnergyConnectionParameters>
#endif
#include "keepawakehelper.h"
#include <chrono>
using namespace std::chrono_literals;
bhfitnesselliptical::bhfitnesselliptical(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
connect(refresh, &QTimer::timeout, this, &bhfitnesselliptical::update);
refresh->start(200ms);
}
void bhfitnesselliptical::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(gattFTMSService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
} else {
connect(gattFTMSService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
}
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, QByteArray((const char *)data, data_len));
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + QByteArray((const char *)data, data_len).toHex(' ') +
QStringLiteral(" // ") + info);
}
loop.exec();
}
void bhfitnesselliptical::forceResistance(resistance_t requestResistance) {
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
write[1] = ((uint16_t)requestResistance) & 0xFF;
writeCharacteristic(write, sizeof(write), QStringLiteral("forceResistance ") + QString::number(requestResistance));
}
void bhfitnesselliptical::update() {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
if (initRequest) {
initRequest = false;
} else if (bluetoothDevice.isValid() &&
m_control->state() == QLowEnergyController::DiscoveredState //&&
// gattCommunicationChannelService &&
// gattWriteCharacteristic.isValid() &&
// gattNotify1Characteristic.isValid() &&
/*initDone*/) {
update_metrics(false, watts());
// updating the treadmill console every second
if (sec1Update++ == (500 / refresh->interval())) {
sec1Update = 0;
// updateDisplay(elapsed);
}
if (requestResistance != -1) {
if (requestResistance > 100) {
requestResistance = 100;
} // TODO, use the bluetooth value
else if (requestResistance == 0) {
requestResistance = 1;
}
if (requestResistance != currentResistance().value()) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
forceResistance(requestResistance);
}
requestResistance = -1;
}
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
// btinit();
requestStart = -1;
emit bikeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
}
void bhfitnesselliptical::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
void bhfitnesselliptical::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
settings.value(QStringLiteral("heart_rate_belt_name"), QStringLiteral("Disabled")).toString();
bool disable_hr_frommachinery = settings.value(QStringLiteral("heart_ignore_builtin"), false).toBool();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
if (characteristic.uuid() == QBluetoothUuid::HeartRate && newValue.length() > 1) {
Heart = (uint8_t)newValue[1];
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
return;
}
if (characteristic.uuid() != QBluetoothUuid((quint16)0x2AD2)) {
return;
}
lastPacket = newValue;
union flags {
struct {
uint16_t moreData : 1;
uint16_t avgSpeed : 1;
uint16_t instantCadence : 1;
uint16_t avgCadence : 1;
uint16_t totDistance : 1;
uint16_t resistanceLvl : 1;
uint16_t instantPower : 1;
uint16_t avgPower : 1;
uint16_t expEnergy : 1;
uint16_t heartRate : 1;
uint16_t metabolic : 1;
uint16_t elapsedTime : 1;
uint16_t remainingTime : 1;
uint16_t spare : 3;
};
uint16_t word_flags;
};
flags Flags;
int index = 0;
Flags.word_flags = (newValue.at(1) << 8) | newValue.at(0);
index += 2;
if (!Flags.moreData) {
if (!settings.value(QStringLiteral("speed_power_based"), false).toBool()) {
// this elliptical doesn't send speed so i have to calculate this based on cadence
/*
Speed = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
100.0;*/
} else {
Speed = metric::calculateSpeedFromPower(m_watt.value(), Inclination.value());
}
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
}
if (Flags.avgSpeed) {
double avgSpeed;
avgSpeed =
((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)))) /
100.0;
index += 2;
emit debug(QStringLiteral("Current Average Speed: ") + QString::number(avgSpeed));
}
if (Flags.instantCadence) {
if (settings.value(QStringLiteral("cadence_sensor_name"), QStringLiteral("Disabled"))
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
2.0;
// this elliptical doesn't send speed so i have to calculate this based on cadence
if (!settings.value(QStringLiteral("speed_power_based"), false).toBool()) {
Speed = Cadence.value() / 10.0;
}
}
index += 2;
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
}
if (Flags.avgCadence) {
double avgCadence;
avgCadence =
((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index)))) /
2.0;
index += 2;
emit debug(QStringLiteral("Current Average Cadence: ") + QString::number(avgCadence));
}
if (Flags.totDistance) {
Distance = ((double)((((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)))) /
1000.0;
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
if (Flags.resistanceLvl) {
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()));
}
if (Flags.instantPower) {
if (settings.value(QStringLiteral("power_sensor_name"), QStringLiteral("Disabled"))
.toString()
.startsWith(QStringLiteral("Disabled")))
m_watt = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
}
if (Flags.avgPower) {
double avgPower;
avgPower =
((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
index += 2;
emit debug(QStringLiteral("Current Average Watt: ") + QString::number(avgPower));
}
if (Flags.expEnergy && newValue.length() > index + 1) {
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) | (uint16_t)((uint8_t)newValue.at(index))));
index += 2;
// energy per hour
index += 2;
// energy per minute
index += 1;
} else {
if (watts())
KCal +=
((((0.048 * ((double)watts()) + 1.19) * settings.value(QStringLiteral("weight"), 75.0).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
}
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
#ifdef Q_OS_ANDROID
if (settings.value("ant_heart", false).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
{
if (Flags.heartRate && !disable_hr_frommachinery && newValue.length() > index) {
Heart = ((double)((newValue.at(index))));
// index += 1; // NOTE: clang-analyzer-deadcode.DeadStores
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
} else {
Flags.heartRate = false;
}
}
if (Flags.metabolic) {
// todo
}
if (Flags.elapsedTime) {
// todo
}
if (Flags.remainingTime) {
// todo
}
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen h;
long appleWatchHeartRate = h.heartRate();
h.setKcal(KCal.value());
h.setDistance(Distance.value());
Heart = appleWatchHeartRate;
debug("Current Heart from Apple Watch: " + QString::number(appleWatchHeartRate));
#endif
#endif
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
/*
bool cadence = settings.value("bike_cadence_sensor", false).toBool();
bool ios_peloton_workaround = settings.value("ios_peloton_workaround", true).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualTreadmill_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualTreadmill_setHeartRate((uint8_t)metrics_override_heartrate());
}
*/
#endif
#endif
emit debug(QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs));
emit debug(QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime));
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
}
void bhfitnesselliptical::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;
}
}
qDebug() << QStringLiteral("all services discovered!");
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
if (s->state() == QLowEnergyService::ServiceDiscovered) {
// establish hook into notifications
connect(s, &QLowEnergyService::characteristicChanged, this, &bhfitnesselliptical::characteristicChanged);
connect(s, &QLowEnergyService::characteristicWritten, this, &bhfitnesselliptical::characteristicWritten);
connect(s, &QLowEnergyService::characteristicRead, this, &bhfitnesselliptical::characteristicRead);
connect(
s, static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &bhfitnesselliptical::errorService);
connect(s, &QLowEnergyService::descriptorWritten, this, &bhfitnesselliptical::descriptorWritten);
connect(s, &QLowEnergyService::descriptorRead, this, &bhfitnesselliptical::descriptorRead);
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
auto characteristics_list = s->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();
auto descriptors_list = c.descriptors();
for (const QLowEnergyDescriptor &d : qAsConst(descriptors_list)) {
qDebug() << QStringLiteral("descriptor uuid") << d.uuid() << QStringLiteral("handle") << d.handle();
}
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);
} else {
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).handle()
<< QStringLiteral(" is not valid");
}
qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("notification subscribed!");
} else if ((c.properties() & QLowEnergyCharacteristic::Indicate) ==
QLowEnergyCharacteristic::Indicate) {
QByteArray descriptor;
descriptor.append((char)0x02);
descriptor.append((char)0x00);
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
} else {
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).uuid()
<< c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).handle()
<< QStringLiteral(" is not valid");
}
qDebug() << s->serviceUuid() << c.uuid() << QStringLiteral("indication subscribed!");
} else if ((c.properties() & QLowEnergyCharacteristic::Read) == QLowEnergyCharacteristic::Read) {
// s->readCharacteristic(c);
// qDebug() << s->serviceUuid() << c.uuid() << "reading!";
}
QBluetoothUuid _gattWriteCharControlPointId((quint16)0x2AD9);
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharControlPointId) {
qDebug() << QStringLiteral("FTMS service and Control Point found");
gattWriteCharControlPointId = c;
gattFTMSService = s;
}
}
}
}
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !virtualTreadmill
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled = settings.value(QStringLiteral("virtual_device_enabled"), true).toBool();
if (virtual_device_enabled) {
emit debug(QStringLiteral("creating virtual treadmill interface..."));
virtualTreadmill = new virtualtreadmill(this, noHeartService);
// connect(virtualTreadmill,&virtualTreadmill::debug ,this,&bhfitnesselliptical::debug);
connect(virtualTreadmill, &virtualtreadmill::changeInclination, this,
&bhfitnesselliptical::changeInclination);
}
}
firstStateChanged = 1;
// ********************************************************************************************************
}
void bhfitnesselliptical::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
QByteArray b = newValue;
if (gattWriteCharControlPointId.isValid()) {
qDebug() << "routing FTMS packet to the bike from virtualTreadmill" << characteristic.uuid()
<< newValue.toHex(' ');
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, b);
}
}
void bhfitnesselliptical::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));
initRequest = true;
emit connectedAndDiscovered();
}
void bhfitnesselliptical::descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
qDebug() << QStringLiteral("descriptorRead ") << descriptor.name() << descriptor.uuid() << newValue.toHex(' ');
}
void bhfitnesselliptical::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
}
void bhfitnesselliptical::characteristicRead(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
qDebug() << QStringLiteral("characteristicRead ") << characteristic.uuid() << newValue.toHex(' ');
}
void bhfitnesselliptical::serviceScanDone(void) {
emit debug(QStringLiteral("serviceScanDone"));
#ifdef Q_OS_ANDROID
QLowEnergyConnectionParameters c;
c.setIntervalRange(24, 40);
c.setLatency(0);
c.setSupervisionTimeout(420);
m_control->requestConnectionUpdate(c);
#endif
initRequest = false;
auto services_list = m_control->services();
for (const QBluetoothUuid &s : qAsConst(services_list)) {
gattCommunicationChannelService.append(m_control->createServiceObject(s));
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
&bhfitnesselliptical::stateChanged);
gattCommunicationChannelService.constLast()->discoverDetails();
}
}
void bhfitnesselliptical::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("bhfitnesselliptical::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void bhfitnesselliptical::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("bhfitnesselliptical::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void bhfitnesselliptical::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &bhfitnesselliptical::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &bhfitnesselliptical::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &bhfitnesselliptical::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &bhfitnesselliptical::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;
}
}
bool bhfitnesselliptical::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void *bhfitnesselliptical::VirtualTreadmill() { return virtualTreadmill; }
void *bhfitnesselliptical::VirtualDevice() { return VirtualTreadmill(); }
uint16_t bhfitnesselliptical::watts() {
if (currentCadence().value() == 0) {
return 0;
}
return m_watt.value();
}
void bhfitnesselliptical::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();
}
}

View File

@@ -1,102 +0,0 @@
#ifndef BHFITNESSELLIPTICAL_H
#define BHFITNESSELLIPTICAL_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 "elliptical.h"
#include "virtualtreadmill.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class bhfitnesselliptical : public elliptical {
Q_OBJECT
public:
bhfitnesselliptical(bool noWriteResistance, bool noHeartService, uint8_t bikeResistanceOffset,
double bikeResistanceGain);
bool connected();
void *VirtualTreadmill();
void *VirtualDevice();
private:
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
uint16_t watts();
void forceResistance(resistance_t requestResistance);
QTimer *refresh;
virtualtreadmill *virtualTreadmill = nullptr;
QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattWriteCharControlPointId;
QLowEnergyService *gattFTMSService = nullptr;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
uint8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Q_SIGNALS:
void disconnected();
void debug(QString string);
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 characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorRead(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);
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
};
#endif // BHFITNESSELLIPTICAL_H

View File

@@ -5,11 +5,14 @@
bike::bike() { elapsed.setType(metric::METRIC_ELAPSED); }
void bike::changeResistance(resistance_t resistance) {
void bike::changeResistance(int8_t resistance) {
lastRawRequestedResistanceValue = resistance;
if (autoResistanceEnable) {
double v = (resistance * m_difficult) + gears();
requestResistance = v;
if (!ergModeSupported)
requestResistance = v;
else
requestPower = powerFromResistanceRequest(v);
emit resistanceChanged(requestResistance);
}
RequestedResistance = resistance * m_difficult + gears();
@@ -24,7 +27,7 @@ void bike::changeInclination(double grade, double percentage) {
}
// originally made for renphobike, but i guess it could be very generic
uint16_t bike::powerFromResistanceRequest(resistance_t requestResistance) {
uint16_t bike::powerFromResistanceRequest(int8_t requestResistance) {
// this bike has resistance level to N.m so the formula is Power (kW) = Torque (N.m) x Speed (RPM) / 9.5488
double cadence = RequestedCadence.value();
if (cadence <= 0)
@@ -43,25 +46,14 @@ void bike::changePower(int32_t power) {
// bool erg_mode = settings.value(QStringLiteral("zwift_erg"), false).toBool(); //Not used anywhere in code
double erg_filter_upper = settings.value(QStringLiteral("zwift_erg_filter"), 0.0).toDouble();
double erg_filter_lower = settings.value(QStringLiteral("zwift_erg_filter_down"), 0.0).toDouble();
double zwift_erg_resistance_up = settings.value(QStringLiteral("zwift_erg_resistance_up"), 999.0).toDouble();
double zwift_erg_resistance_down = settings.value(QStringLiteral("zwift_erg_resistance_down"), 0.0).toDouble();
double deltaDown = wattsMetric().value() - ((double)power);
double deltaUp = ((double)power) - wattsMetric().value();
qDebug() << QStringLiteral("filter ") + QString::number(deltaUp) + " " + QString::number(deltaDown) + " " +
QString::number(erg_filter_upper) + " " + QString::number(erg_filter_lower);
if (!ergModeSupported && force_resistance /*&& erg_mode*/ &&
(deltaUp > erg_filter_upper || deltaDown > erg_filter_lower)) {
resistance_t r = (resistance_t)resistanceFromPowerRequest(power);
if ((double)r > zwift_erg_resistance_up) {
qDebug() << "zwift_erg_resistance_up filter enabled!";
r = (resistance_t)zwift_erg_resistance_up;
} else if ((double)r < zwift_erg_resistance_down) {
qDebug() << "zwift_erg_resistance_down filter enabled!";
r = (resistance_t)zwift_erg_resistance_down;
}
changeResistance(r); // resistance start from 1
}
(deltaUp > erg_filter_upper || deltaDown > erg_filter_lower))
changeResistance((int8_t)resistanceFromPowerRequest(power)); // resistance start from 1
}
int8_t bike::gears() { return m_gears; }
@@ -84,10 +76,10 @@ uint8_t bike::fanSpeed() { return FanSpeed; }
bool bike::connected() { return false; }
uint16_t bike::watts() { return 0; }
metric bike::pelotonResistance() { return m_pelotonResistance; }
resistance_t bike::pelotonToBikeResistance(int pelotonResistance) { return pelotonResistance; }
resistance_t bike::resistanceFromPowerRequest(uint16_t power) { return power / 10; } // in order to have something
int bike::pelotonToBikeResistance(int pelotonResistance) { return pelotonResistance; }
uint8_t bike::resistanceFromPowerRequest(uint16_t power) { return power / 10; } // in order to have something
void bike::cadenceSensor(uint8_t cadence) { Cadence.setValue(cadence); }
void bike::powerSensor(uint16_t power) { m_watt.setValue(power, false); }
void bike::powerSensor(uint16_t power) { m_watt.setValue(power); }
bluetoothdevice::BLUETOOTH_TYPE bike::deviceType() { return bluetoothdevice::BIKE; }
@@ -239,5 +231,3 @@ uint8_t bike::metrics_override_heartrate() {
}
return qRound(currentHeart().value());
}
bool bike::inclinationAvailableByHardware() { return false; }

View File

@@ -20,10 +20,9 @@ class bike : public bluetoothdevice {
virtual uint16_t lastCrankEventTime();
virtual bool connected();
virtual uint16_t watts();
virtual resistance_t pelotonToBikeResistance(int pelotonResistance);
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
virtual uint16_t powerFromResistanceRequest(resistance_t requestResistance);
virtual bool ergManagedBySS2K() { return false; }
virtual int pelotonToBikeResistance(int pelotonResistance);
virtual uint8_t resistanceFromPowerRequest(uint16_t power);
virtual uint16_t powerFromResistanceRequest(int8_t requestResistance);
bluetoothdevice::BLUETOOTH_TYPE deviceType();
metric pelotonResistance();
void clearStats();
@@ -32,18 +31,10 @@ class bike : public bluetoothdevice {
uint8_t metrics_override_heartrate();
void setGears(int8_t d);
int8_t gears();
/**
* @brief currentSteeringAngle Gets a metric object to get or set the current steering angle
* for the Elite Sterzo or emulating device. Expected range -45 to +45 degrees.
* @return A metric object.
*/
metric currentSteeringAngle() { return m_steeringAngle; }
virtual bool inclinationAvailableByHardware();
public Q_SLOTS:
virtual void changeResistance(resistance_t res);
virtual void changeResistance(int8_t res);
virtual void changeCadence(int16_t cad);
virtual void changePower(int32_t power);
virtual void changeRequestedPelotonResistance(int8_t resistance);
@@ -51,12 +42,11 @@ class bike : public bluetoothdevice {
virtual void powerSensor(uint16_t power);
virtual void changeInclination(double grade, double percentage);
virtual void changeSteeringAngle(double angle) { m_steeringAngle = angle; }
virtual void resistanceFromFTMSAccessory(resistance_t res) { Q_UNUSED(res); }
Q_SIGNALS:
void bikeStarted();
void resistanceChanged(resistance_t resistance);
void resistanceRead(resistance_t resistance);
void resistanceChanged(int8_t resistance);
void resistanceRead(int8_t resistance);
void steeringAngleChanged(double angle);
protected:
@@ -65,15 +55,15 @@ class bike : public bluetoothdevice {
metric RequestedCadence;
metric RequestedPower;
resistance_t requestResistance = -1;
double requestInclination = -100;
int8_t requestResistance = -1;
double requestInclination = -1;
int16_t requestPower = -1;
bool ergModeSupported = false; // if a bike has this mode supported, when from the virtual bike there is a power
// request there is no need to translate in resistance levels
int8_t m_gears = 0;
resistance_t lastRawRequestedResistanceValue = -1;
int8_t lastRawRequestedResistanceValue = -1;
uint16_t LastCrankEventTime = 0;
double CrankRevs = 0;

File diff suppressed because it is too large Load Diff

View File

@@ -18,16 +18,11 @@
#include <QtCore/qloggingcategory.h>
#include "activiotreadmill.h"
#include "bhfitnesselliptical.h"
#include "bluetoothdevice.h"
#include "bowflext216treadmill.h"
#include "bowflextreadmill.h"
#include "chronobike.h"
#include "concept2skierg.h"
#include "cscbike.h"
#include "domyosbike.h"
#include "domyoselliptical.h"
#include "domyosrower.h"
#include "domyostreadmill.h"
#include "echelonconnectsport.h"
@@ -36,7 +31,6 @@
#include "elitesterzosmart.h"
#include "eslinkertreadmill.h"
#include "fakebike.h"
#include "fakeelliptical.h"
#include "fitmetria_fanfit.h"
#include "fitplusbike.h"
@@ -49,28 +43,14 @@
#include "horizontreadmill.h"
#include "iconceptbike.h"
#include "inspirebike.h"
#include "keepbike.h"
#include "kingsmithr1protreadmill.h"
#include "kingsmithr2treadmill.h"
#include "m3ibike.h"
#include "mcfbike.h"
#include "nautilusbike.h"
#include "nautiluselliptical.h"
#include "nautilustreadmill.h"
#include "nordictrackelliptical.h"
#include "nordictrackifitadbtreadmill.h"
#include "nordictrackifitadbbike.h"
#include "npecablebike.h"
#include "octanetreadmill.h"
#include "pafersbike.h"
#include "paferstreadmill.h"
#include "proformbike.h"
#include "proformelliptical.h"
#include "proformellipticaltrainer.h"
#include "proformrower.h"
#include "proformtreadmill.h"
#include "proformwifibike.h"
#include "proformwifitreadmill.h"
#include "schwinnic4bike.h"
#include "signalhandler.h"
#include "skandikawiribike.h"
@@ -80,7 +60,6 @@
#include "strydrunpowersensor.h"
#include "shuaa5treadmill.h"
#include "solebike.h"
#include "soleelliptical.h"
#include "solef80treadmill.h"
@@ -92,18 +71,14 @@
#include "renphobike.h"
#include "tacxneo2.h"
#include "technogymmyruntreadmill.h"
#include "technogymmyruntreadmillrfcomm.h"
#include "echelonstride.h"
#include "templateinfosenderbuilder.h"
#include "toorxtreadmill.h"
#include "treadmill.h"
#include "truetreadmill.h"
#include "trxappgateusbbike.h"
#include "trxappgateusbtreadmill.h"
#include "ultrasportbike.h"
#include "wahookickrsnapbike.h"
#include "yesoulbike.h"
class bluetooth : public QObject, public SignalHandler {
@@ -115,7 +90,6 @@ class bluetooth : public QObject, public SignalHandler {
bool testResistance = false, uint8_t bikeResistanceOffset = 4, double bikeResistanceGain = 1.0);
~bluetooth();
bluetoothdevice *device();
bluetoothdevice *externalInclination() { return eliteRizer; }
bluetoothdevice *heartRateDevice() { return heartRateBelt; }
QList<QBluetoothDeviceInfo> devices;
bool onlyDiscover = false;
@@ -127,44 +101,23 @@ class bluetooth : public QObject, public SignalHandler {
TemplateInfoSenderBuilder *innerTemplateManager = nullptr;
QFile *debugCommsLog = nullptr;
QBluetoothDeviceDiscoveryAgent *discoveryAgent;
bhfitnesselliptical *bhFitnessElliptical = nullptr;
bowflextreadmill *bowflexTreadmill = nullptr;
bowflext216treadmill *bowflexT216Treadmill = nullptr;
fitshowtreadmill *fitshowTreadmill = nullptr;
concept2skierg *concept2Skierg = nullptr;
domyostreadmill *domyos = nullptr;
domyosbike *domyosBike = nullptr;
domyosrower *domyosRower = nullptr;
domyoselliptical *domyosElliptical = nullptr;
toorxtreadmill *toorx = nullptr;
iconceptbike *iConceptBike = nullptr;
trxappgateusbtreadmill *trxappgateusb = nullptr;
spirittreadmill *spiritTreadmill = nullptr;
activiotreadmill *activioTreadmill = nullptr;
nautilusbike *nautilusBike = nullptr;
nautiluselliptical *nautilusElliptical = nullptr;
nautilustreadmill *nautilusTreadmill = nullptr;
trxappgateusbbike *trxappgateusbBike = nullptr;
echelonconnectsport *echelonConnectSport = nullptr;
yesoulbike *yesoulBike = nullptr;
flywheelbike *flywheelBike = nullptr;
nordictrackelliptical *nordictrackElliptical = nullptr;
nordictrackifitadbtreadmill *nordictrackifitadbTreadmill = nullptr;
nordictrackifitadbbike *nordictrackifitadbBike = nullptr;
octanetreadmill *octaneTreadmill = nullptr;
proformrower *proformRower = nullptr;
proformbike *proformBike = nullptr;
proformwifibike *proformWifiBike = nullptr;
proformwifitreadmill *proformWifiTreadmill = nullptr;
proformelliptical *proformElliptical = nullptr;
proformellipticaltrainer *proformEllipticalTrainer = nullptr;
proformtreadmill *proformTreadmill = nullptr;
horizontreadmill *horizonTreadmill = nullptr;
technogymmyruntreadmill *technogymmyrunTreadmill = nullptr;
#ifndef Q_OS_IOS
technogymmyruntreadmillrfcomm *technogymmyrunrfcommTreadmill = nullptr;
#endif
truetreadmill *trueTreadmill = nullptr;
horizongr7bike *horizonGr7Bike = nullptr;
schwinnic4bike *schwinnIC4Bike = nullptr;
sportstechbike *sportsTechBike = nullptr;
@@ -178,7 +131,6 @@ class bluetooth : public QObject, public SignalHandler {
mcfbike *mcfBike = nullptr;
npecablebike *npeCableBike = nullptr;
stagesbike *stagesBike = nullptr;
solebike *soleBike = nullptr;
soleelliptical *soleElliptical = nullptr;
solef80treadmill *soleF80 = nullptr;
chronobike *chronoBike = nullptr;
@@ -187,12 +139,10 @@ class bluetooth : public QObject, public SignalHandler {
ftmsrower *ftmsRower = nullptr;
smartrowrower *smartrowRower = nullptr;
echelonstride *echelonStride = nullptr;
keepbike *keepBike = nullptr;
kingsmithr1protreadmill *kingsmithR1ProTreadmill = nullptr;
kingsmithr2treadmill *kingsmithR2Treadmill = nullptr;
ftmsbike *ftmsBike = nullptr;
pafersbike *pafersBike = nullptr;
paferstreadmill *pafersTreadmill = nullptr;
tacxneo2 *tacxneo2Bike = nullptr;
renphobike *renphoBike = nullptr;
shuaa5treadmill *shuaA5Treadmill = nullptr;
@@ -202,13 +152,10 @@ class bluetooth : public QObject, public SignalHandler {
stagesbike *powerSensor = nullptr;
strydrunpowersensor *powerSensorRun = nullptr;
stagesbike *powerBike = nullptr;
ultrasportbike *ultraSportBike = nullptr;
wahookickrsnapbike *wahooKickrSnapBike = nullptr;
strydrunpowersensor *powerTreadmill = nullptr;
eliterizer *eliteRizer = nullptr;
elitesterzosmart *eliteSterzoSmart = nullptr;
fakebike *fakeBike = nullptr;
fakeelliptical *fakeElliptical = nullptr;
QList<fitmetria_fanfit *> fitmetriaFanfit;
QString filterDevice = QLatin1String("");
@@ -233,10 +180,6 @@ class bluetooth : public QObject, public SignalHandler {
bool eliteSterzoSmartAvaiable();
bool fitmetria_fanfit_isconnected(QString name);
#ifdef Q_OS_WIN
QTimer discoveryTimeout;
#endif
signals:
void deviceConnected(QBluetoothDeviceInfo b);
void deviceFound(QString name);

View File

@@ -1,6 +1,5 @@
#include "bluetoothdevice.h"
#include <QFile>
#include <QSettings>
#include <QTime>
@@ -30,7 +29,7 @@ metric bluetoothdevice::currentResistance() { return Resistance; }
metric bluetoothdevice::currentCadence() { return Cadence; }
double bluetoothdevice::currentCrankRevolutions() { return 0; }
uint16_t bluetoothdevice::lastCrankEventTime() { return 0; }
void bluetoothdevice::changeResistance(resistance_t resistance) {}
void bluetoothdevice::changeResistance(int8_t resistance) {}
void bluetoothdevice::changePower(int32_t power) {}
void bluetoothdevice::changeInclination(double grade, double percentage) {}
@@ -123,9 +122,6 @@ double bluetoothdevice::difficult() { return m_difficult; }
void bluetoothdevice::cadenceSensor(uint8_t cadence) { Q_UNUSED(cadence) }
void bluetoothdevice::powerSensor(uint16_t power) { Q_UNUSED(power) }
void bluetoothdevice::speedSensor(double speed) { Q_UNUSED(speed) }
void bluetoothdevice::instantaneousStrideLengthSensor(double length) { Q_UNUSED(length); }
void bluetoothdevice::groundContactSensor(double groundContact) { Q_UNUSED(groundContact); }
void bluetoothdevice::verticalOscillationSensor(double verticalOscillation) { Q_UNUSED(verticalOscillation); }
double bluetoothdevice::calculateMETS() { return ((0.048 * m_watt.value()) + 1.19); }
@@ -156,8 +152,8 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts) {
WeightLoss = metric::calculateWeightLoss(KCal.value());
if (watt_calc) {
m_watt = watts;
WattKg = m_watt.value() / settings.value(QStringLiteral("weight"), 75.0).toFloat();
}
WattKg = m_watt.value() / settings.value(QStringLiteral("weight"), 75.0).toFloat();
} else if (m_watt.value() > 0) {
m_watt = 0;
@@ -167,16 +163,14 @@ void bluetoothdevice::update_metrics(bool watt_calc, const double watts) {
// useful for FTP test
if (watt_calc) {
m_watt = watts;
WattKg = m_watt.value() / settings.value(QStringLiteral("weight"), 75.0).toFloat();
}
WattKg = m_watt.value() / settings.value(QStringLiteral("weight"), 75.0).toFloat();
} else if (m_watt.value() > 0) {
m_watt = 0;
WattKg = 0;
}
METS = calculateMETS();
if (currentInclination().value() > 0)
elevationAcc += (currentSpeed().value() / 3600.0) * 1000.0 * (currentInclination().value() / 100.0) * deltaTime;
_lastTimeUpdate = current;
_firstUpdate = false;
@@ -195,7 +189,6 @@ void bluetoothdevice::clearStats() {
m_watt.clear(false);
WeightLoss.clear(false);
WattKg.clear(false);
Cadence.clear(false);
}
void bluetoothdevice::setPaused(bool p) {
@@ -211,7 +204,6 @@ void bluetoothdevice::setPaused(bool p) {
m_watt.setPaused(p);
WeightLoss.setPaused(p);
WattKg.setPaused(p);
Cadence.setPaused(p);
}
void bluetoothdevice::setLap() {
@@ -226,7 +218,6 @@ void bluetoothdevice::setLap() {
m_watt.setLap(false);
WeightLoss.setLap(false);
WattKg.setLap(false);
Cadence.setLap(false);
}
QStringList bluetoothdevice::metrics() {
@@ -261,8 +252,6 @@ QStringList bluetoothdevice::metrics() {
return r;
}
resistance_t bluetoothdevice::maxResistance() { return 100; }
uint8_t bluetoothdevice::metrics_override_heartrate() {
QSettings settings;
@@ -346,32 +335,7 @@ uint8_t bluetoothdevice::metrics_override_heartrate() {
return currentHeart().value();
}
void bluetoothdevice::changeGeoPosition(QGeoCoordinate p, double azimuth, double avgAzimuthNext300Meters) {
coordinateTS = QDateTime::currentMSecsSinceEpoch();
coordinateOdometer = odometer();
coordinate = p;
this->setAverageAzimuthNext300m(avgAzimuthNext300Meters);
this->azimuth = azimuth;
}
QGeoCoordinate bluetoothdevice::currentCordinate() {
if (coordinateTS) {
double distance = odometer() - coordinateOdometer;
QGeoCoordinate c = coordinate.atDistanceAndAzimuth(distance * 1000.0, this->azimuth);
c.setAltitude(coordinate.altitude());
// qDebug() << "currentCordinate" << c << distance << currentSpeed().value();
return c;
}
return coordinate;
}
void bluetoothdevice::changeGeoPosition(QGeoCoordinate p) { coordinate = p; }
QGeoCoordinate bluetoothdevice::currentCordinate() { return coordinate; }
void bluetoothdevice::workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state) { lastState = state; }
void bluetoothdevice::setInclination(double inclination) { Inclination = inclination; }
void bluetoothdevice::setGPXFile(QString filename) {
gpxFileName = filename;
QFile input(filename);
if (input.open(QIODevice::ReadOnly)) {
QByteArray asSaved = input.readAll();
gpxBase64 = "data:@file/xml;base64," + asSaved.toBase64();
input.close();
}
}

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