Hackster is hosting Hackster Holidays, Ep. 7: Livestream & Giveaway Drawing. Watch previous episodes or stream live on Friday!Stream Hackster Holidays, Ep. 7 on Friday!
Lingnan Entrepreneurship Initiative
Published © CC BY

Bluetooth Control for UV-C Disinfection Lights

Safely control a UV-C disinfection light over Bluetooth.

AdvancedWork in progress10 hours5,819
Bluetooth Control for UV-C Disinfection Lights

Things used in this project

Hardware components

Nano 33 BLE Sense
Arduino Nano 33 BLE Sense
×1
10A 250VAC Relay Module
×1
Adafruit 8-channel Bi-directional Logic Level Converter - TXB0108
×1
Adafruit PIR Motion Sensor
×3
Adafruit GUVA-S12SD UV Sensor Breakout
×1
LED (generic)
LED (generic)
×1
Android device
Android device
×1
Resistor 1k ohm
Resistor 1k ohm
×2
Protoboard (6cm x 8cm)
×1
HLK-PM01 220V to 5V Step Down
×1
13 Amp Fuse
×1

Software apps and online services

MIT App Inventor 2
MIT App Inventor 2
Arduino IDE
Arduino IDE

Hand tools and fabrication machines

Soldering iron (generic)
Soldering iron (generic)
Solder Wire, Lead Free
Solder Wire, Lead Free
PCB Holder, Soldering Iron
PCB Holder, Soldering Iron
Multitool, Screwdriver
Multitool, Screwdriver

Story

Read more

Custom parts and enclosures

Enclosure and Sensor Mount Fusion360 file

This is a mount that fits on a Phottix tripod and used to mount an enclosure for the circuit. This is the Fusion360 file which can be used to modify the part.

Enclosure and Sensor Mount STL file

This is a mount that fits on a Phottix tripod to hold an enclosure for the circuit and sensors. This is the STL file for 3D printing.

Schematics

Circuit Schematic

This schematic illustrates how to connect the Arduino Nano BLE Sense with the necessary sensors, relay and power source.

Code

LU_Light_Control Arduino Code

Arduino
This is code for the Arduino Nano BLE Sense used to control the UV germicidal lights.
/*
  Light Control system for Lingnan University UV Disinfection lights
  For Arduino Nano 33 BLE, connected to 3 motion sensors, UV light detection sensor, 5v <-> 3.3v level shifter, and relay
  Interfaces with UVConnect App
  Version 1.0: Initial release for field testing
*/

#include <ArduinoBLE.h> //Include Arduino BLE library. Install using Arduino IDE Library Manager

// Here you can set the local name of the light. If you change this, ensure that you also change the searchTerm variable in the 
//MIT App Inventor project using the .aia file. Each light can have a unique name, but all lights' name should include the search term. For example, we set the searchTerm to
//"LU_Light" but create unique names by including a different number suffix for each light. 
char bleLocalName[] = "LU_Light 9"; 

/* Bluetooth Low Energy centers around 'Services' and 'Characteristics'. Services hold data organized in Characteristics. This sketch uses one service "lightServices", 
 *  organize in four characteristics(switch, motion, status, connection). Services and characteristics are uniquely defined by UUIDs (the long code of digits).
 */
BLEService lightService("722bd000-ca2d-4512-a50f-d706a6f3cdfa"); //create lightService

/* The switch characteristic holds the data byte that determines if the relay should be triggered on or off. 
This can be written to (BLEWrite) and a notification is sent to connected devices when it changes value
*/
BLEByteCharacteristic switchCharacteristic("722bd000-ca2d-4512-a50f-d706a6f3cdfb", BLEWrite | BLENotify); 

/* The switch characteristic holds the data byte that determines if motion has been detected. 
This can be written to (BLEWrite) and a notification is sent to connected devices when it changes value
*/
BLEByteCharacteristic motionCharacteristic("722bd000-ca2d-4512-a50f-d706a6f3cdfc", BLEWrite | BLENotify);

/* The switch characteristic holds the data byte that determines if the UV sensor determines the light to be on or off. 
This can be written to (BLEWrite) and a notification is sent to connected devices when it changes value
*/
BLEByteCharacteristic statusCharacteristic("722bd000-ca2d-4512-a50f-d706a6f3cdfd", BLEWrite | BLENotify);

/* The connection characteristic holds the data byte that determines if the light is connected to a Bluetooth device or not. 
This can be written to (BLEWrite) and a notification is sent to connected devices when it changes value
*/
BLEByteCharacteristic connectionCharacteristic("722bd000-ca2d-4512-a50f-d706a6f3cdfe", BLEWrite | BLENotify);



//Define pin numbers
const int ledPin = 3; // status indication LED pin
const int motionPin1 = 10; //motion sensor pin
const int motionPin2 = 9; //motion sensor pin
const int motionPin3 = 8; //motion sensor pin
const int uvPin = A0; //UV detector pin
const int relayPin = 7; //Light activitaion relay pin
const int oePin = 2; // Level shifter enable pin

