txtr Beagle Hacking

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 and decompile it using jd-gui. Look for a class called BeagleDevice, 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 adb logcat 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 convertToZipped4bpp or similar.

However, there's one snag: you can decompress these pages using regular gunzip, but if you re-compress new pages with gzip 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 deflateInit2 instead of deflateInit). The native function which actually performs the compression is called compress2K, 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 UTILITYPAGE command looks like it might be helpful. Also, it's not yet clear what OPTION and VCOM do...

Update: Ligius has extracted the content of the internal SD card and wrote a Java program to analyze it. And you can indeed use UTILITYPAGE to update, e.g., the cover page!