sensor_shell: add attribute support

Add 2 new sub-commands to the sensor command (attr_get and attr_set).
These commands can be used to access the driver's attr_set and attr_get
functions.

Signed-off-by: Yuval Peress <peress@google.com>
diff --git a/drivers/sensor/sensor_shell.c b/drivers/sensor/sensor_shell.c
index 442fbd9..37428fc 100644
--- a/drivers/sensor/sensor_shell.c
+++ b/drivers/sensor/sensor_shell.c
@@ -16,6 +16,15 @@
 	"when no channels are provided. Syntax:\n"                                                 \
 	"<device_name> <channel name 0> .. <channel name N>"
 
+#define SENSOR_ATTR_GET_HELP                                                                       \
+	"Get the sensor's channel attribute. Syntax:\n"                                            \
+	"<device_name> [<channel_name 0> <attribute_name 0> .. "                                   \
+	"<channel_name N> <attribute_name N>]"
+
+#define SENSOR_ATTR_SET_HELP                                                                       \
+	"Set the sensor's channel attribute.\n"                                                    \
+	"<device_name> <channel_name> <attribute_name> <value>"
+
 #define SENSOR_INFO_HELP "Get sensor info, such as vendor and model name, for all sensors."
 
 const char *sensor_channel_name[SENSOR_CHAN_ALL] = {
@@ -78,29 +87,105 @@
 	[SENSOR_CHAN_GAUGE_DESIRED_CHARGING_CURRENT] = "gauge_desired_charging_current",
 };
 
-static int handle_channel_by_name(const struct shell *shell, const struct device *dev,
-				  const char *channel_name)
+static const char *sensor_attribute_name[SENSOR_ATTR_COMMON_COUNT] = {
+	[SENSOR_ATTR_SAMPLING_FREQUENCY] = "sampling_frequency",
+	[SENSOR_ATTR_LOWER_THRESH] = "lower_thresh",
+	[SENSOR_ATTR_UPPER_THRESH] = "upper_thresh",
+	[SENSOR_ATTR_SLOPE_TH] = "slope_th",
+	[SENSOR_ATTR_SLOPE_DUR] = "slope_dur",
+	[SENSOR_ATTR_HYSTERESIS] = "hysteresis",
+	[SENSOR_ATTR_OVERSAMPLING] = "oversampling",
+	[SENSOR_ATTR_FULL_SCALE] = "full_scale",
+	[SENSOR_ATTR_OFFSET] = "offset",
+	[SENSOR_ATTR_CALIB_TARGET] = "calib_target",
+	[SENSOR_ATTR_CONFIGURATION] = "configuration",
+	[SENSOR_ATTR_CALIBRATION] = "calibration",
+	[SENSOR_ATTR_FEATURE_MASK] = "feature_mask",
+	[SENSOR_ATTR_ALERT] = "alert",
+	[SENSOR_ATTR_FF_DUR] = "ff_dur",
+};
+
+enum dynamic_command_context {
+	NONE,
+	CTX_GET,
+	CTX_ATTR_GET_SET,
+};
+
+static enum dynamic_command_context current_cmd_ctx = NONE;
+
+static int parse_named_int(const char *name, const char *heystack[], size_t count)
 {
-	struct sensor_value value[3];
 	char *endptr;
-	int err;
 	int i;
 
 	/* Attempt to parse channel name as a number first */
-	i = strtoul(channel_name, &endptr, 0);
+	i = strtoul(name, &endptr, 0);
 
+	if (*endptr == '\0') {
+		return i;
+	}
+
+	/* Channel name is not a number, look it up */
+	for (i = 0; i < count; i++) {
+		if (strcmp(name, heystack[i]) == 0) {
+			return i;
+		}
+	}
+
+	return -ENOTSUP;
+}
+
+static int parse_sensor_value(const char *val_str, struct sensor_value *out)
+{
+	const bool is_negative = val_str[0] == '-';
+	const char *decimal_pos = strchr(val_str, '.');
+	long value;
+	char *endptr;
+
+	/* Parse int portion */
+	value = strtol(val_str, &endptr, 0);
+
+	if (*endptr != '\0' && *endptr != '.') {
+		return -EINVAL;
+	}
+	if (value > INT32_MAX || value < INT32_MIN) {
+		return -EINVAL;
+	}
+	out->val1 = (int32_t)value;
+
+	if (decimal_pos == NULL) {
+		return 0;
+	}
+
+	/* Parse the decimal portion */
+	value = strtoul(decimal_pos + 1, &endptr, 0);
 	if (*endptr != '\0') {
-		/* Channel name is not a number, look it up */
-		for (i = 0; i < ARRAY_SIZE(sensor_channel_name); i++) {
-			if (strcmp(channel_name, sensor_channel_name[i]) == 0) {
-				break;
-			}
-		}
+		return -EINVAL;
+	}
+	while (value < 100000) {
+		value *= 10;
+	}
+	if (value > INT32_C(999999)) {
+		return -EINVAL;
+	}
+	out->val2 = (int32_t)value;
+	if (is_negative) {
+		out->val2 *= -1;
+	}
+	return 0;
+}
 
