mirror of
https://gitlab.com/eql/lqml.git
synced 2025-12-24 02:50:36 -08:00
example 'meshtastic': send phone GPS position to radio; revisions
This commit is contained in:
parent
80acb3c92d
commit
9699a0ce6f
28 changed files with 1566 additions and 79 deletions
|
|
@ -12,5 +12,6 @@
|
|||
(:file "lisp/messages")
|
||||
(:file "lisp/radios")
|
||||
(:file "lisp/lora")
|
||||
(:file "lisp/location")
|
||||
(:file "lisp/main")))
|
||||
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ QMAKE_EXTRA_COMPILERS += lisp
|
|||
win32: PRE_TARGETDEPS = tmp/app.lib
|
||||
!win32: PRE_TARGETDEPS = tmp/libapp.a
|
||||
|
||||
QT += quick qml bluetooth sql
|
||||
QT += quick qml bluetooth sql positioning
|
||||
TEMPLATE = app
|
||||
CONFIG += c++17 no_keywords release
|
||||
DEFINES += DESKTOP_APP INI_ECL_CONTRIB QT_EXTENSION BACKGROUND_INI_LISP
|
||||
|
|
|
|||
|
|
@ -31,7 +31,9 @@ void BLE::startDeviceDiscovery() {
|
|||
|
||||
void BLE::addDevice(const QBluetoothDeviceInfo& device) {
|
||||
if (deviceFilter(device)) {
|
||||
qDebug() << "device added:" << device.name();
|
||||
QString name(device.name());
|
||||
qDebug() << "device added:" << name;
|
||||
Q_EMIT deviceDiscovered(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ public:
|
|||
|
||||
Q_SIGNALS:
|
||||
// notify
|
||||
void deviceDiscovered(const QString&);
|
||||
void mainServiceReady();
|
||||
void deviceDisconnecting();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,15 @@
|
|||
#include "qt.h"
|
||||
#include "ble_meshtastic.h"
|
||||
#include <ecl_fun.h>
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QtDebug>
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
#include <QtAndroid>
|
||||
#include <QAndroidJniEnvironment>
|
||||
#endif
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
|
||||
QObject* ini() {
|
||||
|
|
@ -15,7 +21,12 @@ QObject* ini() {
|
|||
}
|
||||
|
||||
QT::QT() : QObject() {
|
||||
// BLE
|
||||
ble = new BLE_ME;
|
||||
ble->connect(ble, &BLE::deviceDiscovered,
|
||||
[](const QString& fullName) {
|
||||
ecl_fun("radios:device-discovered", fullName.right(4));
|
||||
});
|
||||
}
|
||||
|
||||
// BLE_ME
|
||||
|
|
@ -47,6 +58,48 @@ QVariant QT::write2(const QVariant& bytes) {
|
|||
return QVariant();
|
||||
}
|
||||
|
||||
// GPS
|
||||
|
||||
#ifdef Q_OS_ANDROID
|
||||
static void clearEventualExceptions() {
|
||||
QAndroidJniEnvironment env;
|
||||
if (env->ExceptionCheck()) {
|
||||
env->ExceptionClear();
|
||||
}
|
||||
}
|
||||
|
||||
static qlonglong getLongField(const char* name) {
|
||||
QAndroidJniObject activity = QtAndroid::androidActivity();
|
||||
return static_cast<qlonglong>(activity.getField<jlong>(name));
|
||||
}
|
||||
|
||||
static double getDoubleField(const char* name) {
|
||||
QAndroidJniObject activity = QtAndroid::androidActivity();
|
||||
return static_cast<double>(activity.getField<jdouble>(name));
|
||||
}
|
||||
#endif
|
||||
|
||||
QVariant QT::iniPositioning() {
|
||||
#ifdef Q_OS_ANDROID
|
||||
QtAndroid::runOnAndroidThread([] {
|
||||
QAndroidJniObject activity = QtAndroid::androidActivity();
|
||||
activity.callMethod<void>("iniLocation", "()V");
|
||||
clearEventualExceptions();
|
||||
});
|
||||
#endif
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant QT::lastPosition() {
|
||||
QVariantList pos;
|
||||
#ifdef Q_OS_ANDROID
|
||||
pos << getDoubleField("_position_lat_")
|
||||
<< getDoubleField("_position_lon_")
|
||||
<< QString::number(getLongField("_position_time_")); // 'QString': see QML 'lastPosition()'
|
||||
#endif
|
||||
return pos;
|
||||
}
|
||||
|
||||
// SQLite
|
||||
|
||||
QVariant QT::iniDb(const QVariant& name) {
|
||||
|
|
|
|||
|
|
@ -25,6 +25,10 @@ public:
|
|||
Q_INVOKABLE QVariant read2();
|
||||
Q_INVOKABLE QVariant write2(const QVariant&);
|
||||
|
||||
// GPS
|
||||
Q_INVOKABLE QVariant iniPositioning();
|
||||
Q_INVOKABLE QVariant lastPosition();
|
||||
|
||||
// SQLite
|
||||
Q_INVOKABLE QVariant iniDb(const QVariant&);
|
||||
Q_INVOKABLE QVariant sqlQuery(const QVariant&, const QVariant&);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
QT += bluetooth sql
|
||||
QT += bluetooth sql positioning
|
||||
TEMPLATE = lib
|
||||
CONFIG += c++17 plugin release no_keywords
|
||||
DEFINES += PLUGIN
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
(defun radio-names ()
|
||||
(qjs |radioNames| ui:*group*))
|
||||
|
||||
(defun name-edited (radio name) ; called from QML
|
||||
(defun name-edited (radio name) ; see QML
|
||||
(app:change-setting radio name :sub-key :custom-name)
|
||||
(values))
|
||||
|
||||
|
|
|
|||
41
examples/meshtastic/lisp/location.lisp
Normal file
41
examples/meshtastic/lisp/location.lisp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
(in-package :loc)
|
||||
|
||||
(defvar *positions* nil)
|
||||
(defvar *my-position* nil)
|
||||
|
||||
(defun ini ()
|
||||
#+android
|
||||
(qt:ini-positioning qt:*cpp*)
|
||||
#+mobile
|
||||
(progn
|
||||
(q> |active| ui:*position-source* t)
|
||||
(update-my-position)))
|
||||
|
||||
(defun update-my-position ()
|
||||
"Mobile only: update position from GPS of mobile device."
|
||||
(unless (getf *positions* (lora:my-num))
|
||||
(destructuring-bind (lat lon time)
|
||||
#+android
|
||||
(qt:last-position qt:*cpp*)
|
||||
#-android
|
||||
(qjs |lastPosition| ui:*position-source*)
|
||||
(if (zerop lat)
|
||||
(qsingle-shot 1000 'update-my-position)
|
||||
(let ((pos (list :lat lat
|
||||
:lon lon
|
||||
:time (if (zerop (length time)) 0 (parse-integer time)))))
|
||||
(setf *my-position* pos)
|
||||
(qlog "position-updated: ~A" pos)
|
||||
(set-position (lora:my-num) pos)
|
||||
(send-to-radio)))))) ; just once on startup (for now)
|
||||
|
||||
(defun send-to-radio ()
|
||||
(if lora:*config-complete*
|
||||
(lora:send-position *my-position*)
|
||||
(qsingle-shot 1000 'send-to-radio)))
|
||||
|
||||
(defun set-position (node pos)
|
||||
(let ((lat (getf pos :lat)))
|
||||
(when (and node lat (not (zerop lat)))
|
||||
(setf (getf *positions* node) pos))))
|
||||
|
||||
|
|
@ -25,11 +25,12 @@
|
|||
;;; ini/send/receive
|
||||
|
||||
(defvar *config-id* 0)
|
||||
(defvar *config-complete* nil)
|
||||
(defvar *notify-id* nil)
|
||||
(defvar *ready* nil)
|
||||
(defvar *reading* nil)
|
||||
(defvar *received* nil)
|
||||
(defvar *schedule-clear* nil)
|
||||
(defvar *schedule-clear* t)
|
||||
|
||||
(defun to-bytes (list)
|
||||
(make-array (length list)
|
||||
|
|
@ -45,14 +46,15 @@
|
|||
(defun start-config ()
|
||||
(when *ready*
|
||||
(setf *schedule-clear* t)
|
||||
(setf *channels* nil
|
||||
(setf *config-complete* nil
|
||||
*channels* nil
|
||||
*node-infos* nil)
|
||||
(incf *config-id*)
|
||||
(send-to-radio
|
||||
(me:make-to-radio :want-config-id *config-id*))
|
||||
(q> |playing| ui:*busy* t)))
|
||||
|
||||
(defun set-ready (name &optional (ready t)) ; called from Qt
|
||||
(defun set-ready (name &optional (ready t)) ; see Qt
|
||||
(setf *ready* ready)
|
||||
(when ready
|
||||
(app:toast (x:cc (tr "radio") ": " name) 2)
|
||||
|
|
@ -62,15 +64,25 @@
|
|||
(defun add-line-breaks (text)
|
||||
(x:string-substitute "<br>" (string #\Newline) text))
|
||||
|
||||
(defun my-name ()
|
||||
(when *config-complete*
|
||||
(me:short-name (me:user *my-node-info*))))
|
||||
|
||||
(defun my-num ()
|
||||
(when *config-complete*
|
||||
(me:num *my-node-info*)))
|
||||
|
||||
(defun send-message (text)
|
||||
"Sends TEXT to radio and adds it to QML item model."
|
||||
(msg:check-utf8-length (q< |text| ui:*edit*))
|
||||
(unless (q< |tooLong| ui:*edit*)
|
||||
(incf msg:*message-id*)
|
||||
(when (stringp *receiver*)
|
||||
(setf *receiver* (name-to-node *receiver*)))
|
||||
(send-to-radio
|
||||
(me:make-to-radio
|
||||
:packet (me:make-mesh-packet
|
||||
:from (me:num *my-node-info*)
|
||||
:from (my-num)
|
||||
:to *receiver*
|
||||
:id msg:*message-id*
|
||||
:want-ack t
|
||||
|
|
@ -85,7 +97,7 @@
|
|||
:text (add-line-breaks text)
|
||||
:mid (princ-to-string msg:*message-id*) ; STRING for JS
|
||||
:ack-state (position :sending msg:*states*)
|
||||
:me t)))
|
||||
:me t))))
|
||||
|
||||
(defun read-radio ()
|
||||
"Triggers a read on the radio. Will call RECEIVED-FROM-RADIO on success."
|
||||
|
|
@ -99,7 +111,7 @@
|
|||
(qt:write* qt:*cpp* (header (length bytes)))
|
||||
(qt:write* qt:*cpp* bytes))))
|
||||
|
||||
(defun received-from-radio (bytes &optional notified) ; called from Qt
|
||||
(defun received-from-radio (bytes &optional notified) ; see Qt
|
||||
(if notified
|
||||
(progn
|
||||
(setf *notify-id* bytes)
|
||||
|
|
@ -110,7 +122,7 @@
|
|||
(push from-radio *received*)))
|
||||
(values))
|
||||
|
||||
(defun receiving-done () ; called from Qt
|
||||
(defun receiving-done () ; see Qt
|
||||
(setf *reading* nil)
|
||||
(process-received)
|
||||
(values))
|
||||
|
|
@ -125,14 +137,20 @@
|
|||
(when (string= name (me:short-name (me:user info)))
|
||||
(return (me:num info)))))
|
||||
|
||||
(defun my-name ()
|
||||
(me:short-name (me:user *my-node-info*)))
|
||||
|
||||
(defun timestamp-to-hour (&optional (secs (get-universal-time)))
|
||||
(multiple-value-bind (_ m h)
|
||||
(decode-universal-time secs)
|
||||
(format nil "~D:~2,'0D" h m)))
|
||||
|
||||
(defun set-gps-position (node pos)
|
||||
(flet ((to-float (i)
|
||||
(float (/ i (expt 10 7)))))
|
||||
(when (me:latitude-i pos)
|
||||
(loc:set-position node (list :lat (to-float (me:latitude-i pos))
|
||||
:lon (to-float (me:longitude-i pos))
|
||||
:alt (me:altitude pos)
|
||||
:time (me:time pos))))))
|
||||
|
||||
(defun process-received ()
|
||||
"Walks *RECEIVED* FROM-RADIOs and saves relevant data."
|
||||
(setf *received* (nreverse *received*))
|
||||
|
|
@ -167,7 +185,12 @@
|
|||
(t
|
||||
(qlog "message state changed: ~A" state)
|
||||
:not-received))
|
||||
(princ-to-string (me:request-id decoded)))))))))) ; STRING for JS
|
||||
(princ-to-string (me:request-id decoded))))) ; STRING for JS
|
||||
;; GPS location
|
||||
(:position-app
|
||||
(unless (zerop (length payload))
|
||||
(set-gps-position (me:from packet)
|
||||
(pr:deserialize-from-bytes 'me:position payload)))))))))
|
||||
;; my-info
|
||||
((me:from-radio.has-my-info struct)
|
||||
(setf *my-node-info* (me:my-node-num (me:my-info struct))))
|
||||
|
|
@ -178,6 +201,8 @@
|
|||
(setf *my-node-info* info)
|
||||
(setf *node-infos*
|
||||
(nconc *node-infos* (list info))))
|
||||
(x:when-it (me:position info)
|
||||
(set-gps-position (me:num info) x:it))
|
||||
(when *schedule-clear*
|
||||
(radios:clear)
|
||||
(group:clear))
|
||||
|
|
@ -192,6 +217,7 @@
|
|||
:node-num (princ-to-string (me:num info)) ; STRING for JS
|
||||
:current (equal name (app:setting :latest-receiver)))))
|
||||
(when (find name *ble-names* :test 'string=)
|
||||
(setf radios:*found* t)
|
||||
(radios:add-radio
|
||||
(list :name name
|
||||
:hw-model (symbol-name (me:hw-model (me:user info)))
|
||||
|
|
@ -213,6 +239,7 @@
|
|||
;; config-complete-id
|
||||
((me:from-radio.has-config-complete-id struct)
|
||||
(when (= *config-id* (me:config-complete-id struct))
|
||||
(setf *config-complete* t)
|
||||
(q> |playing| ui:*busy* nil)
|
||||
(qlog "config-complete id: ~A" *config-id*)
|
||||
(unless (find (my-name) (app:setting :configured) :test 'string=)
|
||||
|
|
@ -256,12 +283,12 @@
|
|||
(qsleep 20)
|
||||
(start-device-discovery (app:setting :device)))
|
||||
|
||||
(defun change-region (region) ; called from QML
|
||||
(defun change-region (region) ; see QML
|
||||
(app:change-setting :region (app:kw region))
|
||||
(qlater 'change-lora-config)
|
||||
(values))
|
||||
|
||||
(defun change-modem-preset (modem-preset) ; called from QML
|
||||
(defun change-modem-preset (modem-preset) ; see QML
|
||||
(app:change-setting :modem-preset (app:kw modem-preset))
|
||||
(qlater 'change-lora-config)
|
||||
(values))
|
||||
|
|
@ -277,6 +304,27 @@
|
|||
:role :primary))
|
||||
(change-lora-config))
|
||||
|
||||
(defun send-position (pos &optional (to (my-num)))
|
||||
"Send GPS position to radio."
|
||||
(flet ((to-int (f)
|
||||
(floor (* f (expt 10 7)))))
|
||||
(send-to-radio
|
||||
(me:make-to-radio
|
||||
:packet (me:make-mesh-packet
|
||||
:from (my-num)
|
||||
:to to
|
||||
:id (incf msg:*message-id*)
|
||||
:hop-limit 3
|
||||
:priority :background
|
||||
:decoded (me:make-data
|
||||
:portnum :position-app
|
||||
:payload (pr:serialize-to-bytes
|
||||
(me:make-position
|
||||
:latitude-i (to-int (getf pos :lat))
|
||||
:longitude-i (to-int (getf pos :lon))
|
||||
:time (getf pos :time)))
|
||||
:want-response t))))))
|
||||
|
||||
(defun channel-to-url (&optional channel)
|
||||
(let ((base64 (base64:usb8-array-to-base64-string
|
||||
(pr:serialize-to-bytes (or channel *my-channel*)))))
|
||||
|
|
@ -295,7 +343,7 @@
|
|||
(set-channel channel)
|
||||
channel))))
|
||||
|
||||
(defun change-receiver (receiver) ; called from QML
|
||||
(defun change-receiver (receiver) ; see QML
|
||||
(setf *receiver* (parse-integer receiver)) ; STRING for JS
|
||||
(app:change-setting :latest-receiver (node-to-name *receiver*))
|
||||
(msg:receiver-changed)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
(load-settings)
|
||||
(lora:ini)
|
||||
(db:ini)
|
||||
(loc:ini)
|
||||
(setf msg:*message-id* (1+ (db:max-message-id)))
|
||||
(if (setting :latest-receiver)
|
||||
(msg:show-messages)
|
||||
|
|
@ -12,17 +13,19 @@
|
|||
(q> |playing| ui:*loading* nil)
|
||||
(q> |interactive| ui:*main-view* t)
|
||||
#+android
|
||||
(ensure-permissions :bluetooth-scan :bluetooth-connect) ; android >= 12
|
||||
(progn
|
||||
(ensure-permissions :access-fine-location) ; for sharing location
|
||||
(ensure-permissions :bluetooth-scan :bluetooth-connect)) ; android >= 12
|
||||
(lora:start-device-discovery (or (setting :device) "")))
|
||||
|
||||
(defun view-index-changed (index) ; called from QML
|
||||
(defun view-index-changed (index) ; see QML
|
||||
(when (and (= 1 index)
|
||||
(not (app:setting :latest-receiver)))
|
||||
(q> |currentIndex| ui:*main-view* 0))
|
||||
(q> |visible| ui:*find* (= 1 index))
|
||||
(values))
|
||||
|
||||
(defun icon-press-and-hold (name) ; called from QML
|
||||
(defun icon-press-and-hold (name) ; see QML
|
||||
(cond ((string= ui:*radio-icon* name)
|
||||
;; force update devices
|
||||
(lora:start-device-discovery (or (setting :device) "")))
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@
|
|||
(q> |currentIndex| ui:*main-view* 1) ; 'Messages'
|
||||
(show-messages))
|
||||
|
||||
(defun check-utf8-length (text) ; called from QML
|
||||
(defun check-utf8-length (&optional (text (q< |text| ui:*edit*))) ; see QML
|
||||
"Checks the actual number of bytes to send (e.g. an emoji is 4 utf8 bytes),
|
||||
because we can't exceed 234 bytes, which will give 312 bytes encoded protobuf
|
||||
payload."
|
||||
|
|
@ -66,7 +66,7 @@
|
|||
(q> |tooLong| ui:*edit* nil))))
|
||||
(values))
|
||||
|
||||
(defun message-press-and-hold (text) ; called from QML
|
||||
(defun message-press-and-hold (text) ; see QML
|
||||
(set-clipboard-text text)
|
||||
(app:toast (tr "message copied") 2)
|
||||
(values))
|
||||
|
|
@ -89,7 +89,7 @@
|
|||
(qjs |clearFind| ui:*messages*)
|
||||
(qlater (lambda () (qjs |positionViewAtEnd| ui:*message-view*))))
|
||||
|
||||
(defun highlight-term (text term) ; called from QML
|
||||
(defun highlight-term (text term) ; see QML
|
||||
"Highlights TERM in red, returns NIL if TERM is not found."
|
||||
(let ((len (length term))
|
||||
found)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
(:export
|
||||
#:*channel*
|
||||
#:*channels*
|
||||
#:*config-complete*
|
||||
#:*config-lora*
|
||||
#:*my-node-info*
|
||||
#:*node-infos*
|
||||
|
|
@ -34,6 +35,9 @@
|
|||
#:change-modem-preset
|
||||
#:channel-to-url
|
||||
#:ini
|
||||
#:my-name
|
||||
#:my-num
|
||||
#:send-position
|
||||
#:start-config
|
||||
#:start-device-discovery
|
||||
#:read-radio
|
||||
|
|
@ -80,7 +84,21 @@
|
|||
(defpackage :radios
|
||||
(:use :cl :qml)
|
||||
(:export
|
||||
#:*found*
|
||||
#:add-radio
|
||||
#:change-radio
|
||||
#:clear))
|
||||
#:clear
|
||||
#:device-discovered
|
||||
#:reset-configured
|
||||
#:reset-default-radio))
|
||||
|
||||
(defpackage :location
|
||||
(:nicknames :loc)
|
||||
(:use :cl :qml)
|
||||
(:export
|
||||
#:*my-position*
|
||||
#:*positions*
|
||||
#:ini
|
||||
#:set-position
|
||||
#:update-my-position))
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
#:*cpp*
|
||||
#:ini
|
||||
#:ini-db
|
||||
#:ini-positioning
|
||||
#:last-position
|
||||
#:start-device-discovery
|
||||
#:read*
|
||||
#:short-names
|
||||
|
|
|
|||
|
|
@ -1,5 +1,16 @@
|
|||
(in-package :radios)
|
||||
|
||||
(defvar *found* nil)
|
||||
|
||||
(defun device-discovered (name)
|
||||
"Show discovered (cached) device, which may not be reachable / turned on."
|
||||
(unless *found*
|
||||
(add-radio
|
||||
(list :name name
|
||||
:hw-model "Meshtastic" ; we don't know yet
|
||||
:current (equal name (app:setting :device))
|
||||
:ini t))))
|
||||
|
||||
(defun add-radio (radio)
|
||||
"Adds passed RADIO (a PLIST) to QML item model.
|
||||
The model keys are:
|
||||
|
|
@ -10,10 +21,17 @@
|
|||
(setf lora:*schedule-clear* nil)
|
||||
(q! |clear| ui:*radios*))
|
||||
|
||||
(defun change-radio (name) ; called from QML
|
||||
(defun change-radio (name) ; see QML
|
||||
(app:change-setting :device name)
|
||||
(unless *found*
|
||||
(clear))
|
||||
(qlater (lambda () (lora:start-device-discovery name)))
|
||||
(values))
|
||||
|
||||
(defun reset-default-radio ()
|
||||
;; TODO: add in UI settings
|
||||
(app:change-setting :device nil))
|
||||
|
||||
(defun reset-configured ()
|
||||
;; TODO: add in UI settings
|
||||
(app:change-setting :configured nil))
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#:*messages*
|
||||
#:*message-view*
|
||||
#:*modem*
|
||||
#:*position-source*
|
||||
#:*radio-icon*
|
||||
#:*radios*
|
||||
#:*region*
|
||||
|
|
@ -33,6 +34,7 @@
|
|||
(defparameter *messages* "messages")
|
||||
(defparameter *message-view* "message_view")
|
||||
(defparameter *modem* "modem")
|
||||
(defparameter *position-source* "position_source")
|
||||
(defparameter *radio-icon* "radio_icon")
|
||||
(defparameter *radios* "radios")
|
||||
(defparameter *region* "region")
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@
|
|||
<string>This file was generated by Qt/QMake.</string>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>For connecting to meshtastic radio devices.</string>
|
||||
<key>NSLocationAlwaysUsageDescription</key>
|
||||
<string>To share location.</string>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string>To share location.</string>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
|
|
|
|||
|
|
@ -183,3 +183,4 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ Rectangle {
|
|||
|
||||
Item {
|
||||
id: delegate
|
||||
width: Math.max(text.paintedWidth, rowSender.width + 4 * text.padding) + 2 * text.padding
|
||||
width: Math.max(text.paintedWidth, rowSender.width + 4 * text.padding) + 2 * text.padding + 4
|
||||
height: visible ? (text.contentHeight + 2 * text.padding + sender.contentHeight + 8) : 0
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -95,7 +95,7 @@ Rectangle {
|
|||
AnimatedImage {
|
||||
id: semaphore
|
||||
playing: false
|
||||
y: 3
|
||||
y: 4
|
||||
width: 8
|
||||
height: width
|
||||
source: "../img/semaphore.gif"
|
||||
|
|
@ -105,7 +105,7 @@ Rectangle {
|
|||
|
||||
Text {
|
||||
id: sender
|
||||
font.pixelSize: 11
|
||||
font.pixelSize: 12
|
||||
font.family: fontText.name
|
||||
color: "#8B0000"
|
||||
text: model.senderName ? model.senderName : model.sender
|
||||
|
|
@ -116,7 +116,7 @@ Rectangle {
|
|||
id: timestamp
|
||||
x: delegate.width - contentWidth - text.padding
|
||||
y: text.padding
|
||||
font.pixelSize: 11
|
||||
font.pixelSize: 12
|
||||
font.family: fontText.name
|
||||
color: "#505050"
|
||||
text: model.hour
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ Rectangle {
|
|||
|
||||
// hack to define all model key _types_
|
||||
ListElement {
|
||||
name: ""; hwModel: ""; batteryLevel: 0; current: false
|
||||
name: ""; ini: false; hwModel: ""; batteryLevel: 0; current: false
|
||||
}
|
||||
|
||||
function addRadio(radio) {
|
||||
|
|
@ -66,7 +66,7 @@ Rectangle {
|
|||
id: delegate
|
||||
width: Math.min(265, view.width)
|
||||
height: 35
|
||||
color: (index === view.currentIndex) ? "firebrick" : "steelblue"
|
||||
color: (index === view.currentIndex) ? "firebrick" : (model.ini ? "#808080" : "steelblue")
|
||||
radius: height / 2
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -101,6 +101,7 @@ Rectangle {
|
|||
anchors.right: parent.right
|
||||
anchors.rightMargin: 14
|
||||
level: model.batteryLevel
|
||||
visible: !model.ini
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
|
|
|
|||
Binary file not shown.
|
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 22 KiB |
|
|
@ -1,6 +1,7 @@
|
|||
import QtQuick 2.15
|
||||
import QtQuick.Controls 2.15
|
||||
import QtQuick.Window 2.15
|
||||
import QtPositioning 5.15
|
||||
import "ext/" as Ext
|
||||
|
||||
Item {
|
||||
|
|
@ -74,6 +75,31 @@ Item {
|
|||
playing: false
|
||||
}
|
||||
|
||||
// GPS
|
||||
|
||||
PositionSource {
|
||||
objectName: "position_source"
|
||||
updateInterval: 2000
|
||||
active: false
|
||||
|
||||
property double lat: 0
|
||||
property double lon: 0
|
||||
property string time: "" // no 'long' in JS
|
||||
|
||||
onPositionChanged: {
|
||||
if (position.latitudeValid && position.longitudeValid) {
|
||||
var coor = position.coordinate;
|
||||
lat = coor.latitude
|
||||
lon = coor.longitude
|
||||
time = coor.timestamp ? String(coor.timestamp) : ""
|
||||
}
|
||||
}
|
||||
|
||||
function lastLocation() {
|
||||
return [lat, lon, time]
|
||||
}
|
||||
}
|
||||
|
||||
Ext.Toast {}
|
||||
|
||||
FontLoader { id: fontText; source: "fonts/Ubuntu.ttf" }
|
||||
|
|
|
|||
1181
examples/meshtastic/qt-location-hack/QtActivity.java
Normal file
1181
examples/meshtastic/qt-location-hack/QtActivity.java
Normal file
File diff suppressed because it is too large
Load diff
59
examples/meshtastic/qt-location-hack/diff.java
Normal file
59
examples/meshtastic/qt-location-hack/diff.java
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
64a65,70
|
||||
> // for hack
|
||||
> import android.location.LocationManager;
|
||||
> import android.location.LocationListener;
|
||||
> import android.location.LocationRequest;
|
||||
> import android.util.Log;
|
||||
>
|
||||
66a73,123
|
||||
> // hack
|
||||
> public double _position_lat_ = 0.0;
|
||||
> public double _position_lon_ = 0.0;
|
||||
> public double _position_alt_ = 0.0;
|
||||
> public long _position_time_ = 0;
|
||||
> private static final String LQML = "[LQML]";
|
||||
>
|
||||
> // hack
|
||||
> public void iniLocation()
|
||||
> {
|
||||
> LocationListener mLocationListenerGPS = new LocationListener() {
|
||||
> @Override
|
||||
> public void onLocationChanged(android.location.Location location) {
|
||||
> _position_lat_ = location.getLatitude();
|
||||
> _position_lon_ = location.getLongitude();
|
||||
> _position_alt_ = location.getAltitude();
|
||||
> _position_time_ = location.getTime();
|
||||
> //String msg = "lat: " + latitude + " lon: " + longitude;
|
||||
> //Log.d(LQML, msg);
|
||||
> }
|
||||
>
|
||||
> @Override
|
||||
> public void onStatusChanged(String provider, int status, Bundle extras) {
|
||||
> //Log.d(LQML, "GPS: onStatusChanged");
|
||||
> }
|
||||
>
|
||||
> @Override
|
||||
> public void onProviderEnabled(String provider) {
|
||||
> //Log.d(LQML, "GPS: onProviderEnabled");
|
||||
> }
|
||||
>
|
||||
> @Override
|
||||
> public void onProviderDisabled(String provider) {
|
||||
> //Log.d(LQML, "GPS: onProviderDisabled");
|
||||
> }
|
||||
> };
|
||||
>
|
||||
> try {
|
||||
> //Log.d(LQML, "ini GPS location...");
|
||||
> LocationManager mLocationManager = (LocationManager)getSystemService(Context.LOCATION_SERVICE);
|
||||
> mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER,
|
||||
> 2000, // 2 secs
|
||||
> 100, // HIGH_ACCURACY
|
||||
> mLocationListenerGPS);
|
||||
> //Log.d(LQML, "ini GPS location OK");
|
||||
> }
|
||||
> catch (Exception e) {
|
||||
> Log.e(LQML, Log.getStackTraceString(e));
|
||||
> }
|
||||
> }
|
||||
>
|
||||
25
examples/meshtastic/qt-location-hack/readme.md
Normal file
25
examples/meshtastic/qt-location-hack/readme.md
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
|
||||
**android only**
|
||||
|
||||
|
||||
Why
|
||||
---
|
||||
|
||||
This is needed on android because a mobile device which doesn't move only
|
||||
receives one (or maybe two) GPS updates before stopping the updates.
|
||||
|
||||
Since we have a long startup time with this app, we would completely lose
|
||||
the first GPS updates using the convenient QML wrapper `PositionSource`,
|
||||
hence not being able to communicate our position to the radio device.
|
||||
|
||||
The only workaround seems to be to directly access the Java `Location`
|
||||
functions.
|
||||
|
||||
|
||||
HowTo
|
||||
-----
|
||||
|
||||
Just copy `QtActivity.java` to path in `path.txt` (for Qt 5.15);
|
||||
alternatively apply patch `git.diff`.
|
||||
|
||||
See also [cpp/qt.cpp](../cpp/qt.cpp).
|
||||
|
|
@ -10,6 +10,8 @@ See also notes in [my-cl-protobufs.asd](my-cl-protobufs.asd).
|
|||
You will also need **uiop** installed under e.g. `~/quicklisp/local-projects/`
|
||||
(see ASDF sources).
|
||||
|
||||
For android please see [qt-location-hack](qt-location-hack/).
|
||||
|
||||
|
||||
|
||||
Prepare
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ If you use more than 1 radio, just switch here to the radio you want to use.
|
|||
Changing a radio will take several seconds, because the initial configuration
|
||||
needs to be repeated.
|
||||
|
||||
Initially, the list will contain gray items, so you can choose another radio
|
||||
if you experience that the connection didn't work, in case the selected radio
|
||||
is currently not available.
|
||||
|
||||
A press-and-hold on the radio icon will restart bluetooth device discovery.
|
||||
This may be useful if you forgot to enable bluetooth before starting the app,
|
||||
or if your radio is not being discovered the first time.
|
||||
|
|
@ -79,6 +83,13 @@ Switching to **Group**, a red circle with the number of unread messages is
|
|||
shown on the right of every person.
|
||||
|
||||
|
||||
GPS position
|
||||
------------
|
||||
|
||||
On mobile, and if the radio doesn't have a GPS module, the location of the
|
||||
phone is sent once (at startup) to the radio.
|
||||
|
||||
|
||||
Tips
|
||||
----
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,10 @@ Info
|
|||
Please note: this is **WIP!**
|
||||
|
||||
Currently it can be used to send direct messages between any number of radios.
|
||||
Eventually it will (hopefully) catch up with the official app versions.
|
||||
|
||||
It's basically meant to be used in an emergency situation, where internet is
|
||||
not available, in order to communicate with simple text messages. This kind of
|
||||
mesh network is limited to about 70 nodes/radios/users to remain reliable.
|
||||
|
||||
|
||||
|
||||
|
|
@ -82,25 +85,6 @@ See also [readme-usage](readme-usage.md).
|
|||
|
||||
|
||||
|
||||
TODO / ideas
|
||||
------------
|
||||
|
||||
(1) Since this uses Lisp, there's the possibility to integrate a programmable
|
||||
interface, where users can define their own functions and extend the UI.
|
||||
|
||||
Think of simple scripts, which send protobufs to the radio and process the
|
||||
received data, while having access to all the variables and functions of
|
||||
the app itself.
|
||||
|
||||
Those extensions could also be shared among users.
|
||||
|
||||
(2) Regarding encryption: since I don't like QR codes for sharing a channel,
|
||||
there's the possibility to just share a made up phrase which can easily be
|
||||
remembered, and use the letters of the phrase as encryption key, so people can
|
||||
share their channel in a simple way.
|
||||
|
||||
|
||||
|
||||
Run
|
||||
---
|
||||
```
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue