Initial public release of the 2024A software.
This commit is contained in:
parent
7b9ad3edfd
commit
303e9e1dad
361 changed files with 60083 additions and 2 deletions
17
components/NVM/CMakeLists.txt
Normal file
17
components/NVM/CMakeLists.txt
Normal file
|
@ -0,0 +1,17 @@
|
|||
idf_component_register(
|
||||
SRCS
|
||||
"Key_Value.c"
|
||||
"Settings.c"
|
||||
"SPIFFS.c"
|
||||
"USB.c"
|
||||
|
||||
INCLUDE_DIRS
|
||||
"."
|
||||
REQUIRES
|
||||
"SystemK"
|
||||
"spiffs"
|
||||
"driver"
|
||||
"usb"
|
||||
"usb_host_msc"
|
||||
"esp_timer"
|
||||
)
|
228
components/NVM/Key_Value.c
Normal file
228
components/NVM/Key_Value.c
Normal file
|
@ -0,0 +1,228 @@
|
|||
/*
|
||||
* This program source code file is part of the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
||||
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <SystemK.h>
|
||||
|
||||
#define MAX_LINE_LENGTH 140
|
||||
#define MAX_KEY_LENGTH 64
|
||||
#define MAX_VALUE_LENGTH 64
|
||||
|
||||
static const char *TAG = "NVM KV";
|
||||
static const char *TEMP_FILE = "/usb/esp/temp.txt";
|
||||
|
||||
// Trims the whitespace from both ends of a string.
|
||||
static void KV_Trim(char *str)
|
||||
{
|
||||
char *start = str;
|
||||
char *end = str + strlen(str) - 1;
|
||||
while (*start && (*start == ' ' || *start == '\t'))
|
||||
start++;
|
||||
while (end > start && (*end == ' ' || *end == '\t' || *end == '\n'))
|
||||
end--;
|
||||
*(end + 1) = '\0';
|
||||
memmove(str, start, end - start + 2);
|
||||
}
|
||||
|
||||
// Splits a line around the '=' into a key and value pair.
|
||||
static int KV_Parse_Line(char *line, char *key, char *value)
|
||||
{
|
||||
char *delimiter = strchr(line, '=');
|
||||
if (delimiter == NULL)
|
||||
return 0;
|
||||
*delimiter = '\0';
|
||||
strncpy(key, line, MAX_KEY_LENGTH - 1);
|
||||
strncpy(value, delimiter + 1, MAX_VALUE_LENGTH - 1);
|
||||
key[MAX_KEY_LENGTH - 1] = '\0';
|
||||
value[MAX_VALUE_LENGTH - 1] = '\0';
|
||||
KV_Trim(key);
|
||||
KV_Trim(value);
|
||||
return 1;
|
||||
}
|
||||
|
||||
SystemKResult_T KV_Get_Value_string(const char *filename, const char *search_key, char *value)
|
||||
{
|
||||
FILE *file = fopen(filename, "r");
|
||||
if (file == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename);
|
||||
return SYSTEMK_RESULT_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
char line[MAX_LINE_LENGTH];
|
||||
char key[MAX_KEY_LENGTH];
|
||||
|
||||
while (fgets(line, sizeof(line), file))
|
||||
{
|
||||
if (KV_Parse_Line(line, key, value) && strcmp(key, search_key) == 0)
|
||||
{
|
||||
fclose(file);
|
||||
return SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
KLOG_ERROR(TAG, "Couldn't find key %s in file %s!", search_key, filename);
|
||||
return SYSTEMK_RESULT_KEY_NOT_FOUND;
|
||||
}
|
||||
|
||||
SystemKResult_T KV_Set_Value_string(const char *filename, const char *set_key, const char *set_value)
|
||||
{
|
||||
FILE *file = fopen(filename, "r");
|
||||
if (file == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Couldn't open file %s for reading!", filename);
|
||||
return SYSTEMK_RESULT_FILE_NOT_FOUND;
|
||||
}
|
||||
|
||||
FILE *temp = fopen(TEMP_FILE, "w");
|
||||
if (temp == NULL)
|
||||
{
|
||||
fclose(file);
|
||||
KLOG_ERROR(TAG, "Couldn't open file %s for writing!", TEMP_FILE);
|
||||
return SYSTEMK_RESULT_WRITE_FAILED;
|
||||
}
|
||||
|
||||
char line[MAX_LINE_LENGTH];
|
||||
char line_copy[MAX_LINE_LENGTH];
|
||||
char key[MAX_KEY_LENGTH];
|
||||
char value[MAX_VALUE_LENGTH];
|
||||
int found = 0;
|
||||
|
||||
while (fgets(line, sizeof(line), file))
|
||||
{
|
||||
strncpy(line_copy, line, MAX_LINE_LENGTH);
|
||||
line_copy[MAX_LINE_LENGTH - 1] = '\0'; // Ensure null-termination
|
||||
|
||||
if (KV_Parse_Line(line, key, value) && strcmp(key, set_key) == 0)
|
||||
{
|
||||
fprintf(temp, "%s = %s\n", set_key, set_value);
|
||||
found = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
fputs(line_copy, temp);
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
fprintf(temp, "%s = %s\n", set_key, set_value);
|
||||
}
|
||||
|
||||
fclose(file);
|
||||
fclose(temp);
|
||||
|
||||
remove(filename);
|
||||
rename(TEMP_FILE, filename);
|
||||
|
||||
return SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
SystemKResult_T KV_Get_Value_uint32(const char *filename, const char *search_key, uint32_t *value)
|
||||
{
|
||||
char value_str[MAX_VALUE_LENGTH];
|
||||
|
||||
SystemKResult_T result = KV_Get_Value_string(filename, search_key, value_str);
|
||||
if (result != SYSTEMK_RESULT_SUCCESS)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
char *endptr;
|
||||
// Reset errno to check for conversion errors.
|
||||
errno = 0;
|
||||
*value = strtoul(value_str, &endptr, 10);
|
||||
|
||||
// Check for conversion errors
|
||||
if ((errno == ERANGE && *value == ULONG_MAX) || (errno != 0))
|
||||
{
|
||||
KLOG_ERROR(TAG, "Error converting %s for key %s to uint32_t!", value_str, search_key);
|
||||
return SYSTEMK_RESULT_WRONG_DATATYPE;
|
||||
}
|
||||
|
||||
if (endptr == value_str)
|
||||
{
|
||||
KLOG_ERROR(TAG, "No digits were found in %s (for key %s)!", value_str, search_key);
|
||||
return SYSTEMK_RESULT_WRONG_DATATYPE;
|
||||
}
|
||||
|
||||
if (*value > UINT32_MAX)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Value %s (for key %s) exceeds uint32_t range!", value_str, search_key);
|
||||
return SYSTEMK_RESULT_OVERFLOW;
|
||||
}
|
||||
|
||||
return SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
SystemKResult_T KV_Set_Value_uint32(const char *filename, const char *set_key, uint32_t *set_value)
|
||||
{
|
||||
char value_str[MAX_VALUE_LENGTH];
|
||||
int written = snprintf(value_str, MAX_VALUE_LENGTH, "%lu", *set_value);
|
||||
|
||||
if (written < 0 || written >= MAX_VALUE_LENGTH)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Error converting uint32_t to string for key %s!", set_key);
|
||||
return SYSTEMK_RESULT_WRONG_DATATYPE;
|
||||
}
|
||||
|
||||
return KV_Set_Value_string(filename, set_key, value_str);
|
||||
}
|
||||
|
||||
SystemKResult_T KV_Get_Value_uint8(const char *filename, const char *search_key, uint8_t *value)
|
||||
{
|
||||
uint32_t value32 = 0;
|
||||
|
||||
SystemKResult_T result = KV_Get_Value_uint32(filename, search_key, &value32);
|
||||
if (result != SYSTEMK_RESULT_SUCCESS)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
// Check for conversion errors
|
||||
if (value32 > UINT8_MAX)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Value %lu (for key %s) exceeds uint32_t range!", value32, search_key);
|
||||
return SYSTEMK_RESULT_OVERFLOW;
|
||||
}
|
||||
|
||||
*value = (uint8_t)value32;
|
||||
|
||||
return SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
SystemKResult_T KV_Set_Value_uint8(const char *filename, const char *set_key, uint8_t *set_value)
|
||||
{
|
||||
char value_str[MAX_VALUE_LENGTH];
|
||||
int written = snprintf(value_str, MAX_VALUE_LENGTH, "%u", *set_value);
|
||||
|
||||
if (written < 0 || written >= MAX_VALUE_LENGTH)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Error converting uint8_t to string for key %s!", set_key);
|
||||
return SYSTEMK_RESULT_WRONG_DATATYPE;
|
||||
}
|
||||
|
||||
return KV_Set_Value_string(filename, set_key, value_str);
|
||||
}
|
29
components/NVM/Key_Value.h
Normal file
29
components/NVM/Key_Value.h
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* This program source code file is part of the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
||||
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
SystemKResult_T KV_Get_Value_string(const char *filename, const char *search_key, char *value);
|
||||
SystemKResult_T KV_Set_Value_string(const char *filename, const char *set_key, const char *set_value);
|
||||
|
||||
SystemKResult_T KV_Get_Value_uint32(const char *filename, const char *search_key, uint32_t *value);
|
||||
SystemKResult_T KV_Set_Value_uint32(const char *filename, const char *set_key, uint32_t *set_value);
|
||||
|
||||
SystemKResult_T KV_Get_Value_uint8(const char *filename, const char *search_key, uint8_t *value);
|
||||
SystemKResult_T KV_Set_Value_uint8(const char *filename, const char *set_key, uint8_t *set_value);
|
26
components/NVM/NVM.h
Normal file
26
components/NVM/NVM.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* This program source code file is part of the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
||||
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "Key_Value.h"
|
||||
#include "SPIFFS.h"
|
||||
#include "USB.h"
|
84
components/NVM/SPIFFS.c
Normal file
84
components/NVM/SPIFFS.c
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* This program source code file is part of the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
||||
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <SystemK.h>
|
||||
#include <string.h>
|
||||
#include "esp_spiffs.h"
|
||||
|
||||
static const char *TAG = "SPIFFS";
|
||||
const char *DEFAULT_CONFIG_FILE = "/spiffs/default_config.txt";
|
||||
|
||||
void Initialize_SPIFFS(SemaphoreHandle_t init_complete)
|
||||
{
|
||||
KLOG_INFO(TAG, "Initializing SPI flash file system (SPIFFS)...");
|
||||
|
||||
esp_vfs_spiffs_conf_t conf = {
|
||||
.base_path = "/spiffs", // File path prefix associated with the filesystem.
|
||||
.partition_label = NULL, // If set to NULL, first partition with subtype=spiffs will be used.
|
||||
.max_files = 5, // Maximum files that could be open at the same time.
|
||||
.format_if_mount_failed = false}; // If true, it will format the file system if it fails to mount.
|
||||
|
||||
esp_err_t ret = esp_vfs_spiffs_register(&conf);
|
||||
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
if (ret == ESP_FAIL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to mount or format filesystem!");
|
||||
}
|
||||
else if (ret == ESP_ERR_NOT_FOUND)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to find SPIFFS partition!");
|
||||
}
|
||||
else
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to initialize SPIFFS (%s)!", esp_err_to_name(ret));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
size_t total = 0;
|
||||
size_t used = 0;
|
||||
ret = esp_spiffs_info(NULL, &total, &used);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to get SPIFFS partition information (%s)!", esp_err_to_name(ret));
|
||||
}
|
||||
else
|
||||
{
|
||||
KLOG_INFO(TAG, "SPIFFS partition: %d bytes used out of %d total.", used, total);
|
||||
}
|
||||
|
||||
FILE *f = fopen("/spiffs/boot_message.txt", "r");
|
||||
if (f == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to open boot_message.txt!");
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[64];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
fread(buf, 1, sizeof(buf), f);
|
||||
fclose(f);
|
||||
|
||||
KLOG_INFO(TAG, ">>> %s <<<", buf);
|
||||
xSemaphoreGive(init_complete);
|
||||
}
|
26
components/NVM/SPIFFS.h
Normal file
26
components/NVM/SPIFFS.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* This program source code file is part of the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
||||
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void Initialize_SPIFFS(SemaphoreHandle_t init_complete);
|
||||
|
||||
extern const char *DEFAULT_CONFIG_FILE;
|
209
components/NVM/Settings.c
Normal file
209
components/NVM/Settings.c
Normal file
|
@ -0,0 +1,209 @@
|
|||
/*
|
||||
* This program source code file is part of the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
||||
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <SystemK.h>
|
||||
#include <NVM.h>
|
||||
|
||||
static const uint8_t UNINTIALIZED_UINT8 = UINT8_MAX;
|
||||
|
||||
static uint8_t Cached_Team_ID = UNINTIALIZED_UINT8;
|
||||
static uint8_t Cached_Player_ID = UNINTIALIZED_UINT8;
|
||||
static uint8_t Cached_Weapon_ID = UNINTIALIZED_UINT8;
|
||||
|
||||
SystemKResult_T SETTINGS_get_uint8_t(SystemKSettingID_T id, uint8_t *value)
|
||||
{
|
||||
SystemKResult_T result = SYSTEMK_RESULT_UNSPECIFIED_FAILURE;
|
||||
char *key = "";
|
||||
uint8_t *cached_value = NULL;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case SYSTEMK_SETTING_IS_RIGHT_HANDED:
|
||||
key = "Is_Right_Handed";
|
||||
break;
|
||||
|
||||
case SYSTEMK_SETTING_AUDIO_VOLUME:
|
||||
key = "Audio_Volume";
|
||||
break;
|
||||
|
||||
case SYSTEMK_SETTING_TEAMID:
|
||||
if (Cached_Team_ID != UNINTIALIZED_UINT8)
|
||||
{
|
||||
*value = Cached_Team_ID;
|
||||
result = SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = "Team_ID";
|
||||
cached_value = &Cached_Team_ID;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case SYSTEMK_SETTING_PLAYERID:
|
||||
if (Cached_Player_ID != UNINTIALIZED_UINT8)
|
||||
{
|
||||
*value = Cached_Player_ID;
|
||||
result = SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = "Player_ID";
|
||||
cached_value = &Cached_Player_ID;
|
||||
}
|
||||
break;
|
||||
|
||||
case SYSTEMK_SETTING_WEAPONID:
|
||||
if (Cached_Weapon_ID != UNINTIALIZED_UINT8)
|
||||
{
|
||||
*value = Cached_Weapon_ID;
|
||||
result = SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
||||
else
|
||||
{
|
||||
key = "Weapon_ID";
|
||||
cached_value = &Cached_Weapon_ID;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
result = SYSTEMK_RESULT_WRONG_DATATYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (result != SYSTEMK_RESULT_SUCCESS)
|
||||
{
|
||||
result = KV_Get_Value_uint8(CONFIG_FILE, key, value);
|
||||
|
||||
if (result != SYSTEMK_RESULT_SUCCESS)
|
||||
{
|
||||
result = KV_Get_Value_uint8(DEFAULT_CONFIG_FILE, key, value);
|
||||
|
||||
if (result == SYSTEMK_RESULT_SUCCESS)
|
||||
{
|
||||
(void)KV_Set_Value_uint8(CONFIG_FILE, key, value);
|
||||
}
|
||||
}
|
||||
|
||||
// Save the cached value, if necessary.
|
||||
if ((cached_value != NULL) && (result == SYSTEMK_RESULT_SUCCESS))
|
||||
{
|
||||
*cached_value = *value;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SystemKResult_T SETTINGS_set_uint8_t(SystemKSettingID_T id, uint8_t value)
|
||||
{
|
||||
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case SYSTEMK_SETTING_IS_RIGHT_HANDED:
|
||||
result = KV_Set_Value_uint8(CONFIG_FILE, "Is_Right_Handed", &value);
|
||||
break;
|
||||
|
||||
case SYSTEMK_SETTING_AUDIO_VOLUME:
|
||||
result = KV_Set_Value_uint8(CONFIG_FILE, "Audio_Volume", &value);
|
||||
break;
|
||||
|
||||
case SYSTEMK_SETTING_TEAMID:
|
||||
Cached_Team_ID = value;
|
||||
result = KV_Set_Value_uint8(CONFIG_FILE, "Team_ID", &value);
|
||||
break;
|
||||
|
||||
case SYSTEMK_SETTING_PLAYERID:
|
||||
Cached_Player_ID = value;
|
||||
result = KV_Set_Value_uint8(CONFIG_FILE, "Player_ID", &value);
|
||||
break;
|
||||
|
||||
case SYSTEMK_SETTING_WEAPONID:
|
||||
Cached_Weapon_ID = value;
|
||||
result = KV_Set_Value_uint8(CONFIG_FILE, "Weapon_ID", &value);
|
||||
break;
|
||||
|
||||
default:
|
||||
result = SYSTEMK_RESULT_WRONG_DATATYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SystemKResult_T SETTINGS_get_uint32_t(SystemKSettingID_T id, uint32_t *value)
|
||||
{
|
||||
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
|
||||
char *key = "";
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case SYSTEMK_SETTING_T_START_GAME_in_ms:
|
||||
key = "T_Start_Game_in_ms";
|
||||
break;
|
||||
|
||||
default:
|
||||
result = SYSTEMK_RESULT_WRONG_DATATYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
if (result == SYSTEMK_RESULT_SUCCESS)
|
||||
{
|
||||
result = KV_Get_Value_uint32(CONFIG_FILE, key, value);
|
||||
|
||||
if (result != SYSTEMK_RESULT_SUCCESS)
|
||||
{
|
||||
result = KV_Get_Value_uint32(DEFAULT_CONFIG_FILE, key, value);
|
||||
|
||||
if (result == SYSTEMK_RESULT_SUCCESS)
|
||||
{
|
||||
(void)KV_Set_Value_uint32(CONFIG_FILE, key, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
SystemKResult_T SETTINGS_set_uint32_t(SystemKSettingID_T id, uint32_t value)
|
||||
{
|
||||
SystemKResult_T result = SYSTEMK_RESULT_SUCCESS;
|
||||
|
||||
switch (id)
|
||||
{
|
||||
case SYSTEMK_SETTING_T_START_GAME_in_ms:
|
||||
result = KV_Set_Value_uint32(CONFIG_FILE, "T_Start_Game_in_ms", &value);
|
||||
break;
|
||||
|
||||
default:
|
||||
result = SYSTEMK_RESULT_WRONG_DATATYPE;
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Settings are saved on change in this implementation.
|
||||
SystemKResult_T SETTINGS_Save(void)
|
||||
{
|
||||
return SYSTEMK_RESULT_SUCCESS;
|
||||
}
|
449
components/NVM/USB.c
Normal file
449
components/NVM/USB.c
Normal file
|
@ -0,0 +1,449 @@
|
|||
/*
|
||||
* This program source code file is part of the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
||||
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// From https://github.com/espressif/esp-idf/blob/master/examples/peripherals/usb/host/msc/main/msc_example_main.c
|
||||
|
||||
#include <SystemK.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <assert.h>
|
||||
#include <sys/stat.h>
|
||||
#include <dirent.h>
|
||||
#include <inttypes.h>
|
||||
#include <errno.h>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/queue.h"
|
||||
#include "usb/usb_host.h"
|
||||
#include "usb/msc_host.h"
|
||||
#include "usb/msc_host_vfs.h"
|
||||
#include "esp_timer.h"
|
||||
#include "esp_err.h"
|
||||
#include "Key_Value.h"
|
||||
#include "USB.h"
|
||||
|
||||
#define STACK_SIZE 4096
|
||||
static StaticTask_t xTaskBuffer;
|
||||
static StackType_t xStack[STACK_SIZE];
|
||||
|
||||
#define USB_HOST_TASK_PRIORITY 2
|
||||
#define MSC_HOST_TASK_PRIORITY 3
|
||||
|
||||
#define MNT_PATH "/usb" // Path in the Virtual File System, where the USB flash drive is going to be mounted
|
||||
#define BUFFER_SIZE 4096 // The read/write performance can be improved with larger buffer for the cost of RAM; 4kB is enough for most use cases.
|
||||
|
||||
static const char *TAG = "USB";
|
||||
|
||||
static const char *OTA_FILE = "/usb/esp/OTA_URL.txt";
|
||||
const char *CONFIG_FILE = "/usb/esp/config.txt";
|
||||
|
||||
typedef enum
|
||||
{
|
||||
USB_STATE_UNINITIALIZED,
|
||||
USB_STATE_IDLE,
|
||||
USB_STATE_DEVICE_CONNECTED,
|
||||
USB_STATE_VFS_REGISTERED,
|
||||
USB_STATE_PROCESSING_DISCONNECTION
|
||||
} USB_State_T;
|
||||
|
||||
static QueueHandle_t usb_queue;
|
||||
typedef struct
|
||||
{
|
||||
enum
|
||||
{
|
||||
USB_HOST_INITIALIZED,
|
||||
USB_DEVICE_CONNECTED, // USB device connect event
|
||||
USB_DEVICE_DISCONNECTED, // USB device disconnect event
|
||||
} id;
|
||||
union
|
||||
{
|
||||
uint8_t new_dev_address; // Address of new USB device for APP_DEVICE_CONNECTED event
|
||||
} data;
|
||||
} usb_message_t;
|
||||
|
||||
/**
|
||||
* @brief MSC driver callback
|
||||
*
|
||||
* Signal device connection/disconnection to the main task
|
||||
*
|
||||
* @param[in] event MSC event
|
||||
* @param[in] arg MSC event data
|
||||
*/
|
||||
static void MSC_Event_Callback(const msc_host_event_t *event, void *arg)
|
||||
{
|
||||
if (event->event == MSC_DEVICE_CONNECTED)
|
||||
{
|
||||
usb_message_t message = {
|
||||
.id = USB_DEVICE_CONNECTED,
|
||||
.data.new_dev_address = event->device.address,
|
||||
};
|
||||
xQueueSend(usb_queue, &message, portMAX_DELAY);
|
||||
}
|
||||
else if (event->event == MSC_DEVICE_DISCONNECTED)
|
||||
{
|
||||
usb_message_t message = {
|
||||
.id = USB_DEVICE_DISCONNECTED,
|
||||
};
|
||||
xQueueSend(usb_queue, &message, portMAX_DELAY);
|
||||
}
|
||||
else
|
||||
{
|
||||
KLOG_INFO(TAG, "Event: %d", event->event);
|
||||
}
|
||||
}
|
||||
|
||||
__attribute__((unused)) static void print_device_info(msc_host_device_info_t *info)
|
||||
{
|
||||
const size_t megabyte = 1024 * 1024;
|
||||
uint64_t capacity = ((uint64_t)info->sector_size * info->sector_count) / megabyte;
|
||||
|
||||
printf("Device info:\n");
|
||||
printf("\t Capacity: %llu MB\n", capacity);
|
||||
printf("\t Sector size: %" PRIu32 "\n", info->sector_size);
|
||||
printf("\t Sector count: %" PRIu32 "\n", info->sector_count);
|
||||
printf("\t PID: 0x%04X \n", info->idProduct);
|
||||
printf("\t VID: 0x%04X \n", info->idVendor);
|
||||
wprintf(L"\t iProduct: %S \n", info->iProduct);
|
||||
wprintf(L"\t iManufacturer: %S \n", info->iManufacturer);
|
||||
wprintf(L"\t iSerialNumber: %S \n", info->iSerialNumber);
|
||||
}
|
||||
|
||||
__attribute__((unused)) static void file_operations(void)
|
||||
{
|
||||
const char *directory = "/usb/esp";
|
||||
const char *file_path = "/usb/esp/test.txt";
|
||||
|
||||
// Create /usb/esp directory
|
||||
struct stat s = {0};
|
||||
bool directory_exists = stat(directory, &s) == 0;
|
||||
if (!directory_exists)
|
||||
{
|
||||
if (mkdir(directory, 0775) != 0)
|
||||
{
|
||||
KLOG_ERROR(TAG, "mkdir failed with errno: %s", strerror(errno));
|
||||
}
|
||||
}
|
||||
|
||||
// Create /usb/esp/test.txt file, if it doesn't exist
|
||||
if (stat(file_path, &s) != 0)
|
||||
{
|
||||
KLOG_INFO(TAG, "Creating file");
|
||||
FILE *f = fopen(file_path, "w");
|
||||
if (f == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to open file for writing");
|
||||
return;
|
||||
}
|
||||
fprintf(f, "Hello World!\n");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// Read back the file
|
||||
FILE *f;
|
||||
KLOG_INFO(TAG, "Reading file");
|
||||
f = fopen(file_path, "r");
|
||||
if (f == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to open file for reading");
|
||||
return;
|
||||
}
|
||||
char line[64];
|
||||
fgets(line, sizeof(line), f);
|
||||
fclose(f);
|
||||
// strip newline
|
||||
char *pos = strchr(line, '\n');
|
||||
if (pos)
|
||||
{
|
||||
*pos = '\0';
|
||||
}
|
||||
KLOG_INFO(TAG, "Read from file '%s': '%s'", file_path, line);
|
||||
}
|
||||
|
||||
__attribute__((unused)) void speed_test(void)
|
||||
{
|
||||
#define TEST_FILE "/usb/esp/dummy"
|
||||
#define ITERATIONS 256 // 256 * 4kb = 1MB
|
||||
int64_t test_start, test_end;
|
||||
|
||||
FILE *f = fopen(TEST_FILE, "wb+");
|
||||
if (f == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Failed to open file for writing");
|
||||
return;
|
||||
}
|
||||
// Set larger buffer for this file. It results in larger and more effective USB transfers
|
||||
setvbuf(f, NULL, _IOFBF, BUFFER_SIZE);
|
||||
|
||||
// Allocate application buffer used for read/write
|
||||
uint8_t *data = malloc(BUFFER_SIZE);
|
||||
assert(data);
|
||||
|
||||
KLOG_INFO(TAG, "Writing to file %s", TEST_FILE);
|
||||
test_start = esp_timer_get_time();
|
||||
for (int i = 0; i < ITERATIONS; i++)
|
||||
{
|
||||
if (fwrite(data, BUFFER_SIZE, 1, f) == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
test_end = esp_timer_get_time();
|
||||
KLOG_INFO(TAG, "Write speed %1.2f MiB/s", (BUFFER_SIZE * ITERATIONS) / (float)(test_end - test_start));
|
||||
rewind(f);
|
||||
|
||||
KLOG_INFO(TAG, "Reading from file %s", TEST_FILE);
|
||||
test_start = esp_timer_get_time();
|
||||
for (int i = 0; i < ITERATIONS; i++)
|
||||
{
|
||||
if (0 == fread(data, BUFFER_SIZE, 1, f))
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
test_end = esp_timer_get_time();
|
||||
KLOG_INFO(TAG, "Read speed %1.2f MiB/s", (BUFFER_SIZE * ITERATIONS) / (float)(test_end - test_start));
|
||||
|
||||
fclose(f);
|
||||
free(data);
|
||||
}
|
||||
|
||||
static void usb_host_task(void *args)
|
||||
{
|
||||
usb_host_config_t host_config = {
|
||||
.skip_phy_setup = false,
|
||||
.intr_flags = ESP_INTR_FLAG_LEVEL1,
|
||||
};
|
||||
ESP_ERROR_CHECK(usb_host_install(&host_config));
|
||||
|
||||
const msc_host_driver_config_t msc_config = {
|
||||
.create_backround_task = true,
|
||||
.task_priority = MSC_HOST_TASK_PRIORITY,
|
||||
.stack_size = 4096,
|
||||
.callback = MSC_Event_Callback,
|
||||
};
|
||||
ESP_ERROR_CHECK(msc_host_install(&msc_config));
|
||||
|
||||
vTaskDelay(10); // Short delay to let MSC client task spin up.
|
||||
|
||||
usb_message_t message = {
|
||||
.id = USB_HOST_INITIALIZED};
|
||||
xQueueSend(usb_queue, &message, portMAX_DELAY);
|
||||
|
||||
while (true)
|
||||
{
|
||||
uint32_t event_flags;
|
||||
|
||||
// This function handles all of the USB Host Library's processing and should be called repeatedly in a loop.
|
||||
ESP_ERROR_CHECK(usb_host_lib_handle_events(portMAX_DELAY, &event_flags));
|
||||
|
||||
// Release devices once all clients have deregistered.
|
||||
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS)
|
||||
{
|
||||
if (usb_host_device_free_all() == ESP_OK)
|
||||
{
|
||||
KLOG_INFO(TAG, "USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS");
|
||||
// break;
|
||||
};
|
||||
}
|
||||
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE)
|
||||
{
|
||||
KLOG_INFO(TAG, "USB_HOST_LIB_EVENT_FLAGS_ALL_FREE");
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void app_usb_task(void *args)
|
||||
{
|
||||
SemaphoreHandle_t init_complete = args;
|
||||
|
||||
static USB_State_T Current_State = USB_STATE_UNINITIALIZED;
|
||||
static msc_host_device_handle_t msc_device = NULL;
|
||||
static msc_host_vfs_handle_t vfs_handle = NULL;
|
||||
static uint8_t device_address = 1;
|
||||
usb_message_t msg;
|
||||
|
||||
while (true)
|
||||
{
|
||||
switch (Current_State)
|
||||
{
|
||||
case USB_STATE_UNINITIALIZED:
|
||||
{
|
||||
if (xQueueReceive(usb_queue, &msg, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
if (msg.id == USB_HOST_INITIALIZED)
|
||||
{
|
||||
KLOG_INFO(TAG, "Host initialized.");
|
||||
Current_State = USB_STATE_IDLE;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_STATE_IDLE:
|
||||
{
|
||||
KLOG_TRACE(TAG, "USB_STATE_IDLE");
|
||||
|
||||
if (xQueueReceive(usb_queue, &msg, pdMS_TO_TICKS(1000)) == pdTRUE)
|
||||
{
|
||||
if (msg.id == USB_DEVICE_CONNECTED)
|
||||
{
|
||||
device_address = msg.data.new_dev_address;
|
||||
Current_State = USB_STATE_DEVICE_CONNECTED;
|
||||
KLOG_INFO(TAG, "Device connected.");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
KLOG_ERROR(TAG, "No flash drive detected--rebooting.");
|
||||
vTaskDelay(pdMS_TO_TICKS(100));
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_STATE_DEVICE_CONNECTED:
|
||||
{
|
||||
KLOG_TRACE(TAG, "USB_STATE_DEVICE_CONNECTED");
|
||||
|
||||
// USB device connected. Open it and attempt to map it to Virtual File System.
|
||||
if (msc_host_install_device(device_address, &msc_device) != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "Problem connecting to flash drive.");
|
||||
Current_State = USB_STATE_PROCESSING_DISCONNECTION;
|
||||
}
|
||||
else
|
||||
{
|
||||
const esp_vfs_fat_mount_config_t mount_config = {
|
||||
.format_if_mount_failed = false,
|
||||
.max_files = 3,
|
||||
.allocation_unit_size = 8192,
|
||||
};
|
||||
if (msc_host_vfs_register(msc_device, MNT_PATH, &mount_config, &vfs_handle) != ESP_OK)
|
||||
{
|
||||
ESP_LOGW(TAG, "Problem mounting filesystem.");
|
||||
Current_State = USB_STATE_PROCESSING_DISCONNECTION;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current_State = USB_STATE_VFS_REGISTERED;
|
||||
xSemaphoreGive(init_complete);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_STATE_VFS_REGISTERED:
|
||||
{
|
||||
KLOG_TRACE(TAG, "USB_STATE_VFS_REGISTERED");
|
||||
|
||||
if (xQueueReceive(usb_queue, &msg, pdMS_TO_TICKS(1000)) == pdTRUE)
|
||||
{
|
||||
if (msg.id == USB_DEVICE_DISCONNECTED)
|
||||
{
|
||||
Current_State = USB_STATE_PROCESSING_DISCONNECTION;
|
||||
}
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
break;
|
||||
|
||||
case USB_STATE_PROCESSING_DISCONNECTION:
|
||||
{
|
||||
KLOG_TRACE(TAG, "USB_STATE_PROCESSING_DISCONNECTION");
|
||||
|
||||
if (vfs_handle)
|
||||
{
|
||||
ESP_ERROR_CHECK(msc_host_vfs_unregister(vfs_handle));
|
||||
vfs_handle = NULL;
|
||||
}
|
||||
if (msc_device)
|
||||
{
|
||||
ESP_ERROR_CHECK(msc_host_uninstall_device(msc_device));
|
||||
msc_device = NULL;
|
||||
device_address = 0;
|
||||
}
|
||||
|
||||
Current_State = USB_STATE_IDLE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Initialize_USB(SemaphoreHandle_t init_complete)
|
||||
{
|
||||
KLOG_INFO(TAG, "Initializing USB file system...");
|
||||
|
||||
// Create FreeRTOS primitives
|
||||
usb_queue = xQueueCreate(5, sizeof(usb_message_t));
|
||||
assert(usb_queue);
|
||||
|
||||
BaseType_t usb_host_task_created = xTaskCreatePinnedToCore(usb_host_task, "USB Host", 4096, NULL, USB_HOST_TASK_PRIORITY, NULL, 1);
|
||||
assert(usb_host_task_created);
|
||||
|
||||
TaskHandle_t xHandle = xTaskCreateStaticPinnedToCore(
|
||||
app_usb_task, // Function that implements the task.
|
||||
"USB NVM", // Text name for the task.
|
||||
STACK_SIZE, // Stack size in bytes, not words.
|
||||
(void *)init_complete, // Parameter passed into the task.
|
||||
tskIDLE_PRIORITY + 2, // Priority at which the task is created.
|
||||
xStack, // Array to use as the task's stack.
|
||||
&xTaskBuffer, // Variable to hold the task's data structure.
|
||||
APP_CPU_NUM); // Specify the task's core affinity
|
||||
assert(xHandle);
|
||||
}
|
||||
|
||||
bool OTA_File_Exists(void)
|
||||
{
|
||||
struct stat s = {0};
|
||||
return (stat(OTA_FILE, &s) == 0);
|
||||
}
|
||||
|
||||
char *Get_OTA_Image_URL(void)
|
||||
{
|
||||
static char url[64] = "";
|
||||
FILE *f;
|
||||
f = fopen(OTA_FILE, "r");
|
||||
if (f == NULL)
|
||||
{
|
||||
KLOG_ERROR(TAG, "The OTA file is missing!");
|
||||
return url;
|
||||
}
|
||||
|
||||
fgets(url, sizeof(url), f);
|
||||
fclose(f);
|
||||
// Strip the newline
|
||||
char *pos = strchr(url, '\n');
|
||||
if (pos)
|
||||
{
|
||||
*pos = '\0';
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
void Erase_OTA_File(void)
|
||||
{
|
||||
if (remove(OTA_FILE) != 0)
|
||||
{
|
||||
KLOG_ERROR(TAG, "Error erasing the OTA file: %s", strerror(errno));
|
||||
}
|
||||
}
|
31
components/NVM/USB.h
Normal file
31
components/NVM/USB.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* This program source code file is part of the KTag project.
|
||||
*
|
||||
* 🛡️ <https://ktag.clubk.club> 🃞
|
||||
*
|
||||
* Copyright © 2024-2025 Joseph P. Kearney and the KTag developers.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, either version 3 of the License, or (at your option) any
|
||||
* later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* There should be a copy of the GNU Affero General Public License in the LICENSE
|
||||
* file in the root of this repository. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
void Initialize_USB(SemaphoreHandle_t init_complete);
|
||||
|
||||
extern const char *CONFIG_FILE;
|
||||
|
||||
// OTA Reprogramming Helpers
|
||||
bool OTA_File_Exists(void);
|
||||
char * Get_OTA_Image_URL(void);
|
||||
void Erase_OTA_File(void);
|
Loading…
Add table
Add a link
Reference in a new issue