11.7. פעולות GATT

מאפיין (characteristic) פשוט יושב במסד הנתונים של GATT כערך בעל שם. מה שהופך אותו לשימושי הוא הסט הקטן והמוגדר היטב של הפעולות שלקוח (client) יכול להפעיל עליו. כל מאפיין מצהיר אילו פעולות הוא תומך בהן באמצעות property bitmask – שרת שאין לו דבר לחשוף יכול לפרסם ערך לקריאה בלבד, אוגר בקרה עשוי להיות לכתיבה בלבד, חיישן שמזרים עדכונים יקבע את ביט ה-notify. הלקוח מגלה את ה-bitmask במהלך הגילוי ומכבד אותו.

חמש הפעולות הן read, write, write without response, notify ו-indicate. הן מתחלקות לשתי קבוצות – משיכה (הלקוח מבקש) ודחיפה (השרת שולח).

11.7.1. משיכה: read ו-write

שתי אלה הן הפשוטות ביותר ונראות בדיוק כמו קריאות לפונקציה.

  • Read. הלקוח מבקש את הערך הנוכחי, השרת שולח אותו בחזרה. סבב הלוך-ושוב אחד, הלקוח מקבל את הבייטים שהשרת קבע עבור אותו מאפיין, השרת אינו מקבל דבר לגבי מי שקרא.

  • Write. הלקוח שולח בייטים חדשים, השרת מאחסן אותם (ואופציונלית מריץ לוגיקה יישומית על הערך החדש). קיימים שני סוגים:

    • Write with response – השרת מאשר, ומעלה כל שגיאה יישומית בסטטוס שאינו אפס. אמין, סבב הלוך-ושוב אחד.

    • Write without response – השרת מאחסן את הבייטים בשקט; הלקוח אינו מקבל אישור כלל. מהיר יותר (אין סבב הלוך-ושוב בהמתנה לאישור) ושימושי להזרמה, במחיר של גילוי שגיאות רק באמצעות קריאה חוזרת בערוץ צד.

ב-aioble, ה-API בצד הלקוח מסתיר את הבחירה מאחורי מתודה יחידה aioble.ClientCharacteristic.write() עם מילת מפתח response (True / False / None לבחירה אוטומטית בהתבסס על מה שההתקן העמית מפרסם).

11.7.2. דחיפה: notify ו-indicate

מודל המשיכה שגוי עבור נתוני חיישן. רצועת דופק שהטלפון נאלץ לתשאל כל שנייה תשרוף סוללה על מאה אירועי רדיו מיותרים; רצועה שדוחפת ערך רק כשיש לה קריאה חדשה היא הנקודה של BLE מלכתחילה.

GATT פותר זאת באמצעות פעולות server-initiated. הלקוח נרשם (subscribes) למאפיין; מאותה נקודה והלאה, בכל פעם שהשרת מעדכן את הערך, הערך החדש נדחף דרך הקישור אל הלקוח. שני סוגים:

  • Notify. שגר-ושכח. השרת מכניס לתור הודעת notification, שכבת הקישור משדרת אותה במהלך אירוע החיבור הבא, הלקוח מקבל אותה. אין אישור ברמת GATT; השידור החוזר הרגיל של שכבת הקישור מטפל באובדן בצד הרדיו, אך היישום אינו רואה אישור כלשהו שהערך עובד.

  • Indicate. השרת שולח notification וממתין לאישור ברמת GATT מהלקוח לפני שליחת הבא. indication אחד בכל פעם. בשימוש כאשר השרת צריך לדעת שהלקוח אכן ראה את הערך – מאפיין של התראה קריטית, אישור של תצורה.

Two side-by-side diagrams of a server and a client. On the left, the client sends "read", the server replies with the value. Three reads in a row, each pair of arrows. On the right, the client sends a single "subscribe", then the server pushes three "notify" packets at the times it chooses, without any client request in between.

משיכה (read) לעומת דחיפה (notify). עם notifications, הלקוח נרשם פעם אחת והשרת דוחף ערכים חדשים בכל פעם שהם משתנים.

ההרשמה מתבצעת על ידי כתיבה ל-descriptor המצורף למאפיין – ה-Client Characteristic Configuration Descriptor (CCCD, 0x2902). כתיבת 0x0001 מאפשרת notifications, 0x0002 מאפשרת indications, 0x0000 מבטלת את שתיהן. המתודה aioble.ClientCharacteristic.subscribe() מבצעת את הכתיבה עבורך, עם דגלי מילות מפתח notify=True ו-indicate=True.

לאחר ההרשמה, הלקוח ממתין לדחיפות נכנסות עם notified() ו-indicated() – שתיהן async coroutines שמשעות עד שהדחיפה הבאה מגיעה.

11.7.3. ה-MTU שולט בגודל המטען

כל פעולה מוגבלת על ידי ה-MTU שעליו התייצב החיבור במשא ומתן בזמן עליית הקישור. ה-MTU בברירת מחדל הוא 23 בייטים, מה שמשאיר 20 בייטים לבייטים של ערך המאפיין לאחר כותרת ה-GATT. כל דבר גדול מזה חייב או להיכנס ל-MTU גדול יותר (שנקבע בעלייה במשא ומתן דרך aioble.DeviceConnection.exchange_mtu(), עד 512 בייטים במצלמה) או להתפצל למספר מאפיינים או מספר notifications.

קריאות וכתיבות שיזם הלקוח של ערכים גדולים מה-MTU מטופלות על ידי הפרוצדורות ה-ארוכות של GATT מאחורי הקלעים (Read Long / Prepare-Write + Execute-Write); aioble מריץ אותן בשקיפות, כך שקריאה ל-read() / write() עם ערך גדול מדי פשוט עולה יותר סבבי הלוך-ושוב. notifications ו-indications שיזם השרת אינם מפוצלים – דחיפה אחת חסומה על ידי ה-MTU, והיישום מפצל כל דבר גדול יותר למספר notifications או יורד מ-GATT לחלוטין.

עבור העברות גדולות באמת – פריים שנלכד, אצווה של מדידות, blob של קושחה – התשובה הנכונה היא בדרך כלל לרדת מ-GATT לחלוטין ולהשתמש ב-L2CAP channel במקום (ראה ערוצי L2CAP).

11.7.4. שני הצדדים במבט חטוף

חמש הפעולות נחשפות באופן שונה בכל צד של החיבור: