Magic Button 4k: the 20USD BMPCC 4k (or 6k) Wireless Remote Control

7,247

25

19

Introduction: Magic Button 4k: the 20USD BMPCC 4k (or 6k) 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...

UPDATE 2020: I made version 3.0. It's based on a free rotating wheel using a magnetic encoder. It also connects to my follow focus motor, which basically becomes a second bluetooth device (the ESP32 supports multiple bluetooth connections). The new video demonstrates this.

If you would like to order version 3, please have a look on the MagicButton website

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;
volatilebool scanning =0;
volatileuint32_t pinCode;

The scanning and main loop:

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks{
	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;
		}
		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.");
	}
};

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.

class MySecurity : public BLESecurityCallbacks
{
	uint32_t onPassKeyRequest()
	{
		Serial.println("---> PLEASE ENTER 6 DIGIT PIN (end with ENTER) : ");
		pinCode =0;
		char ch;
		do
		{
			while (!Serial.available())
			{
				delay(1);
			}
			ch = Serial.read();
			if (ch >='0'&& ch <='9')
			{
				pinCode = pinCode *10+ (ch -'0');
				Serial.print(ch);
			}
		}
		while ((ch !='\n'));
		return pinCode;
	}


	void onPassKeyNotify(uint32_t pass_key)
	{
		ESP_LOGE(LOG_TAG, "The passkey Notify number:%d", pass_key);
	}
  
	bool onConfirmPIN(uint32_t pass_key)
	{
		ESP_LOGI(LOG_TAG, "The passkey YES/NO number:%d", pass_key);
		vTaskDelay(5000);
		returntrue;
	}
  
	bool onSecurityRequest()
	{
		ESP_LOGI(LOG_TAG, "Security Request");
		returntrue;
	}
  
	void onAuthenticationComplete(esp_ble_auth_cmpl_t auth_cmpl)
	{
		Serial.print("pair status = ");
		Serial.println(auth_cmpl.success);
	}
};

BLE notification:

The camera notifies its BLE clients about any camera changes, including when the camera starts and stops recording. This code toggles my LED when it starts/stops recording.

static void notifyCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic,
	uint8_t*pData,
	size_t length,
	bool isNotify)
{
// BMPCC4k BLE message format:
// rec on is 255 9 0 0 10 1 1 2 2 0 64 0 2
// rec off is 255 9 0 0 10 1 1 2 0 0 64 0 2

if (length ==13&& pData[0] ==255&& pData[1] ==9&& pData[4] ==10&& pData[5] ==1) { if (pData[8] ==0) { recstatus =0; } if (pData[8] ==2) { recstatus =1; } } }

Step 2: The Code Part 2

This is the part which actually sends the commands to the camera.

Recording:

uint8_t record[] =   {255, 9, 0, 0, 10, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // 0=OFF, 2=ON, [8]
void Record(boolean RecOn) { if (!RecOn) record[8] =0; else record[8] =2; pControlCharacteristic->writeValue((uint8_t*)record, 16, true); }

Focusing:

The camera expects an 11 bit number, ranging from near to far focus. I do advise to put a filter on your ADC value, otherwise the focus might be nervously jittering.

uint8_t focus[] =    {255, 6, 0, 0, 0, 0, 128, 0, 0, 0, 0, 0}; // 0.0 ... 1.0, 11bit, [8]=LSB, [9]=MSB
void Focus(uint16_t val) { //going from a 12bit ADC value to 11bit focus value focus[8] = (uint8_t)(((val >>1) &0xFF)); focus[9] = (uint8_t)(((val >>1) &0xFF00) >>8); pControlCharacteristic->writeValue((uint8_t*)focus, 12, true); }

Aperture:

The camera expects an 11 bit number, ranging from a low to a high aperture value. I do advise to put a filter on your ADC value, otherwise the aperture value might be nervously jittering.

uint8_t aperture[] = {255, 6, 0, 0, 0, 3, 128, 0, 0, 0, 0, 0}; // 0.0 ... 1.0, [8]=LSB, [9]=MSB
void Aperture(uint16_t val) { //going from a 12bit ADC value to 11bit aperture value aperture[8] = (uint8_t)(((val >>1) &0xFF)); aperture[9] = (uint8_t)(((val >>1) &0xFF00) >>8); pControlCharacteristic->writeValue((uint8_t*)aperture, 12, true); }

Step 3: The Circuit

I've attached the PDF of my circuit. Some pics of the PCB are also attached.

The board is powered with micro USB.

After receiving the PCB I decided that I wanted to drive an RGB LED, so I connected two WS2812B in series to the "Button Led" output (that needed some wire patches on the PCB). The PCB's were 8USD with OSHPark.com.

You can see some more connections on the PCB such as "adc" which I'm not using and which were removed from the attached schematics. The plan was to use an external focus wheel in the past but I'm currently perfectly happy with the little thumb wheel.

Step 4: Conclusion

I hope this helped.

I have got some future updates in mind, such as using a rotary encoder without hard stops. This will require the controller to get the current value of the focus or aperture from the camera, and continue from there. The "notifyCallback" function needs to be updated for that probably.

The PCB needs an update to provide the signals for the WS2812B RGB LEDs properly.

I spent a lot (a loooot) of time in making this work, especially the BLE part. If this helped you out and you wanna buy me a drink, that's very much appreciated :) This is a Paypal donation link:

Be the First to Share

    Recommendations

    • The 1000th Contest

      The 1000th Contest
    • Battery Powered Contest

      Battery Powered Contest
    • Hand Tools Only Challenge

      Hand Tools Only Challenge

    19 Discussions

    0
    dpgregory
    dpgregory

    7 months ago

    Thanks so much for this, saved me loads of time. Took me just an afternoon instead of weeks! Can you update the PayPal link, as it's not working, so I can buy you a drink.

    0
    jeppo7745
    jeppo7745

    Reply 7 months ago

    Glad I could help!
    Thanks for letting me know the paypal link doesn't work anymore :D I hope it works now... cheers!

    0
    Hiok
    Hiok

    8 months ago

    When you say schematic, do you mean esp32buttom.sch?

    0
    roger_webhead
    roger_webhead

    Question 11 months ago on Introduction

    Do you happen to use a Zoom recorder for audio? I'm going to give your project a try and then try to mod it to tell my Zoom H6 to record and stop when the camera records and stops. If I get that to work, I will share it.

    0
    SamiSan
    SamiSan

    Answer 8 months ago

    Hey Roger!

    This is exactly what I've been looking for online. A bluetooth remote that could trigger the Zoom f6 and Blackmagic Pocket 4K simultaneously. Did you manage to make this work?

    0
    jeppo7745
    jeppo7745

    Reply 11 months ago

    Great idea! I don't have a Zoom... But the ESP32 is capable of connecting to several BLE devices at the same time, so if the Zoom is a BLE server it should be possible.

    0
    pjuppi
    pjuppi

    9 months ago

    Hi!
    I really like what you have done here, but I find it very hard to compile this without the original .ino file :(
    I'd love to buy you a sixpack if got this to work :)

    1
    lfpoulain
    lfpoulain

    10 months ago

    Awesome ! I was looking for a solution like this for months ! Thank you for sharing.
    However I have a problem with compilation : 'BLEUUID' does not name a type. I tried to reinstall everything but I still got this message. Do you know how to solve it ?
    Also can you please attach the .ino and, if possible, let us know about the wifi update ?
    All the best.

    2
    sviramti
    sviramti

    Question 10 months ago on Step 4

    Hello, wonderful project!
    When compile I got this error:
    expected template-name before '<' token
    in pincode part,
    Do you know why?
    Thanks!
    .....Now after, I see setup is missing in code.
    What is part of setup code, and which library are included in begining of code?
    Thanks again!

    0
    flechnical
    flechnical

    Question 11 months ago

    Do you know if this works with the original Metabones MFT-Speedboosters or the newer T-Version for the Pocket 4K?

    1
    jeppo7745
    jeppo7745

    Answer 11 months ago

    It does. I own the original metabones. Though sometimes it doesn't want to go to infinity for some reason, I don't really know why. Maybe it's solved in the latest v6.6 firmware, still need to try...

    0
    flechnical
    flechnical

    Reply 11 months ago

    Cool, thank you for answering. I think I'll finally get me a Pocket 4K and get a second hand Metabones for my EFS lenses. I've already read online that the firmware on lens adapters can be a bit janky :)

    0
    Chenopup
    Chenopup

    Question 1 year ago on Step 2

    Have you considered selling these?

    0
    jeppo7745
    jeppo7745

    Reply 11 months ago

    that would be too much work ;)

    0
    Chenopup
    Chenopup

    Reply 11 months ago

    Dang - too bad. Keep me in the loupe if you decide to - super impressed!!!

    0
    jeppo7745
    jeppo7745

    Reply 1 year ago

    yes but it would be too much work... ;) I'm doing this in my free time.

    0
    calcot
    calcot

    1 year ago on Step 4

    it is really good job