Skip to content

Commit 4b771e7

Browse files
BLE based on GATT services (first cut)
- first version of BLE GATT services (untested) - enhanced notifications tell which properties changed - saving Flash and RAM in serial UI - removed config param ‘insulationFactor’ (not currently used)
1 parent 5f62586 commit 4b771e7

12 files changed

+473
-191
lines changed

bc_setup.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
// Uncomment AT MOST ONE (!) of the following lines:
55
// #define UNIT_TEST
66
// #define BLE_UI
7-
#define SERIAL_UI
7+
// #define SERIAL_UI
88

99
#ifdef UNIT_TEST
1010
// Comment the following line to prevent execution of STATE tests:
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
2+
#include <stdio.h>
3+
#include "Adafruit_BluefruitLE_GATT.h"
4+
5+
#define RED_LED_PIN 13
6+
7+
const char fmt_gapdevname[] PROGMEM = "AT+GAPDEVNAME=%s";
8+
const char fmt_gattaddservice[] PROGMEM = "AT+GATTADDSERVICE=UUID128=%s";
9+
const char fmt_gattaddchar[] PROGMEM = "AT+GATTADDCHAR=UUID=%#4X,PROPERTIES=%#2X,MIN_LEN=%i,MAX_LEN=%i,VALUE=%s";
10+
const char fmt_gattsetchar[] PROGMEM = "AT+GATTCHAR=%i,%s";
11+
const char fmt_gattgetchar[] PROGMEM = "AT+GATTCHAR=%i";
12+
const char fmt_bintohex[] PROGMEM = "%02X-";
13+
14+
void Adafruit_BluefruitLE_GATT::assertOK(boolean condition, const __FlashStringHelper *err) {
15+
if (condition) return;
16+
17+
Serial.print(F("### S.O.S. ### "));
18+
Serial.println(err);
19+
Serial.flush();
20+
int pulse = 300; // [ms]
21+
while(1) {
22+
// S.O.S.
23+
pulse = (pulse + 200) % 400; // toggles between 100 and 300 ms
24+
delay(pulse);
25+
for(byte i=0; i<3; i++) {
26+
digitalWrite(RED_LED_PIN, HIGH);
27+
delay(pulse);
28+
digitalWrite(RED_LED_PIN, LOW);
29+
delay(pulse);
30+
}
31+
}
32+
}
33+
34+
void Adafruit_BluefruitLE_GATT::setGattDeviceName(const char *name) {
35+
char cmd[strlen_P(fmt_gapdevname) + strlen(name) + 1];
36+
sprintf_P(cmd, fmt_gapdevname, name);
37+
assertOK(sendCommandCheckOK(cmd), F("Could not set device name?"));
38+
}
39+
40+
41+
int8_t Adafruit_BluefruitLE_GATT::addGattService(const char *uuid128) {
42+
char cmd[strlen_P(fmt_gattaddservice) + strlen(uuid128) + 1];
43+
sprintf_P(cmd, fmt_gattaddservice, uuid128);
44+
int32_t pos;
45+
assertOK(sendCommandWithIntReply(cmd, &pos), F("Could not add service"));
46+
return (int8_t) pos;
47+
}
48+
49+
50+
int8_t Adafruit_BluefruitLE_GATT::addGattCharacteristic(uint16_t uuid16, CharacteristicProperties props, uint8_t minLen, uint8_t maxLen) {
51+
char zeros[maxLen*3];
52+
for(uint8_t i=0; i<maxLen; i++) {
53+
zeros[i*3] = '0';
54+
zeros[i*3+1] = '0';
55+
zeros[i*3+2] = '-';
56+
}
57+
zeros[maxLen*3-1] = '\0'; // replace the last '-' by '\0'
58+
59+
char cmd[strlen_P(fmt_gattaddchar) + 2 + 1 + 1 + maxLen*3];
60+
sprintf_P(cmd, fmt_gattaddchar, uuid16, props, minLen, maxLen, zeros);
61+
int32_t pos;
62+
assertOK(sendCommandWithIntReply(cmd, &pos), F("Could not add characteristic"));
63+
return (int8_t) pos;
64+
}
65+
66+
67+
void Adafruit_BluefruitLE_GATT::setGattCharacteristicValue(int8_t id, byte *value, uint16_t len) {
68+
if (len == 0) return;
69+
70+
// AT+GATTCHAR takes each byte in hex separated by a dash, e.g. 4 bytes: xx-xx-xx-xx (= 11 characters)
71+
char str[len*3 + 1]; // +1 caters for terminating '\0' after each 'xx-' group
72+
for (uint16_t i=0; i<len; i++) {
73+
sprintf_P(&str[i*3], fmt_bintohex, value[i]);
74+
}
75+
str[len*3 - 1] = '\0'; // replace the last '-' by '\0'
76+
77+
char cmd[strlen_P(fmt_gattsetchar) + len*3];
78+
sprintf_P(cmd, fmt_gattsetchar, id, str);
79+
assertOK(sendCommandCheckOK(cmd), F("Could not set characteristic value"));
80+
}
81+
82+
83+
void Adafruit_BluefruitLE_GATT::setGattCharacteristicValue(int8_t id, int16_t value) {
84+
byte bytes[sizeof(int16_t)];
85+
memcpy(bytes, &value, sizeof(int16_t));
86+
reverseBytes(bytes, sizeof(int16_t));
87+
setGattCharacteristicValue(id, bytes, sizeof(int16_t));
88+
}
89+
void Adafruit_BluefruitLE_GATT::setGattCharacteristicValue(int8_t id, uint16_t value) {
90+
byte bytes[sizeof(uint16_t)];
91+
memcpy(bytes, &value, sizeof(uint16_t));
92+
reverseBytes(bytes, sizeof(uint16_t));
93+
setGattCharacteristicValue(id, bytes, sizeof(uint16_t));
94+
}
95+
96+
97+
void Adafruit_BluefruitLE_GATT::setGattCharacteristicValue(int8_t id, int32_t value) {
98+
byte bytes[sizeof(int32_t)];
99+
memcpy(bytes, &value, sizeof(int32_t));
100+
reverseBytes(bytes, sizeof(int32_t));
101+
setGattCharacteristicValue(id, bytes, sizeof(int32_t));
102+
}
103+
104+
void Adafruit_BluefruitLE_GATT::setGattCharacteristicValue(int8_t id, uint32_t value) {
105+
byte bytes[sizeof(uint32_t)];
106+
memcpy(bytes, &value, sizeof(uint32_t));
107+
reverseBytes(bytes, sizeof(uint32_t));
108+
setGattCharacteristicValue(id, bytes, sizeof(uint32_t));
109+
}
110+
111+
112+
void Adafruit_BluefruitLE_GATT::setGattCharacteristicValue(int8_t id, float value) {
113+
byte bytes[sizeof(float)];
114+
memcpy(bytes, &value, sizeof(float));
115+
setGattCharacteristicValue(id, bytes, sizeof(float));
116+
}
117+
118+
119+
uint16_t Adafruit_BluefruitLE_GATT::getGattCharacteristicValue(int8_t id, byte *reply, uint16_t maxLen) {
120+
char cmd[strlen_P(fmt_gattgetchar) + 2 + 1];
121+
sprintf_P(cmd, fmt_gattgetchar, id);
122+
123+
// AT+GATTCHAR returns each byte in hex separated by a dash, e.g. 4 bytes: xx-xx-xx-xx (= 11 characters)
124+
char replyStr[maxLen*3]; // includes terminating '\0'
125+
uint16_t strLen;
126+
assertOK(sendCommandWithStringReply(cmd, replyStr, &strLen), F("Could not get characteristic value"));
127+
128+
if (strLen == 0 || (strLen + 1) % 3 != 0) {
129+
return 0;
130+
}
131+
132+
// Parse dash-separated hex string into bytes:
133+
uint16_t numBytes = min((strLen + 1) / 3, maxLen);
134+
for(uint16_t i=0; i<numBytes; i++) {
135+
reply[i] = (byte) strtol(&replyStr[i*3], NULL, 16);
136+
}
137+
return numBytes;
138+
}
139+
140+
141+
void Adafruit_BluefruitLE_GATT::getGattCharacteristicValue(int8_t id, int16_t *reply) {
142+
byte bytes[sizeof(int16_t)];
143+
getGattCharacteristicValue(id, bytes, sizeof(int16_t));
144+
reverseBytes(bytes, sizeof(int16_t));
145+
memcpy(reply, bytes, sizeof(int16_t));
146+
}
147+
148+
void Adafruit_BluefruitLE_GATT::getGattCharacteristicValue(int8_t id, uint16_t *reply) {
149+
byte bytes[sizeof(uint16_t)];
150+
getGattCharacteristicValue(id, bytes, sizeof(uint16_t));
151+
reverseBytes(bytes, sizeof(uint16_t));
152+
memcpy(reply, bytes, sizeof(uint16_t));
153+
}
154+
155+
156+
void Adafruit_BluefruitLE_GATT::getGattCharacteristicValue(int8_t id, int32_t *reply) {
157+
byte bytes[sizeof(int32_t)];
158+
getGattCharacteristicValue(id, bytes, sizeof(int32_t));
159+
reverseBytes(bytes, sizeof(int32_t));
160+
memcpy(reply, bytes, sizeof(int32_t));
161+
}
162+
163+
void Adafruit_BluefruitLE_GATT::getGattCharacteristicValue(int8_t id, uint32_t *reply) {
164+
byte bytes[sizeof(uint32_t)];
165+
getGattCharacteristicValue(id, bytes, sizeof(uint32_t));
166+
reverseBytes(bytes, sizeof(uint32_t));
167+
memcpy(reply, bytes, sizeof(uint32_t));
168+
}
169+
170+
171+
void Adafruit_BluefruitLE_GATT::getGattCharacteristicValue(int8_t id, float *reply) {
172+
byte bytes[sizeof(float)];
173+
getGattCharacteristicValue(id, bytes, sizeof(float));
174+
// don't reverse bytes!
175+
memcpy(reply, bytes, sizeof(float));
176+
}
177+
178+
179+
bool Adafruit_BluefruitLE_GATT::sendCommandWithStringReply(const char cmd[], char *reply, uint16_t *len) {
180+
bool success;
181+
uint8_t current_mode = _mode;
182+
183+
// switch mode if necessary to execute command
184+
if (current_mode == BLUEFRUIT_MODE_DATA ) setMode(BLUEFRUIT_MODE_COMMAND);
185+
186+
println(cmd); // send command
187+
if (_verbose) {
188+
SerialDebug.print( F("\n<- ") );
189+
}
190+
(*len) = readline();
191+
memcpy(reply, buffer, *len);
192+
success = waitForOK();
193+
194+
// switch back if necessary
195+
if (current_mode == BLUEFRUIT_MODE_DATA ) setMode(BLUEFRUIT_MODE_DATA);
196+
197+
return success;
198+
}
199+
200+
201+
void reverseBytes(byte *buf, uint16_t len) {
202+
for(uint16_t i=0; i<len/2; i++) {
203+
byte b = buf[i];
204+
buf[i] = buf[len-1-i];
205+
buf[len-1-i] = b;
206+
}
207+
}

