mirror of
https://gitlab.com/eql/lqml.git
synced 2025-12-06 02:30:38 -08:00
add settings to example 'wear-os-gps'
This commit is contained in:
parent
de50d90245
commit
f1a7d0093c
13 changed files with 174 additions and 36 deletions
|
|
@ -20,6 +20,10 @@ and adding to resources included in the executable).
|
||||||
So, you only need to **manually** care about the usual ASDF project files in
|
So, you only need to **manually** care about the usual ASDF project files in
|
||||||
`app.asd`.
|
`app.asd`.
|
||||||
|
|
||||||
|
But -- *of course* -- you still need to run the respective **qmake** command
|
||||||
|
every time you add new files to the project, because the automation is all
|
||||||
|
defined in `app.pro`.
|
||||||
|
|
||||||
|
|
||||||
Run desktop
|
Run desktop
|
||||||
-----------
|
-----------
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,15 @@
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
QVariant QT::keepScreenOn() {
|
QVariant QT::keepScreenOn(const QVariant& on) {
|
||||||
QtAndroid::runOnAndroidThread([] {
|
QtAndroid::runOnAndroidThread([&] {
|
||||||
QAndroidJniObject activity = QtAndroid::androidActivity();
|
QAndroidJniObject activity = QtAndroid::androidActivity();
|
||||||
if (activity.isValid()) {
|
if (activity.isValid()) {
|
||||||
QAndroidJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
QAndroidJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
|
||||||
if (window.isValid()) {
|
if (window.isValid()) {
|
||||||
const int FLAG_KEEP_SCREEN_ON = 128;
|
const int FLAG_KEEP_SCREEN_ON = 128;
|
||||||
window.callMethod<void>("addFlags", "(I)V", FLAG_KEEP_SCREEN_ON);
|
const char* method = on.toBool() ? "addFlags" : "clearFlags";
|
||||||
|
window.callMethod<void>(method, "(I)V", FLAG_KEEP_SCREEN_ON);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
QAndroidJniEnvironment env;
|
QAndroidJniEnvironment env;
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ class QT : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Q_INVOKABLE QVariant keepScreenOn();
|
Q_INVOKABLE QVariant keepScreenOn(const QVariant& = true);
|
||||||
};
|
};
|
||||||
|
|
||||||
QT_END_NAMESPACE
|
QT_END_NAMESPACE
|
||||||
|
|
|
||||||
|
|
@ -8,8 +8,9 @@
|
||||||
(qt:ini)
|
(qt:ini)
|
||||||
#+android
|
#+android
|
||||||
(progn
|
(progn
|
||||||
(qt:keep-screen-on qt:*cpp*)
|
(qlater (lambda () (qt:keep-screen-on qt:*cpp*))) ; delay until UI is loaded (see 'Settings.qml')
|
||||||
(ensure-permissions :access-fine-location)))
|
(ensure-permissions :access-fine-location))
|
||||||
|
(q> |ready| ui:*position-source* t))
|
||||||
|
|
||||||
(defun closing ()
|
(defun closing ()
|
||||||
(close *log-stream*)
|
(close *log-stream*)
|
||||||
|
|
@ -64,4 +65,12 @@
|
||||||
(round* (speed*) 1)
|
(round* (speed*) 1)
|
||||||
(round* (distance))))))
|
(round* (distance))))))
|
||||||
|
|
||||||
|
(defun always-on-changed (on) ; called from QML
|
||||||
|
#+android
|
||||||
|
(qt:keep-screen-on qt:*cpp* on))
|
||||||
|
|
||||||
|
(defun set-max-speed () ; called from QML
|
||||||
|
(q> |maximumValue| ui:*speed*
|
||||||
|
(q< |value| ui:*max-speed*)))
|
||||||
|
|
||||||
(qlater 'run)
|
(qlater 'run)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,16 @@
|
||||||
(in-package :gps)
|
(in-package :gps)
|
||||||
|
|
||||||
|
(defvar *distance-samples* 5)
|
||||||
|
|
||||||
(let ((speed 0.0)
|
(let ((speed 0.0)
|
||||||
samples)
|
samples)
|
||||||
(defun update-speed ()
|
(defun update-speed ()
|
||||||
"After 10 distance samples, calculate average speed and update on every new
|
"After *DISTANCE-SAMPLES* samples, calculate average speed and update on
|
||||||
distance sample."
|
every new distance sample."
|
||||||
(push (cons (distance)
|
(push (cons (distance)
|
||||||
(get-internal-real-time))
|
(get-internal-real-time))
|
||||||
samples)
|
samples)
|
||||||
(when (> (length samples) 10)
|
(when (> (length samples) *distance-samples*)
|
||||||
(setf samples (butlast samples))
|
(setf samples (butlast samples))
|
||||||
(let ((b (first samples))
|
(let ((b (first samples))
|
||||||
(a (first (last samples))))
|
(a (first (last samples))))
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,14 @@
|
||||||
(:export
|
(:export
|
||||||
#:*accuracy*
|
#:*accuracy*
|
||||||
#:*distance*
|
#:*distance*
|
||||||
|
#:*max-speed*
|
||||||
|
#:*position-source*
|
||||||
#:*speed*))
|
#:*speed*))
|
||||||
|
|
||||||
(in-package :ui)
|
(in-package :ui)
|
||||||
|
|
||||||
(defparameter *accuracy* "accuracy")
|
(defparameter *accuracy* "accuracy")
|
||||||
(defparameter *distance* "distance")
|
(defparameter *distance* "distance")
|
||||||
(defparameter *speed* "speed")
|
(defparameter *max-speed* "max_speed")
|
||||||
|
(defparameter *position-source* "position_source")
|
||||||
|
(defparameter *speed* "speed")
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,11 @@ Rectangle {
|
||||||
color: "black"
|
color: "black"
|
||||||
|
|
||||||
CircularGauge {
|
CircularGauge {
|
||||||
id: gauge
|
id: speed
|
||||||
objectName: "speed"
|
objectName: "speed"
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
maximumValue: 10
|
maximumValue: 10
|
||||||
stepSize: 0.1
|
stepSize: 0.05
|
||||||
|
|
||||||
Behavior on value {
|
Behavior on value {
|
||||||
NumberAnimation {
|
NumberAnimation {
|
||||||
|
|
@ -23,8 +23,8 @@ Rectangle {
|
||||||
|
|
||||||
style: CircularGaugeStyle {
|
style: CircularGaugeStyle {
|
||||||
id: style
|
id: style
|
||||||
labelStepSize: 1
|
labelStepSize: parent.maximumValue / 10
|
||||||
tickmarkStepSize: 1
|
tickmarkStepSize: labelStepSize
|
||||||
minorTickmarkCount: 5
|
minorTickmarkCount: 5
|
||||||
minimumValueAngle: -90
|
minimumValueAngle: -90
|
||||||
maximumValueAngle: 90
|
maximumValueAngle: 90
|
||||||
|
|
@ -43,7 +43,7 @@ Rectangle {
|
||||||
ctx.strokeStyle = limitColor
|
ctx.strokeStyle = limitColor
|
||||||
ctx.lineWidth = outerRadius * 0.02
|
ctx.lineWidth = outerRadius * 0.02
|
||||||
ctx.arc(outerRadius, outerRadius, outerRadius - ctx.lineWidth / 2,
|
ctx.arc(outerRadius, outerRadius, outerRadius - ctx.lineWidth / 2,
|
||||||
toRad(valueToAngle(limit) - 90), toRad(valueToAngle(gauge.maximumValue) - 90))
|
toRad(valueToAngle(limit) - 90), toRad(valueToAngle(speed.maximumValue) - 90))
|
||||||
ctx.stroke()
|
ctx.stroke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
19
examples/wear-os-gps/qml/ext/MainView.qml
Normal file
19
examples/wear-os-gps/qml/ext/MainView.qml
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
import "." as Ext
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
color: "black"
|
||||||
|
|
||||||
|
Ext.Gauge {}
|
||||||
|
|
||||||
|
Column {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.bottomMargin: 10
|
||||||
|
spacing: -5
|
||||||
|
|
||||||
|
Ext.LogText { objectName: "distance"; font.pixelSize: 48 }
|
||||||
|
Ext.LogText { objectName: "accuracy"; font.pixelSize: 24 }
|
||||||
|
}
|
||||||
|
}
|
||||||
46
examples/wear-os-gps/qml/ext/Settings.qml
Normal file
46
examples/wear-os-gps/qml/ext/Settings.qml
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
Column {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
text: "max speed"
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
}
|
||||||
|
|
||||||
|
Tumbler {
|
||||||
|
id: maxSpeed
|
||||||
|
objectName: "max_speed"
|
||||||
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
|
height: 80
|
||||||
|
model: [10, 20, 30, 40, 50]
|
||||||
|
visibleItemCount: 3
|
||||||
|
|
||||||
|
property int value: model[0]
|
||||||
|
|
||||||
|
delegate: Text {
|
||||||
|
text: modelData
|
||||||
|
font.pixelSize: 22
|
||||||
|
font.bold: true
|
||||||
|
opacity: 0.4 + Math.max(0, 1 - Math.abs(Tumbler.displacement)) * 0.6
|
||||||
|
Component.onCompleted: { maxSpeed.width = paintedWidth }
|
||||||
|
}
|
||||||
|
|
||||||
|
onCurrentIndexChanged: { value = model[currentIndex] }
|
||||||
|
}
|
||||||
|
|
||||||
|
Switch {
|
||||||
|
objectName: "always_on"
|
||||||
|
text: "always on"
|
||||||
|
font.pixelSize: 16
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
checked: true
|
||||||
|
|
||||||
|
onCheckedChanged: Lisp.call("gps:always-on-changed", checked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -14,27 +14,51 @@ Rectangle {
|
||||||
updateInterval: 1000
|
updateInterval: 1000
|
||||||
active: true
|
active: true
|
||||||
|
|
||||||
|
property bool ready: false
|
||||||
|
|
||||||
onPositionChanged: {
|
onPositionChanged: {
|
||||||
Lisp.call("gps:position-changed",
|
if (ready) {
|
||||||
position.latitudeValid ? position.coordinate.latitude : null,
|
Lisp.call("gps:position-changed",
|
||||||
position.longitudeValid ? position.coordinate.longitude : null,
|
position.latitudeValid ? position.coordinate.latitude : null,
|
||||||
position.horizontalAccuracyValid ? position.horizontalAccuracy : null,
|
position.longitudeValid ? position.coordinate.longitude : null,
|
||||||
position.speedValid ? position.speed : null,
|
position.horizontalAccuracyValid ? position.horizontalAccuracy : null,
|
||||||
position.directionValid ? position.direction : null,
|
position.speedValid ? position.speed : null,
|
||||||
position.timestamp.toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss"))
|
position.directionValid ? position.direction : null,
|
||||||
|
position.timestamp.toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss"))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ext.Gauge {}
|
SwipeView {
|
||||||
|
id: view
|
||||||
|
anchors.fill: parent
|
||||||
|
orientation: Qt.Vertical
|
||||||
|
|
||||||
Column {
|
onCurrentIndexChanged: {
|
||||||
|
if (currentIndex === 0) {
|
||||||
|
Lisp.call("gps:set-max-speed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ext.MainView {}
|
||||||
|
Ext.Settings {}
|
||||||
|
}
|
||||||
|
|
||||||
|
PageIndicator {
|
||||||
|
id: indicator
|
||||||
|
anchors.bottom: view.bottom
|
||||||
anchors.horizontalCenter: parent.horizontalCenter
|
anchors.horizontalCenter: parent.horizontalCenter
|
||||||
anchors.bottom: parent.bottom
|
height: 14
|
||||||
anchors.bottomMargin: 5
|
count: view.count
|
||||||
spacing: -5
|
currentIndex: view.currentIndex
|
||||||
|
|
||||||
Ext.LogText { objectName: "distance"; font.pixelSize: 48 }
|
delegate: Rectangle {
|
||||||
Ext.LogText { objectName: "accuracy"; font.pixelSize: 24 }
|
implicitWidth: 6
|
||||||
|
implicitHeight: 6
|
||||||
|
radius: width / 2
|
||||||
|
color: "white"
|
||||||
|
opacity: index === indicator.currentIndex ? 1 : 0.35
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// quit
|
// quit
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,18 @@ requires WearOS 2 or later.
|
||||||
|
|
||||||
It is assumed that you already enabled the developer settings on your watch.
|
It is assumed that you already enabled the developer settings on your watch.
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
Every Lisp file under `lisp/` and every qml/image/font/whatever file under
|
||||||
|
`qml/` is added **automatically** to your Qt project file (both for re-compile
|
||||||
|
and adding to resources included in the executable).
|
||||||
|
|
||||||
|
So, you only need to **manually** care about the usual ASDF project files in
|
||||||
|
`app.asd`.
|
||||||
|
|
||||||
|
But -- *of course* -- you still need to run the respective **qmake** command
|
||||||
|
every time you add new files to the project, because the automation is all
|
||||||
|
defined in `app.pro`.
|
||||||
|
|
||||||
|
|
||||||
Build 32bit android APK
|
Build 32bit android APK
|
||||||
|
|
@ -27,7 +39,7 @@ Now you can build the app:
|
||||||
```
|
```
|
||||||
$ cd build-android
|
$ cd build-android
|
||||||
|
|
||||||
$ qmake-android "CONFIG+=32bit" ..
|
$ qmake-android .. # "CONFIG+=32bit" can be omitted here: already defined in app.pro
|
||||||
$ make apk
|
$ make apk
|
||||||
|
|
||||||
$ ./install-run.sh
|
$ ./install-run.sh
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,12 @@ Info
|
||||||
|
|
||||||
This is a practical example of displaying both the speed and the whole distance
|
This is a practical example of displaying both the speed and the whole distance
|
||||||
of e.g. a canoe session (only meant for constant altitude values). You probably
|
of e.g. a canoe session (only meant for constant altitude values). You probably
|
||||||
need to adapt the maximum speed value (km/h) to your personal needs.
|
need to adapt the maximum speed value (km/h) to your personal needs, see
|
||||||
|
settings (swipe up).
|
||||||
|
|
||||||
|
An important feature is keeping the display always on (implemented with the Qt
|
||||||
|
JNI interface). But this also consumes more battery, so you can switch it off
|
||||||
|
in the settings.
|
||||||
|
|
||||||
The data is automatically logged, and can be accessed with e.g.
|
The data is automatically logged, and can be accessed with e.g.
|
||||||
**Device File Explorer** from Android Studio (see Help / Find Action...).
|
**Device File Explorer** from Android Studio (see Help / Find Action...).
|
||||||
|
|
@ -35,8 +40,8 @@ using **Google Earth** (the free desktop app).
|
||||||
|
|
||||||
A simple **Kalman** filter is used for the necessary GPS data smoothing.
|
A simple **Kalman** filter is used for the necessary GPS data smoothing.
|
||||||
|
|
||||||
The UI uses a `CircularGauge` for displaying the speed (an average of the
|
The UI uses a `CircularGauge` for displaying the speed (by default, an average
|
||||||
latest 10 seconds). Additionally it shows the whole distance and the GPS
|
of the latest 5 seconds). Additionally it shows the whole distance and the GPS
|
||||||
accuracy in meters.
|
accuracy in meters.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,18 @@ You'll also need an easy to apply hack for the **heart rate sensor**, see
|
||||||
|
|
||||||
It is assumed that you already enabled the developer settings on your watch.
|
It is assumed that you already enabled the developer settings on your watch.
|
||||||
|
|
||||||
|
--
|
||||||
|
|
||||||
|
Every Lisp file under `lisp/` and every qml/image/font/whatever file under
|
||||||
|
`qml/` is added **automatically** to your Qt project file (both for re-compile
|
||||||
|
and adding to resources included in the executable).
|
||||||
|
|
||||||
|
So, you only need to **manually** care about the usual ASDF project files in
|
||||||
|
`app.asd`.
|
||||||
|
|
||||||
|
But -- *of course* -- you still need to run the respective **qmake** command
|
||||||
|
every time you add new files to the project, because the automation is all
|
||||||
|
defined in `app.pro`.
|
||||||
|
|
||||||
|
|
||||||
Build 32bit android APK
|
Build 32bit android APK
|
||||||
|
|
@ -30,7 +42,7 @@ Now you can build the app:
|
||||||
```
|
```
|
||||||
$ cd build-android
|
$ cd build-android
|
||||||
|
|
||||||
$ qmake-android "CONFIG+=32bit" ..
|
$ qmake-android .. # "CONFIG+=32bit" can be omitted here: already defined in app.pro
|
||||||
$ make apk
|
$ make apk
|
||||||
|
|
||||||
$ ./install-run.sh
|
$ ./install-run.sh
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue