11.6. שירותים ומאפיינים

לאחר ש-GAP הביא שני התקנים לכדי חיבור פתוח, השכבה שמעליו – Generic Attribute Profile, או GATT – צריכה להעניק משמעות לבייטים הזורמים דרך אותו חיבור. הבחירה של BLE כאן חריגה. בעוד ש-TCP חושף זרם בייטים גולמי ומשאיר לאפליקציה להמציא לעצמה מסגור (framing), GATT חושף מסד נתונים קטן של מפתח/ערך שצד אחד מארח והצד השני קורא ממנו, כותב אליו או נרשם אליו.

אותו מסד נתונים הוא מה שמתכנני אפליקציות מבלים עליו את רוב זמן ה-BLE שלהם. מה שהמצלמה מפרסמת לטלפון, מה שהיא מנטרת בחיישן מרוחק, איך מקלדת Bluetooth מודיעה למארח שלה איזה מקש נלחץ – כל אלה הם ערכי מאפיין במסד נתונים GATT כלשהו במקום כלשהו.

11.6.1. שני צירי תפקיד, לא אחד

מקור תכוף לבלבול: peripheral / central ו-server / client הם שני צירים בלתי תלויים, לא מילים נרדפות.

  • Peripheral ו-central הם תפקידי GAP, הנקבעים בעת החיבור. ה-peripheral משדר פרסומות ומתחברים אליו; ה-central סורק ויוזם את החיבור. הדבר נקבע ברגע שהקישור עולה ואינו משתנה.

  • Server ו-client הם תפקידי GATT, הנקבעים לכל פעולת מאפיין בנפרד. ה-server מארח את המאפיין; ה-client קורא ממנו, כותב אליו או נרשם אליו.

שני הצירים מנותקים זה מזה במפרט. peripheral הוא בדרך כלל ה-server (רצועת מד-דופק מפרסמת את מדידותיה) ו-central הוא בדרך כלל ה-client (טלפון קורא אותן), אך BLE מתיר כל צירוף – peripheral עשוי לגלות מאפיין על ה-central שאליו זה עתה התחבר, או שחיבור יחיד יכול לארח שירותים בשני הצדדים בו-זמנית.

רוב אפליקציות המצלמה נצמדות לזיווג המקובל (peripheral + server, או central + client), ולכן שאר חלק זה מתייחס אליהם כאל ציר אחד כאשר המקרה המקובל הוא מה שמתואר. כאשר ההבחנה משמעותית, שני המונחים מאויתים במפורש.

11.6.2. בתוך מסד הנתונים

מסד נתונים GATT הוא עץ. העלים נושאים את הבייטים בפועל. הענפים מקבצים עלים קשורים ליחידות בעלות משמעות אנושית.

A tree with a top node labelled "GATT database". Below it, three Service nodes labelled "Generic Access (0x1800)", "Battery (0x180F)", and "Environmental Sensing (0x181A)". Each Service has child Characteristic nodes; the Battery service has "Battery Level (0x2A19)" with a child Descriptor "CCCD". The Environmental Sensing service has "Temperature (0x2A6E)" and "Humidity (0x2A6F)".

מסד נתונים GATT. שירותים מקבצים מאפיינים; מאפיינים נושאים את הבייטים של האפליקציה; מתארים (descriptors) נושאים מטא-נתונים על המאפיין.

ישנם שלושה סוגי צמתים:

  • שירות (service) הוא קבוצה לוגית של ערכים קשורים. ה-Bluetooth SIG מפרסם הגדרות שירות סטנדרטיות למקרי שימוש נפוצים – Battery Service לרמת סוללה, Environmental Sensing לטמפרטורה / לחות / לחץ, Heart Rate למדי-דופק – כך שאפליקציה גנרית בטלפון יכולה לזהות שירות שמעולם לא ראתה. אפליקציה רשאית גם להגדיר שירותים משלה לדברים שה-SIG לא תיקנן.

  • מאפיין (characteristic) הוא ערך אחד בעל שם בתוך שירות. לשירות ה-Battery יש מאפיין יחיד – Battery Level, אחוז בגודל בייט אחד. ל-Environmental Sensing יש מאפיינים נפרדים לטמפרטורה, לחות, לחץ וכן הלאה. מאפיין הוא יחידת פעולות ה-GATT – קוראים מאפיין, כותבים מאפיין, נרשמים למאפיין.

  • מתאר (descriptor) הוא מטא-נתונים המצורפים למאפיין. חלק מהמתארים מתוקננים – ה-Client Characteristic Configuration Descriptor (CCCD) הוא המפורסם ביותר, מכיוון שכתיבה אליו היא הדרך שבה client אומר ל-server ”שלח לי התראות על המאפיין הזה“. אחרים מוגדרים על ידי המשתמש ונושאים דברים כמו פורמט תצוגה או מאפיינים מורחבים.

