/** * @file profile_repository.c * @brief Future SD-card profile repository with minimal standalone parsing. */ #include "profile_repository.h" #include #include #include #include #include "ad9102_device.h" #include "storage_sd.h" #define PROFILE_REPOSITORY_BUFFER_SIZE 1024u static char *profile_repository_trim(char *text) { char *end; while ((*text != '\0') && isspace((unsigned char)*text)) { ++text; } if (*text == '\0') { return text; } end = text + strlen(text) - 1u; while ((end > text) && isspace((unsigned char)*end)) { *end = '\0'; --end; } return text; } static void profile_repository_copy_string(char *destination, size_t destination_size, const char *source) { if ((destination == NULL) || (destination_size == 0u)) { return; } if (source == NULL) { destination[0] = '\0'; return; } strncpy(destination, source, destination_size - 1u); destination[destination_size - 1u] = '\0'; } static bool profile_repository_parse_bool(const char *value, bool *out_value) { if ((value == NULL) || (out_value == NULL)) { return false; } if ((strcmp(value, "1") == 0) || (strcasecmp(value, "true") == 0) || (strcasecmp(value, "yes") == 0)) { *out_value = true; return true; } if ((strcmp(value, "0") == 0) || (strcasecmp(value, "false") == 0) || (strcasecmp(value, "no") == 0)) { *out_value = false; return true; } return false; } static uint16_t profile_repository_parse_u16(const char *value) { return (uint16_t)strtoul(value, NULL, 0); } static uint32_t profile_repository_parse_u32(const char *value) { return (uint32_t)strtoul(value, NULL, 0); } static float profile_repository_parse_float(const char *value) { return strtof(value, NULL); } static void profile_repository_reset_profile(profile_t *profile) { memset(profile, 0, sizeof(*profile)); profile->auto_run = true; profile->boot_enabled = true; profile->waveform.mode = WAVEFORM_MODE_SAW; profile->waveform.enabled = 1u; profile->waveform.saw_step = 1u; profile->waveform.pat_period_base = 2u; profile->waveform.pat_period = 0xFFFFu; profile->waveform.sample_count = AD9102_SRAM_DEFAULT_SAMPLE_COUNT; profile->waveform.hold_cycles = AD9102_SRAM_DEFAULT_HOLD; profile->waveform.amplitude = AD9102_SRAM_DEFAULT_AMPLITUDE; profile->ds1809.apply_position = false; } static void profile_repository_apply_key_value(profile_t *profile, const char *key, const char *value) { bool bool_value; if ((profile == NULL) || (key == NULL) || (value == NULL)) { return; } if (strcmp(key, "profile_name") == 0) { profile_repository_copy_string(profile->display_name, sizeof(profile->display_name), value); } else if (strcmp(key, "auto_run") == 0) { if (profile_repository_parse_bool(value, &bool_value)) { profile->auto_run = bool_value; } } else if (strcmp(key, "boot_enabled") == 0) { if (profile_repository_parse_bool(value, &bool_value)) { profile->boot_enabled = bool_value; } } else if (strcmp(key, "work_enable") == 0) { profile->work_config.work_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "u5v1_enable") == 0) { profile->work_config.supply_5v1_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "u5v2_enable") == 0) { profile->work_config.supply_5v2_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "ld1_enable") == 0) { profile->work_config.laser1_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "ld2_enable") == 0) { profile->work_config.laser2_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "ref1_enable") == 0) { profile->work_config.reference1_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "ref2_enable") == 0) { profile->work_config.reference2_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "tec1_enable") == 0) { profile->work_config.tec1_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "tec2_enable") == 0) { profile->work_config.tec2_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "ts1_enable") == 0) { profile->work_config.temp_sensor1_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "ts2_enable") == 0) { profile->work_config.temp_sensor2_enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "pid1_from_host") == 0) { profile->work_config.pid1_from_host = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "pid2_from_host") == 0) { profile->work_config.pid2_from_host = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "averages") == 0) { profile->work_config.averages = profile_repository_parse_u16(value); } else if (strcmp(key, "message_id") == 0) { profile->work_config.message_id = profile_repository_parse_u16(value); } else if (strcmp(key, "laser1_target_temp") == 0) { profile->laser_channels[0].target_temperature_raw = profile_repository_parse_u16(value); } else if (strcmp(key, "laser2_target_temp") == 0) { profile->laser_channels[1].target_temperature_raw = profile_repository_parse_u16(value); } else if (strcmp(key, "laser1_current") == 0) { profile->laser_channels[0].current_raw = profile_repository_parse_u16(value); } else if (strcmp(key, "laser2_current") == 0) { profile->laser_channels[1].current_raw = profile_repository_parse_u16(value); } else if (strcmp(key, "laser1_pid_p") == 0) { profile->laser_channels[0].pid_p = profile_repository_parse_float(value); } else if (strcmp(key, "laser1_pid_i") == 0) { profile->laser_channels[0].pid_i = profile_repository_parse_float(value); } else if (strcmp(key, "laser2_pid_p") == 0) { profile->laser_channels[1].pid_p = profile_repository_parse_float(value); } else if (strcmp(key, "laser2_pid_i") == 0) { profile->laser_channels[1].pid_i = profile_repository_parse_float(value); } else if (strcmp(key, "waveform_mode") == 0) { if (strcasecmp(value, "saw") == 0) { profile->waveform.mode = WAVEFORM_MODE_SAW; } else if (strcasecmp(value, "generated_sram") == 0) { profile->waveform.mode = WAVEFORM_MODE_SRAM_GENERATED; } else if (strcasecmp(value, "custom_sram") == 0) { profile->waveform.mode = WAVEFORM_MODE_SRAM_CUSTOM; } } else if (strcmp(key, "waveform_enable") == 0) { profile->waveform.enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "waveform_triangle") == 0) { profile->waveform.triangle = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "waveform_saw_step") == 0) { profile->waveform.saw_step = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "waveform_pat_base") == 0) { profile->waveform.pat_period_base = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "waveform_pat_period") == 0) { profile->waveform.pat_period = profile_repository_parse_u16(value); } else if (strcmp(key, "waveform_sample_count") == 0) { profile->waveform.sample_count = profile_repository_parse_u16(value); } else if (strcmp(key, "waveform_hold_cycles") == 0) { profile->waveform.hold_cycles = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "waveform_amplitude") == 0) { profile->waveform.amplitude = profile_repository_parse_u16(value); } else if (strcmp(key, "waveform_source") == 0) { profile_repository_copy_string(profile->waveform.source_path, sizeof(profile->waveform.source_path), value); } else if (strcmp(key, "ad9833_enable") == 0) { profile->ad9833.enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "ad9833_triangle") == 0) { profile->ad9833.triangle = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "ad9833_frequency_word") == 0) { profile->ad9833.frequency_word = profile_repository_parse_u32(value); } else if (strcmp(key, "stm32_dac_enable") == 0) { profile->stm32_dac.enabled = (uint8_t)profile_repository_parse_u16(value); } else if (strcmp(key, "stm32_dac_code") == 0) { profile->stm32_dac.code = profile_repository_parse_u16(value); } else if (strcmp(key, "ds1809_apply") == 0) { if (profile_repository_parse_bool(value, &bool_value)) { profile->ds1809.apply_position = bool_value; } } else if (strcmp(key, "ds1809_position_from_min") == 0) { profile->ds1809.position_from_min = (uint8_t)profile_repository_parse_u16(value); } } static bool profile_repository_load_ini_file(const char *profile_path, profile_t *profile) { char buffer[PROFILE_REPOSITORY_BUFFER_SIZE]; UINT bytes_read = 0u; char *cursor; FRESULT result; result = storage_sd_read_bytes(profile_path, 0u, buffer, sizeof(buffer) - 1u, &bytes_read); if (result != FR_OK) { return false; } buffer[bytes_read] = '\0'; cursor = buffer; while (*cursor != '\0') { char *line_start = cursor; char *line_end = strpbrk(cursor, "\r\n"); char *separator; char *key; char *value; if (line_end != NULL) { *line_end = '\0'; cursor = line_end + 1; while ((*cursor == '\r') || (*cursor == '\n')) { ++cursor; } } else { cursor += strlen(cursor); } line_start = profile_repository_trim(line_start); if ((*line_start == '\0') || (*line_start == '#') || (*line_start == ';')) { continue; } separator = strchr(line_start, '='); if (separator == NULL) { continue; } *separator = '\0'; key = profile_repository_trim(line_start); value = profile_repository_trim(separator + 1); profile_repository_apply_key_value(profile, key, value); } return true; } static bool profile_repository_load_by_valid_line_index(uint16_t target_index, profile_t *out_profile, uint16_t *out_total_valid_lines) { char buffer[PROFILE_REPOSITORY_BUFFER_SIZE]; UINT bytes_read = 0u; char *cursor; uint16_t valid_line_index = 0u; FRESULT result; if ((out_profile == NULL) || !storage_sd_file_exists(APP_STORAGE_PROFILE_INDEX_FILE)) { return false; } result = storage_sd_read_bytes(APP_STORAGE_PROFILE_INDEX_FILE, 0u, buffer, sizeof(buffer) - 1u, &bytes_read); if (result != FR_OK) { return false; } buffer[bytes_read] = '\0'; cursor = buffer; while (*cursor != '\0') { char *line_start = cursor; char *line_end = strpbrk(cursor, "\r\n"); char *display_name; char *profile_path; char *waveform_path; char *separator_1; char *separator_2; if (line_end != NULL) { *line_end = '\0'; cursor = line_end + 1; while ((*cursor == '\r') || (*cursor == '\n')) { ++cursor; } } else { cursor += strlen(cursor); } line_start = profile_repository_trim(line_start); if ((*line_start == '\0') || (*line_start == '#') || (*line_start == ';')) { continue; } separator_1 = strchr(line_start, ','); if (separator_1 == NULL) { continue; } *separator_1 = '\0'; separator_2 = strchr(separator_1 + 1, ','); if (separator_2 != NULL) { *separator_2 = '\0'; } display_name = profile_repository_trim(line_start); profile_path = profile_repository_trim(separator_1 + 1); waveform_path = (separator_2 != NULL) ? profile_repository_trim(separator_2 + 1) : ""; if (*profile_path == '\0') { continue; } if (valid_line_index == target_index) { profile_repository_reset_profile(out_profile); profile_repository_copy_string(out_profile->display_name, sizeof(out_profile->display_name), display_name); profile_repository_copy_string(out_profile->profile_path, sizeof(out_profile->profile_path), profile_path); profile_repository_copy_string(out_profile->waveform_path, sizeof(out_profile->waveform_path), waveform_path); profile_repository_copy_string(out_profile->waveform.source_path, sizeof(out_profile->waveform.source_path), waveform_path); if (!profile_repository_load_ini_file(profile_path, out_profile)) { return false; } if (out_total_valid_lines != NULL) { *out_total_valid_lines = (uint16_t)(valid_line_index + 1u); } return true; } ++valid_line_index; } if (out_total_valid_lines != NULL) { *out_total_valid_lines = valid_line_index; } return false; } bool profile_repository_load_first(profile_t *out_profile, uint16_t *out_index) { uint16_t total_lines = 0u; if (!profile_repository_load_by_valid_line_index(0u, out_profile, &total_lines)) { return false; } if (out_index != NULL) { *out_index = 0u; } return true; } bool profile_repository_load_next(uint16_t *in_out_index, profile_t *out_profile) { uint16_t requested_index; uint16_t total_lines = 0u; if ((in_out_index == NULL) || (out_profile == NULL)) { return false; } requested_index = (uint16_t)(*in_out_index + 1u); if (profile_repository_load_by_valid_line_index(requested_index, out_profile, &total_lines)) { *in_out_index = requested_index; return true; } if ((total_lines == 0u) || !profile_repository_load_by_valid_line_index(0u, out_profile, NULL)) { return false; } *in_out_index = 0u; return true; }