top of page
  • Writer's picturePuriphico

Prototyping: .5 programming

This post covers the code in great detail, and I hope that, if you wish to understand the functionality of the Puriphico prototype - whether for the purpose of building personal projects or simply staying informed - this article proves helpful.


*Please note: All of my 'annotations' will be in red and have a '//' or a '/*' before them to indicate a comment. This way, you will be able to copy the code directly, if you wish, and not have to omit the annotations. Light blue indicates the original code.


/*

To preface: this code incorporates no screen or serial communication, but it does incorporate bluetooth as well as the calibration mechanism.


In the first part of the code, everything preceded by a #include indicates the calling of a library that will be used throughout the code. Everything that pertains to these libraries contains its own special syntax, which proves fairly difficult to follow if you are unfamiliar with the library; however, I have #included all of these libraries because they are required for some feature to operate properly.

- For example, the library entitled ArduinoBLE.h is used for Bluetooth communication. It contains specific functions, which I access later on, that allow for the Arduino board to communicate with an external device, for example.


*/


#include <PDM.h>

#include <Adafruit_GFX.h>

#include <Adafruit_SSD1306.h>

#include <arduinoFFT.h> //for the Fourier transform

#include <ArduinoBLE.h>


arduinoFFT FFT = arduinoFFT();


#define SAMPLES 256 //Must be a power of 2

#define SAMPLING_FREQUENCY 16000

byte sensorPin = 10;


short sampleBuffer[SAMPLES];

volatile int samplesRead;

double vReal[SAMPLES];

double vImag[SAMPLES];

void onPDMdata(void);


// Declaring variables used throughout the code

int timer = 0;

int timer2 = 0;

int timer3 = 0;

int setting = 0;

byte counter = 0;

byte underCounter = 0;


byte sensorCount = 0;

byte auxiliaryCount = 0;

byte auxiliaryCount2 = 0;


// Here, I give various digital pins names; this way, I don't have to remember which pin corresponded to which component. Later, these pins will be declared as INPUTS and OUTPUTS

int buttonPin = 12;

int GLEDPin = 5;

int RLEDPin = 6;


// Declaring variables used in the calibration system segment:

int buttonRead;

int forceState = 0;

int timer4 = 0;

int counter4 = 0;

int loop_counter_cal = 0;

int loop_counter_cal2 = 0;


float waterav = 0;

float wateravReal = 0;

float watertotal = 0;

float washhandsav = 0;

float washhandsavReal = 0;

float washhandstotal = 0;

float newwashhandsav = 0;

float newwashhandstotal = 0;

float difference = 0;

float difference2 = 0;

float new_average = 0;


// Specifying a Unique Universal ID for Bluetooth purposes

const char* uuidOfService = "00001101-0000-1000-8000-00805f9b34fb";

const char* uuidOfRxChar = "00001142-0000-1000-8000-00805f9b34fb";

const char* uuidOfTxChar = "00001143-0000-1000-8000-00805f9b34fb";


BLEService handwashService(uuidOfService);


BLECharacteristic rxChar(uuidOfRxChar, BLEWriteWithoutResponse | BLEWrite, counter, underCounter);

BLEByteCharacteristic txChar(uuidOfTxChar, BLERead | BLENotify | BLEBroadcast);


// Setting up the code

void setup() {

// PDM refers to functions associated with the built-in microphone. This is telling the program to check that the microphone is ready for operation; if it is not, then the program will wait until it is before continuing.

PDM.onReceive(onPDMdata);

PDM.setBufferSize(SAMPLES);

if (!PDM.begin(1, 16000)) {

while (1);

}


// LED pins configuration - setting up INPUTS and OUTPUTS

pinMode(LED_BUILTIN, OUTPUT);

pinMode(GLEDPin, OUTPUT);

pinMode(RLEDPin, OUTPUT);

// Other configurations

pinMode(sensorPin,INPUT);

pinMode(buttonPin, INPUT);

// Start with two LEDs off

digitalWrite(GLEDPin, LOW);

digitalWrite(RLEDPin, LOW);


// Starting BLE (Bluetooth Low Energy)

if (!BLE.begin()) {

while (1);

}

delay(2000);


BLE.setLocalName("HandwashMonitor");

BLE.setAdvertisedService(handwashService);

handwashService.addCharacteristic(rxChar);

handwashService.addCharacteristic(txChar);

BLE.addService(handwashService);

// Bluetooth LE connection handlers.

BLE.setEventHandler(BLEConnected, onBLEConnected);

BLE.setEventHandler(BLEDisconnected, onBLEDisconnected);

BLE.advertise();

}


