🌴 A BLE-connected outdoor UV sensor keychain 🗝
Display the UV Index color code with the on-board RGB LED
#include <bluefruit.h>
#include "Adafruit_VEML6075.h"
int redPin = PIN_A1;
int greenPin = PIN_A2;
int bluePin = PIN_A3;
const int ENSensorPin = 15;
Adafruit_VEML6075 uv = Adafruit_VEML6075();
uint8_t uvindexvalue = 0x42;
float readUVIndexValue = 0.0;
const int ENBLEPin = 16;
bool isBLEON = false;
/* GATT Services https://www.bluetooth.com/specifications/gatt/services/
Name: Environmental Sensing
Uniform Type Identifier: org.bluetooth.service.environmental_sensing
Assigned Number: 0x181A
Specification: GSS
*/
#define UUID16_SVC_ENVIRONMENTAL_SENSING 0x181A
/* GATT Characteristics https://www.bluetooth.com/specifications/gatt/characteristics/
Name: UV Index
Uniform Type Identifier: org.bluetooth.characteristic.uv_index
Assigned Number: 0x2A76
Specification: GSS
*/
#define UUID16_CHR_UV_INDEX 0x2A76
BLEService environmental_sensing_service = BLEService(UUID16_SVC_ENVIRONMENTAL_SENSING);
BLECharacteristic uv_index_characteristic = BLECharacteristic(UUID16_CHR_UV_INDEX);
BLEDis bledis; // DIS (Device Information Service) helper class instance
// Advanced function prototypes
void startAdv(void);
void setupESService(void);
void setupBattService(void);
void connect_callback(uint16_t conn_handle);
void disconnect_callback(uint16_t conn_handle, uint8_t reason);
// Battery
#define VBAT_PIN (A7)
#define VBAT_MV_PER_LSB (0.73242188F)
#define VBAT_DIVIDER (0.71275837F)
#define VBAT_DIVIDER_COMP (1.403F)
int vbat_raw;
uint8_t vbat_per;
float vbat_mv;
/* GATT Services https://www.bluetooth.com/specifications/gatt/services/
Name: Battery Service
Uniform Type Identifier: org.bluetooth.service.battery_service
Assigned Number: 0x180F
Specification: GSS
*/
#define UUID16_SVC_BATTERY 0x180F
/* GATT Characteristics https://www.bluetooth.com/specifications/gatt/characteristics/
Name: Battery Level
Abstract: The current charge level of a battery.
100% represents fully charged while 0% represents fully discharged.
Uniform Type Identifier: org.bluetooth.characteristic.battery_level
Assigned Number: 0x2A19
Format: uint8
Specification: GSS
*/
#define UUID16_CHR_BATTERY_LEVEL 0x2A19
BLEService battery_service = BLEService(UUID16_SVC_BATTERY);
BLECharacteristic battery_level_characteristic = BLECharacteristic(UUID16_CHR_BATTERY_LEVEL);
void setup() {
Serial.begin(115200);
delay(500);
pinMode(ENSensorPin, OUTPUT);
digitalWrite(ENSensorPin, LOW);
pinMode(ENBLEPin, INPUT);
pinMode(LED_RED, OUTPUT);
Bluefruit.autoConnLed(false); // Turn off BLUE LED
// Battery
analogReference(AR_INTERNAL_3_0);
analogReadResolution(12); // Can be 8, 10, 12 or 14
Serial.println("Palm@Hutscape - UV Index sensor");
Serial.println("-------------------------------------\n");
// Enable UV sensor
int returnValue = uv.begin();
if (!returnValue) {
Serial.println("[ERROR] Sensor VEML6075 cannot be found");
}
Serial.println("[INFO] Sensor VEML6075 enabled");
// TODO: Turn on/off BLE based on the ENBLEPin
Bluefruit.begin();
Bluefruit.setName("Palm@Hutscape");
Bluefruit.Periph.setConnectCallback(connect_callback);
Bluefruit.Periph.setDisconnectCallback(disconnect_callback);
bledis.setManufacturer("Adafruit Industries");
bledis.setModel("Bluefruit Feather52");
bledis.begin();;
setupESService();
setupBattService();
startAdv();
Serial.println("[INFO] Start advertising...");
}
void loop() {
// Battery
vbat_raw = analogRead(VBAT_PIN);
vbat_per = mvToPercent(vbat_raw * VBAT_MV_PER_LSB);
vbat_mv = (float)vbat_raw * VBAT_MV_PER_LSB * VBAT_DIVIDER_COMP;
// UV Index
readUVIndexValue = uv.readUVI();
uvindexvalue = round(abs(readUVIndexValue));
displayLED(uvindexvalue);
// Turn on BLE
if (digitalRead(ENBLEPin) == 1) {
isBLEON = true;
digitalToggle(LED_RED); // Blinking RED LED indicated BLE is ON
} else {
isBLEON = false;
digitalWrite(LED_RED, LOW);
}
if (isBLEON && Bluefruit.connected()) {
// Note: We use .indicate instead of .write!
// If it is connected but CCCD is not enabled
// The characteristic's value is still updated although indicate is not sent
if (uv_index_characteristic.indicate(&uvindexvalue, sizeof(uvindexvalue))) {
Serial.print("[INFO] Updated UV Index: ");
Serial.println(uvindexvalue);
} else {
Serial.println("[ERROR] UV Index Indicate not set in the CCCD or not connected");
}
if (battery_level_characteristic.indicate(&vbat_per, sizeof(vbat_per))) {
Serial.print("[INFO] Updated Battery level: ");
Serial.print(vbat_per);
Serial.println("%");
} else {
Serial.println("[ERROR] Battery level Indicate not set in the CCCD or not connected");
}
}
Serial.print("[INFO] UV Index: ");
Serial.println(uvindexvalue);
Serial.print("[INFO] Battery level: ");
Serial.print(vbat_per);
Serial.println("%");
Serial.println("");
delay(4000);
}
void displayLEDColor(int red, int green, int blue) {
analogWrite(redPin, red);
analogWrite(greenPin, green);
analogWrite(bluePin, blue);
}
void displayLED(uint8_t uvindexvalue) {
if (uvindexvalue <= 2) {
// UV Index <= 2, display GREEN or no color alerts
displayLEDColor(0, 255, 0);
} else if (uvindexvalue >= 3 && uvindexvalue <= 5) {
// UV Index is from 3 to 5, display YELLOW
displayLEDColor(255, 255, 0);
} else if (uvindexvalue >= 6 && uvindexvalue <= 7) {
// UV Index is from 6 to 7, display ORANGE
displayLEDColor(255, 50, 0);
} else if (uvindexvalue >= 8 && uvindexvalue <= 10) {
// UV Index is from 8 to 10, display RED
displayLEDColor(255, 0, 0);
} else {
// UV Index is above 11, display PURPLE
displayLEDColor(255, 0, 255);
}
}
void startAdv(void) {
// Advertising packet
Bluefruit.Advertising.addFlags(BLE_GAP_ADV_FLAGS_LE_ONLY_GENERAL_DISC_MODE);
Bluefruit.Advertising.addTxPower();
// Include HTM Service UUID
Bluefruit.Advertising.addService(environmental_sensing_service);
Bluefruit.Advertising.addService(battery_service);
// Include Name
Bluefruit.Advertising.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);
// in unit of 0.625 ms
Bluefruit.Advertising.setInterval(32, 244);
// number of seconds in fast mode
Bluefruit.Advertising.setFastTimeout(30);
// 0 = Don't stop advertising after n seconds
Bluefruit.Advertising.start(0);
}
void setupESService(void) {
// Configure the Environmental Sensing service
// See: https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.uv_index.xml
// Supported Characteristics:
// Name UUID Requirement Properties
// ---------------------------- ------ ----------- ----------
// UV Index 0x2A76 Mandatory Read
environmental_sensing_service.begin();
// Note: You must call .begin() on the BLEService before calling .begin() on
// any characteristic(s) within that service definition.. Calling .begin() on
// a BLECharacteristic will cause it to be added to the last BLEService that
// was 'begin()'ed!
// Configure the UV Index characteristic
// See:https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.uv_index.xml
// Properties = Indicate
// Min Len = 1
// Max Len = 1
// B0 = UINT8 - UV Index measurement unitless
uv_index_characteristic.setProperties(CHR_PROPS_INDICATE);
uv_index_characteristic.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS);
uv_index_characteristic.setFixedLen(1);
uv_index_characteristic.setCccdWriteCallback(cccd_callback); // Optionally capture CCCD updates
uv_index_characteristic.begin();
uv_index_characteristic.write(&uvindexvalue, sizeof(uvindexvalue));
}
void setupBattService(void) {
battery_service.begin();
battery_level_characteristic.setProperties(CHR_PROPS_INDICATE);
battery_level_characteristic.setPermission(SECMODE_OPEN, SECMODE_NO_ACCESS);
battery_level_characteristic.setFixedLen(1);
battery_level_characteristic.setCccdWriteCallback(cccd_callback); // Optionally capture CCCD updates
battery_level_characteristic.begin();
battery_level_characteristic.write(&vbat_per, sizeof(vbat_per));
}
void connect_callback(uint16_t conn_handle) {
// Get the reference to current connection
BLEConnection* connection = Bluefruit.Connection(conn_handle);
char central_name[32] = { 0 };
connection->getPeerName(central_name, sizeof(central_name));
Serial.print("[INFO] Connected to ");
Serial.println(central_name);
}
/**
* 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
*/
void disconnect_callback(uint16_t conn_handle, uint8_t reason) {
(void) conn_handle;
(void) reason;
Serial.println("[INFO] Disconnected");
Serial.println("[INFO] Advertising!");
}
void cccd_callback(uint16_t conn_hdl,
BLECharacteristic* chr, uint16_t cccd_value) {
// Display the raw request packet
Serial.print("[INFO] CCCD Updated: ");
// Serial.printBuffer(request->data, request->len);
Serial.print(cccd_value);
Serial.println("");
// Check the characteristic this CCCD update is associated with in case
// this handler is used for multiple CCCD records.
if (chr->uuid == uv_index_characteristic.uuid) {
if (chr->indicateEnabled(conn_hdl)) {
Serial.println("[INFO] UV Index Measurement 'Indicate' enabled");
} else {
Serial.println("[INFO] UV Index Measurement 'Indicate' disabled");
}
} else if (chr->uuid == battery_level_characteristic.uuid) {
if (chr->indicateEnabled(conn_hdl)) {
Serial.println("[INFO] Battery level 'Indicate' enabled");
} else {
Serial.println("[INFO] Battery level 'Indicate' disabled");
}
}
}
uint8_t mvToPercent(float mvolts) {
uint8_t battery_level;
if (mvolts >= 3000) {
battery_level = 100;
} else if (mvolts > 2900) {
battery_level = 100 - ((3000 - mvolts) * 58) / 100;
} else if (mvolts > 2740) {
battery_level = 42 - ((2900 - mvolts) * 24) / 160;
} else if (mvolts > 2440) {
battery_level = 18 - ((2740 - mvolts) * 12) / 300;
} else if (mvolts > 2100) {
battery_level = 6 - ((2440 - mvolts) * 6) / 340;
} else {
battery_level = 0;
}
return battery_level;
}
BOARD?=adafruit:nrf52:feather52832
PORT := $(shell ls /dev/tty.SLAB_USBtoUART)
BAUD_RATE = 115200
BUILD=build
.PHONY: default lint all flash clean
default: lint all flash clean
lint:
cpplint --extensions=ino --filter=-legal/copyright *.ino
all:
arduino-cli compile --fqbn $(BOARD) --output-dir $(BUILD) ./
flash:
adafruit-nrfutil dfu genpkg --dev-type 0x0052 --application $(BUILD)/*.hex dfu-package.zip
adafruit-nrfutil dfu serial --package dfu-package.zip -p $(PORT) -b $(BAUD_RATE)
clean:
rm -rf build
rm dfu-package.zip