Skip to content

Commit

Permalink
Implement Nikon remote support.
Browse files Browse the repository at this point in the history
Reverse engineering shows the remote uses a simplified version of the
smart device 4 stage handshake.
In particular, the camera does not require the scrambled/encrypted
device ID exchange.

Remote pairing is now mostly working, able to scan and connect and send
shutter release.
Remote controller mode does not support focus.
Remote controller mode does not appear to use GPS, although the
characteristic is active and accepts the write.

Currently using FreeRTOS queues instead of task notifications, something weird
going on with causing crashes with NimBLE stack.
  • Loading branch information
gkoh committed Feb 5, 2025
1 parent dc504fc commit b92c3cf
Show file tree
Hide file tree
Showing 14 changed files with 558 additions and 29 deletions.
24 changes: 17 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ The following devices have been tested and confirmed to work:
- 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))
- Nikon
- Nikon COOLPIX B600
- Mobile Devices (beta)
- Android
- iOS
Expand Down Expand Up @@ -65,12 +67,12 @@ Currently supported features in `furble`:

### Table of Features

|   | 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: |
| GPS location tagging | ✔️ | :x: (WiFi only) | :x: | :x: |
|   | Fujifilm X & GFX | Canon EOS M6 | Canon EOS R | Nikon COOLPIX | Android & iOS |
| --- | :---: | :---: | :---: | :---: | :---: |
| Scanning & Pairing | ✔️ | ✔️ | ✔️ | ✔️ | ✔️ |
| Shutter Release | ✔️ | ✔️ | ✔️ | ✔️ | ✔️|
| Focus | ✔️ (see [#99](https://github.com/gkoh/furble/discussions/99)) | :x: | ✔️ | :x: | :x: |
| GPS location tagging | ✔️ | :x: (WiFi only) | :x: | :x: | :x: |

## Installation

Expand Down Expand Up @@ -309,6 +311,15 @@ different.
@wolcano kindly implemented initial support for the Canon EOS RP.
@hijae kindly helped with better Canon EOS R support.

#### Nikon

Nikon cameras that support the remote wireless controller (ML-L7) should work,
use the "Connection to remote" menu option.
This has been tested on a Nikon COOLPIX B600. Unfortunately, the remote wireless
mode has no support for GPS or focus functions, thus only shutter release works.
Note that other Nikon cameras will appear in the scan, but will not pair
(further support is under investigation).

#### Protocol Reverse Engineering

Android supports snooping bluetooth traffic so it was trivial to grab a HCI log
Expand Down Expand Up @@ -382,7 +393,6 @@ the following libraries:
- Complete support for newer Canon EOS (eg. RP)
- Get access to and support the following:
- Sony
- Nikon
- Others?

# Links
Expand Down
3 changes: 2 additions & 1 deletion lib/furble/Camera.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Furble {

Camera::Camera(Type type) : m_Type(type) {
Camera::Camera(Type type, PairType pairType) : m_PairType(pairType), m_Type(type) {
m_Client = NimBLEDevice::createClient();
}

Expand All @@ -26,6 +26,7 @@ bool Camera::connect(esp_power_level_t power) {
}
m_Connected = true;
} else {
this->_disconnect();
m_Connected = false;
}

Expand Down
9 changes: 8 additions & 1 deletion lib/furble/Camera.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@ class Camera {
CANON_EOS_R = 3,
MOBILE_DEVICE = 4,
FAUXNY = 5,
NIKON = 6,
};

enum class PairType : uint8_t {
NEW = 1,
SAVED = 2,
};

/**
Expand Down Expand Up @@ -116,7 +122,7 @@ class Camera {
uint8_t getConnectProgress(void) const;

protected:
Camera(Type type);
Camera(Type type, PairType pairType);
std::atomic<uint8_t> m_Progress;

/**
Expand All @@ -134,6 +140,7 @@ class Camera {
*/
virtual void _disconnect(void) = 0;

const PairType m_PairType;
NimBLEAddress m_Address = NimBLEAddress {};
NimBLEClient *m_Client;
std::string m_Name;
Expand Down
7 changes: 7 additions & 0 deletions lib/furble/CameraList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include "FauxNY.h"
#include "Fujifilm.h"
#include "MobileDevice.h"
#include "Nikon.h"

#include "CameraList.h"

Expand Down Expand Up @@ -164,6 +165,9 @@ void CameraList::load(void) {
case Camera::Type::FAUXNY:
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new FauxNY(dbuffer, dbytes)));
break;
case Camera::Type::NIKON:
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new Nikon(dbuffer, dbytes)));
break;
}
}
m_Prefs.end();
Expand Down Expand Up @@ -203,6 +207,9 @@ bool CameraList::match(const NimBLEAdvertisedDevice *pDevice) {
} else if (CanonEOSR::matches(pDevice)) {
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new Furble::CanonEOSR(pDevice)));
return true;
} else if (Nikon::matches(pDevice)) {
m_ConnectList.push_back(std::unique_ptr<Furble::Camera>(new Furble::Nikon(pDevice)));
return true;
}

return false;
Expand Down
4 changes: 2 additions & 2 deletions lib/furble/CanonEOS.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

