Bike Angel BLE
DIY Arduino

Follow the instructions to build your own Arduino bike alarm

Instructables
Bike Angel BLE
Bike Angel BLE

Get your companion Android app

getitongoogleplay

Components

Wiring

wiring

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;
        }