diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..31127a2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +main +cmake_install.cmake +CMakeFiles/ +tags +CMakeCache.txt +CTestTestfile.cmake +main_autogen/ +pul +screenshot.png + diff --git a/.temp.c.swp b/.temp.c.swp new file mode 100644 index 0000000..0291b20 Binary files /dev/null and b/.temp.c.swp differ diff --git a/Audio/fixyou.mp3 b/Audio/fixyou.mp3 new file mode 100644 index 0000000..02dee76 Binary files /dev/null and b/Audio/fixyou.mp3 differ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..d757468 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,36 @@ +cmake_minimum_required(VERSION 3.7.0) + +project(pulse VERSION 1.0.0 LANGUAGES CXX) + +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "RelWithDebInfo") +endif() +add_compile_options(-Wall -Wconversion -Wextra -pedantic) +add_library(cpptimer STATIC CppTimer.cpp) +TARGET_LINK_LIBRARIES(cpptimer rt fcgi ${CMAKE_THREAD_LIBS_INIT} curl) +set_target_properties(cpptimer PROPERTIES POSITION_INDEPENDENT_CODE TRUE) + +set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOUIC ON) +set(THREADS_PREFER_PTHREAD_FLAG ON) + +find_package(Qt5 COMPONENTS Widgets PrintSupport REQUIRED) +find_package(QCustomPlot) +find_library(wiringPi_LIB wiringPi) +find_package(Threads REQUIRED) +find_package( CURL ) + +add_executable(main + CppTimer.cpp + mainwindow.cpp + main.cpp +) + +target_link_libraries(main + Qt5::Widgets Qt5::PrintSupport Threads::Threads cpptimer ${QCustomPlot_LIBRARIES} ${wiringPi_LIB fcgi rt ${CMAKE_THREAD_LIBS_INIT} curl} + ) + diff --git a/CppTimer.cpp b/CppTimer.cpp new file mode 100644 index 0000000..c483c4c --- /dev/null +++ b/CppTimer.cpp @@ -0,0 +1,96 @@ +#include "CppTimer.h" + +/** + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * (C) 2020-2021, Bernd Porr + * + * This is inspired by the timer_create man page. + **/ + +CppTimer::CppTimer(const int signo) +{ + // We create a static handler catches the signal SIG + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = handler; + sigemptyset(&sa.sa_mask); + if (sigaction(signo, &sa, NULL) == -1) + throw("Could not create signal handler"); + + // Create the timer + sev.sigev_notify = SIGEV_SIGNAL; + sev.sigev_signo = signo; + // Cruical is that the signal carries the pointer to this class instance here + // because the handler just handles anything that comes in! + sev.sigev_value.sival_ptr = this; + // create the timer + if (timer_create(CLOCKID, &sev, &timerid) == -1) + throw("Could not create timer"); +} + +void CppTimer::startns(long nanosecs, cppTimerType_t type) +{ + switch (type) + { + case (PERIODIC): + //starts after specified period of nanoseconds + its.it_value.tv_sec = nanosecs / 1000000000; + its.it_value.tv_nsec = nanosecs % 1000000000; + its.it_interval.tv_sec = nanosecs / 1000000000; + its.it_interval.tv_nsec = nanosecs % 1000000000; + break; + case (ONESHOT): + //fires once after specified period of nanoseconds + its.it_value.tv_sec = nanosecs / 1000000000; + its.it_value.tv_nsec = nanosecs % 1000000000; + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + break; + } + if (timer_settime(timerid, 0, &its, NULL) == -1) + throw("Could not start timer"); +} + +void CppTimer::startms(long millisecs, cppTimerType_t type) +{ + switch (type) + { + case (PERIODIC): + //starts after specified period of milliseconds + its.it_value.tv_sec = millisecs / 1000; + its.it_value.tv_nsec = (millisecs % 1000) * 1000000; + its.it_interval.tv_sec = millisecs / 1000; + its.it_interval.tv_nsec = (millisecs % 1000) * 1000000; + break; + case (ONESHOT): + //fires once after specified period of milliseconds + its.it_value.tv_sec = millisecs / 1000; + its.it_value.tv_nsec = (millisecs % 1000) * 1000000; + its.it_interval.tv_sec = 0; + its.it_interval.tv_nsec = 0; + break; + } + if (timer_settime(timerid, 0, &its, NULL) == -1) + throw("Could not start timer"); +} + +void CppTimer::stop() +{ + // disarm + struct itimerspec itsnew; + itsnew.it_value.tv_sec = 0; + itsnew.it_value.tv_nsec = 0; + itsnew.it_interval.tv_sec = 0; + itsnew.it_interval.tv_nsec = 0; + timer_settime(timerid, 0, &itsnew, &its); +} + +CppTimer::~CppTimer() +{ + stop(); + // delete the timer + timer_delete(timerid); + // default action for signal handling + signal(sev.sigev_signo, SIG_IGN); +} diff --git a/CppTimer.h b/CppTimer.h new file mode 100644 index 0000000..b015d21 --- /dev/null +++ b/CppTimer.h @@ -0,0 +1,100 @@ +#ifndef __CPP_TIMER_H_ +#define __CPP_TIMER_H_ + +/** + * GNU GENERAL PUBLIC LICENSE + * Version 3, 29 June 2007 + * + * (C) 2020-2021, Bernd Porr + * + * This is inspired by the timer_create man page. + **/ + +#include +#include +#include +#include +#include + +#define CLOCKID CLOCK_MONOTONIC + +/** + * Enumeration of CppTimer types + **/ +enum cppTimerType_t +{ + PERIODIC, + ONESHOT +}; + +/** + * Timer class which repeatedly fires. It's wrapper around the + * POSIX per-process timer. + **/ +class CppTimer +{ + +public: + /** + * Creates an instance of the timer and connects the + * signal handler to the timer. The default signal which + * is being used is SIGRTMIN but can be changed to other + * signals if other processes / threads use them. + * @param signo The signal used by the timer. + **/ + CppTimer(const int signo = SIGRTMIN); + + /** + * Starts the timer. The timer fires first after + * the specified time in nanoseconds and then at + * that interval in PERIODIC mode. In ONESHOT mode + * the timer fires once after the specified time in + * nanoseconds. + * @param nanosecs Time in nanoseconds + * @param type Either PERIODIC or ONESHOT + **/ + virtual void startns(long nanosecs, cppTimerType_t type = PERIODIC); + + /** + * Starts the timer. The timer fires first after + * the specified time in milliseconds and then at + * that interval in PERIODIC mode. In ONESHOT mode + * the timer fires once after the specified time in + * milliseconds. + * @param millisecs Time in milliseconds + * @param type Either PERIODIC or ONESHOT + **/ + virtual void startms(long millisecs, cppTimerType_t type = PERIODIC); + + /** + * Stops the timer by disarming it. It can be re-started + * with start(). + **/ + virtual void stop(); + + /** + * Destructor disarms the timer, deletes it and + * disconnect the signal handler. + **/ + virtual ~CppTimer(); + +protected: + /** + * Abstract function which needs to be implemented by the children. + * This is called every time the timer fires. + **/ + virtual void timerEvent() = 0; + +private: + timer_t timerid = 0; + struct sigevent sev; + struct sigaction sa; + struct itimerspec its; + + inline static void handler(int, siginfo_t *si, void *) + { + (reinterpret_cast(si->si_value.sival_ptr))->timerEvent(); + } +}; + +#endif diff --git a/CppTimerCallback.h b/CppTimerCallback.h new file mode 100644 index 0000000..88f551f --- /dev/null +++ b/CppTimerCallback.h @@ -0,0 +1,36 @@ +#ifndef __CPP_TIMER_CALLBACK +#define __CPP_TIMER_CALLBACK +#include +#include "CppTimer.h" +#include + + +class CppTimerCallback : public CppTimer { + +public: + class Runnable { + public: + virtual void run() = 0; + }; + + void registerEventRunnable(Runnable &h) { + cppTimerEventRunnable = &h; + } + + void unregisterEventRunnable() { + cppTimerEventRunnable = NULL; + } + + void timerEvent() { + if (cppTimerEventRunnable) { + cppTimerEventRunnable->run(); + } + } + +private: + Runnable* cppTimerEventRunnable = NULL; + +}; + + +#endif diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5d0fa55 --- /dev/null +++ b/Makefile @@ -0,0 +1,299 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 3.16 + +# Default target executed when no arguments are given to make. +default_target: all + +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + + +# Remove some rules from gmake that .SUFFIXES does not remove. +SUFFIXES = + +.SUFFIXES: .hpux_make_needs_suffix_list + + +# Suppress display of executed commands. +$(VERBOSE).SILENT: + + +# A target that is always out of date. +cmake_force: + +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /usr/bin/cmake + +# The command to remove a file. +RM = /usr/bin/cmake -E remove -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /home/pi/Documents/DreamHack + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /home/pi/Documents/DreamHack + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." + /usr/bin/cmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache + +.PHONY : rebuild_cache/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." + /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache + +.PHONY : edit_cache/fast + +# The main all target +all: cmake_check_build_system + $(CMAKE_COMMAND) -E cmake_progress_start /home/pi/Documents/DreamHack/CMakeFiles /home/pi/Documents/DreamHack/CMakeFiles/progress.marks + $(MAKE) -f CMakeFiles/Makefile2 all + $(CMAKE_COMMAND) -E cmake_progress_start /home/pi/Documents/DreamHack/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + $(MAKE) -f CMakeFiles/Makefile2 clean +.PHONY : clean + +# The main clean target +clean/fast: clean + +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + $(MAKE) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + $(MAKE) -f CMakeFiles/Makefile2 preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +#============================================================================= +# Target rules for targets named cpptimer + +# Build rule for target. +cpptimer: cmake_check_build_system + $(MAKE) -f CMakeFiles/Makefile2 cpptimer +.PHONY : cpptimer + +# fast build rule for target. +cpptimer/fast: + $(MAKE) -f CMakeFiles/cpptimer.dir/build.make CMakeFiles/cpptimer.dir/build +.PHONY : cpptimer/fast + +#============================================================================= +# Target rules for targets named main + +# Build rule for target. +main: cmake_check_build_system + $(MAKE) -f CMakeFiles/Makefile2 main +.PHONY : main + +# fast build rule for target. +main/fast: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/build +.PHONY : main/fast + +#============================================================================= +# Target rules for targets named main_autogen + +# Build rule for target. +main_autogen: cmake_check_build_system + $(MAKE) -f CMakeFiles/Makefile2 main_autogen +.PHONY : main_autogen + +# fast build rule for target. +main_autogen/fast: + $(MAKE) -f CMakeFiles/main_autogen.dir/build.make CMakeFiles/main_autogen.dir/build +.PHONY : main_autogen/fast + +CppTimer.o: CppTimer.cpp.o + +.PHONY : CppTimer.o + +# target to build an object file +CppTimer.cpp.o: + $(MAKE) -f CMakeFiles/cpptimer.dir/build.make CMakeFiles/cpptimer.dir/CppTimer.cpp.o + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/CppTimer.cpp.o +.PHONY : CppTimer.cpp.o + +CppTimer.i: CppTimer.cpp.i + +.PHONY : CppTimer.i + +# target to preprocess a source file +CppTimer.cpp.i: + $(MAKE) -f CMakeFiles/cpptimer.dir/build.make CMakeFiles/cpptimer.dir/CppTimer.cpp.i + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/CppTimer.cpp.i +.PHONY : CppTimer.cpp.i + +CppTimer.s: CppTimer.cpp.s + +.PHONY : CppTimer.s + +# target to generate assembly for a file +CppTimer.cpp.s: + $(MAKE) -f CMakeFiles/cpptimer.dir/build.make CMakeFiles/cpptimer.dir/CppTimer.cpp.s + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/CppTimer.cpp.s +.PHONY : CppTimer.cpp.s + +main.o: main.cpp.o + +.PHONY : main.o + +# target to build an object file +main.cpp.o: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/main.cpp.o +.PHONY : main.cpp.o + +main.i: main.cpp.i + +.PHONY : main.i + +# target to preprocess a source file +main.cpp.i: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/main.cpp.i +.PHONY : main.cpp.i + +main.s: main.cpp.s + +.PHONY : main.s + +# target to generate assembly for a file +main.cpp.s: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/main.cpp.s +.PHONY : main.cpp.s + +main_autogen/mocs_compilation.o: main_autogen/mocs_compilation.cpp.o + +.PHONY : main_autogen/mocs_compilation.o + +# target to build an object file +main_autogen/mocs_compilation.cpp.o: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/main_autogen/mocs_compilation.cpp.o +.PHONY : main_autogen/mocs_compilation.cpp.o + +main_autogen/mocs_compilation.i: main_autogen/mocs_compilation.cpp.i + +.PHONY : main_autogen/mocs_compilation.i + +# target to preprocess a source file +main_autogen/mocs_compilation.cpp.i: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/main_autogen/mocs_compilation.cpp.i +.PHONY : main_autogen/mocs_compilation.cpp.i + +main_autogen/mocs_compilation.s: main_autogen/mocs_compilation.cpp.s + +.PHONY : main_autogen/mocs_compilation.s + +# target to generate assembly for a file +main_autogen/mocs_compilation.cpp.s: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/main_autogen/mocs_compilation.cpp.s +.PHONY : main_autogen/mocs_compilation.cpp.s + +mainwindow.o: mainwindow.cpp.o + +.PHONY : mainwindow.o + +# target to build an object file +mainwindow.cpp.o: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/mainwindow.cpp.o +.PHONY : mainwindow.cpp.o + +mainwindow.i: mainwindow.cpp.i + +.PHONY : mainwindow.i + +# target to preprocess a source file +mainwindow.cpp.i: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/mainwindow.cpp.i +.PHONY : mainwindow.cpp.i + +mainwindow.s: mainwindow.cpp.s + +.PHONY : mainwindow.s + +# target to generate assembly for a file +mainwindow.cpp.s: + $(MAKE) -f CMakeFiles/main.dir/build.make CMakeFiles/main.dir/mainwindow.cpp.s +.PHONY : mainwindow.cpp.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... rebuild_cache" + @echo "... edit_cache" + @echo "... cpptimer" + @echo "... main" + @echo "... main_autogen" + @echo "... CppTimer.o" + @echo "... CppTimer.i" + @echo "... CppTimer.s" + @echo "... main.o" + @echo "... main.i" + @echo "... main.s" + @echo "... main_autogen/mocs_compilation.o" + @echo "... main_autogen/mocs_compilation.i" + @echo "... main_autogen/mocs_compilation.s" + @echo "... mainwindow.o" + @echo "... mainwindow.i" + @echo "... mainwindow.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/PulseSensor.h b/PulseSensor.h new file mode 100644 index 0000000..d83cf82 --- /dev/null +++ b/PulseSensor.h @@ -0,0 +1,370 @@ +#include "CppTimer.h" + + +// MCP3004/8 SETTINGS +#define BASE 100 +#define SPI_CHAN 0 + +/** + * Callback for new samples which needs to be implemented by the main program. + * The function hasSample needs to be overloaded in the main program. + **/ +class SensorCallback { +public: + /** + * Called after a sample has arrived. + **/ + virtual void hasSample(int beats, bool mayBeSleep) = 0; +}; + + +class SensorTimer : public CppTimer { + private: + SensorCallback* sensorCallback = nullptr; + private: + unsigned int eventCounter, thisTime, lastTime, elapsedTime, jitter; + int sampleFlag = 0; + int sumJitter, firstTime, secondTime, duration; + int timeOutStart, dataRequestStart, m; + int Signal; + unsigned int sampleCounter; + int threshSetting,lastBeatTime; + int thresh = 550; + int P = 512; // set P default + int T = 512; // set T default + int firstBeat = 1; // set these to avoid noise + int secondBeat = 0; // when we get the heartbeat back + int QS = 0; + int rate[10]; + int BPM = 0; + int IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) + int Pulse = 0; + int amp = 100; // beat amplitude 1/10 of input range. + int call_time_period = 2000; //in microseconds 2 milli s + /** + * Variables to analyze sleep in sleep detection + **/ + time_t nightTime; + time_t wakeTime; + int bpmThreshold = 77; + bool maybeSleep = 0; + time_t surelySleptTime = 2; + bool sleep; + time_t startOfProspectiveSleep; + bool is_audio_playing; + bool play_audio_locally; + pid_t audio_pid; + char audio_name[500]; + bool is_simulation = 1; + public: + SensorTimer(); + void setCallback(SensorCallback* cb); + void timerEvent(); + void initPulseSensorVariables(void); + void getPulse(void); + bool analyzeBeatsForSleep(int bpm); + void initializeVariablesForSleep(void); + void audioprocess(); + pid_t play_audio(char* audio_name); + void kill_the_pid(pid_t x); + void beatsPerMinuteSimulation(); + time_t start_of_simulation; + bool simulation_started = 0; +}; + +SensorTimer::SensorTimer(){ + initPulseSensorVariables(); + initializeVariablesForSleep(); + FILE *fptr; + if ((fptr = fopen("audio.txt", "r")) == NULL) { + printf("Error! opening audio.txt"); + play_audio_locally = 0; + }else{ + fscanf(fptr,"%s", audio_name); + fclose(fptr); + play_audio_locally = 1; + } +} +void SensorTimer::audioprocess(){ + if(sleep == 1 && is_audio_playing == 0 && play_audio_locally){ + audio_pid = play_audio(audio_name); + /* + * If child process then the audio is already ended, pid must kill itself safely + */ + if(audio_pid == 0){ + raise(SIGKILL); + }else{ + /* + * If parent process then continue and put the is_audio_playing flag to be true + */ + is_audio_playing = 1; + printf("Audio pid is %d\n", audio_pid); + } + } + else if(is_audio_playing == 1 && sleep == 0 && play_audio_locally){ + /* + * When the person wakes up again kill the pid which runs the audio + */ + if(audio_pid > 0){ + printf("Kill Reached Here\n"); + kill_the_pid(audio_pid); + is_audio_playing = 0; + } + } +} +pid_t SensorTimer::play_audio(char* audio_name){ + pid_t audio_pid_local; + if(0 == (audio_pid_local = fork())){ + //child process + //printf("reached here\n"); + printf("Reached Here pid is %d\n", audio_pid_local); + execlp("mpg123", "mpg123", "-q", audio_name, 0); + is_audio_playing = 1; + } + return audio_pid_local; +} + +void SensorTimer::kill_the_pid(pid_t x){ + char kil[100] = "kill -9 "; + sprintf(kil,"%s%d",kil, x); + system(kil); +} + +/** + * Sets the callback which is called whenever there is a sample + **/ +void SensorTimer::setCallback(SensorCallback* cb) { + printf("pointer: %p\n", cb); + sensorCallback = cb; +} + +void SensorTimer::timerEvent() { + getPulse(); + if(is_simulation) + beatsPerMinuteSimulation(); + sleep = analyzeBeatsForSleep(BPM); + printf("Value is: %d\n", BPM); + audioprocess(); + printf("BPM is: %d\n", BPM); + printf("Sleep is: %d\n", sleep); + if (nullptr != sensorCallback) { + sensorCallback->hasSample(BPM, sleep); + } + fflush(stdout); +} + +void SensorTimer::beatsPerMinuteSimulation(){ + if(simulation_started == 0){ + start_of_simulation = time(NULL); + simulation_started = 1; + } + int begin_bpm = 80; + int end_bpm = 60; + time_t durationOfSimulation = 60*1; //5 minute duration + float slope = (float)(end_bpm - begin_bpm)/(float)(durationOfSimulation/2); + int t = time(NULL) - start_of_simulation; + //Downwards + int sim_bpm = begin_bpm; + //printf("slope is %f\n",slope); + if(t < durationOfSimulation/2) + sim_bpm = (int)(slope*t) + begin_bpm; + //Upwards + else if(t >= durationOfSimulation/2 && t <= durationOfSimulation){ + t = t - (durationOfSimulation/2); + sim_bpm = -1 * (int)(slope*t) + end_bpm; + } + BPM = sim_bpm; + + + + +} + +void SensorTimer::initPulseSensorVariables(void){ + + wiringPiSetup(); //use the wiringPi pin numbers + mcp3004Setup(BASE,SPI_CHAN); // setup the mcp3004 library + //pinMode(BLINK_LED, OUTPUT); digitalWrite(BLINK_LED,LOW); + + + for (int i = 0; i < 10; ++i) { + rate[i] = 0; + } + QS = 0; + BPM = 0; + IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) + Pulse = 0; + sampleCounter = 0; + lastBeatTime = 0; + P = 512; // peak at 1/2 the input range of 0..1023 + T = 512; // trough at 1/2 the input range. + threshSetting = 550; // used to seed and reset the thresh variable + thresh = 550; // threshold a little above the trough + amp = 100; // beat amplitude 1/10 of input range. + firstBeat = 1; // looking for the first beat + secondBeat = 0; // not yet looking for the second beat in a row + lastTime = micros(); + timeOutStart = lastTime; + call_time_period = 2000; //in microseconds 2 milli s +} + +void SensorTimer::getPulse(void){ + + thisTime = micros(); + Signal = analogRead(BASE); + elapsedTime = thisTime - lastTime; + lastTime = thisTime; + //jitter = elapsedTime - OPT_U; + jitter = elapsedTime - call_time_period; + sumJitter += jitter; + sampleFlag = 1; + + + sampleCounter += 2; // keep track of the time in mS with this variable + int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise + + // FADE LED HERE, IF WE COULD FADE... + + // find the peak and trough of the pulse wave + if (Signal < thresh && N > (IBI / 5) * 3) { // avoid dichrotic noise by waiting 3/5 of last IBI + if (Signal < T) { // T is the trough + T = Signal; // keep track of lowest point in pulse wave + } + } + + if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise + P = Signal; // P is the peak + } // keep track of highest point in pulse wave + + // NOW IT'S TIME TO LOOK FOR THE HEART BEAT + // signal surges up in value every time there is a pulse + if (N > 250) { // avoid high frequency noise + if ( (Signal > thresh) && (Pulse == 0) && (N > ((IBI / 5) * 3)) ) { + Pulse = 1; // set the Pulse flag when we think there is a pulse + IBI = sampleCounter - lastBeatTime; // measure time between beats in mS + lastBeatTime = sampleCounter; // keep track of time for next pulse + + if (secondBeat) { // if this is the second beat, if secondBeat == TRUE + secondBeat = 0; // clear secondBeat flag + for (int i = 0; i <= 9; i++) { // seed the running total to get a realisitic BPM at startup + rate[i] = IBI; + } + } + + if (firstBeat) { // if it's the first time we found a beat, if firstBeat == TRUE + firstBeat = 0; // clear firstBeat flag + secondBeat = 1; // set the second beat flag + // IBI value is unreliable so discard it + return; + } + + + // keep a running total of the last 10 IBI values + int runningTotal = 0; // clear the runningTotal variable + + for (int i = 0; i <= 8; i++) { // shift data in the rate array + rate[i] = rate[i + 1]; // and drop the oldest IBI value + runningTotal += rate[i]; // add up the 9 oldest IBI values + } + + rate[9] = IBI; // add the latest IBI to the rate array + runningTotal += rate[9]; // add the latest IBI to runningTotal + runningTotal /= 10; // average the last 10 IBI values + BPM = 60000 / runningTotal; // how many beats can fit into a minute? that's BPM! + QS = 1; // set Quantified Self flag (we detected a beat) + //fadeLevel = MAX_FADE_LEVEL; // If we're fading, re-light that LED. + } + } + + if (Signal < thresh && Pulse == 1) { // when the values are going down, the beat is over + Pulse = 0; // reset the Pulse flag so we can do it again + amp = P - T; // get amplitude of the pulse wave + thresh = amp / 2 + T; // set thresh at 50% of the amplitude + P = thresh; // reset these for next time + T = thresh; + } + + if (N > 2500) { // if 2.5 seconds go by without a beat + thresh = threshSetting; // set thresh default + P = 512; // set P default + T = 512; // set T default + lastBeatTime = sampleCounter; // bring the lastBeatTime up to date + firstBeat = 1; // set these to avoid noise + secondBeat = 0; // when we get the heartbeat back + QS = 0; + BPM = 0; + IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) + Pulse = 0; + amp = 100; // beat amplitude 1/10 of input range. + + } + + duration = micros()-thisTime; +} +void SensorTimer::initializeVariablesForSleep(void){ + time_t current_time = time(NULL); + struct tm *time_split = localtime(¤t_time); + struct tm night_time_split = {0, 0, 22, time_split->tm_mday, + time_split->tm_mon, time_split->tm_year, time_split->tm_wday, time_split->tm_yday, time_split->tm_isdst}; + nightTime = mktime(&night_time_split); + if(is_simulation) + nightTime = time(NULL) - 60; + time_t estimatedSleepLength = 60*60*8; + wakeTime = nightTime + estimatedSleepLength; +} + +/** +* Function to analyze sleep based on beats +* per minute. +**/ +bool SensorTimer::analyzeBeatsForSleep(int bpm) +{ + bool sleep_local = 0; + time_t maybeSleepTime; + /* + * if BPM is below a certain threshold + * mayBeSleep should be on + * if mayBeSleep is on for a while + * then return 1; + */ + time_t now = time(NULL); + //printf("BPM is %d, now is %d, nightTime is %d wakeTime is %d\n", BPM, now, nightTime, wakeTime); + if (now > nightTime && now < wakeTime) { + if (bpm < bpmThreshold && maybeSleep == 0) { + printf("maybesleep Happened\n"); + startOfProspectiveSleep = time(NULL); + maybeSleep = 1; + } + if (bpm < bpmThreshold && maybeSleep == 1) { + maybeSleepTime = time(NULL) - startOfProspectiveSleep; + if (maybeSleepTime > surelySleptTime) { + sleep_local = 1; + } + } + if (bpm > bpmThreshold && maybeSleep == 1) { + maybeSleep = 0; + } + } + else { + sleep_local = 0; + } +// if(bpm < 77) +// sleep_local = 1; + return sleep_local; +} + + + + +class SenseWindow : public MainWindow{ + private: + int Signal; + public: + SenseWindow(){ + } + void timerEvent( QTimerEvent * ) { + Signal = analogRead(BASE); + addRealtimeSample(Signal); + callPlot(); + } +}; diff --git a/UI/code/index.php b/UI/code/index.php index 304355f..47e2c0b 100644 --- a/UI/code/index.php +++ b/UI/code/index.php @@ -2,6 +2,14 @@ define("ROOTPATH", 'C:/Apache24/htdocs/dreamHacker'); //include ROOTPATH . '/database/db.php'; session_start(); + + if($$_GET) { + $epoch = $_GET["epoch"]; + $bpm = $_GET["beats"]; + $sleep = $_GET["sleep"]; + + } + ?> @@ -68,6 +76,20 @@
  • View Histroy
  • +
    + + + + + + + + + + + +
    EpochBeatsSleep
    +
    \ No newline at end of file diff --git a/audio.txt b/audio.txt new file mode 100644 index 0000000..d6960b6 --- /dev/null +++ b/audio.txt @@ -0,0 +1 @@ +Audio/fixyou.mp3 diff --git a/cpywww.sh b/cpywww.sh new file mode 100755 index 0000000..cc44de3 --- /dev/null +++ b/cpywww.sh @@ -0,0 +1,2 @@ +#!/usr/bash +sudo cp UI /var/www/html/. diff --git a/json_fastcgi_web_api.h b/json_fastcgi_web_api.h new file mode 100644 index 0000000..80e94a1 --- /dev/null +++ b/json_fastcgi_web_api.h @@ -0,0 +1,292 @@ +#ifndef FAST_CGI_H +#define FAST_CGI_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * C++ wrapper around fastCGI which sends and receives JSON + * in a jQuery friendly format. + * + * Copyright (C) 2021 Bernd Porr + * Apache License 2.0 + **/ +class JSONCGIHandler { +public: + /** + * GET callback handler which needs to be implemented by the main + * program. + * This needs to provide the JSON payload as a string either by using + * the simple JSONGenerator or by an external library. + **/ + class GETCallback { + public: + /** + * Needs to return the payload data sent to the web browser. + * Use the JSONGenerator to create the JSON or use an + * external json generator. + * \return JSON data + **/ + virtual std::string getJSONString() = 0; + /** + * The content type of the payload. That's by default + * "application/json" but can be overloaded if needed. + * \return MIME type + **/ + virtual std::string getContentType() { return "application/json"; } + }; + + + /** + * Callback handler which needs to be implemented by the main + * program. + **/ + class POSTCallback { + public: + /** + * Receives the POST data from the web browser. + * Use postDecoder() to decode the postArg string. + * \param postArg POST data received from jQuery + **/ + virtual void postString(std::string postArg) = 0; + }; + + + /** + * Simple helper function to create a key/value json pairs + * for the callback function. + **/ + class JSONGenerator { + public: + /** + * Adds a JSON entry: string + * \param key The JSON key + * \param value The JSON value as a string + **/ + void add(std::string key, std::string value) { + if (!firstEntry) { + json = json + ", "; + } + json = json + "\"" + key + "\":"; + json = json + "\"" + value + "\""; + firstEntry = 0; + } + + /** + * Adds a JSON entry: double + * \param key The JSON key + * \param value The JSON value as a double + **/ + void add(std::string key, double value) { + if (!firstEntry) { + json = json + ", "; + } + json = json + "\"" + key + "\":"; + json = json + std::to_string(value); + firstEntry = 0; + } + + /** + * Adds a JSON entry: float + * \param key The JSON key + * \param value The JSON value as a float + **/ + void add(std::string key, float value) { + add(key, (double)value); + } + + /** + * Adds a JSON entry: long int + * \param key The JSON key + * \param value The JSON value as a long int + **/ + void add(std::string key, long value) { + if (!firstEntry) { + json = json + ", "; + } + json = json + "\"" + key + "\":"; + json = json + std::to_string(value); + firstEntry = 0; + } + + /** + * Adds a JSON entry: int + * \param key The JSON key + * \param value The JSON value as an int + **/ + void add(std::string key, int value) { + add(key, (long)value); + } + + /** + * Gets the json string + * \return The JSON data ready to be sent + **/ + std::string getJSON() { return json + "}"; } + + private: + std::string json = "{"; + int firstEntry = 1; + }; + + +public: + /** + * Parses a POST string and returns a std::map with key/value pairs. + * It also converts back any %xx style encoding back to chars using + * libcurl. + * Note this is a simple parser and it won't deal with nested + * JSON structures. + * \param s The POST string to be decoded. + * \return A std::map which conains the key/value pairs. + **/ + static std::map postDecoder(std::string s) { + std::map postMap; + CURL *curl = curl_easy_init(); + if (NULL == curl) { + std::cerr << "Could not init curl.\n"; + return postMap; + } + size_t pos = 0; + while (1) { + std::string token; + pos = s.find("&"); + if (pos == std::string::npos) { + token = s; + } else { + token = s.substr(0, pos); + } + size_t pos2 = token.find("="); + if (pos2 != std::string::npos) { + std::string key = token.substr(0,pos2); + std::string value = token.substr(pos2+1,token.length()); + char* valueDecoded = curl_easy_unescape( curl, value.c_str(), value.length(), NULL ); + if (NULL != valueDecoded) { + for(int i = 0; i < strlen(valueDecoded); i++) { + if (valueDecoded[i] == '+') valueDecoded[i] = ' '; + } + postMap[key] = valueDecoded; + curl_free(valueDecoded); + } + } + if (pos == std::string::npos) break; + s.erase(0, pos + 1); + } + curl_easy_cleanup(curl); + return postMap; + } + + + /** + * Constructor which opens the connection and starts the main thread. + * Provide an instance of the callback handler which returns the + * payload data. argPostCallback is the callback which returns + * received json packets as a map. The optional socketpath variable + * can be set to another path for the socket which talks to the + * webserver. + * \param argGetCallback Callback handler for sending JSON + * \param argPostCallback Callback handler for receiving JSON + * \param socketpath Path of the socket which communicates to the webserver + **/ + JSONCGIHandler(GETCallback* argGetCallback, + POSTCallback* argPostCallback = nullptr, + const char socketpath[] = "/tmp/fastcgisocket") { + getCallback = argGetCallback; + postCallback = argPostCallback; + int r = curl_global_init(CURL_GLOBAL_NOTHING); + if (r) { + std::cerr << "Curl init error: " << r << "\n"; + } + // set it to zero + memset(&request, 0, sizeof(FCGX_Request)); + // init the connection + FCGX_Init(); + // open the socket + sock_fd = FCGX_OpenSocket(socketpath, 1024); + // making sure the nginx process can read/write to it + chmod(socketpath, S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR|S_IWGRP|S_IWOTH); + // init requests so that we can accept requests + FCGX_InitRequest(&request, sock_fd, 0); + // starting main loop + mainThread = new std::thread(JSONCGIHandler::exec, this); + } + + /** + * Destructor which shuts down the connection to the webserver and + * it also terminates the thread which is waiting for requests. + **/ + ~JSONCGIHandler() { + running = 0; + shutdown(sock_fd, SHUT_RDWR); + mainThread->join(); + delete mainThread; + FCGX_Free(&request, sock_fd); + } + + private: + static void exec(JSONCGIHandler* fastCGIHandler) { + while ((fastCGIHandler->running) && (FCGX_Accept_r(&(fastCGIHandler->request)) == 0)) { + char * method = FCGX_GetParam("REQUEST_METHOD", fastCGIHandler->request.envp); + if (method == nullptr) { + fprintf(stderr,"Please add 'include fastcgi_params;' to the nginx conf.\n"); + throw "JSONCGI parameters missing.\n"; + } + if (strcmp(method, "GET") == 0) { + // create the header + std::string buffer = "Content-type: "+fastCGIHandler->getCallback->getContentType(); + buffer = buffer + "; charset=utf-8\r\n"; + buffer = buffer + "\r\n"; + // append the data + buffer = buffer + fastCGIHandler->getCallback->getJSONString(); + buffer = buffer + "\r\n"; + // send the data to the web server + FCGX_PutStr(buffer.c_str(), buffer.length(), fastCGIHandler->request.out); + FCGX_Finish_r(&(fastCGIHandler->request)); + } + if (strcmp(method, "POST") == 0) { + long reqLen = 1; + char * content_length_str = FCGX_GetParam("CONTENT_LENGTH", + fastCGIHandler->request.envp); + if (content_length_str) reqLen = atol(content_length_str)+1; + char* tmp = new char[reqLen]; + FCGX_GetStr(tmp,reqLen,fastCGIHandler->request.in); + tmp[reqLen - 1] = 0; + if (nullptr != fastCGIHandler->postCallback) { + fastCGIHandler->postCallback->postString(tmp); + } + delete[] tmp; + // create the header + std::string buffer = "Content-type: text/html"; + buffer = buffer + "; charset=utf-8\r\n"; + buffer = buffer + "\r\n"; + // append the data + buffer = buffer + "\r\n"; + buffer = buffer + "\r\n"; + // send the data to the web server + FCGX_PutStr(buffer.c_str(), buffer.length(), fastCGIHandler->request.out); + FCGX_Finish_r(&(fastCGIHandler->request)); + } + } + } + + private: + FCGX_Request request; + int sock_fd = 0; + int running = 1; + std::thread* mainThread = nullptr; + GETCallback* getCallback = nullptr; + POSTCallback* postCallback = nullptr; +}; + +#endif diff --git a/libcpptimer.a b/libcpptimer.a new file mode 100644 index 0000000..317ce6a Binary files /dev/null and b/libcpptimer.a differ diff --git a/main b/main new file mode 100755 index 0000000..1499c73 Binary files /dev/null and b/main differ diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..216f8d7 --- /dev/null +++ b/main.cpp @@ -0,0 +1,119 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "mainwindow.h" +#include "CppTimer.h" +#include +#include "PulseSensor.h" +#include "json_fastcgi_web_api.h" + + +/** + * Handler which receives the data here just saves + * the most recent sample with timestamp. Obviously, + * in a real application the data would be stored + * in a database and/or triggers events and other things! + **/ +class SENSORfastcgicallback : public SensorCallback { +public: + int beatsPerMinute; + bool sleep; + long t; + + /** + * Callback with the fresh ADC data. + * That's where all the internal processing + * of the data is happening. + **/ + virtual void hasSample(int beats, bool mayBeSleep) { + sleep = mayBeSleep; + beatsPerMinute = beats; + t = time(NULL); + } + +}; + +/** + * Callback handler which returns data to the + * nginx server. Here, simply the current timestamp, sleeping possibility + * and the beats per minute is transmitted to nginx and the + * php application. + **/ +class JSONCGIADCCallback : public JSONCGIHandler::GETCallback { +private: + /** + * Pointer to the ADC event handler because it keeps + * the data in this case. In a proper application + * that would be probably a database class or a + * controller keeping it all together. + **/ + SENSORfastcgicallback* sensorfastcgi; + +public: + /** + * Constructor: argument is the ADC callback handler + * which keeps the data as a simple example. + **/ + JSONCGIADCCallback(SENSORfastcgicallback* argSENSORfastcgi) { + sensorfastcgi = argSENSORfastcgi; + } + + /** + * Gets the data sends it to the webserver. + * The callback creates three json entries. One with the + * timestamp, one with the beats and one with the + * sleep possibility from the sensor. + **/ + virtual std::string getJSONString() + { + JSONCGIHandler::JSONGenerator jsonGenerator; + jsonGenerator.add("epoch", (long)time(NULL)); + jsonGenerator.add("beats", sensorfastcgi->beatsPerMinute); + jsonGenerator.add("sleep", sensorfastcgi->sleep); + return jsonGenerator.getJSON(); + } +}; + + + + +int main(int argc, char *argv[]) { + SensorTimer pulseMe; + SENSORfastcgicallback sensorfastcgicallback; + pulseMe.setCallback(&sensorfastcgicallback); + + // Setting up the JSONCGI communication + // The callback which is called when fastCGI needs data + // gets a pointer to the SENSOR callback class which + // contains the samples. Remember this is just a simple + // example to have access to some data. + JSONCGIADCCallback fastCGIADCCallback(&sensorfastcgicallback); + + + // starting the fastCGI handler with the callback and the + // socket for nginx. + JSONCGIHandler* fastCGIHandler = new JSONCGIHandler(&fastCGIADCCallback, NULL, "/tmp/sensorsocket"); + + pulseMe.startms(2); + QApplication a(argc, argv); + SenseWindow w; + w.showMaximized(); + a.exec(); + pulseMe.stop(); + + + return 0; + +}//int main(int argc, char *argv[]) + + diff --git a/mainplot.cpp b/mainplot.cpp new file mode 100644 index 0000000..c93d7ea --- /dev/null +++ b/mainplot.cpp @@ -0,0 +1,14 @@ +/* + * For reference + */ +#include +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.showMaximized(); + + return a.exec(); +} diff --git a/mainwindow.cpp b/mainwindow.cpp new file mode 100644 index 0000000..7459f24 --- /dev/null +++ b/mainwindow.cpp @@ -0,0 +1,340 @@ +#include "mainwindow.h" + +const char instructionsTxt[] = + "\n" + "\n" + "

    Select the axes to drag and zoom them individually.

    \n" + "

    Double click labels or legend items to set user specified strings.

    \n" + "

    Left click on graphs or legend to select graphs.

    \n" + "

    Right click for a popup menu to add/remove graphs and move the legend

    "; + +const char titleTxt[] = "QCustomPlot Dream Hacker Pulse Measurement"; + +MainWindow::MainWindow(QWidget *parent) : + QMainWindow(parent) { + srand(QDateTime::currentDateTime().toTime_t()); + + // Layout + centralWidget = new QWidget(); + verticalLayout = new QVBoxLayout(centralWidget); + verticalLayout->setSpacing(6); + verticalLayout->setContentsMargins(11, 11, 11, 11); + frame_2 = new QFrame(centralWidget); + frame_2->setFrameShape(QFrame::StyledPanel); + frame_2->setFrameShadow(QFrame::Sunken); + frame_2->setLineWidth(1); + frame_2->setMidLineWidth(0); + verticalLayout_3 = new QVBoxLayout(frame_2); + verticalLayout_3->setSpacing(0); + verticalLayout_3->setContentsMargins(0, 0, 0, 0); + customPlot = new QCustomPlot(frame_2); + QSizePolicy sizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding); + sizePolicy.setHorizontalStretch(0); + sizePolicy.setVerticalStretch(0); + sizePolicy.setHeightForWidth(customPlot->sizePolicy().hasHeightForWidth()); + customPlot->setSizePolicy(sizePolicy); + verticalLayout_3->addWidget(customPlot); + verticalLayout->addWidget(frame_2); + frame = new QFrame(centralWidget); + frame->setFrameShape(QFrame::StyledPanel); + frame->setFrameShadow(QFrame::Raised); + verticalLayout_2 = new QVBoxLayout(frame); + verticalLayout_2->setSpacing(6); + verticalLayout_2->setContentsMargins(11, 11, 11, 11); + label = new QLabel(frame); + verticalLayout_2->addWidget(label); + verticalLayout->addWidget(frame); + setCentralWidget(centralWidget); + statusBar = new QStatusBar(); + setStatusBar(statusBar); + + customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectAxes | + QCP::iSelectLegend | QCP::iSelectPlottables); + customPlot->xAxis->setRange(-1, 8); + //Shreyansh input y range + //customPlot->yAxis->setRange(-5, 5); + customPlot->yAxis->setRange(400, 600); + customPlot->axisRect()->setupFullAxesBox(); + + customPlot->plotLayout()->insertRow(0); + QCPTextElement *title = new QCPTextElement(customPlot, titleTxt, QFont("sans", 17, QFont::Bold)); + customPlot->plotLayout()->addElement(0, 0, title); + + customPlot->xAxis->setLabel("x Axis"); + customPlot->yAxis->setLabel("y Axis"); + customPlot->legend->setVisible(true); + QFont legendFont = font(); + legendFont.setPointSize(10); + customPlot->legend->setFont(legendFont); + customPlot->legend->setSelectedFont(legendFont); + customPlot->legend->setSelectableParts(QCPLegend::spItems); // legend box shall not be selectable, only legend items + + addRealtimeGraph(); + //addRandomGraph(); + //addRandomGraph(); + + // connect slot that ties some axis selections together (especially opposite axes): + connect(customPlot, SIGNAL(selectionChangedByUser()), this, SLOT(selectionChanged())); + // connect slots that takes care that when an axis is selected, only that direction can be dragged and zoomed: + connect(customPlot, SIGNAL(mousePress(QMouseEvent*)), this, SLOT(mousePress())); + connect(customPlot, SIGNAL(mouseWheel(QWheelEvent*)), this, SLOT(mouseWheel())); + + // make bottom and left axes transfer their ranges to top and right axes: + connect(customPlot->xAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->xAxis2, SLOT(setRange(QCPRange))); + connect(customPlot->yAxis, SIGNAL(rangeChanged(QCPRange)), customPlot->yAxis2, SLOT(setRange(QCPRange))); + + // connect some interaction slots: + connect(customPlot, SIGNAL(axisDoubleClick(QCPAxis*,QCPAxis::SelectablePart,QMouseEvent*)), this, SLOT(axisLabelDoubleClick(QCPAxis*,QCPAxis::SelectablePart))); + connect(customPlot, SIGNAL(legendDoubleClick(QCPLegend*,QCPAbstractLegendItem*,QMouseEvent*)), this, SLOT(legendDoubleClick(QCPLegend*,QCPAbstractLegendItem*))); + connect(title, SIGNAL(doubleClicked(QMouseEvent*)), this, SLOT(titleDoubleClick(QMouseEvent*))); + + // connect slot that shows a message in the status bar when a graph is clicked: + connect(customPlot, SIGNAL(plottableClick(QCPAbstractPlottable*,int,QMouseEvent*)), this, SLOT(graphClicked(QCPAbstractPlottable*,int))); + + // setup policy and connect slot for context menu popup: + customPlot->setContextMenuPolicy(Qt::CustomContextMenu); + connect(customPlot, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextMenuRequest(QPoint))); + + setWindowTitle(titleTxt); + label->setText(instructionsTxt); +} + +void MainWindow::titleDoubleClick(QMouseEvent* event) { + Q_UNUSED(event) + if (QCPTextElement *title = qobject_cast(sender())) + { + // Set the plot title by double clicking on it + bool ok; + QString newTitle = QInputDialog::getText(this, "QCustomPlot example", "New plot title:", QLineEdit::Normal, title->text(), &ok); + if (ok) + { + title->setText(newTitle); + customPlot->replot(); + } + } +} +void MainWindow::callPlot(){ + customPlot->replot(); +} + +void MainWindow::axisLabelDoubleClick(QCPAxis *axis, QCPAxis::SelectablePart part) { + // Set an axis label by double clicking on it + if (part == QCPAxis::spAxisLabel) // only react when the actual axis label is clicked, not tick label or axis backbone + { + bool ok; + QString newLabel = QInputDialog::getText(this, "QCustomPlot example", "New axis label:", QLineEdit::Normal, axis->label(), &ok); + if (ok) + { + axis->setLabel(newLabel); + customPlot->replot(); + } + } +} + +void MainWindow::legendDoubleClick(QCPLegend *legend, QCPAbstractLegendItem *item) { + // Rename a graph by double clicking on its legend item + Q_UNUSED(legend) + if (item) // only react if item was clicked (user could have clicked on border padding of legend where there is no item, then item is 0) + { + QCPPlottableLegendItem *plItem = qobject_cast(item); + bool ok; + QString newName = QInputDialog::getText(this, "QCustomPlot example", "New graph name:", QLineEdit::Normal, plItem->plottable()->name(), &ok); + if (ok) + { + plItem->plottable()->setName(newName); + customPlot->replot(); + } + } +} + +void MainWindow::selectionChanged() { + /* + normally, axis base line, axis tick labels and axis labels are selectable separately, but we want + the user only to be able to select the axis as a whole, so we tie the selected states of the tick labels + and the axis base line together. However, the axis label shall be selectable individually. + + The selection state of the left and right axes shall be synchronized as well as the state of the + bottom and top axes. + + Further, we want to synchronize the selection of the graphs with the selection state of the respective + legend item belonging to that graph. So the user can select a graph by either clicking on the graph itself + or on its legend item. + */ + + // make top and bottom axes be selected synchronously, and handle axis and tick labels as one selectable object: + if (customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis) || customPlot->xAxis->selectedParts().testFlag(QCPAxis::spTickLabels) || + customPlot->xAxis2->selectedParts().testFlag(QCPAxis::spAxis) || customPlot->xAxis2->selectedParts().testFlag(QCPAxis::spTickLabels)) + { + customPlot->xAxis2->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); + customPlot->xAxis->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); + } + // make left and right axes be selected synchronously, and handle axis and tick labels as one selectable object: + if (customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis) || customPlot->yAxis->selectedParts().testFlag(QCPAxis::spTickLabels) || + customPlot->yAxis2->selectedParts().testFlag(QCPAxis::spAxis) || customPlot->yAxis2->selectedParts().testFlag(QCPAxis::spTickLabels)) + { + customPlot->yAxis2->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); + customPlot->yAxis->setSelectedParts(QCPAxis::spAxis|QCPAxis::spTickLabels); + } + + // synchronize selection of graphs with selection of corresponding legend items: + for (int i=0; igraphCount(); ++i) + { + QCPGraph *graph = customPlot->graph(i); + QCPPlottableLegendItem *item = customPlot->legend->itemWithPlottable(graph); + if (item->selected() || graph->selected()) + { + item->setSelected(true); + graph->setSelection(QCPDataSelection(graph->data()->dataRange())); + } + } +} + +void MainWindow::mousePress() { + // if an axis is selected, only allow the direction of that axis to be dragged + // if no axis is selected, both directions may be dragged + + if (customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) + customPlot->axisRect()->setRangeDrag(customPlot->xAxis->orientation()); + else if (customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) + customPlot->axisRect()->setRangeDrag(customPlot->yAxis->orientation()); + else + customPlot->axisRect()->setRangeDrag(Qt::Horizontal|Qt::Vertical); +} + +void MainWindow::mouseWheel() { + // if an axis is selected, only allow the direction of that axis to be zoomed + // if no axis is selected, both directions may be zoomed + + if (customPlot->xAxis->selectedParts().testFlag(QCPAxis::spAxis)) + customPlot->axisRect()->setRangeZoom(customPlot->xAxis->orientation()); + else if (customPlot->yAxis->selectedParts().testFlag(QCPAxis::spAxis)) + customPlot->axisRect()->setRangeZoom(customPlot->yAxis->orientation()); + else + customPlot->axisRect()->setRangeZoom(Qt::Horizontal|Qt::Vertical); +} + +void MainWindow::addRealtimeGraph() { + customPlot->addGraph(); + customPlot->graph()->setName(QString("Realtime")); + animdata.reset(new QCPDataContainer); + for(int i=0;iadd(data); + } + customPlot->graph()->setData(animdata); + QPen graphPen; + graphPen.setColor(QColor(rand()%245+10, rand()%245+10, rand()%245+10)); + graphPen.setWidthF(2); + customPlot->graph()->setPen(graphPen); + customPlot->replot(); + startTimer(40); +} + +void MainWindow::addRealtimeSample(double v) { + // shift the values + for (auto i = animdata->end(); i != (animdata->begin()); --i) { + i->value = (i-1)->value; + } + // add a new datapoint at the start + animdata->begin()->value = v; +} + +void MainWindow::timerEvent( QTimerEvent * ) { + // demonstrates that adding a few samples before plotting speeds things up + for(int i = 0; i < 5; i++) { + addRealtimeSample(sin(t*5)); + t = t + dt; + } + customPlot->replot(); +} + +void MainWindow::addRandomGraph() { + int n = 2000; // number of points in graph + double xScale = (rand()/(double)RAND_MAX + 0.5)*2; + double yScale = (rand()/(double)RAND_MAX + 0.5)*2; + double xOffset = (rand()/(double)RAND_MAX - 0.5)*4; + double yOffset = (rand()/(double)RAND_MAX - 0.5)*10; + double r1 = (rand()/(double)RAND_MAX - 0.5)*2; + double r2 = (rand()/(double)RAND_MAX - 0.5)*2; + double r3 = (rand()/(double)RAND_MAX - 0.5)*2; + double r4 = (rand()/(double)RAND_MAX - 0.5)*2; + QVector x(n), y(n); + for (int i=0; iaddGraph(); + customPlot->graph()->setName(QString("New graph %1").arg(customPlot->graphCount()-1)); + customPlot->graph()->setData(x, y); + QPen graphPen; + graphPen.setColor(QColor(rand()%245+10, rand()%245+10, rand()%245+10)); + graphPen.setWidthF(2); + customPlot->graph()->setPen(graphPen); + customPlot->replot(); +} + +void MainWindow::removeSelectedGraph() { + if (customPlot->selectedGraphs().size() > 0) + { + customPlot->removeGraph(customPlot->selectedGraphs().first()); + customPlot->replot(); + } +} + +void MainWindow::removeAllGraphs() { + customPlot->clearGraphs(); + customPlot->replot(); +} + +void MainWindow::contextMenuRequest(QPoint pos) { + QMenu *menu = new QMenu(this); + menu->setAttribute(Qt::WA_DeleteOnClose); + + if (customPlot->legend->selectTest(pos, false) >= 0) // context menu on legend requested + { + menu->addAction("Move to top left", this, SLOT(moveLegend()))->setData((int)(Qt::AlignTop|Qt::AlignLeft)); + menu->addAction("Move to top center", this, SLOT(moveLegend()))->setData((int)(Qt::AlignTop|Qt::AlignHCenter)); + menu->addAction("Move to top right", this, SLOT(moveLegend()))->setData((int)(Qt::AlignTop|Qt::AlignRight)); + menu->addAction("Move to bottom right", this, SLOT(moveLegend()))->setData((int)(Qt::AlignBottom|Qt::AlignRight)); + menu->addAction("Move to bottom left", this, SLOT(moveLegend()))->setData((int)(Qt::AlignBottom|Qt::AlignLeft)); + } else // general context menu on graphs requested + { + menu->addAction("Add random graph", this, SLOT(addRandomGraph())); + if (customPlot->selectedGraphs().size() > 0) + menu->addAction("Remove selected graph", this, SLOT(removeSelectedGraph())); + if (customPlot->graphCount() > 0) + menu->addAction("Remove all graphs", this, SLOT(removeAllGraphs())); + } + + menu->popup(customPlot->mapToGlobal(pos)); +} + +void MainWindow::moveLegend() { + if (QAction* contextAction = qobject_cast(sender())) // make sure this slot is really called by a context menu action, so it carries the data we need + { + bool ok; + int dataInt = contextAction->data().toInt(&ok); + if (ok) + { + customPlot->axisRect()->insetLayout()->setInsetAlignment(0, (Qt::Alignment)dataInt); + customPlot->replot(); + } + } +} + +void MainWindow::graphClicked(QCPAbstractPlottable *plottable, int dataIndex) { + // since we know we only have QCPGraphs in the plot, we can immediately access interface1D() + // usually it's better to first check whether interface1D() returns non-zero, and only then use it. + double dataValue = plottable->interface1D()->dataMainValue(dataIndex); + QString message = QString("Clicked on graph '%1' at data point #%2 with value %3.").arg(plottable->name()).arg(dataIndex).arg(dataValue); + statusBar->showMessage(message, 2500); +} + + + + diff --git a/mainwindow.h b/mainwindow.h new file mode 100644 index 0000000..40d715c --- /dev/null +++ b/mainwindow.h @@ -0,0 +1,62 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + + void timerEvent( QTimerEvent * ); + void addRealtimeSample(double v); + void callPlot(); + +private slots: + void titleDoubleClick(QMouseEvent *event); + void axisLabelDoubleClick(QCPAxis* axis, QCPAxis::SelectablePart part); + void legendDoubleClick(QCPLegend* legend, QCPAbstractLegendItem* item); + void selectionChanged(); + void mousePress(); + void mouseWheel(); + void addRandomGraph(); + void addRealtimeGraph(); + void removeSelectedGraph(); + void removeAllGraphs(); + void contextMenuRequest(QPoint pos); + void moveLegend(); + void graphClicked(QCPAbstractPlottable *plottable, int dataIndex); + +private: + QWidget *centralWidget; + QVBoxLayout *verticalLayout; + QFrame *frame_2; + QVBoxLayout *verticalLayout_3; + QCustomPlot *customPlot; + QFrame *frame; + QVBoxLayout *verticalLayout_2; + QLabel *label; + QMenuBar *menuBar; + QStatusBar *statusBar; + QSharedPointer > animdata; + const int nRealtimePoints = 500; + const double dt = 0.02; + double t = 0; +}; + +#endif // MAINWINDOW_H diff --git a/mainwindowdemo.cpp b/mainwindowdemo.cpp new file mode 100644 index 0000000..46e9e8a --- /dev/null +++ b/mainwindowdemo.cpp @@ -0,0 +1,11 @@ +#include +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.showMaximized(); + + return a.exec(); +} diff --git a/note b/note new file mode 100644 index 0000000..34049d2 --- /dev/null +++ b/note @@ -0,0 +1,13 @@ +cpp timer porr +graph +bpm + + +generate a simulated bpm 80 - 60 - 80 + +77 threshold + +audio.txt + +Audio/file.mp3 + diff --git a/pipeline_test.html b/pipeline_test.html new file mode 100644 index 0000000..e9cf2a2 --- /dev/null +++ b/pipeline_test.html @@ -0,0 +1,108 @@ + + + + Welcome to the Dream Hack Test server + + + + + + +

    Realtime data plot with JSON data transfer

    + +

    00 degree celsius

    + +

    This is a realtime demo where the java script requests the data + and then appends it to the plot every second.

    + +
    + + + + +
    +
    +
    +
    + +
    +
    +

    Main page

    +
    +
    +
    +
    + +

    References

    + +

    dygraphs

    + +

    github repo

    + +
    +
    +
    +
    +
    +
    +
    +
    +
    +
    + +

    Text only version

    + + + \ No newline at end of file diff --git a/readme.md b/readme.md index 47e4fd4..a55cae6 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,30 @@ # DreamHacker + +# Objectives +To give audio stimulus to a sleeping person to manipulate his dreams. + +# The Plan +To take data from pressure sensors, ecg, time of the day etc, to predict when the person has slept. +Then after a while start the audio stimulus. + +

    +
    + + +# Social Media + +[YouTube](https://www.youtube.com/channel/UCoZ31rXYGIltQAecAKzutBQ) + +[Facebook](https://www.facebook.com/Dream-Hacker-103619898510175) + +[Instagram](https://www.instagram.com/proj_dreamhacker/) + +# Hardware Needed +1. Raspberry pi +1. Pressure Sensor +1. Pulse Sensor +1. Wires, resistors, etc. + # UI Documentation This Document will be where the reasoning behind certain decisions, plans and prototypes will be added. @@ -11,3 +37,4 @@ The UI for DreamHacker will be a Web application. By using the Raspberry Pi as a 1. Javascript 1. JQuerry + diff --git a/temp.c b/temp.c new file mode 100644 index 0000000..c377a5b --- /dev/null +++ b/temp.c @@ -0,0 +1,345 @@ +/* + + THIS CODE IS RELEASED WITHOUT WARRANTY OF FITNESS + OR ANY PROMISE THAT IT WORKS, EVEN. WYSIWYG. + + YOU SHOULD HAVE RECEIVED A LICENSE FROM THE MAIN + BRANCH OF THIS REPO. IF NOT, IT IS USING THE + MIT FLAVOR OF LICENSE + +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "fft-real-pair.h" + + +#define OPT_R 10 // min uS allowed lag btw alarm and callback +#define OPT_U 2000 // sample time uS between alarms +#define OPT_O_ELAPSED 0 // output option uS elapsed time between alarms +#define OPT_O_JITTER 1 // output option uS jitter (elapsed time - sample time) +#define OPT_O 1 // defaoult output option +#define OPT_C 10000 // number of samples to run (testing) +#define OPT_N 1 // number of Pulse Sensors (only 1 supported) + +#define TIME_OUT 30000000 // uS time allowed without callback response +// PULSE SENSOR LEDS +#define BLINK_LED 0 +// MCP3004/8 SETTINGS +#define BASE 100 +#define SPI_CHAN 0 + +// FIFO STUFF +#define PULSE_EXIT 0 // CLEAN UP AND SHUT DOWN +#define PULSE_IDLE 1 // STOP SAMPLING, STAND BY +#define PULSE_ON 2 // START SAMPLING, WRITE DATA TO FILE +#define PULSE_DATA 3 // SEND DATA PACKET TO FIFO +#define PULSE_CONNECT 9 // CONNECT TO OTHER END OF PIPE + +// VARIABLES USED TO DETERMINE SAMPLE JITTER & TIME OUT +volatile unsigned int eventCounter, thisTime, lastTime, elapsedTime, jitter; +volatile int sampleFlag = 0; +volatile int sumJitter, firstTime, secondTime, duration, window_duration; +unsigned int timeOutStart, dataRequestStart, m; +// VARIABLES USED TO DETERMINE BPM +volatile int Signal; +volatile unsigned int sampleCounter; +volatile int threshSetting,lastBeatTime,fadeLevel; +volatile int thresh = 550; +volatile int P = 512; // set P default +volatile int T = 512; // set T default +volatile int firstBeat = 1; // set these to avoid noise +volatile int secondBeat = 0; // when we get the heartbeat back +volatile int QS = 0; +volatile int rate[10]; +volatile int BPM = 0; +volatile int IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) +volatile int Pulse = 0; +volatile int amp = 100; // beat amplitude 1/10 of input range. +// LED CONTROL +volatile int fadeLevel = 0; +// FILE STUFF +char filename [100]; +struct tm *timenow; +// FUNCTION PROTOTYPES +void getPulse(int sig_num); +void startTimer(int r, unsigned int u); +void stopTimer(void); +void initPulseSensorVariables(void); +void initJitterVariables(void); + +FILE *data; + +void usage() +{ + fprintf + (stderr, + "\n" \ + "Usage: sudo ./pulseProto ... [OPTION] ...\n" \ + " NO OPTIONS AVAILABLE YET\n"\ + "\n"\ + " Data file saved as\n"\ + " /home/pi/Documents/PulseSensor/PULSE_DATA \n"\ + " Data format tab separated:\n"\ + " sampleCount Signal BPM IBI Pulse Jitter\n"\ + "\n" + ); +} + +void sigHandler(int sig_num){ + printf("\nkilling timer\n"); + startTimer(OPT_R,0); // kill the alarm + exit(EXIT_SUCCESS); +} + +void fatal(int show_usage, char *fmt, ...) +{ + char buf[128]; + va_list ap; + char kill[20]; + + va_start(ap, fmt); + vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + fprintf(stderr, "%s\n", buf); + + if (show_usage) usage(); + + fflush(stderr); + printf("killing timer\n"); + startTimer(OPT_R,0); // kill the alarm + fprintf(data,"#%s",fmt); + fclose(data); + + exit(EXIT_FAILURE); +} + +// SAVED FOR FUTURE FEATURES +static int initOpts(int argc, char *argv[]) +{ + //int i, opt; + //while ((opt = getopt(argc, argv, ":")) != -1) + //{ + //i = -1; + //switch (opt) + //{ + //case '': + //default: /* '?' */ + //usage(); + //} + //} + return optind; +} + + +int main(int argc, char *argv[]) +{ + signal(SIGINT,sigHandler); + time_t now = time(NULL); + timenow = gmtime(&now); + + //strftime(filename, sizeof(filename), + //"/home/pi/Documents/PulseSensor/PULSE_DATA_%Y-%m-%d_%H:%M:%S.dat", timenow); + //data = fopen(filename, "w+"); + //fprintf(data,"#Running with %d latency at %duS sample rate\n",OPT_R,OPT_U); + //fprintf(data,"#sampleCount\tSignal\tBPM\tIBI\tjitter\n"); + + printf("Ready to run with %d latency at %duS sample rate\n",OPT_R,OPT_U); + + wiringPiSetup(); //use the wiringPi pin numbers + //piHiPri(99); + mcp3004Setup(BASE,SPI_CHAN); // setup the mcp3004 library + //pinMode(BLINK_LED, OUTPUT); digitalWrite(BLINK_LED,LOW); + + initPulseSensorVariables(); // initilaize Pulse Sensor beat finder + + startTimer(OPT_R, OPT_U); // start sampling + + + const int window_size = 4000; + double window_real[window_size]; + double window_imaginary[window_size]; + for(int i = 0; i < window_size; i++){ + window_imaginary[i] = 0.0; + } + time_t rec_time; + float sampling_rate; + while(1) + { + if(sampleFlag){ + sampleFlag = 0; + timeOutStart = micros(); + //digitalWrite(BLINK_LED,Pulse); + // PRINT DATA TO TERMINAL + //printf("%lu\t%d\t%d\t%d\t%d\n", + //sampleCounter,Signal,BPM,IBI,jitter + //); + rec_time = time(NULL); + window_duration = micros(); + for(int i = 0; i < window_size; i++) + window_real[i] = (double)Signal; + window_duration = micros()- window_duration; + sampling_rate = (float)window_duration/(float)window_size; + Fft_transform(window_real, window_imaginary, window_size); + printf("%f\n", sampling_rate); + + + // PRINT DATA TO FILE + //fprintf(data,"%d\t%d\t%d\t%d\t%d\t%d\n", + //sampleCounter,Signal,IBI,BPM,jitter,duration + //); + } + if((micros() - timeOutStart)>TIME_OUT){ + fatal(0,"0-program timed out",0); + } + } + + return 0; + +}//int main(int argc, char *argv[]) + +void startTimer(int r, unsigned int u){ +// What is a signal function + int latency = r; + unsigned int micros = u; + + signal(SIGALRM, getPulse); + int err = ualarm(latency, micros); + if(err == 0){ + if(micros > 0){ + printf("ualarm ON\n"); + }else{ + printf("ualarm OFF\n"); + } + } + +} + +void initPulseSensorVariables(void){ + for (int i = 0; i < 10; ++i) { + rate[i] = 0; + } + QS = 0; + BPM = 0; + IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) + Pulse = 0; + sampleCounter = 0; + lastBeatTime = 0; + P = 512; // peak at 1/2 the input range of 0..1023 + T = 512; // trough at 1/2 the input range. + threshSetting = 550; // used to seed and reset the thresh variable + thresh = 550; // threshold a little above the trough + amp = 100; // beat amplitude 1/10 of input range. + firstBeat = 1; // looking for the first beat + secondBeat = 0; // not yet looking for the second beat in a row + lastTime = micros(); + timeOutStart = lastTime; +} + +void getPulse(int sig_num){ + + if(sig_num == SIGALRM) + { + thisTime = micros(); + Signal = analogRead(BASE); + elapsedTime = thisTime - lastTime; + lastTime = thisTime; + jitter = elapsedTime - OPT_U; + sumJitter += jitter; + sampleFlag = 1; + + + sampleCounter += 2; // keep track of the time in mS with this variable + int N = sampleCounter - lastBeatTime; // monitor the time since the last beat to avoid noise + +// FADE LED HERE, IF WE COULD FADE... + + // find the peak and trough of the pulse wave + if (Signal < thresh && N > (IBI / 5) * 3) { // avoid dichrotic noise by waiting 3/5 of last IBI + if (Signal < T) { // T is the trough + T = Signal; // keep track of lowest point in pulse wave + } + } + + if (Signal > thresh && Signal > P) { // thresh condition helps avoid noise + P = Signal; // P is the peak + } // keep track of highest point in pulse wave + + // NOW IT'S TIME TO LOOK FOR THE HEART BEAT + // signal surges up in value every time there is a pulse + if (N > 250) { // avoid high frequency noise + if ( (Signal > thresh) && (Pulse == 0) && (N > ((IBI / 5) * 3)) ) { + Pulse = 1; // set the Pulse flag when we think there is a pulse + IBI = sampleCounter - lastBeatTime; // measure time between beats in mS + lastBeatTime = sampleCounter; // keep track of time for next pulse + + if (secondBeat) { // if this is the second beat, if secondBeat == TRUE + secondBeat = 0; // clear secondBeat flag + for (int i = 0; i <= 9; i++) { // seed the running total to get a realisitic BPM at startup + rate[i] = IBI; + } + } + + if (firstBeat) { // if it's the first time we found a beat, if firstBeat == TRUE + firstBeat = 0; // clear firstBeat flag + secondBeat = 1; // set the second beat flag + // IBI value is unreliable so discard it + return; + } + + + // keep a running total of the last 10 IBI values + int runningTotal = 0; // clear the runningTotal variable + + for (int i = 0; i <= 8; i++) { // shift data in the rate array + rate[i] = rate[i + 1]; // and drop the oldest IBI value + runningTotal += rate[i]; // add up the 9 oldest IBI values + } + + rate[9] = IBI; // add the latest IBI to the rate array + runningTotal += rate[9]; // add the latest IBI to runningTotal + runningTotal /= 10; // average the last 10 IBI values + BPM = 60000 / runningTotal; // how many beats can fit into a minute? that's BPM! + QS = 1; // set Quantified Self flag (we detected a beat) + //fadeLevel = MAX_FADE_LEVEL; // If we're fading, re-light that LED. + } + } + + if (Signal < thresh && Pulse == 1) { // when the values are going down, the beat is over + Pulse = 0; // reset the Pulse flag so we can do it again + amp = P - T; // get amplitude of the pulse wave + thresh = amp / 2 + T; // set thresh at 50% of the amplitude + P = thresh; // reset these for next time + T = thresh; + } + + if (N > 2500) { // if 2.5 seconds go by without a beat + thresh = threshSetting; // set thresh default + P = 512; // set P default + T = 512; // set T default + lastBeatTime = sampleCounter; // bring the lastBeatTime up to date + firstBeat = 1; // set these to avoid noise + secondBeat = 0; // when we get the heartbeat back + QS = 0; + BPM = 0; + IBI = 600; // 600ms per beat = 100 Beats Per Minute (BPM) + Pulse = 0; + amp = 100; // beat amplitude 1/10 of input range. + + } + + duration = micros()-thisTime; + + } + +} diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt new file mode 100644 index 0000000..8d50957 --- /dev/null +++ b/test/CMakeLists.txt @@ -0,0 +1,21 @@ +cmake_minimum_required(VERSION 2.8) +enable_testing() +include_directories( + .. + ) + +add_executable (test_startstop startstop.cpp) +TARGET_LINK_LIBRARIES(test_startstop cpptimer) +add_test(TestStartStop test_startstop) + +add_executable (test_twotimers twotimers.cpp) +TARGET_LINK_LIBRARIES(test_twotimers cpptimer) +add_test(TestTwotimers test_twotimers) + +add_executable (test_startstop_ms startstop_ms.cpp) +TARGET_LINK_LIBRARIES(test_startstop_ms cpptimer) +add_test(TestStartStop_ms test_startstop_ms) + +add_executable (test_twotimers_ms twotimers_ms.cpp) +TARGET_LINK_LIBRARIES(test_twotimers_ms cpptimer) +add_test(TestTwotimers_ms test_twotimers_ms) diff --git a/test/Makefile b/test/Makefile new file mode 100644 index 0000000..f7471f0 --- /dev/null +++ b/test/Makefile @@ -0,0 +1,380 @@ +# CMAKE generated file: DO NOT EDIT! +# Generated by "Unix Makefiles" Generator, CMake Version 3.16 + +# Default target executed when no arguments are given to make. +default_target: all + +.PHONY : default_target + +# Allow only one "make -f Makefile2" at a time, but pass parallelism. +.NOTPARALLEL: + + +#============================================================================= +# Special targets provided by cmake. + +# Disable implicit rules so canonical targets will work. +.SUFFIXES: + + +# Remove some rules from gmake that .SUFFIXES does not remove. +SUFFIXES = + +.SUFFIXES: .hpux_make_needs_suffix_list + + +# Suppress display of executed commands. +$(VERBOSE).SILENT: + + +# A target that is always out of date. +cmake_force: + +.PHONY : cmake_force + +#============================================================================= +# Set environment variables for the build. + +# The shell in which to execute make rules. +SHELL = /bin/sh + +# The CMake executable. +CMAKE_COMMAND = /usr/bin/cmake + +# The command to remove a file. +RM = /usr/bin/cmake -E remove -f + +# Escaping for special characters. +EQUALS = = + +# The top-level source directory on which CMake was run. +CMAKE_SOURCE_DIR = /home/pi/refactoring + +# The top-level build directory on which CMake was run. +CMAKE_BINARY_DIR = /home/pi/refactoring + +#============================================================================= +# Targets provided globally by CMake. + +# Special rule for the target install +install: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install + +# Special rule for the target install +install/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Install the project..." + /usr/bin/cmake -P cmake_install.cmake +.PHONY : install/fast + +# Special rule for the target list_install_components +list_install_components: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Available install components are: \"Unspecified\"" +.PHONY : list_install_components + +# Special rule for the target list_install_components +list_install_components/fast: list_install_components + +.PHONY : list_install_components/fast + +# Special rule for the target install/strip +install/strip: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." + /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip + +# Special rule for the target install/strip +install/strip/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing the project stripped..." + /usr/bin/cmake -DCMAKE_INSTALL_DO_STRIP=1 -P cmake_install.cmake +.PHONY : install/strip/fast + +# Special rule for the target rebuild_cache +rebuild_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." + /usr/bin/cmake -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) +.PHONY : rebuild_cache + +# Special rule for the target rebuild_cache +rebuild_cache/fast: rebuild_cache + +.PHONY : rebuild_cache/fast + +# Special rule for the target install/local +install/local: preinstall + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." + /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local + +# Special rule for the target install/local +install/local/fast: preinstall/fast + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Installing only the local directory..." + /usr/bin/cmake -DCMAKE_INSTALL_LOCAL_ONLY=1 -P cmake_install.cmake +.PHONY : install/local/fast + +# Special rule for the target test +test: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running tests..." + /usr/bin/ctest --force-new-ctest-process $(ARGS) +.PHONY : test + +# Special rule for the target test +test/fast: test + +.PHONY : test/fast + +# Special rule for the target edit_cache +edit_cache: + @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." + /usr/bin/cmake -E echo No\ interactive\ CMake\ dialog\ available. +.PHONY : edit_cache + +# Special rule for the target edit_cache +edit_cache/fast: edit_cache + +.PHONY : edit_cache/fast + +# The main all target +all: cmake_check_build_system + cd /home/pi/refactoring && $(CMAKE_COMMAND) -E cmake_progress_start /home/pi/refactoring/CMakeFiles /home/pi/refactoring/test/CMakeFiles/progress.marks + cd /home/pi/refactoring && $(MAKE) -f CMakeFiles/Makefile2 test/all + $(CMAKE_COMMAND) -E cmake_progress_start /home/pi/refactoring/CMakeFiles 0 +.PHONY : all + +# The main clean target +clean: + cd /home/pi/refactoring && $(MAKE) -f CMakeFiles/Makefile2 test/clean +.PHONY : clean + +# The main clean target +clean/fast: clean + +.PHONY : clean/fast + +# Prepare targets for installation. +preinstall: all + cd /home/pi/refactoring && $(MAKE) -f CMakeFiles/Makefile2 test/preinstall +.PHONY : preinstall + +# Prepare targets for installation. +preinstall/fast: + cd /home/pi/refactoring && $(MAKE) -f CMakeFiles/Makefile2 test/preinstall +.PHONY : preinstall/fast + +# clear depends +depend: + cd /home/pi/refactoring && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 1 +.PHONY : depend + +# Convenience name for target. +test/CMakeFiles/test_twotimers.dir/rule: + cd /home/pi/refactoring && $(MAKE) -f CMakeFiles/Makefile2 test/CMakeFiles/test_twotimers.dir/rule +.PHONY : test/CMakeFiles/test_twotimers.dir/rule + +# Convenience name for target. +test_twotimers: test/CMakeFiles/test_twotimers.dir/rule + +.PHONY : test_twotimers + +# fast build rule for target. +test_twotimers/fast: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_twotimers.dir/build.make test/CMakeFiles/test_twotimers.dir/build +.PHONY : test_twotimers/fast + +# Convenience name for target. +test/CMakeFiles/test_startstop.dir/rule: + cd /home/pi/refactoring && $(MAKE) -f CMakeFiles/Makefile2 test/CMakeFiles/test_startstop.dir/rule +.PHONY : test/CMakeFiles/test_startstop.dir/rule + +# Convenience name for target. +test_startstop: test/CMakeFiles/test_startstop.dir/rule + +.PHONY : test_startstop + +# fast build rule for target. +test_startstop/fast: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_startstop.dir/build.make test/CMakeFiles/test_startstop.dir/build +.PHONY : test_startstop/fast + +# Convenience name for target. +test/CMakeFiles/test_startstop_ms.dir/rule: + cd /home/pi/refactoring && $(MAKE) -f CMakeFiles/Makefile2 test/CMakeFiles/test_startstop_ms.dir/rule +.PHONY : test/CMakeFiles/test_startstop_ms.dir/rule + +# Convenience name for target. +test_startstop_ms: test/CMakeFiles/test_startstop_ms.dir/rule + +.PHONY : test_startstop_ms + +# fast build rule for target. +test_startstop_ms/fast: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_startstop_ms.dir/build.make test/CMakeFiles/test_startstop_ms.dir/build +.PHONY : test_startstop_ms/fast + +# Convenience name for target. +test/CMakeFiles/test_twotimers_ms.dir/rule: + cd /home/pi/refactoring && $(MAKE) -f CMakeFiles/Makefile2 test/CMakeFiles/test_twotimers_ms.dir/rule +.PHONY : test/CMakeFiles/test_twotimers_ms.dir/rule + +# Convenience name for target. +test_twotimers_ms: test/CMakeFiles/test_twotimers_ms.dir/rule + +.PHONY : test_twotimers_ms + +# fast build rule for target. +test_twotimers_ms/fast: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_twotimers_ms.dir/build.make test/CMakeFiles/test_twotimers_ms.dir/build +.PHONY : test_twotimers_ms/fast + +startstop.o: startstop.cpp.o + +.PHONY : startstop.o + +# target to build an object file +startstop.cpp.o: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_startstop.dir/build.make test/CMakeFiles/test_startstop.dir/startstop.cpp.o +.PHONY : startstop.cpp.o + +startstop.i: startstop.cpp.i + +.PHONY : startstop.i + +# target to preprocess a source file +startstop.cpp.i: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_startstop.dir/build.make test/CMakeFiles/test_startstop.dir/startstop.cpp.i +.PHONY : startstop.cpp.i + +startstop.s: startstop.cpp.s + +.PHONY : startstop.s + +# target to generate assembly for a file +startstop.cpp.s: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_startstop.dir/build.make test/CMakeFiles/test_startstop.dir/startstop.cpp.s +.PHONY : startstop.cpp.s + +startstop_ms.o: startstop_ms.cpp.o + +.PHONY : startstop_ms.o + +# target to build an object file +startstop_ms.cpp.o: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_startstop_ms.dir/build.make test/CMakeFiles/test_startstop_ms.dir/startstop_ms.cpp.o +.PHONY : startstop_ms.cpp.o + +startstop_ms.i: startstop_ms.cpp.i + +.PHONY : startstop_ms.i + +# target to preprocess a source file +startstop_ms.cpp.i: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_startstop_ms.dir/build.make test/CMakeFiles/test_startstop_ms.dir/startstop_ms.cpp.i +.PHONY : startstop_ms.cpp.i + +startstop_ms.s: startstop_ms.cpp.s + +.PHONY : startstop_ms.s + +# target to generate assembly for a file +startstop_ms.cpp.s: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_startstop_ms.dir/build.make test/CMakeFiles/test_startstop_ms.dir/startstop_ms.cpp.s +.PHONY : startstop_ms.cpp.s + +twotimers.o: twotimers.cpp.o + +.PHONY : twotimers.o + +# target to build an object file +twotimers.cpp.o: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_twotimers.dir/build.make test/CMakeFiles/test_twotimers.dir/twotimers.cpp.o +.PHONY : twotimers.cpp.o + +twotimers.i: twotimers.cpp.i + +.PHONY : twotimers.i + +# target to preprocess a source file +twotimers.cpp.i: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_twotimers.dir/build.make test/CMakeFiles/test_twotimers.dir/twotimers.cpp.i +.PHONY : twotimers.cpp.i + +twotimers.s: twotimers.cpp.s + +.PHONY : twotimers.s + +# target to generate assembly for a file +twotimers.cpp.s: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_twotimers.dir/build.make test/CMakeFiles/test_twotimers.dir/twotimers.cpp.s +.PHONY : twotimers.cpp.s + +twotimers_ms.o: twotimers_ms.cpp.o + +.PHONY : twotimers_ms.o + +# target to build an object file +twotimers_ms.cpp.o: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_twotimers_ms.dir/build.make test/CMakeFiles/test_twotimers_ms.dir/twotimers_ms.cpp.o +.PHONY : twotimers_ms.cpp.o + +twotimers_ms.i: twotimers_ms.cpp.i + +.PHONY : twotimers_ms.i + +# target to preprocess a source file +twotimers_ms.cpp.i: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_twotimers_ms.dir/build.make test/CMakeFiles/test_twotimers_ms.dir/twotimers_ms.cpp.i +.PHONY : twotimers_ms.cpp.i + +twotimers_ms.s: twotimers_ms.cpp.s + +.PHONY : twotimers_ms.s + +# target to generate assembly for a file +twotimers_ms.cpp.s: + cd /home/pi/refactoring && $(MAKE) -f test/CMakeFiles/test_twotimers_ms.dir/build.make test/CMakeFiles/test_twotimers_ms.dir/twotimers_ms.cpp.s +.PHONY : twotimers_ms.cpp.s + +# Help Target +help: + @echo "The following are some of the valid targets for this Makefile:" + @echo "... all (the default if no target is provided)" + @echo "... clean" + @echo "... depend" + @echo "... install" + @echo "... list_install_components" + @echo "... test_twotimers" + @echo "... install/strip" + @echo "... test_startstop" + @echo "... rebuild_cache" + @echo "... test_startstop_ms" + @echo "... install/local" + @echo "... test_twotimers_ms" + @echo "... test" + @echo "... edit_cache" + @echo "... startstop.o" + @echo "... startstop.i" + @echo "... startstop.s" + @echo "... startstop_ms.o" + @echo "... startstop_ms.i" + @echo "... startstop_ms.s" + @echo "... twotimers.o" + @echo "... twotimers.i" + @echo "... twotimers.s" + @echo "... twotimers_ms.o" + @echo "... twotimers_ms.i" + @echo "... twotimers_ms.s" +.PHONY : help + + + +#============================================================================= +# Special targets to cleanup operation of make. + +# Special rule to run CMake to check the build system integrity. +# No rule that depends on this can have commands that come from listfiles +# because they might be regenerated. +cmake_check_build_system: + cd /home/pi/refactoring && $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles/Makefile.cmake 0 +.PHONY : cmake_check_build_system + diff --git a/test/startstop.cpp b/test/startstop.cpp new file mode 100644 index 0000000..e5c74c2 --- /dev/null +++ b/test/startstop.cpp @@ -0,0 +1,35 @@ +#include +#include "CppTimer.h" +#include +#include +#include + +class DemoTimer1 : public CppTimer { + + void timerEvent() { + fprintf(stdout,"."); + fflush(stdout); + } +}; + + +int main( int, const char** ) { + DemoTimer1 demoTimer1; + demoTimer1.startns(250000000); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.stop(); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.startns(25000000); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.stop(); + + fprintf(stdout,"\n"); + + return 0; +} diff --git a/test/startstop_ms.cpp b/test/startstop_ms.cpp new file mode 100644 index 0000000..ec17fe1 --- /dev/null +++ b/test/startstop_ms.cpp @@ -0,0 +1,35 @@ +#include +#include "CppTimer.h" +#include +#include +#include + +class DemoTimer1 : public CppTimer { + + void timerEvent() { + fprintf(stdout,"."); + fflush(stdout); + } +}; + + +int main( int, const char** ) { + DemoTimer1 demoTimer1; + demoTimer1.startms(500); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.stop(); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.startms(1500); + + std::this_thread::sleep_for(std::chrono::seconds(2)); + + demoTimer1.stop(); + + fprintf(stdout,"\n"); + + return 0; +} diff --git a/test/test_startstop b/test/test_startstop new file mode 100755 index 0000000..1a9e0b2 Binary files /dev/null and b/test/test_startstop differ diff --git a/test/test_startstop_ms b/test/test_startstop_ms new file mode 100755 index 0000000..90b055a Binary files /dev/null and b/test/test_startstop_ms differ diff --git a/test/test_twotimers b/test/test_twotimers new file mode 100755 index 0000000..c79e634 Binary files /dev/null and b/test/test_twotimers differ diff --git a/test/test_twotimers_ms b/test/test_twotimers_ms new file mode 100755 index 0000000..1c4bfc7 Binary files /dev/null and b/test/test_twotimers_ms differ diff --git a/test/twotimers.cpp b/test/twotimers.cpp new file mode 100644 index 0000000..a76f6a3 --- /dev/null +++ b/test/twotimers.cpp @@ -0,0 +1,62 @@ +#include +#include "CppTimer.h" +#include +#include +#include + +class DemoTimer1 : public CppTimer { +public: + void timerEvent() { + counter++; + fprintf(stdout,"1"); + fflush(stdout); + } + void startns(long nanosecs) { + counter = 0; + CppTimer::startns(nanosecs); + } + int getCounter() {return counter;} +private: + int counter = 0; +}; + + +class DemoTimer2 : public CppTimer { +public: + DemoTimer1* demoTimer1; + DemoTimer2(DemoTimer1* dt1) : CppTimer() { + demoTimer1 = dt1; + } + void timerEvent() { + fprintf(stdout,"2\n"); + if (0 == demoTimer1->getCounter()) { + const char tmp[] = "BUG! Timer one hasn't fired.\n"; + fprintf(stderr,tmp); + throw tmp; + } + } +}; + + + +int main( int, const char**) { + DemoTimer1 demoTimer1; + demoTimer1.startns(100000000); + DemoTimer2 demoTimer2(&demoTimer1); + demoTimer2.startns(200000000); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.stop(); + demoTimer2.stop(); + + demoTimer1.startns(25000000); + demoTimer2.startns(100000000); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.stop(); + demoTimer2.stop(); + + return 0; +} diff --git a/test/twotimers_ms.cpp b/test/twotimers_ms.cpp new file mode 100644 index 0000000..7d6217b --- /dev/null +++ b/test/twotimers_ms.cpp @@ -0,0 +1,62 @@ +#include +#include "CppTimer.h" +#include +#include +#include + +class DemoTimer1 : public CppTimer { +public: + void timerEvent() { + counter++; + fprintf(stdout,"1"); + fflush(stdout); + } + void start(long millisecs) { + counter = 0; + CppTimer::startms(millisecs); + } + int getCounter() {return counter;} +private: + int counter = 0; +}; + + +class DemoTimer2 : public CppTimer { +public: + DemoTimer1* demoTimer1; + DemoTimer2(DemoTimer1* dt1) : CppTimer() { + demoTimer1 = dt1; + } + void timerEvent() { + fprintf(stdout,"2\n"); + if (0 == demoTimer1->getCounter()) { + const char tmp[] = "BUG! Timer one hasn't fired.\n"; + fprintf(stderr,tmp); + throw tmp; + } + } +}; + + + +int main( int, const char**) { + DemoTimer1 demoTimer1; + demoTimer1.startms(100); + DemoTimer2 demoTimer2(&demoTimer1); + demoTimer2.startms(200); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.stop(); + demoTimer2.stop(); + + demoTimer1.startms(25); + demoTimer2.startms(100); + + std::this_thread::sleep_for(std::chrono::seconds(1)); + + demoTimer1.stop(); + demoTimer2.stop(); + + return 0; +}