//Define somme constants
const int uvThreshold = 30; //Threshold for UV sensor to determine if light is on or off
const int motionInterval = 3000; //(ms) Length of time to leave motion sensors disabled
const int uvInterval = 1000; //(ms) Length of time bewteen UV light samples for determining if light is on or off
const int ledDelay = 1000; //(ms) delay time for led blinking
const int numUvReadings = 10; //number of UV readings to average

//Initialize variables
long uvTime = 0; //timer variable for UV detector
long motionTime = 0; //timer variable for motion sensor
int ledTimer = millis(); //timer variable for status indication led

int uvReadings[numUvReadings]; //array to store UV radings from UV light sensor
int uvIndex = 0; //index of current UV reading
int uvTotal = 0; //running total of UV readings
int uvAverage = 0; //average of UV readings

//int a = 0; For light sensor debugging
boolean motionFlag = false; //Inidcate if any motion sensor has been triggered
boolean motionDisable = false; //Flag for enabling or disabling motion sensors
boolean ledState = false; //state of status indication led


void setup() {
  Serial.begin(9600);
  enableWDT(); //Enable Watchdog timer for detecting microcontroller freezing and resetting
  resetWDT(); // Reset watchdog timer - indicate that the microcontroller is still running
  
  // Set modes for pins
  pinMode(ledPin, OUTPUT);
  pinMode(relayPin, OUTPUT);
  digitalWrite(relayPin, HIGH); //initialize relay as off (HIGH = OFF, LOW = ON);
  pinMode(oePin, OUTPUT);
  digitalWrite(oePin, HIGH); //initialize level shifter as ON
  digitalWrite(ledPin, LOW); //initialize status indication LED as OFF
  pinMode(motionPin1, INPUT);
  pinMode(motionPin2, INPUT);
  pinMode(motionPin3, INPUT);

  // begin initialization
  //Start bluetooth, if bluetooth does not initialize print error message, blink LED rapidly, reset Watchdog timer to keep Arduino set
  if (!BLE.begin()) {
    Serial.println("starting BLE failed!");
    while (1) {
      resetWDT();
      digitalWrite(ledPin, HIGH);
      delay(250);
      digitalWrite(ledPin, LOW);
      delay(250);
    }

  }

  // set advertised local name and service UUID:
  BLE.setLocalName(bleLocalName);
  BLE.setAdvertisedService(lightService);

  // add the characteristic to the service
  lightService.addCharacteristic(switchCharacteristic);
  lightService.addCharacteristic(motionCharacteristic);
  lightService.addCharacteristic(statusCharacteristic);
  lightService.addCharacteristic(connectionCharacteristic);

  // add service
  BLE.addService(lightService);

  // set the initial value for the characeristics:
  switchCharacteristic.writeValue((byte)0x00);
  statusCharacteristic.writeValue((byte)0x00);
  connectionCharacteristic.writeValue((byte) 0x01);

  // start advertising
  BLE.advertise();

  //Attach hardware interrupts for motion sensors and connect to motionDetected interrupt service routine
  attachInterrupt(digitalPinToInterrupt(motionPin1), motionDetected, RISING);
  attachInterrupt(digitalPinToInterrupt(motionPin2), motionDetected, RISING);
  attachInterrupt(digitalPinToInterrupt(motionPin3), motionDetected, RISING);

  //initialize UV readings to 0:
  for (int i = 0; i < numUvReadings; i++) {
    uvReadings[i] = 0;
  }
  
  resetWDT(); //Reset Watchdog timer to prevent Arduino from resetting
  
}

