อยากเพิ่มการติดตามตำแหน่งหรือการมีอยู่ (Presence) ในโปรเจค? หาทางแก้ไม่ได้หรอก?
ไม่ต้องกังวล คุณไม่ได้คนเดียว!
บทความนี้สอนสร้าง Application ติดตามและแจ้งเตือนพื้นฐาน ใช้ Particle Argon และ Tile Mate
จบแล้วคุณจะ: บอกได้ว่า Tile อยู่หรือไม่อยู่ ส่ง Push Notification ไปยังอุปกรณ์ที่ต้องการผ่าน Pushover
หมายเหตุ: บทความนี้ยาว ดาวน์โหลด PDF ไปอ่านภายหลังได้
การสำรวจเบื้องต้น
ใช้โทรศัพท์ดูเหมาะสมกว่า แต่ไม่ไหว - ต้อง Research มาก และสร้าง Bluetooth iOS App
จึงคิดว่า: "อุปกรณ์ไหน Advertise ตลอดเวลา?"
นำไปสู่ Tracker อย่าง Tile
ทดสอบ: ติดต่อ Tile ผ่าน App ได้ เล่นเพลงได้ ใช้ Bluetooth Scanner App หาเจอ Tile Advertise ทุก ~8 วินาที รอหลายชั่วโมงยัง Advertise อยู่ - ใช้สำหรับ Presence Detection ได้
Advertising (การโฆษณา Bluetooth)
Tile Advertise ทุก ~8 วินาที สแกนได้ง่ายด้วย Argon, Xenon, Boron
แนะนำใช้ Argon เพราะ Bluetooth และ Mesh ใช้ Radio เดียวกัน Xenon ที่เชื่อม Mesh บ่อยครั้งพลาด Advertising Packets = False Negatives
สำคัญ: Argon ต้องไม่เชื่อม Mesh Network ลบด้วย CLI:
particle mesh remove <device name/ID>
Advertising ใน Bluetooth: เริ่มต้น Pairing Phase บอกอุปกรณ์อื่นว่าพร้อม ระบุ Services ที่มี ใช้กรองอุปกรณ์
Service UUID ของ Tile: 0xfeed - จะตรวจสอบ UUID นี้ตอนสแกน
Logging (การบันทึก Log)
ใช้ Logger แสดง Log messages ผ่าน particle serial monitor
ฟีเจอร์ดี: Message Hierarchy - เลือกแสดง/ซ่อน Message ได้ ตั้ง Log Level
Log Levels (จาก logging.h):
LOG_LEVEL_ALL = 1, LOG_LEVEL_TRACE = 1, LOG_LEVEL_INFO = 30,
LOG_LEVEL_WARN = 40, LOG_LEVEL_ERROR = 50, LOG_LEVEL_PANIC = 60,
LOG_LEVEL_NONE = 70
ใช้ฟังก์ชัน: Log.trace, Log.info, Log.warn, Log.error
ตั้งค่า: Default = ERROR, App = TRACE
SerialLogHandler logHandler(115200, LOG_LEVEL_ERROR, {
{ "app", LOG_LEVEL_TRACE }
});
การตั้งค่า
1. DeviceOS 1.3.0+ (มี Bluetooth)
2. สแกน Tile ใน loop() ด้วย millis() timer:
if( (millis() > lastSeen + TILE_RE_CHECK_MS) ){
BLE.scan(scanResultCallback, NULL);
}
lastSeen = เวลาล่าสุดที่เจอ Tile, TILE_RE_CHECK_MS = 7500 (7.5 วินาที)
BLE.scan เริ่มสแกน เรียก scanResultCallback เมื่อเจออุปกรณ์
void scanResultCallback(const BleScanResult *scanResult, void *context) { }
BleScanResult มี Address, RSSI, Device Name, Service Info
Scan Timeout default 5 วินาที เปลี่ยนได้ด้วย BLE.setScanTimeout() (หน่วย 10ms) แนะนำ 8 วินาที (800) ครอบคลุมรอบ Advertise ของ Tile
จัดการ Scan Results
ดึง Service UUID: scanResult->advertisingData.serviceUUID(uuids, sizeof(uuids)/sizeof(BleUuid))
หา UUID 0xfeed (#define TILE_UUID 0xfeed) UUID สั้น = Reserved หรือ Bluetooth Spec
Print Diagnostic: MAC Address และ RSSI
Loop ตรวจสอบ UUID:
for(int i = 0; i < uuidsAvail; i++){
if( uuids[i].shorted() == TILE_UUID ) {
Log.trace("UUID: %x", uuids[i].shorted());
BLE.stopScanning();
return;
}
}
ทดสอบ!
Program ลง Argon, รัน particle serial monitor ดู Debug messages
Output ตัวอย่าง: พบ UUID: feed = เจอ Tile แล้ว!
ต่อไป: ใช้ Mode Button "Program" Tile Address ให้จำได้ กรองอุปกรณ์อื่นๆ
เพิ่ม Device เมื่อกดปุ่ม
Monitor Mode Button ด้วย System.on(button_click, eventHandler)
สร้าง eventHandler ทดสอบด้วย LED D7 (Built-in Blue LED)
void setup() {
pinMode(D7, OUTPUT);
}
void eventHandler(system_event_t event, int duration, void* ) {
if( event == button_click ) {
if( digitalRead(D7) ) digitalWrite(D7, LOW);
else digitalWrite(D7, HIGH);
}
}
เก็บ Address ใน EEPROM
เก็บ Tile Address ใน EEPROM (Non-volatile) จำได้หลัง Reboot
ใช้ "Learning Mode" (LED D7 สว่าง) สแกน Tile บันทึก Address
if( uuids[i].shorted() == TILE_UUID ) {
if( isLearningModeOn() ) {
searchAddress = scanResult->address;
EEPROM.put(TILE_EEPROM_ADDRESS, searchAddress);
setLearningModeOff();
}
lastSeen = millis();
lastRSSI = scanResult->rssi;
BLE.stopScanning();
return;
}
isLearningModeOn() = digitalRead(D7) == HIGH
TILE_EEPROM_ADDRESS = 0xa
กรองอุปกรณ์อื่น: ข้ามหาก Address ไม่ตรง และไม่ได้อยู่ใน Learning Mode
โหลด Address ตอน Startup: EEPROM.get(TILE_EEPROM_ADDRESS, searchAddress)
ตรวจสอบ Address ถูกต้อง (ไม่ใช่ 0xFF ทั้งหมด)
ทดสอบ
Compile Run กด Mode Button (LED สว่าง) Tile เจอ LED ดับ บันทึกสำเร็จ Reset ก็ยังจำได้
อัปเดตไปยัง Cloud
ฟังก์ชัน checkTileStateChanged ตรวจสอบการเปลี่ยนแปลงสถานะ
TILE_NOT_HERE_MS = 30000 (30 วินาที)
Enum TilePresenceType: PresenceUnknown, Here, NotHere
Global present = PresenceUnknown
Logic: เปรียบเทียบ lastSeen กับ Timeout อัปเดต State ส่ง JSON Payload:
char address[18];
searchAddress.toString().toCharArray(address, sizeof(address));
status = String::format("{\"address\":\"%s\",\"lastSeen\":%d,\"lastRSSI\":%i,\"status\":\"%s\"}",
address, lastSeen, lastRSSI, messages[present]);
Particle.publish("status", status, PRIVATE, WITH_ACK);
Particle.process();
messages[] = {"unknown", "here", "not here"}
ทดสอบ Cloud
particle subscribe --device <name> status ดู Event
ถอดแบตเตอรี่ Tile รอ "not here" แทรกแบตเตอรี่ รอ "here"
ตั้งค่า Webhook (Push Notification)
ใช้ Pushover API ส่ง Push Notification
- User Key จาก Pushover Dashboard
- สร้าง Application ได้ API Token
- Particle Console → New Integration → Webhook
Webhook Settings:
- Event Name: status
- URL: https://api.pushover.net/1/messages.json
- Advanced Settings: Fields - token, user, title, message
- Token = API Token, User = User Key
- Title = ตามต้องการ
- Message =
The Tile is currently {{{status}}}. RSSI: {{{lastRSSI}}}(Mustache Template)
ผลลัพธ์: The Tile is currently here. RSSI: -77
ทดสอบ Webhook
ถอด/แทรกแบตเตอรี่ Tile ได้ Notification บน iPhone
โค้ดเต็ม
โค้ดครบบน GitHub (ลิงก์ในบทความต้นฉบับ)
สรุป
- ใช้ Bluetooth Central สแกนและระบุ Tile Off-the-shelf
- เก็บ Tile Info ใน EEPROM เรียกได้ตอน Startup
- ใช้
Particle.publishส่ง Update ไป Cloud - ใช้ Particle Integration Webhook สร้าง Push Notification เมื่อ State เปลี่ยน
ขยาย แฮก ทำให้เป็นของคุณ แชร์ไปด้วย! hello@jaredwolff.com