// Syntax associated with PDM library - specifies variables, such as samplesRead

void onPDMdata() {

int bytesAvailable = PDM.available();

PDM.read(sampleBuffer, bytesAvailable);

samplesRead = bytesAvailable / 2;

}


// The primary code loop

void loop() {


// Start of the calibration mechanism: if the button has been pressed (ie buttonRead == 0), then initialize the calibration system, which follows the while forceState == 1 declaration

buttonRead = digitalRead(buttonPin);

if (buttonRead == 0) {

forceState = 1;

}

while (forceState == 1) {

timer4 = timer4 + 1;

// each time the loop runs, there is a delay of one second, and the 'timer' variables augments by 1, as you can see above. In the calibration mechanism, as you may know, there are two primary types of audio data recorded: running water (first 10 seconds) and handwashing (latter 10 seconds). To learn more about the calibration system, find the related article at the bottom of this post.

if (timer4 < 10) {

digitalWrite(GLEDPin, HIGH);

digitalWrite(RLEDPin, LOW);

if (samplesRead) {

for (int i = 0; i < SAMPLES; i++) {

vReal[i] = sampleBuffer[i];

vImag[i] = 0;

}

FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);

FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);

FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);


for (int i = 50; i < 150; i++) {

watertotal = watertotal + vReal[i];

} loop_counter_cal2 = loop_counter_cal2 + 1;

}

} else if (10 < timer4 < 20) {

digitalWrite(RLEDPin, HIGH);

digitalWrite(GLEDPin, LOW);

if (samplesRead) {

for (int i = 0; i < SAMPLES; i++) {

vReal[i] = sampleBuffer[i];

vImag[i] = 0;

}

FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);

FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);

FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

for (int i = 50; i < 150; i++) {

washhandstotal = washhandstotal + vReal[i];

} loop_counter_cal = loop_counter_cal + 1;

}

} if (timer4 == 20) {

counter4 = counter4 + 1;

forceState = 2;

// when the timer reaches a value of 20, the calibration has finished; to indicate this, both the green and red LED's flash three times, as shown below.

digitalWrite(GLEDPin, LOW);

digitalWrite(RLEDPin, LOW);

delay(500);

digitalWrite(GLEDPin, HIGH);

digitalWrite(RLEDPin, HIGH);

delay(500);

digitalWrite(GLEDPin, LOW);

digitalWrite(RLEDPin, LOW);

delay(500);

digitalWrite(GLEDPin, HIGH);

digitalWrite(RLEDPin, HIGH);

delay(500);

digitalWrite(GLEDPin, LOW);

digitalWrite(RLEDPin, LOW);

delay(500);

digitalWrite(GLEDPin, HIGH);

digitalWrite(RLEDPin, HIGH);

delay(500);

digitalWrite(GLEDPin, LOW);

digitalWrite(RLEDPin, LOW);

timer4 = 0;

washhandsav = washhandstotal/100;

washhandsavReal = washhandsav/loop_counter_cal;

waterav = watertotal/100;

wateravReal = waterav/loop_counter_cal2;

new_average = (wateravReal + washhandsavReal)/2;

// new_average will be the standard for evaluating future handwashing sessions - if the audio signals are within a certain range (calculated using the new_average), the device will consider the audio frequencies to be handwashing.

} delay(1000);

}

if (forceState == 2) {

// Once the calibration has finished, it declares. the variable forceState to be 2, meaning that the following code can only be executed once the device has been calibrated.

if (samplesRead) {

for (int i = 0; i < SAMPLES; i++) {

vReal[i] = sampleBuffer[i];

vImag[i] = 0;

}

// evaluating newly-collected audio samples; if the audio samples comply with the requirements, then the variable auxiliaryCount2 is set to 1 - an important setting because, later, the code will ensure that both auxiliaryCount2 (representing the audio check) and auxiliaryCount (representing the visual motion check) have been set to 1 (meaning both sensors detect handwashing).

FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);

FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);

FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

for (int i = 50; i < 150; i++) {

newwashhandstotal = newwashhandstotal + vReal[i];

}

}

newwashhandsav = newwashhandstotal/100;

newwashhandstotal = 0;

if (newwashhandsav > new_average) {

auxiliaryCount2 = 1;

} else {

auxiliaryCount2 = 0;

}


byte state = digitalRead(sensorPin);

if (state == 1) {

auxiliaryCount = auxiliaryCount + 1;

} else if (state == 0) {

auxiliaryCount = 0;

}

if (auxiliaryCount > 0) {

sensorCount = sensorCount + 1;

}

if (auxiliaryCount2 > 0) {

sensorCount = sensorCount + 1;

}

// This is where the program checks that both sensors detect handwashing patterns; if so, it sets the setting variable to 1, which permits the actual timer to begin (this is when the device formally recognizes handwashing and initiates the timer and flashes the red LED).

if (auxiliaryCount > 0 or auxiliaryCount2 > 0) {

if (auxiliaryCount > 0 and auxiliaryCount2 > 0) {

setting = setting + 1;

timer2 = 0;

}

if (setting > 0) {

timer = timer + 1;

digitalWrite(RLEDPin, HIGH);

digitalWrite(GLEDPin, LOW);

delay(500);

digitalWrite(RLEDPin, LOW);

delay(500);


if (sensorCount == 1) {

timer2 = timer2 + 1;

if (timer2 == 5) {

timer = 0;

timer2 = 0;

setting = 0;

}

}

if (timer == 5) {

underCounter = underCounter + 1;

}

if (timer == 20) {

counter = counter + 1;

digitalWrite(GLEDPin, HIGH);

digitalWrite(RLEDPin, LOW);

delay(500);

digitalWrite(GLEDPin,LOW);

delay(500);

digitalWrite(GLEDPin,HIGH);

delay(500);

digitalWrite(GLEDPin,LOW);

delay(500);

digitalWrite(GLEDPin,HIGH);

delay(500);

digitalWrite(GLEDPin,LOW);

delay(500);

timer = 0;

timer2 = 0;

setting = 0;

delay(2000);

}

}

}

else {

timer = 0;

timer2 = 0;

setting = 0;

digitalWrite(RLEDPin, LOW);

digitalWrite(GLEDPin, LOW);

}


buttonRead = digitalRead(buttonPin);

if (buttonRead == 0) {

forceState = 1;

}

}

auxiliaryCount = 0;

auxiliaryCount2 = 0;

sensorCount = 0;

digitalWrite(sensorPin, LOW);


BLEDevice central = BLE.central();

if (central)

{

// Only send data if we are connected to a central device (ie an iPhone).

while (central.connected()) {

connectedLight();

txChar.writeValue(counter);

delay(1000);

txChar.writeValue(underCounter);

}

} else {

disconnectedLight();

}

}


// Functions associated with Bluetooth functionality - when the device is connected to an iPhone, the green LED illuminates; when it is not, both LED's remain off.

void onBLEConnected(BLEDevice central) {

connectedLight();

}


void onBLEDisconnected(BLEDevice central) {

disconnectedLight();

}


void connectedLight() {

digitalWrite(GLEDPin, HIGH);

//digitalWrite(RLEDPin, HIGH);

}


void disconnectedLight() {

digitalWrite(GLEDPin, LOW);

// digitalWrite(RLEDPin, LOW);

}



As I make progress with the development of the prototype, I plan to keep a log here, in the blog, under the "Updates" category. Subscribe to be alerted whenever a new update has been developed! (Also, if you are looking to implement more technical improvements in your Arduino programs, these examples can serve as a framework, so I suggest reading fully and analyzing the code. That said, when you spot an improvement to be made in my codes, PLEASE let me know ASAP - I'm looking for feedback!)


Recent Posts

See All
bottom of page