void loop() {

  // listen for BLE centrals to connect:
  BLEDevice central = BLE.central();

  // If no BLE central is connected, blink LED
  if (!central) {
    if (millis() - ledTimer > ledDelay) {
      ledTimer = millis();
      if (ledState) {
        digitalWrite(ledPin, LOW);
      }
      else {
        digitalWrite(ledPin, HIGH);
      }
      ledState = !ledState;
    }
    resetWDT();
    
  }

  // if a central connects, print its address, and set the status indication LED to solid, and reset the motion sensor timer
  if (central) {
    Serial.print("Connected to central: ");
    // print the central's MAC address:
    Serial.println(central.address());
    digitalWrite(ledPin, HIGH);
    motionTime = millis();
    resetWDT();
    
    // while the central is still connected, reset the watchdog timer to ensure microcontroller is running, check if motion
    // detector has been tripped, check for 'On' or "Off' command, and report status of UV light ('On or Off')

    // On Arduino Nano 33 BLE, "central.connected()" has a bug such that in the case that the central BLE device goes outside
    // the range of the Arduino and disconnects, central.disconnect() will not be changed to false and the Arduino will not
    // adverstise that it is available and it will not be possible to reconnect. This is why the Watchdog timer has been enabled to reset
    // the Arduino in case of a poor disconnection

    while (central.connected()) {
      
      // Re-enable the motion sensor if it has been more than desired motionInterval since the UV lights were turned "On"
      if (motionDisable == true && millis() - motionTime > motionInterval) {
        motionDisable = false;
        Serial.println("Motion Re-enabled");
      }

      // If motion has been detected, send notification to central device, turn relay off and reset motion flag
      if (motionFlag == true) {
        motionCharacteristic.writeValue((byte) 0x00);
        Serial.println(F("Motion Detected"));
        digitalWrite(relayPin, HIGH);
        motionFlag = false;
      }

      if (connectionCharacteristic.written()) {
         if (connectionCharacteristic.value() == (byte) 0x00) {
         central.disconnect();
        }
       else if (connectionCharacteristic.value() == (byte) 0x01) {
          resetWDT(); //reset the Watchdog timer. If the central has disconnected by going out of range, the Arduino will hang and never reach this step
      // After 2 seconds it will reset the Arduino
        }
      }

      // If Central has sent an "On" or "Off" message, determine which one and turn UV light on or off accordingly
      if (switchCharacteristic.written()) {

        // If message is an "On" message, set motionDisable flag to disable motion sensors while the lights turn on so the sensors
        // are not accidently triggered. Start the motion sensor timer so they are re-enabled after 3 seconds
        if (switchCharacteristic.value() == (byte) 0x01) {
          Serial.println(F("LED on"));
          motionDisable = true;
          motionTime = millis();
          digitalWrite(relayPin, LOW);
          digitalWrite(LED_BUILTIN, HIGH); //Turn on internal LED for debugging
        }

        //If "off message", turn off relay
        else if (switchCharacteristic.value() == (byte) 0x00 ) {                  // a 0 value
          Serial.println(F("LED off"));
          digitalWrite(relayPin, HIGH);
          digitalWrite(LED_BUILTIN, LOW);

        }
      }

      // Poll UV sensor at desired interval set by uvInterval, calculate running average of last 10
      // readings. If UV light level is below threshold, indicate that light is off
      // Otherwise indicate that it is on
      long currentMillis = millis();
      if (currentMillis - uvTime >= uvInterval) { //if it is time to take another reading
        uvTime = currentMillis; //We are taking a reading now, record the time
        uvTotal = uvTotal - uvReadings[uvIndex]; //Subtract the 'old' reading with index uvIndex from the total
        uvReadings[uvIndex] = analogRead(uvPin); //Store a new reading with index uvIndex
        Serial.println(uvReadings[uvIndex]);
        uvTotal = uvTotal + uvReadings[uvIndex]; //Calculate new total using new reading with index uvIndex
        uvIndex = uvIndex + 1; // Increase uvIndex by one to prepare for next step

        if (uvIndex >= numUvReadings) { //If the Index has reached the maximum, return to index 0
          uvIndex = 0;
        }
        uvAverage = uvTotal / numUvReadings; //Calculate new average     

        //Serial.println(currentBright);
        if (uvAverage < uvThreshold) {
          statusCharacteristic.writeValue((byte) 0x00); //if UV level is below threshold, report light is off
        }
        else {
          statusCharacteristic.writeValue((byte) 0x01); //if UV level is above threshold, report light is on
        }
      }
    }

    // when the central disconnects, print it out, turn indicator LED off, turn UV light OFF
    Serial.print(F("Disconnected from central: "));
    Serial.println(central.address());
    digitalWrite(ledPin, LOW);
    digitalWrite(relayPin, HIGH);
  }
}


// Watchdog timer setup.
void enableWDT() {
  //Configure WDT on nRF52840.
  NRF_WDT->CONFIG         = 0x01;     // Configure WDT to run when CPU is asleep
  NRF_WDT->CRV            = 196609;  // Timeout set to 6 seconds, timeout[s] = (CRV-1)/32768
  NRF_WDT->RREN           = 0x01;     // Enable the RR[0] reload register
  NRF_WDT->TASKS_START    = 1;        // Start WDT
}

// Reset Watchdog timer
void resetWDT() {
  // Reload the WDTs RR[0] reload register
  NRF_WDT->RR[0] = WDT_RR_RR_Reload;
}
// Motion detect Interrupt Service Routine. If Motion is detected and the motion sensors have been disabled
// throw motion flag. Otherwise do nothing.
void motionDetected() {

  if (motionDisable == false) {
    motionFlag = true;
  }

  else {
  }
}

UV-Connect Android Application .apk File

Plain text
This is the .apk file for the Android application. You can use this to install the application directly on an Android device.
No preview (download only).

UV-Connect Android Application .aia file

Plain text
This is the file used to modify the Android Application in MIT App Inventor. To use it, download the file to your computer. In MIT App Inventor, choose File -> Import and select this file.
No preview (download only).

Credits

Lingnan Entrepreneurship Initiative
2 projects • 4 followers
Lingnan Entrepreneurship Initiative (LEI) is a collaborative Innovation and Entrepreneurship platform at Lingnan University in Hong Kong.

Comments