sensors: Create sensor trig subcommand
This PR relocates the sensor trigger sample application from the `sensor_shell` sample to a subcommand in the actual sensor shell. The subcommand has a UI for enabling a given trigger on a given sensor. A built-in handler for the data_ready trigger is included that prints the latest data to the log. Currently, only `SENSOR_TRIG_DATA_READY` is supported but the groundwork is there to add others. Tested on a `tdk_robokit1` board. Signed-off-by: Tristan Honscheid <honscheid@google.com>
This commit is contained in:
parent
fc73d6a7b9
commit
dc9bd3afe3
@ -44,6 +44,14 @@ config SENSOR_SHELL_BATTERY
|
||||
in a convenient format. It makes use of a fuel gauge to read its
|
||||
information.
|
||||
|
||||
config SENSOR_SHELL_TRIG_PRINT_TIMEOUT_MS
|
||||
int "Timeout for printing the average sensor sample value"
|
||||
default 5000
|
||||
depends on SENSOR_SHELL
|
||||
help
|
||||
Control the frequency of the sampling window over which the sensor
|
||||
interrupt handler will collect data.
|
||||
|
||||
config SENSOR_INFO
|
||||
bool "Sensor Info iterable section"
|
||||
|
||||
|
||||
@ -14,6 +14,8 @@
|
||||
#include <zephyr/shell/shell.h>
|
||||
#include <zephyr/sys/iterable_sections.h>
|
||||
|
||||
LOG_MODULE_REGISTER(sensor_shell);
|
||||
|
||||
#define SENSOR_GET_HELP \
|
||||
"Get sensor data. Channel names are optional. All channels are read " \
|
||||
"when no channels are provided. Syntax:\n" \
|
||||
@ -30,6 +32,10 @@
|
||||
|
||||
#define SENSOR_INFO_HELP "Get sensor info, such as vendor and model name, for all sensors."
|
||||
|
||||
#define SENSOR_TRIG_HELP \
|
||||
"Get or set the trigger type on a sensor. Currently only supports `data_ready`.\n" \
|
||||
"<device_name> <on/off> <trigger_name>"
|
||||
|
||||
const char *sensor_channel_name[SENSOR_CHAN_ALL] = {
|
||||
[SENSOR_CHAN_ACCEL_X] = "accel_x",
|
||||
[SENSOR_CHAN_ACCEL_Y] = "accel_y",
|
||||
@ -108,6 +114,38 @@ static const char *sensor_attribute_name[SENSOR_ATTR_COMMON_COUNT] = {
|
||||
[SENSOR_ATTR_FF_DUR] = "ff_dur",
|
||||
};
|
||||
|
||||
/* Forward declaration */
|
||||
static void data_ready_trigger_handler(const struct device *sensor,
|
||||
const struct sensor_trigger *trigger);
|
||||
|
||||
#define TRIGGER_DATA_ENTRY(trig_enum, str_name, handler_func) \
|
||||
[(trig_enum)] = {.name = #str_name, \
|
||||
.handler = (handler_func), \
|
||||
.trigger = {.chan = SENSOR_CHAN_ALL, .type = (trig_enum)}}
|
||||
|
||||
/**
|
||||
* @brief This table stores a mapping of string trigger names along with the sensor_trigger struct
|
||||
* that gets passed to the driver to enable that trigger, plus a function pointer to a handler. If
|
||||
* that pointer is NULL, this indicates there is not currently support for that trigger type in the
|
||||
* sensor shell.
|
||||
*/
|
||||
static const struct {
|
||||
const char *name;
|
||||
sensor_trigger_handler_t handler;
|
||||
struct sensor_trigger trigger;
|
||||
} sensor_trigger_table[SENSOR_TRIG_COMMON_COUNT] = {
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_TIMER, timer, NULL),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_DATA_READY, data_ready, data_ready_trigger_handler),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_DELTA, delta, NULL),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_NEAR_FAR, near_far, NULL),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_THRESHOLD, threshold, NULL),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_TAP, tap, NULL),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_DOUBLE_TAP, double_tap, NULL),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_FREEFALL, freefall, NULL),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_MOTION, motion, NULL),
|
||||
TRIGGER_DATA_ENTRY(SENSOR_TRIG_STATIONARY, stationary, NULL),
|
||||
};
|
||||
|
||||
enum dynamic_command_context {
|
||||
NONE,
|
||||
CTX_GET,
|
||||
@ -502,6 +540,61 @@ static void device_name_get_for_attr(size_t idx, struct shell_static_entry *entr
|
||||
}
|
||||
SHELL_DYNAMIC_CMD_CREATE(dsub_device_name_for_attr, device_name_get_for_attr);
|
||||
|
||||
static void trigger_name_get(size_t idx, struct shell_static_entry *entry)
|
||||
{
|
||||
int cnt = 0;
|
||||
|
||||
entry->syntax = NULL;
|
||||
entry->handler = NULL;
|
||||
entry->help = NULL;
|
||||
entry->subcmd = NULL;
|
||||
|
||||
for (int i = 0; i < SENSOR_TRIG_COMMON_COUNT; i++) {
|
||||
if (sensor_trigger_table[i].name != NULL) {
|
||||
if (cnt == idx) {
|
||||
entry->syntax = sensor_trigger_table[i].name;
|
||||
break;
|
||||
}
|
||||
cnt++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SHELL_DYNAMIC_CMD_CREATE(dsub_trigger_name, trigger_name_get);
|
||||
|
||||
static void trigger_on_off_get(size_t idx, struct shell_static_entry *entry)
|
||||
{
|
||||
entry->handler = NULL;
|
||||
entry->help = NULL;
|
||||
entry->subcmd = &dsub_trigger_name;
|
||||
|
||||
switch (idx) {
|
||||
case 0:
|
||||
entry->syntax = "on";
|
||||
break;
|
||||
case 1:
|
||||
entry->syntax = "off";
|
||||
break;
|
||||
default:
|
||||
entry->syntax = NULL;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
SHELL_DYNAMIC_CMD_CREATE(dsub_trigger_onoff, trigger_on_off_get);
|
||||
|
||||
static void device_name_get_for_trigger(size_t idx, struct shell_static_entry *entry)
|
||||
{
|
||||
const struct device *dev = shell_device_lookup(idx, NULL);
|
||||
|
||||
entry->syntax = (dev != NULL) ? dev->name : NULL;
|
||||
entry->handler = NULL;
|
||||
entry->help = NULL;
|
||||
entry->subcmd = &dsub_trigger_onoff;
|
||||
}
|
||||
|
||||
SHELL_DYNAMIC_CMD_CREATE(dsub_trigger, device_name_get_for_trigger);
|
||||
|
||||
static int cmd_get_sensor_info(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
ARG_UNUSED(argc);
|
||||
@ -525,6 +618,119 @@ static int cmd_get_sensor_info(const struct shell *sh, size_t argc, char **argv)
|
||||
#endif
|
||||
}
|
||||
|
||||
enum sample_stats_state {
|
||||
SAMPLE_STATS_STATE_UNINITIALIZED = 0,
|
||||
SAMPLE_STATS_STATE_ENABLED,
|
||||
SAMPLE_STATS_STATE_DISABLED,
|
||||
};
|
||||
|
||||
struct sample_stats {
|
||||
int64_t accumulator;
|
||||
uint32_t count;
|
||||
uint64_t sample_window_start;
|
||||
enum sample_stats_state state;
|
||||
};
|
||||
|
||||
static void data_ready_trigger_handler(const struct device *sensor,
|
||||
const struct sensor_trigger *trigger)
|
||||
{
|
||||
static struct sample_stats stats[SENSOR_CHAN_ALL];
|
||||
const int64_t now = k_uptime_get();
|
||||
struct sensor_value value;
|
||||
|
||||
if (sensor_sample_fetch(sensor)) {
|
||||
LOG_ERR("Failed to fetch samples on data ready handler");
|
||||
}
|
||||
for (int i = 0; i < SENSOR_CHAN_ALL; ++i) {
|
||||
int rc;
|
||||
|
||||
/* Skip disabled channels */
|
||||
if (stats[i].state == SAMPLE_STATS_STATE_DISABLED) {
|
||||
continue;
|
||||
}
|
||||
/* Skip 3 axis channels */
|
||||
if (i == SENSOR_CHAN_ACCEL_XYZ || i == SENSOR_CHAN_GYRO_XYZ ||
|
||||
i == SENSOR_CHAN_MAGN_XYZ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = sensor_channel_get(sensor, i, &value);
|
||||
if (rc == -ENOTSUP && stats[i].state == SAMPLE_STATS_STATE_UNINITIALIZED) {
|
||||
/* Stop reading this channel if the driver told us it's not supported. */
|
||||
stats[i].state = SAMPLE_STATS_STATE_DISABLED;
|
||||
}
|
||||
if (rc != 0) {
|
||||
/* Skip on any error. */
|
||||
continue;
|
||||
}
|
||||
/* Do something with the data */
|
||||
stats[i].accumulator += value.val1 * INT64_C(1000000) + value.val2;
|
||||
if (stats[i].count++ == 0) {
|
||||
stats[i].sample_window_start = now;
|
||||
} else if (now > stats[i].sample_window_start +
|
||||
CONFIG_SENSOR_SHELL_TRIG_PRINT_TIMEOUT_MS) {
|
||||
int64_t micro_value = stats[i].accumulator / stats[i].count;
|
||||
|
||||
value.val1 = micro_value / 1000000;
|
||||
value.val2 = (int32_t)llabs(micro_value - (value.val1 * 1000000));
|
||||
LOG_INF("chan=%d, num_samples=%u, data=%d.%06d", i, stats[i].count,
|
||||
value.val1, value.val2);
|
||||
|
||||
stats[i].accumulator = 0;
|
||||
stats[i].count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int cmd_trig_sensor(const struct shell *sh, size_t argc, char **argv)
|
||||
{
|
||||
const struct device *dev;
|
||||
enum sensor_trigger_type trigger;
|
||||
int err;
|
||||
|
||||
if (argc < 4) {
|
||||
shell_error(sh, "Wrong number of args");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* Parse device name */
|
||||
dev = device_get_binding(argv[1]);
|
||||
if (dev == NULL) {
|
||||
shell_error(sh, "Device unknown (%s)", argv[1]);
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Map the trigger string to an enum value */
|
||||
for (trigger = 0; trigger < ARRAY_SIZE(sensor_trigger_table); trigger++) {
|
||||
if (strcmp(argv[3], sensor_trigger_table[trigger].name) == 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (trigger >= SENSOR_TRIG_COMMON_COUNT || sensor_trigger_table[trigger].handler == NULL) {
|
||||
shell_error(sh, "Unsupported trigger type (%s)", argv[3]);
|
||||
return -ENOTSUP;
|
||||
}
|
||||
|
||||
/* Parse on/off */
|
||||
if (strcmp(argv[2], "on") == 0) {
|
||||
err = sensor_trigger_set(dev, &sensor_trigger_table[trigger].trigger,
|
||||
sensor_trigger_table[trigger].handler);
|
||||
} else if (strcmp(argv[2], "off") == 0) {
|
||||
/* Clear the handler for the given trigger on this device */
|
||||
err = sensor_trigger_set(dev, &sensor_trigger_table[trigger].trigger, NULL);
|
||||
} else {
|
||||
shell_error(sh, "Pass 'on' or 'off' to enable/disable trigger");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (err) {
|
||||
shell_error(sh, "Error while setting trigger %d on device %s (%d)", trigger,
|
||||
argv[1], err);
|
||||
}
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/* clang-format off */
|
||||
SHELL_STATIC_SUBCMD_SET_CREATE(sub_sensor,
|
||||
SHELL_CMD_ARG(get, &dsub_device_name, SENSOR_GET_HELP, cmd_get_sensor,
|
||||
@ -535,6 +741,8 @@ SHELL_STATIC_SUBCMD_SET_CREATE(sub_sensor,
|
||||
cmd_sensor_attr_get, 2, 255),
|
||||
SHELL_COND_CMD(CONFIG_SENSOR_INFO, info, NULL, SENSOR_INFO_HELP,
|
||||
cmd_get_sensor_info),
|
||||
SHELL_CMD_ARG(trig, &dsub_trigger, SENSOR_TRIG_HELP, cmd_trig_sensor,
|
||||
2, 255),
|
||||
SHELL_SUBCMD_SET_END
|
||||
);
|
||||
/* clang-format on */
|
||||
|
||||
@ -6,6 +6,5 @@ find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||
project(sensor_shell)
|
||||
|
||||
target_sources(app PRIVATE src/main.c)
|
||||
target_sources_ifdef(CONFIG_INIT_TRIG_DATA_READY app PRIVATE src/trigger.c)
|
||||
|
||||
target_include_directories(app PRIVATE include)
|
||||
|
||||
@ -1,17 +1,4 @@
|
||||
# Copyright (c) 2023 Google LLC
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
config SAMPLE_PRINT_TIMEOUT_MS
|
||||
int "Timeout for printing the average sensor sample value"
|
||||
default 5000
|
||||
help
|
||||
Control the frequency of the sampling window over which the sensor
|
||||
interrupt handler will collect data.
|
||||
|
||||
source "Kconfig.zephyr"
|
||||
|
||||
config INIT_TRIG_DATA_READY
|
||||
bool "Register data ready triggers for all sensors on start"
|
||||
help
|
||||
When the application starts, automatically register data ready trigger
|
||||
listeners to all available sensors.
|
||||
|
||||
@ -4,28 +4,12 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <zephyr/drivers/sensor.h>
|
||||
#include <zephyr/kernel.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
#include <zephyr/sys/iterable_sections.h>
|
||||
|
||||
#include "trigger.h"
|
||||
|
||||
LOG_MODULE_REGISTER(app);
|
||||
/*
|
||||
* This sample app launches a shell. Interact with it using the `sensor` command. See
|
||||
* `drivers/sensor/sensor_shell.c`. There is nothing to do in the main thread.
|
||||
*/
|
||||
|
||||
int main(void)
|
||||
{
|
||||
if (IS_ENABLED(CONFIG_INIT_TRIG_DATA_READY)) {
|
||||
STRUCT_SECTION_FOREACH(sensor_info, sensor)
|
||||
{
|
||||
struct sensor_trigger trigger = {
|
||||
.chan = SENSOR_CHAN_ALL,
|
||||
.type = SENSOR_TRIG_DATA_READY,
|
||||
};
|
||||
sensor_trigger_set(sensor->dev, &trigger,
|
||||
sensor_shell_data_ready_trigger_handler);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -1,78 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Google LLC
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "trigger.h"
|
||||
|
||||
#include <zephyr/device.h>
|
||||
#include <zephyr/drivers/sensor.h>
|
||||
#include <zephyr/logging/log.h>
|
||||
|
||||
LOG_MODULE_DECLARE(app);
|
||||
|
||||
enum sample_stats_state {
|
||||
SAMPLE_STATS_STATE_UNINITIALIZED = 0,
|
||||
SAMPLE_STATS_STATE_ENABLED,
|
||||
SAMPLE_STATS_STATE_DISABLED,
|
||||
};
|
||||
|
||||
struct sample_stats {
|
||||
int64_t accumulator;
|
||||
uint32_t count;
|
||||
uint64_t sample_window_start;
|
||||
enum sample_stats_state state;
|
||||
};
|
||||
|
||||
void sensor_shell_data_ready_trigger_handler(const struct device *sensor,
|
||||
const struct sensor_trigger *trigger)
|
||||
{
|
||||
static struct sample_stats stats[SENSOR_CHAN_ALL];
|
||||
const int64_t now = k_uptime_get();
|
||||
struct sensor_value value;
|
||||
|
||||
ARG_UNUSED(trigger);
|
||||
|
||||
if (sensor_sample_fetch(sensor)) {
|
||||
LOG_ERR("Failed to fetch samples on data ready handler");
|
||||
}
|
||||
for (int i = 0; i < SENSOR_CHAN_ALL; ++i) {
|
||||
int rc;
|
||||
|
||||
/* Skip disabled channels */
|
||||
if (stats[i].state == SAMPLE_STATS_STATE_DISABLED) {
|
||||
continue;
|
||||
}
|
||||
/* Skip 3 axis channels */
|
||||
if (i == SENSOR_CHAN_ACCEL_XYZ || i == SENSOR_CHAN_GYRO_XYZ ||
|
||||
i == SENSOR_CHAN_MAGN_XYZ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rc = sensor_channel_get(sensor, i, &value);
|
||||
if (rc == -ENOTSUP && stats[i].state == SAMPLE_STATS_STATE_UNINITIALIZED) {
|
||||
/* Stop reading this channel if the driver told us it's not supported. */
|
||||
stats[i].state = SAMPLE_STATS_STATE_DISABLED;
|
||||
}
|
||||
if (rc != 0) {
|
||||
/* Skip on any error. */
|
||||
continue;
|
||||
}
|
||||
/* Do something with the data */
|
||||
stats[i].accumulator += value.val1 * INT64_C(1000000) + value.val2;
|
||||
if (stats[i].count++ == 0) {
|
||||
stats[i].sample_window_start = now;
|
||||
} else if (now > stats[i].sample_window_start + CONFIG_SAMPLE_PRINT_TIMEOUT_MS) {
|
||||
int64_t micro_value = stats[i].accumulator / stats[i].count;
|
||||
|
||||
value.val1 = micro_value / 1000000;
|
||||
value.val2 = (int32_t)llabs(micro_value - (value.val1 * 1000000));
|
||||
LOG_INF("chan=%d, num_samples=%u, data=%d.%06d", i, stats[i].count,
|
||||
value.val1, value.val2);
|
||||
|
||||
stats[i].accumulator = 0;
|
||||
stats[i].count = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user