21 Dec 2024
About
I recently created a small Common Lisp package for working with Bluetooth devices on Linux.
For a project I was working on, I needed to read GPS messages being broadcast by a RaceBox Mini.
After a bit of research I learned that Bluetooth on Linux uses BlueZ, and it provides a D-Bus API.
Common Lisp has a library for using D-Bus, so I was able to create this bluetooth-tools
package.
Right now it uses the D-Bus API to:
- list adapters
- scan for devices
- list devices
- read GATT data
- connect and disconnect devices
- read battery levels
- and a bit more
It worked great for the RaceBox Logger project, and I’ve also used it from the Lisp REPL to
scan for devices, check battery levels, etc.
It’s useful as it is, but I’d like to improve the useability, especially from the REPL. Right now it doesn’t support looking up a device by name,
which makes it a little clunky to use for some things. It would also be nice to lookup human readable information about UUIDs.
Eventually I’d like to have functions for all of the functionality of bluetoothctl exported from bluetooth-tools.
I also want to use this library from StumpWM and assign keyboard shortcuts to various Bluetooth commands, like switching headphone modes,
disconnecting a certain device, etc.
Examples
The first thing to do is load the packaged:
CL-USER> (ql:quickload :bluetooth-tools)
After that, if I want to check my headphone’s battery level, I can do this:
CL-USER> (bluetooth:battery-levels)
(("LE_ATH-M50xBT2" . 80) ("SpaceMouse Pro Wireless BT" . 56))
And see that they’re at 80%, and my 3D mouse is at 56%.
I can also programmatically scan for nearby devices and list them:
CL-USER> (bluetooth:scan :timeout 5)
; No value
CL-USER> (bluetooth:list-devices)
("Ion Pro RT" "Ion Pro RT" "LE-Bose SoundLink Micro" "/org/bluez/hci0/dev_72_50_3D_BA_6D_77"
"/org/bluez/hci0/dev_62_1D_FB_87_FD_06" "/org/bluez/hci0/dev_70_49_F0_16_04_04" "Flare RT"
"/org/bluez/hci0/dev_EC_81_93_2A_8E_E0" "Flare RT" "/org/bluez/hci0/dev_C4_B2_1B_1F_46_37" "Ion Pro RT" "Flare RT"
"/org/bluez/hci0/dev_98_E0_D9_AE_13_FA" "S36 2E97 LE" "/org/bluez/hci0/dev_54_8A_4B_D6_7B_39" "Ion Pro RT" "CS100-AO"
"Apple Wireless Keyboard" "GBK_H613E_FC76" "Flare RT" "RaceBox Mini 1221405078" "Govee_H617A_2611" "110092_603C"
"LE_ATH-M50xBT2" "ERGO M575" "/org/bluez/hci1/dev_EC_81_93_68_7F_54" "Wacom One pen tablet medium"
"Jeremiah’s Trackpad" "LE_ATH-M50xBT2" "SpaceMouse Pro Wireless BT")
CL-USER>
20 Sep 2018
LuaJIT and SBCL Benchmark
Overview
This is a quick benchmark comparing LuaJit and SBCL using the Mandelbrot set
program from Debian’s benchmark site.
Software Versions
For this comparison I built both compilers from source using the tip of their Git repos as of this past Tuesday night.
For SBCL I’m using commit 92951c6f4 and for LuaJIT I’m using b025b01c5b.
I’m not familiar with the LuaJIT build process, so I used the default build procedure, and ran “make” and then “sudo
make install”.
For SBCL I built with the following features turned on: (:sb-show-assem :immobile-space :compact-instance-header
:sb-thread :sb-futex :sb-xref-for-internals). I compiled and installed with “make.sh –dynamic-space-size=8192” and
then “sudo sh install.sh”
$> sbcl --version
SBCL 1.4.11.92-92951c6f4
$>
$> luajit -v
LuaJIT 2.0.5 -- Copyright (C) 2005-2017 Mike Pall. http://luajit.org/
$>
Programs
For the LuaJIT program I’m using the fastest Lua code from the benchmark site with no changes. The code can be found here.
The fastest Lisp code uses SBCL’s assembly intrinsics, so I used the second fastest program. I modified it to take a
thread count argument and also tweaked the build flags to create an executable instead of a core. The code can be found here.
Test Procedure
I created two shell scripts (run_lua.sh and run_lisp.sh) to run the benchmark. Images of size 10000 and 20000 were
generated using 4 and 8 threads, and each run was repeated 8 times.
# Runs |
Size |
Threads |
8 |
10000 |
4 |
8 |
20000 |
4 |
8 |
10000 |
8 |
8 |
20000 |
8 |
Results
Size 10000, 4 Threads
LuaJIT |
|
|
|
SBCL |
|
|
|
User |
System |
% CPU |
Total |
User |
System |
% CPU |
Total |
14.13s |
0.16s |
283% |
5.034 |
13.32s |
0.03s |
278% |
4.801 |
14.06s |
0.12s |
308% |
4.591 |
13.34s |
0.02s |
276% |
4.833 |
14.11s |
0.13s |
291% |
4.883 |
13.30s |
0.03s |
275% |
4.831 |
14.13s |
0.14s |
281% |
5.073 |
13.47s |
0.02s |
275% |
4.903 |
14.14s |
0.12s |
310% |
4.596 |
13.31s |
0.03s |
276% |
4.832 |
13.96s |
0.12s |
279% |
5.031 |
13.32s |
0.01s |
275% |
4.831 |
14.10s |
0.13s |
282% |
5.043 |
13.31s |
0.02s |
276% |
4.827 |
14.09s |
0.12s |
280% |
5.065 |
13.32s |
0.02s |
275% |
4.833 |
Size 10000, 8 Threads
LuaJIT |
|
|
|
SBCL |
|
|
|
User |
System |
% CPU |
Total |
User |
System |
% CPU |
Total |
14.83s |
0.11s |
378% |
3.944 |
14.73s |
0.03s |
358% |
4.123 |
14.62s |
0.13s |
357% |
4.122 |
14.72s |
0.04s |
357% |
4.133 |
14.67s |
0.14s |
361% |
4.097 |
14.72s |
0.03s |
354% |
4.158 |
14.60s |
0.13s |
355% |
4.141 |
14.74s |
0.04s |
357% |
4.129 |
14.57s |
0.10s |
357% |
4.111 |
14.77s |
0.03s |
354% |
4.170 |
14.70s |
0.15s |
365% |
4.064 |
14.67s |
0.02s |
353% |
4.163 |
14.71s |
0.16s |
366% |
4.058 |
14.72s |
0.03s |
356% |
4.136 |
14.82s |
0.11s |
376% |
3.966 |
14.70s |
0.05s |
354% |
4.163 |
Size 20000, 4 Threads
LuaJIT |
|
|
|
SBCL |
|
|
|
User |
System |
% CPU |
Total |
User |
System |
% CPU |
Total |
59.50s |
0.45s |
283% |
21.166 |
53.14s |
0.07s |
276% |
19.271 |
59.53s |
0.43s |
285% |
20.992 |
54.11s |
0.09s |
274% |
19.780 |
59.93s |
0.43s |
281% |
21.478 |
53.10s |
0.08s |
276% |
19.266 |
59.35s |
0.42s |
290% |
20.543 |
53.11s |
0.10s |
275% |
19.335 |
59.21s |
0.41s |
283% |
20.997 |
53.00s |
0.06s |
274% |
19.364 |
59.28s |
0.44s |
303% |
19.695 |
53.11s |
0.09s |
275% |
19.291 |
59.50s |
0.45s |
291% |
20.545 |
54.57s |
0.07s |
272% |
20.066 |
59.62s |
0.42s |
295% |
20.329 |
53.13s |
0.08s |
275% |
19.283 |
Size 20000, 8 Threads
LuaJIT |
|
|
|
SBCL |
|
|
|
User |
System |
% CPU |
Total |
User |
System |
% CPU |
Total |
61.61s |
0.54s |
376% |
16.514 |
58.92s |
0.07s |
367% |
16.040 |
61.83s |
0.46s |
376% |
16.537 |
58.67s |
0.08s |
354% |
16.557 |
61.82s |
0.47s |
375% |
16.584 |
58.68s |
0.07s |
355% |
16.513 |
61.52s |
0.47s |
377% |
16.433 |
58.78s |
0.07s |
356% |
16.506 |
61.76s |
0.44s |
373% |
16.635 |
58.53s |
0.08s |
349% |
16.765 |
61.66s |
0.48s |
371% |
16.722 |
58.69s |
0.09s |
352% |
16.668 |
61.52s |
0.43s |
370% |
16.738 |
58.59s |
0.08s |
352% |
16.636 |
61.86s |
0.47s |
371% |
16.763 |
58.72s |
0.09s |
355% |
16.530 |
Summary
As far as overall time spent, SBCL and LuaJIT performed the same on this benchmark.
The “User” time and “% CPU” columns are more interesting, though. The Lisp program spends less time executing user code,
but also uses less of the CPU. My interpretation of this is that SBCL generates better single threaded code, but the Lisp
program’s multi-threading is less efficient than the LuaJIT program.
29 Nov 2015
Here’s a Common Lisp function to generate static HTML and CSS from a GitHub Gist URL. The results are printed to standard out, and can be copy/pasted into an HTML document. This is really useful when working in Emacs with a Slime REPL open.
(defun make-static-gist (gist-url)
"Retrieve a Gist from GitHub and format it as static CSS and HTML."
(let* ((nl-string (format nil "~c" #\newline))
;; Make sure the url has ".js" on the end
(ending (subseq gist-url (- (length gist-url) 3)))
(js-url (if (string= ending ".js")
gist-url
(concatenate 'string gist-url ".js")))
;; Fetch the embedded gist from GitHub
(gist-data (drakma:http-request js-url)))
;; Read the string data
(with-input-from-string (strm gist-data)
(let* (
;; Get the CSS link
(css-html (read-line strm))
;; Get the document data
(doc-part (read-line strm))
;; Parse out the CSS URL and fetch it
;; 45 is the length of "document.write...",
;; 4 is the length of "\");" on the end
(css-url (subseq css-html 45 (- (length css-html) 4)))
(style-sheet (drakma:http-request css-url))
;; Remove \n and \ from the html
(escaped-html (subseq doc-part 16 (- (length doc-part) 4)))
(html-nl (cl-ppcre:regex-replace-all "\\\\n" escaped-html nl-string))
(raw-html (cl-ppcre:regex-replace-all "\\" html-nl "")))
;; Print everything out
(format t "~a~%" style-sheet)
(format t "~a~%" raw-html)))))
29 Nov 2015
I haven’t really done anything with my GitHub pages site, except setup Octopress and publish one blog post. Every time I’ve wanted to add a new entry I’ve got distracted fucking with Octopress and Ruby and Jekyll and a bunch of other stuff I don’t care to learn, and ended up losing my motivation.
This time it happened again, and I’ve decided to just get rid of Octopress and Jekyll entirely and go back to raw HTML. It won’t be the trendiest, prettiest website on the internet, but at least I know how to use it, and it won’t have random shit breaking requiring an hour of tinkering to figure out which of a million Ruby gems is broken or not installed.
12 Nov 2014
I recently wrote a small library, called gpxtools, for processing GPX track files in Common Lisp.
It doesn’t have many features yet, but right now it can read a GPX file and compute elevation gain, elevation loss, and the total distance of all the tracks in the file.
Here is a sample usage from the REPL:
* (ql:quickload 'gpxtools)
To load "gpxtools":
Load 1 ASDF system:
gpxtools
; Loading "gpxtools"
.....
(GPXTOOLS)
* (defparameter *gpx*
(gpxtools:read-gpx "/Users/jeremiah/gpx_tracks/precarious_climb.gpx"))
Processing track: ACTIVE LOG
*GPX*
* (gpxtools:summarize *gpx*)
Total elevation gain: 3567.271946242661d0 feet
Total elevation loss: -3538.885209324304d0 feet
Total elevation loss: 6.437161570619994d0 miles
NIL
*
So far the most challenging part of the project was the calculation of distances given points in latitude and longitude. Doing so requires converting the coordinates to the Universal Transverse Mercator coordinate system which uses meters, and then calculating the distance.
I wasn’t able to find a library to do the conversion for me, so I wrote my own, and requested it be added to QuickLisp. It looks like everything went well, and it should be in the next release.
I’m planning to add more features to the gpxtools library, and to clean up the code in a few spots.
One thing I want to do is add generic traversal code and use it to simplify functions like distance, elevation-gain, and elevation-loss that iterate over the entire GPX data structure.
Another thing I want to add is the ability to split tracks at a certain location and selectively write the results back to one or more files. I’ll often start my GPS at the beginning of a hike, but forget to turn off the GPS when I finish hiking and drive away. The result is a GPX track with 10 miles of hiking and 50 miles of driving. I may even try to do the cut automatically, ending the track when it gets back to (or reasonably close to) the origin, and splitting the drive out into a second track automatically.