Project in progress
Bluetooth Control for UV-C Disinfection Lights

Bluetooth Control for UV-C Disinfection Lights © CC BY

Safely control a UV-C disinfection light over Bluetooth.

  • 8 respects

Components and supplies

Necessary tools and machines

Apps and online services

About this project


This is a method of controlling and monitoring ultraviolet germicidal lights using Bluetooth communication on an Android device along with a Bluetooth-enabled Arduino. This Bluetooth control module was designed to control the ultraviolet germicidal lights for Project Ultra-Violite by the Lingnan Entrepreneurship Initiative at Lingnan University. The lights used in this project enabled the disinfection of low-income housing in Hong Kong. Volunteers from local non-governmental organizations used the lights to disinfect over 1000 low-income residences in Hong Kong during the height of the COVID-19 pandemic. These Bluetooth-controlled lights were designed to be safe, durable and easy to use. The project was a collaboration between Lingnan University, Department of Social Work and Social Administration of The University of Hong Kong, Caritas Youth and Community Service, and Health In Action and funded by the Hong Kong Jockey Club Charities Trust. More information can be found here:

This project can be used to control any ultraviolet light (or traditional lights as well), but was specifically designed and tested with the Project Ultra-Violite lights. Instructions for building those light units can be found here:

Important safety information

This project presents risks from using AC mains power, as well as ultraviolet light in the C band.

Using and interacting with mains power is dangerous. The wiring in this project should only be done by someone trained in and comfortable working with mains power. If you are not familiar with the safety requirements of AC mains power, request assistance from a properly trained technician.

Ultraviolet light in the C band is harmful to the eyes and skin. Exposure can lead to blindness and burning. Never operate the lights in a way where you or other living things could be exposed to the light in any way. You must ensure that no one is in the same room as the lights, or in any location where they could be exposed to the lights while they are operating.


This project consists of four main components: A circuit to connect the Arduino Nano BLE to the sensors and UV lights, the Arduino code for interacting with the sensors and the lights, an Android application created using MIT App Inventor to interact and monitor the lights, and a 3D printed mount to attach the controller to a tripod.

The Materials

The lights use common commercially available hardware, with some optional 3D-printed parts to aid in assembly. Let's go over the details of the parts needed to make the lights:

  • Arduino Nano BLE Sense. This microcontroller is used to interface between the Android application, the lights and the sensors. It was chosen primarily for its Bluetooth capabilities. Any Bluetooth Low Energy-enabled Arduino will work for this project.
  • PassiveInfraredMotionSensors. These sensors are used to determine if any person or other living thing is near the lights during operation. If the sensor is tripped, the lights are automatically turned off. Each sensor has a field of view of around 100 degrees, so using three mounted in a circular pattern gives nearly 360 degrees of coverage.
  • GUVA-S12SDUltravioletSensor. Used to monitor the status of the lights, whether they are illuminated or not. This allows users to ensure that the lights have been turned on as expected, so that the target space has been disinfected. It also give users an indication if the room is safe to enter by displaying if the lights are on or off.
  • 10A250VRelay. Used to trigger the lights.
  • 8Channel Bidirectional Level Shifter. Used to adjust the signal voltage between the Arduino Nano, which operates at 3.3V, and the relay which operates at 5V. The 8 Channel level-shifter was chosen for this project to account for the possibility of using more 5V sensors or relays. However, for the project in its current configuration, only one channel is needed.
  • Hilink HLK-PM01 5V AC/DC Regulator. Provides 5V power to Arduino and sensors from the mains power supply for the lights. If you are properly trained and comfortable working with AC power, you can include this regulator in your circuit to power these other components. If not, then another power source such as a small battery or a cell phone charger.
  • Android Device. Interfaces with the lights over Bluetooth using the included Android application.
  • Protoboard. Platform to construct the circuit.

The Circuit

The core of the circuit is the Arduino Nano BLE Sense. This provides the capability to communicate with the circuit using an Android device over Bluetooth. The Arduino receives information from four sensors - the three PIR sensors and one UV-C sensor. The PIR sensors are connected to three of the Arduino's digital pins, and each are connected to 5V power and ground. The UV-C sensor is connected to one of the Arduino's analog pins, as well as 5V power and ground.

