#include #include #include #include #include #include #include #include #include #include #include #include #include #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);