example 'meshtastic', add iOS version, background ini, startup animation

This commit is contained in:
pls.153 2023-06-06 11:37:12 +02:00
parent 1f72af5440
commit 0446628830
25 changed files with 181 additions and 111 deletions

2
examples/meshtastic/.gitignore vendored Normal file
View file

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

View file

@ -30,7 +30,7 @@ win32: PRE_TARGETDEPS = tmp/app.lib
QT += quick qml bluetooth
TEMPLATE = app
CONFIG += c++17 no_keywords release
DEFINES += DESKTOP_APP INI_LISP INI_ECL_CONTRIB QT_EXTENSION
DEFINES += DESKTOP_APP BACKGROUND_INI_LISP INI_ECL_CONTRIB QT_EXTENSION
INCLUDEPATH = /usr/local/include
ECL_VERSION = $$lower($$system(ecl -v))
ECL_VERSION = $$replace(ECL_VERSION, " ", "-")
@ -87,10 +87,10 @@ ios {
LIBS += -L../../../platforms/ios/lib
QMAKE_INFO_PLIST = platforms/ios/Info.plist
QMAKE_ASSET_CATALOGS += platforms/ios/Assets.xcassets
#QMAKE_ASSET_CATALOGS += platforms/ios/Assets.xcassets
launch.files = platforms/ios/designable.storyboard platforms/img/logo.png
QMAKE_BUNDLE_DATA += launch
#launch.files = platforms/ios/designable.storyboard platforms/img/logo.png
#QMAKE_BUNDLE_DATA += launch
}
32bit {

View file

@ -17,8 +17,6 @@ BLE::BLE(const QBluetoothUuid& uuid) : mainServiceUuid(uuid) {
connect(discoveryAgent, QOverload<QBluetoothDeviceDiscoveryAgent::Error>::of(&QBluetoothDeviceDiscoveryAgent::error),
this, &BLE::deviceScanError);
connect(discoveryAgent, &QBluetoothDeviceDiscoveryAgent::finished, this, &BLE::deviceScanFinished);
QTimer::singleShot(0, this, &BLE::startDeviceDiscovery);
}
void BLE::startDeviceDiscovery() {
@ -60,8 +58,7 @@ void BLE::scanServices() {
if (controller && (previousAddress != currentDevice.address())) {
Q_EMIT deviceDisconnecting();
controller->disconnectFromDevice();
delete controller;
controller = nullptr;
delete controller; controller = nullptr;
}
if (!controller) {

View file

@ -35,10 +35,11 @@ Q_SIGNALS:
void mainServiceReady();
void deviceDisconnecting();
/*** </INTERFACE> *********************************************************/
public Q_SLOTS:
void startDeviceDiscovery();
/*** </INTERFACE> *********************************************************/
void scanServices();
void connectToService(const QString&);
void disconnectFromDevice();

View file

@ -16,6 +16,11 @@ QT::QT() : QObject() {
ble = new BLE_ME;
}
QVariant QT::startDeviceDiscovery() {
ble->startDeviceDiscovery();
return QVariant();
}
QVariant QT::read2() {
ble->read();
return QVariant();

View file

@ -19,6 +19,7 @@ class QT : public QObject {
public:
// BLE_ME
Q_INVOKABLE QVariant startDeviceDiscovery();
Q_INVOKABLE QVariant read2();
Q_INVOKABLE QVariant write2(const QVariant&);

View file

@ -2,8 +2,9 @@
(defun ini ()
(qt:ini)
(qt:start-device-discovery qt:*ble*)
(msg:load-messages)
(q> |visible| ui:*hour-glass* nil) ; shown during Lisp startup
(q> |playing| ui:*busy* t)) ; shown during BLE setup
(q> |playing| ui:*loading* nil) ; shown during Lisp startup
(q> |playing| ui:*busy* t)) ; shown during BLE setup
(qlater 'ini)

View file

@ -7,19 +7,19 @@
(defun add-message (message &optional loading)
"Adds passed MESSAGE (a PLIST) to both the QML item model and *MESSAGES*.
The model keys are:
:m-text :m-sender :m-timestamp :m-id :m-ack-state"
:text :sender :me :timestamp :mid :ack-state"
(qjs |addMessage| ui:*messages* message)
(unless loading
(push message *messages*)
(qlater 'save-messages)))
(defun change-state (state id)
(defun change-state (state mid)
(let ((i-state (position state *states*)))
(qjs |changeState| ui:*messages*
i-state id)
i-state mid)
(dolist (msg *messages*)
(when (eql (getf msg :m-id) id) ; EQL: might be NIL
(setf (getf msg :m-ack-state) i-state)
(when (eql (getf msg :mid) mid) ; EQL: might be NIL
(setf (getf msg :ack-state) i-state)
(return))))
(qlater 'save-messages))
@ -31,7 +31,7 @@
(with-open-file (s *file*)
(setf *messages* (read s)))
(dolist (msg (reverse *messages*))
(setf *message-id* (max (or (getf msg :m-id) 0)
(setf *message-id* (max (or (getf msg :mid) 0)
*message-id*))
(add-message msg t))))

View file

@ -3,6 +3,7 @@
(:export
#:*ble*
#:ini
#:start-device-discovery
#:read*
#:write*))

View file

@ -39,8 +39,6 @@
(values))
(defun start-config ()
#+android
(ensure-permissions :access-coarse-location) ; needed for BLE
(when *ready*
(incf *config-id*)
(send-to-radio
@ -60,11 +58,12 @@
:portnum :text-message-app
:payload (babel:string-to-octets text)))))
(msg:add-message
(list :m-text text
:m-sender (me:short-name (me:user *my-node-info*))
:m-timestamp (timestamp-to-string)
:m-id msg:*message-id*
:m-ack-state (position :not-received msg:*states*))))
(list :text text
:sender (me:short-name (me:user *my-node-info*))
:me t
:timestamp (timestamp-to-string)
:mid msg:*message-id*
:ack-state (position :not-received msg:*states*))))
(defun read-radio ()
"Triggers a read on the radio. Will call RECEIVED-FROM-RADIO on success."
@ -89,9 +88,10 @@
(push from-radio *received*)))
(values))
(defun receiving-done ()
(defun receiving-done () ; called from Qt
(setf *reading* nil)
(process-received))
(process-received)
(values))
(defun node-to-name (num)
(dolist (info *node-infos*)
@ -115,10 +115,10 @@
;; text-message
(:text-message-app
(msg:add-message
(list :m-text (babel:octets-to-string payload)
:m-sender (node-to-name (me:from packet))
:m-timestamp (timestamp-to-string))))
;; for :m-ack-state (acknowledgement state)
(list :text (babel:octets-to-string payload)
:sender (node-to-name (me:from packet))
:timestamp (timestamp-to-string))))
;; for :ack-state (acknowledgement state)
(:routing-app
(msg:change-state (case (me:routing.error-reason
(pr:deserialize-from-bytes 'me:routing payload))
@ -148,8 +148,6 @@
((me:from-radio.has-config-complete-id struct)
(when (= *config-id* (me:config-complete-id struct))
(qlater 'config-device)
(q> |myName| ui:*view*
(me:short-name (me:user *my-node-info*)))
(q> |playing| ui:*busy* nil)
(qlog :config-complete *config-id*)))))
(setf *received* nil))

View file

@ -4,14 +4,14 @@
(:use :cl)
(:export
#:*busy*
#:*hour-glass*
#:*loading*
#:*messages*
#:*view*))
(in-package :ui)
(defparameter *busy* "busy")
(defparameter *hour-glass* "hour_glass")
(defparameter *messages* "messages")
(defparameter *view* "view")
(defparameter *busy* "busy")
(defparameter *loading* "loading")
(defparameter *messages* "messages")
(defparameter *view* "view")

View file

@ -91,35 +91,3 @@
(delete-file to*))
(rename-file from to))
;;; build 'cl-protobufs.fas' (slow on mobile, will be loaded in background)
#|
#-mobile
(asdf:make-build "my-cl-protobufs"
:monolithic t
:type :fasl
:move-here (cc *current* "build/tmp/"))
#+mobile
(progn
(pushnew :interpreter *features*)
(defvar *asdf-system* "my-cl-protobufs")
(defvar *ql-libs* (cc *current* "ql-libs.lisp"))
(defvar *build-type* :fasl)
(defvar *library-path* (format nil "~Abuild-~A/tmp/"
*current*
#+android "android"
#+ios "ios"))
(load "platforms/shared/make"))
;;; rename lib
(let* ((from #-mobile (cc *current* "build/tmp/my-cl-protobufs--all-systems.fasb")
#+mobile (cc *library-path* "my-cl-protobufs--all-systems.fasb"))
(to "cl-protobufs.fas")
(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,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundleExecutable</key>
<string>${EXECUTABLE_NAME}</string>
<key>CFBundleIconFile</key>
<string>${ASSETCATALOG_COMPILER_APPICON_NAME}</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleName</key>
<string>${PRODUCT_NAME}</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>${QMAKE_SHORT_VERSION}</string>
<key>CFBundleSignature</key>
<string>${QMAKE_PKGINFO_TYPEINFO}</string>
<key>CFBundleVersion</key>
<string>${QMAKE_FULL_VERSION}</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>${IPHONEOS_DEPLOYMENT_TARGET}</string>
<key>NOTE</key>
<string>This file was generated by Qt/QMake.</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>For connecting to meshtastic radio devices.</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

View file

@ -16,7 +16,7 @@ Item {
Rectangle {
anchors.fill: parent
color: "#e5d8bd"
color: loading.visible ? "#1974d3" : "#e5d8bd"
}
ListView {
@ -28,27 +28,32 @@ Item {
spacing: 3
delegate: messageDelegate
model: messages
property string myName
}
ListModel {
id: messages
objectName: "messages"
// hack to define all model key _types_
ListElement {
text: ""; sender: ""; me: true; timestamp: ""; mid: 0; ackState: 0
}
function addMessage(message) {
append(message)
view.positionViewAtEnd()
}
function changeState(state, id) {
function changeState(state, mid) {
for (var i = count - 1; i >= 0; i--) {
if (get(i).mId === id) {
setProperty(i, "mAckState", state)
if (get(i).mid === mid) {
setProperty(i, "ackState", state)
break
}
}
}
Component.onCompleted: remove(0) // see hack above
}
Component {
@ -61,7 +66,7 @@ Item {
Rectangle {
anchors.fill: parent
color: (mSender === view.myName) ? "#f2f2f2" : "#ffffcc"
color: model.me ? "#f2f2f2" : "#ffffcc"
radius: 12
border.width: 0
border.color: "#dc1128"
@ -78,8 +83,8 @@ Item {
width: 8
height: width
source: "img/semaphore.gif"
currentFrame: mAckState
visible: (sender.text === view.myName)
currentFrame: model.ackState
visible: model.me
}
Text {
@ -88,7 +93,7 @@ Item {
font.bold: true
font.family: fontMono.name
color: "#8B0000"
text: mSender
text: model.sender
}
}
@ -99,7 +104,7 @@ Item {
font.pixelSize: 10
font.family: fontText.name
color: "#505050"
text: mTimestamp
text: model.timestamp
}
Text {
@ -111,7 +116,7 @@ Item {
font.pixelSize: 18
font.family: fontText.name
color: "#303030"
text: mText
text: model.text
}
}
}
@ -159,28 +164,37 @@ Item {
}
}
// busy image / animation
Item { // shown while loading app (slow...)
// shown while loading app (may take a while)
Item {
visible: loading.visible
anchors.fill: parent
objectName: "hour_glass"
Image {
AnimatedImage {
id: loading
objectName: "loading"
anchors.centerIn: parent
source: "img/busy.png"
source: "img/busy.webp"
visible: playing
playing: true
}
Text {
width: parent.width
anchors.bottom: parent.bottom
anchors.bottomMargin: main.height / 4
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 20
text: qsTr("Loading app...\n(make take a while)")
id: iniCount
anchors.centerIn: parent
color: "white"
font.family: fontText.name
font.pixelSize: 22
}
Timer {
running: loading.playing
interval: 4500
repeat: true
onTriggered: iniCount.text = Number(iniCount.text) + 1
}
}
AnimatedImage { // shown during config
AnimatedImage {
objectName: "busy"
anchors.centerIn: parent
width: 42

View file

@ -2,7 +2,7 @@
Info
----
Please note: this is **WIP!**. It's only a 'proof-of-concept' version.
Please note: this is **WIP!** It's only a 'proof-of-concept' version.
Eventually it will (hopefully) catch up with the official app versions.
@ -22,10 +22,15 @@ some small adaptions and included all generated proto Lisp files in order to be
independent.
Unfortunately cl-protobufs loads very slowly on mobile (and conses hugely
during startup). On an older phone and a cold startup this may take up to 30
seconds. On newer phones and warm startup it should 'only' take around 10
during startup). On an older phone and a cold startup this may take more than
20 seconds. On newer phones and warm startup it should 'only' take around 10
seconds (which seems acceptable).
For the above reason, an animation is shown while loading the app, together
with a counter. For this to work, the app is loaded in the background (that is,
in a separate thread). You'll need to rebuild the lqml library for this to
work.
You will see a json output of all data sent/received. It simply uses the
`print-json` convenience function from cl-protobufs.
@ -34,24 +39,29 @@ You will see a json output of all data sent/received. It simply uses the
Tested
------
Currently tested on Linux, macOS, android. The macOS version shows an exception
Tested on Linux, macOS, android, iOS. The macOS version shows an ECL exception
during BLE ini, but works nevertheless.
The iOS version doesn't currently work yet (WIP).
How to use cl-meshtastic
------------------------
You currently need 2 meshtastic radio devices, both should be running before
you start the app. Both bluetooth and location needs to be enabled (coarse
location permission is required on android for BLE to work).
You currently need exactly 2 meshtastic radio devices, both should be running
before you start the app. Both bluetooth and location needs to be enabled
(coarse location permission is required on android for BLE to work).
Pairing might sometimes require some playing around. If it asks for a PIN and
your device doesn't have a display (like the RAK starter kit), just use
`123456`.
On iOS pairing is not needed beforehand, because it will ask for pairing and
PIN during BLE ini.
If your android phone says "no BLE devices found" (see logcat output), you
might need to uninstall an eventual app which used the LoRa radio before, and
restart the phone.
On Linux you might need to restart the bluetooth service if you want to pair
a different device (after already pairing a first one).

View file

@ -48,7 +48,6 @@ Qt Creator.
TODO
----
* add (very simple) [meshtastic](https://meshtastic.org) app example
* port to CMake (?)

BIN
screenshots/meshtastic.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

View file

@ -23,11 +23,11 @@
QT_BEGIN_NAMESPACE
void iniCLFunctions() {
cl_object qml(STRING("QML"));
if (cl_find_package(qml) == ECL_NIL) {
cl_make_package(1, qml);
cl_object l_qml(STRING("QML"));
if (cl_find_package(l_qml) == ECL_NIL) {
cl_make_package(1, l_qml);
}
si_select_package(qml);
si_select_package(l_qml);
DEFUN ("clipboard-text", clipboard_text, 0)
DEFUN ("%disable-clipboard-menu", disable_clipboard_menu2, 1)
DEFUN ("%ensure-permissions", ensure_permissions2, 1)

View file

@ -7,7 +7,7 @@
#include <QQuickView>
#include <QDebug>
const char LQML::version[] = "23.5.2"; // May 2023
const char LQML::version[] = "23.6.1"; // June 2023
extern "C" void ini_LQML(cl_object);

View file

@ -26,7 +26,7 @@
#define ADD_MACOS_BUNDLE_IMPORT_PATH
#endif
#ifdef INI_LISP
#if (defined INI_LISP) || (defined BACKGROUND_INI_LISP)
extern "C" void ini_app(cl_object);
#endif
@ -54,6 +54,13 @@ int catch_all_qexec() {
return ret;
}
cl_object do_ini_app() {
#ifdef BACKGROUND_INI_LISP
ecl_init_module(NULL, ini_app);
#endif
return Cnil;
}
int main(int argc, char* argv[]) {
#if QT_VERSION < 0x060000
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
@ -93,6 +100,10 @@ int main(int argc, char* argv[]) {
exit(0);
}
cl_object l_qml(STRING("QML"));
si_select_package(l_qml);
DEFUN ("do-ini-app", do_ini_app, 0)
QTranslator translator;
if ((QFile::exists("i18n") && translator.load(QLocale(), QString(), QString(), "i18n"))
|| translator.load(QLocale(), QString(), QString(), ":/i18n")) {
@ -165,6 +176,10 @@ int main(int argc, char* argv[]) {
ecl_init_module(NULL, ini_app);
#endif
#ifdef BACKGROUND_INI_LISP
LQML::eval("(qml::background-ini)", true); // see 'ini.liso'
#endif
#ifdef NO_QT_RESTART
bool qtRestart = false;
#else

View file

@ -1,11 +1,21 @@
#pragma once
#undef SLOT
#include <ecl/ecl.h>
#include <QQmlEngine>
#include <QGuiApplication>
#include <QInputMethodEvent>
QT_BEGIN_NAMESPACE
#define STRING(s) ecl_make_constant_base_string(s, -1)
#define DEFUN(name, c_name, num_args) \
ecl_def_c_function(ecl_read_from_cstring(name), (cl_objectfn_fixed)c_name, num_args);
cl_object do_ini_app (); // for background ini
class Engine : public QQmlEngine {
Q_OBJECT
public:

View file

@ -338,6 +338,12 @@
#-mobile
:mobile-only)
;;; background ini for big apps, so we can show animation during ini
(defun background-ini ()
;; DO-INI-APP is defined in main.cpp
(mp:process-run-function :app-ini 'do-ini-app))
;;; alias
(defmacro alias (s1 s2)

View file

@ -6,6 +6,7 @@
#:*engine*
#:*root-item*
#:*caller*
#:background-ini
#:clipboard-text
#:copy-all-asset-files
#:define-qt-wrappers