ble_gatt/Adafruit_BluefruitLE_GATT.h

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
#ifndef ADAFRUIT_BLUEFRUITLE_GATT_H_INCLUDED
2+
#define ADAFRUIT_BLUEFRUITLE_GATT_H_INCLUDED
3+
4+
#include "Adafruit_BluefruitLE_SPI.h"
5+
6+
#ifndef BLUEFRUIT_SPI_CS
7+
#define BLUEFRUIT_SPI_CS 8
8+
#endif
9+
#ifndef BLUEFRUIT_SPI_IRQ
10+
#define BLUEFRUIT_SPI_IRQ 7
11+
#endif
12+
#ifndef BLUEFRUIT_SPI_RST
13+
#define BLUEFRUIT_SPI_RST 4 // Optional but recommended, set to -1 if unused
14+
#endif
15+
16+
typedef enum {
17+
CHAR_PROP_NONE = 0, // 1
18+
CHAR_PROP_READ = 0x02, // 2
19+
CHAR_PROP_WRITE_NO_RESPONSE = 0x04, // 3
20+
CHAR_PROP_WRITE = 0x08, // 4
21+
CHAR_PROP_NOTIFY = 0x10, // 5
22+
CHAR_PROP_INDICATE = 0x20 // 6
23+
} CharacteristicPropertyEnum;
24+
25+
// bitwise OR combination ("|") of CharacteristicPropertyEnum(s):
26+
typedef unsigned short CharacteristicProperties;
27+
28+
29+
class Adafruit_BluefruitLE_GATT : public Adafruit_BluefruitLE_SPI {
30+
31+
public:
32+
void assertOK(boolean condition, const __FlashStringHelper *err);
33+
34+
Adafruit_BluefruitLE_GATT() : Adafruit_BluefruitLE_SPI(BLUEFRUIT_SPI_CS, BLUEFRUIT_SPI_IRQ, BLUEFRUIT_SPI_RST) { };
35+
Adafruit_BluefruitLE_GATT(int8_t csPin, int8_t irqPin, int8_t rstPin = -1) : Adafruit_BluefruitLE_SPI(csPin, irqPin, rstPin) { };
36+
37+
void setGattDeviceName(const char *name);
38+
39+
int8_t addGattService(const char *uuid128);
40+
41+
int8_t addGattCharacteristic(uint16_t uuid16, CharacteristicProperties props, byte minLen, byte maxLen);
42+
43+
void setGattCharacteristicValue(int8_t id, byte *value, uint16_t len);
44+
void setGattCharacteristicValue(int8_t id, int16_t value);
45+
void setGattCharacteristicValue(int8_t id, uint16_t value);
46+
void setGattCharacteristicValue(int8_t id, int32_t value);
47+
void setGattCharacteristicValue(int8_t id, uint32_t value);
48+
void setGattCharacteristicValue(int8_t id, float value);
49+
50+
/*
51+
* Returns the number of bytes in reply; bytes exceeding maxLen will be read but will not be returned.as reply.
52+
*/
53+
uint16_t getGattCharacteristicValue(int8_t id, byte *reply, uint16_t maxLen);
54+
void getGattCharacteristicValue(int8_t id, int16_t *reply);
55+
void getGattCharacteristicValue(int8_t id, uint16_t *reply);
56+
void getGattCharacteristicValue(int8_t id, int32_t *reply);
57+
void getGattCharacteristicValue(int8_t id, uint32_t *reply);
58+
void getGattCharacteristicValue(int8_t id, float *reply);
59+
60+
protected:
61+
62+
/*
63+
* Reply will always be '\0'-terminated; result characters exceeding maxLen will be read but not returned.
64+
*/
65+
bool sendCommandWithStringReply(const char cmd[], char *reply, uint16_t *len);
66+
};
67+
68+
69+
void reverseBytes(byte *buf, uint16_t len);
70+
71+
#endif

