mirror of
https://gitlab.com/eql/lqml.git
synced 2026-01-03 07:42:27 -08:00
example 'meshtastic': inform of text length limit, add search function
This commit is contained in:
parent
f602dd4ebd
commit
3afa583c73
18 changed files with 292 additions and 87 deletions
|
|
@ -47,6 +47,7 @@ void BLE::deviceScanFinished() {
|
|||
} else {
|
||||
qDebug() << "device scan done";
|
||||
}
|
||||
|
||||
QTimer::singleShot(0, this, &BLE::scanServices);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ void BLE_ME::searchCharacteristics() {
|
|||
}
|
||||
|
||||
if (toRadio.isValid() && fromRadio.isValid() && fromNum.isValid()) {
|
||||
ecl_fun("lora:set-ready");
|
||||
ecl_fun("lora:set-ready", currentDevice.name().right(4));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -151,7 +151,7 @@ void BLE_ME::disconnecting() {
|
|||
// disable notifications
|
||||
mainService->writeDescriptor(notifications, QByteArray::fromHex("0000"));
|
||||
}
|
||||
ecl_fun("lora:set-ready", false);
|
||||
ecl_fun("lora:set-ready", "-", false);
|
||||
delete mainService; mainService = nullptr;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@
|
|||
(qjs |addPerson| ui:*group* person))
|
||||
|
||||
(defun clear ()
|
||||
(setf lora:*schedule-clear* nil)
|
||||
(q! |clear| ui:*group*))
|
||||
|
||||
(defun radio-names ()
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@
|
|||
(defvar *ready* nil)
|
||||
(defvar *reading* nil)
|
||||
(defvar *received* nil)
|
||||
(defvar *schedule-clear* nil)
|
||||
|
||||
(defun to-bytes (list)
|
||||
(make-array (length list)
|
||||
|
|
@ -36,20 +37,25 @@
|
|||
:initial-contents list))
|
||||
|
||||
(defun start-device-discovery (&optional (name ""))
|
||||
(setf radios:*schedule-clear* t)
|
||||
(setf *schedule-clear* t)
|
||||
(setf *ble-names* nil)
|
||||
(qt:start-device-discovery qt:*cpp* name)
|
||||
(q> |playing| ui:*busy* t))
|
||||
|
||||
(defun start-config ()
|
||||
(when *ready*
|
||||
(setf *schedule-clear* t)
|
||||
(setf *channels* nil
|
||||
*node-infos* nil)
|
||||
(incf *config-id*)
|
||||
(send-to-radio
|
||||
(me:make-to-radio :want-config-id *config-id*))))
|
||||
(me:make-to-radio :want-config-id *config-id*))
|
||||
(q> |playing| ui:*busy* t)))
|
||||
|
||||
(defun set-ready (&optional (ready t)) ; called from Qt
|
||||
(defun set-ready (name &optional (ready t)) ; called from Qt
|
||||
(setf *ready* ready)
|
||||
(when ready
|
||||
(app:toast (x:cc (tr "radio") ": " name) 2)
|
||||
(qlater 'start-config))
|
||||
(values))
|
||||
|
||||
|
|
@ -169,7 +175,7 @@
|
|||
(setf *my-node-info* info)
|
||||
(setf *node-infos*
|
||||
(nconc *node-infos* (list info))))
|
||||
(when radios:*schedule-clear*
|
||||
(when *schedule-clear*
|
||||
(radios:clear)
|
||||
(group:clear))
|
||||
(let ((name (me:short-name (me:user info)))
|
||||
|
|
|
|||
|
|
@ -15,10 +15,21 @@
|
|||
(ensure-permissions :bluetooth-scan :bluetooth-connect) ; android >= 12
|
||||
(lora:start-device-discovery (or (setting :device) "")))
|
||||
|
||||
(defun view-index-changed (index)
|
||||
(when (and (= 1 index) ; 'Messages'
|
||||
(defun view-index-changed (index) ; called from QML
|
||||
(when (and (= 1 index)
|
||||
(not (app:setting :latest-receiver)))
|
||||
(q> |currentIndex| ui:*main-view* 0))) ; 'Group'
|
||||
(q> |currentIndex| ui:*main-view* 0))
|
||||
(q> |visible| ui:*find* (= 1 index))
|
||||
(values))
|
||||
|
||||
(defun icon-press-and-hold (name) ; called from QML
|
||||
(cond ((string= ui:*radio-icon* name)
|
||||
;; force update devices
|
||||
(lora:start-device-discovery (or (setting :device) "")))
|
||||
((string= ui:*group-icon* name)
|
||||
;; force update nodes
|
||||
(lora:start-config)))
|
||||
(values))
|
||||
|
||||
;;; settings
|
||||
|
||||
|
|
@ -62,7 +73,7 @@
|
|||
|
||||
;;; toast
|
||||
|
||||
(defun toast (message)
|
||||
(qjs |message| ui:*toast* message))
|
||||
(defun toast (message &optional (seconds 3))
|
||||
(qjs |message| ui:*toast* message seconds))
|
||||
|
||||
(qlater 'ini)
|
||||
|
|
|
|||
|
|
@ -45,9 +45,64 @@
|
|||
(q! |clear| ui:*messages*)
|
||||
(dolist (message (db:load-messages (parse-integer x:it :radix 16)))
|
||||
(add-message (read-from-string message) t))
|
||||
(q! |positionViewAtEnd| ui:*message-view*)))
|
||||
(qsingle-shot 100 (lambda () (q! |positionViewAtEnd| ui:*message-view*)))))
|
||||
|
||||
(defun receiver-changed ()
|
||||
(qsleep 0.1)
|
||||
(q> |currentIndex| ui:*main-view* 1) ; 'Messages'
|
||||
(show-messages))
|
||||
|
||||
(defun check-utf8-length (text) ; called from 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."
|
||||
(let ((len (length (qto-utf8 text)))
|
||||
(too-long (q< |tooLong| ui:*edit*)))
|
||||
(cond ((and (not too-long)
|
||||
(> len 234))
|
||||
(q> |tooLong| ui:*edit* t))
|
||||
((and too-long
|
||||
(<= len 234))
|
||||
(q> |tooLong| ui:*edit* nil))))
|
||||
(values))
|
||||
|
||||
(defun message-press-and-hold (text) ; called from QML
|
||||
(set-clipboard-text text)
|
||||
(app:toast (tr "message copied") 2)
|
||||
(values))
|
||||
|
||||
(defun find-clicked ()
|
||||
(let ((show (not (q< |visible| ui:*find-text*))))
|
||||
(q> |visible| ui:*find-text* show)
|
||||
(if show
|
||||
(progn
|
||||
(q! |selectAll| ui:*find-text*)
|
||||
(q! |forceActiveFocus| ui:*find-text*))
|
||||
(q! |clear| ui:*find-text*)
|
||||
(clear-find))))
|
||||
|
||||
(defun find-text (text)
|
||||
(unless (x:empty-string text)
|
||||
(qjs |clearFind| ui:*messages*)
|
||||
(qjs |find| ui:*messages* text)))
|
||||
|
||||
(defun clear-find ()
|
||||
(qjs |clearFind| ui:*messages*)
|
||||
(qlater (lambda () (qjs |positionViewAtEnd| ui:*message-view*))))
|
||||
|
||||
(defun highlight-term (text term)
|
||||
"Highlights TERM in red, returns NIL if TERM is not found."
|
||||
(let ((len (length term))
|
||||
found)
|
||||
(with-output-to-string (s)
|
||||
(do ((e (search term text :test 'string-equal)
|
||||
(search term text :test 'string-equal :start2 (+ e len)))
|
||||
(b 0 (+ e len)))
|
||||
((not e) (if found
|
||||
(write-string (subseq text b) s)
|
||||
(return-from highlight-term)))
|
||||
(setf found t)
|
||||
(write-string (subseq text b e) s)
|
||||
(format s "<font color='red'>~A</font>"
|
||||
(subseq text e (+ e len)))))))
|
||||
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
(:use :cl :qml)
|
||||
(:export
|
||||
#:change-setting
|
||||
#:icon-press-and-hold
|
||||
#:ini
|
||||
#:load-settings
|
||||
#:save-settings
|
||||
|
|
@ -26,6 +27,7 @@
|
|||
#:*received*
|
||||
#:*receiver*
|
||||
#:*remote-node*
|
||||
#:*schedule-clear*
|
||||
#:*settings*
|
||||
#:change-receiver
|
||||
#:change-region
|
||||
|
|
@ -68,13 +70,16 @@
|
|||
#:*states*
|
||||
#:add-message
|
||||
#:change-state
|
||||
#:check-utf8-length
|
||||
#:clear-find
|
||||
#:find-text
|
||||
#:message-press-and-hold
|
||||
#:receiver-changed
|
||||
#:show-messages))
|
||||
|
||||
(defpackage :radios
|
||||
(:use :cl :qml)
|
||||
(:export
|
||||
#:*schedule-clear*
|
||||
#:add-radio
|
||||
#:change-radio
|
||||
#:clear))
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
(in-package :radios)
|
||||
|
||||
(defvar *schedule-clear* nil)
|
||||
|
||||
(defun add-radio (radio)
|
||||
"Adds passed RADIO (a PLIST) to QML item model.
|
||||
The model keys are:
|
||||
|
|
@ -9,10 +7,14 @@
|
|||
(qjs |addRadio| ui:*radios* radio))
|
||||
|
||||
(defun clear ()
|
||||
(setf *schedule-clear* nil)
|
||||
(setf lora:*schedule-clear* nil)
|
||||
(q! |clear| ui:*radios*))
|
||||
|
||||
(defun change-radio (name) ; called from QML
|
||||
(qlater (lambda () (lora:start-device-discovery name)))
|
||||
(values))
|
||||
|
||||
(defun reset-configured ()
|
||||
;; TODO: add in UI settings
|
||||
(app:change-setting :configured nil))
|
||||
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@
|
|||
#:*busy*
|
||||
#:*edit*
|
||||
#:*group*
|
||||
#:*group-icon*
|
||||
#:*find*
|
||||
#:*find-text*
|
||||
#:*loading*
|
||||
#:*main-view*
|
||||
#:*messages*
|
||||
#:*message-view*
|
||||
#:*modem*
|
||||
#:*radio-icon*
|
||||
#:*radios*
|
||||
#:*region*
|
||||
#:*toast*
|
||||
|
|
@ -21,11 +25,15 @@
|
|||
(defparameter *busy* "busy")
|
||||
(defparameter *edit* "edit")
|
||||
(defparameter *group* "group")
|
||||
(defparameter *group-icon* "group_icon")
|
||||
(defparameter *find* "find")
|
||||
(defparameter *find-text* "find_text")
|
||||
(defparameter *loading* "loading")
|
||||
(defparameter *main-view* "main_view")
|
||||
(defparameter *messages* "messages")
|
||||
(defparameter *message-view* "message_view")
|
||||
(defparameter *modem* "modem")
|
||||
(defparameter *radio-icon* "radio_icon")
|
||||
(defparameter *radios* "radios")
|
||||
(defparameter *region* "region")
|
||||
(defparameter *toast* "toast")
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ Image {
|
|||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: view.currentIndex = parent.Positioner.index
|
||||
onClicked: swipeView.currentIndex = parent.Positioner.index
|
||||
onPressAndHold: Lisp.call("app:icon-press-and-hold", parent.objectName)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ Item {
|
|||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
Ext.MainIcon {
|
||||
objectName: "group_icon"
|
||||
source: "../img/group.png"
|
||||
|
||||
Rectangle {
|
||||
|
|
@ -33,17 +34,19 @@ Item {
|
|||
}
|
||||
|
||||
Ext.MainIcon {
|
||||
objectName: "message_icon"
|
||||
source: "../img/message.png"
|
||||
}
|
||||
|
||||
Ext.MainIcon {
|
||||
objectName: "radio_icon"
|
||||
source: "../img/radio.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SwipeView {
|
||||
id: view
|
||||
id: swipeView
|
||||
objectName: "main_view"
|
||||
y: header.height
|
||||
width: parent.width
|
||||
|
|
@ -61,8 +64,8 @@ Item {
|
|||
PageIndicator {
|
||||
id: control
|
||||
y: header.height - 12
|
||||
count: view.count
|
||||
currentIndex: view.currentIndex
|
||||
count: swipeView.count
|
||||
currentIndex: swipeView.currentIndex
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
|
||||
delegate: Rectangle {
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ Rectangle {
|
|||
ListView {
|
||||
id: view
|
||||
objectName: "message_view"
|
||||
anchors.topMargin: rectFind.height + 4
|
||||
anchors.fill: parent
|
||||
anchors.bottomMargin: rectEdit.height + 5
|
||||
anchors.bottomMargin: rectEdit.height + 3
|
||||
anchors.margins: 5
|
||||
spacing: 5
|
||||
delegate: messageDelegate
|
||||
model: messages
|
||||
clip: true
|
||||
|
|
@ -23,7 +23,8 @@ Rectangle {
|
|||
|
||||
// hack to define all model key _types_
|
||||
ListElement {
|
||||
receiver: ""; sender: ""; senderName: ""; timestamp: ""; hour: ""; text: ""; mid: ""; ackState: 0; me: true
|
||||
receiver: ""; sender: ""; senderName: ""; timestamp: ""; hour: "";
|
||||
text: ""; text2: ""; mid: ""; ackState: 0; me: true
|
||||
}
|
||||
|
||||
function addMessage(message) { append(message) }
|
||||
|
|
@ -37,19 +38,55 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
function find(term) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
var text = get(i).text
|
||||
var highlighted = Lisp.call("msg:highlight-term", text, term)
|
||||
if (highlighted) {
|
||||
if (get(i).text2 === undefined) {
|
||||
setProperty(i, "text2", text)
|
||||
}
|
||||
setProperty(i, "text", highlighted)
|
||||
}
|
||||
view.itemAtIndex(i).visible = !!highlighted
|
||||
}
|
||||
view.positionViewAtBeginning()
|
||||
}
|
||||
|
||||
function clearFind() {
|
||||
for (var i = 0; i < count; i++) {
|
||||
var text2 = get(i).text2
|
||||
if (text2) {
|
||||
setProperty(i, "text", text2)
|
||||
setProperty(i, "text2", undefined)
|
||||
}
|
||||
view.itemAtIndex(i).visible = true
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: remove(0) // see hack above
|
||||
}
|
||||
|
||||
Component {
|
||||
id: messageDelegate
|
||||
|
||||
Rectangle {
|
||||
Item {
|
||||
id: delegate
|
||||
width: Math.max(text.contentWidth, rowSender.width + 4 * text.padding) + 2 * text.padding
|
||||
height: text.contentHeight + 2 * text.padding + sender.contentHeight
|
||||
width: Math.max(text.paintedWidth, rowSender.width + 4 * text.padding) + 2 * text.padding
|
||||
height: visible ? (text.contentHeight + 2 * text.padding + sender.contentHeight + 8) : 0
|
||||
|
||||
Rectangle {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height - 4
|
||||
color: model.me ? "#f2f2f2" : "#ffffcc"
|
||||
radius: 12
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onPressAndHold: Lisp.call("msg:message-press-and-hold", model.text)
|
||||
}
|
||||
|
||||
Row {
|
||||
id: rowSender
|
||||
padding: text.padding
|
||||
|
|
@ -94,18 +131,50 @@ Rectangle {
|
|||
font.pixelSize: 18
|
||||
font.family: fontText.name
|
||||
color: "#303030"
|
||||
textFormat: Text.StyledText // for 'paintedWidth' to always work
|
||||
text: model.text
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// find text
|
||||
|
||||
TextField {
|
||||
id: findText
|
||||
objectName: "find_text"
|
||||
y: 1
|
||||
width: parent.width
|
||||
height: visible ? (edit.paintedHeight + 14) : 0
|
||||
font.pixelSize: 18
|
||||
font.family: fontText.name
|
||||
selectionColor: "#228ae3"
|
||||
selectedTextColor: "white"
|
||||
placeholderText: qsTr("search")
|
||||
visible: false
|
||||
|
||||
background: Rectangle {
|
||||
id: rectFind
|
||||
color: "white"
|
||||
border.width: 3
|
||||
border.color: findText.focus ? "dodgerblue" : "#c0c0c0"
|
||||
radius: 12
|
||||
}
|
||||
|
||||
onEditingFinished: Lisp.call("msg:find-text", text)
|
||||
}
|
||||
|
||||
// send text
|
||||
|
||||
Rectangle {
|
||||
id: rectEdit
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 1
|
||||
width: parent.width
|
||||
height: edit.paintedHeight + 14
|
||||
color: "white"
|
||||
border.width: 3
|
||||
border.color: edit.focus ? "dodgerblue" : "#c0c0c0"
|
||||
border.color: edit.focus ? (edit.tooLong ? "#ff5f57" : "dodgerblue") : "#c0c0c0"
|
||||
radius: 12
|
||||
|
||||
TextArea {
|
||||
|
|
@ -120,6 +189,10 @@ Rectangle {
|
|||
wrapMode: TextEdit.Wrap
|
||||
textMargin: 0
|
||||
placeholderText: qsTr("message")
|
||||
|
||||
property bool tooLong: false
|
||||
|
||||
onLengthChanged: if (length > 150) Lisp.call("msg:check-utf8-length", text)
|
||||
}
|
||||
|
||||
Image {
|
||||
|
|
@ -129,7 +202,7 @@ Rectangle {
|
|||
width: 38
|
||||
height: width
|
||||
source: "../img/send.png"
|
||||
visible: edit.focus
|
||||
visible: edit.focus && !edit.tooLong
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ Rectangle {
|
|||
anchors.fill: parent
|
||||
|
||||
onClicked: {
|
||||
if (index > 0) { // current radio is 0
|
||||
view.currentIndex = index
|
||||
Lisp.call("radios:change-radio", model.name)
|
||||
}
|
||||
|
|
@ -114,3 +115,4 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,11 +11,12 @@ Rectangle {
|
|||
color: "#303030"
|
||||
border.width: 2
|
||||
border.color: "white"
|
||||
radius: height / 2
|
||||
radius: Math.min(25, height / 2)
|
||||
opacity: 0
|
||||
visible: false
|
||||
|
||||
function message(text) { // called from Lisp
|
||||
function message(text, seconds) { // called from Lisp
|
||||
pause.duration = 1000 * seconds
|
||||
toast.visible = true
|
||||
msg.text = text
|
||||
anim.start()
|
||||
|
|
@ -46,6 +47,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
PauseAnimation {
|
||||
id: pause
|
||||
duration: 3000
|
||||
}
|
||||
|
||||
|
|
|
|||
BIN
examples/meshtastic/qml/img/find.png
Normal file
BIN
examples/meshtastic/qml/img/find.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 7 KiB |
|
|
@ -6,12 +6,12 @@ import "ext/" as Ext
|
|||
Item {
|
||||
id: main
|
||||
objectName: "main"
|
||||
width: 300
|
||||
height: 500
|
||||
width: 350
|
||||
height: 550
|
||||
|
||||
property double headerHeight: 48
|
||||
|
||||
Ext.MainView {}
|
||||
Ext.MainView { id: view }
|
||||
|
||||
Image {
|
||||
source: "img/logo-128.png"
|
||||
|
|
@ -20,11 +20,16 @@ Item {
|
|||
}
|
||||
|
||||
Image {
|
||||
source: "img/settings.png"
|
||||
objectName: "find"
|
||||
source: "img/find.png"
|
||||
width: headerHeight
|
||||
height: width
|
||||
anchors.right: parent.right
|
||||
visible: false // currently not needed
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: Lisp.call("msg:find-clicked")
|
||||
}
|
||||
}
|
||||
|
||||
// shown while loading app (may take a while)
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ Messages
|
|||
The initial view shows the messages between you and a chosen person. You choose
|
||||
the desired person in the **Group** view (swipe to the left).
|
||||
|
||||
To copy a message to the clipboard, simply press-and-hold it.
|
||||
|
||||
|
||||
Group
|
||||
-----
|
||||
|
|
@ -62,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.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
Unread messages
|
||||
---------------
|
||||
|
|
@ -72,3 +78,23 @@ inform you of new, unread messages from another user.
|
|||
Switching to **Group**, a red circle with the number of unread messages is
|
||||
shown on the right of every person.
|
||||
|
||||
|
||||
Tips
|
||||
----
|
||||
|
||||
If (for some reason) you want to redo the bluetooth discovery of your radio(s),
|
||||
just press-and-hold on the **Radios** icon.
|
||||
|
||||
If (for some reason) you want to receive again the mesh node configuration from
|
||||
your radio, just press-and-hold on the **Group** icon.
|
||||
|
||||
Both of above is meant to avoid app restart.
|
||||
|
||||
|
||||
Hacker tips
|
||||
-----------
|
||||
|
||||
If it occurs that a RAK device goes into an undefined state and doesn't seem
|
||||
to work anymore, you can try to reset the flash memory from an arduino IDE,
|
||||
see the RAK github and file `reset-flash.ino`. I successfully recovered a
|
||||
RAK device with corrupted memory using this method.
|
||||
|
|
|
|||
|
|
@ -51,6 +51,8 @@ Tested
|
|||
Tested on Linux, macOS, android, iOS. The macOS version shows an ECL exception
|
||||
during BLE ini, but works nevertheless.
|
||||
|
||||
It should also work on Windows >= 10, but this is not tested yet.
|
||||
|
||||
Since this is WIP, it may currently not work on all platforms (e.g. mobile).
|
||||
|
||||
|
||||
|
|
@ -67,9 +69,11 @@ Pairing of your LoRa radios is generally not needed beforehand, the app will
|
|||
ask for pairing/PIN during BLE ini. If your device doesn't have a display, use
|
||||
`123456` as your PIN.
|
||||
|
||||
It may occur that the devices are sometimes not found. For me it worked again
|
||||
after unpairing the devices. Remember to unpair them from all computers/mobile
|
||||
devices.
|
||||
It may occur that the devices are sometimes not found; in those cases
|
||||
|
||||
* try to turn bluetooth off and on again, and/or:
|
||||
* try to reboot your radios, and/or:
|
||||
* try to unpair your radios from all computers/devices
|
||||
|
||||
A generic bluetooth app like **nRF Connect** may help in order to see if the
|
||||
devices themselves work and are able to connect.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue