diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index b1df970..336a4dc 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -7,7 +7,7 @@ cmake_minimum_required(VERSION 3.20.0) find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) -project(app LANGUAGES C VERSION 0.1.0) +project(app LANGUAGES C VERSION 0.2.1) configure_file(app_version.h.in ${CMAKE_BINARY_DIR}/app/include/app_version.h) target_include_directories(app PRIVATE ${CMAKE_BINARY_DIR}/app/include src) @@ -22,6 +22,7 @@ set(PYRRHA_SOURCES src/bluetooth.c src/storage.c src/timestamp.c + src/alerts.c ) target_include_directories(app PRIVATE include src) target_sources(app PRIVATE ${PYRRHA_SOURCES}) diff --git a/app/Kconfig b/app/Kconfig index b1f808e..0776ddc 100644 --- a/app/Kconfig +++ b/app/Kconfig @@ -11,6 +11,10 @@ module = PYRRHA module-str = PYRRHA source "subsys/logging/Kconfig.template.log_config" +module = ALERTS +module-str = ALERTS +source "subsys/logging/Kconfig.template.log_config" + menu "pyrrha threads" menu "stacks" config PYRRHA_COLLECTION_STACKSIZE @@ -56,6 +60,28 @@ config PYRRHA_FCB_MAGIC Magic 32-bit word for to identify valid settings area endmenu +menu "alerts" + +config CO_ALERT_THRESHOLD_PPM + int "Threshold in ppm for co alerts" + range 1 1000 + default 420 + help + Threshold in PPM for CO level when an alert is generated. + 420ppm is the AEGL2 10 minute limit. +config NO2_ALERT_THRESHOLD_PPM + int "Threshold in ppm for no2 alerts" + range 1 100 + default 8 + help + Threshold in PPM for NO2 level when an alert is generated. + 20ppm is the AEGL2 10 minute limit. +config TEMPERATURE_ALERT_THRESHOLD_CELCIUS + int "Threshold in celcius for temperature alerts" + range 60 120 + default 80 +endmenu + config PYRRHA_SAMPLE_PERIOD int "Period (in seconds) of sensor data collection" range 1 300 diff --git a/app/boards/nrf52840dk_nrf52840.overlay b/app/boards/nrf52840dk_nrf52840.overlay index 4a68265..9d5177f 100644 --- a/app/boards/nrf52840dk_nrf52840.overlay +++ b/app/boards/nrf52840dk_nrf52840.overlay @@ -25,6 +25,16 @@ }; }; +&pwm0 { + status = "okay"; + ch0-pin = <13>; + ch0-inverted; + ch1-pin = <14>; + ch1-inverted; + ch2-pin = <15>; + ch2-inverted; +}; + / { gas_sensor: mics4514 { label = "MICS4514"; @@ -35,4 +45,21 @@ rload-red-ohms = <47000>; rload-ox-ohms = <22000>; }; + pwmleds { + compatible = "pwm-leds"; + pwm_led0: pwm_led_0 { + pwms = <&pwm0 13>; + }; + pwm_led1: pwm_led_1 { + pwms = <&pwm0 14>; + }; + pwm_led2: pwm_led_2 { + pwms = <&pwm0 15>; + }; + }; + aliases { + red-pwm-led = &pwm_led0; + green-pwm-led = &pwm_led1; + blue-pwm-led = &pwm_led2; + }; }; diff --git a/app/configs/debug.conf b/app/configs/debug.conf index 640903e..65bbd83 100644 --- a/app/configs/debug.conf +++ b/app/configs/debug.conf @@ -19,6 +19,7 @@ CONFIG_UART_CONSOLE=y CONFIG_LOG=y CONFIG_LOG_MODE_IMMEDIATE=y CONFIG_PYRRHA_LOG_LEVEL_DBG=y +CONFIG_ALERTS_LOG_LEVEL_DBG=y CONFIG_SENSOR_LOG_LEVEL_DBG=y CONFIG_I2C_LOG_LEVEL_DBG=y diff --git a/app/include/alerts.h b/app/include/alerts.h new file mode 100644 index 0000000..4d0ba5d --- /dev/null +++ b/app/include/alerts.h @@ -0,0 +1,48 @@ +/** + * @file alerts.h + * @author Brian Bradley (brian.bradley.p@gmail.com) + * @date 2022-01-26 + * + * @copyright Copyright (C) 2022 LION + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + + +#ifndef __PYRRHA_BLUETOOTH_H +#define __PYRRHA_BLUETOOTH_H + +#include + +/** + * @brief Check for and generate any alerts necessary based on + * sensor data + * + * @param data : sensor data used to match alert conditions + * @retval 0 on success + * @retval -ENOLCK if alert mutex could not be locked within 250ms + * @retval -errno otherwise + */ +int generate_alerts(struct pyrrha_data * data); + +/** + * @brief Override the current alert condition. + * This will block local alert generation until a release is issued. + * + * @param color : hex representation of led color used in alarm + * @retval 0 on success + * @retval -ENOLCK if alert mutex could not be locked within 250ms + */ +int override_alert(uint32_t color); + +/** + * @brief Release the current override for the LED. For instance, + * if the host device disconnects and can no longer control the alarm + * + * @retval 0 on success + * @retval -ENOLCK if alert mutex could not be locked within 250ms + */ +int release_alert_override(); + +#endif \ No newline at end of file diff --git a/app/prj.conf b/app/prj.conf index 96836f6..770a66a 100644 --- a/app/prj.conf +++ b/app/prj.conf @@ -8,3 +8,4 @@ CONFIG_JSON_LIBRARY=y CONFIG_FLASH=y CONFIG_FLASH_MAP=y CONFIG_FCB=y +CONFIG_PWM=y diff --git a/app/src/alerts.c b/app/src/alerts.c new file mode 100644 index 0000000..7305d7a --- /dev/null +++ b/app/src/alerts.c @@ -0,0 +1,172 @@ +/** + * @file alerts.c + * @author Brian Bradley (brian.bradley.p@gmail.com) + * @brief + * @date 2022-01-26 + * + * @copyright Copyright (C) 2022 LION + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(alerts, CONFIG_PYRRHA_LOG_LEVEL); + +#define OVER_TEMPERATURE(temp) (temp > CONFIG_TEMPERATURE_ALERT_THRESHOLD_CELCIUS) +#define OVER_CO(co) (co > CONFIG_CO_ALERT_THRESHOLD_PPM) +#define OVER_NO2(no2) (no2 > CONFIG_NO2_ALERT_THRESHOLD_PPM) + +/* + * 50hz is flicker fusion threshold. Modulated light will be perceived + * as steady by our eyes when blinking rate is at least 50. + */ +#define PERIOD_USEC (USEC_PER_SEC / 50U) + +#define RED_LED_NODE DT_ALIAS(red_pwm_led) +#define GREEN_LED_NODE DT_ALIAS(green_pwm_led) +#define BLUE_LED_NODE DT_ALIAS(blue_pwm_led) + +#define COLOR_TO_PULSE(color, period) (color == UINT8_MAX ? (period) : (color * (period) >> 8)) + +/** + * @brief union which allows easy set/conversion to hex or rgb directly + * + */ +typedef union Color{ + uint32_t hex; + struct{ + uint8_t red; + uint8_t green; + uint8_t blue; + }; +}color_t; + +/** + * @brief Configuration table for pwm led + * + */ +struct pwm_cfg{ + const struct device * pwm; + const uint8_t pin; + const uint8_t flags; + const uint32_t period; +}; + +/** + * @brief Container for all pwm leds associated with the rgb led + * + */ +struct rgb_led_device{ + const struct pwm_cfg red; + const struct pwm_cfg green; + const struct pwm_cfg blue; +}; + +K_MUTEX_DEFINE(mtx_alert); +static color_t g_alert_override = {0}; + +const struct rgb_led_device g_led = { + .red = { + .pwm = DEVICE_DT_GET(DT_PWMS_CTLR(RED_LED_NODE)), + .pin = DT_PWMS_CHANNEL(RED_LED_NODE), + .flags = DT_PWMS_FLAGS(RED_LED_NODE), + .period = PERIOD_USEC + }, + .green = { + .pwm = DEVICE_DT_GET(DT_PWMS_CTLR(GREEN_LED_NODE)), + .pin = DT_PWMS_CHANNEL(GREEN_LED_NODE), + .flags = DT_PWMS_FLAGS(GREEN_LED_NODE), + .period = PERIOD_USEC + }, + .blue = { + .pwm = DEVICE_DT_GET(DT_PWMS_CTLR(BLUE_LED_NODE)), + .pin = DT_PWMS_CHANNEL(BLUE_LED_NODE), + .flags = DT_PWMS_FLAGS(BLUE_LED_NODE), + .period = PERIOD_USEC + }, +}; + +/** + * @brief Set pwm output directly to the coresponding 8-bit color value + * + * @param cfg : configuration table for pwm device + * @param color : 8-bit color value for the specified pwm + * @retval 0 on success + * @retval -ENODEV if no configuration table + * @retval -EFAULT if pwm set error + */ +static int set_pwm(const struct pwm_cfg * cfg, uint8_t color) +{ + if (cfg == NULL){ + return -ENODEV; + } + const struct device *dev = cfg->pwm; + uint32_t pulse = COLOR_TO_PULSE(color, cfg->period); + /* Verify pwm_pin_set_usec() */ + if (pwm_pin_set_usec(dev, cfg->pin, cfg->period, pulse, cfg->flags)) { + LOG_ERR("Fail to set the period and pulse width"); + return -EFAULT; + } + return 0; +} + +/** + * @brief Set the alert led to a specific color + * + * @param color : rgb representation of desired color + * @retval 0 on success + * @retval -errno otherwise + */ +static int set_alert_led(color_t color){ + set_pwm(&g_led.red, color.red); + set_pwm(&g_led.green, color.green); + set_pwm(&g_led.blue, color.blue); + return 0; +} + +/** + * @brief generate an alert from sensor data exceeding set thresholds + */ +static void threshold_alert(void){ + const color_t alert_color = { + .red = 0xff + }; + set_alert_led(alert_color); +} + +int override_alert(uint32_t color){ + if (k_mutex_lock(&mtx_alert, K_MSEC(250)) != 0){ + return -ENOLCK; + } + g_alert_override.hex = color; + set_alert_led(g_alert_override); + k_mutex_unlock(&mtx_alert); + return 0; +} + +int release_alert_override(){ + return override_alert(0); +} + +int generate_alerts(struct pyrrha_data * data){ + if (k_mutex_lock(&mtx_alert, K_MSEC(250)) != 0){ + return -ENOLCK; + } + /* Only generate an alert when there is no override condition */ + if (g_alert_override.hex == 0){ + if (OVER_TEMPERATURE(data->rht.temperature.val1) || + OVER_CO(data->gas.co.val1) || + OVER_NO2(data->gas.no2.val1)) + { + threshold_alert(); + } + } + k_mutex_unlock(&mtx_alert); + return 0; +} diff --git a/app/src/collector.c b/app/src/collector.c index 2cb9e2d..3598271 100644 --- a/app/src/collector.c +++ b/app/src/collector.c @@ -14,6 +14,7 @@ #include #include #include +#include LOG_MODULE_REGISTER(collector, CONFIG_PYRRHA_LOG_LEVEL); void data_collection_process(void){ @@ -25,7 +26,12 @@ void data_collection_process(void){ data.err |= (capture_rht_data(&data.rht) != 0 ? ERR_RHT_SENSOR : 0); data.timestamp = get_timestamp(); capture_rht_data(&data.rht); + + /* Queue data for storage and / or notification to a host */ queue_sensor_data(&data); + /* Process any alerts based on current sample data */ + generate_alerts(&data); + k_sleep(K_SECONDS(CONFIG_PYRRHA_SAMPLE_PERIOD)); } }