Magic Button 4k: the 20USD BMPCC4k Wireless Remote Control

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


About The Author

Muhammad Bilal

I am a highly skilled and motivated individual with a Master's degree in Computer Science. I have extensive experience in technical writing and a deep understanding of SEO practices.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top