Skip to content

Commit

Permalink
Initial rewrite to better support Canon EOS R. (#172)
Browse files Browse the repository at this point in the history
* Initial rewrite to better support Canon EOS R.

Refactor existing Canon EOS support into Canon EOS M6.

Reimplement Canon EOS R support.

* Include the step 3 prefix.

Add the 0x03 prefix to the CanonEOSR connection handshake.

* Update README for Canon EOS R support.
  • Loading branch information
gkoh authored Jan 31, 2025
1 parent af3933a commit 705aa03
Show file tree
Hide file tree
Showing 12 changed files with 361 additions and 301 deletions.
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ The following devices have been tested and confirmed to work:
- Canon
- Canon EOS M6 ([@tardisx](https://github.com/tardisx))
- Canon EOS RP ([@wolcano](https://github.com/wolcano))
- Canon EOS R6 Mark II ([@hijae](https://github.com/hijae))
- Mobile Devices (beta)
- Android
- iOS
Expand Down Expand Up @@ -64,11 +65,11 @@ Currently supported features in `furble`:

### Table of Features

|   | Fujifilm X & GFX | Canon EOS M6 | Canon EOS RP | Android & iOS |
|   | Fujifilm X & GFX | Canon EOS M6 | Canon EOS R | Android & iOS |
| --- | :---: | :---: | :---: | :---: |
| Scanning & Pairing | ✔️ | ✔️ | ✔️ | ✔️ |
| Shutter Release | ✔️ | ✔️ | ✔️ | ✔️ |
| Focus | ✔️ (see [#99](https://github.com/gkoh/furble/discussions/99)) | :x: | :x: (see [#29](https://github.com/gkoh/furble/issues/29)) | :x: |
| Focus | ✔️ (see [#99](https://github.com/gkoh/furble/discussions/99)) | :x: | ✔️ | :x: |
| GPS location tagging | ✔️ | :x: (WiFi only) | :x: | :x: |

## Installation
Expand Down Expand Up @@ -305,7 +306,8 @@ Reports of further confirmed working Fujifilm cameras are welcome.
With access to a Canon EOS M6, I was able to implement support for it. Other
Canon cameras might work, but I suspect the shutter control protocol will be
different.
@wolcano kindly implemented support for the Canon EOS RP.
@wolcano kindly implemented initial support for the Canon EOS RP.
@hijae kindly helped with better Canon EOS R support.

#### Protocol Reverse Engineering

Expand Down
2 changes: 1 addition & 1 deletion lib/furble/Camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class Camera {
enum class Type : uint32_t {
FUJIFILM = 1,
CANON_EOS_M6 = 2,
CANON_EOS_RP = 3,
CANON_EOS_R = 3,
MOBILE_DEVICE = 4,
FAUXNY = 5,
};
Expand Down
10 changes: 5 additions & 5 deletions lib/furble/CameraList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#include <Preferences.h>

#include "CanonEOSM6.h"
#include "CanonEOSRP.h"
#include "CanonEOSR.h"
#include "FauxNY.h"
#include "Fujifilm.h"
#include "MobileDevice.h"
Expand Down Expand Up @@ -155,8 +155,8 @@ void CameraList::load(void) {
case Camera::Type::CANON_EOS_M6:
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new CanonEOSM6(dbuffer, dbytes)));
break;
case Camera::Type::CANON_EOS_RP:
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new CanonEOSRP(dbuffer, dbytes)));
case Camera::Type::CANON_EOS_R:
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new CanonEOSR(dbuffer, dbytes)));
break;
case Camera::Type::MOBILE_DEVICE:
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new MobileDevice(dbuffer, dbytes)));
Expand Down Expand Up @@ -200,8 +200,8 @@ bool CameraList::match(const NimBLEAdvertisedDevice *pDevice) {
} else if (CanonEOSM6::matches(pDevice)) {
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new Furble::CanonEOSM6(pDevice)));
return true;
} else if (CanonEOSRP::matches(pDevice)) {
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new Furble::CanonEOSRP(pDevice)));
} else if (CanonEOSR::matches(pDevice)) {
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new Furble::CanonEOSR(pDevice)));
return true;
}

Expand Down
179 changes: 8 additions & 171 deletions lib/furble/CanonEOS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@

