example 'cl-repl': simple edit mode for uLisp (Arduino) connected to USB

This commit is contained in:
pls.153 2024-05-07 12:31:32 +02:00
parent 9aba49287c
commit ff27767747
10 changed files with 186 additions and 12 deletions

View file

@ -23,5 +23,7 @@
(:file "lisp/swank-quicklisp")
#+mobile
(:file "lisp/upload-download")
#+linux
(:file "lisp/usb-ulisp")
(:file "lisp/main")))

View file

@ -6,6 +6,10 @@
#include <QNetworkInterface>
#include <QHostAddress>
#ifdef Q_OS_LINUX
#include "usb/usb.h"
#endif
#ifdef PLUGIN
#include <ecl_fun_plugin.h>
#else
@ -335,4 +339,29 @@ QVariant QT::localIp() {
return QVariant();
}
// USB
#ifdef Q_OS_LINUX
QVariant QT::connectUsb() {
if (usb == nullptr) {
usb = new USB();
if (usb->connect2()) {
connect(usb, &USB::receivingDone,
[](const QByteArray& data) {
ecl_fun("ed:received-from-ulisp", QString::fromUtf8(data));
});
return true;
}
}
return QVariant();
}
QVariant QT::sendToUlisp(const QVariant &vData) {
if (usb != nullptr) {
usb->write2(vData.toByteArray());
}
return QVariant();
}
#endif
QT_END_NAMESPACE

View file

@ -28,6 +28,10 @@ QT_BEGIN_NAMESPACE
extern "C" { LIB_EXPORT QObject* ini(); }
#ifdef Q_OS_LINUX
class USB;
#endif
class SyntaxHighlighter : public QSyntaxHighlighter {
Q_OBJECT
friend class QT;
@ -104,7 +108,15 @@ public:
Q_INVOKABLE QVariant textDocument (const QVariant&);
// etc
Q_INVOKABLE QVariant localIp ();
Q_INVOKABLE QVariant localIp();
// USB
#ifdef Q_OS_LINUX
Q_INVOKABLE QVariant connectUsb ();
Q_INVOKABLE QVariant sendToUlisp (const QVariant&);
USB* usb = nullptr;
#endif
};
QT_END_NAMESPACE

View file

@ -13,6 +13,9 @@ HEADERS += qt.h
SOURCES += qt.cpp
linux {
QT += serialport
HEADERS += usb/usb.h
SOURCES += usb/usb.cpp
LIBS += -L../../../platforms/linux/lib
}

View file

@ -0,0 +1,82 @@
#include "usb.h"
#include <QSerialPortInfo>
#include <QTimer>
#include <QtDebug>
USB::USB() {
connect(this, &QSerialPort::readyRead, this, &USB::read2);
connect(this, &QSerialPort::errorOccurred,
[](QSerialPort::SerialPortError error) {
if (error != QSerialPort::NoError) {
qDebug() << "USB error:" << error;
}
});
setBaudRate(Baud9600);
timer.setSingleShot(true);
connect(&timer, &QTimer::timeout, this, &USB::done);
}
bool USB::connect2() {
if (isOpen()) {
setReady(portName());
qDebug() << "USB already open:" << portName();
return true;
}
const auto infos = QSerialPortInfo::availablePorts();
const QStringList supported = { "Arduino" }; // set here micro controller name
for (auto info : infos) {
QString name(info.manufacturer() + " | " + info.description());
QString port(info.portName());
if (port.startsWith("tty") &&
(port.contains("ACM", Qt::CaseInsensitive) || // Linux
port.contains("USB", Qt::CaseInsensitive))) { // macOS
for (auto s : supported) {
if (name.contains(s, Qt::CaseInsensitive)) {
setPortName(info.portName());
qDebug() << "USB:" << port
<< "VID" << info.vendorIdentifier()
<< "PID" << info.productIdentifier()
<< "name" << name;
goto done;
}
}
}
}
done:
if (!open(QIODevice::ReadWrite)) {
qDebug() << "USB: unable to open port" << portName();
return false;
} else {
ready = true;
setReady(portName());
qDebug() << "USB open";
}
return true;
}
void USB::disconnect() {
close();
qDebug() << "USB closed";
}
void USB::write2(const QByteArray& data) {
if (ready) {
write(data);
} else {
qDebug() << "USB not ready: write()";
}
}
void USB::read2() {
packets << readAll();
timer.start(250); // assume receiving done after pause
}
void USB::done() {
Q_EMIT receivingDone(packets.join());
packets.clear();
}

View file

@ -0,0 +1,29 @@
#pragma once
#include <QSerialPort>
#include <QTimer>
class USB : public QSerialPort {
Q_OBJECT
public:
USB();
bool ready = false;
QTimer timer;
QByteArrayList packets;
void received(const QByteArray&);
public Q_SLOTS:
bool connect2();
void disconnect();
void read2();
void write2(const QByteArray&);
void done();
Q_SIGNALS:
void setReady(const QString&);
void receivingDone(const QByteArray&);
};

View file

@ -655,6 +655,9 @@
(defvar *ex-cmd* nil)
(defun feed-top-level (text)
(if #+linux *ulisp-mode* #-linux nil
#+linux (send-to-ulisp text) #-linux nil
(progn
(when eval:*eval-thread*
(if (mp:process-active-p eval:*eval-thread*)
(progn
@ -662,7 +665,7 @@
(eval* ":k"))
(setf eval:*eval-thread* nil)))
(unless eval:*eval-thread*
(eval:feed-top-level text)))
(eval:feed-top-level text)))))
(defun eval* (text)
(if (find #\Newline text)

View file

@ -2,6 +2,7 @@
(:nicknames :ed)
(:use :cl :qml)
(:export
;; editor
#:*file*
#:*plain-text-search*
#:append-output
@ -14,5 +15,9 @@
#:reload-qml
#:save-changes
#:set-font
#:start))
#:start
;; uLisp mode (e.g. Arduino)
#+linux #:*ulisp-mode*
#+linux #:send-to-ulisp
#+linux #:received-from-ulisp))

View file

@ -29,7 +29,10 @@
#:set-format
#:set-pattern
#:text
#:text-document))
#:text-document
;; ulisp (e.g. Arduino)
#+linux #:connect-usb
#+linux #:send-to-ulisp))
(in-package :qt)

View file

@ -20,6 +20,12 @@
(defun option (name)
(find name (ext:command-args) :test 'search))
;;; use app to send code to e.g. Arduino uLisp connected to USB
#+linux
(when (option "-ulisp")
(setf ed:*ulisp-mode* t))
;;; trivial auto reload of all QML files after saving any change
(when (option "-auto")