ble_gatt/ble_gatt.ino

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#include <Arduino.h>
2+
#include "Adafruit_BluefruitLE_GATT.h"
3+
4+
#define CONTROL_CYCLE_DURATION 6000L // [ms]
5+
6+
#define BLE_VERBOSE_MODE true // If set to 'true' enables debug output
7+
8+
/*
9+
* Create the bluefruit object using hardware SPI (SCK/MOSI/MISO hardware SPI pins and then user selected CS/IRQ/RST)
10+
*/
11+
Adafruit_BluefruitLE_GATT ble = Adafruit_BluefruitLE_GATT();
12+
13+
/* The service information */
14+
int8_t controllerServiceId;
15+
int8_t waterTempMeasureCharId;
16+
int8_t ambientTempMeasureCharId;
17+
int8_t targetTempCharId;
18+
int8_t tankCapacityCharId;
19+
20+
21+
void setup(void) {
22+
Serial.begin(115200);
23+
while (!Serial) {
24+
; // wait for serial port to connect.
25+
}
26+
//Test::min_verbosity = TEST_VERBOSITY_ALL;
27+
delay(500);
28+
29+
randomSeed(micros());
30+
31+
ble.assertOK(ble.begin(BLE_VERBOSE_MODE), F("Couldn't find Bluefruit, make sure it's in CoMmanD mode & check wiring?"));
32+
33+
/* Perform a factory reset to make sure everything is in a known state */
34+
ble.assertOK(ble.factoryReset(), F("Could not factory reset"));
35+
36+
/* Disable command echo from Bluefruit */
37+
ble.echo(false);
38+
39+
/* Print Bluefruit information */
40+
ble.info();
41+
42+
// this line is particularly required for Flora, but is a good idea
43+
// anyways for the super long lines ahead!
44+
// ble.setInterCharWriteDelay(5); // 5 ms
45+
46+
ble.setGattDeviceName("Boiler Controller");
47+
controllerServiceId = ble.addGattService("4C-EF-DD-58-CB-95-44-50-90-FB-F4-04-DC-20-2F-7C");
48+
waterTempMeasureCharId = ble.addGattCharacteristic(0x0001, CHAR_PROP_NOTIFY, 2, 2);
49+
ambientTempMeasureCharId = ble.addGattCharacteristic(0x0002, CHAR_PROP_READ, 4, 4);
50+
targetTempCharId = ble.addGattCharacteristic(0x0003, CHAR_PROP_READ | CHAR_PROP_WRITE, 2, 4);
51+
tankCapacityCharId = ble.addGattCharacteristic(0x0004, CHAR_PROP_READ | CHAR_PROP_WRITE, 4, 4);
52+
53+
/* Add the Heart Rate Service to the advertising data (needed for Nordic apps to detect the service) */
54+
ble.assertOK(ble.sendCommandCheckOK( F("AT+GAPSETADVDATA=02-01-06-05-02-0d-18-0a-18")), F("Could not set advertising data"));
55+
56+
/* Reset the device for the new service setting changes to take effect */
57+
ble.reset();
58+
}
59+
60+
void loop(void) {
61+
62+
static unsigned long controlCycleStart = 0L;
63+
64+
unsigned long now = millis();
65+
unsigned long elapsed = now - controlCycleStart;
66+
67+
if (controlCycleStart == 0L || elapsed >= CONTROL_CYCLE_DURATION) {
68+
controlCycleStart = now;
69+
elapsed = 0L;
70+
}
71+
72+
if (elapsed == 0L) {
73+
74+
int waterTemp = random(20, 42);
75+
ble.setGattCharacteristicValue(waterTempMeasureCharId, waterTemp);
76+
77+
long ambientTemp = random(13, 28);
78+
ble.setGattCharacteristicValue(ambientTempMeasureCharId, ambientTemp);
79+
80+
Serial.print(F("New water temp = 0x"));
81+
Serial.print(waterTemp, HEX);
82+
Serial.print(F(", ambient temp = 0x"));
83+
Serial.println(ambientTemp, HEX);
84+
}
85+
86+
static long previousTargetTemp = 0;
87+
long targetTemp;
88+
ble.getGattCharacteristicValue(targetTempCharId, &targetTemp);
89+
if(targetTemp != previousTargetTemp) {
90+
previousTargetTemp = targetTemp;
91+
Serial.print(F("** New target temp = 0x"));
92+
Serial.println(targetTemp, HEX);
93+
}
94+
95+
static float previousTankCapacity = 0.0;
96+
float tankCapacity;
97+
ble.getGattCharacteristicValue(tankCapacityCharId, &tankCapacity);
98+
if(tankCapacity != previousTankCapacity) {
99+
previousTankCapacity = tankCapacity;
100+
Serial.print(F("** New tank capacity = "));
101+
Serial.println(tankCapacity);
102+
}
103+
104+
/* Delay before next measurement update */
105+
delay(2000);
106+
}
107+

0 commit comments

Comments
 (0)