This publication will detail my plans/designs for the upcoming Puriphico prototype, including its code, custom PCB schematic, and case design.
The new prototype is scheduled for the end of June/beginning of July, when I will reveal it in full detail and demonstrate its operation in various bathroom settings.
Code:
The below code incorporates Bluetooth connectivity as well as the calibration system. The calibration system featured here, however, is different to the one published prior in the way that it recognizes handwashing. A separate article (I don't want this one to be too long) will be published to cover this mechanism in greater detail, as well as other parts of the code, so that you have the option to follow along and understand the logic behind it.
*Please note: I know there are areas for improvement in this code, especially considering I have largely emitted the use of return-functions; if you spot any areas for improvement, or simply have questions, send them through the contact page or simply write a comment. Thanks!
// this code incorporates no screen or serial communication
// this code incorporates bluetooth and the calibration mechanism
#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);
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;
int buttonPin = 12;
int GLEDPin = 5;
int RLEDPin = 6;
// variables used in the calibration system:
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; //ns
float difference2 = 0; //ns
float new_average = 0;
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);
void setup() {
PDM.onReceive(onPDMdata);
PDM.setBufferSize(SAMPLES);
if (!PDM.begin(1, 16000)) {
while (1);
}
// LED pins configuration
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
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();
}
void onPDMdata() {
int bytesAvailable = PDM.available();
PDM.read(sampleBuffer, bytesAvailable);
samplesRead = bytesAvailable / 2;
}
void loop() {
buttonRead = digitalRead(buttonPin);
if (buttonRead == 0) {
forceState = 1;
}
while (forceState == 1) {
timer4 = timer4 + 1;
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;
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;
} delay(1000);
}
if (forceState == 2) {
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++) {
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;
}
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.
while (central.connected()) {
connectedLight();
txChar.writeValue(counter);
delay(1000);
txChar.writeValue(underCounter);
}
} else {
disconnectedLight();
}
}
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);
}
Schematic/Circuit:
The below graphic of a custom printed circuit board (PCB) encompasses the various new features described in prior blog posts: the calibration system, an on/off switch, and the removal of a screen (which has been replaced with an iPhone 'app'). An upcoming article will be released to describe in greater detail the circuitry, as well as how it incorporates with the program above.
Schematic:
3-D Renders (missing components):
Styling:
The below sketches illustrate my visions for the prototype's case, as well as its incorporation with the above PCB. In designing this case, I wanted to attain a natural, earthy feel, wherein the functions of the device remained inconspicuous and concealed by the apparent purpose of housing a succulent. In a separate article, each individual graphic will be accompanied by a unique description of what it depicts, so that, if you wish, you can follow along my line of thinking.
Cross-Sections:
External Renderings:
Leave any thoughts or suggestions in the comments - more to come soon!
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!)
Comments