Code
#include <SoftwareSerial.h>
#include <LowPower.h>
#include <EEPROM.h>
#define BLE_TX 3 // HM-10 TX pin connected to Arduino pin 3
#define BLE_RX 9 // HM-10 RX pin connected to Arduino interrupt pin 9
#define BUZZER_PIN 6
#define SENSOR_PIN 2 // ball sensor on interrupt pin
#define BATTERY_PIN A0 // measure battery level
// customize low, med and high sensibility levels
#define LOW_SENS_FACTOR 5
#define MED_SENS_FACTOR 3
#define HIGH_SENS_FACTOR 1
// For a 3.7V battery, 3.2V would be low voltage. Since the voltage divider
// has equal resistors, the voltage read is half, so 1.6V
#define LOW_BATTERY_THRESHOLD 1.76 // for the 3.3V model
// ingress commands
#define CMD_START 0xFF // start movement detection
#define CMD_STOP 0xFE // stop movement detection
#define CMD_PING 0xFC // test connection
#define CMD_BEEP 0xFB // ring alarm
#define CMD_LOW 0xED // sensitivity
#define CMD_MED 0xEE // sensitivity
#define CMD_HIGH 0xEF // sensitivity
#define CMD_SAVE_CONFIG 0xAA // saves config variables (sensibility, duration, etc)
#define CMD_MUTE_ON 0xAB // mute the alarm
#define CMD_MUTE_OFF 0xAC
#define CMD_READ_CONFIG 0xAD // send to android app the current configuration (sensibility, duration, etc)
#define CMD_BEACON_ON 0xEB // send beacon at regular intervals
#define CMD_BEACON_OFF 0xEC
#define CMD_DURATION 0xB8 //0xB8 - 0xBF alarm duration
// egress commands
#define CMD_PONG 0xFD // response to CMD_PING
#define CMD_BEACON 0xEA // send beacon signal to let the android app know the system is functional
#define CMD_ALARM 0xFA
#define CMD_LOW_BATTERY 0xDC
// Also, ACK signal for ingress commands, as CMD - 8
// Addresses in EEPROM to store variables
#define ADDR_BEACON 0
#define ADDR_MUTED 1
#define ADDR_SENSITIVITY 2
#define ADDR_DURATION 3
// write buffer
String buffer;
// config vars
boolean sendBeacon = false;
boolean isDetectionActive = true;
boolean isMuted = false;
unsigned short int alarmDuration = 1500;
unsigned char sensitivity = 1;
// interrupt flags
volatile bool alarmInterrupt = false;
volatile bool rxInterrupt = false;
// ball sensor is very sensitive, on a slight move it could trigger tens of times
// The number of interrupts is directly linked to the alarm sensibility level
volatile unsigned long ballMovement = 0;
SoftwareSerial BLE(BLE_TX, BLE_RX);
// interrupt ISR
void wakeUpOnRX() {
rxInterrupt = true;
}
// interrupt ISR
void detectAlarm() {
ballMovement += 1;
alarmInterrupt = true;
}
void setup() {
pinMode(LED_BUILTIN, OUTPUT);
pinMode(SENSOR_PIN, INPUT_PULLUP); // Watchdog pin to wake up on movement
pinMode(BLE_TX, INPUT_PULLUP); // Watchdog pin to wake up on BLE comms
pinMode(BATTERY_PIN, INPUT); // battery level
pinMode(BLE_RX, OUTPUT); // BLE comms
pinMode(BUZZER_PIN, OUTPUT); // piezzo buzzer
digitalWrite(BUZZER_PIN, HIGH);
attachInterrupt(digitalPinToInterrupt(SENSOR_PIN), detectAlarm, FALLING);
attachInterrupt(digitalPinToInterrupt(BLE_TX), wakeUpOnRX, FALLING);
buffer = "";
Serial.begin(9600); // Initialize Serial Monitor
BLE.begin(9600); // Initialize BLE communication
// load config vars
EEPROM.get(ADDR_BEACON, sendBeacon);
EEPROM.get(ADDR_MUTED, isMuted);
EEPROM.get(ADDR_SENSITIVITY, sensitivity);
EEPROM.get(ADDR_DURATION, alarmDuration);
validateEEPROMvars(); // if values from EEPROM are invalid, init with default
Serial.println("Starting..");
delay(200);
}
void loop() {
// read battery level
int adcValue = analogRead(BATTERY_PIN);
float voltage = (adcValue / 1023.0) * 3.3; // for 3.3V arduino
Serial.print(voltage);
Serial.println(" V");
delay(100);
// read for incoming commands before sending signals back (low battery, beacon, alarm)
if (BLE.available() || rxInterrupt) {
readIncoming();
delay(100);
rxInterrupt = false;
}
// send low battery signal
if (voltage < LOW_BATTERY_THRESHOLD){
Serial.println("Low battery");
buffer += (char)CMD_LOW_BATTERY;
}
// detect alarms
if (alarmInterrupt && isDetectionActive) {
// Serial.println(ballMovement);
// sensitivity * 5 determines how sensitive the movement detection is
if (sensitivity * 5 < ballMovement) {
Serial.println("ALARM!");
buffer += (char)CMD_ALARM;
beep();
}
ballMovement = 0;
alarmInterrupt = false;
}
if (!isDetectionActive)
ballMovement = 0;
if (sendBeacon){
buffer += (char)CMD_BEACON;
}
sendBuffer();
// low power sleep saves battery
LowPower.powerDown(SLEEP_8S, ADC_OFF, BOD_OFF);
}
void readIncoming() {
delay(500); // Allow buffer to fill, if BLE data is incoming
while (BLE.available()) {
byte command = BLE.read();
byte response = runCommand(command);
// send ACK signal back to the android app
if (response != 0x00) {
// confirm on Arduino side (blink builtin led)
digitalWrite(LED_BUILTIN, HIGH);
delay(400);
digitalWrite(LED_BUILTIN, LOW);
buffer += (char)response;
Serial.print("CMD: "); Serial.println(command, HEX);
}
}
}
// ring alarm
void beep() {
if (isMuted) {
delay(alarmDuration);
return;
}
digitalWrite(BUZZER_PIN, LOW);
delay(alarmDuration);
digitalWrite(BUZZER_PIN, HIGH);
}
// if values read from EEPROM are not valid, initialize with default
void validateEEPROMvars() {
bool initVars = false;
if (sendBeacon != 0 && sendBeacon != 1) initVars = true;
if (isMuted != 0 && isMuted != 1) initVars = true;
if (sensitivity > 255) initVars = true;
if (alarmDuration % 250 != 0) initVars = true;
if (initVars) {
sendBeacon = false;
isMuted = false;
sensitivity = 1;
alarmDuration = 1500;
runSaveConfig();
}
}
void runSaveConfig() {
EEPROM.put(ADDR_BEACON, sendBeacon);
EEPROM.put(ADDR_MUTED, isMuted);
EEPROM.put(ADDR_SENSITIVITY, sensitivity);
EEPROM.put(ADDR_DURATION, alarmDuration);
Serial.println("Saved Config");
}
byte getCurrentConfig() {
unsigned short int sensitivityEncoded;
if (sensitivity == LOW_SENS_FACTOR) sensitivityEncoded = 0;
if (sensitivity == MED_SENS_FACTOR) sensitivityEncoded = 1;
if (sensitivity == HIGH_SENS_FACTOR) sensitivityEncoded = 2;
unsigned short int durationEncoded = (alarmDuration / 250 - 1) % 8; // ensure durationEncoded takes only 3 bits
byte encodedByte1 = (5 << 4) | (isMuted << 3) | durationEncoded;
byte encodedByte2 = (6 << 4) | (isDetectionActive << 3) | (sendBeacon << 2) | sensitivityEncoded;
buffer += (char)encodedByte1;
buffer += (char)encodedByte2;
}
void sendBuffer() {
if (buffer.length() > 0) {
Serial.println(buffer.length());
BLE.write(buffer.c_str(), buffer.length());
buffer = ""; // Clear buffer after sending
BLE.flush();
delay(300);
}
}
byte runCommand(byte cmd) {
// Serial.print(" "); Serial.println(cmd, HEX);
byte reponse = cmd - 8;
if (cmd >= CMD_DURATION && cmd <= CMD_DURATION + 7) {
alarmDuration = (cmd - CMD_DURATION + 1) * 250;
return reponse;
}
switch (cmd) {
case CMD_START: isDetectionActive = true; break;
case CMD_STOP: isDetectionActive = false; break;
case CMD_LOW: sensitivity = LOW_SENS_FACTOR; break;
case CMD_MED: sensitivity = MED_SENS_FACTOR; break;
case CMD_HIGH: sensitivity = HIGH_SENS_FACTOR; break;
case CMD_PING: buffer += (char)CMD_PONG; break;
case CMD_SAVE_CONFIG: runSaveConfig(); break;
case CMD_READ_CONFIG: getCurrentConfig(); break;
case CMD_BEEP: beep(); break;
case CMD_MUTE_ON: isMuted = true; break;
case CMD_MUTE_OFF: isMuted = false; break;
case CMD_BEACON_ON: sendBeacon = true; break;
case CMD_BEACON_OFF: sendBeacon = false; break;
default: reponse = 0x00; // No response for unknown commands
}
return reponse;
}