The Arduino has two main outputs, the 10A relay and a status LED. The relay acts as a switch to turn on and off the power to the lights. It is connected to the Arduino through the level-shifter because the Arduino Nano operates at 3.3V and the relay can only be triggered by a 5V signal. The level-shifter is connected to 3.3V power from the Arduino Nano for the 3.3V logic reference, 5V power from the voltage regulator for the 5V logic reference, and a common ground. The level shifter also has an enable pin, which when driven high enables output from the level-shifter. Therefore, it is connected through a 1K ohm resistor to a digital pin on the Arduino Nano. The relay is also connected to 5V power and ground. The live mains power line of the lights runs through the relay, allowing it to be switched on and off.

The status LED is connected to the a digital pin of the Arduino through a 1K ohm resistor. It acts as a visual indicator of the power and connection status of the control unit.

Power was provided through using the HiLink HLK-PM01 5V AC/DC Regulator. This is connected to mains power through a 13 amp fuse. If you are properly trained and comfortable working with AC power, you can include this regulator in your circuit to power these other components. If not, then another power source such as a small battery or a cell phone charger. If you choose to use the cell phone charger, it can be connected to the Arduino through the USB port. The reset of the schematic remains the same. If you choose to use the HiLink HLK-PM01, you may consider using this breakout board from OpenHardware: This breakout board adds safety using two types of fuses and a varistor.

You may assemble the circuit on a breadboard, protoboard, strip board or other similar platform according to the schematic included below. While we have not yet created a printed circuit board for the circuit, we have included the preliminary Eagle files if others wish to do so.


As the project includes AC mains power, a proper enclosure is important to prevent accidental contact with the high voltage. We used a generic plastic electrical enclosure with dimensions of 150mm x 70mm x 27mm. We mounted the protoboard, relay and 3D printed parts with hot glue.

We also designed a mount to attach the box and PIR motion sensors to the Phottix P220 tripod used to hold the lights (see our tutorial for constructing the lights here). The CAD files are included in this tutorial, but may need to be modified for enclosures or tripods of different sizes.

The Arduino Code

The Arduino code is written using the ArduinoBLE library. Bluetooth Low Energy devices can either be Peripheral devices, which act as servers holding for the information being communicated, or Central devices, which query the Peripherals for data, and can modify data contained on the Peripheral devices. In our implementation, the Arduino is a Peripheral device, and the connect Android device is the Central.

Bluetooth Low Energy communication centers around Services, which allow for the organization of the data being communicated between devices. Each service contains multiple characteristics, which hold specific data. In this example, there is one Service (the 'lightService' service), and four Characteristics. There is one Characteristic to hold data concerning whether the relay should be switched on or off, one to hold data concerning whether or not the motion sensors have been triggered, one holding data from the UV-C sensor, and one holding data about whether any Android devices are connected to the Arduino. These Services and Characteristics are defined right at the beginning of the sketch, as shown in the code block below.

Also defined at the beginning of the sketch is the local name. This is important because the Android application will search for a portion of this name when it automatically connects to the lights. For example, our version of the Android application searches for the phrase 'LU_Light' in the names of available Bluetooth devices. If it finds this phrase, it automatically connects. Therefore, the lights that we use are named 'LU_Light_1', 'LU_Light_2', 'LU_Light_3' etc. Therefore, you should choose to set the LocalName to include whatever phrase you program the Android application to search for. More of this is explained in the 'Android Application' section below.

The next section of the code is standard definitions of Arduino pins and initialization of variables:

In the setup section, the Watchdog Timer appears for the first time. The Watchdog timer is used to handle any unexpected disconnections or freezing of the Arduino. This timer counts down for the desired time (in this case 2 seconds). By calling resetWDT() in strategic locations in the code, we can ensure that the timer does not reach zero. If the resetWDT() command is never called and the timer reaches zero, this means that the Arduino has frozen or become disconnected before the next resetWDT() command. Thus, the Watchdog timer restarts the Arduino, allowing us to reconnect to it. This is especially important since it is critical that a user always be able to monitor the status of the lights and turn off the lights when necessary.

The Watchdog timer in this sketch is especially useful because of a known bug with the Arduino Nano BLE using the ArduinoBLE library. See here: and The Arduino Nano BLE does not properly recognize disconnection events. Thus it is possible that if the Android device disconnects unexpectedly, the Arduino will continue to act as if it is connected, and then it becomes impossible to reconnect and monitor or control the lights. To overcome this, the Arduino listens for a polling message from the Android device. If it receives this message, we know that they are still properly connected, and can reset the Watchdog timer and the Arduino continues operating normally. If the polling message is not received in time, then the Watchdog timer is not reset, reaches 'zero', and restarts the Arduino.

