From 671e7f91c801d188167a27fa4e2130503b3a3388 Mon Sep 17 00:00:00 2001 From: "pls.153" Date: Fri, 4 Aug 2023 14:46:41 +0200 Subject: [PATCH] example 'meshtastic': add map with (known) positions of all radios --- examples/meshtastic/cpp/qt.cpp | 11 +++ examples/meshtastic/cpp/qt.h | 1 + examples/meshtastic/cpp/qt.pro | 1 + examples/meshtastic/cpp/tile_server.h | 62 +++++++++++++++++ examples/meshtastic/lisp/location.lisp | 44 +++++++++++- examples/meshtastic/lisp/main.lisp | 3 +- examples/meshtastic/lisp/messages.lisp | 5 +- examples/meshtastic/lisp/package.lisp | 4 ++ examples/meshtastic/lisp/qt.lisp | 1 + examples/meshtastic/lisp/ui-vars.lisp | 8 +++ examples/meshtastic/qml/ext/Group.qml | 6 ++ examples/meshtastic/qml/ext/MainView.qml | 2 +- examples/meshtastic/qml/ext/Map.qml | 85 +++++++++++++++++++++++ examples/meshtastic/qml/img/location.png | Bin 6091 -> 6023 bytes examples/meshtastic/qml/img/marker.png | Bin 0 -> 1173 bytes examples/meshtastic/qml/main.qml | 17 ++++- examples/meshtastic/readme-usage.md | 6 ++ 17 files changed, 248 insertions(+), 8 deletions(-) create mode 100644 examples/meshtastic/cpp/tile_server.h create mode 100644 examples/meshtastic/qml/ext/Map.qml create mode 100644 examples/meshtastic/qml/img/marker.png diff --git a/examples/meshtastic/cpp/qt.cpp b/examples/meshtastic/cpp/qt.cpp index be02749..3db56a7 100644 --- a/examples/meshtastic/cpp/qt.cpp +++ b/examples/meshtastic/cpp/qt.cpp @@ -1,5 +1,6 @@ #include "qt.h" #include "ble_meshtastic.h" +#include "tile_server.h" #include #include #include @@ -170,4 +171,14 @@ QVariant QT::localIp() { return QVariant(); } +QVariant QT::startTileServer() { + static bool start = true; + int port = 1702; + if (start) { + start = false; + new TileServer(port); + } + return port; +} + QT_END_NAMESPACE diff --git a/examples/meshtastic/cpp/qt.h b/examples/meshtastic/cpp/qt.h index ccc1165..68098b6 100644 --- a/examples/meshtastic/cpp/qt.h +++ b/examples/meshtastic/cpp/qt.h @@ -35,6 +35,7 @@ public: // etc Q_INVOKABLE QVariant localIp(); + Q_INVOKABLE QVariant startTileServer(); QT(); diff --git a/examples/meshtastic/cpp/qt.pro b/examples/meshtastic/cpp/qt.pro index 1351c34..b160cc2 100644 --- a/examples/meshtastic/cpp/qt.pro +++ b/examples/meshtastic/cpp/qt.pro @@ -12,6 +12,7 @@ MOC_DIR = ./tmp/ HEADERS += \ ble.h \ ble_meshtastic.h \ + tile_server.h \ qt.h SOURCES += \ diff --git a/examples/meshtastic/cpp/tile_server.h b/examples/meshtastic/cpp/tile_server.h new file mode 100644 index 0000000..622b4e4 --- /dev/null +++ b/examples/meshtastic/cpp/tile_server.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + +// trivial local tile web-server which doesn't need an API key + +class TileServer : public QTcpServer { + Q_OBJECT + +public: + int port; + + TileServer(int p = 0, QObject* parent = nullptr) : QTcpServer(parent) { + listen(QHostAddress::Any, p); + port = serverPort(); + } + + void incomingConnection(qintptr socket) override { + QTcpSocket* s = new QTcpSocket(this); + connect(s, &QTcpSocket::readyRead, this, &TileServer::readClient); + connect(s, &QTcpSocket::disconnected, this, &TileServer::discardClient); + s->setSocketDescriptor(socket); + } + +public Q_SLOTS: + void readClient() { + QString xml = QStringLiteral( + "{\"UrlTemplate\" : \"https://tile.openstreetmap.org/%z/%x/%y.png\"," + " \"ImageFormat\" : \"png\"," + " \"QImageFormat\" : \"Indexed8\"," + " \"ID\" : \"wmf-intl-1x\"," + " \"MaximumZoomLevel\" : 19," + " \"MapCopyRight\" : \"OpenStreetMap\"," + " \"DataCopyRight\" : \"\"}"); + + QTcpSocket* socket = static_cast(sender()); + if (socket->canReadLine()) { + QString line = socket->readLine(); + QStringList tokens = line.split(QRegExp("[ \r\n][ \r\n]*")); + if (tokens.at(0) == "GET") { + QTextStream s(socket); + s.setCodec("UTF-8"); + s << QStringLiteral("HTTP/1.0 200 Ok\r\n" + "Content-Type: text/html; charset=\"utf-8\"\r\n\r\n") + << xml + << QStringLiteral("\r\n"); + socket->close(); + + if (socket->state() == QTcpSocket::UnconnectedState) { + delete socket; + } + } + } + } + + void discardClient() { + QTcpSocket* socket = static_cast(sender()); + socket->deleteLater(); + } +}; + diff --git a/examples/meshtastic/lisp/location.lisp b/examples/meshtastic/lisp/location.lisp index 55b4739..ccb63a0 100644 --- a/examples/meshtastic/lisp/location.lisp +++ b/examples/meshtastic/lisp/location.lisp @@ -1,6 +1,6 @@ (in-package :loc) -(defvar *positions* nil) ; will be shown on an offline map (TODO) +(defvar *positions* nil) (defvar *my-position* nil) (defun ini () @@ -42,6 +42,13 @@ (lora:send-position *my-position*)) (qsingle-shot 1000 'send-to-radio))) +(defun position* (node) + (when (stringp node) + (setf node (parse-integer node))) ; for JS + (x:when-it (getf *positions* node) + (list (getf x:it :lat) + (getf x:it :lon)))) + (defun set-position (node pos) (let ((lat (getf pos :lat))) (when (and node lat (not (zerop lat))) @@ -56,7 +63,7 @@ (defun distance (from to) ;; Haversine formula - (destructuring-bind ((lat-1 . lon-1) (lat-2 . lon-2)) + (destructuring-bind ((lat-1 lon-1) (lat-2 lon-2)) (list from to) (if (and (numberp lat-1) (numberp lat-2)) @@ -73,3 +80,36 @@ (floor (+ 0.5 (* x +earth-mean-radius+ 1000))))) 0))) +(defun tile-path () + (namestring (merge-pathnames "data/tiles/"))) + +(defun activate-map () + (unless (q< |active| ui:*map-loader*) + (qt:start-tile-server qt:*cpp*) + (q> |active| ui:*map-loader* t) + #+mobile + (destructuring-bind (lat lon time) + (last-gps-position) + (unless (zerop lat) + (qjs |setCenter| ui:*map* lat lon))) + #-mobile + (let ((my-pos (position* (lora:my-num)))) + (when my-pos + (qjs |setCenter| ui:*map* my-pos))))) + +(defun show-map-clicked () ; see QML + (let ((show (not (q< |visible| ui:*map-view*)))) + (when show + (activate-map) + (qjs |updatePositions| ui:*map* + (find-quick-item ui:*group*))) + (q> |visible| ui:*map-view* show) + ;; move map (not page) when swiping to left + (q> |interactive| ui:*main-view* (not show)) + (unless show + (q> |active| ui:*map-loader* nil))) + (values)) + +(defun position-count () + (length *positions*)) + diff --git a/examples/meshtastic/lisp/main.lisp b/examples/meshtastic/lisp/main.lisp index 1e72241..9d5a386 100644 --- a/examples/meshtastic/lisp/main.lisp +++ b/examples/meshtastic/lisp/main.lisp @@ -22,7 +22,8 @@ (when (and (= 1 index) (not (app:setting :latest-receiver))) (q> |currentIndex| ui:*main-view* 0)) - (q> |visible| ui:*find* (= 1 index)) + (q> |visible| ui:*location* (= 0 index)) + (q> |visible| ui:*find* (= 1 index)) (values)) (defun icon-press-and-hold (name) ; see QML diff --git a/examples/meshtastic/lisp/messages.lisp b/examples/meshtastic/lisp/messages.lisp index 6270d44..3c77b21 100644 --- a/examples/meshtastic/lisp/messages.lisp +++ b/examples/meshtastic/lisp/messages.lisp @@ -112,7 +112,7 @@ another mobile node is moving to different places (using GPS of phone), sending an ':e ...' text message, which will be echoed with info about signal strength, position and distance." - (let ((from-pos (getf loc:*positions* from)) + (let ((from-pos (loc:position* from)) (my-pos #+mobile (loc:last-gps-position) #-mobile nil)) (format nil "~A~%~%snr: ~F rssi: ~D~%lat: ~,5F lon: ~,5F~%distance: ~:D m" @@ -120,7 +120,6 @@ (if my-pos (first my-pos) "-") (if my-pos (second my-pos) "-") (if (and from-pos my-pos) - (loc:distance (cons (first my-pos) (second my-pos)) - (cons (getf from-pos :lat) (getf from-pos :lon))) + (loc:distance my-pos from-pos) "-")))) diff --git a/examples/meshtastic/lisp/package.lisp b/examples/meshtastic/lisp/package.lisp index 4999aad..b837cbc 100644 --- a/examples/meshtastic/lisp/package.lisp +++ b/examples/meshtastic/lisp/package.lisp @@ -99,9 +99,13 @@ (:export #:*my-position* #:*positions* + #:activate-map #:distance #:ini #:last-gps-position + #:position* + #:position-count #:set-position + #:tile-path #:update-my-position)) diff --git a/examples/meshtastic/lisp/qt.lisp b/examples/meshtastic/lisp/qt.lisp index e8171ab..3026a56 100644 --- a/examples/meshtastic/lisp/qt.lisp +++ b/examples/meshtastic/lisp/qt.lisp @@ -8,6 +8,7 @@ #:last-position #:local-ip #:start-device-discovery + #:start-tile-server #:read* #:short-names #:sql-query diff --git a/examples/meshtastic/lisp/ui-vars.lisp b/examples/meshtastic/lisp/ui-vars.lisp index 7c7594f..1099f78 100644 --- a/examples/meshtastic/lisp/ui-vars.lisp +++ b/examples/meshtastic/lisp/ui-vars.lisp @@ -11,7 +11,11 @@ #:*find* #:*find-text* #:*loading* + #:*location* #:*main-view* + #:*map* + #:*map-loader* + #:*map-view* #:*messages* #:*message-view* #:*modem* @@ -32,6 +36,10 @@ (defparameter *find* "find") (defparameter *find-text* "find_text") (defparameter *loading* "loading") +(defparameter *location* "location") +(defparameter *map* "map") +(defparameter *map-loader* "map_loader") +(defparameter *map-view* "map_view") (defparameter *main-view* "main_view") (defparameter *messages* "messages") (defparameter *message-view* "message_view") diff --git a/examples/meshtastic/qml/ext/Group.qml b/examples/meshtastic/qml/ext/Group.qml index 6b29175..5c581ce 100644 --- a/examples/meshtastic/qml/ext/Group.qml +++ b/examples/meshtastic/qml/ext/Group.qml @@ -221,5 +221,11 @@ Rectangle { } } } + + Ext.Map { + objectName: "map_view" + anchors.fill: rect + visible: false + } } diff --git a/examples/meshtastic/qml/ext/MainView.qml b/examples/meshtastic/qml/ext/MainView.qml index b91476d..88b2b6d 100644 --- a/examples/meshtastic/qml/ext/MainView.qml +++ b/examples/meshtastic/qml/ext/MainView.qml @@ -54,7 +54,7 @@ Item { currentIndex: 1 interactive: false - Ext.Group {} + Ext.Group { id: group } Ext.Messages {} Ext.Radios {} diff --git a/examples/meshtastic/qml/ext/Map.qml b/examples/meshtastic/qml/ext/Map.qml new file mode 100644 index 0000000..4f9d333 --- /dev/null +++ b/examples/meshtastic/qml/ext/Map.qml @@ -0,0 +1,85 @@ +import QtQuick 2.15 +import QtLocation 5.15 +import QtPositioning 5.15 +import "." as Ext + +Item { + anchors.fill: parent + + Component { + id: mapComponent + + Map { + id: map + objectName: "map" + anchors.fill: parent + plugin: mapPlugin + zoomLevel: 14 + + function coordinate(pos) { + return QtPositioning.coordinate(pos[0], pos[1]) + } + + function setCenter(pos) { + center = coordinate(pos) + } + + function updatePositions(group) { + for (var i = 0; i < group.count; i++) { + var data = group.get(i) + var pos = Lisp.call("loc:position*", data.nodeNum) + if (pos) { + var marker = markers.itemAt(i) + marker.radioName = data.radioName + marker.customName = data.customName + marker.coordinate = coordinate(pos) + marker.visible = true + } + } + } + + Plugin { + id: mapPlugin + name: "osm" // Open Street Map + + // for downloading tiles + PluginParameter { + name: "osm.mapping.cache.directory" + value: Lisp.call("loc:tile-path") + } + // for offline tiles (from cache) + PluginParameter { + name: "osm.mapping.offline.directory" + value: Lisp.call("loc:tile-path") + } + // number tiles (instead of MB) + PluginParameter { + name: "osm.mapping.cache.disk.cost_strategy" + value: "unitary" + } + // max number cached/offline tiles + PluginParameter { + name: "osm.mapping.cache.disk.size" + value: 10000 + } + // local tile web-server (no API key needed), see 'cpp/tile_server.h' + PluginParameter { + name: "osm.mapping.providersrepository.address" + value: "http://127.0.0.1:1702/" + } + } + + Ext.Markers { + id: markers + } + } + } + + Loader { + id: mapLoader + objectName: "map_loader" + anchors.fill: parent + sourceComponent: mapComponent + active: false + } +} diff --git a/examples/meshtastic/qml/img/location.png b/examples/meshtastic/qml/img/location.png index 3d65ee3eb53c0f29f208f0eaad3f0f140e7e758a..2e12795d307d13cef15a3dc53aef7080013b9578 100644 GIT binary patch delta 5968 zcmX@D->zTZ8Q|y6%O%Cdz`(%k>ERN@z`(EoggMw47#OFt%hmI|q0=J1Z0v z<)>xlq%tsQOst(~>v7mY=4gEIQdez0F~PzU0dsXax~#H9y8;xiaILkP5%Y`HS89@o zxckAa4;~y(x_ae4vqHYIGX2)F00{;ePRchz;qaP90$coN|tDV1`a{~!DQ-CK)eb zV&v3fTEy#nOl(Ttq$e5{4Hjxm7NizNE`D?YGAIKI^*|C4?pWw4Mw`RZ3Z4KW2N`Zv|8>q)7h#_{s!`z z?qBa`-&=WSf4rjq(x7J(PTF|Oh1_x9x^?S{l`A<*OG*^t;^I_PR9aF}QU&MwxzC*& zYrZnXYwMz>L&<`H7mb!bJzacol0ZX@;PeR!7p~q+{Z_wV(V>5FFKzE$UmxFXQ}cnL zw7i@@LTsjw+o@BhJbK)+LsD1Vi`!6;D5I}$diK_-RjfL^j6XzAYOdV${oUQ$VG$7r z^fdLR{rd3m@PzA%N=i*vu3UMwwY214Na~7rhQ`LVeAE6P?6fJ*Teo=fWy4xAZfB0# z?e%VMZa4hTojblRrhc!L_?{2UMbC1cTzR~3;l*24R`WeoE|v4{`~TZrX`+GJ%h=o7Pns~nAw+AdZ|gya&FSYI zlS~YQzHUyhKR45Q`rq@f>o?4rCH3XQL*_3#_s(6va3N#uuP>e1d*{rJjjK15;4zoB z-u?R0W|itXmH7d6OOCm@&*eXV?o?d)_jhyuf6sSyJvw1y=gIqv7G379s;as)UDx>U zk+hfUb~QgX+<)HH)vaqObI!!5Gv(L$+2&T3H*Y@DkKgy^`%LfYdgp3>y~*_R&OV*l za-r%4<8jIGb#YTy)O>mI>!EY|lUuiM|E`bSIcsKgjga{K%CjeKJmGTEIDhKUp5zT* z)6$-Q2+|ZuOG`V}_3Kwv`;;e7PRh#Gc0Ydn+*nq2 zx!*ocWO!fSTlnU!?2@Zju4Mlz-ga_R>fPw=`S<^C+2!`4`bEj(XS%Q6EqL(vnV8t! zzb}`rT^#)Q(WW4&Jr-^szkHgtbk(x3%h$E0_xH=+e)Rggik_llrplM!xv#?t+MHwik6-7Q^7{;vl9E-|u37u~chQ@*-{lqJqSG_WfKQV8w zyX2}>wQLKU4}bcl!sjlu$ExkdyZ8Us=(p=dY*|-M(t;=k# zuh$*zDJdz+QYev|+c%ekyI6|*t%ac2GneI;IsXe=u8p`TFn@vu$33~$mib*B zibbo@#^G>Q$`Wn>-H{fI#hFdW$^MSEv<9gW471rO?3;~b}-=TMr)1w z9}^}_5J(i5*VWO%nBQ-7&eip7Xn6R4pC<qbb9?COL!EQ#Q}YrpZoPZIKEPT!e0}U=TS2jB&)>Yc zqoJl|w!mll%ge4C=g*ldeSU5~*SS;YW?IbnwLR~)o3Xk1d!GQN9+7lil48GeSYA#vX4@`@y#Pgp0FOu-TW~zPTI_Z-zDttX|_*CPlxvzORPFP zso_+D!3J(|ecAQ(Hw_k)SH{Ij&*{9EtrxxR4Xe6p>ZgeB59?=~W6#N}I)Co``Qy34 zl9JN(1!aXNd94eZeqYUvniXAAvZXxprR|~no+?7KY^%+dtzBDOe`?FhrBSnH-L0&w zjPy~vyko_V8PAhHv%3`*3jV#hIbDV4aKZWM)21z&oS1lTt*-7<4#joV`sv}*?}Y!a z`>LpE_;K}`mrYGfp4{Hv^|mbmQYEg+N7CfyXS?j}y}y3{T*;0(OP-|I>gK++Ymb$c zo%_x-`}*Aey=A$MMP6Kq{L|)4^6KmDj^0rAcGu_b8E5Q_W4CPCdVBJu$zRzP&0jzN zzNAv5^d9rLHVp|yt6jTRvbDAK?wzZotlS)Xe(%PXXv>KcpP$HE9X9p5sDWAiwzBiV zmoMF5zNl-eJ2y8y{r?{QnXBvm?ferHB68ii@Z9|C(>*-Bg=O=y-n=@vLV#EK^E?0e z$VkcOQ>NzSZQW^}=l3A&+{q6g;sfQ|+iL#)nU|XOoJY&h#QHfur~Ur;`GU)vCvNzE z<+N!1vwJfh?7Fme+jbdIQBj>=hg|<|d~>|saQDu;w{EQ}{r|5vX6KqUIj#RLsmAY^ z7#I;@vUJ6U7vhWVpE+}Ob#G5kgqmAhvsbUVkx7PuiBZbTnX|(m*B=dti@Vph|I??P zQ7#+4POqCdb?V9&v339d)n57ZavHY*YewO6lPsW~tH z!Rs$a=gpHVS^s4o56c4%gUU}SF>1!WZqDb<>Ak&ks^Zg=&g|l1<8^a(&Y1b;<-<55 zk*cbyiYYotMw1J*e>_Y3^5mp&_`2}J=k(s1NYw?qJ0xrpVtG){apCgiUg;{9))u?f zt5+7?Wu7*5YSwqX$+OM*S7v9h@Go8WN=oWr@zW<9ac8Gw7Hc~@$Lhuf1bi?yFKb}< z!)LQ@)uE||5eZ`))cQ+Ccl_HQJ$`&bKkmWuiY<~`q|ChcCu@|Nnk1B8NV||# zHGO}@N2a64j~BjW&Xmkt_lAM-hit|7b)5F{kDmWO@%Ho2pRFrCzKxFAIcZYR>w*{F z%P$vdf8L&de^FBt)4Eijxkjw!`*U+@*cmN^wrt=2u>I&=85Xy{FZDW;Cd%?zAt=Oh`%T-p(w~^1#C&nXNY=BJW96v{!sR z=OV!q8ag^pB>8H&xjq$cTXr!c#Gwu{_{VP}2D%_CM+|@5?hU zJ3Q~4x^Uq_PiR1ZN(;FacVaB9*M(1IV{_Fp<1T;i6&fn4rK!17LVW&$zq(dx zDk@pir%Z`iqjY0!!p6w{n>l5I+c!OV^5o5}Tl?z19yxrtIVC0av?7B$gQMa>FICk) zb~T6Z^LB~y%NK2xnkG;`|EH^;UmkaFTfAplYU-z@%a*0Y_wF@Y@iQT&=Fp-kSEjsK zv&QF6wa~2|Ne@52yzZ7CKI<+WJEbibAT9OpSm{+SZZQRr$t*SX_e@PWH%F{VyQ1wG z93Ia9`NM}Nziw^)am3|PS^fW*EAHPuEpk1>|M20%X!b?7UmB+~&G30v-@Y#;@B8=j zz8+qWejS;&dw2VRM@M)4m|V}scGmao*+T-n$}R;SCzO@g<*jNeK07=q2nt$K`t{4N zJ^leME-b}`g==pJZYXwjOA~w8z5UFZHIj_h ztSer8NK=Y*Ja|x4fK~a##>We9cgWOl&fM(((IGW8Ep28*WaRhi+;>wngAI9kd%tVn zy_40~-e$M4yx>N?jmejjUgq1SczMr$|9iKrOW@F&HEVVU2VIU}zM6aa+1ZxsB{KGc z+jtixuyEeo9X{u1+GCl;>(6KTK2h=J4UtnLcWy zRljmyo%X>G-EVJi_m^3e&|+w6to`P#jM2=V+?$VfR~dW~nrz|S>+6%%ZuFz4r#E}D z#hj(h7oW#RZ(E}#93zYJH2_LdEl>Z66fC0Um&Tk4&ak@2XC!_)KRx?n-UUH6rKN7ek!{G=1a-Pj~;z8yw7~<#HoL4 zx4W!guw+TZ^ZlpmrKO}Qva+&l1f90CYu`S2PLEk}%ETiNb#~Og-gfr#a{rl1mtCiY z+*!Zi%GIlju3ZZ|a{he(lGKz7e|~z&zL%2DKcRg>{MD;hYEjXVwm+_IyL$b4rP-dE z&eP{Erf=j8{n4YL0eSaq z91{~09d}lKW{Hi7WfiuZqxkpH)2lZk0-c=?_ub2#WtuIv&06CG(;mC7im$(V8@ju* zg)dInpzc=X(n0F8mZ!ul#H6h6M*67M%&* zxbe!oA4gO+Htnf7^yVF>a7F!WJ)JA{On1*|C~I50AG&w1@x#0q$;X`!9}9~1Yn#ud zoYg*c>(}FF&ge*0R8J9-5E1cxV!`cw`r9Mx^5ZVA3NnAzN=i#dA6otB+uQr{ym4|z zMZ~6`*}Qf0Vaq`0NTDDNrIdSvqO z?9X31V#hrF3?+K4(o@nTH}LXsl!qVkn`grqC+jR@Sznbit8wAO{hf0%)AQ@IOh&4uK8}Muqya) zLG8mc2^Gao=h(efzPy3!ld`gYRqfvYf5i=Uem>5H5mAwQ)h-q9-CbRp74>V!zLuIh zbqqJJ-C9*V?aG>Z(?yFG+3u}BeddhF>lZIF8;Y;XKiFI7kQMc-#?&J`7&0RaM^zt;aQeeLn$$$sU8 z)f@+3S_*Zxu(rSX$h^K+d0ot2tB238u9g@6W@cq2>p3Uo>-$^U%a^Zy{pw|@z{|() z-u<)CQ%q#4$ekl7R~S{kZ<5f%n$vd=FW$1^$+;u%7arfcX;Y{49`k8JFE>u`dv7N% zx|I8#)bbxWH;Y`4CQ&S5)dQ^4MgQbOj5jQ-QH8nL4 z{hYpZYG_?t-N6k!M^($;`EA+0{k;0B@^f=6WtX3yW6JdYkHVVra{lKJA0A|7vbVQ? zwzdACb%MpKJ8vEfA2@%$fA!?{cJ|Nret7@rf5rW&|3-#X@3YkG?DpSWY1!G@RWYHG z72obu&Rw`B|M4>4PgDElShsC^WbgRs#gi)r$;Wu~Ux?Kie3Q3WaXP_(BmC&`I zPu|__Y$UI(tuG$Cz3#8}qN*!bukP-bvE^EQPk!p$-Ste)f%5XJ?+I@B{3+mypomP- z%#0oG9Epn6-&&f)>k{~6|J6R4wKaRb5NrE}4F%~558mbPic0?9$Q+iGlr*>F$KA)5 zJ1(cIHHp_1oO5%_l8})2CcxU+-Tiv`=7|%Vt=KBW6xIIy^UvwX@AI~ejk~uuJ#Ae7;ns%64`M-u zg@tb(EOch};=8dccX#`a9TpGwoUeYYe>5SN!Tm}r_oQ!pd@byUrcMpLrGNa0!On@o z^Jkn$F$lTc@x)?n#Kk&QH8-I(yaJob?ph^BMn)c*cly}1Z8n{g@4T;HuDN+r@edi< zxu*m_=CN-peC(FI!RUhe_W!4p*GIpX^PIAwLEunL&A+Sf`&z#ri|-S(bzc}@kpI4S z;?bk3DI1I~==ZOZJHKh$#!5dM5$)uO&(E({sCpL@@`cN^wDiz{(ujR~zGar4)!CqT zKy>-}IagEk;_kSL_<%Y^FC}Nj*TqR(nUh_Q!YS>VvZm90)&p@gk@%biI?Qd;WZR zZeH7s+=?wu&i-$dP8B(FByQVQwlaME(ZB<<4lMBS^bNjp>5`I6l&+4>?t1&GEA>|n z9hzjYF!5v3-D6*Re0BtI{4nd2<&QN@KPPi2R;aX;_s~55xVU}le?pFEZICliVPR!| zzJRetQZwcJJ)I3wR=}aG<-p`XOmC znYnZ1!tK|$CQm)^VbaXGxjQ;e{?R&Jd2x~J+?h9Twk60u*!|$;)6~tE&aQ2rK3zQe u<+fGFFP}cmwuT8~>=M6#8me^jgL;O|1#3-b=V%-S$#}Z@xvXERN@z`zjAz`)SJ!N$PAkaAwMl!1YI#O}GkpF${`_p&J8v?Wq z&%TmiHo0py`QwZk=hk#JhLlw zv0@czH+)o|cOE{Hw6AxnSsH7w@;sHk6|?+)I_)~B^<4K{)XGD%zCY3DN&PxCb=_qt z7n$yeCKf?1EsaGi(t)>1gLG70+B|zGU+`CGr$~|L!4n%Bj7(0flmEkC_x;)_-yZWx zPKFCUA1$^6T z|231}-M!Wg^P74Km&iWiYc&_i|s!t`7$2JueZCJEO zsHnI|LDf?76ZfZw=koU-e82y{yorg4LvgXNP2C>}fsg*rB-tu#`655a%4 zDrBogPH>B{EuQIfZ6(8GyJydy)$hpon6ziwj2pF%{=pg_n)&VCTv*#A`a(?X$3=JT zz=<l#;oRMdix2bnW+G8*pJ|CfDtclYrsoAmVbA0HM^HK_dbq|nIt@(n%ivY?`R zuDm~wxI6mnIQyfbZvFj#?AWn{^n8B4Z+s7)z2lRcCzp_Ohnt6o$3aJ|IYQ^(rBjD$ zzRKTUzh>RdD;F+oSha8=qm;Dt!hiQ{PnO9`m#nsd8 ze?WI(qDAJVlr5V!Wtr!nJ9n<)!-a?IHct-^2~%xd6W^cIxg_@1fd!7u1!r%S&M0_$ zolj8h<1gMnIftKHRD2M4QnWMcv({9vkCXlFkFJ_^%XcnUNt`3&xp~I!xzBdS==DiG zZMJ`Nr?v7Y>)d*y^3K%AxWLNq?XkNr+^*hmYx<(j$!dvZd2z=-&7U{1bKkzjSD9{I zxgrw2E~Wvb!JW9L{(*N>Dv05+EAvm20VQ- zmO}pXtghUqUUF4w})Oeofws#aCeu$)Ot3RoiTmCmHEDK96q=3u=|B$$J~~3?XLT)ersy^ z_j`La#JX29-@b9W%O_1 zm$&~Y{O#@S>00|2FIHAlUA|%R{$0z;9!lr`<1bZ6ejF!J_#olD=@I+8K|Ve{D=d8G z)|>HWzkYQr%f{o!wf8Hw>ohtv+|RjpXK~}d=4~x)iyRiXUp=tx?Tv+!VkcK-zIgD! z!?w3sT=!kUg6zEOH$$)Ale;Z>>)a$&@0GV^Or9*fOjG3T@syXFo?hvzt*J5CzaU_F zLcI5$K6XC2LsM6WB|9W2?E3b;KP;?h%E^=r)#;0~>;M0g5U^kb#T7fB+@+n}t2eBO zczX2tUw*p`dH(Q6k5*m}H#Rozv8)bLZ_+d}Dtujj`@-?#?5$1jS9NXC)UjJr6`TeCFM-P@NDn^N`=*Bi?Ut8yDY;1fj z&!jE!!j&^T_4aX_*@7=$+IsHwty0Ahx%rQl&sulvc<|iKadB}omLGk)c-6A9#2?q* zU$}KEN`2o^yV_p~Kd!Bf2o2sH5N-2ZROFF@zW%3E(@z@cDLR_Eou0m4Vg3###&`EN zhi5x1*EA~l)gx(D&SA{cd^!Ehfh{Gi*DCJsP3Qmgv*yu^)8T)(>q9x#J49PsC$THL z_Z|6kYL!Z^LABjCrd`z)B`etP-n}cEtRHuU>w#$K)rod?PYz$YcTY}KQpCUZ#)2FWdT5D`tprEjj(cRsB!prY`+(@6pkMkC#%vNE^?Z`uxuE>*6*xHVQwlt$lt= zKycBbMI7wOF*dVi%@V3#dfu$&T$P#1@?{HW@7^6{eBtzI?*DrWmG61%T6%4VdA@4> z<72+@k0vNOUoF1OE3x1v>l4XeNsrx%o;3KhaqidZ-16($?XyR8UDEQ_{h5C&Ieq0` z+p=3@Bc47JXBZiXxw#1XVupW zM~<-6TS=}BX1=s?e*MBKvr65L zM2Ui;qPBgtPo!3Rd@L*sh=^FQr8iMx#{LAxJs*E%Tk*!tvB^}b7v7UGF-Ve;;nU~O zii(PHkN-Uln|5#Ay1$)oSFT+7A$~)5ch}Tc*E=|x_w0$_torlAaM8PWFJ7>0PCNTf zAo`27aeCwC?(R!wE-jCb_icVvKjFH#W%p_L*kLY>dF;=Q=GY}=+) zpSyYUX7=>-=X|c!S2_fH=I-5lY1{FBd1p7bwqv#4OM)f{sHG(*E8A3jV6f2bk6N&D zrRU4(E>4OaM`tz9ojlpursD3My9b=o#JFXBnA!Q3e3-q~@zLq{1o7cbw{?%dX?4tTU1hl<@L*# z+PT$VUI=cnso|J&?OK?^TrL|q{wLGKE}Nt_Y)N8Z(9zYkX3d%DK95z$QQetXc5PaZS@Uh(N?BuKF&Ih?~-)cDg zu%Po)U39i~{gq7}=O28kaM}Cq-{0SJnaYx#nSZuF_#FB5?zLNPb7Exl*ez=8`YoE} zw{39}Cr4XT^PR|s%vW<#da(SesyZOp zeN;hB=lieU-xq#te5Ss|+g@CtbISh-M_pal=n2jWaC2i55!JdcdNj$lxX z5}L*7efQQashpf#(|5nm<(GFvuM1IXj8jt!oU`v*t;D3g41$Wz3op!IOjmbLd$A$$ z$hYF>=MLWId2O}iM)&j&K0Z7fYmanHe2|d7dSjrDwe{?byC%ky8}D37l4575KXC8h zyow1+moD2V^r_eUK1*$_Vxrj}w#4Xd4Q9@Vr`+So&hO{t=WgA#%j(fQtDj%5NAymd zHOpyUiQc*Te;aF$e}AH(#LpiVxf4`FEOzT%v@9Z9*{8`(+B|pBvYOvNer)Js)mgn_ z&+CT|J>S${e`i~Ns{G+2)ucuHSFY4Nwz=Nt*?nGTtC*`t-f&KM2FS8Bc78t?3#AhY+L$!yCjK@$&-bj*+?@xch}A^t>()XNYalv za<3)+fBpLCQ@bVz=xAxCn#KisYOdpsT)!!*1?0B41o;fh>}xrXBpQX)7F??B{ldR) z-QSH1R?ILBp1to+f7DZ>KY#uh7`@U63%$N&^)dHQ-?Ph3tXTZw`>WMj#~*yG*zxl8 z{|(btE?6NE8ynmCIOo@=)6b7vh*vkxh|m(@`dD&C@>);*)oWL8o}Itn;cZ9o{|1IT zLSk*^d1`{rQ%`HJo|JdT=g@`^1z-3%0(QS&UbA7@WyACU4G}F_XC1M}KW}iK?+=cN z>UuY2!SOQnX93fGT*wPID> zxVmS8gg!Ud#&s`$eLb>rrDlru{VOZ$ zFJIwj+*xScS(V{C@6Rpnz;8`1`TKsVSuVbDODccqmg3a2GmWR;SU7Q_;5^&%f2}Nk z{78g@@Z$y4AQTns;XY%w zJea=zSYY?AA3uI1q+0tPI)8E9-Rc!57u_n*De2y8rO~nK@%!8I2j$u)Z7Py6D$#H? zHoVrgcDMHBo1Zsk_*`^oy&7=N+^5R7SDG3gDS5J*S8+oy@xjn$YKgOltM~7gR`R{#%=wZPDpLC@-NeLXNtlAiMaRtT z=N6Q>vRPSM2m6Zc&sh0K@MfA(x!%<)J5ye6l4JbVRlnDN>gT&Z>pz$3r`H@Z-LhrN zlCZY_OJ}ccNN6qiukFTyuXAe#-J-cjqpwAKij%}+C zUe>TSpB@#_ef;smXBRHyS?&&OyZP^;t z;pXqp|NVu=t+1t9KYqQ=514+t{*5|cN1n5zLU@${7wjHKQUT9$fu4CF>gNO0E1iFJIn#DzAR-+}XupylE0__CLRDJ~v%a ze}9A2uLA}=`?jZKPG|d_>6-8;{l$faQJgccP1^WO!%O46qu0`)9;>P~-b{6Gj`)Yv z$FE+vP*Kjla8dTheHkC^Un=fzX1AaJ?!wL0n-(~vhMrdS-u+;ek8gNDvyo)m!Bl^) zh2@e}|C{+&KJQ(QJp&D+4uV6Wt$QX9`X}Sx%lX)R@~CEizl@e ztahKeyiItaM@#j$x35JKzsXxPC{64*GdrN8+Pc%cxAWOAc7`|C*USH&{_K{gX6DNm z&z`wGeVrK>_~I{j_qwqxGm>t2PNI32z&Nd7{K z1l#nr5f}g7t-QIjQtRrPkB|SBim?R-MYV0oh+%Nh&wt-nzb7ud#ORmE)#b+@7JSHD zS-1A`tgYEA`E6&vnH@_$-k%-%BKf%gg8Gdc4NHGtILh5H z*UwdT>5J#zSDkvgM)!8PTiCW!d%hXg)kU37d|y=ZGfU>w?=OKL3u-2Wwahoy@7Oiz zNs(pTwT}nyyxgK{((`M}{CV@_Ubk26yS;6$)btb2Kkr`qeSd67ba=XX@im=SYc_A( zntT89n)-#>g&#ALlf7eYjnmIo71m!04!SJ*;?=GByXLd8ux#7iMXn$idKHXSXl*xbwZpl8>K6 zv+C#B95%2>e`llRQT*&&=*nlYHI6|Xp_iWNE?jl>VD9aJWYc`_7v2m0FPL?^w9GjD z+>&#C(b3U8-T!|_KP%+q%*gf*|9o$zrEqrM=IXOsjt9~SgTe~DWM4faGLGP diff --git a/examples/meshtastic/qml/img/marker.png b/examples/meshtastic/qml/img/marker.png new file mode 100644 index 0000000000000000000000000000000000000000..2dc79f2189d674d64b05aed80f2404e58742dce7 GIT binary patch literal 1173 zcmeAS@N?(olHy`uVBq!ia0y~yU@!t<4mJh`208nVjSLJ7Y)RhkE)4%caKYZ?lNlHo zI14-?iy0WWg+Z8+Vb&Z81_lQ95>H=O_QyQZ9F`1n)k=a43@l2XE{-7?_pZ*2&4_Uo zIbQ$V%y6yz-vtY|)!A=T3mDDYF@J0+@03n-S~BOpFU@6yx-Qi z_+L63)9h!5w|q3staxDRw`KO%ke$*G6(7hg;Ej;`Z^JpI@$vz6ml{phy4mlQiv`X$ ze>pz?{o>gjix0@o+shyLbiovjbKhrjcGxj~vRko`dqd+hH@~ikT81Z=*Em@1oR#Lc zFo)OXV*84s^;2eisWmB*`?K%I-3xxgj}Iz76FVKIq_(BIZ~t{aEy2LU$p_d|5;uSG z>P&WYyu#qcf2eTNR(AnE4?~8IXN_9sjP{#N%9!heCI%-e1uE?w1mo4He zK3H9tp1@$w_?|KT`2Fn+zZ;{4e^gC32Tmzlig^900Qi0?C-V%|8X z{d{-QPOXdIR$B%fK0QxBzeBZOhlkODJ9T3T^ZJS(xnYL|&#KOiYSUbBbmQj5NtP?v zj_f)qZMuio=H-j}s5kbz&dR!M-_Ub4rsR%*UPtQMvPK?@XG?ahm2zu*J?GNW3o5Q@ ze~o@;eX~+O6IqshPnIdE!_sp9ZOiAH{OS`m`mM?g^i;h(Lsoxjj`-DB=su;^(_x!s z-}8Qkok>RmyYEhAdE$25@eB*Y%(9&FVxb8uFK}5k^mnT8owKsqug1`^x_|ROg;U4- z&P6jfY_VE5vHy>w(!EbBg8N>tV<`>MU-8iSIeXfxe$7?0E4yPpM4YIWOrE=c-P5WS z^QHeyy}3WZ<+iOuR?(pi#hbDgE!r(6UiSIl8nG$yHmk1WGdzizbXO^9|MhdmC$~uX z?5&E{_dO~bGLvbR&8wfsrB@5w-5RiVE=wE}+tE+eYt~*`CE!auUu|%{yQF};|^Rk?0cp$ z9+M7Ai8y6<$1XTq{Y3hLw$F_r@14rxbY5=}URUBXOM=D0oMHA02T32!6zOcqDZ(#9 z(x#u#R&d?N#J7~;d$zgmGfs)T|0GUa9@Re%Q}`X0c>v fNXgCAZ{n-HOlsfR&YH)-z`)??>gTe~DWM4fy;TR& literal 0 HcmV?d00001 diff --git a/examples/meshtastic/qml/main.qml b/examples/meshtastic/qml/main.qml index b5821e8..f10b509 100644 --- a/examples/meshtastic/qml/main.qml +++ b/examples/meshtastic/qml/main.qml @@ -20,12 +20,27 @@ Item { height: width } - Image { + Image { // location icon ('Group') + objectName: "location" + source: "img/location.png" + width: headerHeight + height: width + anchors.right: parent.right + visible: false + + MouseArea { + anchors.fill: parent + onClicked: Lisp.call("loc:show-map-clicked") + } + } + + Image { // find icon ('Messages') objectName: "find" source: "img/find.png" width: headerHeight height: width anchors.right: parent.right + visible: false MouseArea { anchors.fill: parent diff --git a/examples/meshtastic/readme-usage.md b/examples/meshtastic/readme-usage.md index e366cd9..168c631 100644 --- a/examples/meshtastic/readme-usage.md +++ b/examples/meshtastic/readme-usage.md @@ -55,6 +55,11 @@ person is associated to a specific radio. This view is populated automatically. You can set a name to every person associated to a radio, which defaults to 'Anonym': a press-and-hold on the name will enter edit mode. +A tap on the location item on the right shows a map with all known positions of +the persons. The map tiles are cached automatically for offline usage, which +means: once you visited a place on the map, it will remain available even +without internet connection. + Radios ------ @@ -137,3 +142,4 @@ to will be shown once the server is running. Beware though that Swank on mobile isn't very stable, but it's perfect for simple debugging purposes, or to get/set variables on the fly (but it might crash regularily if you try to eval some buffer). +