BLE

BLE for Tcl on Mac OSX and iOS

BLE for Tcl is a Bluetooth Low Energy (BLE) binding that gives Tcl a single, portable ble command reaching from the desktop to the phone, wired into the Tcl event loop. On macOS and iOS it drives Apple's CoreBluetooth, and it is API-compatible with AndroWish's built-in ble command — so the same Tcl script talks to Bluetooth devices on macOS, iOS *and* Android without change. It was presented by John Buckman at EuroTcl 2026 in Vienna.

https://decentespresso.com/img/ble.jpg

Hardware talks BLE — Tcl should too

Scales, sensors, machines and wearables all speak BLE, but every OS ships its own different, C-heavy Bluetooth stack, and Tcl had no portable, event-loop-friendly binding.

  • AndroWish added Bluetooth to Tcl on Android — enough to build the Decent Espresso de1app — but that tied the app to Android. We wanted macOS and iOS too.
  • Each OS ships a different, C-heavy Bluetooth stack.
  • So we built one ble package with a single Tcl API. The same commands run on macOS, iOS and Android; the OS stack hides behind Tcl.

Bluetooth quickly explained

BLE's vocabulary is small. You only need four ideas:

  • Advertise & scan — peripherals broadcast; the central scans and picks a device by name or address.
  • Services — a device groups its functions into services, each with a UUID you connect against.
  • Characteristics — the read/write values inside a service; the actual bytes you exchange.
  • Notifications — subscribe once and the device pushes updates, perfect for live sensor streams.

The API: scan, connect, subscribe

The design is callbacks, not polling. Notifications arrive as ordinary Tcl events, the GUI stays live because nothing blocks, and binary payloads are handled as Tcl byte strings. A minimal scan looks like this:

package require ble

proc cb {event data} {
    if {$event eq "scan"} {
        puts "[dict get $data rssi] dBm  [dict get $data name]  [dict get $data address]"
    }
}

ble scanner cb        ;# start scanning; cb fires for every device
vwait forever         ;# a script must run the event loop

From there the flow is: connect to the address you want, discover its services and characteristics, enable notifications on the ones you care about, and let your callback fire as data streams in. Because BLE is asynchronous, your callback only runs while Tcl's event loop is running — wish and undroidwish enter it automatically, but under tclsh you must end with vwait (see the repository README and examples/ for the full command set).

One API, native backends

The Tcl script never changes — only the backend does:

  • macOS & iOS — Apple CoreBluetooth
  • Android — Android BLE via AndroWish

On the Mac the library offers two interchangeable CoreBluetooth backends and picks the best automatically: an in-process loadable extension (lowest overhead, and the only option on iOS, where spawning a subprocess isn't allowed), and a small Swift subprocess helper that works even from an unsignable host like undroidwish.

It lives in the Tcl event loop

  • Native BLE callbacks are marshalled onto the Tcl thread.
  • Notifications become ordinary after-style events.
  • There are no blocking reads, so a real-time chart keeps redrawing.
  • Connect and disconnect are asynchronous, with completion callbacks.

Sensor streams drive the UI at full rate without stalling the interpreter.

In production: driving a DE1 espresso machine

This binding runs on thousands of machines, pulling every shot over Tcl BLE:

  • The Decent Espresso DE1 is controlled entirely over BLE.
  • You write shot profiles and read pressure, flow and temperature live.
  • A companion BLE scale streams weight in real time.
  • The same ble package is used on the desktop, an Android tablet and an iPhone.

Takeaway

BLE is now a Tcl one-liner away: package require ble, then scan, connect and subscribe, with CoreBluetooth and Android behind one API and notifications landing in the event loop while the GUI stays live. In our experience Bluetooth has been more stable and faster on Mac and iOS than on Android.

Built to bring the Decent Espresso de1app to macOS and iOS, but usable by any Tcl program that wants Bluetooth LE.

See also

  • AndroWish — the built-in ble command this library is compatible with
  • undroidwish — Tcl/Tk on SDL, a supported host on the Mac
  • vwait — running the event loop so callbacks fire