aboutsummaryrefslogtreecommitdiffstats
path: root/arduino/libraries/Bluefruit52Lib/examples/Peripheral/ancs_oled/ancs_oled.ino
blob: ce91a312a3dfd0f4b02f0e4a895a7f32f881c5a7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
/*********************************************************************
 This is an example for our nRF52 based Bluefruit LE modules

 Pick one up today in the adafruit shop!

 Adafruit invests time and resources providing this open source code,
 please support Adafruit and open-source hardware by purchasing
 products from Adafruit!

 MIT license, check LICENSE for more information
 All text above, and the splash screen below must be included in
 any redistribution
*********************************************************************/

/*
 * This sketch is similar to 'ancs', but it also uses a Feather OLED
 * Wing to display incoming ANCS alerts:
 * https://www.adafruit.com/product/2900
 *
 * BUTTON A: Up or accept call
 * BUTTON B: Not used since it is hard to press
 * BUTTON C: Down or decline call
 */
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include <bluefruit.h>

/*------------- OLED and Buttons -------------*/
#if defined ARDUINO_NRF52_FEATHER
// Feather nRF52832
#define BUTTON_A    31
#define BUTTON_B    30
#define BUTTON_C    27

#elif defined ARDUINO_NRF52840_FEATHER
// Feather nRF52840
#define BUTTON_A    9
#define BUTTON_B    6
#define BUTTON_C    5

#else
#error board not supported
#endif


#define OLED_RESET 4 // TODO remove ?
Adafruit_SSD1306 oled(OLED_RESET);


/*------------- Notification List -------------*/
#define MAX_COUNT   20
#define BUFSIZE     64

typedef struct
{
  AncsNotification_t ntf;
  char title[BUFSIZE];
  char message[BUFSIZE];
  char app_name[BUFSIZE];
} MyNotif_t;

MyNotif_t myNotifs[MAX_COUNT] = { 0 };

// Number of notifications
int notifCount = 0;

/*------------- Display Management -------------*/
#define ONSCREEN_TIME 5000  // On-screen time for each notification

int  activeIndex  = 0;      // Index of currently displayed notification
int  displayIndex = -1;     // Index of notification about to display

uint32_t drawTime = 0;      // Last time oled display notification

/*------------- BLE Client Service-------------*/
BLEAncs       bleancs;

void setup()
{
  // Button configured
  pinMode(BUTTON_A, INPUT_PULLUP);
  pinMode(BUTTON_B, INPUT_PULLUP);
  pinMode(BUTTON_C, INPUT_PULLUP);

  // init with the I2C addr 0x3C (for the 128x32) and show splashscreen
  oled.begin(SSD1306_SWITCHCAPVCC, 0x3C); 
  oled.display();

  oled.setTextSize(1);// max is 4 line, 21 chars each
  oled.setTextColor(WHITE);

  // Config the peripheral connection with maximum bandwidth
  // more SRAM required by SoftDevice
  // Note: All config***() function must be called before begin()
  //Bluefruit.configPrphBandwidth(BANDWIDTH_MAX);

  Bluefruit.begin();
  // Set max power. Accepted values are: -40, -30, -20, -16, -12, -8, -4, 0, 4
  Bluefruit.setTxPower(4);
  Bluefruit.setName("Bluefruit52");
  Bluefruit.setConnectCallback(connect_callback);
  Bluefruit.setDisconnectCallback(disconnect_callback);

  // Configure and Start Service
  bleancs.begin();
  bleancs.setNotificationCallback(ancs_notification_callback);

  // Set up and start advertising
  startAdv();

  // splash screen effect
  delay(100);

  oled.clearDisplay();
  oled.setCursor(0, 0);
  oled.println("Not connected");
  oled.display();
}

void startAdv(void)
{
  // Advertising packet
  Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
  Bluefruit.Advertising.addTxPower();

  // Include ANCS 128-bit uuid
  Bluefruit.Advertising.addService(bleancs);

  // Secondary Scan Response packet (optional)
  // Since there is no room for 'Name' in Advertising packet
  Bluefruit.ScanResponse.addName();
  
  /* Start Advertising
   * - Enable auto advertising if disconnected
   * - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
   * - Timeout for fast mode is 30 seconds
   * - Start(timeout) with timeout = 0 will advertise forever (until connected)
   * 
   * For recommended advertising interval
   * https://developer.apple.com/library/content/qa/qa1931/_index.html   
   */
  Bluefruit.Advertising.restartOnDisconnect(true);
  Bluefruit.Advertising.setInterval(32, 244);    // in unit of 0.625 ms
  Bluefruit.Advertising.setFastTimeout(30);      // number of seconds in fast mode
  Bluefruit.Advertising.start(0);                // 0 = Don't stop advertising after n seconds  
}