namespace Furble {

constexpr uint8_t CanonEOS::MODE_SHOOT;

CanonEOS::CanonEOS(Type type, const void *data, size_t len) : Camera(type) {
if (len != sizeof(eos_t))
abort();
Expand All @@ -29,181 +27,20 @@ CanonEOS::CanonEOS(Type type, const NimBLEAdvertisedDevice *pDevice) : Camera(ty
Device::getUUID128(&m_Uuid);
}

// Handle pairing notification
void CanonEOS::pairCallback(BLERemoteCharacteristic *pBLERemoteCharacteristic,
uint8_t *pData,
size_t length,
bool isNotify) {
if (!isNotify && (length > 0)) {
ESP_LOGI(LOG_TAG, "Got pairing callback: 0x%02x", pData[0]);
m_PairResult = pData[0];
}
void CanonEOS::_disconnect(void) {
m_Client->disconnect();
m_Connected = false;
}

bool CanonEOS::write_value(NimBLEClient *pClient,
const NimBLEUUID &serviceUUID,
bool CanonEOS::writePrefix(const NimBLEUUID &serviceUUID,
const NimBLEUUID &characteristicUUID,
const uint8_t *data,
size_t length) {
NimBLERemoteService *pSvc = pClient->getService(serviceUUID);
if (pSvc) {
NimBLERemoteCharacteristic *pChr = pSvc->getCharacteristic(characteristicUUID);
return ((pChr != nullptr) && pChr->canWrite() && pChr->writeValue(data, length, true));
}

return false;
}

bool CanonEOS::write_prefix(NimBLEClient *pClient,
const NimBLEUUID &serviceUUID,
const NimBLEUUID &characteristicUUID,
const uint8_t prefix,
const uint8_t *data,
size_t length) {
const uint8_t prefix,
const void *data,
uint16_t length) {
uint8_t buffer[length + 1] = {0};
buffer[0] = prefix;
memcpy(&buffer[1], data, length);
return write_value(pClient, serviceUUID, characteristicUUID, buffer, length + 1);
}

/**
* Connect to a Canon EOS.
*
* The EOS uses the 'just works' BLE bonding to pair, all bond management is
* handled by the underlying NimBLE and ESP32 libraries.
*/
bool CanonEOS::_connect(void) {
if (NimBLEDevice::isBonded(m_Address)) {
// Already bonded? Assume pair acceptance!
m_PairResult = PAIR_ACCEPT;
} else {
m_PairResult = 0x00;
}

ESP_LOGI(LOG_TAG, "Connecting");
if (!m_Client->connect(m_Address)) {
ESP_LOGI(LOG_TAG, "Connection failed!!!");
return false;
}

ESP_LOGI(LOG_TAG, "Connected");
m_Progress = 10;

ESP_LOGI(LOG_TAG, "Securing");
if (!m_Client->secureConnection()) {
return false;
}
ESP_LOGI(LOG_TAG, "Secured!");
m_Progress = 20;

NimBLERemoteService *pSvc = m_Client->getService(SVC_IDEN_UUID);
if (pSvc) {
NimBLERemoteCharacteristic *pChr = pSvc->getCharacteristic(CHR_NAME_UUID);
if ((pChr != nullptr) && pChr->canIndicate()) {
ESP_LOGI(LOG_TAG, "Subscribed for pairing indication");
pChr->subscribe(false,
[this](BLERemoteCharacteristic *pChr, uint8_t *pData, size_t length,
bool isNotify) { this->pairCallback(pChr, pData, length, isNotify); });
}
}

ESP_LOGI(LOG_TAG, "Identifying 1!");
const auto name = Device::getStringID();
if (!write_prefix(m_Client, SVC_IDEN_UUID, CHR_NAME_UUID, 0x01, (uint8_t *)name.c_str(),
name.length()))
return false;

m_Progress = 30;

ESP_LOGI(LOG_TAG, "Identifying 2!");
if (!write_prefix(m_Client, SVC_IDEN_UUID, CHR_IDEN_UUID, 0x03, m_Uuid.uint8,
Device::UUID128_LEN))
return false;

m_Progress = 40;

ESP_LOGI(LOG_TAG, "Identifying 3!");
if (!write_prefix(m_Client, SVC_IDEN_UUID, CHR_IDEN_UUID, 0x04, (uint8_t *)name.c_str(),
name.length()))
return false;

m_Progress = 50;

ESP_LOGI(LOG_TAG, "Identifying 4!");

uint8_t x = 0x02;
if (!write_prefix(m_Client, SVC_IDEN_UUID, CHR_IDEN_UUID, 0x05, &x, 1))
return false;

m_Progress = 60;

ESP_LOGI(LOG_TAG, "Identifying 5!");

// Give the user 60s to confirm/deny pairing
ESP_LOGI(LOG_TAG, "Waiting for user to confirm/deny pairing.");
for (unsigned int i = 0; i < 60; i++) {
m_Progress = m_Progress.load() + (i % 2);
if (m_PairResult != 0x00) {
break;
}
vTaskDelay(pdMS_TO_TICKS(1000));
}

if (m_PairResult != PAIR_ACCEPT) {
bool deleted = NimBLEDevice::deleteBond(m_Address);
ESP_LOGW(LOG_TAG, "Rejected, delete pairing: %d", deleted);
return false;
}

ESP_LOGI(LOG_TAG, "Paired!");

/* write to 0xf104 */
x = 0x01;
if (!write_value(m_Client, SVC_IDEN_UUID, CHR_IDEN_UUID, &x, 1))
return false;

m_Progress = 90;

ESP_LOGI(LOG_TAG, "Switching mode!");

/* write to 0xf307 */
if (!write_value(m_Client, SVC_MODE_UUID, CHR_MODE_UUID, &MODE_SHOOT, sizeof(MODE_SHOOT)))
return false;

ESP_LOGI(LOG_TAG, "Done!");
m_Progress = 100;

return true;
}

void CanonEOS::shutterPress(void) {
uint8_t x[2] = {0x00, 0x01};
write_value(m_Client, SVC_SHUTTER_UUID, CHR_SHUTTER_UUID, &x[0], 2);
}

void CanonEOS::shutterRelease(void) {
uint8_t x[2] = {0x00, 0x02};
write_value(m_Client, SVC_SHUTTER_UUID, CHR_SHUTTER_UUID, &x[0], 2);
}

void CanonEOS::focusPress(void) {
// do nothing
return;
}

void CanonEOS::focusRelease(void) {
// do nothing
return;
}

void CanonEOS::updateGeoData(const gps_t &gps, const timesync_t &timesync) {
// do nothing
return;
}

void CanonEOS::_disconnect(void) {
m_Client->disconnect();
m_Connected = false;
return m_Client->setValue(serviceUUID, characteristicUUID, {&buffer[0], (uint16_t)(length + 1)});
}

size_t CanonEOS::getSerialisedBytes(void) const {
Expand Down
52 changes: 8 additions & 44 deletions lib/furble/CanonEOS.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,57 +23,21 @@ class CanonEOS: public Camera {
CanonEOS(Type type, const void *data, size_t len);
CanonEOS(Type type, const NimBLEAdvertisedDevice *pDevice);

const NimBLEUUID SVC_IDEN_UUID {0x00010000, 0x0000, 0x1000, 0x0000d8492fffa821};
/** 0xf108 */
const NimBLEUUID CHR_NAME_UUID {0x00010006, 0x0000, 0x1000, 0x0000d8492fffa821};
/** 0xf104 */
const NimBLEUUID CHR_IDEN_UUID {0x0001000a, 0x0000, 0x1000, 0x0000d8492fffa821};

const NimBLEUUID SVC_MODE_UUID {0x00030000, 0x0000, 0x1000, 0x0000d8492fffa821};
/** 0xf307 */
const NimBLEUUID CHR_MODE_UUID {0x00030010, 0x0000, 0x1000, 0x0000d8492fffa821};

const NimBLEUUID SVC_SHUTTER_UUID {0x00030000, 0x0000, 0x1000, 0x0000d8492fffa821};
/** 0xf311 */
const NimBLEUUID CHR_SHUTTER_UUID {0x00030030, 0x0000, 0x1000, 0x0000d8492fffa821};

static constexpr uint8_t PAIR_ACCEPT = 0x02;
static constexpr uint8_t PAIR_REJECT = 0x03;
static constexpr uint8_t MODE_PLAYBACK = 0x01;
static constexpr uint8_t MODE_SHOOT = 0x02;
static constexpr uint8_t MODE_WAKE = 0x03;

bool write_value(NimBLEClient *pClient,
const NimBLEUUID &serviceUUID,
const NimBLEUUID &characteristicUUID,
const uint8_t *data,
size_t length);

bool write_prefix(NimBLEClient *pClient,
const NimBLEUUID &serviceUUID,
const NimBLEUUID &characteristicUUID,
const uint8_t prefix,
const uint8_t *data,
size_t length);
Device::uuid128_t m_Uuid;

void shutterPress(void) override;
void shutterRelease(void) override;
void focusPress(void) override;
void focusRelease(void) override;
void updateGeoData(const gps_t &gps, const timesync_t &timesync) override;
size_t getSerialisedBytes(void) const override;
bool serialise(void *buffer, size_t bytes) const override;

Device::uuid128_t m_Uuid;

protected:
bool _connect(void) override;
void _disconnect(void) override;
void _disconnect(void) override final;

private:
volatile uint8_t m_PairResult = 0x00;
bool writePrefix(const NimBLEUUID &serviceUUID,
const NimBLEUUID &characteristicUUID,
const uint8_t prefix,
const void *data,
uint16_t length);

void pairCallback(NimBLERemoteCharacteristic *, uint8_t *, size_t, bool);
private:
};

} // namespace Furble
Expand Down
Loading

0 comments on commit 705aa03

Please sign in to comment.