Summary of Magic Button 4k: the 20USD BMPCC4k Wireless Remote Control
This article details a DIY wireless Bluetooth controller for the Blackmagic Pocket Cinema Camera 4K (BMPCC4k) using an ESP32 microcontroller. The device allows remote control of recording, focus, and aperture via a custom wheel and button interface. The author provides code snippets for establishing a BLE connection with the camera and lists specific hardware components required for assembly, including a TTGO Micro-32 module, a potentiometer, and a push button.
Parts used in the BMPCC4k Wireless Controller:
- ESP32 module with wifi and bluetooth (specifically LILYGO-TTGO-Micro-32-V2_0)
- Potentiometer (focus wheel with hard stops)
- Rec/mode push button
- Standard components such as resistors and capacitors

Many people have asked me to share some details about my wireless controller for the BMPCC4k. Most questions were about the bluetooth control, so I’ll mention a few details about that. I am assuming you are familiar with the ESP32 Arduino environment.
This version of the remote can control the recording, focus and aperture of the camera via bluetooth. Have a look at the video. It’s quite easy to add more control functions as per the bluetooth control manual of the BMPCC4k. Basically anything in the camera can be controlled, as far as I’ve seen.
It would be an easy step to add a LIDAR module to measure the distance of a subject, so you can get some kind of an autofocus system… Though it’s questionable if you can get an accurate enough focus onto specific areas such as eyes etc…
Supplies:
Any ESP32 module with wifi and bluetooth. I used the TTGO micro32 because it’s tiny:
https://www.banggood.com/LILYGO-TTGO-Micro-32-V2_0…
A focus wheel, any potentiometer would do. I used the following because it’s tiny:
https://www.aliexpress.com/item/32963061806.html?s…
This kind has hard stops at the upper and lower boundary. In a future version I’ll use a rotary encoder. This way the focus or aperture doesn’t “jump” to the current wheel setting when I enter a mode.
A rec/mode button. I used the following:
https://www.aliexpress.com/item/32806223591.html?s…
Other standard components such as resistors, caps, … (see schematic)
Step 1: The Code
I use the wifi capability of the ESP32 to either connect to a known network in AP mode, or, when I’m in the field, it becomes a station (STA) to which I can connect to. That way I can configure the module. I won’t go into detail of the wifi/webpage section, I might add this at a later stage.
The ESP32 connects to the camera and becomes a Bluetooth LE client. The bluetooth code included in Arduino’s ESP32 framework doesn’t work with the BMPCC4k. Wakwak-koba has fixed it for us. Thank you Wakwak-koba! I used the BLE library from here:
https://github.com/wakwak-koba/arduino-esp32
Nevertheless that version of the BLE lib is still under development and the latest version of BLEUUID.cpp doesn’t seem to work at this moment, so take the earlier “verified” version of this file.
For the rest, most of my bluetooth code is a lot as per the BLE examples included in the Arduino framework:
Some BLE UUID and variable defines:
static BLEUUID BlackMagic("00001800-0000-1000-8000-00805f9b34fb");
static BLEUUID ControlserviceUUID("291D567A-6D75-11E6-8B77-86F30CA893D3");
static BLEUUID DevInfoServiceControlUUID("180A");
static BLEUUID ControlcharUUID("5DD3465F-1AEE-4299-8493-D2ECA2F8E1BB");
static BLEUUID NotifcharUUID("B864E140-76A0-416A-BF30-5876504537D9");
static BLEUUID ClientNamecharUUID("FFAC0C52-C9FB-41A0-B063-CC76282EB89C");
static BLEUUID CamModelcharUUID("2A24");
static BLEScan *pBLEScan = BLEDevice::getScan();
static BLEAddress *pServerAddress;
static BLEAdvertisedDevice* myDevice;
static BLERemoteCharacteristic *pControlCharacteristic;
static BLERemoteCharacteristic *pNotifCharacteristic;
static boolean doConnect = 0;
static boolean connected = 0;
volatile bool scanning = 0;
volatile uint32_t pinCode;
The scanning and main loop:
class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks<br>{
void onResult(BLEAdvertisedDevice advertisedDevice)
{
Serial.print("BLE Advertised Device found: ");
Serial.println(advertisedDevice.toString().c_str());
if (advertisedDevice.haveServiceUUID() && advertisedDevice.getServiceUUID().equals(BlackMagic))
{
Serial.print("Found our device!");
advertisedDevice.getScan()->stop();
myDevice = new BLEAdvertisedDevice(advertisedDevice);
doConnect = true;
}
}
};
static void scanCompleteCB(BLEScanResults scanResults)
{
Serial.println("scanning done");
scanning = false;
}
void loop(void)
{
if (!connected && ((uint32_t)(millis() - Timer) > BLE_RESCAN_TIME || (!scanning)))
{
Serial.println("scanning...");
scanning = true;
pBLEScan->start(BLE_SCAN_TIME, scanCompleteCB);
Timer = millis();
}
if (doConnect == true)
{
if (connectToServer())
{
Serial.println("We are now connected to the BLE Server.");
connected = true;
doLed(CONNECTED_RGB);
}
else
{
Serial.println("We have failed to connect to the server; there is nothin more we will do.");
}
doConnect = false;
}
}
Connecting to the camera:
bool connectToServer()
{ Serial.print("Forming a connection to "); Serial.println(myDevice->getAddress().toString().c_str()); BLEDevice::setEncryptionLevel(ESP_BLE_SEC_ENCRYPT); BLEDevice::setSecurityCallbacks(new MySecurity()); BLESecurity *pSecurity = new BLESecurity(); pSecurity->setKeySize(); pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_MITM_BOND); pSecurity->setCapability(ESP_IO_CAP_IN); pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK); BLEClient *pClient = BLEDevice::createClient(); pClient->setClientCallbacks(new MyClientCallback()); pClient->connect(myDevice); Serial.println(" - Connected to server"); BLEDevice::setMTU(BLEDevice::getMTU()); // OBTAIN CAMERA MODEL BLERemoteService *pRemoteService = pClient->getService(DevInfoServiceControlUUID); if (pRemoteService == nullptr) { Serial.print(" - Failed to get device info service"); Serial.println(DevInfoServiceControlUUID.toString().c_str()); goto fail; } Serial.println(" - Reading device info"); // Obtain a reference to the characteristic in the service of the remote BLE server. BLERemoteCharacteristic *pRemoteCamModelCharacteristic = pRemoteService->getCharacteristic(CamModelcharUUID); if (pRemoteCamModelCharacteristic == nullptr) { Serial.print(" - Failed to find camera model"); Serial.println(CamModelcharUUID.toString().c_str()); goto fail; } // Read the value of the characteristic. std::string value = pRemoteCamModelCharacteristic->readValue(); Serial.print("Camera is "); Serial.println(value.c_str()); if (CamModel != value.c_str()) { Serial.print(" - Camera is not BMPCC4k"); goto fail; } // OBTAIN CONTROL pRemoteService = pClient->getService(ControlserviceUUID); if (pRemoteService == nullptr) { Serial.print(" - Failed to get camera service"); Serial.println(ControlserviceUUID.toString().c_str()); goto fail; } BLERemoteCharacteristic *pRemoteClientNameCharacteristic = pRemoteService->getCharacteristic(ClientNamecharUUID); if (pRemoteClientNameCharacteristic != nullptr) { pRemoteClientNameCharacteristic->writeValue(MyName.c_str(), MyName.length()); } pControlCharacteristic = pRemoteService->getCharacteristic(ControlcharUUID); if (pControlCharacteristic == nullptr) { Serial.print(" - Failed to get control characteristic"); Serial.println(ControlcharUUID.toString().c_str()); goto fail; } pNotifCharacteristic = pRemoteService->getCharacteristic(NotifcharUUID); if (pNotifCharacteristic != nullptr) // && pNotifCharacteristic->canIndicate()) { Serial.println(" - subscribing to notification"); const uint8_t indicationOn[] = {0x2, 0x0}; pNotifCharacteristic->registerForNotify(notifyCallback, false); pNotifCharacteristic->getDescriptor(BLEUUID((uint16_t)0x2902))->writeValue((uint8_t *)indicationOn, 2, true); } return true; fail: pClient->disconnect(); return false; }
The connected/disconnected callback:
class MyClientCallback : public BLEClientCallbacks
{ void onConnect(BLEClient *pclient) { Serial.println("We are connected."); } void onDisconnect(BLEClient *pclient) { connected = false; pclient->disconnect(); Serial.println("We got disconnected."); doLed(NOTCONNECTED_RGB); } };
The pin code part:
In my current version I can enter the pincode via the web interface but these are wifi/webpage details which I might add later.
Read more: Magic Button 4k: the 20USD BMPCC4k Wireless Remote Control
- What functions can this wireless controller perform?
The remote can control recording, focus, and aperture of the camera via bluetooth. - Can I add more control functions to the device?
Yes, it is easy to add more control functions as per the bluetooth control manual of the BMPCC4k. - Is it possible to integrate a LIDAR module for autofocus?
An easy step would be adding a LIDAR module to measure subject distance for an autofocus system. - Which ESP32 module did the author use for this project?
The author used the LILYGO-TTGO-Micro-32 because it is tiny. - What type of component was used for the focus wheel?
A potentiometer with hard stops at the upper and lower boundary was used. - Why might a rotary encoder be better than a potentiometer?
A rotary encoder prevents focus or aperture from jumping to the current wheel setting when entering a mode. - Does the standard Arduino ESP32 framework work with the BMPCC4k out of the box?
No, the included bluetooth code does not work; a fixed library by Wakwak-koba is required. - How does the ESP32 handle network connectivity?
The ESP32 connects to a known network in AP mode or becomes a station (STA) for configuration.
