chore: initial commit
This commit is contained in:
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "domain_internal.h"
|
||||
|
||||
float domain_angle_normalize(float angle)
|
||||
{
|
||||
while (angle > 180.0f) {
|
||||
angle -= 360.0f;
|
||||
}
|
||||
while (angle <= -180.0f) {
|
||||
angle += 360.0f;
|
||||
}
|
||||
return angle;
|
||||
}
|
||||
|
||||
float domain_angle_shortest_rotation(float current, float target)
|
||||
{
|
||||
float diff = target - current;
|
||||
|
||||
while (diff > 180.0f) {
|
||||
diff -= 360.0f;
|
||||
}
|
||||
while (diff < -180.0f) {
|
||||
diff += 360.0f;
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "domain_internal.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <string.h>
|
||||
|
||||
static const domain_direction_entry_t s_default_directions[] = {
|
||||
{"南", "S", 0.0f},
|
||||
{"西南", "SW", 45.0f},
|
||||
{"西", "W", 90.0f},
|
||||
{"西北", "NW", 135.0f},
|
||||
{"北", "N", 180.0f},
|
||||
{"东北", "NE", -135.0f},
|
||||
{"东", "E", -90.0f},
|
||||
{"东南", "SE", -45.0f},
|
||||
};
|
||||
|
||||
/*
|
||||
* Aliases for cardinal directions. The server fortune API returns the four
|
||||
* cardinals with a "正" prefix (正北/正南/正东/正西); map those to the catalog's
|
||||
* canonical names. The four diagonals (东北/西北/东南/西南) already match.
|
||||
*/
|
||||
static const struct {
|
||||
const char *alias;
|
||||
const char *canonical_cn;
|
||||
} s_direction_aliases[] = {
|
||||
{"正南", "南"},
|
||||
{"正西", "西"},
|
||||
{"正北", "北"},
|
||||
{"正东", "东"},
|
||||
};
|
||||
|
||||
static void trimmed_span(const char *input, const char **start_out, size_t *len_out)
|
||||
{
|
||||
const char *start = input;
|
||||
const char *end = NULL;
|
||||
|
||||
if (input == NULL) {
|
||||
*start_out = NULL;
|
||||
*len_out = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
while (*start != '\0' && isspace((unsigned char)*start)) {
|
||||
start++;
|
||||
}
|
||||
|
||||
end = start + strlen(start);
|
||||
while (end > start && isspace((unsigned char)*(end - 1))) {
|
||||
end--;
|
||||
}
|
||||
|
||||
*start_out = start;
|
||||
*len_out = (size_t)(end - start);
|
||||
}
|
||||
|
||||
static bool equals_trimmed(const char *input, const char *token, bool case_insensitive)
|
||||
{
|
||||
const char *start = NULL;
|
||||
size_t len = 0;
|
||||
size_t token_len = 0;
|
||||
|
||||
if (input == NULL || token == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
trimmed_span(input, &start, &len);
|
||||
token_len = strlen(token);
|
||||
if (len == 0 || len != token_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < len; i++) {
|
||||
char left = start[i];
|
||||
char right = token[i];
|
||||
|
||||
if (case_insensitive) {
|
||||
left = (char)tolower((unsigned char)left);
|
||||
right = (char)tolower((unsigned char)right);
|
||||
}
|
||||
|
||||
if (left != right) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t domain_direction_catalog_count(void)
|
||||
{
|
||||
return sizeof(s_default_directions) / sizeof(s_default_directions[0]);
|
||||
}
|
||||
|
||||
const domain_direction_entry_t *domain_direction_catalog_get(size_t index)
|
||||
{
|
||||
if (index >= domain_direction_catalog_count()) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return &s_default_directions[index];
|
||||
}
|
||||
|
||||
static bool matches_direction_alias(const char *input, const char *canonical_cn)
|
||||
{
|
||||
for (size_t i = 0; i < sizeof(s_direction_aliases) / sizeof(s_direction_aliases[0]); i++) {
|
||||
if (strcmp(s_direction_aliases[i].canonical_cn, canonical_cn) == 0 &&
|
||||
equals_trimmed(input, s_direction_aliases[i].alias, false)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool domain_direction_catalog_find_by_name(const char *input, size_t *out_index)
|
||||
{
|
||||
for (size_t i = 0; i < domain_direction_catalog_count(); i++) {
|
||||
const domain_direction_entry_t *entry = domain_direction_catalog_get(i);
|
||||
if (entry == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (equals_trimmed(input, entry->name_cn, false) ||
|
||||
equals_trimmed(input, entry->name_en, true) ||
|
||||
matches_direction_alias(input, entry->name_cn)) {
|
||||
if (out_index != NULL) {
|
||||
*out_index = i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool domain_direction_catalog_find_by_angle(float angle, size_t *out_index)
|
||||
{
|
||||
for (size_t i = 0; i < domain_direction_catalog_count(); i++) {
|
||||
const domain_direction_entry_t *entry = domain_direction_catalog_get(i);
|
||||
if (entry == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (entry->angle == angle) {
|
||||
if (out_index != NULL) {
|
||||
*out_index = i;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,317 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#include "domain.h"
|
||||
|
||||
#include "domain_internal.h"
|
||||
#include "esp_log.h"
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/portmacro.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/task.h"
|
||||
|
||||
static const char *TAG = "domain";
|
||||
|
||||
#define DOMAIN_DEFAULT_ROTATE_SPEED_US 1500
|
||||
#define DOMAIN_ROTATE_TIMEOUT_MS 8000
|
||||
#define DOMAIN_STATE_LOCK_TIMEOUT_MS 2000
|
||||
#define DOMAIN_DEFAULT_CALIBRATION_ENABLED 1
|
||||
#define DOMAIN_DEFAULT_CALIBRATION_ANGLE_DEG 180.0f
|
||||
#define DOMAIN_DEFAULT_CALIBRATION_STEP_ANGLE_DEG 5.0f
|
||||
#define DOMAIN_DEFAULT_CALIBRATION_MAX_SWEEP_DEG 360.0f
|
||||
#define DOMAIN_DEFAULT_CALIBRATION_SEARCH_CLOCKWISE 1
|
||||
#define DOMAIN_DEFAULT_CALIBRATION_SETTLE_DELAY_MS 10
|
||||
|
||||
static float s_current_angle = 0.0f;
|
||||
static int s_rotate_speed_us = DOMAIN_DEFAULT_ROTATE_SPEED_US;
|
||||
static bool s_initialized;
|
||||
static SemaphoreHandle_t s_state_mutex;
|
||||
static portMUX_TYPE s_state_mutex_init_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
static StaticSemaphore_t s_state_mutex_buf;
|
||||
#endif
|
||||
|
||||
static const domain_direction_axis_config_t s_default_axis_config = {
|
||||
.initial_angle = 0.0f,
|
||||
.rotate_speed_us = 0
|
||||
};
|
||||
|
||||
static const domain_direction_calibration_t s_default_calibration = {
|
||||
.enabled = DOMAIN_DEFAULT_CALIBRATION_ENABLED,
|
||||
.calibration_angle = DOMAIN_DEFAULT_CALIBRATION_ANGLE_DEG,
|
||||
.step_angle_deg = DOMAIN_DEFAULT_CALIBRATION_STEP_ANGLE_DEG,
|
||||
.max_sweep_deg = DOMAIN_DEFAULT_CALIBRATION_MAX_SWEEP_DEG,
|
||||
.search_clockwise = DOMAIN_DEFAULT_CALIBRATION_SEARCH_CLOCKWISE,
|
||||
.settle_delay_ms = DOMAIN_DEFAULT_CALIBRATION_SETTLE_DELAY_MS
|
||||
};
|
||||
|
||||
static int remaining_timeout_ms(TickType_t start_tick, int timeout_ms)
|
||||
{
|
||||
int effective_timeout_ms = timeout_ms > 0 ? timeout_ms : DOMAIN_ROTATE_TIMEOUT_MS;
|
||||
TickType_t timeout_ticks = pdMS_TO_TICKS(effective_timeout_ms);
|
||||
if (timeout_ticks == 0) {
|
||||
timeout_ticks = 1;
|
||||
}
|
||||
|
||||
TickType_t elapsed_ticks = xTaskGetTickCount() - start_tick;
|
||||
if (elapsed_ticks >= timeout_ticks) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
TickType_t remaining_ticks = timeout_ticks - elapsed_ticks;
|
||||
int remaining_ms = (int)(remaining_ticks * portTICK_PERIOD_MS);
|
||||
return remaining_ms > 0 ? remaining_ms : 1;
|
||||
}
|
||||
|
||||
static bool ensure_state_mutex(void)
|
||||
{
|
||||
if (s_state_mutex != NULL) {
|
||||
return true;
|
||||
}
|
||||
#if ( configSUPPORT_STATIC_ALLOCATION == 1 )
|
||||
portENTER_CRITICAL(&s_state_mutex_init_lock);
|
||||
if (s_state_mutex == NULL) {
|
||||
s_state_mutex = xSemaphoreCreateRecursiveMutexStatic(&s_state_mutex_buf);
|
||||
}
|
||||
portEXIT_CRITICAL(&s_state_mutex_init_lock);
|
||||
#else
|
||||
SemaphoreHandle_t created_mutex = xSemaphoreCreateRecursiveMutex();
|
||||
|
||||
portENTER_CRITICAL(&s_state_mutex_init_lock);
|
||||
if (s_state_mutex == NULL) {
|
||||
s_state_mutex = created_mutex;
|
||||
created_mutex = NULL;
|
||||
}
|
||||
portEXIT_CRITICAL(&s_state_mutex_init_lock);
|
||||
|
||||
if (created_mutex != NULL) {
|
||||
vSemaphoreDelete(created_mutex);
|
||||
}
|
||||
#endif
|
||||
return (s_state_mutex != NULL);
|
||||
}
|
||||
|
||||
bool domain_direction_lock_state(int timeout_ms)
|
||||
{
|
||||
if (!ensure_state_mutex()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int effective_timeout_ms = timeout_ms > 0 ? timeout_ms : DOMAIN_STATE_LOCK_TIMEOUT_MS;
|
||||
TickType_t timeout_ticks = pdMS_TO_TICKS(effective_timeout_ms);
|
||||
if (timeout_ticks == 0) {
|
||||
timeout_ticks = 1;
|
||||
}
|
||||
return (xSemaphoreTakeRecursive(s_state_mutex, timeout_ticks) == pdTRUE);
|
||||
}
|
||||
|
||||
void domain_direction_unlock_state(void)
|
||||
{
|
||||
if (s_state_mutex != NULL) {
|
||||
(void)xSemaphoreGiveRecursive(s_state_mutex);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t domain_init(const domain_direction_axis_config_t *config,
|
||||
const domain_direction_calibration_t *calibration)
|
||||
{
|
||||
esp_err_t runtime_err = domain_runtime_platform_init();
|
||||
if (runtime_err != ESP_OK) {
|
||||
return runtime_err;
|
||||
}
|
||||
|
||||
if (!domain_direction_lock_state(DOMAIN_STATE_LOCK_TIMEOUT_MS)) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
if (s_initialized) {
|
||||
domain_direction_unlock_state();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_current_angle = 0.0f;
|
||||
s_rotate_speed_us = DOMAIN_DEFAULT_ROTATE_SPEED_US;
|
||||
|
||||
if (config != NULL) {
|
||||
s_current_angle = domain_angle_normalize(config->initial_angle);
|
||||
if (config->rotate_speed_us > 0) {
|
||||
s_rotate_speed_us = config->rotate_speed_us;
|
||||
}
|
||||
}
|
||||
|
||||
s_initialized = true;
|
||||
domain_direction_unlock_state();
|
||||
|
||||
domain_calibration_store_set(calibration);
|
||||
|
||||
ESP_LOGI(TAG, "Direction axis initialized (angle=%.1f, speed=%dus)",
|
||||
s_current_angle, s_rotate_speed_us);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const domain_direction_axis_config_t *domain_direction_default_axis_config(void)
|
||||
{
|
||||
return &s_default_axis_config;
|
||||
}
|
||||
|
||||
const domain_direction_calibration_t *domain_direction_default_calibration(void)
|
||||
{
|
||||
return &s_default_calibration;
|
||||
}
|
||||
|
||||
bool domain_is_initialized(void)
|
||||
{
|
||||
bool initialized = false;
|
||||
if (domain_direction_lock_state(DOMAIN_STATE_LOCK_TIMEOUT_MS)) {
|
||||
initialized = s_initialized;
|
||||
domain_direction_unlock_state();
|
||||
}
|
||||
return initialized;
|
||||
}
|
||||
|
||||
size_t domain_get_direction_count(void)
|
||||
{
|
||||
return domain_direction_catalog_count();
|
||||
}
|
||||
|
||||
bool domain_get_direction_view(size_t index,
|
||||
const char **out_name_cn,
|
||||
const char **out_name_en,
|
||||
float *out_angle)
|
||||
{
|
||||
const domain_direction_entry_t *entry = domain_direction_catalog_get(index);
|
||||
if (entry == NULL) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (out_name_cn != NULL) {
|
||||
*out_name_cn = entry->name_cn;
|
||||
}
|
||||
if (out_name_en != NULL) {
|
||||
*out_name_en = entry->name_en;
|
||||
}
|
||||
if (out_angle != NULL) {
|
||||
*out_angle = entry->angle;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool domain_find_direction_by_name(const char *input, size_t *out_index)
|
||||
{
|
||||
return domain_direction_catalog_find_by_name(input, out_index);
|
||||
}
|
||||
|
||||
bool domain_get_current_direction_index(size_t *out_index)
|
||||
{
|
||||
if (!domain_direction_lock_state(DOMAIN_STATE_LOCK_TIMEOUT_MS)) {
|
||||
return false;
|
||||
}
|
||||
if (!s_initialized) {
|
||||
domain_direction_unlock_state();
|
||||
return false;
|
||||
}
|
||||
float angle = s_current_angle;
|
||||
domain_direction_unlock_state();
|
||||
return domain_direction_catalog_find_by_angle(angle, out_index);
|
||||
}
|
||||
|
||||
float domain_get_current_angle(void)
|
||||
{
|
||||
if (!domain_direction_lock_state(DOMAIN_STATE_LOCK_TIMEOUT_MS)) {
|
||||
return 0.0f;
|
||||
}
|
||||
float angle = s_current_angle;
|
||||
domain_direction_unlock_state();
|
||||
return angle;
|
||||
}
|
||||
|
||||
void domain_set_current_angle(float angle)
|
||||
{
|
||||
if (!domain_direction_lock_state(DOMAIN_STATE_LOCK_TIMEOUT_MS)) {
|
||||
return;
|
||||
}
|
||||
s_current_angle = domain_angle_normalize(angle);
|
||||
domain_direction_unlock_state();
|
||||
}
|
||||
|
||||
int domain_get_rotate_speed_us(void)
|
||||
{
|
||||
if (!domain_direction_lock_state(DOMAIN_STATE_LOCK_TIMEOUT_MS)) {
|
||||
return 0;
|
||||
}
|
||||
int speed = s_rotate_speed_us;
|
||||
domain_direction_unlock_state();
|
||||
return speed;
|
||||
}
|
||||
|
||||
esp_err_t domain_rotate_to_angle_with_timeout(float target_angle, int timeout_ms)
|
||||
{
|
||||
TickType_t start_tick = xTaskGetTickCount();
|
||||
int effective_timeout_ms = timeout_ms > 0 ? timeout_ms : DOMAIN_ROTATE_TIMEOUT_MS;
|
||||
|
||||
if (!domain_direction_lock_state(effective_timeout_ms)) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
if (!s_initialized) {
|
||||
domain_direction_unlock_state();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
|
||||
float normalized_target = domain_angle_normalize(target_angle);
|
||||
float rotation = domain_angle_shortest_rotation(s_current_angle, normalized_target);
|
||||
if (rotation == 0.0f) {
|
||||
domain_direction_unlock_state();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int motor_timeout_ms = remaining_timeout_ms(start_tick, effective_timeout_ms);
|
||||
if (motor_timeout_ms <= 0) {
|
||||
domain_direction_unlock_state();
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
esp_err_t motor_err = domain_runtime_stepper_rotate_angle_with_accel_timeout(
|
||||
rotation,
|
||||
s_rotate_speed_us,
|
||||
motor_timeout_ms);
|
||||
if (motor_err != ESP_OK) {
|
||||
domain_direction_unlock_state();
|
||||
return motor_err;
|
||||
}
|
||||
|
||||
s_current_angle = domain_angle_normalize(s_current_angle + rotation);
|
||||
domain_direction_unlock_state();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t domain_apply_rotation_delta(float delta)
|
||||
{
|
||||
if (!domain_direction_lock_state(DOMAIN_STATE_LOCK_TIMEOUT_MS)) {
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
if (!s_initialized) {
|
||||
domain_direction_unlock_state();
|
||||
return ESP_ERR_INVALID_STATE;
|
||||
}
|
||||
if (delta == 0.0f) {
|
||||
domain_direction_unlock_state();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
s_current_angle = domain_angle_normalize(s_current_angle + delta);
|
||||
domain_direction_unlock_state();
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void domain_set_calibration(const domain_direction_calibration_t *config)
|
||||
{
|
||||
domain_calibration_store_set(config);
|
||||
}
|
||||
|
||||
bool domain_get_calibration(domain_direction_calibration_t *out_config)
|
||||
{
|
||||
return domain_calibration_store_get(out_config);
|
||||
}
|
||||
Reference in New Issue
Block a user