My most recent impulse buy for 20 € was a txtr Beagle
. It's an ultra-simple e-ink reader
which can't even do rendering by itself, you have to connect it to an Android smartphone via Bluetooth and the phone will convert the whole book
into a stack of bitmaps and push it over the link.
While this is rather unimpressive if you look at it as an e-reader (although it's fairly light and small), I'm looking at it as a Bluetooth-enabled e-paper display, e.g. as an electronic door sign.
Consequently, I needed to figure out how to push content to the device without having to rely on the Android companion app. Apparently, somebody called Ligius had a similar idea and took the whole thing apart:
txtr Beagle teardown
txtr Beagle - Part two - software
txtr Beagle - part 3 - storage and transfer protocol
Before I was pointed to this blog, I did something very similar to the second post: pull the companion app from my phone with ES3 Explorer, run the APK file through dex2jar
decompile it using jd-gui
. Look for a class called
, which is where the device connection is handled. In fact, most of the information in this class can
already be discovered by just viewing the regular Android debug log (run
on a connected computer), the app is positively chatty and will log all communication with the device.
Side note: to find out some more details about the protocol, I ran an RFCOMM server on my laptop, changed the laptop's Bluetooth name to "Beagle" and let the Android app connect to that fake device. Somewhat to my surprise, this
worked and resulted in a series of gzip-compressed 4-bit images being transferred to my laptop. This is also visible somewhere in the debug log, there's a function
However, there's one snag: you can decompress these pages using regular
, but if you re-compress new pages with
and send them back to the device, it crashes. After some more digging, I discovered that the conversion
function was located in a native library (probably for speed reasons) and that it indeed uses plain gzip compression created by zlib, but with custom initialization parameters (calls
). The native
function which actually performs the compression is called
, so I just guessed that the window size is 2K. I took the zlib sample code, changed the init code to use a 2K window and was able to get a compressed page which
was byte-by-byte identical to the original one.
I've thrown the resulting mess of tools on Github: floe/opentxtr
. Hope you find it useful - I won't be able to do a lot more for the foreseaable future, but I'd be happy about pull requests.
In particular, the
command looks like it might be helpful. Also, it's not yet clear what
Update: Ligius has extracted
the content of the internal SD card and wrote a Java program to analyze it. And you can indeed use
to update, e.g., the cover page!