526 lines
15 KiB
C
526 lines
15 KiB
C
/**
|
|
* @file profile_repository.c
|
|
* @brief Future SD-card profile repository with minimal standalone parsing.
|
|
*/
|
|
|
|
#include "profile_repository.h"
|
|
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h>
|
|
|
|
#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;
|
|
}
|