เคยต้องการที่จะเพิ่มการแสดงตนหรือการติดตามสถานที่ในโครงการหรือไม่? ผิดหวังกับวิธีแก้ปัญหา (หรือขาดมัน)?
ไม่ต้องกังวล คุณไม่ใช่คนเดียว!
ในโพสต์นี้ คุณจะได้เรียนรู้วิธีใช้แอปพลิเคชันการติดตามและการแจ้งเตือนขั้นพื้นฐาน เราจะใช้ Particle Argon และ Tile Mate
ในตอนท้ายคุณจะสามารถบอกได้ว่าไทล์นั้นมีอยู่หรือไม่ นอกจากนี้ เราจะใช้ Pushover เพื่อส่งการแจ้งเตือนแบบพุชไปยังอุปกรณ์ที่คุณเลือก
ไปกันเถอะ!
หมายเหตุ ก่อนที่เราจะเริ่มต้น โพสต์นี้ ยาว . คุณสามารถดาวน์โหลดเวอร์ชัน PDF เพื่อบันทึกและดูในภายหลังได้
การตรวจสอบเบื้องต้น
แนวคิดในการใช้ไทล์นั้นไม่ชัดเจนในแวบแรก ตามหลักการแล้ว การใช้โทรศัพท์ดูสมเหตุสมผลกว่า น่าเสียดายที่นี่ไม่ใช่ตัวเลือกที่ใช้การได้ จำเป็นต้องมีการวิจัยเพิ่มเติมและการสร้างแอป Bluetooth iOS
ความคิดในการใช้โทรศัพท์จึงหมดไป
แล้วฉันก็คิดว่า "อุปกรณ์อะไรทำ โฆษณาตลอดเวลา?"
นั่นคือสิ่งที่นำฉันไปสู่เส้นทางของตัวติดตามเช่นไทล์
หลังจากมาถึงก็มีการทดสอบตามธรรมเนียม ขั้นแรก แอปพลิเคชันไทล์
ฉันสามารถเชื่อมต่อและใช้อุปกรณ์ได้ ฉันยังทำให้มันเล่นท่วงทำนองที่ติดหู ?
จากนั้นฉันก็เปลี่ยนไปใช้แอพสแกนเนอร์ Bluetooth ตัวใดตัวหนึ่ง ฉันเลื่อนดูผลลัพธ์และบิงโกทั้งหมด มีกระเบื้อง!
ฉันยังรอสองสามชั่วโมงและตรวจสอบอีกครั้ง ฉันต้องการให้แน่ใจว่ามันไม่ได้ไปนอนหลังจากนั้นสักครู่ ปรากฎว่าโฆษณาอยู่เสมอ เท่าที่ฉันสามารถบอกได้ ทุกๆ 8 วินาที
การทดสอบทั้งหมดนี้นำไปสู่ข้อสรุปเดียว:สามารถใช้สำหรับการตรวจจับการมีอยู่ได้อย่างง่ายดาย
ขั้นตอนต่อไปในกระบวนการนี้คือการพยายามหาวิธีทำให้มันทำงานกับอาร์กอน
โฆษณา
ตามที่เราได้รวบรวมไว้ในขั้นตอนก่อนหน้านี้ เรารู้ว่าไทล์กำลังโฆษณาทุกๆ 8 วินาที ซึ่งหมายความว่าควรสแกนหาอุปกรณ์ใดๆ ก็ได้ เช่น Argon, Zenon หรือ Boron อย่างง่ายดาย
สำหรับตัวอย่างนี้ ฉันแนะนำให้คุณใช้ Argon เนื่องจากบลูทูธและเมชใช้วิทยุร่วมกัน เมื่อสแกนหากระเบื้อง Xenon ที่เชื่อมต่อกับ Mesh มักจะพลาดแพ็กเก็ตโฆษณา สิ่งนี้จะนำไปสู่การลบเท็จ (และความยุ่งยาก!)
ในทำนองเดียวกัน คุณจะต้องแน่ใจว่า Argon ของคุณไม่ได้เชื่อมต่อกับเครือข่ายแบบเมช คุณสามารถลบออกได้โดยใช้ CLI เชื่อมต่ออุปกรณ์กับคอมพิวเตอร์และเรียกใช้คำสั่งต่อไปนี้:
particle mesh remove <device name/ID>
ตรวจสอบให้แน่ใจว่าคุณได้เปลี่ยน <ชื่ออุปกรณ์/ID> ด้วยชื่อหรือ ID ของอุปกรณ์
เอาล่ะ กลับเข้าเรื่องกันดีกว่า
การโฆษณาสามารถมีวัตถุประสงค์ที่แตกต่างกันเล็กน้อยในบลูทูธ โดยปกติแล้วจะเป็นจุดเริ่มต้นของขั้นตอนการจับคู่ วิธีนี้จะทำให้อุปกรณ์อื่นๆ รู้ว่ามีอุปกรณ์โฆษณาพร้อมให้บริการ
นอกจากนี้ อุปกรณ์โฆษณาจะระบุว่ามีบริการใดบ้าง เราสามารถใช้ความรู้นี้ในการกรองอุปกรณ์ที่ไม่ตรงกันออก
ตัวอย่างเช่น นี่คือภาพหน้าจอของบริการที่มีอยู่ในอุปกรณ์ไทล์:
เมื่อสแกน เราจะตรวจสอบอีกครั้งว่าอุปกรณ์ที่เราเชื่อมต่อนั้นมี UUID ของบริการเป็น 0xfeed
.
ก่อนที่เราจะเจาะลึกเข้าไปในพื้นที่ของ Bluetooth เรามาตั้งค่าแอปของเราสำหรับการดีบักโดยใช้ Logger
การบันทึก
ในบทช่วยสอนนี้ เราจะใช้ Logger ช่วยให้คุณสามารถแสดงข้อความบันทึกจากแอปของคุณโดยใช้ particle serial monitor
.
หนึ่งในคุณสมบัติที่เจ๋งกว่าเกี่ยวกับตัวตัดไม้คือแนวคิดเกี่ยวกับลำดับชั้นของข้อความ ซึ่งจะช่วยให้คุณซึ่งเป็นผู้ออกแบบสามารถเลือกปิดเสียงข้อความที่อาจไม่จำเป็นได้
ตัวอย่างเช่น หากคุณมีข้อความที่ใช้สำหรับการดีบัก คุณสามารถลบออกหรือแสดงความคิดเห็นได้ หรือคุณสามารถเพิ่ม LOG_LEVEL
ดังนั้นจึงถูกละเลยอย่างมีประสิทธิภาพ
นี่คือระดับการบันทึกที่มีอยู่ใน logging.h
ในที่เก็บระบบปฏิบัติการของอุปกรณ์ของอนุภาค:
// Log level. Ensure log_level_name() is updated for newly added levels
typedef enum LogLevel {
LOG_LEVEL_ALL = 1, // Log all messages
LOG_LEVEL_TRACE = 1,
LOG_LEVEL_INFO = 30,
LOG_LEVEL_WARN = 40,
LOG_LEVEL_ERROR = 50,
LOG_LEVEL_PANIC = 60,
LOG_LEVEL_NONE = 70, // Do not log any messages
// Compatibility levels
DEFAULT_LEVEL = 0,
ALL_LEVEL = LOG_LEVEL_ALL,
TRACE_LEVEL = LOG_LEVEL_TRACE,
LOG_LEVEL = LOG_LEVEL_TRACE, // Deprecated
DEBUG_LEVEL = LOG_LEVEL_TRACE, // Deprecated
INFO_LEVEL = LOG_LEVEL_INFO,
WARN_LEVEL = LOG_LEVEL_WARN,
ERROR_LEVEL = LOG_LEVEL_ERROR,
PANIC_LEVEL = LOG_LEVEL_PANIC,
NO_LOG_LEVEL = LOG_LEVEL_NONE
} LogLevel;
เจ๋งระดับล็อก แต่เราจะใช้มันอย่างไร?
เราสามารถใช้ฟังก์ชันเหล่านี้ได้โดยเรียกใช้ฟังก์ชันใดฟังก์ชันหนึ่งต่อไปนี้:
Log.trace
, Log.info
, Log.warn
, Log.error
.
ตัวอย่างเช่น:
Log.trace("This is a TRACE message.");
หากเราตั้งค่าระดับบันทึกเป็น LOG_LEVEL_INFO
เราจะเห็นเฉพาะข้อความจาก Log.info
, Log.warn
และ Log.error
. LOG_LEVEL_WARN
? เฉพาะ Log.warn
และ Log.error
จะปรากฏขึ้น (หวังว่าคุณจะเข้าใจ)
ในการตั้งค่า เราจะตั้งค่าระดับเริ่มต้นเป็น LOG_LEVEL_ERROR
. นอกจากนี้เรายังจะตั้งค่าเฉพาะแอป LOG_LEVEL
ถึง LOG_LEVEL_TRACE
. ผลลัพธ์ที่ได้ควรมีลักษณะเช่นนี้
// For logging
SerialLogHandler logHandler(115200, LOG_LEVEL_ERROR, {
{ "app", LOG_LEVEL_TRACE }, // enable all app messages
});
วิธีนี้ทำให้เราไม่ได้รับสแปมด้วยข้อความบันทึก DeviceOS นอกจากนี้ เรายังได้รับข้อความที่เกี่ยวข้องทั้งหมดจากตัวแอปเองด้วย
อย่างไรก็ตาม หากคุณต้องการตั้งค่าอุปกรณ์ของคุณเป็น LOG_LEVEL
single เดียว คุณสามารถตั้งค่าได้ดังนี้:
SerialLogHandler logHandler(LOG_LEVEL_INFO);
ในขณะที่คุณเดินทางต่อไปโดยใช้ DeviceOS ของ Particle คุณจะรู้ได้ทันทีว่ามันสะดวกเพียงใด เอาล่ะ ไปต่อกันเลยดีกว่า!
การตั้งค่า
ก่อนอื่น เราต้องตรวจสอบให้แน่ใจว่าเราใช้ DeviceOS เวอร์ชันที่ถูกต้อง เวอร์ชันใดหลังจาก 1.3 จะมีบลูทูธ คุณสามารถรับคำแนะนำได้ที่นี่
ต่อไปเราจะต้องการเริ่มสแกนหาไทล์ เราจะต้องทำสิ่งนี้ใน loop()
ทำงานในช่วงเวลาที่กำหนด เราจะใช้ millis()
ตัวจับเวลาในกรณีนี้:
// Scan for devices
if( (millis() > lastSeen + TILE_RE_CHECK_MS) ){
BLE.scan(scanResultCallback, NULL);
}
ตรวจสอบให้แน่ใจว่าคุณได้กำหนด lastSeen
ที่ด้านบนของไฟล์ดังนี้:
system_tick_t lastSeen = 0;
เราจะใช้เพื่อติดตามครั้งสุดท้ายที่ "เห็น" ไทล์ เช่น เมื่อครั้งสุดท้ายที่ Argon เห็นแพ็กเก็ตโฆษณาจากไทล์
TILE_RE_CHECK_MS
สามารถกำหนดเป็น
#define TILE_RE_CHECK_MS 7500
ด้วยวิธีนี้ เราจะตรวจสอบอย่างน้อยที่สุดทุกๆ 7.5 วินาทีสำหรับแพ็กเก็ตโฆษณา
ในการค้นหาอุปกรณ์ไทล์ เราจะใช้ BLE.scan
. เมื่อเราเรียกมัน มันจะเริ่มกระบวนการสแกน ตามที่พบอุปกรณ์ scanResultCallback
จะยิง
สำหรับตอนนี้ เราสามารถกำหนด scanResultCallback
. ได้ ที่ด้านบนของไฟล์:
void scanResultCallback(const BleScanResult *scanResult, void *context) {
}
คุณสังเกตเห็นว่ามันมี BleScanResult
. ซึ่งจะประกอบด้วยที่อยู่ RSSI และชื่ออุปกรณ์ (ถ้ามี) และข้อมูลบริการที่มี สิ่งนี้จะมีประโยชน์ในภายหลังเมื่อเรากำลังมองหาอุปกรณ์ไทล์ของเรา!
จำไว้ว่า BLE.scan
ไม่กลับมาจนกว่าการสแกนจะเสร็จสิ้น ระยะหมดเวลาเริ่มต้นสำหรับการสแกนคือ 5 วินาที คุณสามารถเปลี่ยนค่านั้นได้โดยใช้ BLE.setScanTimeout()
. setScanTimeout
ใช้หน่วยเพิ่มขึ้นทีละ 10ms ดังนั้น สำหรับการหมดเวลา 500ms จะต้องมีค่า 50
สำหรับกรณีของแอปนี้ ฉันขอแนะนำให้ใช้ค่า 8 วินาที (8000 มิลลิวินาที) คุณสามารถตั้งค่าดังนี้:
BLE.setScanTimeout(800);
ในกรณีนี้ อุปกรณ์จะสแกนตราบเท่าที่ใช้ไทล์เพื่อโฆษณา วิธีนี้จึงมีโอกาสน้อยที่จะพลาดแพ็กเก็ตโฆษณา
การจัดการผลการสแกน
ตอนนี้เรามี scanResultCallback
มากำหนดสิ่งที่เกิดขึ้นภายในกันเถอะ
อันดับแรกเราต้องการรับข้อมูลการบริการภายในข้อมูลโฆษณา วิธีที่ดีที่สุดคือการใช้ scanResult->advertisingData.serviceUUID
. เราจะส่งต่ออาร์เรย์ของ UUID ว่าสิ่งใดจะถูกคัดลอกสำหรับการใช้งานของเรา
BleUuid uuids[4];
int uuidsAvail = scanResult->advertisingData.serviceUUID(uuids,sizeof(uuids)/sizeof(BleUuid));
สิ่งนี้จะเติม uuids
ด้วยวิธีนี้คุณสามารถทำซ้ำได้ uuidsAvail
จะเท่ากับจำนวน UUID ที่มี
ในกรณีของเรา เรากำลังมองหา UUID เฉพาะ เราจะกำหนดให้เป็นส่วนบนของไฟล์:
#define TILE_UUID 0xfeed
โดยปกติ UUID จะ มาก อีกต่อไป UUID สั้นๆ แบบนี้หมายความว่ามีการสงวนไว้หรือเป็นส่วนหนึ่งของข้อกำหนด Bluetooth ไม่ว่าในกรณีใด เราจะตรวจสอบในลักษณะเดียวกับที่เราจะตรวจสอบเวอร์ชัน 32 บิตหรือ 128 บิต
ด้วยเหตุผลในการวินิจฉัย เรายังสามารถพิมพ์ข้อมูลอุปกรณ์ได้ ในกรณีนี้ RSSI และที่อยู่ MAC ของอุปกรณ์จะสะดวก:
// Print out mac info
BleAddress addr = scanResult->address;
Log.trace("MAC: %02X:%02X:%02X:%02X:%02X:%02X", addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
Log.trace("RSSI: %dBm", scanResult->rssi);
สุดท้ายมาตั้งค่าลูปเพื่อดูว่าอุปกรณ์ที่พบมี UUID หรือไม่:
// Loop over all available UUIDs
// For tile devices there should only be one
for(int i = 0; i < uuidsAvail; i++){
// Print out the UUID we're looking for
if( uuids[i].shorted() == TILE_UUID ) {
Log.trace("UUID: %x", uuids[i].shorted());
// Stop scanning
BLE.stopScanning();
return;
}
}
เราสามารถเปรียบเทียบ UUID เวอร์ชัน "ย่อ" กับ TILE_UUID
. ได้อย่างง่ายดาย . เป็นจำนวนเต็มอย่างง่าย จึงไม่จำเป็นต้องมีการดำเนินการเปรียบเทียบหน่วยความจำที่ซับซ้อน ดังนั้น ใช้ if( uuids[i].shorted() == TILE_UUID )
ทำงานได้ดี
คุณยังสามารถใช้ Log.trace
เพื่อพิมพ์ข้อมูลการวินิจฉัย ในกรณีนี้ เราจะใช้พิมพ์ shorted()
เวอร์ชันของ UUID
ทดสอบเลย!
มาทดสอบกันว่าเรามีอะไรบ้าง!
ตั้งโปรแกรมแอปให้กับ Argon ของคุณ เปิดเทอร์มินัลแล้วเรียกใช้ particle serial monitor
เพื่อดูข้อความแก้ไขข้อบกพร่อง นี่คือตัวอย่างสิ่งที่คุณอาจเห็น:
0000005825 [app] TRACE: MAC: 65:C7:B3:AF:73:5C
0000005827 [app] TRACE: RSSI: -37Bm
0000005954 [app] TRACE: MAC: B3:D9:F1:F0:5D:7E
0000005955 [app] TRACE: RSSI: -62Bm
0000006069 [app] TRACE: MAC: C5:F0:74:3D:13:77
0000006071 [app] TRACE: RSSI: -62Bm
0000006217 [app] TRACE: MAC: 65:C7:B3:AF:73:5C
0000006219 [app] TRACE: RSSI: -39Bm
0000006224 [app] TRACE: MAC: B3:D9:F1:F0:5D:7E
0000006225 [app] TRACE: RSSI: -62Bm
0000006296 [app] TRACE: MAC: D7:E7:FE:0C:A5:C0
0000006298 [app] TRACE: RSSI: -60Bm
0000006299 [app] TRACE: UUID: feed
สังเกตว่าข้อความมี TRACE
. อย่างไร และยัง [app]
? นั่นหมายความว่าเป็นข้อความติดตามที่มาจากรหัสแอปพลิเคชัน สะดวกใช่มั้ย
รหัสนี้ได้รับสแปมอย่างรวดเร็ว โดยเฉพาะอย่างยิ่งหากคุณอยู่ในสภาพแวดล้อมที่มีอุปกรณ์บลูทูธโฆษณาจำนวนมาก หากคุณเปิดไทล์และทำงานในที่สุด คุณจะเห็นข้อความ UUID: feed
. นั่นหมายความว่า Argon ของคุณพบกระเบื้องแล้ว!
ต่อไป เราจะใช้ปุ่มโหมดออนบอร์ดเพื่อ "โปรแกรม" ที่อยู่ของไทล์ไปยังหน่วยความจำ ด้วยวิธีนี้ เราจึงสามารถกรองอุปกรณ์ทั้งหมดที่เราไม่สนใจออกไปได้
เพิ่มการกดปุ่มเปิดอุปกรณ์
ก่อนอื่นเราต้องหาวิธีตรวจสอบปุ่มโหมด ทางที่ดีที่สุดตามเอกสารคือใช้ System.on
.
System.on(button_click, eventHandler);
อาร์กิวเมนต์แรกคือชื่อของเหตุการณ์ของระบบ ในกรณีของเราคือ button_click
. อาร์กิวเมนต์ที่สองคือฟังก์ชันตัวจัดการเหตุการณ์ เราจะเรียกมันว่า eventHandler
สำหรับตอนนี้
มาสร้าง eventHandler
. กัน
void eventHandler(system_event_t event, int duration, void* )
{
}
สำคัญ: คุณไม่สามารถใช้ Log
ฟังก์ชันภายใน eventHandler
. วิธีง่ายๆ ในการทดสอบคือการสลับ LED บน D7 มาตั้งค่ากันเถอะ!
เริ่มต้น LED ใน setup()
// Set LED pin
pinMode(D7,OUTPUT);
จากนั้นเราสามารถเพิ่มสิ่งนี้ลงใน eventHandler
if( event == button_click ) {
if( digitalRead(D7) ) {
digitalWrite(D7,LOW);
} else {
digitalWrite(D7,HIGH);
}
}
จากนั้นเราสามารถเขียนถึง D7 (ไฟ LED สีฟ้าออนบอร์ด) เรายังใช้ digitalRead
. ได้ เพื่ออ่านสถานะของ LED มันจะตอบสนองด้วย HIGH
หรือ LOW
แล้วแต่สถานการณ์
โหลดเฟิร์มแวร์ลงในอุปกรณ์แล้วเราจะควบคุมไฟ LED สีฟ้าได้ดี!
ในส่วนถัดไป เราจะใช้ปุ่มโหมดเพื่อทำให้อุปกรณ์อยู่ในโหมด "การเรียนรู้" ซึ่งจะทำให้เราสามารถตั้งค่าด้วยการแตะเพียงครั้งเดียวกับอุปกรณ์ไทล์เป้าหมาย
กำลังจัดเก็บที่อยู่ใน EEPROM
ในขั้นตอนต่อไป เราจะเก็บที่อยู่ของไทล์ไว้ใน EEPROM ด้วยวิธีนี้ เมื่ออุปกรณ์ถูกรีสตาร์ทหรือหมดพลังงาน เราจะยังสามารถระบุไทล์ได้ในภายหลัง
มีคำถามหนึ่งที่เอ้อระเหยแม้ว่า เราจะเอามันไปบันทึกที่อยู่ได้อย่างไร?
โดยการตรวจสอบการกดปุ่ม เราสามารถกำหนดให้อุปกรณ์เข้าสู่โหมด "การเรียนรู้" อุปกรณ์จะสแกนหาไทล์และบันทึกที่อยู่หากพบ
ขั้นแรกให้เพิ่มเงื่อนไขภายใน if( uuids[i].shorted() == TILE_UUID )
:
// If we're in learning mode. Save to EEPROM
if( isLearningModeOn() ) {
searchAddress = scanResult->address;
EEPROM.put(TILE_EEPROM_ADDRESS, searchAddress);
setLearningModeOff();
}
เราจะใช้สถานะของ D7 เพื่อให้รู้ว่าเราอยู่ใน "โหมดการเรียนรู้" เราทำได้โดยการอ่าน D7 โดยใช้ digitalRead(D7)
. มาสร้างฟังก์ชันที่ทำให้ชัดเจนยิ่งขึ้นกันเถอะ:
bool isLearningModeOn() {
return (digitalRead(D7) == HIGH);
}
นอกจากนี้เรายังสามารถแทนที่ digitalWrite(D7,LOW);
และ digitalWrite(D7,HIGH);
ที่มีลักษณะการทำงานที่คล้ายคลึงกัน วิธีนี้จะทำให้สิ่งที่เราทำตรงไปตรงมายิ่งขึ้น
// Set "Learning mode" on
void setLearningModeOn() {
digitalWrite(D7,HIGH);
}
// Set "Learning mode" off
void setLearningModeOff() {
digitalWrite(D7,LOW);
}
จากนั้นเรากำหนดตัวแปรส่วนกลาง searchAddress
เป็นผลการสแกน เราตั้งค่า searchAddress
แบบนี้ที่ด้านบนของไฟล์:
BleAddress searchAddress;
ต่อไปเราต้องการบันทึกลงในหน่วยความจำแบบไม่ลบเลือนโดยใช้ EEPROM.put
. TILE_EEPROM_ADDRESS
ถูกกำหนดเป็น 0xa
. คุณสามารถกำหนด TILE_EEPROM_ADDRESS
เพื่อใช้ที่อยู่หน่วยความจำใดก็ได้ที่คุณต้องการ นี่คือคำจำกัดความแบบเต็มที่ด้านบนของไฟล์
#define TILE_EEPROM_ADDRESS 0xa
สุดท้าย เราปิด LED และ "โหมดการเรียนรู้" โดยใช้ setLearningModeOff()
ทุกครั้งที่พบอุปกรณ์ เราจะใช้ millis()
เพื่อตั้งค่า lastSeen
. นอกจากนี้ เราสามารถติดตาม RSSI ล่าสุดได้โดยใช้ lastRSSI
. เป็นวิธีที่ประหยัดในการทราบว่าอุปกรณ์อยู่ใกล้แค่ไหน เราจะใช้ scanResult->rssi
เพื่อรับข้อมูลนี้และตั้งค่าเป็น lastRSSI
ตัวแปร
โดยรวมแล้ว การเปลี่ยนแปลงของคุณควรมีลักษณะดังนี้:
...
// Print out the UUID we're looking for
if( uuids[i].shorted() == TILE_UUID ) {
Log.trace("UUID: %x", uuids[i].shorted());
// If we're in learning mode. Save to EEprom
if( isLearningModeOn() ) {
searchAddress = scanResult->address;
EEPROM.put(TILE_EEPROM_ADDRESS, searchAddress);
setLearningModeOff();
}
// Save info
lastSeen = millis();
lastRSSI = scanResult->rssi;
// Stop scanning
BLE.stopScanning();
return;
}
ก่อนฟังก์ชันนี้ เราสามารถกรองอุปกรณ์ที่ไม่ตรงกับ searchAddress
. ของเรา . เพิ่มสิ่งต่อไปนี้ก่อน if( uuids[i].shorted() == TILE_UUID )
:
// If device address doesn't match or we're not in "learning mode"
if( !(searchAddress == scanResult->address) && !isLearningModeOn() ) {
return;
}
การดำเนินการนี้จะข้ามผ่านอุปกรณ์ที่ไม่ตรงกัน จะดำเนินการได้ก็ต่อเมื่อที่อยู่ตรงกันหรือเราอยู่ใน "โหมดการเรียนรู้"
ทีนี้ เพื่อให้เราโหลด searchAddress
ตอนสตาร์ทเราต้องโหลดจากแฟลช เพิ่มบรรทัดนี้ใน setup():
. ของคุณ
EEPROM.get(TILE_EEPROM_ADDRESS, searchAddress);
จากนั้นตรวจสอบเพื่อให้แน่ใจว่าที่อยู่ถูกต้อง จะไม่ถูกต้องหากไบต์ทั้งหมดเป็น 0xFF
:
// Warning about address
if( searchAddress == BleAddress("ff:ff:ff:ff:ff:ff") ) {
Log.warn("Place this board into learning mode");
Log.warn("and keep your Tile near by.");
}
เราควรจะสามารถ "สอน" Argon ของเราถึงที่อยู่ของไทล์ของเราได้ มาทดสอบกัน!
ทดสอบเลย
ตอนนี้ถ้าเราคอมไพล์และรันแอพ สังเกตว่าไม่มีเอาต์พุตบันทึกอีกแล้วหรือ เราต้อง "สอน" ที่อยู่ไทล์ให้กับอุปกรณ์อนุภาค ดังนั้นให้กดปุ่มโหมด ไฟ LED สีฟ้าควรเปิดขึ้น
เมื่อพบไทล์ของคุณแล้ว ไฟ LED จะดับลง และคุณจะเห็นผลลัพธ์บางอย่างในบรรทัดคำสั่ง คล้ายกับที่เราเคยเห็นมาก่อน:
0000006296 [app] TRACE: MAC: D7:E7:FE:0C:A5:C0
0000006298 [app] TRACE: RSSI: -60Bm
0000006299 [app] TRACE: UUID: feed
อุปกรณ์ได้รับการผูกมัดกับหน่วยความจำแล้ว!
คุณยังตรวจสอบได้ด้วยว่าระบบยังบันทึกอยู่หลังจากรีเซ็ตแล้วหรือไม่ กด รีเซ็ต ปุ่มและตรวจสอบเอาต์พุตเดียวกันกับด้านบน ถ้ามันขึ้นแสดงว่าเรายังดีอยู่!
อัปเดตระบบคลาวด์
สุดท้ายมาตั้งค่าฟังก์ชันที่ชื่อว่า checkTileStateChanged
. เราจะใช้เพื่อตรวจสอบการเปลี่ยนแปลงสถานะของไทล์ในช่วงเวลาปกติ
bool checkTileStateChanged( TilePresenceType *presence ) {
}
จุดประสงค์หลักของฟังก์ชันนี้คือการเปรียบเทียบ lastSeen
ตัวแปรที่มีระยะเวลา "หมดเวลา" ในกรณีของเรา ระยะเวลาหมดเวลาของเราคือ TILE_NOT_HERE_MS
ซึ่งควรตั้งค่าเป็น
#define TILE_NOT_HERE_MS 30000
ใกล้ด้านบนสุดของโปรแกรมของคุณ ยังมีอีกสองเงื่อนไขที่ต้องค้นหา ที่แห่งหนึ่ง lastSeen
เท่ากับ 0 ซึ่งโดยปกติเนื่องจากแอปยังไม่พบไทล์หลังจากเริ่มต้น
กรณีสุดท้ายคงเป็นกรณีที่มีคนเห็นเครื่องแล้วและ lastSeen
ไม่ใช่ 0 ดังนั้นภายใน checkTileStateChanged
มารวมทุกอย่างเข้าด้วยกัน
// Check to see if it's here.
if( millis() > lastSeen+TILE_NOT_HERE_MS ) {
} else if ( lastSeen == 0 ) {
} else {
}
return false;
ตอนนี้เราต้องการให้ฟังก์ชันนี้คืนค่าเป็นจริง หากสถานะมีการเปลี่ยนแปลง . เราจึงต้องใช้ประโยชน์จาก TilePresenceType
ตัวชี้ในข้อตกลง
TilePresenceType
เป็นเพียงการแจงนับสถานะที่เป็นไปได้ทั้งหมด คุณสามารถติดไว้ที่ด้านบนของไฟล์ได้เช่นกัน นี่คือ:
typedef enum {
PresenceUnknown,
Here,
NotHere
} TilePresenceType;
คุณจะต้องมีตัวแปรส่วนกลางที่เราสามารถส่งผ่านไปยังฟังก์ชันได้ ตั้งค่านี้ที่ด้านบนสุดของไฟล์ด้วย:
// Default status
TilePresenceType present = PresenceUnknown;
ตอนนี้เราสามารถเปรียบเทียบในแต่ละขั้นตอนได้ เป็นไปตามเกณฑ์หรือไม่? รัฐแตกต่างจากที่แล้วหรือไม่? หากเป็นเช่นนั้น ให้คืนค่าเป็น จริง
จำไว้ว่าเราต้องการตั้งค่า presence
เป็นค่าที่ปรับปรุงใหม่ ดังนั้นแต่ละเงื่อนไขควรอัปเดตค่าการมีอยู่ ตัวอย่างเช่น:
*presence = NotHere;
นี่คือหน้าตาของฟังก์ชันที่ล้างออกอย่างสมบูรณ์:
bool checkTileStateChanged( TilePresenceType *presence ) {
// Check to see if it's here.
if( millis() > lastSeen+TILE_NOT_HERE_MS ) {
if( *presence != NotHere ) {
*presence = NotHere;
Log.trace("not here!");
return true;
}
// Case if we've just started up
} else if ( lastSeen == 0 ) {
if( *presence != PresenceUnknown ) {
*presence = PresenceUnknown;
Log.trace("unknown!");
return true;
}
// Case if lastSeen is < TILE_NOT_HERE_MS
} else {
if( *presence != Here ) {
*presence = Here;
Log.trace("here!");
return true;
}
}
return false;
}
ขณะนี้สามารถใช้ฟังก์ชันนี้ในลูปหลักใต้ตัวจับเวลาเพื่อเริ่ม Ble.scan()
. เราสามารถใช้เพื่อส่งเพย์โหลด JSON ในกรณีนี้ เราจะรวมข้อมูลสำคัญ เช่น ที่อยู่บลูทูธ lastSeen
ข้อมูล lastRSSI
ข้อมูลและข้อความ
// If we have a change
if( checkTileStateChanged(&present) ) {
}
เราจะใช้อาร์เรย์ของ char
เพื่อรับที่อยู่ของเราในรูปแบบสตริง โยงกันได้ toString()
ด้วย toCharArray
เพื่อให้ได้สิ่งที่เราต้องการ
// Get the address string
char address[18];
searchAddress.toString().toCharArray(address,sizeof(address));
ตัวอย่างสตริงเพย์โหลดอาจมีลักษณะดังนี้:
// Create payload
status = String::format("{\"address\":\"%s\",\"lastSeen\":%d,\"lastRSSI\":%i,\"status\":\"%s\"}",
address, lastSeen, lastRSSI, messages[present]);
status
เป็นเพียงสตริงที่กำหนดไว้ที่ด้านบนของไฟล์:
// The payload going to the cloud
String status;
คุณสังเกตเห็นว่ามีตัวแปรชื่อ messages
. ด้วย . นี่คืออาร์เรย์ const แบบคงที่ของสตริง มีการแมปกับค่าจาก TilePresenceType
. นี่คือลักษณะที่ปรากฏ
const char * messages[] {
"unknown",
"here",
"not here"
};
ทางนั้น PresenceUnknown
ตรงกับ "unknown"
, Here
ตรงกับ "here"
ฯลฯ เป็นวิธีที่ง่ายในการเชื่อมโยงสตริงกับ enum
สุดท้าย เราจะเผยแพร่และดำเนินการ ซึ่งช่วยให้เราส่งข้อมูลอัปเดตได้ทันที
// Publish the RSSI and Device Info
Particle.publish("status", status, PRIVATE, WITH_ACK);
// Process the publish event immediately
Particle.process();
ฟังก์ชันโดยรวมควรมีลักษณะดังนี้:
// If we have a change
if( checkTileStateChanged(&present) ) {
// Get the address string
char address[18];
searchAddress.toString().toCharArray(address,sizeof(address));
// Create payload
status = String::format("{\"address\":\"%s\",\"lastSeen\":%d,\"lastRSSI\":%i,\"status\":\"%s\"}",
address, lastSeen, lastRSSI, messages[present]);
// Publish the RSSI and Device Info
Particle.publish("status", status, PRIVATE, WITH_ACK);
// Process the publish event immediately
Particle.process();
}
มาทดสอบกันเลย!
กำลังทดสอบ!
เราสามารถทดสอบเพื่อให้แน่ใจว่าเหตุการณ์ Publish ของเราเกิดขึ้นโดยที่เหตุการณ์ไม่ออกจาก Particle Workbench เปิดเทอร์มินัลใหม่โดยไปที่ ดู → เทอร์มินัล จากนั้นใช้คำสั่งต่อไปนี้:
particle subscribe --device <device_name> <event_name>
แทนที่ <device_name>
ด้วยชื่อหรือ ID ของอุปกรณ์ของคุณ
แทนที่ <event_name>
กับชื่องาน ในกรณีของเราคือ status
.
จากนั้นคุณสามารถทดสอบได้ทั้งหมดโดยถอดแบตเตอรี่ออกและรอการแจ้งเตือน "ไม่อยู่ที่นี่" เสียบแบตเตอรี่กลับเข้าไปและคุณควรได้รับการแจ้งเตือน "ที่นี่"
นี่คือตัวอย่างผลลัพธ์
> particle subscribe --device hamster_turkey status
Subscribing to "status" from hamster_turkey's stream
Listening to: /v1/devices/hamster_turkey/events/status
{"name":"status","data":"{\"address\":\"C0:A5:0C:FE:E7:D7\",\"lastSeen\":40154002,\"lastRSSI\":-82,\"status\":\"not here\"}","ttl":60,"published_at":"2019-09-07T02:29:42.232Z","coreid":"e00fce68d36c42ef433428eb"}
{"name":"status","data":"{\"address\":\"C0:A5:0C:FE:E7:D7\",\"lastSeen\":40193547,\"lastRSSI\":-83,\"status\":\"here\"}","ttl":60,"published_at":"2019-09-07T02:29:50.352Z","coreid":"e00fce68d36c42ef433428eb"}
การกำหนดค่าเว็บฮุค
ในส่วนสุดท้ายของบทช่วยสอนนี้ เราจะตั้งค่าการแจ้งเตือนแบบพุชโดยใช้เว็บฮุค ดังที่ได้กล่าวไว้ก่อนหน้านี้ เราจะใช้ Pushover และ API ที่มีประโยชน์เพื่อส่งการแจ้งเตือนแบบพุชไปยังอุปกรณ์ที่คุณเลือก
Pushover มี API ที่ใช้งานง่ายอย่างน่าอัศจรรย์ แอปพลิเคชันของพวกเขาคือมีดทหารสวิสสำหรับสถานการณ์ที่คุณไม่ต้องการเข้ารหัสแอปเพื่อส่งการแจ้งเตือนแบบพุช
สิ่งแรกที่คุณต้องจำไว้คือรหัสผู้ใช้ คุณสามารถรับสิ่งนั้นได้โดยลงชื่อเข้าใช้ Pushover หมายเหตุ:คุณจะต้องสร้างบัญชีก่อนหากยังไม่ได้ทำ
ควรมีลักษณะดังนี้:
หากคุณเข้าสู่ระบบแล้วไม่เห็นหน้านี้ ให้คลิกที่โลโก้ Pushover และนั่นควรนำคุณกลับมา
ต่อไปเราจะต้องการสร้างแอปพลิเคชัน คลิกที่ แอปและปลั๊กอิน ที่ด้านบนของหน้าจอ
จากนั้นคุณควรคลิก สร้างแอปพลิเคชันใหม่ ซึ่งจะทำให้เราได้รับ API Token ที่จำเป็นในการตั้งค่า Particle Webhook
ตั้งชื่อตามที่เห็นสมควร กรอกรายละเอียดหากต้องการเตือนความจำ คลิกที่ช่อง แล้วคลิก สร้างแอปพลิเคชัน
คุณควรไปที่หน้าถัดไป คัดลอกและบันทึก โทเค็น/คีย์ API เราต้องการสิ่งนี้ในไม่กี่ขั้นตอนด้วย
ตอนนี้ มาตั้งค่า Webhook กัน ข้ามไปที่ https://console.particle.io และสร้างการผสานรวมใหม่
เราจะตั้งค่า ชื่อกิจกรรม เป็น สถานะ .
URL ถึง https://api.pushover.net/1/messages.json
นอกจากนี้ หากคุณต้องการกรองตามอุปกรณ์เฉพาะ ให้เลือกใน อุปกรณ์แบบเลื่อนลง
ภายใต้ การตั้งค่าขั้นสูง เราจะเสร็จสิ้นด้วยการตั้งค่าสองสามฟิลด์
สร้างฟิลด์ต่อไปนี้:โทเค็น ผู้ใช้ , ชื่อเรื่อง และ ข้อความ . จากนั้นตั้งค่าโทเค็นเป็น โทเค็น API เราได้รับก่อนหน้านี้ ทำเช่นเดียวกันสำหรับ รหัสผู้ใช้
ชื่อเรื่อง จะแสดงเป็นชื่อข้อความของคุณ ทำให้ทุกอย่างเหมาะสมสำหรับคุณ
คุณสามารถตั้งค่าข้อความ เป็น The Tile is currently {{{status}}}. RSSI: {{{lastRSSI}}}
.
เรากำลังใช้แม่แบบหนวดที่นี่ อนุญาตให้คุณใช้ข้อมูลในเพย์โหลดที่เผยแพร่และฟอร์แมตใหม่ตามที่คุณต้องการ ในกรณีของเรา เราใช้พวกเขาเพื่อ "เติมในช่องว่าง" ข้อความ เมื่อประมวลผลแล้วจะมีลักษณะดังนี้:
The Tile is currently here. RSSI: -77
ฉันจะพูดถึงเทมเพลตเหล่านี้เพิ่มเติมในคำแนะนำของฉัน โปรดคอยติดตาม!
ทดสอบเลย
เมื่อผสานรวมเรียบร้อยแล้ว คุณสามารถทดสอบสิ่งที่เราทำในขั้นตอนก่อนหน้านี้ได้ ถอดแบตเตอรี่ออกและรอข้อความ "not here" ใส่กลับแล้วรอข้อความ "ที่นี่"
iPhone จะมีลักษณะดังนี้:
อย่างที่คุณเห็นฉันทดสอบมันเป็นกลุ่ม! ?
ถ้าคุณมาไกลถึงขนาดนี้แล้วและทุกอย่างทำงานได้ดี เยี่ยมมาก ตอนนี้คุณมีตัวติดตามไทล์สำหรับบ้าน สำนักงาน หรือที่ใดก็ตาม
เดอะโค้ด
กำลังมองหาโค้ดสำเร็จรูปสำหรับตัวอย่างนี้หรือไม่? ฉันก็เหมือนกัน! มันโฮสต์บน Github และสามารถใช้ได้ที่นี่
บทสรุป
อย่างที่คุณจินตนาการ เทคนิคและเทคโนโลยีที่ใช้ในบทความนี้สามารถใช้ได้หลายวิธี มาสรุปประเด็นสำคัญบางส่วนกัน:
- การใช้ Bluetooth Central เพื่อสแกนหาและระบุอุปกรณ์ Tile ที่มีจำหน่ายในท้องตลาด
- การจัดเก็บข้อมูลการระบุไทล์ไปยัง EEPROM ด้วยวิธีนี้จะสามารถดึงข้อมูลได้เมื่อเริ่มต้นใช้งาน
- การใช้
Particle.publish
. ที่คุ้นเคยของเรา เพื่อผลักดันการอัปเดตไปยังระบบคลาวด์ - การใช้ Particle Integration Webhook เพื่อสร้างการแจ้งเตือนแบบพุชเกี่ยวกับการเปลี่ยนแปลงสถานะ
ตอนนี้คุณใช้งานได้แล้ว ขยายขอบเขต แฮ็กและทำให้เป็นของคุณ โอ้และอย่าลืมแบ่งปัน! ฉันชอบที่จะได้ยินจากคุณ [email protected]
ชอบโพสต์นี้? คลิกลิงก์แชร์ด้านล่างและแชร์กับคนทั้งโลก :)
นี่เป็นการข้ามโพสต์จากบล็อกของฉัน คุณสามารถตรวจสอบต้นฉบับได้ที่นี่
สนใจเรียนรู้เพิ่มเติม? ฉันกำลังเขียนคู่มือเกี่ยวกับวิธีการใช้ประโยชน์สูงสุดจากแพลตฟอร์มอนุภาค เรียนรู้เพิ่มเติมได้ที่นี่