new example: 'wear-os-gps' (meant for use on e.g. a canoe)

This commit is contained in:
pls.153 2022-07-03 14:15:07 +02:00
parent 33d7ed3cce
commit ab22dd809f
50 changed files with 920 additions and 3 deletions

View file

@ -0,0 +1,10 @@
(defsystem :app
:serial t
:depends-on ()
:components ((:file "lisp/package")
(:file "lisp/ui-vars")
(:file "lisp/kalman")
(:file "lisp/distance")
(:file "lisp/speed")
(:file "lisp/qt")
(:file "lisp/main")))

View file

@ -0,0 +1,84 @@
CONFIG += 32bit
LISP_FILES = $$files(lisp/*) app.asd make.lisp
android {
32bit {
ECL = $$(ECL_ANDROID_32)
} else {
ECL = $$(ECL_ANDROID)
}
lisp.commands = $$ECL/../ecl-android-host/bin/ecl \
-norc -shell $$PWD/make.lisp
} else:unix {
lisp.commands = /usr/local/bin/ecl -shell $$PWD/make.lisp
} else:win32 {
lisp.commands = ecl.exe -shell $$PWD/make.lisp
}
lisp.input = LISP_FILES
win32: lisp.output = tmp/app.lib
!win32: lisp.output = tmp/libapp.a
QMAKE_EXTRA_COMPILERS += lisp
win32: PRE_TARGETDEPS = tmp/app.lib
!win32: PRE_TARGETDEPS = tmp/libapp.a
QT += quick qml sensors positioning
TEMPLATE = app
CONFIG += c++17 no_keywords release
DEFINES = DESKTOP_APP INI_LISP #INI_ECL_CONTRIB
INCLUDEPATH = /usr/local/include
LIBS = -L/usr/local/lib -lecl
DESTDIR = .
TARGET = app
OBJECTS_DIR = tmp
MOC_DIR = tmp
linux: LIBS += -L../../../platforms/linux/lib
macx: LIBS += -L../../../platforms/macos/lib
win32: LIBS += -L../../../platforms/windows/lib
win32 {
include(../../src/windows.pri)
}
android {
QT += androidextras
#DEFINES += INI_ASDF
DEFINES -= DESKTOP_APP
DEFINES += QT_EXTENSION
INCLUDEPATH = $$ECL/include
LIBS = -L$$ECL/lib -lecl
LIBS += -L../../../platforms/android/lib
HEADERS += cpp/qt.h
SOURCES += cpp/qt.cpp
ANDROID_EXTRA_LIBS += $$ECL/lib/libecl.so
ANDROID_PACKAGE_SOURCE_DIR = ../platforms/android
32bit {
ANDROID_ABIS = "armeabi-v7a"
} else {
ANDROID_ABIS = "arm64-v8a"
}
}
32bit {
LIBS += -llqml32 -llisp32
} else {
LIBS += -llqml -llisp
}
LIBS += -Ltmp -lapp
HEADERS += ../../src/cpp/main.h
SOURCES += ../../src/cpp/main.cpp
RESOURCES += $$files(qml/*)
RESOURCES += $$files(i18n/*.qm)
lupdate_only {
SOURCES += i18n/tr.h
}

View file

@ -0,0 +1,3 @@
# install/update (keeps app data)
adb install -r android-build/*.apk
adb shell am start -n org.qtproject.example.gps/org.qtproject.qt5.android.bindings.QtActivity # Qt5

View file

@ -0,0 +1,25 @@
#include "qt.h"
#include <QtAndroid>
#include <QAndroidJniEnvironment>
QT_BEGIN_NAMESPACE
QVariant QT::keepScreenOn() {
QtAndroid::runOnAndroidThread([] {
QAndroidJniObject activity = QtAndroid::androidActivity();
if (activity.isValid()) {
QAndroidJniObject window = activity.callObjectMethod("getWindow", "()Landroid/view/Window;");
if (window.isValid()) {
const int FLAG_KEEP_SCREEN_ON = 128;
window.callMethod<void>("addFlags", "(I)V", FLAG_KEEP_SCREEN_ON);
}
}
QAndroidJniEnvironment env;
if (env->ExceptionCheck()) {
env->ExceptionClear();
}
});
return QVariant();
}
QT_END_NAMESPACE

View file

@ -0,0 +1,17 @@
#pragma once
#include <QObject>
#include <QVariant>
QT_BEGIN_NAMESPACE
extern "C" { QObject* ini(); }
class QT : public QObject {
Q_OBJECT
public:
Q_INVOKABLE QVariant keepScreenOn();
};
QT_END_NAMESPACE

View file

@ -0,0 +1,89 @@
;;; usage: ecl -shell kml.lisp
(in-package :cl-user)
(defun file-contents (file)
(with-open-file (s file)
(let ((str (make-string (file-length s))))
(read-sequence str s)
str)))
(defvar *kml-1* (file-contents "template-1.kml"))
(defvar *kml-2* (file-contents "template-2.kml"))
(defvar *kml-3* (file-contents "template-3.kml"))
(defvar *kml-4* (file-contents "template-4.kml"))
(defvar *kml-5* (file-contents "template-5.kml"))
(defun cc (&rest strings)
(apply 'concatenate 'string strings))
(defun split (str &optional (sep #\,))
(unless (zerop (length str))
(let (list)
(do ((e (position sep str) (position sep str :start (1+ e)))
(b 0 (1+ e)))
((not e) (push (subseq str b) list))
(push (subseq str b e) list))
(nreverse list))))
(defun generate-kml (file)
(let* ((name (cc "logs/" file "."))
(csv (cc name "csv"))
(kml (cc name "kml"))
(kmz (cc name "kmz")))
(unless (probe-file kmz)
(format t "~&generating ~S~%" kmz)
(let* ((point "point.png")
(header "<h2>~A</h2>~%")
(row "<tr><td class=s~D>~A</td><td>~A</td></tr>~%")
(lines (with-open-file (s csv)
(loop :for line = (split (read-line s nil nil))
:while line :collect line)))
(i-timestamp 0)
(i-accuracy 3)
(i-lat 4)
(i-lon 5)
(i-direction 7)
(i-speed 8)
(i-distance 9)
(*print-pretty* nil))
(dolist (file (list kml kmz))
(when (probe-file file)
(delete-file file)))
(with-open-file (s kml :direction :output :if-exists :supersede)
(write-string *kml-1* s)
(dolist (line lines)
(format s "~A,~A,0 "
(nth i-lon line)
(nth i-lat line)))
(write-string *kml-2* s)
(let ((c 0))
(dolist (line lines)
(when (zerop (mod (incf c) 60)) ; a marker every minute
(write-string *kml-3* s)
(format s header (nth i-timestamp line))
(write-line "<table>" s)
;; table of log values
(format s row 2
(nth i-distance line)
"m")
(format s row 2
(nth i-speed line)
"km/h")
(format s row 1
(nth i-accuracy line)
"acc")
(format s row 1
(nth i-direction line)
"°")
;; position
(format s *kml-4*
(nth i-lon line)
(nth i-lat line)))))
(write-string *kml-5* s))
(ext:run-program "zip" (list kmz kml point))
(delete-file kml)))))
(dolist (file (directory "logs/*.csv"))
(generate-kml (pathname-name file)))

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<kml xmlns="http://www.opengis.net/kml/2.2" xmlns:gx="http://www.google.com/kml/ext/2.2" xmlns:kml="http://www.opengis.net/kml/2.2" xmlns:atom="http://www.w3.org/2005/Atom">
<Document>
<name>LQML Example GPS.kml</name>
<Style id="point">
<IconStyle>
<scale>1.0</scale>
<Icon>
<href>point.png</href>
</Icon>
<hotSpot x="100" y="75" xunits="pixels" yunits="pixels"/>
</IconStyle>
<LineStyle>
<color>ff0000ff</color>
<width>6</width>
</LineStyle>
</Style>
<Style id="point_hl">
<IconStyle>
<scale>1.8</scale>
<Icon>
<href>point.png</href>
</Icon>
<hotSpot x="100" y="75" xunits="pixels" yunits="pixels"/>
</IconStyle>
<LineStyle>
<color>ff0000ff</color>
<width>6</width>
</LineStyle>
</Style>
<StyleMap id="marker">
<Pair>
<key>normal</key>
<styleUrl>#point</styleUrl>
</Pair>
<Pair>
<key>highlight</key>
<styleUrl>#point_hl</styleUrl>
</Pair>
</StyleMap>
<Placemark>
<name></name>
<styleUrl>#marker</styleUrl>
<LineString>
<tessellate>0</tessellate>
<altitudemode>relativeToGround</altitudemode>
<coordinates>

View file

@ -0,0 +1,4 @@
</coordinates>
</LineString>
</Placemark>

View file

@ -0,0 +1,11 @@
<Placemark>
<styleUrl>#marker</styleUrl>
<description><![CDATA[
<style>
.s1 { font-size: 16px; text-align: right; font-weight: bold; }
.s2 { font-size: 22px; text-align: right; font-weight: bold; }
td { font-size: 10px; }
table { border-collapse: collapse; margin: auto; width: 90%; }
table, th, td { border: 1px solid #d0d0d0; }
h2 { text-align: center; color: blue; }
</style>

View file

@ -0,0 +1,6 @@
</table>
]]></description>
<Point>
<coordinates>~A,~A,0</coordinates>
</Point>
</Placemark>

View file

@ -0,0 +1,2 @@
</Document>
</kml>

View file

@ -0,0 +1,69 @@
(in-package :gps)
(defconstant +earth-mean-radius+ 6371.0072d0)
(defun to-rad (deg)
(/ (* deg pi) 180))
(defun to-deg (rad)
(/ (* 180 rad) pi))
(defun point-distance (from to)
"Coordiante distance according to Haversine formula."
(let ((from-x (car from))
(from-y (cdr from))
(to-x (car to))
(to-y (cdr to)))
(if (and (numberp from-x)
(numberp to-x))
(let* ((dlat (to-rad (- to-x from-x)))
(dlon (to-rad (- to-y from-y)))
(h-dlat (sin (/ dlat 2)))
(h-dlon (sin (/ dlon 2))))
(setf h-dlat (expt h-dlat 2)
h-dlon (expt h-dlon 2))
(let* ((y (+ h-dlat (* (cos (to-rad from-x))
(cos (to-rad to-x))
h-dlon)))
(x (* 2 (asin (sqrt y)))))
(* x +earth-mean-radius+ 1000)))
0)))
(let ((positions-distance 0)
(distance 0)
position-1
position-2
constant-direction)
(defun update-distance ()
"Calculate distance of farest points with same direction
(fallback: re-calculate after a certain distance)."
(when (and *direction*
(not constant-direction))
(setf constant-direction *direction*))
(let ((pos (cons kal:*lat* kal:*lon*))
(reset (if constant-direction
(or (>= (abs (- *direction* constant-direction))
15) ; not too small (not accurate with low speed)
(>= positions-distance 75)) ; 75 m
(>= positions-distance 25)))) ; 25 m
(if reset
(progn
(setf constant-direction *direction*)
(if position-1
(progn
(setf position-2 pos)
(incf distance (positions-distance))
(setf positions-distance 0)
(shiftf position-1 position-2 nil))
(setf position-1 pos)))
(progn
(if position-1
(setf position-2 pos)
(setf position-1 pos))
(setf positions-distance (positions-distance))))))
(defun positions-distance ()
(if position-2
(point-distance position-1 position-2)
0))
(defun distance ()
(+ distance positions-distance)))

View file

@ -0,0 +1,37 @@
(defpackage :kalman
(:use :cl)
(:nicknames :kal)
(:export
#:*lat*
#:*lon*
#:*timestamp*
#:*variance*
#:filter
#:reset))
(in-package :kal)
(defvar *lat* nil)
(defvar *lon* nil)
(defvar *timestamp* nil)
(defvar *variance* nil)
(defun reset ()
(setf *variance* nil))
(defun filter (lat lon accuracy speed)
(when (and lat lon accuracy speed)
(setf accuracy (max 1 accuracy))
(let ((timestamp (get-internal-real-time)))
(if *variance*
(let ((time-inc (- timestamp *timestamp*)))
(incf *variance* (/ (* time-inc (expt speed 2)) 1000))
(setf *timestamp* timestamp)
(let ((k (/ *variance* (+ *variance* (expt accuracy 2)))))
(incf *lat* (* k (- lat *lat*)))
(incf *lon* (* k (- lon *lon*)))
(setf *variance* (* (- 1 k) *variance*))))
(setf *lat* lat
*lon* lon
*timestamp* timestamp
*variance* (expt accuracy 2))))))

View file

@ -0,0 +1,59 @@
(in-package :gps)
(defvar *start-time* nil)
(defvar *direction* nil)
(defvar *log-stream* nil)
(defun run ()
(qt:ini)
#+android
(progn
(qt:keep-screen-on qt:*cpp*)
(ensure-permissions :access-fine-location)))
(defun closing ()
(close *log-stream*)
(qquit))
(defun round* (x &optional (digits 0))
(when (numberp x)
(let* ((f (expt 10 digits))
(r (/ (truncate (+ 0.5 (* f x)))
f)))
(if (zerop digits)
r
(float r)))))
(defun str (x)
(cond ((stringp x)
x)
((null x)
"")
(t
(princ-to-string x))))
(defun position-changed (lat lon accuracy speed direction timestamp)
(unless *start-time*
(setf *start-time* timestamp)
(setf *log-stream* (open (format nil "LOG ~A.csv" (substitute #\. #\: timestamp))
:direction :output :if-does-not-exist :create)))
(when (and lat lon accuracy)
(when direction
(setf *direction* direction))
(kal:filter lat lon accuracy speed)
(update-distance)
(update-speed)
(q> |value| ui:*speed* (speed*))
(q> |text| ui:*distance* (str (round* (distance))))
(q> |text| ui:*accuracy* (str (round* accuracy 1)))
(when kal:*lat*
(format *log-stream* "~A,~F,~F,~F,~F,~F,~A,~A,~A,~A~%"
timestamp
lat lon (round* accuracy 1)
kal:*lat* kal:*lon*
(str (round* speed 1))
(str (round* direction))
(round* (speed*) 1)
(round* (distance))))))
(qlater 'run)

View file

@ -0,0 +1,3 @@
(defpackage :gps
(:use :cl :qml)
(:export))

View file

@ -0,0 +1,17 @@
(defpackage :qt
(:use :cl :qml)
(:export
#:*cpp*
#:ini
#:keep-screen-on))
(in-package :qt)
(defvar *cpp* nil)
(defun ini ()
#+android
(progn
(setf *cpp* (qfind-child nil "QT"))
(let ((*package* (find-package :qt)))
(define-qt-wrappers *cpp*))))

View file

@ -0,0 +1,20 @@
(in-package :gps)
(let ((speed 0.0)
samples)
(defun update-speed ()
"After 10 distance samples, calculate average speed and update on every new
distance sample."
(push (cons (distance)
(get-internal-real-time))
samples)
(when (> (length samples) 10)
(setf samples (butlast samples))
(let ((b (first samples))
(a (first (last samples))))
(setf speed (* 3.6 ; km/h
(/ (- (car b) (car a)) ; m
(/ (- (cdr b) (cdr a)) ; s
1000.0)))))))
(defun speed* ()
speed))

View file

@ -0,0 +1,12 @@
(defpackage ui
(:use :cl :qml)
(:export
#:*accuracy*
#:*distance*
#:*speed*))
(in-package :ui)
(defparameter *accuracy* "accuracy")
(defparameter *distance* "distance")
(defparameter *speed* "speed")

View file

@ -0,0 +1,60 @@
;; check target
(let ((arg (first (ext:command-args))))
(mapc (lambda (name feature)
(when (search name arg)
(pushnew feature *features*)))
(list "/ecl-android/" "/ecl-ios/")
(list :android :ios)))
#+(or android ios)
(pushnew :mobile *features*)
(require :asdf)
(push (merge-pathnames "../")
asdf:*central-registry*)
(setf *default-pathname-defaults*
(truename (merge-pathnames "../../../"))) ; LQML root
(defvar *current*
(let ((name (namestring *load-truename*)))
(subseq name
(length (namestring *default-pathname-defaults*))
(1+ (position #\/ name :from-end t)))))
(dolist (file (list "package" "x" "ecl-ext" "ini" "qml")) ; load LQML symbols
(load (merge-pathnames file "src/lisp/")))
(defun cc (&rest args)
(apply 'concatenate 'string args))
#-mobile
(asdf:make-build "app"
:monolithic t
:type :static-library
:move-here (cc *current* "build/tmp/")
:init-name "ini_app")
#+mobile
(progn
(pushnew :interpreter *features*)
(defvar *asdf-system* "app")
(defvar *ql-libs* (cc *current* "ql-libs.lisp"))
(defvar *init-name* "ini_app")
(defvar *library-path* (format nil "~Abuild-~A/tmp/"
*current*
#+android "android"
#+ios "ios"))
(defvar *require* (list :ecl-curl))
(load "platforms/shared/make"))
;; rename lib
(let* ((from #-mobile (cc *current* "build/tmp/app--all-systems.a")
#+mobile (cc *library-path* "app--all-systems.a"))
(to "libapp.a")
(to* #-mobile (cc *current* "build/tmp/" to)
#+mobile (cc *library-path* to)))
(when (probe-file to*)
(delete-file to*))
(rename-file from to))

View file

@ -0,0 +1,78 @@
<?xml version="1.0"?>
<manifest package="org.qtproject.example.gps" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" 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. -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<!-- The following comment will be replaced upon deployment with default features based on the dependencies of the application.
Remove the comment if you do not require these default features. -->
<uses-feature android:name="android.hardware.location.gps" android:required="false"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="GPS" android:extractNativeLibs="true">
<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="GPS" android:screenOrientation="unspecified" android:launchMode="singleTop" android:theme="@android:style/Theme.DeviceDefault">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<!-- Application arguments -->
<!-- meta-data android:name="android.app.arguments" android:value="arg1 arg2 arg3"/ -->
<!-- Application arguments -->
<meta-data android:name="android.app.lib_name" android:value="app"/>
<meta-data android:name="android.app.qt_sources_resource_id" android:resource="@array/qt_sources"/>
<meta-data android:name="android.app.repository" android:value="default"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_libs_resource_id" android:resource="@array/bundled_libs"/>
<!-- Deploy Qt libs as part of package -->
<meta-data android:name="android.app.bundle_local_qt_libs" android:value="1"/>
<!-- Run with local libs -->
<meta-data android:name="android.app.use_local_qt_libs" android:value="1"/>
<meta-data android:name="android.app.libs_prefix" android:value="/data/local/tmp/qt/"/>
<meta-data android:name="android.app.load_local_libs_resource_id" android:resource="@array/load_local_libs"/>
<meta-data android:name="android.app.load_local_jars" android:value="jar/QtAndroid.jar:jar/QtAndroidExtras.jar:jar/QtAndroidBearer.jar:jar/QtPositioning.jar"/>
<meta-data android:name="android.app.static_init_classes" android:value="org.qtproject.qt5.android.positioning.QtPositioning"/>
<!-- Used to specify custom system library path to run with local system libs -->
<!-- <meta-data android:name="android.app.system_libs_prefix" android:value="/system/lib/"/> -->
<!-- Messages maps -->
<meta-data android:value="@string/ministro_not_found_msg" android:name="android.app.ministro_not_found_msg"/>
<meta-data android:value="@string/ministro_needed_msg" android:name="android.app.ministro_needed_msg"/>
<meta-data android:value="@string/fatal_error_msg" android:name="android.app.fatal_error_msg"/>
<meta-data android:value="@string/unsupported_android_version" android:name="android.app.unsupported_android_version"/>
<!-- Messages maps -->
<!-- Splash screen -->
<!-- Orientation-specific (portrait/landscape) data is checked first. If not available for current orientation,
then android.app.splash_screen_drawable. For best results, use together with splash_screen_sticky and
use hideSplashScreen() with a fade-out animation from Qt Android Extras to hide the splash screen when you
are done populating your window with content. -->
<!-- meta-data android:name="android.app.splash_screen_drawable_portrait" android:resource="@drawable/logo_portrait" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable_landscape" android:resource="@drawable/logo_landscape" / -->
<!-- meta-data android:name="android.app.splash_screen_drawable" android:resource="@drawable/logo"/ -->
<!-- meta-data android:name="android.app.splash_screen_sticky" android:value="true"/ -->
<!-- Splash screen -->
<!-- Background running -->
<!-- Warning: changing this value to true may cause unexpected crashes if the
application still try to draw after
"applicationStateChanged(Qt::ApplicationSuspended)"
signal is sent! -->
<meta-data android:name="android.app.background_running" android:value="false"/>
<!-- Background running -->
<!-- auto screen scale factor -->
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
<!-- auto screen scale factor -->
<!-- extract android style -->
<!-- available android:values :
* default - In most cases this will be the same as "full", but it can also be something else if needed, e.g., for compatibility reasons
* full - useful QWidget & Quick Controls 1 apps
* minimal - useful for Quick Controls 2 apps, it is much faster than "full"
* 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 -->
</activity>
<!-- For adding service(s) please check: https://wiki.qt.io/AndroidServices -->
</application>
</manifest>

View file

@ -0,0 +1,92 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Controls.Styles 1.4
import QtQuick.Extras 1.4
Rectangle {
anchors.fill: parent
color: "black"
CircularGauge {
id: gauge
objectName: "speed"
anchors.fill: parent
maximumValue: 10
stepSize: 0.1
Behavior on value {
NumberAnimation {
duration: 1000
easing.type: Easing.InOutCubic
}
}
style: CircularGaugeStyle {
id: style
labelStepSize: 1
tickmarkStepSize: 1
minorTickmarkCount: 5
minimumValueAngle: -90
maximumValueAngle: 90
property int limit: parent.maximumValue * 8/10
property string baseColor: "white"
property string limitColor: "orange"
function toRad(deg) { return deg * (Math.PI / 180) }
background: Canvas {
onPaint: {
var ctx = getContext("2d")
ctx.reset()
ctx.beginPath()
ctx.strokeStyle = limitColor
ctx.lineWidth = outerRadius * 0.02
ctx.arc(outerRadius, outerRadius, outerRadius - ctx.lineWidth / 2,
toRad(valueToAngle(limit) - 90), toRad(valueToAngle(gauge.maximumValue) - 90))
ctx.stroke()
}
}
tickmark: Rectangle {
implicitWidth: outerRadius * 0.02
antialiasing: true
implicitHeight: outerRadius * 0.06
color: styleData.value >= limit ? limitColor : baseColor
}
minorTickmark: Rectangle {
visible: styleData.value < limit
implicitWidth: outerRadius * 0.01
antialiasing: true
implicitHeight: outerRadius * 0.03
color: baseColor
}
tickmarkLabel: Text {
font.pixelSize: Math.max(6, outerRadius * 0.15)
font.bold: true
text: styleData.value
color: styleData.value >= limit ? limitColor : baseColor
antialiasing: true
}
needle: Rectangle {
y: outerRadius * 0.15
implicitWidth: outerRadius * 0.07
implicitHeight: outerRadius * 0.85
radius: implicitWidth / 2
antialiasing: true
color: baseColor
}
foreground: Rectangle {
width: outerRadius * 0.15
height: width
radius: width / 2
color: baseColor
anchors.centerIn: parent
}
}
}
}

View file

@ -0,0 +1,7 @@
import QtQuick 2.15
Text {
anchors.horizontalCenter: parent.horizontalCenter
color: "white"
font.bold: true
}

View file

@ -0,0 +1,48 @@
import QtQuick 2.15
import QtQuick.Controls 2.15
import QtSensors 5.15
import QtPositioning 5.15
import "ext/" as Ext
Rectangle {
width: 210 // for desktop
height: 210
color: "black"
PositionSource {
objectName: "position_source"
updateInterval: 1000
active: true
onPositionChanged: {
Lisp.call("gps:position-changed",
position.latitudeValid ? position.coordinate.latitude : null,
position.longitudeValid ? position.coordinate.longitude : null,
position.horizontalAccuracyValid ? position.horizontalAccuracy : null,
position.speedValid ? position.speed : null,
position.directionValid ? position.direction : null,
position.timestamp.toLocaleString(Qt.locale(), "yyyy-MM-dd hh:mm:ss"))
}
}
Ext.Gauge {}
Column {
anchors.horizontalCenter: parent.horizontalCenter
anchors.bottom: parent.bottom
anchors.bottomMargin: 5
spacing: -5
Ext.LogText { objectName: "distance"; font.pixelSize: 48 }
Ext.LogText { objectName: "accuracy"; font.pixelSize: 24 }
}
// quit
Keys.onPressed: {
if (event.key === Qt.Key_Back) {
event.accepted = true
Lisp.call("gps:closing")
}
}
}

View file

@ -42,7 +42,6 @@ Log note: for showing only your own messages, see `log.sh`.
Important note (Qt bug)
-----------------------
After editing [AndroidManifest.xml](platforms/android/AndroidManifest.xml)
in **Qt Creator** you need to add this inside `activity`:
```

View file

@ -0,0 +1,59 @@
Prepare
-------
Please copy the app template files first:
```
$ cd ..
$ ./copy.sh wear-os-gps
```
Info
----
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
need to adapt the maximum speed value (km/h) to your personal needs.
The data is automatically logged, and can be accessed with e.g.
**Device File Explorer** from Android Studio (see Help / Find Action...).
The path of the log files is:
```
/data/data/org.qtproject.example.gps/files/
```
You can then put those log files in directory [kml/logs/](kml/logs/) and run
```
cd kml
ecl -shell kml.lisp
```
This will generate **kmz** files of all log files, which can then be viewed
using **Google Earth** (the free desktop app).
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
latest 10 seconds). Additionally it shows the whole distance and the GPS
accuracy in meters.
Run (desktop demo)
------------------
```
lqml run.lisp
```
Optionally pass `-slime` to start a Swank server, and connect from Emacs with
`M-x slime-connect`.
During development you can pass `-auto`, which will releoad all QML files after
you made a change to any of them and saved it. For re-initialization after
reloading, file `lisp/qml-reload/on-reloaded` will be loaded.
Closing the window quits the app. If you try to kill it with `ctrl-c`, you need
an additional `ctrl-d` to exit from ECL. To quit from Slime, do `(qq)` which is
short for `(qquit)`.

2
examples/wear-os-heart/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
*
!.gitignore

View file

@ -0,0 +1,5 @@
# IP of your watch / run and confirm on the watch
adb kill-server
adb connect 192.168.1.?:5555

View file

@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.qtproject.example.app" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<manifest package="org.qtproject.example.heart" xmlns:android="http://schemas.android.com/apk/res/android" android:versionName="1.0" android:versionCode="1" android:installLocation="auto">
<uses-sdk android:minSdkVersion="25" android:targetSdkVersion="30"/>
<uses-feature android:name="android.hardware.type.watch"/>
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>

View file

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Before After
Before After

View file

@ -0,0 +1,52 @@
Note
----
This will only work with Qt 5.15 (customized AndroidManifest.xml file) and
requires WearOS 2 or later.
You'll also need an easy to apply hack for the **heart rate sensor**, see
[qt-sensor-hack](qt-sensor-hack/).
It is assumed that you already enabled the developer settings on your watch.
Build 32bit android APK
-----------------------
First you need to connect your device through WiFi. Please set your watch IP
address in [build-android/connect.sh](build-android/connect.sh) and run:
```
$ ./build-android/connect.sh
```
You probably want to build a **32bit** version, because most watches are 32bit.
For this you first need to build a 32bit version of the LQML library, see
[readme-prepare-android.md](../../readme-prepare-android.md).
Now you can build the app:
```
$ cd build-android
$ qmake-android "CONFIG+=32bit" ..
$ make apk
$ ./install-run.sh
```
Log note: for showing only your own messages, see `log.sh`.
Important note (Qt bug)
-----------------------
After editing [AndroidManifest.xml](platforms/android/AndroidManifest.xml)
in **Qt Creator** you need to add this inside `activity`:
```
android:theme="@android:style/Theme.DeviceDefault"
```
If you omit the above setting, the app will not start and just show an error
message instead.

View file

@ -5,7 +5,7 @@ Prepare
Please copy the app template files first:
```
$ cd ..
$ ./copy.sh wear-os
$ ./copy.sh wear-os-heart
```

BIN
screenshots/wear-os-gps.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

View file

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 18 KiB

Before After
Before After