Skip to content

Commit fe66035

Browse files
Merge pull request #1882 from PaintYourDragon/main
Add EyeLights_Bluetooth_Scroller project
2 parents 945a085 + 500885e commit fe66035

4 files changed

Lines changed: 321 additions & 0 deletions

File tree

File renamed without changes.
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
#include <bluefruit.h>
2+
3+
// packetbuffer holds inbound data
4+
#define READ_BUFSIZE 20
5+
uint8_t packetbuffer[READ_BUFSIZE + 1]; // +1 is for NUL string terminator
6+
7+
/**************************************************************************/
8+
/*!
9+
@brief Casts the four bytes at the specified address to a float.
10+
@param ptr Pointer into packet buffer.
11+
@returns Floating-point value.
12+
*/
13+
/**************************************************************************/
14+
float parsefloat(uint8_t *ptr) {
15+
float f; // Make a suitably-aligned float variable,
16+
memcpy(&f, ptr, 4); // because in-buffer instance might be misaligned!
17+
return f; // (You can't always safely parse in-place)
18+
}
19+
20+
/**************************************************************************/
21+
/*!
22+
@brief Prints a series of bytes in 0xNN hexadecimal notation.
23+
@param buf Pointer to array of byte data.
24+
@param len Data length in bytes.
25+
*/
26+
/**************************************************************************/
27+
void printHex(const uint8_t *buf, const uint32_t len) {
28+
for (uint32_t i=0; i < len; i++) {
29+
Serial.print(F("0x"));
30+
if (buf[i] <= 0xF) Serial.write('0'); // Zero-pad small values
31+
Serial.print(buf[i], HEX);
32+
if (i < (len - 1)) Serial.write(' '); // Space between bytes
33+
}
34+
Serial.println();
35+
}
36+
37+
static const struct { // Special payloads from Bluefruit Connect app...
38+
char id; // Packet type identifier
39+
uint8_t len; // Size of complete, well-formed packet of this type
40+
} _app_packet[] = {
41+
{'A', 15}, // Accelerometer
42+
{'G', 15}, // Gyro
43+
{'M', 15}, // Magnetometer
44+
{'Q', 19}, // Quaterion
45+
{'B', 5}, // Button
46+
{'C', 6}, // Color
47+
{'L', 15}, // Location
48+
};
49+
#define NUM_PACKET_TYPES (sizeof _app_packet / sizeof _app_packet[0])
50+
#define SHORTEST_PACKET_LEN 5 // Button, for now
51+
52+
/**************************************************************************/
53+
/*!
54+
@brief Given packet data, identify if it's one of the known
55+
Bluefruit Connect app packet types.
56+
@param buf Pointer to packet data.
57+
@param len Size of packet in bytes.
58+
@returns Packet type index (0 to NUM_PACKET_TYPES-1) if recognized,
59+
-1 if unrecognized.
60+
*/
61+
/**************************************************************************/
62+
int8_t packetType(uint8_t *buf, uint8_t len) {
63+
if ((len >= SHORTEST_PACKET_LEN) && (buf[0] == '!')) {
64+
for (int8_t type=0; type<NUM_PACKET_TYPES; type++) {
65+
if ((buf[1] == _app_packet[type].id) &&
66+
(len == _app_packet[type].len)) {
67+
return type;
68+
}
69+
}
70+
}
71+
return -1; // Length too short for a packet, or not a recognized type
72+
}
73+
74+
/**************************************************************************/
75+
/*!
76+
@brief Wait for incoming data and determine if it's one of the
77+
special Bluefruit Connect app packet types.
78+
@param ble Pointer to BLE UART object.
79+
timeout Character read timeout in milliseconds.
80+
@returns Length of data, or 0 if checksum is invalid for the type of
81+
packet detected.
82+
@note Packet buffer is not cleared. Calling function is expected
83+
to check return value before deciding whether to act on the
84+
data.
85+
*/
86+
/**************************************************************************/
87+
uint8_t readPacket(BLEUart *ble, uint16_t timeout) {
88+
int8_t type = -1; // App packet type, -1 if unknown or freeform string
89+
uint8_t len = 0, xsum = 255; // Packet length and ~checksum so far
90+
uint32_t now, start_time = millis();
91+
do {
92+
now = millis();
93+
if (ble->available()) {
94+
char c = ble->read();
95+
if (c == '!') { // '!' resets buffer to start
96+
len = 0;
97+
xsum = 255;
98+
}
99+
packetbuffer[len++] = c;
100+
// Stop when buffer's full or packet type recognized
101+
if ((len >= READ_BUFSIZE) ||
102+
((type = packetType(packetbuffer, len)) >= 0)) break;
103+
start_time = now; // Reset timeout on char received
104+
xsum -= c; // Not last char, do checksum math
105+
type = -1; // Reset packet type finder
106+
}
107+
} while((now - start_time) < timeout);
108+
109+
// If packet type recognized, verify checksum (else freeform string)
110+
if ((type >= 0) && (xsum != packetbuffer[len-1])) { // Last byte = checksum
111+
Serial.print("Packet checksum mismatch: ");
112+
printHex(packetbuffer, len);
113+
return 0;
114+
}
115+
116+
packetbuffer[len] = 0; // Terminate packet string
117+
118+
return len; // Checksum is valid for packet, or it's a freeform string
119+
}

Temperature_GIF_Player/.none.test.only

Whitespace-only changes.

0 commit comments

Comments
 (0)