/ EyeLights_Bluetooth_Scroller / EyeLights_Bluetooth_Scroller.ino
EyeLights_Bluetooth_Scroller.ino
  1  // SPDX-FileCopyrightText: 2021 Phil Burgess for Adafruit Industries
  2  //
  3  // SPDX-License-Identifier: MIT
  4  
  5  /*
  6  BLUETOOTH SCROLLING MESSAGE for Adafruit EyeLights (LED Glasses + Driver).
  7  Use BLUEFRUIT CONNECT app on iOS or Android to connect to LED glasses.
  8  Use the app's UART input to enter a new message.
  9  Use the app's Color Picker (under "Controller") to change text color.
 10  This is based on the glassesdemo-3-smooth example from the
 11  Adafruit_IS31FL3741 library, with Bluetooth additions on top. If this
 12  code all seems a bit too much, you can start with that example (or the two
 13  that precede it) to gain an understanding of the LED glasses basics, then
 14  return here to see what the extra Bluetooth layers do.
 15  */
 16  
 17  #include <Adafruit_IS31FL3741.h> // For LED driver
 18  #include <bluefruit.h>           // For Bluetooth communication
 19  #include <EyeLightsCanvasFont.h> // Smooth scrolly font for glasses
 20  
 21  // These items are over in the packetParser.cpp tab:
 22  extern uint8_t packetbuffer[];
 23  extern uint8_t readPacket(BLEUart *ble, uint16_t timeout);
 24  extern int8_t packetType(uint8_t *buf, uint8_t len);
 25  extern float parsefloat(uint8_t *buffer);
 26  extern void printHex(const uint8_t * data, const uint32_t numBytes);
 27  
 28  // GLOBAL VARIABLES -------
 29  
 30  // 'Buffered' glasses for buttery animation,
 31  // 'true' to allocate a drawing canvas for smooth graphics:
 32  Adafruit_EyeLights_buffered glasses(true);
 33  GFXcanvas16 *canvas; // Pointer to glasses' canvas object
 34  // Because 'canvas' is a pointer, always use -> when calling
 35  // drawing functions there. 'glasses' is an object in itself,
 36  // so . is used when calling its functions.
 37  
 38  char message[51] = "Run Bluefruit Connect app"; // Scrolling message
 39  int16_t text_x;   // Message position on canvas
 40  int16_t text_min; // Leftmost position before restarting scroll
 41  
 42  BLEUart bleuart;  // Bluetooth low energy UART
 43  
 44  int8_t last_packet_type = 99; // Last BLE packet type, init to nonsense value
 45  
 46  // ONE-TIME SETUP ---------
 47  
 48  void setup() { // Runs once at program start...
 49  
 50    Serial.begin(115200);
 51    //while(!Serial);
 52  
 53    // Configure and start the BLE UART service
 54    Bluefruit.begin();
 55    Bluefruit.setTxPower(4);
 56    bleuart.begin();
 57    startAdv(); // Set up and start advertising
 58  
 59    if (!glasses.begin()) err("IS3741 not found", 2);
 60  
 61    canvas = glasses.getCanvas();
 62    if (!canvas) err("Can't allocate canvas", 5);
 63  
 64    // Configure glasses for full brightness and enable output
 65    glasses.setLEDscaling(0xFF);
 66    glasses.setGlobalCurrent(0xFF);
 67    glasses.enable(true);
 68  
 69    // Set up for scrolling text, initialize color and position
 70    canvas->setFont(&EyeLightsCanvasFont);
 71    canvas->setTextWrap(false); // Allow text to extend off edges
 72    canvas->setTextColor(glasses.color565(0x303030)); // Dim white to start
 73    reposition_text(); // Sets up initial position & scroll limit
 74  }
 75  
 76  // Crude error handler, prints message to Serial console, flashes LED
 77  void err(char *str, uint8_t hz) {
 78    Serial.println(str);
 79    pinMode(LED_BUILTIN, OUTPUT);
 80    for (;;) digitalWrite(LED_BUILTIN, (millis() * hz / 500) & 1);
 81  }
 82  
 83  // Set up, start BLE advertising
 84  void startAdv(void) {
 85    // Advertising packet
 86    Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
 87    Bluefruit.Advertising.addTxPower();
 88    
 89    // Include the BLE UART (AKA 'NUS') 128-bit UUID
 90    Bluefruit.Advertising.addService(bleuart);
 91  
 92    // Secondary Scan Response packet (optional)
 93    // Since there is no room for 'Name' in Advertising packet
 94    Bluefruit.ScanResponse.addName();
 95  
 96    // Start Advertising
 97    // - Enable auto advertising if disconnected
 98    // - Interval:  fast mode = 20 ms, slow mode = 152.5 ms
 99    // - Timeout for fast mode is 30 seconds
100    // - Start(timeout) with timeout = 0 will advertise forever (until connected)
101    // 
102    // For recommended advertising interval
103    // https://developer.apple.com/library/content/qa/qa1931/_index.html   
104    Bluefruit.Advertising.restartOnDisconnect(true);
105    Bluefruit.Advertising.setInterval(32, 244); // in unit of 0.625 ms
106    Bluefruit.Advertising.setFastTimeout(30);   // number of seconds in fast mode
107    Bluefruit.Advertising.start(0);             // 0 = Don't stop advertising after n seconds  
108  }
109  
110  // MAIN LOOP --------------
111  
112  void loop() { // Repeat forever...
113    // The packet read timeout (9 ms here) also determines the text
114    // scrolling speed -- if no data is received over BLE in that time,
115    // the function exits and returns here with len=0.
116    uint8_t len = readPacket(&bleuart, 9);
117    if (len) {
118      int8_t type =  packetType(packetbuffer, len);
119      // The Bluefruit Connect app can return a variety of data from
120      // a phone's sensors. To keep this example relatively simple,
121      // we'll only look at color and text, but here's where others
122      // would go if we were to extend this. See Bluefruit library
123      // examples for the packet data formats. packetParser.cpp
124      // has a couple functions not used in this code but that may be
125      // helpful in interpreting these other packet types.
126      switch(type) {
127       case 0: // Accelerometer
128        Serial.println("Accel");
129        break;
130       case 1: // Gyro:
131        Serial.println("Gyro");
132        break;
133       case 2: // Magnetometer
134        Serial.println("Mag");
135        break;
136       case 3: // Quaternion
137        Serial.println("Quat");
138        break;
139       case 4: // Button
140        Serial.println("Button");
141        break;
142       case 5: // Color
143        Serial.println("Color");
144        // packetbuffer[2] through [4] contain R, G, B byte values.
145        // Because the drawing canvas uses lower-precision '565' color,
146        // and because glasses.scale() applies gamma correction and may
147        // quantize the dimmest colors to 0, set a brightness floor here
148        // so text isn't invisible.
149        for (uint8_t i=2; i<=4; i++) {
150          if (packetbuffer[i] < 0x20) packetbuffer[i] = 0x20;
151        }
152        canvas->setTextColor(glasses.color565(glasses.Color(
153          packetbuffer[2], packetbuffer[3], packetbuffer[4])));
154        break;
155       case 6: // Location
156        Serial.println("Location");
157        break;
158       default: // -1
159        // Packet is not one of the Bluefruit Connect types. Most programs
160        // will ignore/reject it as not valud, but in this case we accept
161        // it as a freeform string for the scrolling message.
162        if (last_packet_type != -1) {
163          // If prior data was a packet, this is a new freeform string,
164          // initialize the message string with it...
165          strncpy(message, (char *)packetbuffer, 20);
166        } else {
167          // If prior data was also a freeform string, concatenate this onto
168          // the message (up to the max message length). BLE packets can only
169          // be so large, so long strings are broken into multiple packets.
170          uint8_t message_len = strlen(message);
171          uint8_t max_append = sizeof message - 1 - message_len;
172          strncpy(&message[message_len], (char *)packetbuffer, max_append);
173          len = message_len + max_append;
174        }
175        message[len] = 0; // End of string NUL char
176        Serial.println(message);
177        reposition_text(); // Reset text off right edge of canvas
178      }
179      last_packet_type = type; // Save packet type for next pass
180    } else {
181      last_packet_type = 99; // BLE read timeout, reset last type to nonsense
182    }
183  
184    canvas->fillScreen(0); // Clear the whole drawing canvas
185    // Update text to new position, and draw on canvas
186    if (--text_x < text_min) {  // If text scrolls off left edge,
187      text_x = canvas->width(); // reset position off right edge
188    }
189    canvas->setCursor(text_x, canvas->height());
190    canvas->print(message);
191    glasses.scale(); // 1:3 downsample canvas to LED matrix
192    glasses.show();  // MUST call show() to update matrix
193  }
194  
195  // When new message text is assigned, call this to reset its position
196  // off the right edge and calculate column where scrolling resets.
197  void reposition_text() {
198    uint16_t w, h, ignore;
199    canvas->getTextBounds(message, 0, 0, (int16_t *)&ignore, (int16_t *)&ignore, &w, &ignore);
200    text_x = canvas->width();
201    text_min = -w; // Off left edge this many pixels
202  }