class heart_rate

pixart_heart_rate.cpp

Committer:
bell_huang
Date:
2017-10-27
Revision:
0:31d7988294aa
Child:
1:e9f4fb198b63

File content as of revision 0:31d7988294aa:

#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;
    }

}