void loop()
{
  // If service is not yet discovered
  if ( !bleancs.discovered() ) return;

  // No notifications, do nothing
  if ( notifCount == 0 ) return;

  // Check buttons
  uint32_t presedButtons = readPressedButtons();

  if ( myNotifs[activeIndex].ntf.categoryID == ANCS_CAT_INCOMING_CALL )
  {
    /* Incoming call event
     * - Button A to accept call
     * - Button C to decline call
     */
    if ( presedButtons & bit(BUTTON_A) )
    {
      bleancs.actPositive(myNotifs[activeIndex].ntf.uid);
    }

    if ( presedButtons & bit(BUTTON_C) )
    {
      bleancs.actNegative(myNotifs[activeIndex].ntf.uid);
    }
  }
  else
  {
    /* Normal events navigation (wrap around)
     * - Button A to display previous notification
     * - Button C to display next notification
     *
     * When a notification is display ONSCREEN_TIME,
     * we will display the next one
     */
    if ( presedButtons & bit(BUTTON_A) )
    {
      displayIndex = (activeIndex != 0) ? (activeIndex-1) : (notifCount-1) ;
    }

    if ( presedButtons & bit(BUTTON_C) )
    {
      displayIndex = (activeIndex != (notifCount-1)) ? (activeIndex + 1) : 0;
    }

    // Display requested notification
    if ( displayIndex >= 0 )
    {
      activeIndex = displayIndex;
      displayIndex = -1;

      displayNotification(activeIndex);
      drawTime = millis(); // Save time we draw
    }
    // Display next notification if time is up
    else if ( drawTime + ONSCREEN_TIME < millis() )
    {
      activeIndex = (activeIndex+1)%notifCount;

      displayNotification(activeIndex);
      drawTime = millis(); // Save time we draw
    }
  }
}

/**
 * Display notification contents to oled screen
 * @param index index of notification
 */
void displayNotification(int index)
{
  // safeguard
  if ( index < 0 || (index >= notifCount) ) return;

  // let's Turn on and off RED LED when we draw to get attention
  digitalWrite(LED_RED, HIGH);

  /*------------- Display to OLED -------------*/
  MyNotif_t* myNtf = &myNotifs[index];

  oled.clearDisplay();
  oled.setCursor(0, 0);

  // Incoming call event, display a bit differently
  if ( myNtf->ntf.categoryID == ANCS_CAT_INCOMING_CALL )
  {
    oled.println(myNtf->title);
    oled.println("          is calling");
    oled.println("  Btn A to ACCEPT");
    oled.println("  Btn C to DECLINE");
  }else
  {
    // Text size = 1, max char is 21. Text size = 2, max char is 10
    char tempbuf[22];
    sprintf(tempbuf, "%-15s %02d/%02d", myNtf->app_name, index+1, notifCount);

    oled.println(tempbuf);
    oled.println(myNtf->title);

    oled.print("  ");
    oled.print(myNtf->message);
  }

  oled.display();

  digitalWrite(LED_RED, LOW);
}

/**
 * Connect Callback
 *  Perform ANCS discovering, request Pairing
 */
void connect_callback(uint16_t conn_handle)
{
  oled.clearDisplay();
  oled.setCursor(0, 0);
  oled.println("Connected.");
  oled.print("Discovering ... ");
  oled.display();
  
  if ( bleancs.discover( conn_handle ) )
  {
    oled.println("OK");

    // ANCS requires pairing to work
    oled.print("Paring      ... ");

    oled.display();

    if ( Bluefruit.requestPairing() )
    {
      oled.println("OK");

      bleancs.enableNotification();
      oled.println("Receiving   ...");
    }else
    {
      oled.println("Failed");
    }
  }else
  {
    oled.println("Failed");
  }

  oled.display();
}

/**
 * Notification callback
 * @param notif Notification from iDevice
 *
 * Save/Modify notification into myNotifs struct to display later
 */
