/* 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 //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 { } }