本次实现功能是eps32 的i2c接口获取
BH1750FVI 光强的lux值,分别实现摄影中的3种曝光模式,并可以通过微信小程序蓝牙控制对应的光圈,快门,拍照.曝光模式自动曝光:
AE(自动曝光):自动曝光模式下,相机会根据测光结果自动决定光圈和快门的组合,以获得合适的曝光。用户可以设定曝光补偿来微调相机自动计算的曝光值。光圈优先:
AV(光圈优先自动曝光):在AV模式下,用户可以自行选择光圈大小,相机会自动运算出合适的快门速度以获得正确的曝光。这种模式适合控制景深,例如在拍摄人像时,通过选择大光圈来实现背景虚化的效果。快门优先:
TV(快门优先自动曝光):与AV模式相对应,TV模式允许用户自行选择快门速度,相机会自动调整光圈大小以获得正确的曝光。这种模式适合捕捉动态场景,比如运动摄影,用户可以通过设置快门速度来冻结动作或创造运动模糊的效果。使用说明:
屏幕显示光圈,快门,BH1750FVI获取的lux值,iso曝光信息.右上角会显示曝光模式分别AE,TV,AV,可以使用D0按键进行模式切换.同时可以通过遮挡或者光源照射方式来观察屏幕曝光参数变化.可以按D2按钮进行控制舵机模拟拍摄.同时屏幕会闪屏一下.蓝牙控制通过手机小程序代码链接DigiKey&EEPW 的设备.连接成功以后屏幕背景会变成蓝色.大概这样
同时可以使用手机根据开发板设置的曝光模式不同进行快门,光圈,拍照控制.如下
项目代码:本项目使用arduino作为开发ide,一共涉及到几个模块.按钮 tft屏幕 BH1750FVI 舵机 蓝牙ble ws2812都分别对功能在项目上做了拆分都比较简单.有一些注释.不得不说arduino对于多文件开发是真的一点都不友好...使用到的库
NimBLEDevice
M5_UNIT_8SERVO
M5_DLight
Adafruit-ST7735-Library
Adafruit_NeoPixel
Adafruit_TestBed
目录结构
9959 ◯ tree -L 3 ⏎
.
├── BleCamera
│ ├── BLEManager.cpp
│ ├── BLEManager.h
│ ├── BleCamera.ino
│ ├── ButtonController.cpp
│ ├── ButtonController.h
│ ├── CalculateParams.cpp
│ ├── CalculateParams.h
│ ├── DisplayManager.cpp
│ ├── DisplayManager.h
│ ├── LedController.cpp
│ ├── LedController.h
│ ├── ServoController.cpp
│ └── ServoController.h
└── Toolbox
├── app.js
├── app.json
├── app.wxss
├── components
│ └── navigation-bar
├── images
│ └── bluetooth.png
├── package-lock.json
├── package.json
├── pages
│ ├── ble
│ ├── bt
│ ├── index
│ └── mqtt
├── project.config.json
├── project.private.config.json
├── sitemap.json
└── utils
├── mqtt.min.js
└── util.js
12 directories, 24 files
BleCamera本项目的固件项目Toolbox小程序蓝牙控制项目BleCamera(固件代码)BleCamera.ino 项目主文件
#include <Arduino.h>
#include "CalculateParams.h"
#include "BLEManager.h"
#include "ServoController.h"
#include "DisplayManager.h"
#include "ButtonController.h"
#include "ledController.h"
extern bool deviceConnected;
extern int currentAperture; // 光圈值,如 f/1.8
extern int currentShutterSpeed ; // 快门速度,如 1/1000 秒
extern int currentISO; // ISO 值
extern ExposureMode exposureMode; // 曝光模式
void setup() {
Serial.begin(115200);
buttonInit();
calculateInit();
bleInit();
tftInit();
servoInit();
ledInit();
Serial.println("Start");
}
void loop() {
buttonTick();
calculate();
displayParameters(currentAperture, currentShutterSpeed, readLUX(), currentISO, exposureMode);
delay(10);
}
BLEManager 蓝牙外设
#include <NimBLEDevice.h>
#include <M5_UNIT_8SERVO.h>
#include "DisplayManager.h"
#include "ServoController.h"
NimBLEServer *pServer;
NimBLEService *pService;
NimBLECharacteristic *pPhotoCharacteristic;
NimBLECharacteristic *pApertureCharacteristic;
NimBLECharacteristic *pShutterCharacteristic;
NimBLECharacteristic *pIsoCharacteristic;
// BLE 客户端连接状态
bool deviceConnected = false;
int currentAperture = 18; // 模拟光圈值,如 f/1.8
int currentShutterSpeed = 1000; // 模拟快门速度,如 1/1000 秒
int currentISO = 100; // 模拟 ISO 值
// 服务和特性的 UUID
const char *SERVICE_UUID = "0000fea6-0000-1000-8000-00805f9b34fb";
const char *PHOTO_CHAR_UUID = "b5f90072-aa8d-11e3-9046-0002a5d5c51b";
const char *APERTURE_CHAR_UUID = "0000fea7-0000-1000-8000-00805f9b34fb";
const char *SHUTTER_CHAR_UUID = "0000fea8-0000-1000-8000-00805f9b34fb";
const char *ISO_CHAR_UUID = "0000fea9-0000-1000-8000-00805f9b34fb";
// 通知当前设置
void notifyCurrentSettings() {
if (deviceConnected) {
// 光圈通知
char apertureValue[10];
snprintf(apertureValue, sizeof(apertureValue), "f/%.1f", currentAperture / 10.0);
pApertureCharacteristic->setValue(apertureValue);
pApertureCharacteristic->notify();
// 快门速度通知
char shutterValue[10];
snprintf(shutterValue, sizeof(shutterValue), "1/%d", currentShutterSpeed);
pShutterCharacteristic->setValue(shutterValue);
pShutterCharacteristic->notify();
// ISO 通知
char isoValue[10];
snprintf(isoValue, sizeof(isoValue), "ISO %d", currentISO);
pIsoCharacteristic->setValue(isoValue);
pIsoCharacteristic->notify();
Serial.printf("通知更新: 光圈: %s, 快门速度: %s, ISO: %s\n", apertureValue, shutterValue, isoValue);
}
}
// BLE 服务回调
class ServerCallbacks : public NimBLEServerCallbacks {
void onConnect(NimBLEServer* pServer) {
Serial.println("BLE 客户端已连接");
deviceConnected = true;
notifyCurrentSettings(); // 客户端连接后立即通知当前数据
}
void onDisconnect(NimBLEServer* pServer) {
Serial.println("BLE 客户端已断开连接");
deviceConnected = false;
pServer->startAdvertising(); // 重新开始广播
}
};
// 拍照特性写入回调
class PhotoCharacteristicCallbacks : public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0 && rxValue[0] == 0x03 && rxValue[1] == 0x01) {
Serial.println("执行拍照指令");
Serial.printf("当前光圈: f/%.1f, 当前快门速度: 1/%d 秒, ISO: %d\n",
currentAperture / 10.0, currentShutterSpeed, currentISO);
flashScreen();
shutter();
}
}
};
// 光圈控制特性写入回调
class ApertureCharacteristicCallbacks : public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
currentAperture = (int)rxValue[0];
Serial.printf("设置光圈为: f/%.1f\n", currentAperture / 10.0);
notifyCurrentSettings(); // 更新通知
}
}
};
// 快门控制特性写入回调
class ShutterCharacteristicCallbacks : public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 1) {
currentShutterSpeed = (int)rxValue[0] * 100 + (int)rxValue[1] * 10;
Serial.printf("设置快门速度为: 1/%d 秒\n", currentShutterSpeed);
notifyCurrentSettings(); // 更新通知
}
}
};
// ISO 控制特性写入回调
class IsoCharacteristicCallbacks : public NimBLECharacteristicCallbacks {
void onWrite(NimBLECharacteristic *pCharacteristic) {
std::string rxValue = pCharacteristic->getValue();
if (rxValue.length() > 0) {
currentISO = (int)rxValue[0] * 100;
Serial.printf("设置 ISO 为: %d\n", currentISO);
notifyCurrentSettings(); // 更新通知
}
}
};
// 初始化 BLE
void bleInit() {
NimBLEDevice::init("DigiKey&EEPW");
pServer = NimBLEDevice::createServer();
pServer->setCallbacks(new ServerCallbacks());
// 创建 GoPro 服务
pService = pServer->createService(SERVICE_UUID);
// 创建拍照特性
pPhotoCharacteristic = pService->createCharacteristic(
PHOTO_CHAR_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE
);
pPhotoCharacteristic->setCallbacks(new PhotoCharacteristicCallbacks());
// 创建光圈特性
pApertureCharacteristic = pService->createCharacteristic(
APERTURE_CHAR_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
);
pApertureCharacteristic->setCallbacks(new ApertureCharacteristicCallbacks());
// 创建快门速度特性
pShutterCharacteristic = pService->createCharacteristic(
SHUTTER_CHAR_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
);
pShutterCharacteristic->setCallbacks(new ShutterCharacteristicCallbacks());
// 创建 ISO 特性
pIsoCharacteristic = pService->createCharacteristic(
ISO_CHAR_UUID, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY
);
pIsoCharacteristic->setCallbacks(new IsoCharacteristicCallbacks());
// 启动服务并开始广播
pService->start();
pServer->getAdvertising()->start();
Serial.println("BLE 广播开始,等待客户端连接...");
}
ButtonController 按钮逻辑
#ifndef DISPLAY_HANDLER_H
#define DISPLAY_HANDLER_H
#include "CalculateParams.h"
// ISO 范围限制
#define ISO_MIN 100
#define ISO_MAX 102400
// 快门速度范围
#define SHUTTER_MIN 20 // 最慢 1/20 秒
#define SHUTTER_MAX 6000 // 最快 1/6000 秒
#include <M5_DLight.h>
extern int currentAperture; // 模拟光圈值,如 f/1.8
extern int currentShutterSpeed; // 模拟快门速度,如 1/1000 秒
extern int currentISO; // 模拟 ISO 值
M5_DLight sensor;
uint16_t lux = 0;
ExposureMode exposureMode = AUTO;
// 基础曝光参数结构体
struct ExposureParams {
float aperture; // 光圈值 F值
int shutter; // 快门速度(秒)
int iso; // ISO感光度
float lux; // 环境光照度(lux)
};
void calculateInit() {
sensor.begin();
// if (!sensor.begin()) {
// Serial.println("光照传感器初始化失败");
// while (1);
// }
// Serial.println("光照传感器初始化成功");
sensor.setMode(CONTINUOUSLY_H_RESOLUTION_MODE);
}
uint16_t readLUX() {
lux = sensor.getLUX();
return lux;
}
// 自动曝光计算
ExposureParams calculateAutoExposure(float lux) {
ExposureParams params;
// 基于光照度计算合适的曝光参数
if (lux > 100000) {
params.aperture = 16.0;
params.shutter = 1000;
params.iso = 100;
} else if (lux > 10000) {
params.aperture = 8.0;
params.shutter = 500;
params.iso = 200;
} else if (lux > 1000) {
params.aperture = 5.6;
params.shutter = 250;
params.iso = 400;
} else if (lux > 100) {
params.aperture = 4.0;
params.shutter = 125;
params.iso = 600;
} else if (lux > 10) {
params.aperture = 2.8;
params.shutter = 60;
params.iso = 800;
} else {
params.aperture = 2.0;
params.shutter = 30;
params.iso = 1600;
}
params.lux = lux;
return params;
}
// 光圈优先模式计算
ExposureParams calculateAperturePriority(float lux, float targetAperture) {
ExposureParams params;
params.aperture = targetAperture;
// 基于目标光圈和光照度计算快门速度
float ev = log2(lux / 2.5);
params.shutter = (pow(2, ev) / params.aperture * params.aperture);
// 根据快门速度调整ISO
if (params.shutter < 2000) {
params.iso = 100;
} else if (params.shutter < 500) {
params.iso = 200;
} else if (params.shutter < 125) {
params.iso = 400;
} else {
params.iso = 800;
}
params.lux = lux;
return params;
}
// 快门优先模式计算
ExposureParams calculateShutterPriority(float lux, float targetShutter) {
ExposureParams params;
params.shutter = targetShutter;
// 基于目标快门速度和光照度计算光圈值
float ev = log2(lux / 2.5);
params.aperture = sqrt(pow(2, ev) * targetShutter);
// 根据光圈值调整ISO
if (params.aperture > 11) {
params.iso = 100;
} else if (params.aperture > 8) {
params.iso = 200;
} else if (params.aperture > 4) {
params.iso = 400;
} else {
params.iso = 800;
}
params.lux = lux;
return params;
}
ExposureParams calculateExposure(ExposureMode mode, float lux) {
if (mode == AUTO) {
return calculateAutoExposure(lux);
} else if (mode == APERTURE) {
return calculateAperturePriority(lux, currentAperture / 10.0f);
} else {
return calculateShutterPriority(lux, currentShutterSpeed);
}
}
void calculate() {
ExposureParams params = calculateExposure(exposureMode, lux);
currentAperture = params.aperture * 10;
currentShutterSpeed = params.shutter;
currentISO = params.iso;
Serial.print("当前拍摄模式: ");
Serial.println(exposureMode);
Serial.print("ISO 调整后的 EV 值: ");
Serial.println(params.iso);
Serial.print("快门速度 (T): 1/ ");
Serial.print(params.shutter);
Serial.println(" 秒");
Serial.print("光圈值 (F): ");
Serial.println(params.aperture);
Serial.println("------------------------");
}
#endif
CalculateParams.cpp lux计算逻辑
#ifndef DISPLAY_HANDLER_H
#define DISPLAY_HANDLER_H
#include "CalculateParams.h"
// ISO 范围限制
#define ISO_MIN 100
#define ISO_MAX 102400
// 快门速度范围
#define SHUTTER_MIN 20 // 最慢 1/20 秒
#define SHUTTER_MAX 6000 // 最快 1/6000 秒
#include <M5_DLight.h>
extern int currentAperture; // 模拟光圈值,如 f/1.8
extern int currentShutterSpeed; // 模拟快门速度,如 1/1000 秒
extern int currentISO; // 模拟 ISO 值
M5_DLight sensor;
uint16_t lux = 0;
ExposureMode exposureMode = AUTO;
// 基础曝光参数结构体
struct ExposureParams {
float aperture; // 光圈值 F值
int shutter; // 快门速度(秒)
int iso; // ISO感光度
float lux; // 环境光照度(lux)
};
void calculateInit() {
sensor.begin();
// if (!sensor.begin()) {
// Serial.println("光照传感器初始化失败");
// while (1);
// }
// Serial.println("光照传感器初始化成功");
sensor.setMode(CONTINUOUSLY_H_RESOLUTION_MODE);
}
uint16_t readLUX() {
lux = sensor.getLUX();
return lux;
}
// 自动曝光计算
ExposureParams calculateAutoExposure(float lux) {
ExposureParams params;
// 基于光照度计算合适的曝光参数
if (lux > 100000) {
params.aperture = 16.0;
params.shutter = 1000;
params.iso = 100;
} else if (lux > 10000) {
params.aperture = 8.0;
params.shutter = 500;
params.iso = 200;
} else if (lux > 1000) {
params.aperture = 5.6;
params.shutter = 250;
params.iso = 400;
} else if (lux > 100) {
params.aperture = 4.0;
params.shutter = 125;
params.iso = 600;
} else if (lux > 10) {
params.aperture = 2.8;
params.shutter = 60;
params.iso = 800;
} else {
params.aperture = 2.0;
params.shutter = 30;
params.iso = 1600;
}
params.lux = lux;
return params;
}
// 光圈优先模式计算
ExposureParams calculateAperturePriority(float lux, float targetAperture) {
ExposureParams params;
params.aperture = targetAperture;
// 基于目标光圈和光照度计算快门速度
float ev = log2(lux / 2.5);
params.shutter = (pow(2, ev) / (params.aperture * params.aperture));
// 根据快门速度调整ISO
if (params.shutter < 2000) {
params.iso = 100;
} else if (params.shutter < 500) {
params.iso = 200;
} else if (params.shutter < 125) {
params.iso = 400;
} else {
params.iso = 800;
}
params.lux = lux;
return params;
}
// 快门优先模式计算
ExposureParams calculateShutterPriority(float lux, float targetShutter) {
ExposureParams params;
params.shutter = targetShutter;
// 基于目标快门速度和光照度计算光圈值
float ev = log2(lux / 2.5);
params.aperture = sqrt(pow(2, ev) * targetShutter);
// 根据光圈值调整ISO
if (params.aperture > 11) {
params.iso = 100;
} else if (params.aperture > 8) {
params.iso = 200;
} else if (params.aperture > 4) {
params.iso = 400;
} else {
params.iso = 800;
}
params.lux = lux;
return params;
}
ExposureParams calculateExposure(ExposureMode mode, float lux) {
if (mode == AUTO) {
return calculateAutoExposure(lux);
} else if (mode == APERTURE) {
return calculateAperturePriority(lux, currentAperture);
} else {
return calculateShutterPriority(lux, currentShutterSpeed);
}
}
void calculate() {
ExposureParams params = calculateExposure(exposureMode, lux);
currentAperture = params.aperture * 10;
currentShutterSpeed = params.shutter;
currentISO = params.iso;
Serial.print("当前拍摄模式: ");
Serial.println(exposureMode);
Serial.print("ISO 调整后的 EV 值: ");
Serial.println(params.iso);
Serial.print("快门速度 (T): 1/ ");
Serial.print(params.shutter);
Serial.println(" 秒");
Serial.print("光圈值 (F): ");
Serial.println(params.aperture);
Serial.println("------------------------");
}
#endif
DisplayManager.cpp 显示处理逻辑
#include <Adafruit_GFX.h> // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <SPI.h>
#include "CalculateParams.h"
extern bool deviceConnected;
// Use dedicated hardware SPI pins
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);
GFXcanvas16 canvas(240, 135);
void tftInit() {
Serial.print(F("Hello! Feather TFT Test"));
// turn on backlite
pinMode(TFT_BACKLITE, OUTPUT);
digitalWrite(TFT_BACKLITE, HIGH);
// turn on the TFT / I2C power supply
pinMode(TFT_I2C_POWER, OUTPUT);
digitalWrite(TFT_I2C_POWER, HIGH);
delay(10);
// initialize TFT
tft.init(135, 240); // Init ST7789 240x135
tft.setRotation(3);
tft.fillScreen(ST77XX_GREEN);
Serial.println(F("Initialized"));
}
void flashScreen() {
tft.fillScreen(ST77XX_WHITE);
delay(500);
}
void displayParameters(int aperture, int shutter_speed, int lux, int iso, ExposureMode exposureMode) {
static int lastAperture = -1;
static int lastShutterSpeed = -1;
static unsigned int lastLux = 0;
static float lastIso = -1;
static bool lastDeviceConnected = false;
static ExposureMode lastExposureMode = AUTO;
if (lastAperture != aperture || lastShutterSpeed != shutter_speed || lastLux != lux || lastIso != iso || lastDeviceConnected != deviceConnected || lastExposureMode != exposureMode) {
lastAperture = aperture;
lastShutterSpeed = shutter_speed;
lastLux = lux;
lastIso = iso;
lastDeviceConnected = deviceConnected;
lastExposureMode = exposureMode;
// 使用 canvas 清除背景
canvas.fillScreen(deviceConnected ? ST77XX_BLUE : ST77XX_BLACK);
// 设置字体颜色和大小
canvas.setTextColor(ST77XX_WHITE);
canvas.setTextSize(2);
// 显示光圈值
canvas.setCursor(10, 5);
canvas.printf("Aperture: f/%.1f", aperture / 10.0f);
// 显示快门速度
canvas.setCursor(10, 35);
canvas.printf("Shutter: 1/%d", shutter_speed);
// 显示 Lux 值
canvas.setCursor(10, 65);
canvas.printf("Lux: %u", lux);
// 显示 ISO 值
canvas.setCursor(10, 95);
canvas.printf("ISO: %d", iso);
// 在右上角显示曝光模式
const char* modeStr = "";
uint16_t bgColor = ST77XX_BLACK; // 默认背景色
switch (exposureMode) {
case AUTO:
modeStr = "AE"; // 自动曝光
bgColor = ST77XX_RED; // 红色背景
break;
case APERTURE:
modeStr = "AV"; // 光圈优先
bgColor = ST77XX_GREEN; // 绿色背景
break;
case SHUTTER:
modeStr = "TV"; // 快门优先
bgColor = ST77XX_MAGENTA; // 紫色背景
break;
}
// 设置右上角曝光模式背景色
canvas.fillRect(240 - 40, 0, 40, 20, bgColor);
canvas.setCursor(240 - 35, 5); // 设置文本位置
canvas.setTextColor(ST77XX_WHITE); // 文字为白色
canvas.setTextSize(2);
canvas.print(modeStr);
// 将 canvas 的内容绘制到屏幕
tft.drawRGBBitmap(0, 0, canvas.getBuffer(), 240, 135);
}
}
LedController.cpp
#include <Adafruit_NeoPixel.h>
#include "Adafruit_TestBed.h"
extern Adafruit_TestBed TB;
void ledInit() {
TB.neopixelPin = PIN_NEOPIXEL;
TB.neopixelNum = 1;
TB.begin();
}
void ledOn() {
TB.setColor(WHITE);
}
ServoController.cpp
#include "ServoController.h"
#include <M5_UNIT_8SERVO.h>
M5_UNIT_8SERVO unit_8servo;
void servoInit() {
unit_8servo.begin();
unit_8servo.setAllPinMode(SERVO_CTL_MODE);
}
void rotateServos(int angle) {
for (uint8_t i = 0; i < 8; i++) {
unit_8servo.setServoAngle(i, angle);
}
}
void shutter() {
unit_8servo.setServoAngle(1, 180);
unit_8servo.setServoAngle(2, 180);
vTaskDelay(500); // 等待 500ms 防止重复触发
unit_8servo.setServoAngle(1, 0); // 设定所有舵机回正到 0 度
unit_8servo.setServoAngle(2, 0); // 设定所有舵机回正到 0 度
}
Toolbox(小程序代码)bt/device/ device.js
const app = getApp()
const apertureList = [18, 20, 22, 28, 32, 40, 56, 80, 110, 160]; // 光圈列表 除以10
const shutterSpeedList = [1, 2, 4, 8, 15, 30, 60, 125, 250, 500, 1000, 2000, 4000]; // 快门速度列表 (单位: n分之1/秒)
// 当前光圈和快门速度的索引
let currentApertureIndex = 0;
let currentShutterSpeedIndex = 0;
// 获取下一个光圈值
function nextAperture() {
currentApertureIndex = (currentApertureIndex + 1) % apertureList.length; // 循环列表
const apertureValue = apertureList[currentApertureIndex];
console.log(`当前光圈值: f/${apertureValue}`);
return apertureValue;
}
// 获取下一个快门速度值
function nextShutterSpeed() {
currentShutterSpeedIndex = (currentShutterSpeedIndex + 1) % shutterSpeedList.length; // 循环列表
const shutterSpeedValue = shutterSpeedList[currentShutterSpeedIndex];
console.log(`当前快门速度: 1/${shutterSpeedValue} 秒`);
return shutterSpeedValue;
}
Page({
data: {
title: "测光笔",
inputText: 'mootek',
receiveText: '',
name: '',
connectedDeviceId: '',
services: [],
characteristics: [],
connected: true,
serviceId: "0000FEA6-0000-1000-8000-00805F9B34FB",
apertureCharId: '0000FEA7-0000-1000-8000-00805F9B34FB', // 光圈控制特性 UUID
shutterCharId: '0000FEA8-0000-1000-8000-00805F9B34FB', // 快门控制特性 UUID
photoCharId: 'B5F90072-AA8D-11E3-9046-0002A5D5C51B', // 拍照特性 UUID
isoCharId: "0000FEA9-0000-1000-8000-00805F9B34FB",
aperture: "f/1.2",
shutter: "1/4000",
iso: "ISO 100",
},
onLoad(options) {
const { connectedDeviceId, name } = options;
this.setData({
name: decodeURIComponent(name),
connectedDeviceId,
});
// 获取蓝牙服务
this.getBLEDeviceServices();
wx.onBLEConnectionStateChange((res) => {
console.log("蓝牙连接状态:", res.connected);
this.setData({ connected: res.connected });
});
},
// 获取蓝牙服务
getBLEDeviceServices() {
const { connectedDeviceId } = this.data;
wx.getBLEDeviceServices({ deviceId: connectedDeviceId })
.then( (res) => {
console.log("服务列表:", res.services);
this.setData({ services: res.services });
this.getCharacteristics(res.services[0].uuid);
})
.catch((err) => {
console.error("获取服务失败:", err);
})
},
// 获取特征值
getCharacteristics(serviceId) {
const { connectedDeviceId } = this.data;
wx.getBLEDeviceCharacteristics({ deviceId: connectedDeviceId, serviceId })
.then((res) => {
console.log("特征值列表:", res.characteristics);
this.setData({ characteristics: res.characteristics });
// 启用需要监听的特征值
this.enableNotify(this.data.isoCharId);
this.enableNotify(this.data.apertureCharId);
this.enableNotify(this.data.shutterCharId);
})
.catch((err) => {
console.error("获取特征值失败:", err);
});
},
// 启用 Notify
enableNotify(characteristicId) {
const { connectedDeviceId, services } = this.data;
wx.notifyBLECharacteristicValueChange({
state: true,
deviceId: connectedDeviceId,
serviceId: services[0].uuid,
characteristicId
})
.then(() => {
console.log(`启用通知成功:${characteristicId}`);
})
.catch((err) => {
console.error(`启用通知失败:${characteristicId}`, err);
});
},
// 写入特征值
writeCharacteristic(characteristicId, buffer) {
const { connectedDeviceId, serviceId } = this.data;
wx.writeBLECharacteristicValue({ deviceId: connectedDeviceId, serviceId, characteristicId, value: buffer })
.then(() => {
console.log(`写入特征值成功:${characteristicId}`);
})
.catch((err) => {
console.error(`写入特征值失败:${characteristicId}`, err);
});
},
// 设置光圈
setAperture(apertureValue) {
const buffer = new ArrayBuffer(1);
new DataView(buffer).setUint8(0, apertureValue);
this.writeCharacteristic(this.data.apertureCharId, buffer);
console.log(`光圈设置为 f/${apertureValue / 10}`);
},
// 设置快门速度
setShutterSpeed(shutterSpeed) {
const buffer = new ArrayBuffer(2);
const dataView = new DataView(buffer);
dataView.setUint8(0, Math.floor(shutterSpeed / 100));
dataView.setUint8(1, shutterSpeed % 100);
this.writeCharacteristic(this.data.shutterCharId, buffer);
console.log(`快门速度设置为 1/${shutterSpeed} 秒`);
},
// 拍照
takePhoto() {
const buffer = new ArrayBuffer(2);
const dataView = new DataView(buffer);
dataView.setUint8(0, 0x03);
dataView.setUint8(1, 0x01);
this.writeCharacteristic(this.data.photoCharId, buffer);
console.log("拍照指令已发送");
},
handleNextAperture(){
this.setAperture(nextAperture());
},
handleNextShutterSpeed(){
this.setShutterSpeed(nextShutterSpeed());
},
handleTakePhoto(){
this.takePhoto();
},
onReady: function () {
wx.onBLECharacteristicValueChange((res) => {
const receiveText = app.buf2string(res.value);
console.log("特征值变化:", receiveText);
if(res.characteristicId === this.data.apertureCharId){
this.setData({ aperture: receiveText });
} else if (res.characteristicId === this.data.isoCharId){
this.setData({ iso: receiveText });
} else if (res.characteristicId === this.data.shutterCharId){
this.setData({ shutter: receiveText });
}
});
}
});
bt/search/ search.js
const app = getApp()
Page({
data: {
title: "测光笔",
list_height: wx.getWindowInfo().safeArea.height - 88,
searching: false,
devicesList: []
},
handleSearch() {
if (!this.data.searching) {
wx.closeBluetoothAdapter()
.then(console.log)
.catch(console.log)
wx.openBluetoothAdapter()
.then(() => {
return wx.getBluetoothAdapterState().then(console.log)
})
.then(() => wx.startBluetoothDevicesDiscovery())
.then((res) => {
console.log(res)
this.setData({
searching: true,
devicesList: []
})
}).catch((err) => {
console.log(err)
wx.showModal({
title: '提示',
content: '请检查手机蓝牙是否打开',
showCancel: false,
success(res) {
wx.openAppAuthorizeSetting().then(console.log).catch(console.log)
// wx.openSystemBluetoothSetting().then(console.log).catch(console.log)
this.setData({
searching: false
})
}
})
})
} else {
wx.stopBluetoothDevicesDiscovery().then(res => {
console.log(res)
this.setData({
searching: false
})
})
}
},
handleConnect(e) {
wx.stopBluetoothDevicesDiscovery({
success: (res) => {
console.log(res)
this.setData({
searching: false
})
}
})
wx.showLoading({
title: '连接蓝牙设备中...',
})
const { id, dataset } = e.currentTarget;
wx.createBLEConnection({
deviceId: id,
success(res) {
console.log("连接蓝牙设备成功" + res)
wx.hideLoading()
wx.showToast({
title: '连接成功',
icon: 'success',
duration: 1000
})
wx.navigateTo({
url: '../device/device?connectedDeviceId=' + e.currentTarget.id + '&name=' + encodeURIComponent(dataset.name)
})
},
fail(res) {
console.log(res)
wx.hideLoading()
wx.showModal({
title: '提示',
content: '连接失败',
showCancel: false
})
}
})
},
onLoad() {
wx.authorize({ scope: 'scope.bluetooth' })
.then(({ errMsg }) => {
if (errMsg !== 'authorize:ok') {
return wx.openSetting()
}
}).catch(console.log)
wx.onBluetoothAdapterStateChange((res) => {
console.log(res)
this.setData({
searching: res.discovering
})
if (!res.available) {
this.setData({
searching: false
})
}
})
wx.onBluetoothDeviceFound((devices) => {
devices.devices
.filter(v => v.name.trim())
.forEach(newDevice => {
// 转换 `advertisData` 为 Hex 字符串
newDevice.advertisData = newDevice.advertisData
? app.buf2hex(newDevice.advertisData)
: '';
console.log("发现新设备:", newDevice);
// 检查设备是否已存在于列表中
const isExist = this.data.devicesList.some(
(item) => item.deviceId === newDevice.deviceId
);
// 不存在则添加到设备列表
if (!isExist) {
this.data.devicesList.push(newDevice);
this.setData({
devicesList: this.data.devicesList,
});
}
})
});
},
onHide() {
this.setData({
devicesList: []
})
if (this.data.searching) {
wx.stopBluetoothDevicesDiscovery().then((res) => {
console.log(res)
this.setData({
searching: false
})
})
}
}
})
项目源码:https://github.com/netseye/Let-sDo3演示视频:(等转接板到了在进行完善)