void ancs_notification_callback(AncsNotification_t* notif)
{
  if (notif->eventID == ANCS_EVT_NOTIFICATION_ADDED )
  {
    myNotifs[ notifCount ].ntf = *notif;

    /*------------- Retrieve Title, Message, App Name -------------*/
    MyNotif_t* myNtf = &myNotifs[notifCount];
    uint32_t uid = myNtf->ntf.uid;

    // iDevice often include Unicode "Bidirection Text Control" in the Title.
    // Mostly are U+202D as beginning and U+202C as ending. Let's remove them
    if ( bleancs.getTitle  (uid, myNtf->title   , BUFSIZE) )
    {
      char u202D[3] = { 0xE2, 0x80, 0xAD }; // U+202D in UTF-8
      char u202C[3] = { 0xE2, 0x80, 0xAC }; // U+202C in UTF-8

      int len = strlen(myNtf->title);

      if ( 0 == memcmp(&myNtf->title[len-3], u202C, 3) )
      {
        len -= 3;
        myNtf->title[len] = 0; // chop ending U+202C
      }

      if ( 0 == memcmp(myNtf->title, u202D, 3) )
      {
        memmove(myNtf->title, myNtf->title+3, len-2); // move null-terminator as well
      }
    }

    bleancs.getMessage(uid, myNtf->message , BUFSIZE);
    bleancs.getAppName(uid, myNtf->app_name, BUFSIZE);

    displayIndex = notifCount++; // display new notification
  }else if (notif->eventID == ANCS_EVT_NOTIFICATION_REMOVED )
  {
    for(int i=0; i<notifCount; i++)
    {
      if ( notif->uid == myNotifs[i].ntf.uid )
      {
        // remove by swapping with the last one
        notifCount--;
        myNotifs[i] = myNotifs[notifCount];

        // Invalid removed data
        memset(&myNotifs[notifCount], 0, sizeof(MyNotif_t));

        if (activeIndex == notifCount)
        {
          // If remove the last notification, adjust display index
          displayIndex = notifCount-1;
        }else if (activeIndex == i)
        {
          // Re-draw if remove currently active one
          displayIndex = activeIndex;
        }

        break;
      }
    }
  }else
  {
    // Modification
    for(int i=0; i<notifCount; i++)
    {
      if ( notif->uid == myNotifs[i].ntf.uid )
      {
        // Display modification
        displayIndex = i;
        break;
      }
    }
  }
}

/**
 * Callback invoked when a connection is dropped
 * @param conn_handle connection where this event happens
 * @param reason is a BLE_HCI_STATUS_CODE which can be found in ble_hci.h
 * https://github.com/adafruit/Adafruit_nRF52_Arduino/blob/master/cores/nRF5/nordic/softdevice/s140_nrf52_6.1.1_API/include/ble_hci.h
 */
void disconnect_callback(uint16_t conn_handle, uint8_t reason)
{
  (void) conn_handle;
  (void) reason;

  // reset notification array
  notifCount   = 0;
  activeIndex  = 0;
  displayIndex = -1;

  memset(myNotifs, 0, sizeof(myNotifs));

  oled.clearDisplay();
  oled.setCursor(0, 0);
  oled.println("Not connected");
  oled.display();
}

/**
 * Check if button A,B,C state are pressed, include some software
 * debouncing.
 *
 * Note: Only set bit when Button is state change from
 * idle -> pressed. Press and hold only report 1 time, release
 * won't report as well
 *
 * @return Bitmask of pressed buttons e.g If BUTTON_A is pressed
 * bit 31 will be set.
 */
uint32_t readPressedButtons(void)
{
  // must be exponent of 2
  enum { MAX_CHECKS = 8, SAMPLE_TIME = 10 };

  /* Array that maintains bounce status/, which is sampled
   * 10 ms each. Debounced state is regconized if all the values
   * of a button has the same value (bit set or clear)
   */
  static uint32_t lastReadTime = 0;
  static uint32_t states[MAX_CHECKS] = { 0 };
  static uint32_t index = 0;

  // Last Debounced state, used to detect changed
  static uint32_t lastDebounced = 0;

  // Too soon, nothing to do
  if (millis() - lastReadTime < SAMPLE_TIME ) return 0;

  lastReadTime = millis();

  // Take current read and masked with BUTTONs
  // Note: Bitwise inverted since buttons are active (pressed) LOW
  uint32_t debounced = ~(*portInputRegister( digitalPinToPort(0) ));
  debounced &= (bit(BUTTON_A) | bit(BUTTON_B) | bit(BUTTON_C));

  // Copy current state into array
  states[ (index & (MAX_CHECKS-1)) ] = debounced;
  index++;

  // Bitwise And all the state in the array together to get the result
  // This means pin must stay at least MAX_CHECKS time to be realized as changed
  for(int i=0; i<MAX_CHECKS; i++)
  {
    debounced &= states[i];
  }

  // result is button changed and current debounced is set
  // Mean button is pressed (idle previously)
  uint32_t result = (debounced ^ lastDebounced) & debounced;

  lastDebounced = debounced;

  return result;
}