mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
19 Commits
android_pi
...
kingsmith-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
53a41b35e2 | ||
|
|
1f178ff3fa | ||
|
|
fd3cc25732 | ||
|
|
64e9052f25 | ||
|
|
e8a59937f6 | ||
|
|
20c69a374c | ||
|
|
3eb3dbecc8 | ||
|
|
f8a6e428ee | ||
|
|
196bfe351d | ||
|
|
5169cf78a4 | ||
|
|
11425ea861 | ||
|
|
d4a5604af6 | ||
|
|
b53ff4a516 | ||
|
|
1adf0b2e08 | ||
|
|
883c4c56d6 | ||
|
|
13a2c95671 | ||
|
|
48df8b3057 | ||
|
|
e771b4a62d | ||
|
|
c7bed0a259 |
@@ -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.12.10" android:versionCode="440" android:installLocation="auto">
|
||||
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="2.12.13" android:versionCode="443" 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:resizeableActivity="true" android:supportsPictureInPicture="true" android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="qdomyos-zwift" android:launchMode="singleTask">
|
||||
<activity android:theme="@style/Theme.AppCompat" android:exported="true" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="qdomyos-zwift" android:launchMode="singleTop">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
@@ -67,13 +67,14 @@
|
||||
* none - useful for apps that don't use any of the above Qt modules
|
||||
-->
|
||||
<meta-data android:name="android.app.extract_android_style" android:value="default"/>
|
||||
<!-- extract android style -->
|
||||
<!-- extract android style -->
|
||||
</activity>
|
||||
<service
|
||||
android:name=".ForegroundService"
|
||||
android:enabled="true"
|
||||
android:exported="true"></service>
|
||||
<service android:name=".ChannelService"></service>
|
||||
<service android:name=".FloatingWindowGFG" android:enabled="true" android:exported="true"/>
|
||||
<activity android:name="org.cagnulen.qdomyoszwift.MyActivity" />
|
||||
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
|
||||
</application>
|
||||
@@ -87,7 +88,8 @@
|
||||
<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="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
|
||||
<uses-permission android:name="com.android.vending.BILLING"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
|
||||
</manifest>
|
||||
|
||||
@@ -17,9 +17,14 @@ repositories {
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
dependencies {
|
||||
def appcompat_version = "1.1.0"
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
|
||||
implementation "com.android.billingclient:billing:4.0.0"
|
||||
implementation 'com.android.support:appcompat-v7:28.0.0'
|
||||
|
||||
implementation "androidx.appcompat:appcompat:$appcompat_version"
|
||||
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
|
||||
androidTestImplementation "com.android.support:support-annotations:28.0.0"
|
||||
}
|
||||
|
||||
@@ -41,6 +46,11 @@ android {
|
||||
|
||||
buildToolsVersion '29.0.2'
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
|
||||
2
src/android/gradle.properties
Normal file
2
src/android/gradle.properties
Normal file
@@ -0,0 +1,2 @@
|
||||
android.useAndroidX=true
|
||||
android.enableJetifier=true
|
||||
19
src/android/res/layout/floating_layout.xml
Normal file
19
src/android/res/layout/floating_layout.xml
Normal file
@@ -0,0 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#CC000000">
|
||||
|
||||
<WebView android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="20dp"
|
||||
android:gravity="center"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/buttonMaximize"/>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
65
src/android/src/FloatingHandler.java
Normal file
65
src/android/src/FloatingHandler.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.ActivityManager;
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import android.os.Looper;
|
||||
import android.os.Handler;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
|
||||
public class FloatingHandler {
|
||||
static Context _context;
|
||||
static public int _port = 0;
|
||||
|
||||
public static void show(Context context, int port) {
|
||||
_context = context;
|
||||
_port = port;
|
||||
// First it confirms whether the
|
||||
// 'Display over other apps' permission in given
|
||||
if (checkOverlayDisplayPermission()) {
|
||||
// FloatingWindowGFG service is started
|
||||
context.startService(new Intent(context, FloatingWindowGFG.class));
|
||||
// The MainActivity closes here
|
||||
//finish();
|
||||
} else {
|
||||
Intent intent = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:" + _context.getPackageName()));
|
||||
|
||||
// This method will start the intent. It takes two parameter, one is the Intent and the other is
|
||||
// an requestCode Integer. Here it is -1.
|
||||
Activity a = (Activity)_context;
|
||||
a.startActivityForResult(intent, -1);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkOverlayDisplayPermission() {
|
||||
// Android Version is lesser than Marshmallow
|
||||
// or the API is lesser than 23
|
||||
// doesn't need 'Display over other apps' permission enabling.
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
|
||||
// If 'Display over other apps' is not enabled it
|
||||
// will return false or else true
|
||||
if (!Settings.canDrawOverlays(_context)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
188
src/android/src/FloatingWindowGFG.java
Normal file
188
src/android/src/FloatingWindowGFG.java
Normal file
@@ -0,0 +1,188 @@
|
||||
// https://www.geeksforgeeks.org/how-to-make-a-floating-window-application-in-android/
|
||||
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.app.Service;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.DisplayMetrics;
|
||||
import android.view.Gravity;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.Toast;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class FloatingWindowGFG extends Service {
|
||||
|
||||
// The reference variables for the
|
||||
// ViewGroup, WindowManager.LayoutParams,
|
||||
// WindowManager, Button, EditText classes are created
|
||||
private ViewGroup floatView;
|
||||
private int LAYOUT_TYPE;
|
||||
private WindowManager.LayoutParams floatWindowLayoutParam;
|
||||
private WindowManager windowManager;
|
||||
private Button maximizeBtn;
|
||||
|
||||
// As FloatingWindowGFG inherits Service class,
|
||||
// it actually overrides the onBind method
|
||||
@Nullable
|
||||
@Override
|
||||
public IBinder onBind(Intent intent) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
|
||||
// The screen height and width are calculated, cause
|
||||
// the height and width of the floating window is set depending on this
|
||||
DisplayMetrics metrics = getApplicationContext().getResources().getDisplayMetrics();
|
||||
int width = metrics.widthPixels;
|
||||
int height = metrics.heightPixels;
|
||||
|
||||
// To obtain a WindowManager of a different Display,
|
||||
// we need a Context for that display, so WINDOW_SERVICE is used
|
||||
windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
|
||||
|
||||
// A LayoutInflater instance is created to retrieve the
|
||||
// LayoutInflater for the floating_layout xml
|
||||
LayoutInflater inflater = (LayoutInflater) getBaseContext().getSystemService(LAYOUT_INFLATER_SERVICE);
|
||||
|
||||
// inflate a new view hierarchy from the floating_layout xml
|
||||
floatView = (ViewGroup) inflater.inflate(R.layout.floating_layout, null);
|
||||
|
||||
WebView wv = (WebView)floatView.findViewById(R.id.webview);
|
||||
wv.setWebViewClient(new WebViewClient(){
|
||||
public boolean shouldOverrideUrlLoading(WebView view, String url) {
|
||||
view.loadUrl(url);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
WebSettings settings = wv.getSettings();
|
||||
settings.setJavaScriptEnabled(true);
|
||||
wv.loadUrl("http://localhost:" + FloatingHandler._port + "/floating/floating.htm");
|
||||
wv.clearView();
|
||||
wv.measure(100, 100);
|
||||
wv.setAlpha(0.6f);
|
||||
settings.setBuiltInZoomControls(true);
|
||||
settings.setUseWideViewPort(true);
|
||||
settings.setDomStorageEnabled(true);
|
||||
Log.d("QZ","loadurl");
|
||||
|
||||
|
||||
// WindowManager.LayoutParams takes a lot of parameters to set the
|
||||
// the parameters of the layout. One of them is Layout_type.
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
// If API Level is more than 26, we need TYPE_APPLICATION_OVERLAY
|
||||
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
|
||||
} else {
|
||||
// If API Level is lesser than 26, then we can
|
||||
// use TYPE_SYSTEM_ERROR,
|
||||
// TYPE_SYSTEM_OVERLAY, TYPE_PHONE, TYPE_PRIORITY_PHONE.
|
||||
// But these are all
|
||||
// deprecated in API 26 and later. Here TYPE_TOAST works best.
|
||||
LAYOUT_TYPE = WindowManager.LayoutParams.TYPE_TOAST;
|
||||
}
|
||||
|
||||
// Now the Parameter of the floating-window layout is set.
|
||||
// 1) The Width of the window will be 55% of the phone width.
|
||||
// 2) The Height of the window will be 58% of the phone height.
|
||||
// 3) Layout_Type is already set.
|
||||
// 4) Next Parameter is Window_Flag. Here FLAG_NOT_FOCUSABLE is used. But
|
||||
// problem with this flag is key inputs can't be given to the EditText.
|
||||
// This problem is solved later.
|
||||
// 5) Next parameter is Layout_Format. System chooses a format that supports
|
||||
// translucency by PixelFormat.TRANSLUCENT
|
||||
floatWindowLayoutParam = new WindowManager.LayoutParams(
|
||||
(int) (width * (0.30f)),
|
||||
(int) (height * (0.22f)),
|
||||
LAYOUT_TYPE,
|
||||
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
|
||||
PixelFormat.TRANSLUCENT
|
||||
);
|
||||
|
||||
// The Gravity of the Floating Window is set.
|
||||
// The Window will appear in the center of the screen
|
||||
floatWindowLayoutParam.gravity = Gravity.CENTER;
|
||||
|
||||
// X and Y value of the window is set
|
||||
floatWindowLayoutParam.x = 0;
|
||||
floatWindowLayoutParam.y = 0;
|
||||
|
||||
// The ViewGroup that inflates the floating_layout.xml is
|
||||
// added to the WindowManager with all the parameters
|
||||
windowManager.addView(floatView, floatWindowLayoutParam);
|
||||
|
||||
|
||||
// Another feature of the floating window is, the window is movable.
|
||||
// The window can be moved at any position on the screen.
|
||||
floatView.setOnTouchListener(new View.OnTouchListener() {
|
||||
final WindowManager.LayoutParams floatWindowLayoutUpdateParam = floatWindowLayoutParam;
|
||||
double x;
|
||||
double y;
|
||||
double px;
|
||||
double py;
|
||||
|
||||
@Override
|
||||
public boolean onTouch(View v, MotionEvent event) {
|
||||
|
||||
Log.d("QZ","onTouch");
|
||||
|
||||
switch (event.getAction()) {
|
||||
// When the window will be touched,
|
||||
// the x and y position of that position
|
||||
// will be retrieved
|
||||
case MotionEvent.ACTION_DOWN:
|
||||
x = floatWindowLayoutUpdateParam.x;
|
||||
y = floatWindowLayoutUpdateParam.y;
|
||||
|
||||
// returns the original raw X
|
||||
// coordinate of this event
|
||||
px = event.getRawX();
|
||||
|
||||
// returns the original raw Y
|
||||
// coordinate of this event
|
||||
py = event.getRawY();
|
||||
break;
|
||||
// When the window will be dragged around,
|
||||
// it will update the x, y of the Window Layout Parameter
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
floatWindowLayoutUpdateParam.x = (int) ((x + event.getRawX()) - px);
|
||||
floatWindowLayoutUpdateParam.y = (int) ((y + event.getRawY()) - py);
|
||||
|
||||
// updated parameter is applied to the WindowManager
|
||||
windowManager.updateViewLayout(floatView, floatWindowLayoutUpdateParam);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// It is called when stopService()
|
||||
// method is called in MainActivity
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
stopSelf();
|
||||
// Window is removed from the screen
|
||||
windowManager.removeView(floatView);
|
||||
}
|
||||
}
|
||||
@@ -8,8 +8,9 @@ import android.app.Service;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
|
||||
public class ForegroundService extends Service {
|
||||
public static final String CHANNEL_ID = "ForegroundServiceChannel";
|
||||
@Override
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
package org.cagnulen.qdomyoszwift;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.os.Message;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.NumberPicker;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import android.util.Log;
|
||||
import android.content.Intent;
|
||||
import android.app.PictureInPictureParams;
|
||||
import android.util.Rational;
|
||||
import android.provider.Settings;
|
||||
import android.view.Display;
|
||||
import android.graphics.Point;
|
||||
|
||||
public class PiP {
|
||||
|
||||
public static void enterPiP(Activity a) {
|
||||
Display d = a.getWindowManager()
|
||||
.getDefaultDisplay();
|
||||
Point p = new Point();
|
||||
d.getSize(p);
|
||||
int width = p.x;
|
||||
int height = p.y;
|
||||
|
||||
Rational ratio
|
||||
= new Rational(width, height);
|
||||
PictureInPictureParams.Builder
|
||||
pip_Builder
|
||||
= new PictureInPictureParams
|
||||
.Builder();
|
||||
pip_Builder.setAspectRatio(ratio).build();
|
||||
Log.v("QZ", "Pip");
|
||||
a.enterPictureInPictureMode(pip_Builder.build());
|
||||
}
|
||||
}
|
||||
@@ -151,6 +151,10 @@ void bluetooth::finished() {
|
||||
bool eliteSterzoSmartFound = eliteSterzoSmartName.startsWith(QStringLiteral("Disabled"));
|
||||
bool heartRateBeltFound = heartRateBeltName.startsWith(QStringLiteral("Disabled"));
|
||||
bool ftmsAccessoryFound = ftmsAccessoryName.startsWith(QStringLiteral("Disabled"));
|
||||
bool ss2k_peloton = settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool();
|
||||
|
||||
if (ss2k_peloton)
|
||||
ftmsAccessoryFound = true;
|
||||
|
||||
// since i can have multiple fanfit i can't wait more because i don't have the full list of the fanfit
|
||||
// devices connected to QZ. edit: let's wait at the last one item
|
||||
@@ -237,6 +241,9 @@ bool bluetooth::ftmsAccessoryAvaiable() {
|
||||
QSettings settings;
|
||||
QString ftmsAccessoryName =
|
||||
settings.value(QZSettings::ftms_accessory_name, QZSettings::default_ftms_accessory_name).toString();
|
||||
bool ss2k_peloton = settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool();
|
||||
if (ss2k_peloton)
|
||||
return false;
|
||||
|
||||
Q_FOREACH (QBluetoothDeviceInfo b, devices) {
|
||||
if (!ftmsAccessoryName.compare(b.name())) {
|
||||
@@ -1184,10 +1191,14 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
|
||||
(b.name().toUpper().startsWith("DS25-")) || // Bodytone DS25
|
||||
(b.name().toUpper().startsWith("SCHWINN 510T")) ||
|
||||
(b.name().toUpper().startsWith("FLXCY-")) || // Pro FlexBike
|
||||
(b.name().toUpper().startsWith("WAHOO KICKR")) || (b.name().toUpper().startsWith("B94")) ||
|
||||
(b.name().toUpper().startsWith("STAGES BIKE")) || (b.name().toUpper().startsWith("SUITO")) ||
|
||||
(b.name().toUpper().startsWith("D2RIDE")) || (b.name().toUpper().startsWith("DIRETO XR")) ||
|
||||
(b.name().toUpper().startsWith("SMB1")) || (b.name().toUpper().startsWith("INRIDE"))) &&
|
||||
(b.name().toUpper().startsWith(ftmsAccessoryName.toUpper()) &&
|
||||
settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton)
|
||||
.toBool()) || // ss2k on a peloton bike
|
||||
(b.name().toUpper().startsWith("WAHOO KICKR")) ||
|
||||
(b.name().toUpper().startsWith("B94")) || (b.name().toUpper().startsWith("STAGES BIKE")) ||
|
||||
(b.name().toUpper().startsWith("SUITO")) || (b.name().toUpper().startsWith("D2RIDE")) ||
|
||||
(b.name().toUpper().startsWith("DIRETO XR")) || (b.name().toUpper().startsWith("SMB1")) ||
|
||||
(b.name().toUpper().startsWith("INRIDE"))) &&
|
||||
!ftmsBike && !snodeBike && !fitPlusBike && !stagesBike && filter) {
|
||||
this->setLastBluetoothDevice(b);
|
||||
this->stopDiscovery();
|
||||
@@ -1964,7 +1975,8 @@ void bluetooth::connectedAndDiscovered() {
|
||||
|
||||
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
|
||||
if (((b.name().startsWith(ftmsAccessoryName))) && !ftmsAccessory &&
|
||||
!ftmsAccessoryName.startsWith(QStringLiteral("Disabled"))) {
|
||||
!ftmsAccessoryName.startsWith(QStringLiteral("Disabled")) &&
|
||||
!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool()) {
|
||||
settings.setValue(QZSettings::ftms_accessory_lastdevice_name, b.name());
|
||||
|
||||
#ifndef Q_OS_IOS
|
||||
|
||||
@@ -254,14 +254,14 @@ void bowflext216treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
}
|
||||
|
||||
double bowflext216treadmill::GetSpeedFromPacket(const QByteArray &packet) {
|
||||
if(bowflex_t6 == false) {
|
||||
if (bowflex_t6 == false) {
|
||||
uint16_t convertedData = (packet.at(7) << 8) | packet.at(6);
|
||||
double data = (double)convertedData / 100.0f;
|
||||
return data * 1.60934;
|
||||
} else {
|
||||
uint16_t convertedData = (packet.at(13) << 8) | packet.at(12);
|
||||
uint16_t convertedData = (uint16_t)((uint8_t)packet.at(12)) + ((uint16_t)((uint8_t)packet.at(13)) << 8);
|
||||
double data = (double)convertedData / 100.0f;
|
||||
return data;
|
||||
return data / 1.60934;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -663,7 +663,30 @@ resistance_t domyosbike::resistanceFromPowerRequest(uint16_t power) {
|
||||
}
|
||||
|
||||
uint16_t domyosbike::wattsFromResistance(double resistance) {
|
||||
return ((10.39 + 1.45 * (resistance - 1.0)) * (exp(0.028 * (currentCadence().value()))));
|
||||
QSettings settings;
|
||||
if(!settings.value(QZSettings::domyos_bike_500_profile_v1, QZSettings::default_domyos_bike_500_profile_v1).toBool() || resistance < 8)
|
||||
return ((10.39 + 1.45 * (resistance - 1.0)) * (exp(0.028 * (currentCadence().value()))));
|
||||
else {
|
||||
switch((int)resistance) {
|
||||
case 8:
|
||||
return (13.6 * Cadence.value()) / 9.5488;
|
||||
case 9:
|
||||
return (15.3 * Cadence.value()) / 9.5488;
|
||||
case 10:
|
||||
return (17.3 * Cadence.value()) / 9.5488;
|
||||
case 11:
|
||||
return (19.8 * Cadence.value()) / 9.5488;
|
||||
case 12:
|
||||
return (22.5 * Cadence.value()) / 9.5488;
|
||||
case 13:
|
||||
return (25.6 * Cadence.value()) / 9.5488;
|
||||
case 14:
|
||||
return (28.4 * Cadence.value()) / 9.5488;
|
||||
case 15:
|
||||
return (35.9 * Cadence.value()) / 9.5488;
|
||||
}
|
||||
return ((10.39 + 1.45 * (resistance - 1.0)) * (exp(0.028 * (currentCadence().value()))));
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t domyosbike::watts() {
|
||||
|
||||
@@ -10,7 +10,7 @@ void elliptical::update_metrics(bool watt_calc, const double watts) {
|
||||
double deltaTime = (((double)_lastTimeUpdate.msecsTo(current)) / ((double)1000.0));
|
||||
QSettings settings;
|
||||
if (!_firstUpdate && !paused) {
|
||||
if (currentSpeed().value() > 0.0 || settings.value(QZSettings::continuous_moving, true).toBool()) {
|
||||
if (currentSpeed().value() > 0.0 || settings.value(QZSettings::continuous_moving, true).toBool()) {
|
||||
elapsed += deltaTime;
|
||||
}
|
||||
if (currentSpeed().value() > 0.0) {
|
||||
@@ -57,8 +57,17 @@ uint16_t elliptical::watts() {
|
||||
return m_watt.value();
|
||||
}
|
||||
void elliptical::changeResistance(resistance_t resistance) {
|
||||
requestResistance = resistance;
|
||||
RequestedResistance = resistance;
|
||||
lastRawRequestedResistanceValue = resistance;
|
||||
requestResistance = resistance + gears();
|
||||
RequestedResistance = resistance + gears();
|
||||
}
|
||||
int8_t elliptical::gears() { return m_gears; }
|
||||
void elliptical::setGears(int8_t gears) {
|
||||
qDebug() << "setGears" << gears;
|
||||
m_gears = gears;
|
||||
if (lastRawRequestedResistanceValue != -1) {
|
||||
changeResistance(lastRawRequestedResistanceValue);
|
||||
}
|
||||
}
|
||||
void elliptical::changeInclination(double grade, double inclination) {
|
||||
Q_UNUSED(grade);
|
||||
|
||||
@@ -28,6 +28,8 @@ class elliptical : public bluetoothdevice {
|
||||
void setPaused(bool p);
|
||||
void setLap();
|
||||
uint16_t watts();
|
||||
void setGears(int8_t d);
|
||||
int8_t gears();
|
||||
|
||||
public Q_SLOTS:
|
||||
virtual void changeSpeed(double speed);
|
||||
@@ -51,6 +53,8 @@ class elliptical : public bluetoothdevice {
|
||||
volatile double requestSpeed = -1;
|
||||
double requestInclination = -100;
|
||||
double CrankRevs = 0;
|
||||
int8_t m_gears = 0;
|
||||
resistance_t lastRawRequestedResistanceValue = -1;
|
||||
};
|
||||
|
||||
#endif // ELLIPTICAL_H
|
||||
|
||||
@@ -447,6 +447,7 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
|
||||
QObject::connect(stack, SIGNAL(volumeDown()), this, SLOT(volumeDown()));
|
||||
QObject::connect(stack, SIGNAL(keyMediaPrevious()), this, SLOT(keyMediaPrevious()));
|
||||
QObject::connect(stack, SIGNAL(keyMediaNext()), this, SLOT(keyMediaNext()));
|
||||
QObject::connect(stack, SIGNAL(floatingOpen()), this, SLOT(floatingOpen()));
|
||||
|
||||
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
|
||||
QObject::connect(engine, &QQmlApplicationEngine::quit, &QGuiApplication::quit);
|
||||
@@ -546,6 +547,14 @@ void homeform::volumeDown() {
|
||||
Minus(QStringLiteral("gears"));
|
||||
}
|
||||
|
||||
void homeform::floatingOpen() {
|
||||
#ifdef Q_OS_ANDROID
|
||||
QSettings settings;
|
||||
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/FloatingHandler", "show",
|
||||
"(Landroid/content/Context;I)V", QtAndroid::androidContext().object(), settings.value("template_inner_QZWS_port", 6666).toInt());
|
||||
#endif
|
||||
}
|
||||
|
||||
void homeform::peloton_abort_workout() {
|
||||
qDebug() << QStringLiteral("peloton_abort_workout!");
|
||||
pelotonAbortedName = pelotonAskedName;
|
||||
@@ -1967,6 +1976,11 @@ void homeform::sortTiles() {
|
||||
preset_resistance_5->setGridId(i);
|
||||
dataList.append(preset_resistance_5);
|
||||
}
|
||||
if (settings.value(QZSettings::tile_gears_enabled, false).toBool() &&
|
||||
settings.value(QZSettings::tile_gears_order, 25).toInt() == i) {
|
||||
gears->setGridId(i);
|
||||
dataList.append(gears);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2329,15 +2343,8 @@ void homeform::LargeButton(const QString &name) {
|
||||
}
|
||||
|
||||
void homeform::Plus(const QString &name) {
|
||||
|
||||
QAndroidJniObject activity = QAndroidJniObject::callStaticObjectMethod("org/qtproject/qt5/android/QtNative",
|
||||
"activity", "()Landroid/app/Activity;");
|
||||
QAndroidJniObject::callStaticMethod<void>(
|
||||
"org/cagnulen/qdomyoszwift/PiP",
|
||||
"enterPiP", "(Landroid/app/Activity;)V", activity.object<jobject>());
|
||||
|
||||
|
||||
QSettings settings;
|
||||
|
||||
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
|
||||
qDebug() << QStringLiteral("Plus") << name;
|
||||
if (name.contains(QStringLiteral("speed"))) {
|
||||
@@ -2416,6 +2423,9 @@ void homeform::Plus(const QString &name) {
|
||||
if (bluetoothManager->device()) {
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
((bike *)bluetoothManager->device())->setGears(((bike *)bluetoothManager->device())->gears() + 1);
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) {
|
||||
((elliptical *)bluetoothManager->device())
|
||||
->setGears(((elliptical *)bluetoothManager->device())->gears() + 1);
|
||||
}
|
||||
}
|
||||
} else if (name.contains(QStringLiteral("target_resistance"))) {
|
||||
@@ -2454,7 +2464,7 @@ void homeform::Plus(const QString &name) {
|
||||
((elliptical *)bluetoothManager->device())
|
||||
->changeResistance(((elliptical *)bluetoothManager->device())->currentResistance().value() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (name.contains(QStringLiteral("target_power"))) {
|
||||
if (bluetoothManager->device()) {
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
@@ -2569,6 +2579,9 @@ void homeform::Minus(const QString &name) {
|
||||
if (bluetoothManager->device()) {
|
||||
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
|
||||
((bike *)bluetoothManager->device())->setGears(((bike *)bluetoothManager->device())->gears() - 1);
|
||||
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) {
|
||||
((elliptical *)bluetoothManager->device())
|
||||
->setGears(((elliptical *)bluetoothManager->device())->gears() - 1);
|
||||
}
|
||||
}
|
||||
} else if (name.contains(QStringLiteral("target_resistance"))) {
|
||||
|
||||
@@ -668,6 +668,7 @@ class homeform : public QObject {
|
||||
void volumeUp();
|
||||
void keyMediaPrevious();
|
||||
void keyMediaNext();
|
||||
void floatingOpen();
|
||||
void deviceFound(const QString &name);
|
||||
void deviceConnected(QBluetoothDeviceInfo b);
|
||||
void ftmsAccessoryConnected(smartspin2k *d);
|
||||
|
||||
@@ -866,26 +866,30 @@ void horizontreadmill::update() {
|
||||
/*if (horizon_treadmill_7_8)*/ {
|
||||
// stop
|
||||
if (requestPause == -1) {
|
||||
messageID++;
|
||||
uint8_t write1[] = {0x55, 0xaa, 0x13, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x00};
|
||||
write1[2] = messageID & 0xff;
|
||||
write1[3] = messageID >> 8;
|
||||
|
||||
writeCharacteristic(gattCustomService, gattWriteCharCustomService, write1, sizeof(write1),
|
||||
QStringLiteral("requestStop"), false, true);
|
||||
Speed = 0; // forcing the speed to be sure, maybe I could remove this
|
||||
if(!settings.value(QZSettings::horizon_treadmill_disable_pause, QZSettings::default_horizon_treadmill_disable_pause).toBool()) {
|
||||
messageID++;
|
||||
uint8_t write1[] = {0x55, 0xaa, 0x13, 0x00, 0x01, 0x14, 0x00, 0x00, 0x00, 0x00};
|
||||
write1[2] = messageID & 0xff;
|
||||
write1[3] = messageID >> 8;
|
||||
|
||||
writeCharacteristic(gattCustomService, gattWriteCharCustomService, write1, sizeof(write1),
|
||||
QStringLiteral("requestStop"), false, true);
|
||||
}
|
||||
// pause
|
||||
} else {
|
||||
requestPause = -1;
|
||||
messageID++;
|
||||
uint8_t write1[] = {0x55, 0xaa, 0x12, 0x00, 0x03, 0x03, 0x01, 0x00, 0xf0, 0xe1, 0x00};
|
||||
write1[2] = messageID & 0xff;
|
||||
write1[3] = messageID >> 8;
|
||||
|
||||
writeCharacteristic(gattCustomService, gattWriteCharCustomService, write1, sizeof(write1),
|
||||
QStringLiteral("requestPause"), false, false);
|
||||
Speed = 0; // forcing the speed to be sure, maybe I could remove this
|
||||
horizonPaused = true;
|
||||
if(!settings.value(QZSettings::horizon_treadmill_disable_pause, QZSettings::default_horizon_treadmill_disable_pause).toBool()) {
|
||||
messageID++;
|
||||
uint8_t write1[] = {0x55, 0xaa, 0x12, 0x00, 0x03, 0x03, 0x01, 0x00, 0xf0, 0xe1, 0x00};
|
||||
write1[2] = messageID & 0xff;
|
||||
write1[3] = messageID >> 8;
|
||||
|
||||
writeCharacteristic(gattCustomService, gattWriteCharCustomService, write1, sizeof(write1),
|
||||
QStringLiteral("requestPause"), false, false);
|
||||
horizonPaused = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -31,5 +31,6 @@
|
||||
<file>icons/unlock.png</file>
|
||||
<file>icons/maps-icon-16.png</file>
|
||||
<file>icons/video.png</file>
|
||||
<file>icons/mini-display.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
BIN
src/icons/mini-display.png
Normal file
BIN
src/icons/mini-display.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 6.1 KiB |
120
src/inner_templates/floating/floating.htm
Normal file
120
src/inner_templates/floating/floating.htm
Normal file
@@ -0,0 +1,120 @@
|
||||
<!doctype html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<!-- Include the CesiumJS JavaScript and CSS files -->
|
||||
<script src="jquery-3.6.0.min.js"></script>
|
||||
<script src="globals.js"></script>
|
||||
<script src="main_ws_manager.js"></script>
|
||||
<style>
|
||||
td {
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
}
|
||||
canvas{
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: #000000; opacity: 0.8">
|
||||
<table class="metrics" style="color: #FFFFFF; font-family: sans-serif border: 0">
|
||||
<tr class="speed"><td>🏃</td><td><small>km/h</small></td><td class="speed-value"><b>0.0</b></td><td><small>AVG</small></td><td class="speed-avg">0.0</td></tr>
|
||||
<tr class="cadence"><td>🚴</td><td><small>rpm</small></td><td class="cadence-value"><b>0</b></td><td><small>AVG</small></td><td class="cadence-avg">0</td></tr>
|
||||
<tr class="heart"><td>💓</td><td><small>bpm</small></td><td class="heart-value"><b>0</b></td><td><small>AVG</small></td><td class="heart-avg">0</td></tr>
|
||||
<tr class="watt"><td>⚡</td><td><small>watt</small></td><td class="watt-value"><b>0</b></td><td><small>AVG</small></td><td class="watt-avg">0</td></tr>
|
||||
<tr class="resistance"><td>🧲</td><td><small>level</small></td><td class="resistance-value"><b>1</b></td><td><small>AVG</small></td><td class="resistance-avg">1</td></tr>
|
||||
<tr class="pelotonresistance"><td>🅿</td><td><small>level</small></td><td class="pelotonresistance-value"><b>1</b></td><td><small>AVG</small></td><td class="pelotonresistance-avg">1</td></tr>
|
||||
<tr class="calories"><td>🔥</td><td><small>kcal</small></td><td class="calories-value" colspan="3"><b>0</b></td></tr>
|
||||
<tr class="distance"><td>📏</td><td><small>km</small></td><td class="distance-value" colspan="3"><b>0.00</b></td></tr>
|
||||
<tr class="elapsed"><td>⏲️</td><td><small>time</small></td><td class="elapsed-value" colspan="3"><b>0:00:00</b></td></tr>
|
||||
</table>
|
||||
<script type="text/javascript">
|
||||
function a() {
|
||||
onWorkout = false;
|
||||
keys_arr = ['speed', 'speed_avg','cadence','cadence_avg', 'heart','heart_avg', 'calories', 'distance', 'watts','watts_avg', 'elapsed_h', 'elapsed_m', 'elapsed_s', 'resistance','resistance_avg', 'peloton_resistance','peloton_resistance_avg']
|
||||
let ell = new MainWSQueueElement(null, function(msg) {
|
||||
if (msg.msg === 'workout') {
|
||||
var speed = 0;
|
||||
var speed_avg = 0;
|
||||
var cadence = 0;
|
||||
var cadence_avg = 0;
|
||||
var hr = 0;
|
||||
var hr_avg = 0;
|
||||
var calories = 0;
|
||||
var odometer = 0;
|
||||
var watt = 0;
|
||||
var watt_avg = 0;
|
||||
var elapsed_h = 0;
|
||||
var elapsed_m = 0;
|
||||
var elapsed_s = 0;
|
||||
var resistance = 0;
|
||||
var resistance_avg = 0;
|
||||
var peloton_resistance = 0;
|
||||
var peloton_resistance_avg = 0;
|
||||
for (let key of keys_arr) {
|
||||
if (msg.content[key] === undefined)
|
||||
continue;
|
||||
if (key === 'speed') {
|
||||
speed = msg.content[key];
|
||||
} else if (key === 'speed_avg') {
|
||||
speed_avg = msg.content[key];
|
||||
} else if (key === 'cadence') {
|
||||
cadence = msg.content[key];
|
||||
} else if (key === 'cadence_avg') {
|
||||
cadence_avg = msg.content[key];
|
||||
} else if (key === 'heart') {
|
||||
hr = msg.content[key];
|
||||
} else if (key === 'heart_avg') {
|
||||
hr_avg = msg.content[key];
|
||||
} else if (key === 'calories') {
|
||||
calories = msg.content[key];
|
||||
} else if (key === 'distance') {
|
||||
odometer = msg.content[key];
|
||||
} else if (key === 'watts') {
|
||||
watt = msg.content[key];
|
||||
} else if (key === 'watts_avg') {
|
||||
watt_avg = msg.content[key];
|
||||
} else if (key === 'elapsed_h') {
|
||||
elapsed_h = msg.content[key];
|
||||
} else if (key === 'elapsed_m') {
|
||||
elapsed_m = msg.content[key];
|
||||
} else if (key === 'elapsed_s') {
|
||||
elapsed_s = msg.content[key];
|
||||
} else if (key === 'resistance') {
|
||||
resistance = msg.content[key];
|
||||
} else if (key === 'resistance_avg') {
|
||||
resistance_avg = msg.content[key];
|
||||
} else if (key === 'peloton_resistance') {
|
||||
peloton_resistance = msg.content[key];
|
||||
} else if (key === 'peloton_resistance_avg') {
|
||||
peloton_resistance_avg = msg.content[key];
|
||||
}
|
||||
}
|
||||
$('.speed-value').html("<b>" + speed.toFixed(1) + "</b>");
|
||||
$('.speed-avg').html(speed_avg.toFixed(1));
|
||||
$('.cadence-value').html("<b>" + cadence.toFixed(0) + "</b>");
|
||||
$('.cadence-avg').html(cadence_avg.toFixed(0));
|
||||
$('.heart-value').html("<b>" + hr.toFixed(0) + "</b>");
|
||||
$('.heart-avg').html(hr_avg.toFixed(0));
|
||||
$('.watt-value').html("<b>" + watt.toFixed(0) + "</b>");
|
||||
$('.watt-avg').html(watt_avg.toFixed(0));
|
||||
$('.resistance-value').html("<b>" + resistance.toFixed(0) + "</b>");
|
||||
$('.resistance-avg').html(resistance_avg.toFixed(0));
|
||||
$('.peloton_resistance-value').html("<b>" + peloton_resistance.toFixed(0) + "</b>");
|
||||
$('.peloton_resistance-avg').html(peloton_resistance_avg.toFixed(0));
|
||||
$('.distance-value').html("<b>" + odometer.toFixed(2) + "</b>");
|
||||
$('.elapsed-value').html("<b>" + elapsed_h.toString().padStart(2, "0") + ":" + elapsed_m.toString().padStart(2, "0") + ":"+ elapsed_s.toString().padStart(2, "0") + "</b>");
|
||||
$('.calories-value').html("<b>" + calories.toFixed(0) + "</b>");
|
||||
}
|
||||
return null;
|
||||
}, 15000, 3);
|
||||
ell.enqueue().then(onWorkout).catch(function(err) {
|
||||
console.error('Error is ' + err);
|
||||
});
|
||||
}
|
||||
setTimeout(a,0);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
9
src/inner_templates/floating/globals.js
Normal file
9
src/inner_templates/floating/globals.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const host_url = (!location.host || location.host.length == 0)?'192.168.25.24:7666':location.host;
|
||||
|
||||
function get_template_name() {
|
||||
let splits = location.pathname.split('/');
|
||||
if (splits.length>=2)
|
||||
return splits[splits.length - 2];
|
||||
else
|
||||
return '';
|
||||
}
|
||||
2
src/inner_templates/floating/jquery-3.6.0.min.js
vendored
Normal file
2
src/inner_templates/floating/jquery-3.6.0.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
132
src/inner_templates/floating/main_ws_manager.js
Normal file
132
src/inner_templates/floating/main_ws_manager.js
Normal file
@@ -0,0 +1,132 @@
|
||||
let main_ws = null;
|
||||
let main_ws_queue = [];
|
||||
|
||||
class MainWSQueueElement {
|
||||
constructor(msg_to_send, _inner_process, timeout, retry_num) {
|
||||
this.msg_to_send = msg_to_send;
|
||||
this.needs_to_send = msg_to_send != null;
|
||||
this.timeout = timeout || 5000;
|
||||
this.retry_num = retry_num || 1;
|
||||
this.timer = null;
|
||||
this.resolve = null;
|
||||
this.reject = null;
|
||||
this._inner_process = _inner_process;
|
||||
}
|
||||
|
||||
inner_process_msg(msg) {
|
||||
if (this._inner_process)
|
||||
return this._inner_process(msg);
|
||||
else
|
||||
return {};
|
||||
}
|
||||
|
||||
process_arrived_msg(msg) {
|
||||
let out = this.inner_process_msg(msg);
|
||||
if (out) {
|
||||
if (this.timer !==null) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
if (this.resolve)
|
||||
setTimeout(function() { this.resolve(out); }.bind(this), 0);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
enqueue() {
|
||||
main_ws_enqueue(this);
|
||||
return new Promise(function(resolve, reject) {
|
||||
this.resolve = resolve;
|
||||
this.reject = reject;
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
pop_msg_to_send() {
|
||||
if (this.needs_to_send) {
|
||||
this.needs_to_send = false;
|
||||
if (this.retry_num < 0 || this.retry_num > 0)
|
||||
this.timer = setTimeout(function() {
|
||||
this.timer = null;
|
||||
if (this.retry_num == 0) {
|
||||
main_ws_dequeue(this);
|
||||
if (this.reject) {
|
||||
this.reject(new Error('Timeout error detected'));
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.needs_to_send = true;
|
||||
main_ws_enqueue();
|
||||
}
|
||||
}.bind(this), this.timeout);
|
||||
if (this.retry_num > 0)
|
||||
this.retry_num--;
|
||||
return this.msg_to_send;
|
||||
}
|
||||
else
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function main_ws_dequeue(el) {
|
||||
let idx = main_ws_queue.indexOf(el);
|
||||
if (idx >= 0) {
|
||||
main_ws_queue.splice(idx, 1);
|
||||
}
|
||||
}
|
||||
|
||||
function main_ws_enqueue(el) {
|
||||
if (el)
|
||||
main_ws_queue.push(el);
|
||||
if (main_ws)
|
||||
main_ws_queue_process();
|
||||
}
|
||||
|
||||
function main_ws_queue_process(msg) {
|
||||
if (!main_ws)
|
||||
return;
|
||||
let jsonobj;
|
||||
for (let i = 0; i < main_ws_queue.length; i++) {
|
||||
let el = main_ws_queue[i];
|
||||
if ((jsonobj = el.pop_msg_to_send())) {
|
||||
let logString = JSON.stringify(jsonobj);
|
||||
main_ws.send(logString);
|
||||
console.log('WS >> ' + logString);
|
||||
}
|
||||
else if (msg) {
|
||||
if (el.process_arrived_msg(msg)) {
|
||||
main_ws_queue.splice(i, 1);
|
||||
i--;
|
||||
msg = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function main_ws_connect() {
|
||||
let socket = new WebSocket((location.protocol == 'https:'?'wss://' : 'ws://') + host_url + '/' + get_template_name() + '-ws');
|
||||
socket.onopen = function (event) {
|
||||
console.log('Upgrade HTTP connection OK');
|
||||
main_ws = socket;
|
||||
main_ws_queue_process();
|
||||
};
|
||||
socket.onclose = function(e) {
|
||||
main_ws = null;
|
||||
console.log('Socket is closed. Reconnect will be attempted in 30 second.', e.reason);
|
||||
setTimeout(function() {
|
||||
main_ws_connect();
|
||||
}, 5000);
|
||||
};
|
||||
|
||||
socket.onerror = function(err) {
|
||||
main_ws = null;
|
||||
console.error('Socket encountered error: ', err.message, 'Closing socket');
|
||||
socket.close();
|
||||
};
|
||||
socket.onmessage = function (event) {
|
||||
console.log(event.data);
|
||||
let msg = JSON.parse(event.data);
|
||||
main_ws_queue_process(msg);
|
||||
};
|
||||
}
|
||||
main_ws_connect();
|
||||
@@ -25,6 +25,12 @@ kingsmithr2treadmill::kingsmithr2treadmill(uint32_t pollDeviceTime, bool noConso
|
||||
if (forceInitInclination > 0) {
|
||||
lastInclination = forceInitInclination;
|
||||
}
|
||||
if (lastControlMode != UNKNOWN_CONTROL_MODE) {
|
||||
lastControlMode = UNKNOWN_CONTROL_MODE;
|
||||
}
|
||||
if (lastRunState != UNKNOWN_RUN_STATE) {
|
||||
lastRunState = UNKNOWN_RUN_STATE;
|
||||
}
|
||||
|
||||
refresh = new QTimer(this);
|
||||
initDone = false;
|
||||
@@ -120,8 +126,11 @@ void kingsmithr2treadmill::update() {
|
||||
QSettings settings;
|
||||
// ******************************************* virtual treadmill init *************************************
|
||||
if (!firstInit && !virtualTreadMill && !virtualBike) {
|
||||
bool virtual_device_enabled = settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
bool virtual_device_force_bike = settings.value(QZSettings::virtual_device_force_bike, QZSettings::default_virtual_device_force_bike).toBool();
|
||||
bool virtual_device_enabled =
|
||||
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
|
||||
bool virtual_device_force_bike =
|
||||
settings.value(QZSettings::virtual_device_force_bike, QZSettings::default_virtual_device_force_bike)
|
||||
.toBool();
|
||||
if (virtual_device_enabled) {
|
||||
if (!virtual_device_force_bike) {
|
||||
debug("creating virtual treadmill interface...");
|
||||
@@ -151,74 +160,84 @@ void kingsmithr2treadmill::update() {
|
||||
// writeCharacteristic(QStringLiteral(""), QStringLiteral("noOp"), false, true);
|
||||
}
|
||||
|
||||
// byte 3 - 4 = elapsed time
|
||||
// byte 17 = inclination
|
||||
if (requestSpeed != -1) {
|
||||
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
|
||||
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
|
||||
if (lastRunState == KINGSMITH_R2_RUN_STATE::START) {
|
||||
if (requestSpeed != -1) {
|
||||
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
|
||||
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
|
||||
|
||||
double inc = Inclination.value();
|
||||
if (requestInclination != -100) {
|
||||
double inc = Inclination.value();
|
||||
if (requestInclination != -100) {
|
||||
|
||||
// only 0.5 steps ara available
|
||||
requestInclination = qRound(requestInclination * 2.0) / 2.0;
|
||||
inc = requestInclination;
|
||||
requestInclination = -100;
|
||||
// only 0.5 steps ara available
|
||||
requestInclination = qRound(requestInclination * 2.0) / 2.0;
|
||||
inc = requestInclination;
|
||||
requestInclination = -100;
|
||||
}
|
||||
forceSpeedOrIncline(requestSpeed, inc);
|
||||
}
|
||||
forceSpeedOrIncline(requestSpeed, inc);
|
||||
requestSpeed = -1;
|
||||
}
|
||||
requestSpeed = -1;
|
||||
}
|
||||
if (requestInclination != -100) {
|
||||
if(requestInclination < 0)
|
||||
requestInclination = 0;
|
||||
// only 0.5 steps ara available
|
||||
requestInclination = qRound(requestInclination * 2.0) / 2.0;
|
||||
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
|
||||
requestInclination <= 15) {
|
||||
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
|
||||
if (requestInclination != -100) {
|
||||
if (requestInclination < 0)
|
||||
requestInclination = 0;
|
||||
// only 0.5 steps ara available
|
||||
requestInclination = qRound(requestInclination * 2.0) / 2.0;
|
||||
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
|
||||
requestInclination <= 15) {
|
||||
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
|
||||
|
||||
double speed = currentSpeed().value();
|
||||
if (requestSpeed != -1) {
|
||||
double speed = currentSpeed().value();
|
||||
if (requestSpeed != -1) {
|
||||
|
||||
speed = requestSpeed;
|
||||
requestSpeed = -1;
|
||||
speed = requestSpeed;
|
||||
requestSpeed = -1;
|
||||
}
|
||||
forceSpeedOrIncline(speed, requestInclination);
|
||||
}
|
||||
forceSpeedOrIncline(speed, requestInclination);
|
||||
requestInclination = -100;
|
||||
}
|
||||
requestInclination = -100;
|
||||
}
|
||||
if (requestStart != -1) {
|
||||
emit debug(QStringLiteral("starting..."));
|
||||
if (lastSpeed == 0.0) {
|
||||
|
||||
lastSpeed = 0.5;
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
if (lastRunState != STOP) {
|
||||
writeCharacteristic(QStringLiteral("props runState 0"), QStringLiteral("stopping"), false, true);
|
||||
}
|
||||
// don't go to standby mode automatically
|
||||
requestStop = -1;
|
||||
}
|
||||
// btinit(true);
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
if (requestStop != -1) {
|
||||
emit debug(QStringLiteral("stopping..."));
|
||||
requestStop = -1;
|
||||
}
|
||||
if (requestFanSpeed != -1) {
|
||||
emit debug(QStringLiteral("changing fan speed..."));
|
||||
if (requestFanSpeed != -1) {
|
||||
emit debug(QStringLiteral("changing fan speed..."));
|
||||
|
||||
// sendChangeFanSpeed(requestFanSpeed);
|
||||
requestFanSpeed = -1;
|
||||
}
|
||||
if (requestIncreaseFan != -1) {
|
||||
emit debug(QStringLiteral("increasing fan speed..."));
|
||||
// sendChangeFanSpeed(requestFanSpeed);
|
||||
requestFanSpeed = -1;
|
||||
}
|
||||
if (requestIncreaseFan != -1) {
|
||||
emit debug(QStringLiteral("increasing fan speed..."));
|
||||
|
||||
// sendChangeFanSpeed(FanSpeed + 1);
|
||||
requestIncreaseFan = -1;
|
||||
} else if (requestDecreaseFan != -1) {
|
||||
emit debug(QStringLiteral("decreasing fan speed..."));
|
||||
// sendChangeFanSpeed(FanSpeed + 1);
|
||||
requestIncreaseFan = -1;
|
||||
} else if (requestDecreaseFan != -1) {
|
||||
emit debug(QStringLiteral("decreasing fan speed..."));
|
||||
|
||||
// sendChangeFanSpeed(FanSpeed - 1);
|
||||
requestDecreaseFan = -1;
|
||||
}
|
||||
// sendChangeFanSpeed(FanSpeed - 1);
|
||||
requestDecreaseFan = -1;
|
||||
}
|
||||
} else if (lastRunState == KINGSMITH_R2_RUN_STATE::STOP ||
|
||||
lastRunState == KINGSMITH_R2_RUN_STATE::UNKNOWN_RUN_STATE)
|
||||
if (requestStart != -1) {
|
||||
emit debug(QStringLiteral("starting..."));
|
||||
if (lastControlMode != MANUAL) {
|
||||
writeCharacteristic(QStringLiteral("props ControlMode 1"),
|
||||
QStringLiteral("turn on treadmill to manual mode"), false, true);
|
||||
}
|
||||
if (lastRunState != START) {
|
||||
writeCharacteristic(QStringLiteral("props runState 1"), QStringLiteral("starting"), false, true);
|
||||
}
|
||||
if (lastSpeed == 0.0) {
|
||||
lastSpeed = 0.5;
|
||||
}
|
||||
requestStart = -1;
|
||||
emit tapeStarted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,11 +295,16 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
QStringList _props = data.split(QStringLiteral(" "), QString::SkipEmptyParts);
|
||||
for (int i = 1; i < _props.size(); i += 2) {
|
||||
QString key = _props.at(i);
|
||||
// Error key only can have error code
|
||||
// props Error "ErrorCode" -5000
|
||||
if (!key.compare(QStringLiteral("Error"))) {
|
||||
break;
|
||||
}
|
||||
// skip string params
|
||||
if (!key.compare(QStringLiteral("mcu_version")) || !key.compare(QStringLiteral("goal"))) {
|
||||
continue;
|
||||
}
|
||||
if(i+1 >= _props.count()) {
|
||||
if (i + 1 >= _props.count()) {
|
||||
qDebug() << "error decoding" << i;
|
||||
return;
|
||||
}
|
||||
@@ -291,6 +315,9 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
|
||||
double speed = props.value("CurrentSpeed", 0);
|
||||
Cadence = props.value("spm", 0);
|
||||
KINGSMITH_R2_CONTROL_MODE controlMode =
|
||||
(KINGSMITH_R2_CONTROL_MODE)(int)props.value("ControlMode", (double)UNKNOWN_CONTROL_MODE);
|
||||
KINGSMITH_R2_RUN_STATE runState = (KINGSMITH_R2_RUN_STATE)(int)props.value("runState", (double)UNKNOWN_RUN_STATE);
|
||||
|
||||
// TODO:
|
||||
// - RunningDistance (int; meter) : update each 10miters / 0.01 mile
|
||||
@@ -299,6 +326,9 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
// - RunningTotalTime (int; sec)
|
||||
// - spm (int) : steps per minute
|
||||
|
||||
// TODO: check 'ControlMode' and 'runState' of treadmill side
|
||||
// then update current running status of application.
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
|
||||
Heart = (uint8_t)KeepAwakeHelper::heart();
|
||||
@@ -329,7 +359,8 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
if (!firstCharacteristicChanged) {
|
||||
if (watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
|
||||
KCal +=
|
||||
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) + 1.19) *
|
||||
((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
|
||||
1.19) *
|
||||
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
|
||||
200.0) /
|
||||
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(
|
||||
@@ -364,6 +395,25 @@ void kingsmithr2treadmill::characteristicChanged(const QLowEnergyCharacteristic
|
||||
// lastInclination = incline;
|
||||
}
|
||||
|
||||
if (lastControlMode != controlMode) {
|
||||
lastControlMode = controlMode;
|
||||
if (controlMode != UNKNOWN_CONTROL_MODE) {
|
||||
emit debug(QStringLiteral("kingsmith r2 is ready"));
|
||||
initDone = true;
|
||||
}
|
||||
}
|
||||
if (lastRunState != runState) {
|
||||
lastRunState = runState;
|
||||
// TODO define bitflag enums to bluetoothdevice
|
||||
switch (runState) {
|
||||
case STOP:
|
||||
// emit deviceStateChanged(1);
|
||||
break;
|
||||
case START:
|
||||
// emit deviceStateChanged(2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
firstCharacteristicChanged = false;
|
||||
}
|
||||
|
||||
@@ -392,7 +442,7 @@ void kingsmithr2treadmill::btinit(bool startTape) {
|
||||
writeCharacteristic(QStringLiteral("servers getProp 1 2 7 12 23 24 31"), QStringLiteral("init"), false, true);
|
||||
|
||||
// TODO need reset BurnCalories & RunningDistance
|
||||
initDone = true;
|
||||
// initDone = true;
|
||||
}
|
||||
|
||||
void kingsmithr2treadmill::stateChanged(QLowEnergyService::ServiceState state) {
|
||||
|
||||
@@ -80,6 +80,11 @@ class kingsmithr2treadmill : public treadmill {
|
||||
QDateTime lastTimeCharacteristicChanged;
|
||||
bool firstCharacteristicChanged = true;
|
||||
|
||||
enum KINGSMITH_R2_CONTROL_MODE { AUTOMODE = 0, MANUAL, STANDBY, UNKNOWN_CONTROL_MODE };
|
||||
enum KINGSMITH_R2_RUN_STATE { STOP = 0, START, UNKNOWN_RUN_STATE };
|
||||
KINGSMITH_R2_CONTROL_MODE lastControlMode = UNKNOWN_CONTROL_MODE;
|
||||
KINGSMITH_R2_RUN_STATE lastRunState = UNKNOWN_RUN_STATE;
|
||||
|
||||
QTimer *refresh;
|
||||
virtualtreadmill *virtualTreadMill = nullptr;
|
||||
virtualbike *virtualBike = 0;
|
||||
|
||||
11
src/main.qml
11
src/main.qml
@@ -34,6 +34,7 @@ ApplicationWindow {
|
||||
signal volumeDown()
|
||||
signal keyMediaPrevious()
|
||||
signal keyMediaNext()
|
||||
signal floatingOpen()
|
||||
|
||||
property bool lockTiles: false
|
||||
|
||||
@@ -334,6 +335,14 @@ ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
ToolButton {
|
||||
id: toolButtonFloating
|
||||
icon.source: "icons/icons/mini-display.png"
|
||||
onClicked: { console.log("floating!"); floatingOpen(); }
|
||||
anchors.left: toolButton.right
|
||||
visible: OS_VERSION === "Android" ? true : false
|
||||
}
|
||||
|
||||
Popup {
|
||||
id: popupAutoResistance
|
||||
parent: Overlay.overlay
|
||||
@@ -655,7 +664,7 @@ ApplicationWindow {
|
||||
}
|
||||
|
||||
ItemDelegate {
|
||||
text: "version 2.12.10"
|
||||
text: "version 2.12.13"
|
||||
width: parent.width
|
||||
}
|
||||
FileDialog {
|
||||
|
||||
@@ -668,12 +668,14 @@ DISTFILES += \
|
||||
android/gradlew \
|
||||
android/gradlew.bat \
|
||||
android/libs/android_antlib_4-14-0.jar \
|
||||
android/res/layout/floating_layout.xml \
|
||||
android/res/values/libs.xml \
|
||||
android/src/Ant.java \
|
||||
android/src/ChannelService.java \
|
||||
android/src/FloatingHandler.java \
|
||||
android/src/FloatingWindowGFG.java \
|
||||
android/src/ForegroundService.java \
|
||||
android/src/NotificationClient.java \
|
||||
android/src/PiP.java \
|
||||
android/src/ScanRecordResult.java \
|
||||
android/src/NativeScanCallback.java \
|
||||
android/src/HeartChannelController.java \
|
||||
@@ -681,6 +683,7 @@ DISTFILES += \
|
||||
android/src/PowerChannelController.java \
|
||||
android/src/SpeedChannelController.java \
|
||||
android/src/com/dsi/ant/channel/PredefinedNetwork.java \
|
||||
android/gradle.properties \
|
||||
android/src/org/qtproject/qt/android/purchasing/Security.java \
|
||||
android/src/org/qtproject/qt/android/purchasing/InAppPurchase.java \
|
||||
android/src/org/qtproject/qt/android/purchasing/Base64.java \
|
||||
@@ -690,7 +693,7 @@ DISTFILES += \
|
||||
|
||||
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android
|
||||
|
||||
ANDROID_ABIS = armeabi-v7a
|
||||
ANDROID_ABIS = armeabi-v7a arm64-v8a x86 x86_64
|
||||
|
||||
ios {
|
||||
ios_icon.files = $$files($$PWD/icons/ios/*.png)
|
||||
@@ -727,4 +730,4 @@ INCLUDEPATH += purchasing/inapp
|
||||
|
||||
WINRT_MANIFEST = AppxManifest.xml
|
||||
|
||||
VERSION = 2.12.10
|
||||
VERSION = 2.12.13
|
||||
|
||||
@@ -86,5 +86,9 @@
|
||||
<file>GPXList.qml</file>
|
||||
<file>videoPlayback.qml</file>
|
||||
<file>settings-tiles.qml</file>
|
||||
<file>inner_templates/floating/floating.htm</file>
|
||||
<file>inner_templates/floating/globals.js</file>
|
||||
<file>inner_templates/floating/jquery-3.6.0.min.js</file>
|
||||
<file>inner_templates/floating/main_ws_manager.js</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@@ -557,8 +557,11 @@ const QString QZSettings::domyos_elliptical_inclination = QStringLiteral("domyos
|
||||
const QString QZSettings::gpx_loop = QStringLiteral("gpx_loop");
|
||||
const QString QZSettings::android_notification = QStringLiteral("android_notification");
|
||||
const QString QZSettings::kingsmith_encrypt_v4 = QStringLiteral("kingsmith_encrypt_v4");
|
||||
const QString QZSettings::horizon_treadmill_disable_pause = QStringLiteral("horizon_treadmill_disable_pause");
|
||||
const QString QZSettings::domyos_bike_500_profile_v1 = QStringLiteral("domyos_bike_500_profile_v1");
|
||||
const QString QZSettings::ss2k_peloton = QStringLiteral("ss2k_peloton");
|
||||
|
||||
const uint32_t allSettingsCount = 456;
|
||||
const uint32_t allSettingsCount = 459;
|
||||
QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
|
||||
{QZSettings::bluetooth_no_reconnection, QZSettings::default_bluetooth_no_reconnection},
|
||||
@@ -1018,6 +1021,9 @@ QVariant allSettings[allSettingsCount][2] = {
|
||||
{QZSettings::gpx_loop, QZSettings::default_gpx_loop},
|
||||
{QZSettings::android_notification, QZSettings::default_android_notification},
|
||||
{QZSettings::kingsmith_encrypt_v4, QZSettings::default_kingsmith_encrypt_v4},
|
||||
{QZSettings::horizon_treadmill_disable_pause, QZSettings::default_horizon_treadmill_disable_pause},
|
||||
{QZSettings::domyos_bike_500_profile_v1, QZSettings::domyos_bike_500_profile_v1},
|
||||
{QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton},
|
||||
};
|
||||
|
||||
void QZSettings::qDebugAllSettings(bool showDefaults) {
|
||||
|
||||
@@ -1589,11 +1589,20 @@ class QZSettings {
|
||||
static constexpr bool default_gpx_loop = false;
|
||||
|
||||
static const QString android_notification;
|
||||
static constexpr bool default_android_notification = true;
|
||||
static constexpr bool default_android_notification = false;
|
||||
|
||||
static const QString kingsmith_encrypt_v4;
|
||||
static constexpr bool default_kingsmith_encrypt_v4 = false;
|
||||
|
||||
static const QString horizon_treadmill_disable_pause;
|
||||
static constexpr bool default_horizon_treadmill_disable_pause = false;
|
||||
|
||||
static const QString domyos_bike_500_profile_v1;
|
||||
static constexpr bool default_domyos_bike_500_profile_v1 = false;
|
||||
|
||||
static const QString ss2k_peloton;
|
||||
static constexpr bool default_ss2k_peloton = false;
|
||||
|
||||
/**
|
||||
* @brief Write the QSettings values using the constants from this namespace.
|
||||
* @param showDefaults Optionally indicates if the default should be shown with the key.
|
||||
|
||||
@@ -590,10 +590,19 @@ import Qt.labs.settings 1.0
|
||||
property bool gpx_loop: false
|
||||
|
||||
// from version 2.12.6
|
||||
property bool android_notification: true
|
||||
property bool android_notification: false
|
||||
|
||||
// from version 2.12.8
|
||||
property bool kingsmith_encrypt_v4: false
|
||||
|
||||
// from versiomn 2.12.11
|
||||
property bool horizon_treadmill_disable_pause: false
|
||||
|
||||
// from version 2.12.13
|
||||
property bool domyos_bike_500_profile_v1: false
|
||||
|
||||
// from version 2.12.14
|
||||
property bool ss2k_peloton: false
|
||||
}
|
||||
|
||||
function paddingZeros(text, limit) {
|
||||
@@ -2016,6 +2025,20 @@ import Qt.labs.settings 1.0
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.domyos_bike_display_calories = checked
|
||||
}
|
||||
SwitchDelegate {
|
||||
id: domyosBike500ProfileV1Delegate
|
||||
text: qsTr("Bike 500 wattage profile")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.domyos_bike_500_profile_v1
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.domyos_bike_500_profile_v1 = checked
|
||||
}
|
||||
}
|
||||
AccordionElement {
|
||||
id: proformBikeAccordion
|
||||
@@ -3786,6 +3809,21 @@ import Qt.labs.settings 1.0
|
||||
onClicked: settings.horizon_treadmill_7_8 = checked
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
id: horizonTreadmillDisablePauseDelegate
|
||||
text: qsTr("Disable Pause")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.horizon_treadmill_disable_pause
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.horizon_treadmill_disable_pause = checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
spacing: 10
|
||||
Label {
|
||||
@@ -4834,6 +4872,21 @@ import Qt.labs.settings 1.0
|
||||
text: "Refresh Devices List"
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
onClicked: refresh_bluetooth_devices_clicked();
|
||||
}
|
||||
|
||||
SwitchDelegate {
|
||||
id: ss2kPelotonDelegate
|
||||
text: qsTr("Peloton Bike")
|
||||
spacing: 0
|
||||
bottomPadding: 0
|
||||
topPadding: 0
|
||||
rightPadding: 0
|
||||
leftPadding: 0
|
||||
clip: false
|
||||
checked: settings.ss2k_peloton
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
Layout.fillWidth: true
|
||||
onClicked: settings.ss2k_peloton = checked
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
|
||||
@@ -647,7 +647,8 @@ void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
<< QStringLiteral("resistance sent to ifit") << resistance;
|
||||
|
||||
double odometer = Bike->odometer() * 1000;
|
||||
double calories = Bike->calories().value();
|
||||
// ifit applies a constant multiplier to the kcal sent from bluetooth
|
||||
double calories = Bike->calories().value() * 1.488;
|
||||
if (resistance > 0x26)
|
||||
resistance = 0x26;
|
||||
qint64 t = (QDateTime::currentSecsSinceEpoch() - iFit_timer);
|
||||
@@ -668,8 +669,10 @@ void virtualbike::characteristicChanged(const QLowEnergyCharacteristic &characte
|
||||
reply3[15] = t & 0xff;
|
||||
reply3[16] = t >> 8;
|
||||
reply4[3] = ((uint16_t)calories); // KCal
|
||||
reply4[4] = (((uint16_t)calories) >> 8); // KCal
|
||||
reply4[10] = ((uint16_t)calories); // KCal estimated
|
||||
reply3[19] = 0xEE - (reply3[15] * 3) - (reply4[10] * 2) - (reply2[18]) - (reply2[11]) - (reply2[12]) -
|
||||
reply4[11] = (((uint16_t)calories) >> 8); // KCal
|
||||
reply3[19] = 0xEE - (reply3[15] * 3) - (reply4[10] * 2) - (reply4[4] * 2) - (reply2[18]) - (reply2[11]) - (reply2[12]) -
|
||||
(reply2[13]) - (reply3[13]) - (reply3[14]) - (reply2[14]) - (reply3[7]) - (reply3[12]) -
|
||||
(reply3[16]) - (reply2[15]) - (reply2[16]);
|
||||
reply4[19] = reply3[19];
|
||||
|
||||
Reference in New Issue
Block a user