-		if (i == ARRAY_SIZE(sensor_channel_name)) {
-			shell_error(shell, "Channel not supported (%s)", channel_name);
-			return -ENOTSUP;
-		}
+static int handle_channel_by_name(const struct shell *shell_ptr, const struct device *dev,
+				  const char *channel_name)
+{
+	struct sensor_value value[3];
+	int err;
+	const int i =
+		parse_named_int(channel_name, sensor_channel_name, ARRAY_SIZE(sensor_channel_name));
+
+	if (i < 0) {
+		shell_error(shell_ptr, "Channel not supported (%s)", channel_name);
+		return i;
 	}
 
 	err = sensor_channel_get(dev, i, value);
@@ -109,15 +194,15 @@
 	}
 
 	if (i >= ARRAY_SIZE(sensor_channel_name)) {
-		shell_print(shell, "channel idx=%d value = %10.6f", i,
+		shell_print(shell_ptr, "channel idx=%d value = %10.6f", i,
 			    sensor_value_to_double(&value[0]));
 	} else if (i != SENSOR_CHAN_ACCEL_XYZ && i != SENSOR_CHAN_GYRO_XYZ &&
 		   i != SENSOR_CHAN_MAGN_XYZ) {
-		shell_print(shell, "channel idx=%d %s = %10.6f", i, sensor_channel_name[i],
+		shell_print(shell_ptr, "channel idx=%d %s = %10.6f", i, sensor_channel_name[i],
 			    sensor_value_to_double(&value[0]));
 	} else {
 		/* clang-format off */
-		shell_print(shell,
+		shell_print(shell_ptr,
 			"channel idx=%d %s x = %10.6f y = %10.6f z = %10.6f",
 			i, sensor_channel_name[i],
 			sensor_value_to_double(&value[0]),
@@ -128,6 +213,7 @@
 
 	return 0;
 }
+
 static int cmd_get_sensor(const struct shell *shell, size_t argc, char *argv[])
 {
 	const struct device *dev;
@@ -163,10 +249,140 @@
 	return 0;
 }
 
+static int cmd_sensor_attr_set(const struct shell *shell_ptr, size_t argc, char *argv[])
+{
+	const struct device *dev;
+	int rc;
+
+	dev = device_get_binding(argv[1]);
+	if (dev == NULL) {
+		shell_error(shell_ptr, "Device unknown (%s)", argv[1]);
+		return -ENODEV;
+	}
+
+	for (size_t i = 2; i < argc; i += 3) {
+		int channel = parse_named_int(argv[i], sensor_channel_name,
+					      ARRAY_SIZE(sensor_channel_name));
+		int attr = parse_named_int(argv[i + 1], sensor_attribute_name,
+					   ARRAY_SIZE(sensor_attribute_name));
+		struct sensor_value value = {0};
+
+		if (channel < 0) {
+			shell_error(shell_ptr, "Channel '%s' unknown", argv[i]);
+			return -EINVAL;
+		}
+		if (attr < 0) {
+			shell_error(shell_ptr, "Attribute '%s' unknown", argv[i + 1]);
+			return -EINVAL;
+		}
+		if (parse_sensor_value(argv[i + 2], &value)) {
+			shell_error(shell_ptr, "Sensor value '%s' invalid", argv[i + 2]);
+			return -EINVAL;
+		}
+
+		rc = sensor_attr_set(dev, channel, attr, &value);
+		if (rc) {
+			shell_error(shell_ptr, "Failed to set channel(%s) attribute(%s): %d",
+				    sensor_channel_name[channel], sensor_attribute_name[attr], rc);
+			continue;
+		}
+		shell_info(shell_ptr, "%s channel=%s, attr=%s set to value=%s", dev->name,
+			   sensor_channel_name[channel], sensor_attribute_name[attr], argv[i + 2]);
+	}
+	return 0;
+}
+
+static void cmd_sensor_attr_get_handler(const struct shell *shell_ptr, const struct device *dev,
+					const char *channel_name, const char *attr_name,
+					bool print_missing_attribute)
+{
+	int channel =
+		parse_named_int(channel_name, sensor_channel_name, ARRAY_SIZE(sensor_channel_name));
+	int attr = parse_named_int(attr_name, sensor_attribute_name,
+				   ARRAY_SIZE(sensor_attribute_name));
+	struct sensor_value value = {0};
+	int rc;
+
+	if (channel < 0) {
+		shell_error(shell_ptr, "Channel '%s' unknown", channel_name);
+		return;
+	}
+	if (attr < 0) {
+		shell_error(shell_ptr, "Attribute '%s' unknown", attr_name);
+		return;
+	}
+
+	rc = sensor_attr_get(dev, channel, attr, &value);
+
+	if (rc != 0) {
+		if (rc == -EINVAL && !print_missing_attribute) {
+			return;
+		}
+		shell_error(shell_ptr, "Failed to get channel(%s) attribute(%s): %d",
+			    sensor_channel_name[channel], sensor_attribute_name[attr], rc);
+		return;
+	}
+
+	shell_info(shell_ptr, "%s(channel=%s, attr=%s) value=%.6f", dev->name,
+		   sensor_channel_name[channel], sensor_attribute_name[attr],
+		   sensor_value_to_double(&value));
+}
+
+static int cmd_sensor_attr_get(const struct shell *shell_ptr, size_t argc, char *argv[])
+{
+	const struct device *dev;
+
+	dev = device_get_binding(argv[1]);
+	if (dev == NULL) {
+		shell_error(shell_ptr, "Device unknown (%s)", argv[1]);
+		return -ENODEV;
+	}
+
+	if (argc > 2) {
+		for (size_t i = 2; i < argc; i += 2) {
+			cmd_sensor_attr_get_handler(shell_ptr, dev, argv[i], argv[i + 1],
+						    /*print_missing_attribute=*/true);
+		}
+	} else {
+		for (size_t channel_idx = 0; channel_idx < ARRAY_SIZE(sensor_channel_name);
+		     ++channel_idx) {
+			for (size_t attr_idx = 0; attr_idx < ARRAY_SIZE(sensor_attribute_name);
+			     ++attr_idx) {
+				cmd_sensor_attr_get_handler(shell_ptr, dev,
+							    sensor_channel_name[channel_idx],
+							    sensor_attribute_name[attr_idx],
+							    /*print_missing_attribute=*/false);
+			}
+		}
+	}
+	return 0;
+}
+
 static void channel_name_get(size_t idx, struct shell_static_entry *entry);
 
 SHELL_DYNAMIC_CMD_CREATE(dsub_channel_name, channel_name_get);
 
+static void attribute_name_get(size_t idx, struct shell_static_entry *entry)
+{
+	int cnt = 0;
+
+	entry->syntax = NULL;
+	entry->handler = NULL;
+	entry->help = NULL;
+	entry->subcmd = &dsub_channel_name;
+
+	for (int i = 0; i < SENSOR_ATTR_COMMON_COUNT; i++) {
+		if (sensor_attribute_name[i] != NULL) {
+			if (cnt == idx) {
+				entry->syntax = sensor_attribute_name[i];
+				break;
+			}
+			cnt++;
+		}
+	}
+}
+SHELL_DYNAMIC_CMD_CREATE(dsub_attribute_name, attribute_name_get);
+
 static void channel_name_get(size_t idx, struct shell_static_entry *entry)
 {
 	int cnt = 0;
@@ -174,7 +390,13 @@
 	entry->syntax = NULL;
 	entry->handler = NULL;
 	entry->help = NULL;
-	entry->subcmd = &dsub_channel_name;
+	if (current_cmd_ctx == CTX_GET) {
+		entry->subcmd = &dsub_channel_name;
+	} else if (current_cmd_ctx == CTX_ATTR_GET_SET) {
+		entry->subcmd = &dsub_attribute_name;
+	} else {
+		entry->subcmd = NULL;
+	}
 
 	for (int i = 0; i < SENSOR_CHAN_ALL; i++) {
 		if (sensor_channel_name[i] != NULL) {
@@ -195,12 +417,25 @@
 {
 	const struct device *dev = shell_device_lookup(idx, NULL);
 
+	current_cmd_ctx = CTX_GET;
 	entry->syntax = (dev != NULL) ? dev->name : NULL;
 	entry->handler = NULL;
 	entry->help = NULL;
 	entry->subcmd = &dsub_channel_name;
 }
 
+static void device_name_get_for_attr(size_t idx, struct shell_static_entry *entry)
+{
+	const struct device *dev = shell_device_lookup(idx, NULL);
+
+	current_cmd_ctx = CTX_ATTR_GET_SET;
+	entry->syntax = (dev != NULL) ? dev->name : NULL;
+	entry->handler = NULL;
+	entry->help = NULL;
+	entry->subcmd = &dsub_channel_name;
+}
+SHELL_DYNAMIC_CMD_CREATE(dsub_device_name_for_attr, device_name_get_for_attr);
+
 static int cmd_get_sensor_info(const struct shell *sh, size_t argc, char **argv)
 {
 	ARG_UNUSED(argc);
@@ -228,6 +463,10 @@
 SHELL_STATIC_SUBCMD_SET_CREATE(sub_sensor,
 	SHELL_CMD_ARG(get, &dsub_device_name, SENSOR_GET_HELP, cmd_get_sensor,
 			2, 255),
+	SHELL_CMD_ARG(attr_set, &dsub_device_name_for_attr, SENSOR_ATTR_SET_HELP,
+			cmd_sensor_attr_set, 2, 255),
+	SHELL_CMD_ARG(attr_get, &dsub_device_name_for_attr, SENSOR_ATTR_GET_HELP,
+			cmd_sensor_attr_get, 2, 255),
 	SHELL_COND_CMD(CONFIG_SENSOR_INFO, info, NULL, SENSOR_INFO_HELP,
 			cmd_get_sensor_info),
 	SHELL_SUBCMD_SET_END