GATT server (בדרך כלל ה-peripheral) מצהיר על מסד הנתונים שלו פעם אחת בהפעלה, ומסד הנתונים אינו משתנה בזמן ריצה. GATT client (בדרך כלל ה-central) מגלה מה יש במסד הנתונים לאחר ההתחברות – הוא מטייל בעץ, קורא את ה-UUID-ים של השירותים שהוא מוצא, ואז את המאפיינים שבתוך כל אחד מהם.

11.6.3. UUID-ים

לכל שירות, מאפיין ומתאר יש UUID (Universally Unique IDentifier) המזהה איזה סוג של דבר זה. UUID-ים מגיעים בשלושה רוחבים:

  • 16 ביט. שמור לתקנים שמגדיר ה-Bluetooth SIG. שירות ה-Battery הוא 0x180F. Battery Level (מאפיין) הוא 0x2A19. הרשימה המלאה מתפרסמת באתר המספרים המוקצים של ה-Bluetooth SIG בכתובת https://www.bluetooth.com/specifications/assigned-numbers/.

  • 32 ביט. דרך ביניים בשימוש נדיר.

  • 128 ביט. מה שכל השאר משתמשים בו – ספק או אפליקציה מייצרים אחד באקראי ומשתמשים בו לשירות או למאפיין מותאם אישית. מצלמות המגדירות פרוטוקול משלהן שוכנות כאן.

המחלקה bluetooth.UUID מקבלת כל אחד משלושת הרוחבים:

import bluetooth

BATTERY_SERVICE = bluetooth.UUID(0x180F)
CUSTOM_SERVICE = bluetooth.UUID("12345678-1234-5678-9abc-def012345678")

UUID של 16 ביט מקודד למטען פרסום קטן, וזו סיבה אחת לכך שעדיף להשתמש בשירותים סטנדרטיים כשקיים כזה – רצועת מד-דופק המפרסמת 0x180D (Heart Rate) עולה שני בייטים; UUID מותאם אישית עולה שישה-עשר. עבור אפליקציות שאינן זקוקות ליכולת פעולה הדדית סטנדרטית, UUID מיוצר של 128 ביט הוא התשובה הנכונה.

11.6.4. מה שהשירותים המתוקננים של ה-SIG מקנים לך

הנימוק לשימוש בשירות סטנדרטי פשוט: אפליקציות קיימות כבר יודעות לדבר אתו. התקן המפרסם את שירות ה-Heart Rate (0x180D) וחושף את מאפיין ה-Heart Rate Measurement (0x2A37) עובד עם כל אפליקציית כושר בעולם מבלי שאיש יכתוב קוד חדש. התקן המיישם מחדש את אותם נתונים עם UUID-ים מותאמים אישית זקוק לאפליקציה נלווית משלו ולמסמך פרוטוקול משלו.

התקנים אכן באים במחיר. פריסות הבייטים בתוך כל מאפיין מוגדרות – ה-SIG החליט ש-Heart Rate Measurement הוא שדה דגלים בגודל בייט אחד ואחריו ערך דופק של 8 ביט או 16 ביט, ובאופן אופציונלי ואחריו מרווחי R-R – והתקן תואם חייב לעקוב אחר פריסות אלה. שירותים מותאמים אישית חופשיים ממגבלה זו.

התשובה הפרגמטית למצלמות: השתמש בשירות הסטנדרטי כשקיים כזה לסוג הנתונים שיש לך (Battery Service, Environmental Sensing), והגדר שירות מותאם אישית עם UUID של 128 ביט לכל דבר ייחודי לאפליקציה שלך.

11.6.5. אובייקטים בצד השרת ובצד הלקוח

עבור אותם אבני בניין רעיוניות (שירות, מאפיין, מתאר), כל ספריית GATT חושפת שתי קבוצות מקבילות של אובייקטים: