BLE Control of Insta360 Cameras

7 min read Original article ↗

Patrickchwalek

I started working on a project where I needed to trigger remote cameras for wildlife surveillance and be able to turn them on and off to save power for long-term deployments. Given that for my application, the trigger would be an acoustic signature and I wouldn’t know which direction the target would come from, a camera that can shoot a 360-degree image would be ideal. I went down the rabbit hole of 360-degree cameras and of all the ones I found, Insta360 looked the most promising given its resolution, water resistance, and price — Ricoh was a close second. The other reason I chose Insta360 was that they have a GPS Action Remote that can work with a variety of their cameras and, more importantly, can wake up the cameras “when turned off.” The wake-on feature was intriguing and they are strategically vague on how they accomplish this. I have a hunch that there is a BLE radio in the cameras that can toggle the power to the remainder of the system and that I can connect a custom BLE device to control them.

Goal: reverse engineer the Insta360 GPS Action Remote and recreate it using an ESP32

For testing, I purchased an Insta360 X3 and an RS 1-inch, both really great cameras for all the bells and whistles they feature. Playing around with the cameras, I noticed both have a feature called “Bluetooth Wakeup” that enables them to be woken up somehow via Bluetooth. What’s interesting is when activating this feature and powering down the cameras, there is still a ~0.1Wh power draw when a USB power cable is applied directly to the device (batteries are disconnected). What this implies is that there is still some active circuitry and my hunch is that it’s the BLE radio waiting for a wake-on command.

Power Measurement of Insta360 RS 1-Inch in “Power Off” Mode (with Bluetooth Wakeup Active)

Tabling the above findings, I focused on the GPS Action Remote which can be paired with either or both the cameras. The funny thing is that the remote can be paired with any device when powering it on right out of the box. Using the ST BLE Toolbox app on my iPhone, I paired with it and was able to see all the services, characteristics, descriptors, and traits (i.e., read, write, notify, indicate). What was also interesting is that upon each button press, I noticed characteristic CE82 was changing. In fact, I noticed that there were static values mapped to each of the button presses. So, if you press any of the buttons, CE82 changes and because it can be subscribed to (i.e., notifiable), any connected device will be notified. THIS is how the cameras receive the commands (shoot, mode, and toggle the screen). Per the user guide, when pressing the power button for 3s, the remote is able to shut off any connected camera(s). I held the power button for 3s and I found a fourth unique value that CE82 can become. Now we have four commands that can control the camera in four different ways.

  • Shutter: FCEF-FE86–0003–0102–00
  • Mode: FCEF-FE86–0003–0101–00
  • Power (short-press): FCEF-FE86–0003–0100–00
  • Power (3s-press): FCEF-FE86–0003–0100–03

Before trying to implement this on an ESP32, I decided to take an nrf52840 dongle, load on the BLE sniffer code, fire up Wireshark, and sniff away. I connected the GPS Action Remote to my RS 1-inch and sniffed the device connection. After pressing the shoot command, you can see that the camera sends several write requests to the CE81 characteristic. This information doesn’t seem critical and it looks like it’s just data to update the screen on the remote. The data isn’t encrypted so you can see information such as the remaining frame count and the current mode the camera is in (e.g., 21MP 6.5K2:1). I’m not going to focus on decoding all the different types of data coming from the camera since all I care about right now is controlling the camera in specific settings that I will preset them to.

Press enter or click to view image in full size

Metadata Received from Insta360 Cameras After Each Shutter Command

Okay, so I feel reasonably confident that I have figured out 4 of the outputs of the remote but what about the wake-on feature? After turning off the camera via the remote (3s power button press), I noticed via Wireshark that the remote unpaired with the camera and is now advertising. Now, per the user manual, a press of the power button on the remote should awaken any previously connected cameras. Doing this, I noticed that this does in fact happen and the remote reconnects to the camera after a few seconds. But HOW?! I repeated turning off and back on the camera several times while closely inspecting the packets coming from the remote. I noticed that when the power button is pressed, the remote’s advertisement packet changes. When pressed, manufacturer-specific data is inserted with a 27-byte payload. What was also odd was that the company ID in the manufacturer data was mapped to Apple…. weird. (For a moment, I thought I was sniffing packets from my Apple Airpods but I confirmed they were from the remote.) The hunch here is that the camera’s always-on BLE radio is sniffing advertising packets and looking for a specific payload in the manufacturer-specific data section to wake up the camera.

Press enter or click to view image in full size

Wake-on Advertisement (Left) vs Normal Advertisement (Right)

Now I have enough breadcrumbs to start testing it on an ESP32. I grabbed an ESP32, loaded up Arduino IDE, and mimicked both discovered services, all their characteristics and descriptors, the traits of each one, and even the values of the readable ones. I tried discovering the device via the camera’s Bluetooth Remote discovery feature but that didn’t work. Then I thought, “Hmm, I wonder if it’s expecting the name ‘Insta360 GPS Remote.’” I changed the advertising name to that and it worked! The camera successfully connected to the ESP32 and recognized it as a remote device.

I first began sending it the 4 button commands. The shutter worked as expected. The mode button characteristic value cycled between each mode of the camera successfully. The short-press power button value toggled the screen of the camera. The 3s-press power-button value successfully turned off the camera. Wonderful!!

But now, how about waking back up? I wrote some code to change the advertisement packet of the ESP32 to mimic that of the remote when the power button is pressed. And…. it worked! The camera came back to life!

Great! So now I had the features I wanted working with the RS 1-inch but let’s try the X3. Shutter worked! Mode worked! Both power options worked! Wake-on? No… why?! Firing up Wireshark again, I noticed something interesting. When the GPS Action Remote was paired with the X3, the 27-byte manufacturer-specific payload to wake on the system was different. It was identical to the one seen for the RS 1-inch except for 6 bytes.

Changing the wake-on payload on the ESP32 by those new 6 bytes yielded the result I was looking for (see here). The camera was able to turn back on without a problem. So, it looks like the wake-on payload is at least different across camera types. Given that I only have two cameras of differing models, I can only verify that the payload is different between the two. And that the wake-on payload doesn’t change when repaired.

To summarize, we have successfully,

  • Recreated the GPS Action Remote using an ESP32 (shutter, mode, screen Toggle, and power off/on)
  • Shown that there is parsable metadata returned from the camera upon each button press that can be useful for connected systems (e.g., estimate how many frames are left to shoot)
  • Shown that the wake-on payload is unique across camera models and may be unique to individual cameras
  • You can also pair the ESP32 to multiple cameras and control them in parallel but note the wake-on advertisement needs to be duplicated and made unique to each camera to support the waking up of all cameras.

Here is a concise Arduino example tested on an Adafruit ESP32 Feather: https://github.com/pchwalek/insta360_ble_esp32/tree/main

note: maybe there is a more elegant way of doing this but I was getting tired of parsing through the ESP32 BLE API. I created a new member function in BLEAdvertising.cpp that changed the advertising packet to include the 27-byte wake-on payload.

 m_advData.flag                = (ESP_BLE_ADV_FLAG_GEN_DISC | ESP_BLE_ADV_FLAG_DMT_CONTROLLER_SPT | ESP_BLE_ADV_FLAG_DMT_HOST_SPT);
m_advData.manufacturer_len = 31;
m_advData.p_manufacturer_data = manuf_data; //uint8_t array