namespace Furble {

CanonEOS::CanonEOS(Type type, const void *data, size_t len) : Camera(type) {
CanonEOS::CanonEOS(Type type, const void *data, size_t len) : Camera(type, PairType::SAVED) {
if (len != sizeof(eos_t))
abort();

Expand All @@ -19,7 +19,7 @@ CanonEOS::CanonEOS(Type type, const void *data, size_t len) : Camera(type) {
memcpy(&m_Uuid, &eos->uuid, sizeof(Device::uuid128_t));
}

CanonEOS::CanonEOS(Type type, const NimBLEAdvertisedDevice *pDevice) : Camera(type) {
CanonEOS::CanonEOS(Type type, const NimBLEAdvertisedDevice *pDevice) : Camera(type, PairType::NEW) {
m_Name = pDevice->getName();
m_Address = pDevice->getAddress();
ESP_LOGI(LOG_TAG, "Name = %s", m_Name.c_str());
Expand Down
4 changes: 2 additions & 2 deletions lib/furble/FauxNY.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

namespace Furble {

FauxNY::FauxNY(const void *data, size_t len) : Camera(Type::FAUXNY) {
FauxNY::FauxNY(const void *data, size_t len) : Camera(Type::FAUXNY, PairType::SAVED) {
if (len != sizeof(fauxNY_t)) {
abort();
}
Expand All @@ -15,7 +15,7 @@ FauxNY::FauxNY(const void *data, size_t len) : Camera(Type::FAUXNY) {
m_Address = NimBLEAddress(m_ID, 0);
}

FauxNY::FauxNY(void) : Camera(Type::FAUXNY) {
FauxNY::FauxNY(void) : Camera(Type::FAUXNY, PairType::NEW) {
m_ID = esp_random();
m_Name = std::string("FauxNY-") + std::to_string(m_ID % 42);
m_Address = NimBLEAddress(m_ID, 0);
Expand Down
14 changes: 3 additions & 11 deletions lib/furble/Fujifilm.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ void Fujifilm::notify(BLERemoteCharacteristic *pChr, uint8_t *pData, size_t leng
ESP_LOGI(LOG_TAG, "Got %s callback: %u bytes from %s", isNotify ? "notification" : "indication",
length, pChr->getUUID().toString().c_str());
if (length > 0) {
for (int i = 0; i < length; i++) {
ESP_LOGI(LOG_TAG, " [%d] 0x%02x", i, pData[i]);
}
ESP_LOGI(LOG_TAG, " %s", NimBLEUtils::dataToHexString(pData, length).c_str());
}

if (pChr->getUUID() == CHR_NOT1_UUID) {
Expand Down Expand Up @@ -59,7 +57,7 @@ bool Fujifilm::subscribe(const NimBLEUUID &svc, const NimBLEUUID &chr, bool noti
true);
}

Fujifilm::Fujifilm(const void *data, size_t len) : Camera(Type::FUJIFILM) {
Fujifilm::Fujifilm(const void *data, size_t len) : Camera(Type::FUJIFILM, PairType::SAVED) {
if (len != sizeof(fujifilm_t))
abort();

Expand All @@ -69,7 +67,7 @@ Fujifilm::Fujifilm(const void *data, size_t len) : Camera(Type::FUJIFILM) {
memcpy(m_Token.data(), fujifilm->token, TOKEN_LEN);
}

Fujifilm::Fujifilm(const NimBLEAdvertisedDevice *pDevice) : Camera(Type::FUJIFILM) {
Fujifilm::Fujifilm(const NimBLEAdvertisedDevice *pDevice) : Camera(Type::FUJIFILM, PairType::NEW) {
const char *data = pDevice->getManufacturerData().data();
m_Name = pDevice->getName();
m_Address = pDevice->getAddress();
Expand Down Expand Up @@ -256,12 +254,6 @@ void Fujifilm::updateGeoData(const gps_t &gps, const timesync_t &timesync) {
}
}

void Fujifilm::print(void) {
ESP_LOGI(LOG_TAG, "Name: %s", m_Name.c_str());
ESP_LOGI(LOG_TAG, "Address: %s", m_Address.toString().c_str());
ESP_LOGI(LOG_TAG, "Type: %d", m_Address.getType());
}

void Fujifilm::_disconnect(void) {
m_Client->disconnect();
m_Connected = false;
Expand Down
1 change: 0 additions & 1 deletion lib/furble/Fujifilm.h
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,6 @@ class Fujifilm: public Camera {
static constexpr std::array<uint8_t, 2> SHUTTER_FOCUS = {0x03, 0x00};

void print_token(const std::array<uint8_t, TOKEN_LEN> &token);
void print(void);
void notify(NimBLERemoteCharacteristic *, uint8_t *, size_t, bool);
bool subscribe(const NimBLEUUID &svc, const NimBLEUUID &chr, bool notifications);
void sendGeoData(const gps_t &gps, const timesync_t &timesync);
Expand Down
5 changes: 3 additions & 2 deletions lib/furble/MobileDevice.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

namespace Furble {

MobileDevice::MobileDevice(const void *data, size_t len) : Camera(Type::MOBILE_DEVICE) {
MobileDevice::MobileDevice(const void *data, size_t len)
: Camera(Type::MOBILE_DEVICE, PairType::SAVED) {
if (len != sizeof(mobile_device_t))
abort();

Expand All @@ -22,7 +23,7 @@ MobileDevice::MobileDevice(const void *data, size_t len) : Camera(Type::MOBILE_D
}

MobileDevice::MobileDevice(const NimBLEAddress &address, const std::string &name)
: Camera(Type::MOBILE_DEVICE) {
: Camera(Type::MOBILE_DEVICE, PairType::NEW) {
m_Name = name;
m_Address = address;
m_HIDServer = HIDServer::getInstance();
Expand Down
Loading

0 comments on commit b92c3cf

Please sign in to comment.