class heart_rate

Revision:
0:31d7988294aa
Child:
1:e9f4fb198b63
diff -r 000000000000 -r 31d7988294aa pixart_heart_rate.cpp
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/pixart_heart_rate.cpp	Fri Oct 27 05:55:57 2017 +0000
@@ -0,0 +1,226 @@
+#include "pixart_heart_rate.h"
+
+#include "pah8series_api_c.h"
+
+#include <limits>
+#include <string.h>
+#include <stdlib.h>
+
+
+namespace pixart {
+    
+    
+    const float     heart_rate::GRAVITY_OF_EARTH = 9.80665f;
+    const size_t    heart_rate::PPG_SYNC_COUNT = 20;
+    const size_t    heart_rate::ACCEL_AXIS_NUM = 3;
+    
+    heart_rate::heart_rate(TIME_TO_MILLISEC_HANDLE time_to_millisec_handler, size_t ppg_max_sample_num, size_t accel_max_sample_num)
+        : PPG_MAX_SAMPLE_NUM(ppg_max_sample_num)
+        , PPG_CH_NUM(2)
+        , ACCEL_MAX_SAMPLE_NUM(accel_max_sample_num)
+        , m_time_to_millisec_handler(time_to_millisec_handler)
+        , m_alg_buffer(NULL)
+        , m_gravity_mode(gravity_4g)
+        , m_last_report_timestamp(0)
+        , m_frame_count(0)
+    {
+    }
+    
+    heart_rate::~heart_rate()
+    {
+        pah8series_close();
+        
+        if (m_alg_buffer)
+            free(m_alg_buffer);
+    }
+    
+    bool heart_rate::init(gravity_mode gravity)
+    {
+        if (!m_alg_buffer)
+            m_alg_buffer = malloc(pah8series_query_open_size());
+        
+        if (!m_alg_buffer)
+            return false;
+            
+        uint8_t ret = pah8series_open(m_alg_buffer);
+        if (ret != MSG_SUCCESS)
+            return false;
+            
+        pah8series_set_param(PAH8SERIES_PARAM_IDX_PPG_CH_NUM, (float)PPG_CH_NUM);
+        pah8series_set_param(PAH8SERIES_PARAM_IDX_HAS_IR_CH, 0.0f);
+        
+        float gsensor_mode = (float)gravity_to_hr_alg_gsensor_mode(gravity);
+        pah8series_set_param(PAH8SERIES_PARAM_IDX_GSENSOR_MODE, gsensor_mode);
+        
+        m_gravity_mode = gravity;
+        
+        return true;
+    }
+    
+    void heart_rate::reset()
+    {
+        m_ppg_sample_queue.clear();
+        m_accel_sample_queue.clear();
+        m_last_report_timestamp = 0;
+        m_frame_count = 0;
+        memset(&m_result, 0, sizeof(m_result));
+    }
+    
+    bool heart_rate::set_ppg_sample(const ppg_sample &ppg_sample)
+    {
+        // overflow
+        if (m_ppg_sample_queue.size() + 1 > PPG_MAX_SAMPLE_NUM)
+            return false;
+            
+        m_ppg_sample_queue.push_back(ppg_sample);
+        
+        flush();
+        
+        return true;
+    }
+    
+    bool heart_rate::set_accel_sample(const accel_sample &accel_sample)
+    {
+        // overflow
+        if (m_accel_sample_queue.size() + 1 > ACCEL_MAX_SAMPLE_NUM)
+            return false;
+            
+        m_accel_sample_queue.push_back(accel_sample);
+        
+        return true;
+    }
+    
+    const heart_rate::result& heart_rate::get_result() const
+    {
+        return m_result;
+    }
+    
+    int heart_rate::gravity_to_hr_alg_gsensor_mode(gravity_mode gravity)
+    {
+        int gsensor_mode = 0;
+    
+        switch (gravity)
+        {
+        case gravity_2g:
+            gsensor_mode = 0;
+            break;
+    
+        case gravity_4g:
+            gsensor_mode = 1;
+            break;
+    
+        case gravity_8g:
+            gsensor_mode = 2;
+            break;
+    
+        case gravity_16g:
+        default:
+            gsensor_mode = 3;
+            break;
+        }
+    
+        return gsensor_mode;
+    }
+    
+    int16_t heart_rate::gravity_to_int16(gravity_mode gravity, float value)
+    {
+        typedef int16_t result_type;
+        static const result_type MAX = std::numeric_limits<result_type>::max();
+        static const result_type MIN = std::numeric_limits<result_type>::min();
+        
+        float result = (value  * MAX) / ((float)gravity * GRAVITY_OF_EARTH);
+        if (result > MAX)
+            return MAX;
+        else if (result < MIN)
+            return MIN;
+        return (result_type)result;
+    }
+
+    void heart_rate::flush()
+    {
+        // Get sync timestamp
+        if (m_ppg_sample_queue.size() < PPG_SYNC_COUNT)
+            return;
+        uint64_t sync_timestamp = m_ppg_sample_queue[PPG_SYNC_COUNT - 1].timestamp;
+        
+        // Ensure accel sensor has data timestamp later than sync_timestamp
+        if (m_ppg_sample_queue.size() < PPG_MAX_SAMPLE_NUM)
+        {
+            if (m_accel_sample_queue.empty() || (m_accel_sample_queue.back().timestamp < sync_timestamp))
+                return;
+        }
+        
+        // Flush PPG
+        size_t flush_ppg_sample_count = PPG_SYNC_COUNT;
+        std::vector<int32_t> hr_ppg_data(flush_ppg_sample_count * PPG_CH_NUM);
+        for (size_t i = 0; i < flush_ppg_sample_count; ++i)
+        {
+            hr_ppg_data[i * PPG_CH_NUM + 0] = m_ppg_sample_queue[i].ch1;
+            hr_ppg_data[i * PPG_CH_NUM + 1] = m_ppg_sample_queue[i].ch2;
+        }
+        uint8_t touch_flag = m_ppg_sample_queue[flush_ppg_sample_count - 1].is_touched;
+        uint64_t ppg_timestamp = m_ppg_sample_queue[flush_ppg_sample_count - 1].timestamp;
+        if (m_last_report_timestamp == 0)
+            m_last_report_timestamp = m_ppg_sample_queue.front().timestamp;
+        std::deque<ppg_sample>::iterator flush_ppg_end_iter(m_ppg_sample_queue.begin());
+        std::advance(flush_ppg_end_iter, flush_ppg_sample_count);
+        m_ppg_sample_queue.erase(m_ppg_sample_queue.begin(), flush_ppg_end_iter);
+        
+        // Flush ACCEL
+        size_t flush_accel_sample_count = 0;
+        for (size_t i = 0; i < m_accel_sample_queue.size(); ++i)
+        {
+            if (m_accel_sample_queue[i].timestamp <= sync_timestamp)
+                ++flush_accel_sample_count;
+            else
+                break;
+        }
+        std::vector<int16_t> hr_accel_data(flush_accel_sample_count * ACCEL_AXIS_NUM);
+        for (size_t i = 0; i < flush_accel_sample_count; ++i)
+        {
+            hr_accel_data[i * ACCEL_AXIS_NUM + 0] = (int16_t)gravity_to_int16(m_gravity_mode, m_accel_sample_queue[i].x);
+            hr_accel_data[i * ACCEL_AXIS_NUM + 1] = (int16_t)gravity_to_int16(m_gravity_mode, m_accel_sample_queue[i].y);
+            hr_accel_data[i * ACCEL_AXIS_NUM + 2] = (int16_t)gravity_to_int16(m_gravity_mode, m_accel_sample_queue[i].z);
+        }
+        std::deque<accel_sample>::iterator flush_accel_end_iter(m_accel_sample_queue.begin());
+        std::advance(flush_accel_end_iter, flush_accel_sample_count);
+        m_accel_sample_queue.erase(m_accel_sample_queue.begin(), flush_accel_end_iter);
+        
+        // Duration
+        uint32_t time_ms = (uint32_t)m_time_to_millisec_handler(ppg_timestamp - m_last_report_timestamp);
+        m_last_report_timestamp = ppg_timestamp;
+        
+        pah8series_data_t pah8series_data;
+        pah8series_data.frame_count        = m_frame_count;
+        pah8series_data.time               = time_ms;
+        pah8series_data.touch_flag         = touch_flag;
+        pah8series_data.nf_ppg_channel     = PPG_CH_NUM;
+        pah8series_data.nf_ppg_per_channel = flush_ppg_sample_count;
+        pah8series_data.ppg_data           = &hr_ppg_data[0];
+        pah8series_data.nf_mems            = flush_accel_sample_count;
+        pah8series_data.mems_data          = &hr_accel_data[0];
+        ++m_frame_count;
+        
+        uint8_t ret = pah8series_entrance(&pah8series_data);
+        
+        float hr = 0.0f;
+        pah8series_get_hr(&hr);
+        
+        int hr_trust_level = 0;
+        pah8series_get_hr_trust_level(&hr_trust_level);
+        
+        if (ret == MSG_SUCCESS
+         || ret == MSG_HR_READY)
+            m_result.ret = ret_success;
+        else if (ret == MSG_NO_TOUCH)
+            m_result.ret = ret_no_touch;
+        else if (ret == MSG_SIGNAL_POOR)
+            m_result.ret = ret_signal_poor;
+        else
+            m_result.ret = ret_fail;
+        m_result.hr = hr;
+        m_result.hr_trust_level = hr_trust_level;
+    }
+
+}
+