Special thanks to Arduino user dniklewicz for posting the suggestion of using the Watchdog timer.

In the code snippet below, enableWDT() and resetWDT() are functions defined at the end of the sketch. In this snippet, we can see the first use of the resetWDT() function as well. We attempt to start the Bluetooth library. If it fails to begin, we keep the LED blinking to indicate a problem. the resetWDT() function is used in the while loop to prevent the Watchdog timer from reaching zero and the Arduino from restarting.

In the next section, the Bluetooth service is configured. Next, the characteristics are added to the service, ensuring their data is visible to connected devices. In addition, the interrupts for the motion sensors are attached. If a motion sensor detects motion, it will trigger this interrupt.

The next section begins the loop, which will run repeatedly. In the loop, the Arduino listens for devices to connect. If none is connected, the LED blinks. We see here another resetWDT() ensuring that the Watchdog timer does not initiate a restart of the Arduino.

The next section handles the motion sensors. As will be seen later in the sketch, for three seconds after the lights are initially turned on, the motion sensors are disabled. This is because we found that the sudden change in light that occurs when the lights turned on falsely triggered the motion sensor. Thus we disable the motion sensors for a short time after turning on the lights. If it has been long enough since the lights turned on (about 3 seconds), we re-enable the motion sensors.

If the motion sensors have been triggered (motionFlag set to true by the motionDetected() interrupt service routine at the end of the sketch), then we change the value of the motionCharacteristic, which will be automatically reported to the connected device and a message displayed to the user. We also automatically turn the lights off by switching the relay. At the end, motionFlag is set to false so that it can be triggered again later if needed.

The next section handles the data from the other characteristics. First is the connectionCharacteristic, which tracks whether or not the Android device is still connected. It does so by listening for a value of 0x01 which is sent by the Android device. This indicates they are still connected, so the Watchdog timer is reset. If the Android device sends 0x00, this indicates that the user wishes to disconnect form the lights.

The switchCharacteristic is the 'on/off' switch for the lights. If the Android device sends 0x01, then the user has pushed the 'On' button. Thus the relay is turned on (triggered by a low signal). As discussed above, the motion sensors are also temporarily disabled to avoid false triggers from the quick flash of the lights turning on, and the time is recorded so that the motion sensors can be re-enabled in approximately three seconds. If the 0x00 byte is sent, then the use wishes to turn off the light and the relay is switched off (set to high).

Since users cannot be in the same room as the lights while they are operating for safety reasons, we include a UV sensor so they can verify whether the light is truly on or off. The UV sensor is polled at a desired interval, by default every second. We use a 10 reading running average to determine if the level of UV light exceeds a threshold or not. The variable uvReadings is a list of ten UV readings, indexed 1 to 10. The readings are collected in 10 steps. At each step, the old reading from the step is subtracted from the total, and the new reading is added. An average is then collected by dividing the total by the number of readings taken. If the average is below a threshold, it is determined that the light is off, and a 0x00 byte is sent to the Android device so to create an indication for the user. If the average is above a threshold, the light is on and a 0x01 byte is sent to the Android device.

The last section are the functions used earlier in the sketch. The first is the function used to enable the watchdog timer. Of note is the is the CRV, which is used to set the timeout time. It is calculated as timeout = (CRV-1)/32768. So if you would like to have a 6 second timeout, you must set the CRV to 196609, since (196609 - 1) / 32768 = 6. This calculation comes from the natural frequency of the crystal used to keep time in the processor, 32768 Hz.

The first section is the code used to reset the watchdog timer. The last is the Interrupt Service Routine used if the motion sensors have been triggered. If you are unfamiliar with Arduino interrupts, you can learn more here: If the trigger occurs during the three second period right after the lights have been turned on, in which we purposefully disable the lights, nothing is done. If not, then the motionFlag variable is set to true. In the next iteration of the loop, the relay will be turned off and a message will be sent to the Android device indicating that motion has been detected.

Android Application

The Android application was built using MIT App Inventor 2 with the BluetoothLE extension. The application is designed to be used with a set of three lights. However, it can also be used with just one set or two sets. The main functions of the lights are connecting the Android device to the lights over Bluetooth, turning the lights on and off, monitoring the status of the lights, and handling notifications of motion detected by the lights motion sensors.

