Files
buzzer/firmware/src/protocol.c
2026-02-28 10:39:28 +01:00

695 lines
19 KiB
C

#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zephyr/logging/log_ctrl.h>
#include <string.h>
#include <errno.h>
#include <fs.h>
#include <app_version.h>
#include <zephyr/sys/crc.h>
#include <zephyr/dfu/mcuboot.h>
#include <utils.h>
#include <usb.h>
#include <protocol.h>
#include <audio.h>
#define PROTOCOL_VERSION 2
LOG_MODULE_REGISTER(protocol, LOG_LEVEL_DBG);
#define PROTOCOL_STACK_SIZE 2048
#define PROTOCOL_PRIORITY 6
#define BUFFER_SIZE 256
static uint8_t buffer[BUFFER_SIZE];
static volatile uint32_t rx_index = 0;
static protocol_state_t current_protocol_state = PS_WAITING_FOR_COMMAND;
static protocol_cmd_t current_command = CMD_INVALID;
void send_ok()
{
const char *response = "OK\n";
LOG_DBG("Sending response: OK");
usb_write_buffer((const uint8_t *)response, strlen(response));
}
static protocol_error_t protocol_map_error(int32_t rc)
{
if (rc == 0)
{
return P_ERR_NONE;
}
int32_t err = rc < 0 ? -rc : rc;
switch (err)
{
case ENOENT:
return P_ERR_FILE_NOT_FOUND;
case EEXIST:
return P_ERR_ALREADY_EXISTS;
case ENOTDIR:
return P_ERR_NOT_A_DIRECTORY;
case EISDIR:
return P_ERR_IS_A_DIRECTORY;
case EACCES:
case EPERM:
return P_ERR_ACCESS_DENIED;
case ENOSPC:
return P_ERR_NO_SPACE;
case EFBIG:
return P_ERR_FILE_TOO_LARGE;
case ETIMEDOUT:
return P_ERR_TIMEOUT;
case EMSGSIZE:
return P_ERR_COMMAND_TOO_LONG;
case EINVAL:
return P_ERR_INVALID_PARAMETERS;
case EILSEQ:
return P_ERR_INVALID_COMMAND;
case ECANCELED:
return P_ERR_TRANSFER_ABORTED;
case ENOSYS:
case ENOTSUP:
return P_ERR_NOT_SUPPORTED;
case EBUSY:
return P_ERR_BUSY;
case EBADMSG:
return P_ERR_CRC_MISMATCH;
case EIO:
return P_ERR_IO;
default:
return P_ERR_INTERNAL;
}
}
void send_error(protocol_error_t error_code)
{
char response[32];
snprintf(response, sizeof(response), "ERR %d\n", error_code);
LOG_DBG("Sending response: ERR %d", error_code);
usb_write_buffer((const uint8_t *)response, strlen(response));
}
int cmd_ls(const char *path)
{
struct fs_dir_t dirp;
struct fs_dirent entry;
const char *ls_path = (path == NULL || path[0] == '\0') ? "/" : path;
fs_dir_t_init(&dirp);
if (fs_pm_opendir(&dirp, ls_path) < 0)
{
LOG_ERR("Failed to open directory '%s'", ls_path);
return -ENOENT;
}
char tx_buffer[300];
while (fs_readdir(&dirp, &entry) == 0 && entry.name[0] != '\0')
{
snprintf(tx_buffer, sizeof(tx_buffer), "%s,%u,%s\n", entry.type == FS_DIR_ENTRY_FILE ? "F" : "D", entry.size, entry.name);
usb_write_buffer((const uint8_t *)tx_buffer, strlen(tx_buffer));
}
fs_pm_closedir(&dirp);
return 0;
}
int cmd_info()
{
char info[112];
struct fs_statvfs stat;
int rc = fs_pm_statvfs("/lfs", &stat);
if (rc)
{
LOG_ERR("Failed to get filesystem stats: %d", rc);
return rc;
}
snprintf(info, sizeof(info), "%u;%s;%lu;%lu;%lu;%s\n", PROTOCOL_VERSION, APP_VERSION_STRING, stat.f_frsize, stat.f_blocks, stat.f_bfree, boot_is_img_confirmed() ? "CONFIRMED" : "UNCONFIRMED");
usb_write_buffer((const uint8_t *)info, strlen(info));
return 0;
}
int cmd_put_binary_file(const char *filename, ssize_t filesize, uint32_t expected_crc32)
{
int rc;
ssize_t bytes_written = 0;
uint32_t running_crc32 = 0;
uint32_t retry_count = 0;
size_t accumulated = 0;
struct fs_file_t file;
bool firmware_update = false;
if (strcmp(filename, "update.bin") == 0 ||
strcmp(filename, "firmware.bin") == 0 ||
strcmp(filename, "update") == 0 ||
strcmp(filename, "firmware") == 0)
{
firmware_update = true;
LOG_INF("Firmware update requested with file '%s'", filename);
}
if (firmware_update)
{
slot_info_t slot1_info;
rc = flash_get_slot_info(&slot1_info);
if (rc < 0)
{
LOG_ERR("Failed to get slot 1 info: %d", rc);
return rc;
}
if (filesize > slot1_info.size)
{
LOG_ERR("File size %zd exceeds slot 1 size %zu", filesize, slot1_info.size);
return -EFBIG;
}
flash_init_firmware_upload();
}
else
{
fs_file_t_init(&file);
fs_pm_unlink(filename);
LOG_DBG("Opening file '%s' for writing (expected size: %zd bytes, expected CRC32: 0x%08x)", filename, filesize, expected_crc32);
rc = fs_pm_open(&file, filename, FS_O_CREATE | FS_O_WRITE);
if (rc < 0)
{
LOG_ERR("Failed to open file '%s' for writing: %d", filename, rc);
return rc;
}
}
usb_write_buffer((const uint8_t *)"READY\n", 6);
uint32_t start = k_uptime_get_32();
while (bytes_written < filesize)
{
/* Nur so viel lesen, wie in den restlichen Puffer passt (oder bis zum Dateiende) */
size_t remaining_file = filesize - bytes_written - accumulated;
size_t to_read = MIN(sizeof(buffer) - accumulated, remaining_file);
ssize_t read = usb_read_buffer(buffer + accumulated, to_read);
if (read < 0)
{
LOG_ERR("Error reading from USB: %d", read);
if (firmware_update)
{
}
else
{
fs_pm_close(&file);
}
return (int)read;
}
else if (read == 0)
{
if (retry_count >= 10)
{
LOG_ERR("No data received from USB after multiple attempts");
if (firmware_update)
{
}
else
{
fs_pm_close(&file);
}
return -ETIMEDOUT;
}
usb_resume_rx();
if ((bytes_written + accumulated) == 0)
{
usb_wait_for_data(K_SECONDS(1));
}
else
{
usb_wait_for_data(K_MSEC(100));
}
retry_count++;
continue;
}
/* Wir haben Daten bekommen: Zähler hochsetzen und USB weiterlauschen lassen */
accumulated += read;
retry_count = 0;
usb_resume_rx();
/* SCHREIBEN: Erst auf den Flash schreiben, wenn der Puffer voll ist (4096 Bytes)
ODER wenn wir das Ende der Datei erreicht haben. */
if (accumulated == sizeof(buffer) || (bytes_written + accumulated) == filesize)
{
if (firmware_update)
{
int rc = flash_write_firmware_block(buffer, accumulated, (bytes_written + accumulated) == filesize);
if (rc < 0)
{
LOG_ERR("Error writing to flash: %d", rc);
return rc;
}
}
else
{
ssize_t written = fs_write(&file, buffer, accumulated);
if (written < 0)
{
LOG_ERR("Error writing to file '%s': %d", filename, (int)written);
fs_pm_close(&file);
return (int)written;
}
}
/* CRC erst nach dem erfolgreichen Block-Schreiben berechnen */
running_crc32 = crc32_ieee_update(running_crc32, buffer, accumulated);
bytes_written += accumulated;
/* Puffer für die nächste Runde leeren */
accumulated = 0;
}
}
uint32_t duration = k_uptime_get_32() - start;
uint32_t kb_per_s = (filesize * 1000) / (duration * 1024 + 1);
LOG_DBG("Received file '%s' (%zd bytes) in %u ms (%u kb/s), CRC32: 0x%08x", filename, filesize, duration, kb_per_s, running_crc32);
if (firmware_update)
{
int rc;
rc = boot_request_upgrade(BOOT_UPGRADE_TEST);
if (rc < 0)
{
LOG_ERR("Failed to request firmware upgrade: %d", rc);
return rc;
}
send_ok();
LOG_INF("Firmware upgrade requested, rebooting into bootloader...");
reboot_with_status(REBOOT_STATUS_FIRMWARE_UPDATE);
}
else
{
fs_pm_close(&file);
LOG_DBG("Closed file '%s' after writing", filename);
}
if (running_crc32 != expected_crc32)
{
LOG_ERR("CRC32 mismatch for file '%s': expected 0x%08x, got 0x%08x", filename, expected_crc32, running_crc32);
return -EBADMSG;
}
LOG_DBG("File '%s' received successfully with matching CRC32", filename);
return 0;
}
int cmd_mkdir(const char *path)
{
int rc = fs_pm_mkdir(path);
if (rc < 0)
{
LOG_ERR("Failed to create directory '%s': %d", path, rc);
}
LOG_DBG("Directory '%s' created successfully", path);
return rc;
}
int cmd_rm(const char *path)
{
int rc = fs_pm_unlink(path);
if (rc < 0)
{
LOG_ERR("Failed to remove '%s': %d", path, rc);
}
LOG_DBG("'%s' removed successfully", path);
return rc;
}
int cmd_confirm_firmware()
{
if (!boot_is_img_confirmed())
{
int rc = boot_write_img_confirmed();
if (rc < 0)
{
LOG_ERR("Failed to confirm firmware: %d", rc);
return rc;
}
LOG_INF("Firmware confirmed successfully");
send_ok();
audio_play("/lfs/sys/confirm");
}
else
{
LOG_INF("Firmware is already confirmed, no action taken");
}
return 0;
}
int cmd_reboot_device()
{
LOG_INF("Rebooting device as requested by host...");
send_ok();
reboot_with_status(REBOOT_STATUS_NORMAL);
return 0; // Dieser Code wird nie erreicht, aber wir geben ihn der Vollständigkeit halber zurück
}
void cmd_play(const char *filename)
{
LOG_DBG("Play command received with filename: '%s'", filename);
audio_stop();
audio_play(filename);
}
int cmd_check(const char *param)
{
LOG_DBG("Check command received with parameter: '%s'", param);
struct fs_file_t file;
fs_file_t_init(&file);
int rc = fs_pm_open(&file, param, FS_O_READ);
if (rc < 0)
{
LOG_ERR("Check failed: file '%s' not found", param);
return -ENOENT;
}
uint32_t crc32 = 0;
uint32_t start_time = k_uptime_get_32();
uint8_t buffer[256];
ssize_t read;
while ((read = fs_read(&file, buffer, sizeof(buffer))) > 0)
{
crc32 = crc32_ieee_update(crc32, buffer, read);
}
fs_pm_close(&file);
if (read < 0)
{
LOG_ERR("Check failed: error reading file '%s': %d", param, (int)read);
return (int)read;
}
uint32_t duration = k_uptime_get_32() - start_time;
LOG_DBG("Check successful: file '%s' has CRC32 0x%08x, check took %u ms", param, crc32, duration);
char response[64];
snprintf(response, sizeof(response), "CRC32 %s 0x%08x\n", param, crc32);
usb_write_buffer((const uint8_t *)response, strlen(response));
return 0;
}
void execute_current_command(void)
{
int rc;
switch (current_command)
{
case CMD_LS:
LOG_DBG("Executing LS command with parameters: '%s'", buffer);
rc = cmd_ls((char *)buffer);
if (rc == 0)
{
send_ok();
}
else
{
send_error(protocol_map_error(rc));
}
break;
case CMD_INFO:
if (buffer[0] != '\0')
{
LOG_WRN("INFO command received with unexpected parameters: '%s'", buffer);
}
LOG_DBG("Executing INFO command");
rc = cmd_info();
if (rc == 0)
{
send_ok();
}
else
{
send_error(protocol_map_error(rc));
}
break;
case CMD_PUT_BINARY_FILE:
char filename[128];
ssize_t filesize;
uint32_t crc32;
rc = sscanf((char *)buffer, "%127[^;];%zd;%i", filename, &filesize, &crc32);
if (rc != 3)
{
LOG_ERR("Invalid parameters for PUT_BINARY_FILE command (got %d): '%s'", rc, buffer);
send_error(P_ERR_INVALID_PARAMETERS);
break;
}
LOG_DBG("Executing PUT_BINARY_FILE command filename: '%s', filesize: %zd, crc32: 0x%08x", filename, filesize, crc32);
rc = cmd_put_binary_file(filename, filesize, crc32);
if (rc == 0)
{
send_ok();
audio_refresh_file_count(); // Nach erfolgreichem Upload die Anzahl der verfügbaren Audiodateien aktualisieren
}
else
{
usb_flush_rx();
send_error(protocol_map_error(rc));
}
break;
case CMD_MKDIR:
LOG_DBG("Executing MKDIR command with parameters: '%s'", buffer);
rc = cmd_mkdir((char *)buffer);
if (rc == 0)
{
send_ok();
}
else
{
send_error(protocol_map_error(rc));
}
break;
case CMD_RM:
LOG_DBG("Executing RM command with parameters: '%s'", buffer);
rc = cmd_rm((char *)buffer);
if (rc == 0)
{
send_ok();
audio_refresh_file_count(); // Nach erfolgreichem Löschen die Anzahl der verfügbaren Audiodateien aktualisieren
}
else
{
send_error(protocol_map_error(rc));
}
break;
case CMD_CONFIRM:
LOG_DBG("Executing CONFIRM command");
rc = cmd_confirm_firmware();
if (rc != 0)
{
send_error(protocol_map_error(rc));
break;
}
send_ok();
break;
case CMD_REBOOT:
LOG_DBG("Executing REBOOT command");
rc = cmd_reboot_device();
if (rc != 0)
{
send_error(protocol_map_error(rc));
}
break;
case CMD_PLAY:
LOG_DBG("Executing PLAY command");
cmd_play((char *)buffer);
send_ok();
break;
case CMD_CHECK:
LOG_DBG("Executing CHECK command");
rc = cmd_check((char *)buffer);
if (rc == 0)
{
send_ok();
}
else
{
send_error(protocol_map_error(rc));
}
break;
default:
LOG_ERR("No execution logic for command %d", current_command);
send_error(P_ERR_NOT_SUPPORTED);
break;
}
}
protocol_state_t waiting_for_command(uint8_t byte)
{
if (byte < 'a' || byte > 'z')
{
LOG_DBG("Ignoring non-command byte: 0x%02x", byte); // Nur aktivieren, wenn nötig!
rx_index = 0;
return PS_WAITING_FOR_COMMAND;
}
buffer[rx_index++] = byte;
return PS_READING_COMMAND;
}
protocol_state_t reading_command(uint8_t byte)
{
if (byte == ' ' || byte == '\n' || byte == '\r')
{
buffer[rx_index] = '\0';
rx_index = 0;
if (strcmp((char *)buffer, "ls") == 0)
{
LOG_DBG("Received LS command");
current_command = CMD_LS;
}
else if (strcmp((char *)buffer, "info") == 0)
{
LOG_DBG("Received INFO command");
current_command = CMD_INFO;
}
else if (strcmp((char *)buffer, "put") == 0)
{
LOG_DBG("Received PUT_BINARY_FILE command");
current_command = CMD_PUT_BINARY_FILE;
}
else if (strcmp((char *)buffer, "mkdir") == 0)
{
LOG_DBG("Received MKDIR command");
current_command = CMD_MKDIR;
}
else if (strcmp((char *)buffer, "rm") == 0)
{
LOG_DBG("Received RM command");
current_command = CMD_RM;
}
else if (strcmp((char *)buffer, "confirm") == 0)
{
LOG_DBG("Received CONFIRM command");
current_command = CMD_CONFIRM;
}
else if (strcmp((char *)buffer, "reboot") == 0)
{
LOG_DBG("Received REBOOT command");
current_command = CMD_REBOOT;
}
else if (strcmp((char *)buffer, "play") == 0)
{
LOG_DBG("Received PLAY command");
current_command = CMD_PLAY;
}
else if (strcmp((char *)buffer, "check") == 0)
{
LOG_DBG("Received CHECK command");
current_command = CMD_CHECK;
}
else
{
LOG_DBG("Unknown command: %s", buffer);
current_command = CMD_INVALID;
send_error(P_ERR_INVALID_COMMAND);
if (byte != '\n' && byte != '\r')
return PS_WAITING_FOR_END_OF_LINE;
return PS_WAITING_FOR_COMMAND;
}
if (byte == ' ')
{
rx_index = 0;
return PS_READING_PARAMETERS;
}
else
{
buffer[0] = '\0';
rx_index = 0;
execute_current_command();
return PS_WAITING_FOR_COMMAND;
}
}
else
{
if (rx_index < BUFFER_SIZE - 1)
{
buffer[rx_index++] = byte;
}
else
{
send_error(P_ERR_COMMAND_TOO_LONG);
return PS_WAITING_FOR_END_OF_LINE;
}
}
return PS_READING_COMMAND;
}
protocol_state_t reading_parameters(uint8_t byte)
{
if (byte == '\n' || byte == '\r')
{
buffer[rx_index] = '\0';
rx_index = 0;
execute_current_command();
return PS_WAITING_FOR_COMMAND;
}
else
{
buffer[rx_index++] = byte;
if (rx_index >= BUFFER_SIZE)
{
rx_index = 0;
send_error(P_ERR_COMMAND_TOO_LONG);
return PS_WAITING_FOR_COMMAND;
}
return PS_READING_PARAMETERS;
}
}
protocol_state_t waiting_for_end_of_line(uint8_t byte)
{
if (byte == '\n' || byte == '\r')
{
return PS_WAITING_FOR_COMMAND;
}
else
{
return PS_WAITING_FOR_END_OF_LINE;
}
}
void protocol_thread_entry(void *p1, void *p2, void *p3)
{
uint8_t rx_byte;
LOG_DBG("Protocol thread started, waiting for data...");
while (1)
{
/* 1. Thread schläft, bis der USB-Interrupt triggert */
if (usb_wait_for_data(K_FOREVER))
{
while (usb_read_char(&rx_byte) > 0)
{
switch (current_protocol_state)
{
case PS_WAITING_FOR_COMMAND:
current_protocol_state = waiting_for_command(rx_byte);
break;
case PS_READING_COMMAND:
current_protocol_state = reading_command(rx_byte);
break;
case PS_READING_PARAMETERS:
current_protocol_state = reading_parameters(rx_byte);
break;
case PS_WAITING_FOR_END_OF_LINE:
current_protocol_state = waiting_for_end_of_line(rx_byte);
break;
default:
LOG_ERR("Invalid protocol state: %d", current_protocol_state);
current_protocol_state = PS_WAITING_FOR_COMMAND;
break;
}
}
usb_resume_rx();
}
}
}
/* Thread statisch definieren und automatisch starten lassen */
K_THREAD_DEFINE(protocol_tid, PROTOCOL_STACK_SIZE,
protocol_thread_entry, NULL, NULL, NULL,
PROTOCOL_PRIORITY, 0, 0);