mirror of
https://gitlab.com/eql/lqml.git
synced 2025-12-06 02:30:38 -08:00
new example: 'wear-os-gps' (meant for use on e.g. a canoe)
This commit is contained in:
parent
33d7ed3cce
commit
ab22dd809f
50 changed files with 920 additions and 3 deletions
10
examples/wear-os-gps/app.asd
Normal file
10
examples/wear-os-gps/app.asd
Normal 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")))
|
||||
84
examples/wear-os-gps/app.pro
Normal file
84
examples/wear-os-gps/app.pro
Normal 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
|
||||
}
|
||||
3
examples/wear-os-gps/build-android/install-run.sh
Executable file
3
examples/wear-os-gps/build-android/install-run.sh
Executable 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
|
||||
25
examples/wear-os-gps/cpp/qt.cpp
Normal file
25
examples/wear-os-gps/cpp/qt.cpp
Normal 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
|
||||
17
examples/wear-os-gps/cpp/qt.h
Normal file
17
examples/wear-os-gps/cpp/qt.h
Normal 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
|
||||
89
examples/wear-os-gps/kml/kml.lisp
Normal file
89
examples/wear-os-gps/kml/kml.lisp
Normal 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)))
|
||||
|
||||
BIN
examples/wear-os-gps/kml/point.png
Normal file
BIN
examples/wear-os-gps/kml/point.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.9 KiB |
47
examples/wear-os-gps/kml/template-1.kml
Normal file
47
examples/wear-os-gps/kml/template-1.kml
Normal 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>
|
||||
4
examples/wear-os-gps/kml/template-2.kml
Normal file
4
examples/wear-os-gps/kml/template-2.kml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
</coordinates>
|
||||
</LineString>
|
||||
</Placemark>
|
||||
11
examples/wear-os-gps/kml/template-3.kml
Normal file
11
examples/wear-os-gps/kml/template-3.kml
Normal 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>
|
||||
6
examples/wear-os-gps/kml/template-4.kml
Normal file
6
examples/wear-os-gps/kml/template-4.kml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
</table>
|
||||
]]></description>
|
||||
<Point>
|
||||
<coordinates>~A,~A,0</coordinates>
|
||||
</Point>
|
||||
</Placemark>
|
||||
2
examples/wear-os-gps/kml/template-5.kml
Normal file
2
examples/wear-os-gps/kml/template-5.kml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
</Document>
|
||||
</kml>
|
||||
69
examples/wear-os-gps/lisp/distance.lisp
Normal file
69
examples/wear-os-gps/lisp/distance.lisp
Normal 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)))
|
||||
37
examples/wear-os-gps/lisp/kalman.lisp
Normal file
37
examples/wear-os-gps/lisp/kalman.lisp
Normal 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))))))
|
||||
59
examples/wear-os-gps/lisp/main.lisp
Normal file
59
examples/wear-os-gps/lisp/main.lisp
Normal 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)
|
||||
3
examples/wear-os-gps/lisp/package.lisp
Normal file
3
examples/wear-os-gps/lisp/package.lisp
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
(defpackage :gps
|
||||
(:use :cl :qml)
|
||||
(:export))
|
||||
17
examples/wear-os-gps/lisp/qt.lisp
Normal file
17
examples/wear-os-gps/lisp/qt.lisp
Normal 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*))))
|
||||
20
examples/wear-os-gps/lisp/speed.lisp
Normal file
20
examples/wear-os-gps/lisp/speed.lisp
Normal 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))
|
||||
12
examples/wear-os-gps/lisp/ui-vars.lisp
Normal file
12
examples/wear-os-gps/lisp/ui-vars.lisp
Normal 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")
|
||||
60
examples/wear-os-gps/make.lisp
Normal file
60
examples/wear-os-gps/make.lisp
Normal 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))
|
||||
78
examples/wear-os-gps/platforms/android/AndroidManifest.xml
Normal file
78
examples/wear-os-gps/platforms/android/AndroidManifest.xml
Normal 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>
|
||||
92
examples/wear-os-gps/qml/ext/Gauge.qml
Normal file
92
examples/wear-os-gps/qml/ext/Gauge.qml
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
7
examples/wear-os-gps/qml/ext/LogText.qml
Normal file
7
examples/wear-os-gps/qml/ext/LogText.qml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import QtQuick 2.15
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
color: "white"
|
||||
font.bold: true
|
||||
}
|
||||
48
examples/wear-os-gps/qml/main.qml
Normal file
48
examples/wear-os-gps/qml/main.qml
Normal 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")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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`:
|
||||
```
|
||||
59
examples/wear-os-gps/readme.md
Normal file
59
examples/wear-os-gps/readme.md
Normal 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
2
examples/wear-os-heart/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
||||
5
examples/wear-os-heart/build-android/connect.sh
Executable file
5
examples/wear-os-heart/build-android/connect.sh
Executable file
|
|
@ -0,0 +1,5 @@
|
|||
# IP of your watch / run and confirm on the watch
|
||||
|
||||
adb kill-server
|
||||
adb connect 192.168.1.?:5555
|
||||
|
||||
|
|
@ -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"/>
|
||||
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB |
52
examples/wear-os-heart/readme-build.md
Normal file
52
examples/wear-os-heart/readme-build.md
Normal 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.
|
||||
|
||||
|
|
@ -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
BIN
screenshots/wear-os-gps.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
|
Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB |
Loading…
Add table
Add a link
Reference in a new issue