When a user opens the application, they are presented with four buttons allowing them to connect to a device, disconnect from a device, turn the connected lights on or turn the connected lights off. The bottom half of the screen shows the status of each of the lights. The name of the light is shown on the left. The connection status is indicate using a 'wireless' symbol in the center. A red 'X' through the symbol indicates that the light is not disconnected. No 'X' indicates the device is connected. A light bulb icon will show on the right to indicate if the light is on. Otherwise, no light bulb appearing indicates the light is off.

The first step is to connect to available lights. When a user presses the Connect button, the application begins scanning for available Bluetooth devices, which it aggregates into a list. During the scanning process, the wireless icon is yellow. For each Bluetooth device that it finds, it queries the name of the device. If the name of the device matches the prefix used to designate the UV lights, in our case we use the prefix LU_Light, then it connects to that light.

Once a light is connected, the name of the device is shown and the wireless icon turns blue.

When the desired number of lights are connected, the lights can be turned on using the 'ON' button. Pressing the 'ON' button causes the Android device to send a 0x01 byte to all of the lights on the switchCharacterstic described above. When the lights are on, the UV sensors in the lights detect this, and the Arduinos inside the lights send a 0x01 byte on the statusCharacteristic. This is then indicated on the screen using a light bulb icon on the right hand side of the screen.

Pressing the 'OFF' button causes a 0x00 byte to be sent to all of the lights on the switchCharacteristic. When the UV sensors in the lights detect that they are not on, the Arduinos send a 0x00 byte on the statusCharacteristic, and the light bulb icons disappear.

When motion is detected by one of the lights, that Arduino sends a message on the motionCharacteristic. This triggers the Arduino device to send a 0x00 byte on the switchCharacteristic to all lights, ensuring that all lights turn off. A message is displayed on the application screen. This message can be cleared by clicking on the 'X' icon.

Whenever the lights are connected to the Android application, the Android device is sending a 0x01 byte on the connectionCharacteristic. As explained in the Arduino section above, this is to indicate that the devices are still properly connected. When the 'Disconnect' button is pressed, the application sends a 0x00 byte on the connectionCharacteristic of all lights. This triggers them to disconnect. Although MIT App Inventor includes a 'disconnect' function, this was found to not work reliably with the Arduino Nano BLE Sense, so instead of sending an explicit 'disconnect' command, we send the 0x00 byte, which triggers the Arduino to disconnect itself.

Install the Application.apk File

We have included both the.apk file for the app as well as the MIT App Inventor project file, which uses the.aia format. To install from the.apk, simply download the file from below on your Android device. Tapping on the downloaded file will start the installation process. You may need to allow the installation of non-market apps in your device settings. For more information on installing from.apk, follow this guide:

Using the included Arduino code and the.apk file is the easiest way to get started. If you wish to modify the application, follow the steps below.

Modifying the Application with MIT App Inventor

To modify the application, you will need to first create an account on MIT App Inventor if you do not have one. Then, download the.aia file from below. In MIT App Inventor, click File -> Import and select the.aia file. Opening the file will bring you to the App Inventor workspace, where you can modify the interface from the Designer tab or modify the code from the Blocks tab. The included code is fully documented. To view comment in MIT App Inventor, click the question mark in the upper left corner of each Block.


LU_Light_Control Arduino CodeArduino
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() {
  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) {
      digitalWrite(ledPin, HIGH);
      digitalWrite(ledPin, LOW);


  // set advertised local name and service UUID:

  // add the characteristic to the service

  // add service

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

  // start advertising

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

  // 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:
    digitalWrite(ledPin, HIGH);
    motionTime = millis();
    // 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) {
       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
        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     

        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: "));
    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 FilePlain 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 filePlain 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).

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.


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


Similar projects you might like

Connected Visor

Project in progress by Team Yayi Make

  • 1 comment
  • 9 respects

Bluetooth Control Hat

Project showcase by EyalSch

  • 56 respects

Car Control with Arduino Uno and Bluetooth

Project tutorial by Mehmet SARI

  • 48 respects

Bluetooth control led with lcd led status display real time.

Project tutorial by Youssef Sabaa

  • 55 respects

How to Make a Gesture Control Robot at Home

Project tutorial by Shubham Shinganapure

  • 74 respects

Health Kit: Humidity and Temperature Control

Project tutorial by Diana Khalipina

  • 53 respects
Add projectSign up / Login