I recently tackled the challenge of figuring out how to send images to a pair of smart glasses we're developing. The significant hurdle was the lack of direct access to the firmware and extremely limited documentation on available commands or their formats. All I initially had was an outdated APK that no longer worked and some partial source code that only supported basic functionality.
This project was entirely new territory for me—I'd never worked with Bluetooth protocols, real-time operating systems (RTOS), or even Android development extensively. Fortunately, the availability of powerful language models (LLMs) made the exploration feasible.
### Initial Attempts
I began by sending commands based on the limited documentation, hoping to trigger an image display. These attempts failed completely, giving no response or indication from the glasses that anything had been received—akin to shouting into a void.
### Reverse Engineering
Realizing I needed deeper insight into what code could possibly be running on the glasses, I decompiled an old Android APK to investigate the original implementation for sending commands. Surprisingly, I discovered the APK didn't contain a working example of sending images. Instead, I found unused code snippets that suggested the capability existed at one point, but it had not been completed in this version of the app.
I noticed there *were* commands for drawing primitives (lines, rectangles, text) with clear examples, yet nothing similar for images. I reconstructed packet headers by referencing working commands and attempted to modify them to accommodate image data, but initially, this yielded no results.
### Leveraging UUIDs and Subscriptions
Next, I shifted my approach toward trying to get any sort of feedback from the glasses. I learned that when connecting via Bluetooth Low Energy (BLE), devices respond with a list of subscription UUIDs. Unfortunately, these UUIDs lacked descriptive documentation.
By systematically logging every UUID interaction, I gradually identified patterns correlating specific UUIDs to particular functionalities. Even though the returned data was raw bytes and challenging to interpret directly, logging helped me detect responses indicating malformed requests. This marked significant progress—I finally had confirmation that the glasses were at least receiving and processing my messages.
### Image Format and Compression Issues
Initially, I assumed the issue was image formatting. Since the APK command for image drawing was unused, I had no guidance on the expected image format. Additionally, the image underwent compression via a native binary library written in C—completely opaque to my investigation efforts.
Unable to decipher the compression algorithm through online searches, I extracted and incorporated the native binary directly into my workflow, hoping to correctly mimic the required compression step. Despite my efforts, repeated "malformed request" errors continued.
### Breakthrough with Header Adjustment
I revisited the header configurations and realized I had mistakenly used a "draw canvas" command instead of the correct "raw image" command. Correcting this mistake resulted in the glasses returning a success message—my first tangible indication that the protocol was partially correct.
However, despite receiving success confirmations, no image appeared on the glasses. Suspecting color issues, I experimented by switching the bitmap color from black to white. Immediately, a visible line appeared on the glasses' screen.
### Line to Box
However, I expected a 16x16 box and not a line. I discovered that for RAW_IMAGE messages the glasses expect every pixel to be defined. So when it received the 256 bytes, it started placing them on the first row of pixels. So if I wanted just a box in the middle of the screen, I needed to pad the box with black pixels.
This means sending a *lot* more pixels, which meant I immediately required sending multiple packets to display a single image on the screen. This broke the CRC calculation, because I was only calculating the CRC once for the whole image data, and apparently this firmware requires the CRC to be calculated for each packet. But after that, it worked.
*7.2.25*