Bài viết gốc từ www.jaredwolff.com
Tôi từng thất bại. Cả đêm debug project Bluetooth Low Energy (BLE). Đau khổ, bực bội. Sắp bỏ cuộc.
Đó là những ngày đầu BLE. Giờ dễ dàng hơn rất nhiều. Particle Mesh Bluetooth Library không ngoại lệ.
Hướng dẫn này chỉ cách dùng Bluetooth API của Particle. Cấu hình LED và quan sát thay đổi trên tất cả thiết bị trong mạng Mesh. Dùng board Argon và Xenon.
Sẵn sàng? Bắt đầu!
P.S. Bài viết dài. Muốn tải PDF đẹp mắt, click đây.
Giai Đoạn 1: Thiết Lập Bluetooth
- Tải/cài Particle Workbench
- Tạo project mới. Chọn vị trí, đặt tên
ble_mesh - Vào thư mục
/src/mở file<project name>.ino - Đảm bảo deviceOS version > 1.3.0
Viết Code
Thiết lập service với 3 characteristic. Mỗi characteristic tương ứng cường độ LED RGB. Cách setup Bluetooth:
- Trong
Setup()bật điều khiển app cho LED:RGB.control(true); - Định nghĩa UUID đầu file
.ino:const char* serviceUuid = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E"; const char* red = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"; const char* green = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"; const char* blue = "6E400004-B5A3-F393-E0A9-E50E24DCCA9E";UUID = định danh duy nhất. Phân biệt service/characteristic trên thiết bị.
UUID trên dùng trong ví dụ Particle cũ. Tự tạo UUID:
uuidgentrên OSX command line hoặc web Online GUID Generator.
Dùng setting trên để tự tạo UUID. Tạo service/characteristic UUID từ UUID sinh ra:
const char* serviceUuid = "b425040**0**-fb4b-4746-b2b0-93f0e61122c6"; //service const char* red = "b4250401-fb4b-4746-b2b0-93f0e61122c6"; //red char const char* green = "b4250402-fb4b-4746-b2b0-93f0e61122c6"; //green char const char* blue = "b4250403-fb4b-4746-b2b0-93f0e61122c6"; //blue charKhông có đúng/sai. Chỉ tránh UUID reserved bởi Bluetooth SIG (hiếm). Check tại đây và tại đây nếu muốn. Tạm dùng set UUID đầu tiên.
- Trong
Setup(), khởi tạo service:BleUuid rgbService(serviceUuid);Bước đầu "đăng ký" service. Chi tiết sau.
- Khởi tạo từng characteristic trong
Setup():BleCharacteristic redCharacteristic("red", BleCharacteristicProperty::WRITE_WO_RSP, red, serviceUuid, onDataReceived, (void*)red); BleCharacteristic greenCharacteristic("green", BleCharacteristicProperty::WRITE_WO_RSP, green, serviceUuid, onDataReceived, (void*)green); BleCharacteristic blueCharacteristic("blue", BleCharacteristicProperty::WRITE_WO_RSP, blue, serviceUuid, onDataReceived, (void*)blue);Dùng property
WRITE_WO_RSP(ghi không cần response). Tham số 1: tên characteristic. Tham số 2: property. Tham số 3: characteristic UUID. Tham số 4: service UUID. Tham số 5: callback function khi ghi data. Tham số 6: context.Callback dùng chung cho 3 characteristic. Cách biết characteristic nào được ghi (trong deviceOS) là qua context. Dùng chính UUID đã có.
- Sau khi định nghĩa characteristic, thêm để hiển thị:
BLE.addCharacteristic(redCharacteristic); BLE.addCharacteristic(greenCharacteristic); BLE.addCharacteristic(blueCharacteristic); - Setup callback function (đặt đầu file, trên
Setup()):static void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) { }Sẽ định nghĩa chi tiết sau.
- Cuối
Setup(), setup advertising để thiết bị có thể kết nối:BleAdvertisingData advData; advData.appendServiceUUID(rgbService); BLE.advertise(&advData);Tạo object
BleAdvertisingData. ThêmrgbService(bước 3). Bắt đầu advertising – service và characteristic có thể khám phá được!
Test
Lúc này có chương trình tối thiểu. Compile nạp vào hardware Particle. Hoạt động trên mọi thiết bị Mesh-enabled (Xenon, Argon, Boron).
- Tạm thêm
SYSTEM_MODE(MANUAL);đầu file. Ngăn kết nối mesh network. Nếu LED nháy xanh khi khởi động, setup qua Particle App trước. - Tải image 1.3.0-rc.1 tại đây. Xenon cần xenon-system-part1@1.3.0-rc.1.bin. Các board khác tìm boron-system-part1@1.3.0-rc.1.bin và argon-system-part1@1.3.0-rc.1.bin. File ở cuối trang dưới Assets.
- Đưa thiết bị vào DFU mode: giữ Mode Button, nhấn chút Reset Button. Giữ Mode cho đến khi LED nháy vàng.
- Command line chuyển thư mục chứa file tải. Ví dụ:
cd ~/Downloads/ - Chạy:
particle flash --usb xenon-system-part1@1.3.0-rc.1.bin. Cài OS mới cho Xenon. Xong vẫn nháy vàng nhanh. Board khác đổi tên file cho khớp. - Visual Code: Command + Shift + P → Particle: Compile application (local)
- Fix lỗi nếu có.
- Command menu → Flash application (local)
- Khi nạp xong, lấy điện thoại. Mở app BLE scanner. Tốt nhất: NRF Connect và Light Blue Explorer. Dùng Light Blue Explorer.
- Kiểm tra thiết bị tên "Xenon-<id>" đang advertising. Thay <id> bằng ID thiết bị.
- Click tên thiết bị.
- Xem danh sách service & characteristic. Có UUID service/characteristic đã set không? Ví dụ service UUID 6E400001-B5A3-F393-E0A9-E50E24DCCA9E?
- Nếu đúng như mong đợi, OK. Không thì kiểm tra lại hướng dẫn trước.
Giai Đoạn 2: Xử Lý Dữ Liệu
Tiếp theo: xử lý event ghi (write). Cập nhật function onDataReceived.
Viết Code
- Tạo struct lưu trạng thái LED (đầu file):
typedef struct { uint8_t red; uint8_t green; uint8_t blue; } led_level_t; - Tạo static variable kiểu này:
static led_level_t m_led_level;Hai bước cho phép dùng 1 biến duy nhất biểu diễn 3 giá trị RGB.
- Kiểm tra lỗi cơ bản trong
onDataReceive. Chỉ nhận 1 byte:if( len != 1 ) { return; } - Xác định characteristic nào sinh event qua biến
context:if( context == red ) { m_led_level.red = data[0]; } else if ( context == green ) { m_led_level.green = data[0]; } else if ( context == blue ) { m_led_level.blue = data[0]; }Context = con trỏ string UUID red/green/blue. Set
m_led_levelđể cập nhật RGB LED ngay cả khi chỉ 1 giá trị thay đổi. - Ghi vào object
RGB:RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);
Test Code
Lặp quy trình flash như trước.
- Đưa thiết bị DFU mode (giữ Mode, nhấn Reset, giữ Mode đến khi LED nháy vàng).
- Command menu → Flash application (local)
- Khi nạp xong, kết nối thiết bị bằng Light Blue Explorer.
- Chạm characteristic LED đỏ.
- Ghi FF. LED đỏ sáng.
- Ghi 00. LED đỏ tắt.
- Làm tương tự 2 characteristic còn lại. Đã có điều khiển đầy đủ RGB LED qua BLE!
Giai Đoạn 3: Chia Sẻ Qua Mesh
Đã nhận BLE message thành công, giờ forward lên mạng Mesh.
Viết Code
- Bỏ MANUAL mode: comment
SYSTEM_MODE(MANUAL); - Đầu file thêm biến theo dõi cần publish:
static bool m_publish; - Khởi tạo trong
Setup():m_publish = false; - Sau khi set RGB LED trong
onDataReceived, set true:RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue); m_publish = true; - Thêm điều kiện trong
loop()publish trạng thái LED lên Mesh:if( m_publish ) { m_publish = false; Mesh.publish("red", String::format("%d", m_led_level.red)); Mesh.publish("green", String::format("%d", m_led_level.green)); Mesh.publish("blue", String::format("%d", m_led_level.blue)); }Mesh.publishyêu cầu string cho 2 tham số. DùngString::formattạo string từ giá trị red/green/blue. - Subscribe cùng biến trong
Setup()để thiết bị khác cũng điều khiển LED trên thiết bị này:Mesh.subscribe("red", meshHandler); Mesh.subscribe("green", meshHandler); Mesh.subscribe("blue", meshHandler); - Đầu file tạo
meshHandler:static void meshHandler(const char *event, const char *data) { } - Trong
meshHandler, chuyểneventvàdatasangStringđể dùng hàm chuyển đổi/so sánh built-in:String eventString = String(event); String dataString = String(data); - So sánh: kiểm tra tên event, set giá trị
dataStringcho led level tương ứng:if( eventString.equals("red") ) { m_led_level.red = dataString.toInt(); } else if ( eventString.equals("green") ) { m_led_level.green = dataString.toInt(); } else if ( eventString.equals("blue") ) { m_led_level.blue = dataString.toInt(); } else { return; } RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);Cuối set màu RGB mới. Xử lý unknown state bằng
returntrongelse– lọc lỗi sớm!
Test Code
- Mở Particle App trên điện thoại.
- Setup Argon trước. Nếu không nháy xanh, giữ Mode button đến khi nháy xanh.
- Làm theo quy trình pairing app. Nhớ mật khẩu Admin mesh network.
- Nạp firmware mới (1.3.0) cho Argon (xem Giai đoạn 1 - Test - Bước 2)
- Khi nháy vàng nhanh, nạp app Tinker cho Argon. Tải tại release page.
- Khi LED cyan đẹp (kết nối Particle Cloud), nạp app. Dùng Cloud Flash trong menu.
- Kết nối Xenon qua phone app
- Kết nối Xenon vào Mesh network qua phone app
- Flash Xenon dùng Cloud Flash. Dùng tên đặt lúc setup app. Miễn thiết bị kết nối Particle Cloud hoặc safe mode (LED tím), sẽ nạp được.
- Khi kết nối xong, phần vui: mở Light Blue Explorer. Kết nối Argon HOẶC Xenon.
- Chọn 1 characteristic LED, thay đổi giá trị.
Lưu ý: nếu đã nạp app, LED tắt mặc định. Giữ Mode 5 giây rồi tiếp tục.
Particle buộc thiết bị flash locally vào safe mode khi kết nối cloud. Có thể do setup của tôi. Best practice: dùng Cloud Flash.
Chọn đúng deviceOS version (1.3.0-rc1), device type (Argon), device name (tên đặt lúc setup).
LED thay đổi trên TẤT CẢ thiết bị!
Code Hoàn Chỉnh
Code full ghép lại. Dùng để kiểm tra vị trí đặt code!
/*
* Project ble_mesh
* Description: Bluetooth Low Energy + Mesh Example
* Author: Jared Wolff
* Date: 7/13/2019
*/
//SYSTEM_MODE(MANUAL);
// UUIDs for service + characteristics
const char* serviceUuid = "6E400001-B5A3-F393-E0A9-E50E24DCCA9E";
const char* red = "6E400002-B5A3-F393-E0A9-E50E24DCCA9E";
const char* green = "6E400003-B5A3-F393-E0A9-E50E24DCCA9E";
const char* blue = "6E400004-B5A3-F393-E0A9-E50E24DCCA9E";
// Set the RGB BLE service
BleUuid rgbService(serviceUuid);
// Variables for keeping state
typedef struct {
uint8_t red;
uint8_t green;
uint8_t blue;
} led_level_t;
// Static level tracking
static led_level_t m_led_level;
// Tracks when to publish to Mesh
static bool m_publish;
// Mesh event handler
static void meshHandler(const char *event, const char *data) {
String eventString = String(event);
String dataString = String(data);
if( eventString.equals("red") ) {
m_led_level.red = dataString.toInt();
} else if ( eventString.equals("green") ) {
m_led_level.green = dataString.toInt();
} else if ( eventString.equals("blue") ) {
m_led_level.blue = dataString.toInt();
} else {
return;
}
RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);
}
// Static function for handling Bluetooth Low Energy callbacks
static void onDataReceived(const uint8_t* data, size_t len, const BlePeerDevice& peer, void* context) {
if( len != 1 ) { return; }
if( context == red ) {
m_led_level.red = data[0];
} else if ( context == green ) {
m_led_level.green = data[0];
} else if ( context == blue ) {
m_led_level.blue = data[0];
}
RGB.color(m_led_level.red, m_led_level.green, m_led_level.blue);
m_publish = true;
}
// setup() runs once, when the device is first turned on.
void setup() {
RGB.control(true);
m_led_level.red = 0;
m_led_level.green = 0;
m_led_level.blue = 0;
m_publish = false;
Mesh.subscribe("red",meshHandler);
Mesh.subscribe("green",meshHandler);
Mesh.subscribe("blue",meshHandler);
BleCharacteristic redCharacteristic("red", BleCharacteristicProperty::WRITE_WO_RSP, red, serviceUuid, onDataReceived, (void*)red);
BleCharacteristic greenCharacteristic("green", BleCharacteristicProperty::WRITE_WO_RSP, green, serviceUuid, onDataReceived, (void*)green);
BleCharacteristic blueCharacteristic("blue", BleCharacteristicProperty::WRITE_WO_RSP, blue, serviceUuid, onDataReceived, (void*)blue);
BLE.addCharacteristic(redCharacteristic);
BLE.addCharacteristic(greenCharacteristic);
BLE.addCharacteristic(blueCharacteristic);
BleAdvertisingData advData;
advData.appendServiceUUID(rgbService);
BLE.advertise(&advData);
}
// loop() runs over and over again, as quickly as it can execute.
void loop() {
if( m_publish ) {
m_publish = false;
Mesh.publish("red", String::format("%d", m_led_level.red));
Mesh.publish("green", String::format("%d", m_led_level.green));
Mesh.publish("blue", String::format("%d", m_led_level.blue));
}
}
Kết Luận
Hướng dẫn này chỉ cách thêm Bluetooth vào project Particle Mesh. Khả năng vô tận. Ví dụ: thêm app user/admin vào trải nghiệm. Tuyệt vời! 😊
Mong đợi thêm nội dung như này trong sách sắp ra mắt: The Ultimate Guide to Particle Mesh. Đăng ký mailing list nhận update và nội dung độc quyền. Subscriber sớm được giảm giá khi phát hành! Đăng ký tại đây.