diff --git a/README.md b/README.md index 41bdf35c..d2f90008 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,15 @@ Document is generated with Doxygen. Run `make doxygen` to generate the docs loca browse to [Travis built docs](ruuvi.github.io/ruuvi.firmware.c) # Changelog +## 3.29.0 + - Refactor, move tasks under drivers. + - Refactor, store log data to flash. + - Improve GATT power consumption. + - Improve GATT data rate. + +## 3.28.12 + - Fix leds not blinking + ## 3.28.12 - Automatically release a tag diff --git a/project.yml b/project.yml index b1806713..64f1e4e8 100644 --- a/project.yml +++ b/project.yml @@ -28,6 +28,22 @@ :extension: :executable: .out +:tools: +# Ceedling defaults to using gcc for compiling, linking, etc. +# As [:tools] is blank, gcc will be used (so long as it's in your system path) +# See documentation to configure a given toolchain for use + :test_linker: + :executable: gcc #absolute file path + :name: 'gcc linker' + :arguments: + - ${1} #list of object files to link (Ruby method call param list sub) + - -lm #link with math header + - -o ${2} #executable file output (Ruby method call param list sub) + +:tools_gcov_linker: + :arguments: + - -lm + :paths: :test: - +:test/** @@ -63,6 +79,7 @@ :enforce_strict_ordering: TRUE :plugins: - :ignore + - :ignore_arg - :callback - :return_thru_ptr - :array diff --git a/src/app_comms.c b/src/app_comms.c index 3d0297d9..f77829e7 100644 --- a/src/app_comms.c +++ b/src/app_comms.c @@ -1,7 +1,12 @@ #include "app_config.h" #include "app_comms.h" +#include "app_heartbeat.h" +#include "app_sensor.h" #include "ruuvi_boards.h" +#include "ruuvi_endpoints.h" +#include "ruuvi_interface_communication.h" #include "ruuvi_interface_communication_radio.h" +#include "ruuvi_interface_scheduler.h" #include "ruuvi_task_advertisement.h" #include "ruuvi_task_communication.h" #include "ruuvi_task_gatt.h" @@ -24,8 +29,66 @@ * TODO * @endcode */ - #if APP_GATT_ENABLED + +static void handle_comms (const ri_comm_xfer_fp_t reply_fp, void * p_data, + size_t data_len) +{ + rd_status_t err_code = RD_SUCCESS; + + if (NULL == p_data) + { + err_code |= RD_ERROR_NULL; + } + else if (data_len < RE_STANDARD_MESSAGE_LENGTH) + { + err_code |= RD_ERROR_INVALID_PARAM; + } + else + { + // Stop heartbeat processing. + err_code |= app_heartbeat_stop(); + // Parse message type + const uint8_t * const raw_message = (uint8_t *) p_data; + re_type_t type = raw_message[RE_STANDARD_DESTINATION_INDEX]; + + // Route message to proper handler + switch (type) + { + case RE_ACC_XYZ: + case RE_ACC_X: + case RE_ACC_Y: + case RE_ACC_Z: + case RE_GYR_XYZ: + case RE_GYR_X: + case RE_GYR_Y: + case RE_GYR_Z: + case RE_ENV_ALL: + case RE_ENV_TEMP: + case RE_ENV_HUMI: + case RE_ENV_PRES: + err_code |= app_sensor_handle (reply_fp, raw_message, data_len); + break; + + default: + break; + } + + // Resume heartbeat processing. + err_code |= app_heartbeat_start(); + } + + RD_ERROR_CHECK (err_code, ~RD_ERROR_FATAL); +} + +#ifndef CEEDLING +static +#endif +void handle_gatt (void * p_data, uint16_t data_len) +{ + handle_comms (&rt_gatt_send_asynchronous, p_data, data_len); +} + /** @brief Callback when GATT is connected" */ #ifndef CEEDLING static @@ -45,7 +108,21 @@ void on_gatt_disconnected_isr (void * p_data, size_t data_len) // Start advertising for GATT rt_gatt_enable(); } -// TODO: On data received + +/** + * @brief Callback when data is received via GATT + * + * Schedule handling incoming message and replying back via given function pointer. + */ +#ifndef CEEDLING +static +#endif +void on_gatt_data_isr (void * p_data, size_t data_len) +{ + rd_status_t err_code = RD_SUCCESS; + err_code |= ri_scheduler_event_put (p_data, (uint16_t) data_len, &handle_gatt); + RD_ERROR_CHECK (err_code, ~RD_ERROR_FATAL); +} static rd_status_t ble_name_string_create (char * const name_str, const size_t name_len) { @@ -92,7 +169,6 @@ rd_status_t app_comms_init (void) { rd_status_t err_code = RD_SUCCESS; ri_comm_dis_init_t dis = {0}; - // Allow switchover to extended / 2 MBPS comms. err_code |= ri_radio_init (APP_MODULATION); if (RD_SUCCESS == err_code) @@ -113,6 +189,7 @@ rd_status_t app_comms_init (void) err_code |= rt_gatt_nus_init(); rt_gatt_set_on_connected_isr (&on_gatt_connected_isr); rt_gatt_set_on_disconn_isr (&on_gatt_disconnected_isr); + rt_gatt_set_on_received_isr (&on_gatt_data_isr); err_code |= rt_gatt_enable(); #endif } diff --git a/src/app_comms.h b/src/app_comms.h index 5b2bf86c..cf25108c 100644 --- a/src/app_comms.h +++ b/src/app_comms.h @@ -48,6 +48,8 @@ rd_status_t app_comms_init (void); /** Handles for unit test framework */ void on_gatt_connected_isr (void * p_data, size_t data_len); void on_gatt_disconnected_isr (void * p_data, size_t data_len); +void on_gatt_data_isr (void * p_data, size_t data_len); +void handle_gatt (void * p_data, uint16_t data_len); #endif diff --git a/src/app_heartbeat.c b/src/app_heartbeat.c index f6c0a21f..fb0a5fb0 100644 --- a/src/app_heartbeat.c +++ b/src/app_heartbeat.c @@ -8,6 +8,7 @@ #include "app_config.h" #include "app_comms.h" #include "app_heartbeat.h" +#include "app_log.h" #include "app_sensor.h" #include "ruuvi_driver_error.h" #include "ruuvi_driver_sensor.h" @@ -23,6 +24,7 @@ #include "ruuvi_task_nfc.h" static ri_timer_id_t heart_timer; //!< Timer for updating data. + #ifndef CEEDLING static #endif @@ -110,6 +112,9 @@ void heartbeat (void * p_event, uint16_t event_size) { ri_watchdog_feed(); } + + err_code = app_log_process (&data); + RD_ERROR_CHECK (err_code, ~RD_ERROR_FATAL); } /** @@ -147,7 +152,37 @@ rd_status_t app_heartbeat_init (void) return err_code; } +rd_status_t app_heartbeat_start (void) +{ + rd_status_t err_code = RD_SUCCESS; + + if (NULL == heart_timer) + { + err_code |= RD_ERROR_INVALID_STATE; + } + else + { + err_code |= ri_timer_start (heart_timer, APP_HEARTBEAT_INTERVAL_MS, NULL); + } + return err_code; +} + +rd_status_t app_heartbeat_stop (void) +{ + rd_status_t err_code = RD_SUCCESS; + + if (NULL == heart_timer) + { + err_code |= RD_ERROR_INVALID_STATE; + } + else + { + err_code |= ri_timer_stop (heart_timer); + } + + return err_code; +} #ifdef CEEDLING // Give CEEDLING a handle to state of module. diff --git a/src/app_heartbeat.h b/src/app_heartbeat.h index d479a345..38dead21 100644 --- a/src/app_heartbeat.h +++ b/src/app_heartbeat.h @@ -21,12 +21,39 @@ * The heartbeat interval should be at most as logging rate to make sure * that application will log fresh data. * + * The heartbeat data is passed on to logging module which then decides + * if sample should be logged. + * * @retval RD_SUCCESS on success * @retval RD_ERROR_INVALID_STATE if timers or scheduler is not initialized. * @retval RD_ERROR_RESOURCES if a timer cannot be allocated. */ rd_status_t app_heartbeat_init (void); +/** + * @brief (Re)starts app heartbeats. + * + * Calling this while heartbeats are ongoing has no effect. + * + * @retval RD_SUCCESS on success + * @retval RD_ERROR_INVALID_STATE if heartbeat is not initialized. + */ +rd_status_t app_heartbeat_start (void); + +/** + * @brief Stops app heartbeats. + * + * This should be called to reduce the number of interrupts during heavier processing, + * e.g. stop while replaying sensor logs to attached device. + * Calling this has no effect if heartbeats are already stopped. + * + * @note Remember to restart the heartbeat, othewise app watchdog will trigger. + * + * @retval RD_SUCCESS on success. + * @retval RD_ERROR_INVALID_STATE if heartbeat is not initialized. + */ +rd_status_t app_heartbeat_stop (void); + #ifdef CEEDLING #include "ruuvi_interface_timer.h" ri_timer_id_t * get_heart_timer (void); diff --git a/src/app_log.c b/src/app_log.c new file mode 100644 index 00000000..81920ee2 --- /dev/null +++ b/src/app_log.c @@ -0,0 +1,384 @@ +#include "app_log.h" + +#include "app_config.h" +#include "ruuvi_driver_error.h" +#include "ruuvi_driver_sensor.h" +#include "ruuvi_library.h" +#include "ruuvi_library_compress.h" +#include "ruuvi_interface_log.h" +#include "ruuvi_interface_yield.h" +#include "ruuvi_task_flash.h" +#if RT_FLASH_ENABLED + +static inline void LOG (const char * const msg) +{ + ri_log (RI_LOG_LEVEL_INFO, msg); +} + +static inline void LOGD (const char * const msg) +{ + ri_log (RI_LOG_LEVEL_DEBUG, msg); +} + + +/** + * @addtogroup app_log + */ +/** @{ */ +/** + * @file app_log.c + * @author Otso Jousimaa + * @date 2020-07-17 + * @copyright Ruuvi Innovations Ltd, license BSD-3-Clause. + */ + +#ifndef CEEDLING +static +#endif +app_log_record_t m_log_input_block; //!< Block to be stored to flash. + +#ifndef CEEDLING +static +#endif +app_log_record_t m_log_output_block; //!< Block read from flash for examination. + +#ifndef CEEDLING +static +#endif +app_log_config_t m_log_config; //!< Configuration for logging. + +#ifndef CEEDLING +static +#endif +uint64_t m_last_sample_ms; //!< Timestamp of last processed sample. + +static rd_status_t store_block (const app_log_record_t * const record) +{ + static uint8_t record_idx = 0; + uint8_t num_tries = 0; + uint32_t end_timestamp = m_log_input_block.end_timestamp_s; + rd_status_t err_code = RD_SUCCESS; + + do + { + err_code = rt_flash_free (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + record_idx); + + // It's not a problem if there wasn't old block to erase. + if (RD_SUCCESS == err_code) + { + LOG ("LOG: Erasing old block\r\n"); + } + + err_code &= ~RD_ERROR_NOT_FOUND; + + while (rt_flash_busy()) + { + ri_yield(); + } + + err_code |= rt_flash_gc_run (); + + while (rt_flash_busy()) + { + ri_yield(); + } + + err_code |= rt_flash_store (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + record_idx, + &m_log_input_block, sizeof (m_log_input_block)); + + while (rt_flash_busy()) + { + ri_yield(); + } + + RD_ERROR_CHECK (err_code, ~RD_ERROR_FATAL); + // Erase another block and try again if there was error. + } while (RD_SUCCESS != err_code && num_tries < APP_FLASH_LOG_DATA_RECORDS_NUM); + + memset (&m_log_input_block, 0, sizeof (m_log_input_block)); + m_log_input_block.start_timestamp_s = end_timestamp; + record_idx++; + record_idx = record_idx % APP_FLASH_LOG_DATA_RECORDS_NUM; + return err_code; +} + +rd_status_t app_log_init (void) +{ + rd_status_t err_code = RD_SUCCESS; + // Defaults get overwritten by flash load and stored if the load fails. + app_log_config_t config = + { + .interval_s = APP_LOG_INTERVAL_S, + .overflow = APP_LOG_OVERFLOW, + .fields = { + .datas.temperature_c = APP_LOG_TEMPERATURE_ENABLED, + .datas.humidity_rh = APP_LOG_HUMIDITY_ENABLED, + .datas.pressure_pa = APP_LOG_PRESSURE_ENABLED + } + }; + err_code |= rt_flash_load (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &config, sizeof (config)); + + if (RD_ERROR_NOT_FOUND == err_code) + { + err_code = rt_flash_store (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &config, sizeof (config)); + } + + if (RD_SUCCESS == err_code) + { + memcpy (&m_log_config, &config, sizeof (config)); + } + + return err_code; +} + +rd_status_t app_log_process (const rd_sensor_data_t * const sample) +{ + rd_status_t err_code = RD_SUCCESS; + uint64_t next_sample_ms = m_last_sample_ms + (m_log_config.interval_s * 1000U); + LOGD ("LOG: Sample received\r\n"); + + // Always store first sample. + if (0 == m_last_sample_ms) + { + next_sample_ms = 0; + } + + //Check if new sample should be processed + if (next_sample_ms <= sample->timestamp_ms) + { + LOGD ("LOG: Storing sample\r\n"); + app_log_element_t element = + { + .timestamp_s = sample->timestamp_ms / 1000U, + .temperature_c = rd_sensor_data_parse (sample, RD_SENSOR_TEMP_FIELD), + .humidity_rh = rd_sensor_data_parse (sample, RD_SENSOR_HUMI_FIELD), + .pressure_pa = rd_sensor_data_parse (sample, RD_SENSOR_PRES_FIELD), + }; + + if (APP_LOG_MAX_SAMPLES > m_log_input_block.num_samples) + { + m_log_input_block.storage[m_log_input_block.num_samples++] = element; + } + + if (m_log_input_block.num_samples >= APP_LOG_MAX_SAMPLES) + { + LOG ("LOG: Storing block\r\n"); + err_code |= store_block (&m_log_input_block); + RD_ERROR_CHECK (err_code, RD_SUCCESS); + } + + m_last_sample_ms = sample->timestamp_ms; + m_log_input_block.end_timestamp_s = sample->timestamp_ms / 1000U; + } + else + { + // No action needed. + } + + return err_code; +} + +/** + * @brief Load new block to be read if needed. + * + * Can also copy input block to + * output block if there's no more stored blocks in flash. + */ +static rd_status_t app_log_read_load_block (app_log_read_state_t * const p_rs) +{ + rd_status_t err_code = RD_SUCCESS; + + if ( (0 == p_rs->element_idx) + && (0 == p_rs->page_idx)) + { + // Clear out previous state + err_code |= rt_flash_load (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + p_rs->page_idx, + &m_log_output_block, sizeof (m_log_output_block)); + p_rs->page_idx++; + } + else if ( (APP_FLASH_LOG_DATA_RECORDS_NUM > p_rs->page_idx) + && (p_rs->element_idx >= m_log_output_block.num_samples)) + { + // Returns NOT_FOUND if page IDX is not in flash. + err_code |= rt_flash_load (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + p_rs->page_idx, + &m_log_output_block, sizeof (m_log_output_block)); + p_rs->page_idx++; + p_rs->element_idx = 0; + } + else if ( (APP_FLASH_LOG_DATA_RECORDS_NUM == p_rs->page_idx) + && (p_rs->element_idx >= m_log_output_block.num_samples)) + { + memcpy (&m_log_output_block, &m_log_input_block, sizeof (m_log_output_block)); + p_rs->page_idx++; + p_rs->element_idx = 0; + } + else + { + // No action needed. + } + + // Clear out state if block was not found + if (RD_ERROR_NOT_FOUND == err_code) + { + memset (&m_log_output_block, 0, sizeof (m_log_output_block)); + } + + return err_code; +} + +/** + * @brief Forward read state to first next valid element. + * + * @retval RD_SUCCESS p_rs points to a valid element + * @retval RD_ERROR_NOT_FOUND if block doesn't have a valid element. + */ +static rd_status_t app_log_read_fast_forward (app_log_read_state_t * const p_rs) +{ + rd_status_t err_code = RD_SUCCESS; + uint64_t ts = m_log_output_block.storage[p_rs->element_idx].timestamp_s * 1000U; + + while ( (p_rs->oldest_element_ms > ts) + && (p_rs->element_idx < m_log_output_block.num_samples)) + { + p_rs->element_idx++; + } + + if (p_rs->element_idx >= m_log_output_block.num_samples) + { + err_code |= RD_ERROR_NOT_FOUND; + } + + return err_code; +} + +static rd_status_t app_log_read_populate (rd_sensor_data_t * const sample, + app_log_read_state_t * const p_rs) +{ + rd_status_t err_code = RD_SUCCESS; + + if (p_rs->element_idx < m_log_output_block.num_samples) + { + const app_log_element_t * const p_el = &m_log_output_block.storage[p_rs->element_idx]; + rd_sensor_data_set (sample, RD_SENSOR_TEMP_FIELD, p_el->temperature_c); + rd_sensor_data_set (sample, RD_SENSOR_HUMI_FIELD, p_el->humidity_rh); + rd_sensor_data_set (sample, RD_SENSOR_PRES_FIELD, p_el->pressure_pa); + sample->timestamp_ms = (uint64_t) (p_el->timestamp_s) * 1000U; + p_rs->element_idx++; + } + else if (p_rs->page_idx >= APP_FLASH_LOG_DATA_RECORDS_NUM) + { + err_code |= RD_ERROR_NOT_FOUND; + } + else + { + // No action required. + } + + return err_code; +} + +rd_status_t app_log_read (rd_sensor_data_t * const sample, + app_log_read_state_t * const p_rs) +{ + rd_status_t err_code = RD_SUCCESS; + + if ( (NULL != sample) + && (NULL != p_rs)) + { + // Load new block if needed + do + { + err_code = app_log_read_load_block (p_rs); + + // Decompress block -todo. + // Check if ths block contains data in desired time range - TODO + // Fast forward to start of desired time range. + if (RD_SUCCESS == err_code) + { + err_code |= app_log_read_fast_forward (p_rs); + } + } while ( (err_code != RD_SUCCESS) + + && (p_rs->page_idx <= APP_FLASH_LOG_DATA_RECORDS_NUM)); + + // AStyle fails here. ^ + // Populate record + err_code |= app_log_read_populate (sample, p_rs); + } + else + { + err_code = RD_ERROR_NULL; + } + + return err_code; +} + +rd_status_t app_log_config_set (const app_log_config_t * const configuration) +{ + rd_status_t err_code = RD_SUCCESS; + + if (NULL == configuration) + { + err_code |= RD_ERROR_NULL; + } + else + { + err_code |= rt_flash_store (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &configuration, sizeof (configuration)); + + if (RD_SUCCESS == err_code) + { + store_block (&m_log_input_block); + memcpy (&m_log_config, configuration, sizeof (m_log_config)); + } + } + + return err_code; +} + +rd_status_t app_log_config_get (app_log_config_t * const configuration) +{ + rd_status_t err_code = RD_SUCCESS; + err_code |= rt_flash_load (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + configuration, sizeof (configuration)); + memcpy (configuration, &m_log_config, sizeof (m_log_config)); + return err_code; +} + + +#else +//Dummy implementation to save flash space +rd_status_t app_log_init (void) +{ + return RD_SUCCESS; +} +rd_status_t app_log_process (const rd_sensor_data_t * const sample) +{ + return RD_SUCCESS; +} +rd_status_t app_log_config_get (app_log_config_t * const configuration) +{ + return RD_SUCCESS; +} +rd_status_t app_log_config_set (const app_log_config_t * const configuration) +{ + return RD_SUCCESS; +} +rd_status_t app_log_read (rd_sensor_data_t * const sample, + app_log_read_state_t * const p_read_state) +{ + return RD_ERROR_NOT_FOUND; +} +#endif + +/** @} */ diff --git a/src/app_log.h b/src/app_log.h new file mode 100644 index 00000000..5a20532f --- /dev/null +++ b/src/app_log.h @@ -0,0 +1,129 @@ +#ifndef APP_LOG_H +#define APP_LOG_H +/** + * @addtogroup app + */ +/** @{ */ +/** + * @defgroup app_log Storing sensor data to memory and reading logs. + * + */ +/** @} */ +/** + * @addtogroup app_log + */ +/** @{ */ +/** + * @file app_log.h + * @author Otso Jousimaa + * @date 2020-07-17 + * @copyright Ruuvi Innovations Ltd, license BSD-3-Clause. + */ + +#include "ruuvi_boards.h" +#include "ruuvi_driver_error.h" +#include "ruuvi_driver_sensor.h" + +#define STORAGE_RECORD_HEADER_SIZE (96U) //!< bytes allocated for header. +/** @brief bytes of compressed data. */ +#define STORAGE_BLOCK_SIZE (RB_FLASH_PAGE_SIZE - STORAGE_RECORD_HEADER_SIZE) + +typedef struct +{ + uint16_t interval_s; //!< Interval to log at, in seconds. + /** + * True -> erase old elements automatically. + * False -> return RD_ERROR_NO_MEMORY when full. + */ + bool overflow; + rd_sensor_data_fields_t fields; //!< Fields to log. +} app_log_config_t; //!< Logging configuration. + +// TODO: Generalize to N elements. +typedef struct +{ + uint32_t timestamp_s; + float temperature_c; + float humidity_rh; + float pressure_pa; +} app_log_element_t; + +typedef struct +{ + uint8_t page_idx; //!< Index of page being read. + uint16_t element_idx; //!< Index of element being read. + const uint64_t oldest_element_ms; //!< Age of oldest element to return in system time. +} app_log_read_state_t; //!< Log read state. + +#define APP_LOG_MAX_SAMPLES (STORAGE_BLOCK_SIZE/sizeof(app_log_element_t)) + +typedef struct +{ + uint32_t start_timestamp_s; //!< Timestamp of first sample. + uint32_t end_timestamp_s; //!< Timestamp of last sample. + size_t num_samples; //!< Number of samples in block. + app_log_config_t block_configuration; //!< Configuration of this data block. + app_log_element_t storage[APP_LOG_MAX_SAMPLES]; //!< Raw storage. +} app_log_record_t; //!< Record for application sensor logs. + +/** + * @brief Initialize logging. + * + * After initialization flash driver is ready to store data. + * If there is a logging configuration stored to flash, stored configuration is used. + * If not, default configuration is used and stored to flash. + * + * @retval RD_SUCCESS if logging was initialized. + */ +rd_status_t app_log_init(); + +/** + * @brief Process data into log. + * + * If time elapsed since last logged element is larger than logging interval, data is + * stored to RAM buffer. The RAM buffer may be compressed. When the buffer fills + * @ref STORAGE_BLOCK_SIZE bytes it will be written to flash. + * If there is no more room for new blocks in flash, oldest flash block is erased and + * replaced with new data. + * + * @retval RD_SUCCESS Data was logged. + * @retval RD_ERROR_NO_MEMORY Data cannot be stored to flash and overflow is configured + * as false. + * @retval RD_ERROR_BUSY Previous operation is in process, e.g. writing to flash. + */ +rd_status_t app_log_process (const rd_sensor_data_t * const sample); + +/** + * @brief Get data from log. + * + * Searches for first logged samples after given timestamp and returns them + * without guarantees about sample order. Loop over this function to get all + * logged data. + * + * @param[out] sample Sensor sample. + * @param[in,out] p_read_state State of reads. + * + * @retval RD_SUCCESS if a sample was retrieved. + * @retval RD_ERROR_NULL if either parameter is NULL. + * @ + * @retval RD_ERROR_NOT_FOUND if no newer data than requested timestamp was found. + * + */ +rd_status_t app_log_read (rd_sensor_data_t * const sample, + app_log_read_state_t * const p_read_state); + +/** + * @brief Configure logging. + * + * Calling this function will flush current log buffer into flash, possibly leading + * to NULL entries. + */ +rd_status_t app_log_config_set (const app_log_config_t * const configuration); + +/** + * @brief Read current logging configuration. + */ +rd_status_t app_log_config_get (app_log_config_t * const configuration); + +/** @} */ +#endif // APP_LOG_H diff --git a/src/app_sensor.c b/src/app_sensor.c index dd993472..3f484a0f 100644 --- a/src/app_sensor.c +++ b/src/app_sensor.c @@ -1,8 +1,10 @@ #include "app_config.h" +#include "app_log.h" #include "app_sensor.h" #include "ruuvi_boards.h" #include "ruuvi_driver_error.h" #include "ruuvi_driver_sensor.h" +#include "ruuvi_endpoints.h" #include "ruuvi_interface_communication_radio.h" #include "ruuvi_interface_gpio.h" #include "ruuvi_interface_gpio_interrupt.h" @@ -15,9 +17,12 @@ #include "ruuvi_interface_rtc.h" #include "ruuvi_interface_shtcx.h" #include "ruuvi_interface_spi.h" +#include "ruuvi_interface_yield.h" #include "ruuvi_task_adc.h" #include "ruuvi_task_sensor.h" +#include + static inline void LOG (const char * const msg) { ri_log (RI_LOG_LEVEL_INFO, msg); @@ -42,6 +47,7 @@ static inline void LOG (const char * const msg) * @endcode */ #define APP_SENSOR_HANDLE_UNUSED (0xFFU) //!< Mark sensor unavailable with this handle. +#define APP_SENSOR_COMM_TIMEOUT_MS (2000U) //!< Timeout for comms. #ifndef CEEDLING static @@ -50,6 +56,22 @@ rt_sensor_ctx_t * m_sensors[SENSOR_COUNT]; //!< Sensor APIs. static uint64_t vdd_update_time; //!< timestamp of VDD update. static uint32_t m_event_counter; //!< Number of events registered in app_sensor. +/** + * @brief Sensor operation, such as read or configure. + * + * These are used when outside central commands sensor to e.g. configure a sensor + * or read log. Operations are targeted on specific data types, such as + * temperature or acceleration. The operation is execcuted on first + * provider of given data if applicable. + * + * @param[in] reply_fp A function to which send operation acknowledgement. + * @param[in] fields Affected fields. + * @param[in] raw_message Original message triggering operation. + */ +typedef rd_status_t (*sensor_op) (const ri_comm_xfer_fp_t reply_fp, + const rd_sensor_data_fields_t fields, + const uint8_t * const raw_message); + #if APP_SENSOR_BME280_ENABLED static rt_sensor_ctx_t bme280 = { @@ -384,6 +406,7 @@ rd_status_t app_sensor_init (void) if (RD_SUCCESS == err_code) { app_sensor_rtc_init(); + for (size_t ii = 0; ii < SENSOR_COUNT; ii++) { rd_status_t init_code = RD_SUCCESS; @@ -579,6 +602,402 @@ rd_status_t app_sensor_acc_thr_set (float * const threshold_g) return err_code; } +/** + * @brief Determine which fields are affected by given endpoint. + * + * @param[in] type Ruuvi Endpoint type. + * @return Ruuvi Driver fields corresponding to endpoint. + */ +static rd_sensor_data_fields_t re2rd_fields (const re_type_t type) +{ + rd_sensor_data_fields_t fields = {0}; + + switch (type) + { + case RE_ACC_XYZ: + fields.datas.acceleration_x_g = 1; + fields.datas.acceleration_y_g = 1; + fields.datas.acceleration_z_g = 1; + break; + + case RE_ACC_X: + fields.datas.acceleration_x_g = 1; + break; + + case RE_ACC_Y: + fields.datas.acceleration_y_g = 1; + break; + + case RE_ACC_Z: + fields.datas.acceleration_z_g = 1; + break; + + case RE_GYR_XYZ: + fields.datas.gyro_x_dps = 1; + fields.datas.gyro_y_dps = 1; + fields.datas.gyro_z_dps = 1; + break; + + case RE_GYR_X: + fields.datas.gyro_x_dps = 1; + break; + + case RE_GYR_Y: + fields.datas.gyro_y_dps = 1; + break; + + case RE_GYR_Z: + fields.datas.gyro_z_dps = 1; + break; + + case RE_ENV_ALL: + fields.datas.humidity_rh = 1; + fields.datas.pressure_pa = 1; + fields.datas.temperature_c = 1; + break; + + case RE_ENV_HUMI: + fields.datas.humidity_rh = 1; + break; + + case RE_ENV_PRES: + fields.datas.pressure_pa = 1; + break; + + case RE_ENV_TEMP: + fields.datas.temperature_c = 1; + break; + + default: + RD_ERROR_CHECK (RD_ERROR_NOT_IMPLEMENTED, ~RD_ERROR_FATAL); + break; + } + + return fields; +} + +/** + * @brief Convert Ruuvi Driver data type to Ruuvi endpoint header. + * + * @param[in] field Data field to convert, exactly one must be set. + * @return Ruuvi Endpoint constant corresponding to field. 0 if field is invalid. + */ +static uint8_t rd2re_fields (const rd_sensor_data_bitfield_t fields) +{ + // Implementing field->header assigments as a look-up table would + // rely on specific representation of bitfield at compile time. + // Little-endian, big-endian, BE-8, BE-32 etc. Using if-else + // here for robustness. + uint8_t header_value = 0; + + if (fields.acceleration_x_g) { header_value = RE_ACC_X; } + else if (fields.acceleration_y_g) { header_value = RE_ACC_Y; } + else if (fields.acceleration_z_g) { header_value = RE_ACC_Z; } + else if (fields.gyro_x_dps) { header_value = RE_GYR_X; } + else if (fields.gyro_y_dps) { header_value = RE_GYR_Y; } + else if (fields.gyro_z_dps) { header_value = RE_GYR_Z; } + else if (fields.humidity_rh) { header_value = RE_ENV_HUMI; } + else if (fields.pressure_pa) { header_value = RE_ENV_PRES; } + else if (fields.temperature_c) { header_value = RE_ENV_TEMP; } + else + { + // No action needed + } + + return header_value; +} + +/** + * @brief encode log element into given buffer. + * + * @param[in] buffer Buffer to encode data into. + * @param[in] timestamp_ms Float value to encode. + * @param[in] data Float value to encode. + * @param[in] type Type of data to encode. Only one type/message is implemented. + * + * @note This function will not set the destination endpoint in buffer. + * @retval RD_SUCCESS Data was encoded successfully. + * @retval RD_ERROR_NULL Buffer or data was NULL. + * @retval RD_ERROR_INVALID_PARAM Type had no field set. + * @retval RD_ERROR_NOT_IMPLEMENTED Type had > 1 field set or encoding type is not implemented. + */ +static rd_status_t app_sensor_encode_log (uint8_t * const buffer, + const uint64_t timestamp_ms, + const float data, + const rd_sensor_data_bitfield_t type) +{ + rd_status_t err_code = RD_SUCCESS; + const uint8_t source = rd2re_fields (type); + + if (0 != source) + { + re_log_write_header (buffer, source); + re_log_write_timestamp (buffer, timestamp_ms); + re_log_write_data (buffer, data, source); + } + else + { + err_code |= RD_ERROR_INVALID_PARAM; + } + + return err_code; +} + +/** + * @brief Blocking send message function. + * + * Calls reply_fp with given data, and if reply_fp returns ERR_NO_MEM + * yields and retries. Has optional timeout. Function will return once + * message has been queued to driver buffer, not necessarily sent. + * + * @param[in] reply_fp Function pointer to use to send the data. + * @param[in] msg Message to send. + * @param[in] timeout_ms Number of milliseconds until timeout. 0 to disable. + * @retval RD_SUCCESS Message was queued to TX buffer. + * @retval RD_ERROR_TIMEOUT Message was queued to TX buffer. + * @return Error code from driver, such as RD_ERROR_INVALID_STATE. + * + * @note Timeout requires RTC and some process which brings thread out + * of yield. + */ +static rd_status_t app_sensor_blocking_send (const ri_comm_xfer_fp_t reply_fp, + ri_comm_message_t * const msg, + const uint32_t timeout_ms) +{ + rd_status_t err_code = RD_SUCCESS; + const uint64_t now = ri_rtc_millis(); + + do + { + err_code = reply_fp (msg); + + if (RD_ERROR_NO_MEM == err_code) + { + ri_yield(); + } + + if ( (0 != timeout_ms) + && (ri_rtc_millis() > (now + timeout_ms))) + { + err_code |= RD_ERROR_TIMEOUT; + } + } while (RD_ERROR_NO_MEM == err_code); + + return err_code; +} + +/** + * if valid data in sample + * parse data type + * parse data value + * format msg + * send msg + */ +static rd_status_t send_field (const ri_comm_xfer_fp_t reply_fp, + const uint8_t * const raw_message, + const rd_sensor_data_bitfield_t type, + const float value, + const int64_t real_time_ms) +{ + rd_status_t err_code = RD_SUCCESS; + ri_comm_message_t msg = {0}; + err_code |= app_sensor_encode_log (msg.data, real_time_ms, value, + type); + + if (RD_SUCCESS == err_code) + { + msg.repeat_count = 1; + msg.data_length = RE_STANDARD_MESSAGE_LENGTH; + msg.data[RE_STANDARD_DESTINATION_INDEX] = raw_message[RE_STANDARD_SOURCE_INDEX]; + err_code |= app_sensor_blocking_send (reply_fp, &msg, APP_SENSOR_COMM_TIMEOUT_MS); + } + + return err_code; +} + +/** + * @brief Send data element. + * + * This function sends given data element to given reply function pointer. + * + * @param[in] reply_fp Function pointer to reply to. + * @param[in] raw_message original query from remote. + * @param[in] sample Data sample to send. + * @param[in] time_offset_ms Offset between tag time and real time. + * @retval RD_SUCCESS reply was sent successfully. + * @retval error code from reply_fp in case of error. + * + * @note This function blocks until reply_fp returns something else than + * RD_ERROR_NO_MEM. + */ +static rd_status_t app_sensor_send_data (const ri_comm_xfer_fp_t reply_fp, + const uint8_t * const raw_message, + const rd_sensor_data_t * const sample, + const int64_t time_offset_ms) +{ + rd_status_t err_code = RD_SUCCESS; + const uint8_t fieldcount = rd_sensor_data_fieldcount (sample); + int64_t real_time_ms = 0; + + if ( (0 - time_offset_ms) < (int64_t) sample->timestamp_ms) + { + real_time_ms = sample->timestamp_ms + time_offset_ms; + } + + for (uint8_t ii = 0; ii < fieldcount; ii++) + { + if (rd_sensor_has_valid_data (sample, ii)) + { + rd_sensor_data_bitfield_t type = rd_sensor_field_type (sample, ii); + err_code |= send_field (reply_fp, raw_message, + type, sample->data[ii], real_time_ms); + } + } + + return err_code; +} + +/** + * @brief Send no more sensor log data message. + * TODO -refactor encoding to endpoints. + * TODO -refactor into comms + */ +static rd_status_t app_sensor_send_eof (const ri_comm_xfer_fp_t reply_fp, + const uint8_t * const raw_message) +{ + rd_status_t err_code = RD_SUCCESS; + ri_comm_message_t msg = {0}; + msg.data_length = RE_STANDARD_MESSAGE_LENGTH; + msg.data[RE_STANDARD_DESTINATION_INDEX] = raw_message[RE_STANDARD_SOURCE_INDEX]; + msg.data[RE_STANDARD_SOURCE_INDEX] = raw_message[RE_STANDARD_DESTINATION_INDEX]; + msg.data[RE_STANDARD_OPERATION_INDEX] = RE_STANDARD_LOG_VALUE_WRITE; + memset (&msg.data[RE_STANDARD_HEADER_LENGTH], 0xFF, RE_STANDARD_PAYLOAD_LENGTH); + app_sensor_blocking_send (reply_fp, &msg, APP_SENSOR_COMM_TIMEOUT_MS); + return err_code; +} + +/** + * @brief Log read sensor op. + * + * @ref sensor_op. + * + * @param[in] reply_fp Function pointer to which send logs. + * @param[fields] Fields to read. + * @param[raw_message] Original message from remote. + * @retval RD_SUCCESS on success. + * @retval RD_ERROR_INVALID_PARAM if start of logs is after current time. + * @retval error code from reply_fp if reply fails. + * + * @note This function blocks until all requested logs are sent and will therefore + * block for a long time. + */ +static rd_status_t app_sensor_log_read (const ri_comm_xfer_fp_t reply_fp, + const rd_sensor_data_fields_t fields, + const uint8_t * const raw_message) +{ + rd_status_t err_code = RD_SUCCESS; + rd_sensor_data_t sample = {0}; + app_log_read_state_t rs = {0}; + sample.fields = fields; + float data[rd_sensor_data_fieldcount (&sample)]; + sample.data = data; + // Parse start, end times. + uint32_t current_time_s = re_std_log_current_time (raw_message); + uint32_t start_s = re_std_log_start_time (raw_message); + + // Cannot have start_s >= current_time_s + if (current_time_s > start_s) + { + // Parse offset to system clock - flows over in 68 years. + LOG ("Sending logged data\r\n"); + int32_t system_time_s = (int32_t) (ri_rtc_millis() / 1000U); + int64_t offset_ms = ( (int64_t) current_time_s - (int64_t) system_time_s) * + (int64_t) 1000; + + if (offset_ms > sample.timestamp_ms) + { + sample.timestamp_ms = 0; + } + else + { + sample.timestamp_ms -= offset_ms; + } + + while (RD_SUCCESS == err_code) + { + // Reset data validity + sample.valid.bitfield = 0; + // Timestamp and fields are set in log read function. + err_code |= app_log_read (&sample, &rs); + + // If data element was found, send log element. + if (RD_SUCCESS == err_code) + { + err_code |= app_sensor_send_data (reply_fp, raw_message, + &sample, offset_ms); + } + else if (RD_ERROR_NOT_FOUND == err_code) + { + err_code |= app_sensor_send_eof (reply_fp, raw_message); + LOG ("Logged data sent\r\n"); + } + else + { + // No action needed, error code gets returned to caller. + } + } + } + // Start time >= current time + else + { + err_code |= RD_ERROR_INVALID_PARAM; + } + + // Send op status, remove expected not found + err_code &= ~RD_ERROR_NOT_FOUND; + return err_code; +} + +rd_status_t app_sensor_handle (const ri_comm_xfer_fp_t reply_fp, + const uint8_t * const raw_message, + const uint16_t data_len) +{ + rd_status_t err_code = RD_SUCCESS; + + if (NULL == raw_message) + { + err_code |= RD_ERROR_NULL; + } + else if (data_len < RE_STANDARD_MESSAGE_LENGTH) + { + err_code |= RD_ERROR_DATA_SIZE; + } + else + { + // Parse affected fields. It's ok to have unspecified value here, + // in that case fields end up being empty and error is reported via reply_fp. + re_type_t type = (re_type_t) raw_message[RE_STANDARD_DESTINATION_INDEX]; + rd_sensor_data_fields_t target_fields = re2rd_fields (type); + // Parse desired operation. + re_op_t op = (re_op_t) raw_message[RE_STANDARD_OPERATION_INDEX]; + + // If target and op are valid, execute. + switch (op) + { + case RE_STANDARD_LOG_VALUE_READ: + err_code |= app_sensor_log_read (reply_fp, + target_fields, raw_message); + break; + + default: + // Reply with error on unknown op. + break; + } + } + + return err_code; +} + #ifdef RUUVI_RUN_TESTS void app_sensor_ctx_get (rt_sensor_ctx_t *** p_sensors, size_t * num_sensors) { diff --git a/src/app_sensor.h b/src/app_sensor.h index 8874cd92..8caa3bcb 100644 --- a/src/app_sensor.h +++ b/src/app_sensor.h @@ -28,6 +28,7 @@ #include "app_config.h" #include "ruuvi_boards.h" #include "ruuvi_driver_error.h" +#include "ruuvi_interface_communication.h" #include "ruuvi_task_sensor.h" #define APP_SENSOR_SELFTEST_RETRIES (5U) //!< Number of times to retry init on self-test fail. @@ -187,6 +188,32 @@ uint32_t app_sensor_event_count_get (void); */ rd_status_t app_sensor_acc_thr_set (float * threshold_g); +/** + * @brief Handle data coming in to the application. + * + * Interprets the desired action, executes it and aknowledges to + * reply_fp with ri_comm_message_t containing ruuvi endpoint encoded message. + * + * For example a Log Read command gets replied with all the logged elements. + * + * + * @param[in] ri_reply_fp Function pointer to send replies to. + * @param[in] raw_message Payload sent by client. Must be a standard Ruuvi Endpoint + * message. + * @param[in] data_len Payload sent by client. + * + * @retval RD_SUCCESS Data was handled, including replying with error to reply_fp. + * @retval RD_ERROR_NULL Raw message or replyfp is NULL. + * @retval RD_ERROR_DATA_SIZE data_len is less than RE_STANDARD_MESSAGE_LENGTH. + * + * @warning Executing these commands can be resource intensive. Consider + * stopping app_heartbeat before entering this function. + * + */ +rd_status_t app_sensor_handle (const ri_comm_xfer_fp_t ri_reply_fp, + const uint8_t * const raw_message, + const uint16_t data_len); + #ifdef RUUVI_RUN_TESTS void app_sensor_ctx_get (rt_sensor_ctx_t *** m_sensors, size_t * num_sensors); diff --git a/src/application_config/app_config.h b/src/application_config/app_config.h index 28d69fce..b4da0b6c 100644 --- a/src/application_config/app_config.h +++ b/src/application_config/app_config.h @@ -195,7 +195,8 @@ #endif #ifndef APP_GATT_ENABLED -# define APP_GATT_ENABLED (RB_APP_PAGES > 0U) //!< If Flash is at premium, cut GATT off by default +//!< If Flash is at premium, cut GATT off by default. +# define APP_GATT_ENABLED (RB_FLASH_SPACE_AVAILABLE > RB_FLASH_SPACE_SMALL) #endif /** @brief Enable GATT tasks */ @@ -213,8 +214,22 @@ # define RI_COMM_ENABLED RT_COMMUNICATION_ENABLED #endif +/** @brief Enable Flash tasks if there is storage space */ +#ifndef RT_FLASH_ENABLED +# define RT_FLASH_ENABLED (RB_FLASH_SPACE_AVAILABLE > RB_FLASH_SPACE_SMALL) +#endif + +/** @brief Enable Ruuvi Flash interface. */ +#define RI_FLASH_ENABLED RT_FLASH_ENABLED + // ***** Flash storage constants *****/ -// These constants can be any non-zero uint8, but two files and two records in same file can't have same ID. + +#define APP_FLASH_PAGES (16U) //!< 64 kB flash storage if page size is 4 kB. +#define APP_FLASH_LOG_DATA_RECORDS_NUM (APP_FLASH_PAGES - 2U) //!< swap page + settings. + +// File constants can be any non-zero uint8. +// Record constants can be any non-zero uint16 +// Two files and two records in same file can't have same ID. #define APP_FLASH_SENSOR_FILE (0xCEU) #define APP_FLASH_SENSOR_NTC_RECORD (0xC1U) #define APP_FLASH_SENSOR_PHOTO_RECORD (0xC2U) @@ -222,6 +237,27 @@ #define APP_FLASH_SENSOR_LIS2DH12_RECORD (0x2DU) #define APP_FLASH_SENSOR_BME280_RECORD (0x28U) +#define APP_FLASH_LOG_FILE (0xF0U) +#define APP_FLASH_LOG_CONFIG_RECORD (0x01U) +#define APP_FLASH_LOG_DATA_RECORD_PREFIX (0xF0U) //!< Prefix, append with U8 number + +// ** Logging constants ** // +#ifndef APP_LOG_INTERVAL_S +# define APP_LOG_INTERVAL_S (5U * 60U) +#endif +#ifndef APP_LOG_OVERFLOW +# define APP_LOG_OVERFLOW (true) +#endif +#ifndef APP_LOG_TEMPERATURE_ENABLED +# define APP_LOG_TEMPERATURE_ENABLED (true) +#endif +#ifndef APP_LOG_HUMIDITY_ENABLED +# define APP_LOG_HUMIDITY_ENABLED (true) +#endif +#ifndef APP_LOG_PRESSURE_ENABLED +# define APP_LOG_PRESSURE_ENABLED (true) +#endif + /** @brief Enable ADC tasks */ #ifndef RT_ADC_ENABLED # define RT_ADC_ENABLED (1U) @@ -271,13 +307,6 @@ # define RI_I2C_ENABLED (1U) #endif -/** - * @brief Enable Ruuvi Flash interface on boards with enough RAM & Flash - */ -#ifndef RI_FLASH_ENABLED -# define RI_FLASH_ENABLED (RB_APP_PAGES > 0U) -#endif - /** @brief Enable Ruuvi led tasks. */ #ifndef RT_LED_ENABLED # define RT_LED_ENABLED (1U) @@ -329,8 +358,8 @@ /** @brief Logs reserve lot of flash, enable only on debug builds */ #ifndef RI_LOG_ENABLED -#define RI_LOG_ENABLED (0U) -#define APP_LOG_LEVEL RI_LOG_LEVEL_NONE +# define RI_LOG_ENABLED (0U) +# define APP_LOG_LEVEL RI_LOG_LEVEL_NONE #endif /*@}*/ diff --git a/src/application_config/application_mode_debug.h b/src/application_config/application_mode_debug.h index 1f218eca..3bd97be7 100644 --- a/src/application_config/application_mode_debug.h +++ b/src/application_config/application_mode_debug.h @@ -10,4 +10,8 @@ # define APP_HEARTBEAT_INTERVAL_MS (221U) #endif +#ifndef APP_LOG_INTERVAL_S +# define APP_LOG_INTERVAL_S (1U) //!< Gets limited to heartbeat rate. +#endif + #endif \ No newline at end of file diff --git a/src/gcc_sources.make b/src/gcc_sources.make index f689774e..2d2d9922 100644 --- a/src/gcc_sources.make +++ b/src/gcc_sources.make @@ -180,6 +180,7 @@ RUUVI_LIB_SOURCES= \ $(PROJ_DIR)/ruuvi.drivers.c/src/tasks/ruuvi_task_sensor.c \ $(PROJ_DIR)/ruuvi.drivers.c/src/ruuvi_driver_error.c \ $(PROJ_DIR)/ruuvi.drivers.c/src/ruuvi_driver_sensor.c \ + $(PROJ_DIR)/ruuvi.endpoints.c/src/ruuvi_endpoints.c \ $(PROJ_DIR)/ruuvi.endpoints.c/src/ruuvi_endpoint_3.c \ $(PROJ_DIR)/ruuvi.endpoints.c/src/ruuvi_endpoint_5.c \ $(PROJ_DIR)/ruuvi.libraries.c/src/libs/compress/ruuvi_library_compress.c \ @@ -201,6 +202,7 @@ RUUVI_PRJ_SOURCES= \ $(PROJ_DIR)/app_comms.c \ $(PROJ_DIR)/app_heartbeat.c \ $(PROJ_DIR)/app_led.c \ + $(PROJ_DIR)/app_log.c \ $(PROJ_DIR)/app_power.c \ $(PROJ_DIR)/app_sensor.c diff --git a/src/main.c b/src/main.c index 0ba77776..fae9000a 100644 --- a/src/main.c +++ b/src/main.c @@ -2,11 +2,7 @@ * @defgroup main Program main * */ -/*@}*/ -/** - * @addtogroup main - */ -/*@{*/ +/** @{ */ /** * @file main.c * @author Otso Jousimaa @@ -18,6 +14,7 @@ #include "app_comms.h" #include "app_heartbeat.h" #include "app_led.h" +#include "app_log.h" #include "app_power.h" #include "app_sensor.h" #include "main.h" @@ -29,6 +26,7 @@ #include "ruuvi_interface_watchdog.h" #include "ruuvi_interface_yield.h" #include "ruuvi_task_button.h" +#include "ruuvi_task_flash.h" #include "ruuvi_task_gpio.h" #include "ruuvi_task_led.h" @@ -53,24 +51,26 @@ void setup (void) # if (!RUUVI_RUN_TESTS) err_code |= ri_watchdog_init (APP_WDT_INTERVAL_MS, &on_wdt); err_code |= ri_log_init (APP_LOG_LEVEL); // Logging to terminal. - err_code |= ri_yield_init(); # endif + err_code |= ri_yield_init(); err_code |= ri_timer_init(); err_code |= ri_scheduler_init(); err_code |= rt_gpio_init(); + err_code |= ri_yield_low_power_enable (true); + err_code |= rt_flash_init(); err_code |= app_button_init(); err_code |= app_dc_dc_init(); err_code |= app_led_init(); err_code |= app_sensor_init(); - err_code |= app_sensor_acc_thr_set (&motion_threshold); + err_code |= app_log_init(); + // Allow fail on boards which do not have accelerometer. + (void) app_sensor_acc_thr_set (&motion_threshold); err_code |= app_comms_init(); err_code |= app_heartbeat_init(); + err_code |= app_heartbeat_start(); RD_ERROR_CHECK (err_code, RD_SUCCESS); } -/** - * @brief Actual main, redirected for Ceedling - */ #ifdef CEEDLING int app_main (void) #else diff --git a/src/main.h b/src/main.h index 1a67c191..6e1f1dec 100644 --- a/src/main.h +++ b/src/main.h @@ -21,10 +21,10 @@ */ // Submodule requirements -#define RUUVI_BOARDS_REQ "0.7.1" -#define RUUVI_DRIVERS_REQ "0.2.5" +#define RUUVI_BOARDS_REQ "0.7.2" +#define RUUVI_DRIVERS_REQ "0.3.1" #define RUUVI_ENDPOINTS_REQ "0.2.0" -#define RUUVI_LIBRARIES_REQ "0.2.0" +#define RUUVI_LIBRARIES_REQ "0.3.1" #ifdef CEEDLING void on_wdt (void); diff --git a/src/run_integration_tests.c b/src/run_integration_tests.c index 024a48f5..900df050 100644 --- a/src/run_integration_tests.c +++ b/src/run_integration_tests.c @@ -74,6 +74,7 @@ void integration_test_start (void) void integration_test_stop (void) { LOG ("}"); + ri_yield_uninit(); } static void integration_test_power (void) @@ -144,13 +145,17 @@ static void driver_integration_tests_run (void) ri_watchdog_feed(); ri_communication_ble_gatt_run_integration_test (&LOG, RI_RADIO_BLE_125KBPS); #endif -#if defined(RB_GPIO_TEST_INPUT) && defined(RB_GPIO_TEST_OUTPUT) - ri_communication_uart_run_integration_test (&LOG, RB_GPIO_TEST_INPUT, - RB_GPIO_TEST_OUTPUT); - ri_gpio_run_integration_test (&LOG, RB_GPIO_TEST_INPUT, RB_GPIO_TEST_OUTPUT); - ri_gpio_interrupt_run_integration_test (&LOG, RB_GPIO_TEST_INPUT, RB_GPIO_TEST_OUTPUT); - ri_gpio_pwm_run_integration_test (&LOG, RB_GPIO_TEST_INPUT, RB_GPIO_TEST_OUTPUT); -#endif + + if ( (RI_GPIO_ID_UNUSED != RB_GPIO_TEST_INPUT) + && (RI_GPIO_ID_UNUSED != RB_GPIO_TEST_OUTPUT)) + { + ri_communication_uart_run_integration_test (&LOG, RB_GPIO_TEST_INPUT, + RB_GPIO_TEST_OUTPUT); + ri_gpio_run_integration_test (&LOG, RB_GPIO_TEST_INPUT, RB_GPIO_TEST_OUTPUT); + ri_gpio_interrupt_run_integration_test (&LOG, RB_GPIO_TEST_INPUT, RB_GPIO_TEST_OUTPUT); + ri_gpio_pwm_run_integration_test (&LOG, RB_GPIO_TEST_INPUT, RB_GPIO_TEST_OUTPUT); + } + #if RB_NFC_INTERNAL_INSTALLED ri_watchdog_feed(); ri_communication_nfc_run_integration_test (&LOG); @@ -160,7 +165,7 @@ static void driver_integration_tests_run (void) /** @brief Run library integration tests */ static void library_integration_tests_run (void) { - ruuvi_library_test_all_run (&LOG); + rl_test_all_run (&LOG); } void integration_tests_run (void) diff --git a/src/ruuvi.boards.c b/src/ruuvi.boards.c index db1e27b0..e9239507 160000 --- a/src/ruuvi.boards.c +++ b/src/ruuvi.boards.c @@ -1 +1 @@ -Subproject commit db1e27b0774f3806b3eea948ae54cf0379e89d8c +Subproject commit e9239507bf1c1eaa3ae27a41bb34edaa5ca23a9e diff --git a/src/ruuvi.drivers.c b/src/ruuvi.drivers.c index 118b846b..cb0d9d1f 160000 --- a/src/ruuvi.drivers.c +++ b/src/ruuvi.drivers.c @@ -1 +1 @@ -Subproject commit 118b846bce7700e54f23745d044970d4f5e08205 +Subproject commit cb0d9d1f5e772a61d0243d63414ef004f730cb58 diff --git a/src/ruuvi.endpoints.c b/src/ruuvi.endpoints.c index 139e1921..80a74ecf 160000 --- a/src/ruuvi.endpoints.c +++ b/src/ruuvi.endpoints.c @@ -1 +1 @@ -Subproject commit 139e1921a06becdebeccff0e1c88a5ab618447e7 +Subproject commit 80a74ecf0d6abb8881bd9db31b422c25b103a58a diff --git a/src/ruuvi.firmware.c.emProject b/src/ruuvi.firmware.c.emProject index 09632a65..d797f479 100644 --- a/src/ruuvi.firmware.c.emProject +++ b/src/ruuvi.firmware.c.emProject @@ -366,18 +366,12 @@ path="application_config" recurse="Yes" /> - - - - - - - - - - - - + @@ -830,6 +824,7 @@ + @@ -855,6 +850,8 @@ + + @@ -908,6 +905,7 @@ + @@ -975,13 +973,13 @@ Name="ruuvi.endpoints.c" exclude="" filter="*.c;*.h" - path="ruuvi.endpoints.c" + path="ruuvi.endpoints.c/src" recurse="Yes" /> @@ -1206,7 +1204,7 @@ Name="ruuvi.libraries.c" exclude="" filter="*.c;*.h" - path="ruuvi.libraries.c" + path="ruuvi.libraries.c/src" recurse="Yes" /> diff --git a/src/ruuvi.libraries.c b/src/ruuvi.libraries.c index 3636239c..e053d1ea 160000 --- a/src/ruuvi.libraries.c +++ b/src/ruuvi.libraries.c @@ -1 +1 @@ -Subproject commit 3636239ce045b24c1b519bfe3b4853573f0bfd6a +Subproject commit e053d1ea291493c3409f9708d3695563b4290ade diff --git a/src/targets/kaarle/armgcc/kaarle.ld b/src/targets/kaarle/armgcc/kaarle.ld index 7c2aecb0..6d1b79b9 100644 --- a/src/targets/kaarle/armgcc/kaarle.ld +++ b/src/targets/kaarle/armgcc/kaarle.ld @@ -122,6 +122,12 @@ SECTIONS KEEP(*(SORT(.pwr_mgmt_data*))) PROVIDE(__stop_pwr_mgmt_data = .); } > FLASH + .storage_flash 0x60000 (NOLOAD) : + { + __start_storage_flash = .; + KEEP(*(SORT(.storage_flash*))) + __stop_storage_flash = . + 0x15000; + } > FLASH } INSERT AFTER .text diff --git a/src/targets/kaarle/ses/flash_placement.xml b/src/targets/kaarle/ses/flash_placement.xml index 32b70f59..ceb1fb3d 100644 --- a/src/targets/kaarle/ses/flash_placement.xml +++ b/src/targets/kaarle/ses/flash_placement.xml @@ -1,7 +1,7 @@ - + @@ -30,6 +30,8 @@ + + diff --git a/src/targets/kalervo/ses/flash_placement.xml b/src/targets/kalervo/ses/flash_placement.xml index 2c853599..4f6756d9 100755 --- a/src/targets/kalervo/ses/flash_placement.xml +++ b/src/targets/kalervo/ses/flash_placement.xml @@ -1,24 +1,28 @@ - + + - + - + - + + + + - + @@ -32,8 +36,8 @@ - + @@ -48,4 +52,4 @@ - + \ No newline at end of file diff --git a/src/targets/keijo/ses/flash_placement.xml b/src/targets/keijo/ses/flash_placement.xml index d196eb1f..4f6756d9 100755 --- a/src/targets/keijo/ses/flash_placement.xml +++ b/src/targets/keijo/ses/flash_placement.xml @@ -1,25 +1,28 @@ - + + - + - + - - + + + + - + @@ -33,8 +36,8 @@ - + @@ -49,4 +52,4 @@ - + \ No newline at end of file diff --git a/src/targets/ruuvitag_b/armgcc/ruuvitag_b.ld b/src/targets/ruuvitag_b/armgcc/ruuvitag_b.ld index 9383492b..a3a6273f 100644 --- a/src/targets/ruuvitag_b/armgcc/ruuvitag_b.ld +++ b/src/targets/ruuvitag_b/armgcc/ruuvitag_b.ld @@ -123,6 +123,12 @@ SECTIONS KEEP(*(SORT(.pwr_mgmt_data*))) PROVIDE(__stop_pwr_mgmt_data = .); } > FLASH + .storage_flash 0x60000 (NOLOAD) : + { + __start_storage_flash = .; + KEEP(*(SORT(.storage_flash*))) + __stop_storage_flash = . + 0x15000; + } > FLASH } INSERT AFTER .text diff --git a/src/targets/ruuvitag_b/ses/flash_placement.xml b/src/targets/ruuvitag_b/ses/flash_placement.xml index 32b70f59..ceb1fb3d 100644 --- a/src/targets/ruuvitag_b/ses/flash_placement.xml +++ b/src/targets/ruuvitag_b/ses/flash_placement.xml @@ -1,7 +1,7 @@ - + @@ -30,6 +30,8 @@ + + diff --git a/test/test_app_comms.c b/test/test_app_comms.c index df1c11c2..a3e93774 100644 --- a/test/test_app_comms.c +++ b/test/test_app_comms.c @@ -3,8 +3,13 @@ #include "app_config.h" #include "app_comms.h" #include "ruuvi_boards.h" +#include "ruuvi_endpoints.h" +#include "mock_app_heartbeat.h" +#include "mock_app_sensor.h" +#include "mock_ruuvi_driver_error.h" #include "mock_ruuvi_interface_communication_ble_advertising.h" #include "mock_ruuvi_interface_communication_radio.h" +#include "mock_ruuvi_interface_scheduler.h" #include "mock_ruuvi_task_advertisement.h" #include "mock_ruuvi_task_communication.h" #include "mock_ruuvi_task_gatt.h" @@ -18,6 +23,13 @@ void tearDown (void) { } +static void RD_ERROR_CHECK_EXPECT (rd_status_t err_code, rd_status_t fatal) +{ + rd_error_check_Expect (err_code, fatal, "", 0); + rd_error_check_IgnoreArg_p_file (); + rd_error_check_IgnoreArg_line (); +} + static void test_dis_init (ri_comm_dis_init_t * const p_dis) { rt_com_get_mac_str_ExpectAnyArgsAndReturn (RD_SUCCESS); @@ -80,6 +92,7 @@ void test_app_comms_init_ok (void) rt_gatt_nus_init_ExpectAndReturn (RD_SUCCESS); rt_gatt_set_on_connected_isr_Expect (&on_gatt_connected_isr); rt_gatt_set_on_disconn_isr_Expect (&on_gatt_disconnected_isr); + rt_gatt_set_on_received_isr_Expect (&on_gatt_data_isr); rt_gatt_enable_ExpectAndReturn (RD_SUCCESS); #endif rd_status_t err_code = app_comms_init(); @@ -97,4 +110,47 @@ void test_on_gatt_disconnected (void) { rt_gatt_enable_ExpectAndReturn (RD_SUCCESS); on_gatt_disconnected_isr (NULL, 0); +} + +void test_on_gatt_received (void) +{ + uint8_t data[RI_SCHEDULER_SIZE]; + ri_scheduler_event_put_ExpectAndReturn (data, sizeof (data), &handle_gatt, RD_SUCCESS); + RD_ERROR_CHECK_EXPECT (RD_SUCCESS, ~RD_ERROR_FATAL); + on_gatt_data_isr (data, sizeof (data)); +} + +void test_on_gatt_received_toobig (void) +{ + uint8_t toobig[RI_SCHEDULER_SIZE + 1]; + ri_scheduler_event_put_ExpectAndReturn (toobig, sizeof (toobig), &handle_gatt, + RD_ERROR_INVALID_LENGTH); + RD_ERROR_CHECK_EXPECT (RD_ERROR_INVALID_LENGTH, ~RD_ERROR_FATAL); + on_gatt_data_isr (toobig, sizeof (toobig)); +} + +void test_handle_gatt_sensor_op_acc (void) +{ + uint8_t mock_data[RE_STANDARD_MESSAGE_LENGTH] = {0}; + mock_data[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_ACCELERATION; + app_heartbeat_stop_ExpectAndReturn (RD_SUCCESS); + app_sensor_handle_ExpectAndReturn (&rt_gatt_send_asynchronous, mock_data, + sizeof (mock_data), RD_SUCCESS); + app_heartbeat_start_ExpectAndReturn (RD_SUCCESS); + RD_ERROR_CHECK_EXPECT (RD_SUCCESS, ~RD_ERROR_FATAL); + handle_gatt (mock_data, sizeof (mock_data)); +} + +void test_handle_gatt_null_data (void) +{ + RD_ERROR_CHECK_EXPECT (RD_ERROR_NULL, ~RD_ERROR_FATAL); + handle_gatt (NULL, 0); +} + +void test_handle_gatt_short_data (void) +{ + uint8_t mock_data[RE_STANDARD_HEADER_LENGTH] = {0}; + mock_data[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_ACCELERATION; + RD_ERROR_CHECK_EXPECT (RD_ERROR_INVALID_PARAM, ~RD_ERROR_FATAL); + handle_gatt (mock_data, sizeof (mock_data)); } \ No newline at end of file diff --git a/test/test_app_heartbeat.c b/test/test_app_heartbeat.c index 34ccdee8..17d9d2e2 100644 --- a/test/test_app_heartbeat.c +++ b/test/test_app_heartbeat.c @@ -3,6 +3,7 @@ #include "app_config.h" #include "app_heartbeat.h" #include "mock_app_comms.h" +#include "mock_app_log.h" #include "mock_app_sensor.h" #include "mock_ruuvi_driver_error.h" #include "mock_ruuvi_driver_sensor.h" @@ -96,6 +97,42 @@ void test_schedule_heartbeat_isr (void) schedule_heartbeat_isr (NULL); } +void test_app_heartbeat_stop (void) +{ + rd_status_t err_code = RD_SUCCESS; + ri_timer_id_t * p_heart_timer = get_heart_timer(); + ri_timer_stop_ExpectAndReturn (*p_heart_timer, RD_SUCCESS); + err_code |= app_heartbeat_stop(); + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_heartbeat_start (void) +{ + rd_status_t err_code = RD_SUCCESS; + ri_timer_id_t * p_heart_timer = get_heart_timer(); + ri_timer_start_ExpectAndReturn (*p_heart_timer, APP_HEARTBEAT_INTERVAL_MS, NULL, + RD_SUCCESS); + err_code |= app_heartbeat_start(); + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_heartbeat_start_notinit (void) +{ + rd_status_t err_code = RD_SUCCESS; + ri_timer_id_t * p_heart_timer = get_heart_timer(); + *p_heart_timer = NULL; + err_code |= app_heartbeat_start(); + TEST_ASSERT (RD_ERROR_INVALID_STATE == err_code); +} + +void test_app_heartbeat_stop_notinit (void) +{ + rd_status_t err_code = RD_SUCCESS; + ri_timer_id_t * p_heart_timer = get_heart_timer(); + *p_heart_timer = NULL; + err_code |= app_heartbeat_stop(); + TEST_ASSERT (RD_ERROR_INVALID_STATE == err_code); +} extern uint16_t m_measurement_count; @@ -125,6 +162,7 @@ void test_heartbeat_df5_all_ok (void) rt_gatt_send_asynchronous_ExpectAnyArgsAndReturn (RD_SUCCESS); rt_nfc_send_ExpectAnyArgsAndReturn (RD_SUCCESS); ri_watchdog_feed_ExpectAndReturn (RD_SUCCESS); + app_log_process_ExpectAnyArgsAndReturn (RD_SUCCESS); heartbeat (NULL, 0); } @@ -139,6 +177,7 @@ void test_heartbeat_df5_adv_ok (void) rt_gatt_send_asynchronous_ExpectAnyArgsAndReturn (RD_ERROR_INVALID_STATE); rt_nfc_send_ExpectAnyArgsAndReturn (RD_ERROR_NOT_ENABLED); ri_watchdog_feed_ExpectAndReturn (RD_SUCCESS); + app_log_process_ExpectAnyArgsAndReturn (RD_SUCCESS); heartbeat (NULL, 0); } @@ -152,6 +191,7 @@ void test_heartbeat_df5_none_ok (void) rt_adv_send_data_ExpectAnyArgsAndReturn (RD_ERROR_INVALID_STATE); rt_gatt_send_asynchronous_ExpectAnyArgsAndReturn (RD_ERROR_NOT_ENABLED); rt_nfc_send_ExpectAnyArgsAndReturn (RD_ERROR_NOT_ENABLED); + app_log_process_ExpectAnyArgsAndReturn (RD_SUCCESS); heartbeat (NULL, 0); } @@ -172,6 +212,7 @@ void test_heartbeat_df5_measurement_cnt_rollover (void) rt_gatt_send_asynchronous_ExpectAnyArgsAndReturn (RD_SUCCESS); rt_nfc_send_ExpectAnyArgsAndReturn (RD_SUCCESS); ri_watchdog_feed_ExpectAndReturn (RD_SUCCESS); + app_log_process_ExpectAnyArgsAndReturn (RD_SUCCESS); heartbeat (NULL, 0); resetTest(); // Avoid running out of memory. } diff --git a/test/test_app_log.c b/test/test_app_log.c new file mode 100644 index 00000000..7882cd09 --- /dev/null +++ b/test/test_app_log.c @@ -0,0 +1,854 @@ +#include "unity.h" + +#include "app_config.h" +#include "app_log.h" +#include "ruuvi_library.h" +#include "ruuvi_interface_communication.h" +#include "mock_ruuvi_driver_error.h" +#include "mock_ruuvi_driver_sensor.h" +#include "mock_ruuvi_endpoints.h" +#include "mock_ruuvi_interface_log.h" +#include "mock_ruuvi_interface_rtc.h" +#include "mock_ruuvi_interface_yield.h" +#include "mock_ruuvi_task_flash.h" +#include "mock_ruuvi_library_compress.h" + +#include + +#define NUM_FIELDS 4 //XXX, should support any number of fields +#define STORED_FIELDS ( APP_LOG_TEMPERATURE_ENABLED + \ + APP_LOG_HUMIDITY_ENABLED + \ + APP_LOG_PRESSURE_ENABLED) +#define IGNORE_RECORD_IDX 0xFFU + +const app_log_element_t e_1_1 = +{ + .timestamp_s = 800 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_1_2 = +{ + .timestamp_s = 1000 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_1_3 = +{ + .timestamp_s = 1200 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_1_4 = +{ + .timestamp_s = 1400 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_2_1 = +{ + .timestamp_s = 1600 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_2_2 = +{ + .timestamp_s = 1800 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_2_3 = +{ + .timestamp_s = 2000 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_2_4 = +{ + .timestamp_s = 2200 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_3_1 = +{ + .timestamp_s = 2400 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_3_2 = +{ + .timestamp_s = 2600 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_3_3 = +{ + .timestamp_s = 2800 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_3_4 = +{ + .timestamp_s = 3000 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_4_1 = +{ + .timestamp_s = 3200 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_4_2 = +{ + .timestamp_s = 3400 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_4_3 = +{ + .timestamp_s = 3600 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; +const app_log_element_t e_4_4 = +{ + .timestamp_s = 3800 * 1000, + .temperature_c = 0, + .humidity_rh = 0, + .pressure_pa = 0 +}; + +void setUp (void) +{ + ri_log_Ignore(); + rd_error_check_Ignore(); +} + +void tearDown (void) +{ +} + +extern app_log_record_t m_log_input_block; +extern app_log_record_t m_log_output_block; +extern app_log_config_t m_log_config; +extern uint64_t m_last_sample_ms; +#if RL_COMPRESS_ENABLED +extern rl_compress_state_t m_compress_state; +#endif + +static void sample_process_expect (const rd_sensor_data_t * const sample) +{ + if (APP_LOG_TEMPERATURE_ENABLED) + { + rd_sensor_data_parse_ExpectAndReturn (sample, RD_SENSOR_TEMP_FIELD, 0); + } + + if (APP_LOG_HUMIDITY_ENABLED) + { + rd_sensor_data_parse_ExpectAndReturn (sample, RD_SENSOR_HUMI_FIELD, 0); + } + + if (APP_LOG_PRESSURE_ENABLED) + { + rd_sensor_data_parse_ExpectAndReturn (sample, RD_SENSOR_PRES_FIELD, 0); + } +} + +static void sample_read_expect (rd_sensor_data_t * const sample, + const app_log_element_t * const p_el) +{ + if (APP_LOG_TEMPERATURE_ENABLED) + { + rd_sensor_data_set_Expect (sample, RD_SENSOR_TEMP_FIELD, p_el->temperature_c); + } + + if (APP_LOG_HUMIDITY_ENABLED) + { + rd_sensor_data_set_Expect (sample, RD_SENSOR_HUMI_FIELD, p_el->humidity_rh); + } + + if (APP_LOG_PRESSURE_ENABLED) + { + rd_sensor_data_set_Expect (sample, RD_SENSOR_PRES_FIELD, p_el->pressure_pa); + } +} + +static void store_block_expect (const uint8_t record_idx, const bool block_flash) +{ + rt_flash_free_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + record_idx, + RD_SUCCESS); + + if (IGNORE_RECORD_IDX == record_idx) + { + rt_flash_free_IgnoreArg_record_id(); + } + + rt_flash_busy_ExpectAndReturn (block_flash); + + if (block_flash) + { + ri_yield_ExpectAndReturn (RD_SUCCESS); + rt_flash_busy_ExpectAndReturn (false); + } + + rt_flash_gc_run_ExpectAndReturn (RD_SUCCESS); + rt_flash_busy_ExpectAndReturn (block_flash); + + if (block_flash) + { + ri_yield_ExpectAndReturn (RD_SUCCESS); + rt_flash_busy_ExpectAndReturn (false); + } + + rt_flash_store_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + record_idx, + &m_log_input_block, sizeof (m_log_input_block), + RD_SUCCESS); + + if (IGNORE_RECORD_IDX == record_idx) + { + rt_flash_store_IgnoreArg_record_id(); + } + + rt_flash_busy_ExpectAndReturn (block_flash); + + if (block_flash) + { + ri_yield_ExpectAndReturn (RD_SUCCESS); + rt_flash_busy_ExpectAndReturn (false); + } +} + +/** + * @brief Initialize logging. + * + * After initialization driver is ready to store data with @ref app_log_process. + * If there is a logging configuration stored to flash, stored configuration is used. + * If not, default configuration is used and stored to flash. + * + * @retval RD_SUCCESS if logging was initialized. + * @retval RD_ERROR_INVALID_STATE if flash task is not initialized. + */ +void test_app_log_init_nostored (void) +{ + rd_status_t err_code = RD_SUCCESS; + app_log_config_t defaults = + { + .interval_s = APP_LOG_INTERVAL_S, + .overflow = APP_LOG_OVERFLOW, + .fields = { + .datas.temperature_c = APP_LOG_TEMPERATURE_ENABLED, + .datas.humidity_rh = APP_LOG_HUMIDITY_ENABLED, + .datas.pressure_pa = APP_LOG_PRESSURE_ENABLED + } + }; + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &defaults, sizeof (defaults), + RD_ERROR_NOT_FOUND); + rt_flash_load_IgnoreArg_message(); + rt_flash_store_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &defaults, sizeof (defaults), + RD_SUCCESS); + rt_flash_store_IgnoreArg_message(); + err_code |= app_log_init(); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT (!memcmp (&defaults, &m_log_config, sizeof (m_log_config))); +} + +void test_app_log_init_stored (void) +{ + rd_status_t err_code = RD_SUCCESS; + app_log_config_t stored = + { + .interval_s = 10, + .overflow = false, + .fields = { + .datas.temperature_c = 1, + .datas.humidity_rh = 0, + .datas.pressure_pa = 1 + } + }; + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + NULL, sizeof (stored), + RD_SUCCESS); + rt_flash_load_IgnoreArg_message(); + rt_flash_load_ReturnMemThruPtr_message (&stored, sizeof (stored)); + err_code |= app_log_init(); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT (!memcmp (&stored, &m_log_config, sizeof (m_log_config))); +} + +void test_app_log_init_noflash (void) +{ + rd_status_t err_code = RD_SUCCESS; + app_log_config_t defaults = {0}; + memcpy (&defaults, &m_log_config, sizeof (defaults)); + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &defaults, sizeof (defaults), + RD_ERROR_INVALID_STATE); + rt_flash_load_IgnoreArg_message(); + err_code |= app_log_init(); + TEST_ASSERT (RD_ERROR_INVALID_STATE == err_code); + TEST_ASSERT (!memcmp (&defaults, &m_log_config, sizeof (m_log_config))); +} + +/** + * @brief Process data into log. + * + * If time elapsed since last logged element is larger than logging interval, data is + * stored to RAM buffer. While data is being stored to RAM buffer, it is also stored to + * compressed buffer. When compressed buffer fills 4000 bytes it will be written to flash. + * If there is no more room for new blocks in flash, oldest flash block is erased and + * replaced with new data. + * + * @retval RD_SUCCESS if data was logged. + * @retval RD_ERROR_NO_MEMORY if data cannot be stored to flash and overflow is false. + * @retval RD_ERROR_BUSY previous operation is in process, e.g. writing to flash. + */ +void test_app_log_process_ok (void) +{ + rd_status_t err_code = RD_SUCCESS; + float samples[NUM_FIELDS] = {0}; + const rd_sensor_data_t sample = + { + .timestamp_ms = 1U, + .fields = { + .datas.temperature_c = 1, + .datas.humidity_rh = 1, + .datas.pressure_pa = 1, + .datas.voltage_v = 1 + }, + .valid = { + .datas.temperature_c = 1, + .datas.humidity_rh = 1, + .datas.pressure_pa = 1, + .datas.voltage_v = 1 + }, + .data = samples + }; + sample_process_expect (&sample); +# if RL_COMPRESS_ENABLED + rl_compress_ExpectAndReturn (NULL, + m_log_input_block.storage, + sizeof (m_log_input_block.storage), + &m_compress_state, + RL_SUCCESS); + rl_compress_IgnoreArg_data(); +# endif + err_code |= app_log_process (&sample); + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_log_process_sequence (void) +{ + rd_status_t err_code = RD_SUCCESS; + float samples[NUM_FIELDS] = {0}; + const uint16_t interval_s = 4U; + m_last_sample_ms = 0; + app_log_config_t store = + { + .interval_s = interval_s, + .overflow = APP_LOG_OVERFLOW, + .fields = { + .datas.temperature_c = APP_LOG_TEMPERATURE_ENABLED, + .datas.humidity_rh = APP_LOG_HUMIDITY_ENABLED, + .datas.pressure_pa = APP_LOG_PRESSURE_ENABLED + } + }; + memcpy (&m_log_config, &store, sizeof (m_log_config)); + rd_sensor_data_t sample = + { + .timestamp_ms = 1U, + .fields = { + .datas.temperature_c = 1, + .datas.humidity_rh = 1, + .datas.pressure_pa = 1, + .datas.voltage_v = 1 + }, + .valid = { + .datas.temperature_c = 1, + .datas.humidity_rh = 1, + .datas.pressure_pa = 1, + .datas.voltage_v = 1 + }, + .data = samples + }; + + for (size_t ii = 0; ii < 6; ii++) + { + if (! (ii % 2)) + { + sample_process_expect (&sample); +# if RL_COMPRESS_ENABLED + rl_compress_ExpectAndReturn (NULL, + m_log_input_block.storage, + sizeof (m_log_input_block.storage), + &m_compress_state, + RL_SUCCESS); + rl_compress_IgnoreArg_data(); +# endif + } + + err_code |= app_log_process (&sample); + sample.timestamp_ms += (interval_s / 2) * 1000; + } + + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_log_process_fill_blocks (void) +{ + rd_status_t err_code = RD_SUCCESS; + m_last_sample_ms = 0; + float samples[4] = {0}; //!< number of fields to mock-store. + rd_sensor_data_t sample = + { + .timestamp_ms = 1U, + .fields = { + .datas.temperature_c = 1, + .datas.humidity_rh = 1, + .datas.pressure_pa = 1, + .datas.voltage_v = 1 + }, + .valid = { + .datas.temperature_c = 1, + .datas.humidity_rh = 1, + .datas.pressure_pa = 1, + .datas.voltage_v = 1 + }, + .data = samples + }; + uint8_t record_idx = 0; + + for (size_t ii = 0; ii < APP_FLASH_LOG_DATA_RECORDS_NUM * 2; ii++) + { + for (size_t ii = 0; ii < STORED_FIELDS; ii++) + { + rd_sensor_data_parse_ExpectAnyArgsAndReturn (0); + } + +# if RL_COMPRESS_ENABLED + rl_compress_ExpectAndReturn (NULL, + m_log_input_block.storage, + sizeof (m_log_input_block.storage), + &m_compress_state, + RL_COMPRESS_END); + rl_compress_IgnoreArg_data(); +# else + m_log_input_block.num_samples = APP_LOG_MAX_SAMPLES; +# endif + store_block_expect (record_idx, ii % 2); + sample.timestamp_ms += (m_log_config.interval_s * 1001U); + record_idx++; + record_idx = record_idx % APP_FLASH_LOG_DATA_RECORDS_NUM; + err_code |= app_log_process (&sample); + } + + TEST_ASSERT (RD_SUCCESS == err_code); +} + +/** + * @brief Configure logging. + * + * Calling this function will flush current log buffer into flash, possibly leading + * to NULL entries. + * + * @retval RD_SUCCESS on successful configuration of log. + * @retcal RD_ERROR_INVALID_STATE if flash is not initialized. + */ +void test_app_log_config_set_ok (void) +{ + rd_status_t err_code = RD_SUCCESS; + app_log_config_t defaults = + { + .interval_s = APP_LOG_INTERVAL_S, + .overflow = APP_LOG_OVERFLOW, + .fields = { + .datas.temperature_c = APP_LOG_TEMPERATURE_ENABLED, + .datas.humidity_rh = APP_LOG_HUMIDITY_ENABLED, + .datas.pressure_pa = APP_LOG_PRESSURE_ENABLED + } + }; + rt_flash_store_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &defaults, sizeof (defaults), + RD_SUCCESS); + rt_flash_store_IgnoreArg_message(); + store_block_expect (IGNORE_RECORD_IDX, false); + err_code |= app_log_config_set (&defaults); + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_log_config_set_notinit (void) +{ + rd_status_t err_code = RD_SUCCESS; + app_log_config_t defaults = + { + .interval_s = APP_LOG_INTERVAL_S, + .overflow = APP_LOG_OVERFLOW, + .fields = { + .datas.temperature_c = APP_LOG_TEMPERATURE_ENABLED, + .datas.humidity_rh = APP_LOG_HUMIDITY_ENABLED, + .datas.pressure_pa = APP_LOG_PRESSURE_ENABLED + } + }; + rt_flash_store_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &defaults, sizeof (defaults), + RD_ERROR_INVALID_STATE); + rt_flash_store_IgnoreArg_message(); + err_code |= app_log_config_set (&defaults); + TEST_ASSERT (RD_ERROR_INVALID_STATE == err_code); +} + + +/** + * @brief Read current logging configuration. + * + * @retval RD_SUCCESS if configuration was loaded successfully. + * @retval RD_ERROR_INVALID_STATE if flash is not initialized. + * @retval RD_ERROR_NOT_FOUND if configuration could not found. + */ +void test_app_log_config_get_not_found (void) +{ + rd_status_t err_code = RD_SUCCESS; + app_log_config_t defaults = + { + .interval_s = APP_LOG_INTERVAL_S, + .overflow = APP_LOG_OVERFLOW, + .fields = { + .datas.temperature_c = APP_LOG_TEMPERATURE_ENABLED, + .datas.humidity_rh = APP_LOG_HUMIDITY_ENABLED, + .datas.pressure_pa = APP_LOG_PRESSURE_ENABLED + } + }; + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &defaults, sizeof (defaults), + RD_ERROR_NOT_FOUND); + rt_flash_load_IgnoreArg_message(); + err_code |= app_log_config_get (&defaults); + TEST_ASSERT (RD_ERROR_NOT_FOUND == err_code); +} + +void test_app_log_config_get_found (void) +{ + rd_status_t err_code = RD_SUCCESS; + app_log_config_t defaults = + { + .interval_s = APP_LOG_INTERVAL_S, + .overflow = APP_LOG_OVERFLOW, + .fields = { + .datas.temperature_c = APP_LOG_TEMPERATURE_ENABLED, + .datas.humidity_rh = APP_LOG_HUMIDITY_ENABLED, + .datas.pressure_pa = APP_LOG_PRESSURE_ENABLED + } + }; + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &defaults, sizeof (defaults), + RD_SUCCESS); + rt_flash_load_IgnoreArg_message(); + err_code |= app_log_config_get (&defaults); + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_log_config_get_not_init (void) +{ + rd_status_t err_code = RD_SUCCESS; + app_log_config_t defaults = + { + .interval_s = APP_LOG_INTERVAL_S, + .overflow = APP_LOG_OVERFLOW, + .fields = { + .datas.temperature_c = APP_LOG_TEMPERATURE_ENABLED, + .datas.humidity_rh = APP_LOG_HUMIDITY_ENABLED, + .datas.pressure_pa = APP_LOG_PRESSURE_ENABLED + } + }; + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + APP_FLASH_LOG_CONFIG_RECORD, + &defaults, sizeof (defaults), + RD_ERROR_NOT_INITIALIZED); + rt_flash_load_IgnoreArg_message(); + err_code |= app_log_config_get (&defaults); + TEST_ASSERT (RD_ERROR_NOT_INITIALIZED == err_code); +} + +/** + * @brief Get data from log. + * + * Searches for first logged sample after given timestamp and returns it. Loop over + * this function to get all logged data. + * + * @param[in,out] sample Input: Requested data fields with timestamp set. + * Output: Filled fields with valid data. + * + * @retval RD_SUCCESS if a sample was retrieved. + * @retval RD_ERROR_NOT_FOUND if no newer data than requested timestamp was found. + * + */ +void test_app_log_read_from_start (void) +{ + rd_status_t err_code = RD_SUCCESS; + float samples[NUM_FIELDS] = {0}; + rd_sensor_data_t sample = + { + .timestamp_ms = 0U, + .fields = { + .datas.temperature_c = 1, + .datas.humidity_rh = 1, + .datas.pressure_pa = 1, + .datas.voltage_v = 1 + }, + .valid = { 0 }, + .data = samples + }; + app_log_record_t r1 = + { + .start_timestamp_s = 800 * 1000, + .end_timestamp_s = 1600 * 1000, + .num_samples = 4, + .storage = { e_1_1, e_1_2, e_1_3, e_1_4 } + }; + app_log_read_state_t rs = {0}; + uint8_t record_idx = 0; + // Load flash, check if we can find a block which has end timestamp after target time. + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + record_idx, + &r1, sizeof (r1), + RD_SUCCESS); + rt_flash_load_IgnoreArg_message(); + rt_flash_load_ReturnArrayThruPtr_message (&r1, 1); +#if RL_COMPRESS_ENABLED + uint32_t timestamp = sample.timestamp_ms / 1000; + rl_data_t data = {0}; + rl_decompress_ExpectWithArrayAndReturn (&data, 1, + m_log_output_block.storage, sizeof (m_log_output_block.storage), + sizeof (m_log_output_block.storage), + &m_compress_state, 1, + ×tamp, 1, + RD_SUCCESS); +#endif + sample_read_expect (&sample, &r1.storage[0]); + err_code |= app_log_read (&sample, &rs); + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_log_read_null (void) +{ + rd_status_t err_code = RD_SUCCESS; + rd_sensor_data_t sample = {0}; + app_log_read_state_t rs = {0}; + err_code = app_log_read (NULL, &rs); + TEST_ASSERT (RD_ERROR_NULL == err_code); + err_code = app_log_read (&sample, NULL); + TEST_ASSERT (RD_ERROR_NULL == err_code); + err_code = app_log_read (NULL, NULL); + TEST_ASSERT (RD_ERROR_NULL == err_code); +} + +void test_app_log_read_no_stored_data (void) +{ + rd_status_t err_code = RD_SUCCESS; + rd_sensor_data_t sample = {0}; + app_log_read_state_t rs = {0}; + uint8_t record_idx = 0; + + for (; record_idx < APP_FLASH_LOG_DATA_RECORDS_NUM;) + { + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + record_idx, + &m_log_output_block, sizeof (m_log_output_block), + RD_ERROR_NOT_FOUND); + record_idx++; + } + + err_code = app_log_read (&sample, &rs); + TEST_ASSERT (RD_ERROR_NOT_FOUND == err_code); +} + +void test_app_log_read_skip_old_data (void) +{ + rd_status_t err_code = RD_SUCCESS; + rd_sensor_data_t sample = {0}; + app_log_read_state_t rs = + { + .oldest_element_ms = 1000 * 1000 + }; + app_log_record_t old = {0}; + app_log_record_t new = + { + .start_timestamp_s = 800 * 1000, + .end_timestamp_s = 1600 * 1000, + .num_samples = 4, + .storage = { e_1_1, e_1_2, e_1_3, e_1_4 } + }; + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U), + &m_log_output_block, sizeof (m_log_output_block), + RD_SUCCESS); + rt_flash_load_ReturnMemThruPtr_message (&old, sizeof (old)); + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + 1U, + &m_log_output_block, sizeof (m_log_output_block), + RD_SUCCESS); + rt_flash_load_ReturnMemThruPtr_message (&new, sizeof (new)); + sample_read_expect (&sample, &new.storage[0]); + err_code = app_log_read (&sample, &rs); + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_log_read_skip_missing_data (void) +{ + rd_status_t err_code = RD_SUCCESS; + rd_sensor_data_t sample = {0}; + app_log_read_state_t rs = + { + .oldest_element_ms = 1000 * 1000 + }; + app_log_record_t old = {0}; + app_log_record_t new = + { + .start_timestamp_s = 3200 * 1000, + .end_timestamp_s = 4000 * 1000, + .num_samples = 4, + .storage = { e_4_1, e_4_2, e_4_3, e_4_4 } + }; + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U), + &m_log_output_block, sizeof (m_log_output_block), + RD_SUCCESS); + rt_flash_load_ReturnMemThruPtr_message (&old, sizeof (old)); + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + 1U, + &m_log_output_block, sizeof (m_log_output_block), + RD_ERROR_NOT_FOUND); + rt_flash_load_ReturnMemThruPtr_message (&new, sizeof (new)); + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + 2U, + &m_log_output_block, sizeof (m_log_output_block), + RD_SUCCESS); + rt_flash_load_ReturnMemThruPtr_message (&new, sizeof (new)); + sample_read_expect (&sample, &new.storage[0]); + err_code = app_log_read (&sample, &rs); + TEST_ASSERT (RD_SUCCESS == err_code); +} + +void test_app_log_read_out_of_sequence_data (void) +{ + rd_status_t err_code = RD_SUCCESS; + rd_sensor_data_t sample = {0}; + app_log_read_state_t rs = + { + .oldest_element_ms = 1000 * 1000 + }; + app_log_record_t r1 = + { + .start_timestamp_s = 800 * 1000, + .end_timestamp_s = 1600 * 1000, + .num_samples = 4, + .storage = { e_1_1, e_1_2, e_1_3, e_1_4 } + }; + app_log_record_t r2 = + { + .start_timestamp_s = 1600 * 1000, + .end_timestamp_s = 2400 * 1000, + .num_samples = 4, + .storage = { e_2_1, e_2_2, e_2_3, e_2_4 } + }; + app_log_record_t r3 = + { + .start_timestamp_s = 2400 * 1000, + .end_timestamp_s = 3200 * 1000, + .num_samples = 4, + .storage = { e_3_1, e_3_2, e_3_3, e_3_4 } + }; + app_log_record_t r4 = + { + .start_timestamp_s = 3200 * 1000, + .end_timestamp_s = 4000 * 1000, + .num_samples = 4, + .storage = { e_4_1, e_4_2, e_4_3, e_4_4 } + }; + app_log_record_t * records[4] = {&r1, &r2, &r3, &r4}; + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U), + &m_log_output_block, sizeof (m_log_output_block), + RD_SUCCESS); + rt_flash_load_ReturnMemThruPtr_message (&r3, sizeof (r3)); + sample_read_expect (&sample, & (records[2]->storage[0])); + sample_read_expect (&sample, & (records[2]->storage[1])); + sample_read_expect (&sample, & (records[2]->storage[2])); + sample_read_expect (&sample, & (records[2]->storage[3])); + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + 1U, + &m_log_output_block, sizeof (m_log_output_block), + RD_SUCCESS); + rt_flash_load_ReturnMemThruPtr_message (&r4, sizeof (r4)); + sample_read_expect (&sample, & (records[3]->storage[0])); + sample_read_expect (&sample, & (records[3]->storage[1])); + sample_read_expect (&sample, & (records[3]->storage[2])); + sample_read_expect (&sample, & (records[3]->storage[3])); + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + 2U, + &m_log_output_block, sizeof (m_log_output_block), + RD_SUCCESS); + rt_flash_load_ReturnMemThruPtr_message (&r1, sizeof (r1)); + sample_read_expect (&sample, & (records[0]->storage[0])); + sample_read_expect (&sample, & (records[0]->storage[1])); + sample_read_expect (&sample, & (records[0]->storage[2])); + sample_read_expect (&sample, & (records[0]->storage[3])); + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + 3U, + &m_log_output_block, sizeof (m_log_output_block), + RD_SUCCESS); + rt_flash_load_ReturnMemThruPtr_message (&r2, sizeof (r2)); + sample_read_expect (&sample, & (records[1]->storage[0])); + sample_read_expect (&sample, & (records[1]->storage[1])); + sample_read_expect (&sample, & (records[1]->storage[2])); + sample_read_expect (&sample, & (records[1]->storage[3])); + + for (size_t ii = 4; ii < APP_FLASH_LOG_DATA_RECORDS_NUM; ii++) + { + rt_flash_load_ExpectAndReturn (APP_FLASH_LOG_FILE, + (APP_FLASH_LOG_DATA_RECORD_PREFIX << 8U) + ii, + &m_log_output_block, sizeof (m_log_output_block), + RD_ERROR_NOT_FOUND); + } + + uint8_t num_reads = 0; + + while (RD_SUCCESS == err_code) + { + err_code |= app_log_read (&sample, &rs); + num_reads++; + } + + TEST_ASSERT (RD_ERROR_NOT_FOUND == err_code); + TEST_ASSERT (17 == num_reads); +} + + +/** @} */ diff --git a/test/test_app_sensor.c b/test/test_app_sensor.c index 56b67d78..29e8fab3 100644 --- a/test/test_app_sensor.c +++ b/test/test_app_sensor.c @@ -1,9 +1,12 @@ #include "unity.h" +#include "app_config.h" #include "app_sensor.h" +#include "mock_app_log.h" #include "mock_ruuvi_driver_error.h" #include "mock_ruuvi_driver_sensor.h" +#include "mock_ruuvi_endpoints.h" #include "mock_ruuvi_interface_gpio.h" #include "mock_ruuvi_interface_gpio_interrupt.h" #include "mock_ruuvi_interface_i2c.h" @@ -16,12 +19,19 @@ #include "mock_ruuvi_interface_lis2dh12.h" #include "mock_ruuvi_interface_shtcx.h" #include "mock_ruuvi_interface_tmp117.h" +#include "mock_ruuvi_interface_yield.h" #include "mock_ruuvi_task_adc.h" #include "mock_ruuvi_task_sensor.h" #include "mock_ruuvi_interface_communication_radio.h" #include extern rt_sensor_ctx_t * m_sensors[]; +static uint32_t m_expect_sends = 0; + +static rd_status_t dummy_comm (ri_comm_message_t * const msg) +{ + return RD_SUCCESS; +} void setUp (void) { @@ -608,3 +618,261 @@ void test_app_sensor_accelerometer_isr (void) incremented_cnt = app_sensor_event_count_get (); TEST_ASSERT ( (orig_cnt + 1) == incremented_cnt); } + + +static void app_sensor_encode_log_Expect (const uint8_t source) +{ + re_log_write_header_ExpectAndReturn (NULL, source, RE_SUCCESS); + re_log_write_header_IgnoreArg_buffer(); + re_log_write_timestamp_ExpectAndReturn (NULL, 0, RE_SUCCESS); + re_log_write_timestamp_IgnoreArg_buffer(); + re_log_write_timestamp_IgnoreArg_timestamp_ms(); + re_log_write_data_ExpectAndReturn (NULL, 0, source, RE_SUCCESS); + re_log_write_data_IgnoreArg_buffer(); + re_log_write_data_IgnoreArg_data(); +} + +static void app_sensor_blocking_send_Expect() +{ + ri_rtc_millis_ExpectAndReturn (0); + ri_rtc_millis_ExpectAndReturn (0); + m_expect_sends++; +} + +static void app_sensor_send_data_Expect (const ri_comm_xfer_fp_t reply_fp, + const uint8_t * const raw_message, + const rd_sensor_data_t * const sample, + const uint8_t fieldcount, + const uint8_t * const sources, + const rd_sensor_data_bitfield_t * const types, + const int64_t time_offset_ms) +{ + rd_sensor_data_fieldcount_ExpectAndReturn (NULL, fieldcount); + rd_sensor_data_fieldcount_IgnoreArg_target(); + + for (size_t ii = 0; ii < fieldcount; ii++) + { + rd_sensor_has_valid_data_ExpectAndReturn (NULL, ii, true); + rd_sensor_has_valid_data_IgnoreArg_target(); + rd_sensor_field_type_ExpectAndReturn (NULL, ii, types[ii]); + rd_sensor_field_type_IgnoreArg_target(); + app_sensor_encode_log_Expect (sources[ii]); + app_sensor_blocking_send_Expect(); + } +} + +static void app_sensor_send_eof_Expect () +{ + app_sensor_blocking_send_Expect (); +} + +static void app_sensor_log_read_Expect (const ri_comm_xfer_fp_t reply_fp, + const rd_sensor_data_fields_t fields, + const uint8_t fieldcount, + const uint8_t * const sources, + const rd_sensor_data_bitfield_t * const types, + const uint8_t * const raw_message) +{ + uint32_t current_time_s = (1000 * 1000 * 1000); + rd_sensor_data_t sample = {0}; + sample.fields = fields; + float data[fieldcount]; + sample.data = data; + rd_sensor_data_fieldcount_ExpectAndReturn (NULL, fieldcount); + rd_sensor_data_fieldcount_IgnoreArg_target(); + re_std_log_current_time_ExpectAndReturn (raw_message, current_time_s); + re_std_log_start_time_ExpectAndReturn (raw_message, (0)); + ri_rtc_millis_ExpectAndReturn (0); + app_log_read_ExpectAnyArgsAndReturn (RD_SUCCESS); + // Assuming tests are run on 64-bit system + app_sensor_send_data_Expect (reply_fp, raw_message, &sample, fieldcount, sources, + types, current_time_s * 1000); +} + +static void app_sensor_log_read_eof_Expect () +{ + app_log_read_ExpectAnyArgsAndReturn (RD_ERROR_NOT_FOUND); + app_sensor_send_eof_Expect (); +} + +void test_app_sensor_handle_accx (void) +{ + rd_status_t err_code = RD_SUCCESS; + m_expect_sends = 0; + uint8_t raw_message[RE_STANDARD_MESSAGE_LENGTH] = {0}; + raw_message[RE_STANDARD_OPERATION_INDEX] = RE_STANDARD_LOG_VALUE_READ; + raw_message[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_ACCELERATION_X; + rd_sensor_data_fields_t fields = { .datas.acceleration_x_g = 1 }; + const uint8_t fieldcount = 1; + const uint8_t sources[1] = { RE_STANDARD_DESTINATION_ACCELERATION_X }; + const rd_sensor_data_bitfield_t types[1] = {RD_SENSOR_ACC_X_FIELD.datas}; + app_sensor_log_read_Expect (&dummy_comm, fields, fieldcount, sources, types, raw_message); + app_sensor_log_read_eof_Expect (); + err_code |= app_sensor_handle (&dummy_comm, + raw_message, + sizeof (raw_message)); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT (2 == m_expect_sends); +} + +void test_app_sensor_handle_accy (void) +{ + rd_status_t err_code = RD_SUCCESS; + m_expect_sends = 0; + uint8_t raw_message[RE_STANDARD_MESSAGE_LENGTH] = {0}; + raw_message[RE_STANDARD_OPERATION_INDEX] = RE_STANDARD_LOG_VALUE_READ; + raw_message[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_ACCELERATION_Y; + rd_sensor_data_fields_t fields = { .datas.acceleration_y_g = 1 }; + const uint8_t fieldcount = 1; + const uint8_t sources[1] = { RE_STANDARD_DESTINATION_ACCELERATION_Y }; + const rd_sensor_data_bitfield_t types[1] = {RD_SENSOR_ACC_Y_FIELD.datas}; + app_sensor_log_read_Expect (&dummy_comm, fields, fieldcount, sources, types, raw_message); + app_sensor_log_read_eof_Expect (); + err_code |= app_sensor_handle (&dummy_comm, + raw_message, + sizeof (raw_message)); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT (2 == m_expect_sends); +} + +void test_app_sensor_handle_accz (void) +{ + rd_status_t err_code = RD_SUCCESS; + m_expect_sends = 0; + uint8_t raw_message[RE_STANDARD_MESSAGE_LENGTH] = {0}; + raw_message[RE_STANDARD_OPERATION_INDEX] = RE_STANDARD_LOG_VALUE_READ; + raw_message[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_ACCELERATION_Z; + rd_sensor_data_fields_t fields = { .datas.acceleration_z_g = 1 }; + const uint8_t fieldcount = 1; + const uint8_t sources[1] = { RE_STANDARD_DESTINATION_ACCELERATION_Z }; + const rd_sensor_data_bitfield_t types[1] = {RD_SENSOR_ACC_Z_FIELD.datas}; + app_sensor_log_read_Expect (&dummy_comm, fields, fieldcount, sources, types, raw_message); + app_sensor_log_read_eof_Expect (); + err_code |= app_sensor_handle (&dummy_comm, + raw_message, + sizeof (raw_message)); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT (2 == m_expect_sends); +} + +void test_app_sensor_handle_accxyz (void) +{ + rd_status_t err_code = RD_SUCCESS; + m_expect_sends = 0; + uint8_t raw_message[RE_STANDARD_MESSAGE_LENGTH] = {0}; + raw_message[RE_STANDARD_OPERATION_INDEX] = RE_STANDARD_LOG_VALUE_READ; + raw_message[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_ACCELERATION; + rd_sensor_data_fields_t fields = + { + .datas.acceleration_x_g = 1, + .datas.acceleration_y_g = 1, + .datas.acceleration_z_g = 1 + }; + const uint8_t fieldcount = 3; + const uint8_t sources[3] = + { + RE_STANDARD_DESTINATION_ACCELERATION_X, + RE_STANDARD_DESTINATION_ACCELERATION_Y, + RE_STANDARD_DESTINATION_ACCELERATION_Z, + }; + const rd_sensor_data_bitfield_t types[3] = + { + RD_SENSOR_ACC_X_FIELD.datas, + RD_SENSOR_ACC_Y_FIELD.datas, + RD_SENSOR_ACC_Z_FIELD.datas + }; + app_sensor_log_read_Expect (&dummy_comm, fields, fieldcount, sources, types, raw_message); + app_sensor_log_read_eof_Expect (); + err_code |= app_sensor_handle (&dummy_comm, + raw_message, + sizeof (raw_message)); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT ( (fieldcount + 1) == m_expect_sends); +} + +void test_app_sensor_handle_humidity (void) +{ + rd_status_t err_code = RD_SUCCESS; + m_expect_sends = 0; + uint8_t raw_message[RE_STANDARD_MESSAGE_LENGTH] = {0}; + raw_message[RE_STANDARD_OPERATION_INDEX] = RE_STANDARD_LOG_VALUE_READ; + raw_message[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_HUMIDITY; + rd_sensor_data_fields_t fields = + { + .datas.humidity_rh = 1, + }; + const uint8_t fieldcount = 1; + const uint8_t sources[1] = + { + RE_STANDARD_DESTINATION_HUMIDITY + }; + const rd_sensor_data_bitfield_t types[1] = + { + RD_SENSOR_HUMI_FIELD.datas, + }; + app_sensor_log_read_Expect (&dummy_comm, fields, fieldcount, sources, types, raw_message); + app_sensor_log_read_eof_Expect (); + err_code |= app_sensor_handle (&dummy_comm, + raw_message, + sizeof (raw_message)); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT ( (fieldcount + 1) == m_expect_sends); +} + +void test_app_sensor_handle_pressure (void) +{ + rd_status_t err_code = RD_SUCCESS; + m_expect_sends = 0; + uint8_t raw_message[RE_STANDARD_MESSAGE_LENGTH] = {0}; + raw_message[RE_STANDARD_OPERATION_INDEX] = RE_STANDARD_LOG_VALUE_READ; + raw_message[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_PRESSURE; + rd_sensor_data_fields_t fields = + { + .datas.pressure_pa = 1, + }; + const uint8_t fieldcount = 1; + const uint8_t sources[1] = + { + RE_STANDARD_DESTINATION_PRESSURE + }; + const rd_sensor_data_bitfield_t types[1] = + { + RD_SENSOR_PRES_FIELD.datas, + }; + app_sensor_log_read_Expect (&dummy_comm, fields, fieldcount, sources, types, raw_message); + app_sensor_log_read_eof_Expect (); + err_code |= app_sensor_handle (&dummy_comm, + raw_message, + sizeof (raw_message)); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT ( (fieldcount + 1) == m_expect_sends); +} + +void test_app_sensor_handle_temperature (void) +{ + rd_status_t err_code = RD_SUCCESS; + m_expect_sends = 0; + uint8_t raw_message[RE_STANDARD_MESSAGE_LENGTH] = {0}; + raw_message[RE_STANDARD_OPERATION_INDEX] = RE_STANDARD_LOG_VALUE_READ; + raw_message[RE_STANDARD_DESTINATION_INDEX] = RE_STANDARD_DESTINATION_TEMPERATURE; + rd_sensor_data_fields_t fields = + { + .datas.temperature_c = 1, + }; + const uint8_t fieldcount = 1; + const uint8_t sources[1] = + { + RE_STANDARD_DESTINATION_TEMPERATURE + }; + const rd_sensor_data_bitfield_t types[1] = + { + RD_SENSOR_TEMP_FIELD.datas, + }; + app_sensor_log_read_Expect (&dummy_comm, fields, fieldcount, sources, types, raw_message); + app_sensor_log_read_eof_Expect (); + err_code |= app_sensor_handle (&dummy_comm, + raw_message, + sizeof (raw_message)); + TEST_ASSERT (RD_SUCCESS == err_code); + TEST_ASSERT ( (fieldcount + 1) == m_expect_sends); +} \ No newline at end of file diff --git a/test/test_main.c b/test/test_main.c index 25903951..7956991a 100644 --- a/test/test_main.c +++ b/test/test_main.c @@ -13,9 +13,11 @@ #include "mock_app_comms.h" #include "mock_app_heartbeat.h" #include "mock_app_led.h" +#include "mock_app_log.h" #include "mock_app_power.h" #include "mock_app_sensor.h" #include "mock_ruuvi_driver_error.h" +#include "mock_ruuvi_task_flash.h" #include "mock_ruuvi_interface_gpio.h" #include "mock_ruuvi_interface_log.h" #include "mock_ruuvi_interface_scheduler.h" @@ -50,13 +52,17 @@ void test_main (void) ri_timer_init_ExpectAndReturn (RD_SUCCESS); ri_scheduler_init_ExpectAndReturn (RD_SUCCESS); rt_gpio_init_ExpectAndReturn (RD_SUCCESS); + ri_yield_low_power_enable_ExpectAndReturn (true, RD_SUCCESS); + rt_flash_init_ExpectAndReturn (RD_SUCCESS); app_button_init_ExpectAndReturn (RD_SUCCESS); app_dc_dc_init_ExpectAndReturn (RD_SUCCESS); app_led_init_ExpectAndReturn (RD_SUCCESS); app_sensor_init_ExpectAndReturn (RD_SUCCESS); + app_log_init_ExpectAndReturn (RD_SUCCESS); app_sensor_acc_thr_set_ExpectWithArrayAndReturn (&motion_threshold, 1, RD_SUCCESS); app_comms_init_ExpectAndReturn (RD_SUCCESS); app_heartbeat_init_ExpectAndReturn (RD_SUCCESS); + app_heartbeat_start_ExpectAndReturn (RD_SUCCESS); // ri_scheduler_execute_ExpectAndReturn (RD_SUCCESS); ri_yield_ExpectAndReturn (RD_SUCCESS);