528 lines
23 KiB
C
528 lines
23 KiB
C
#include "esp_app_desc.h"
|
|
#include "esp_err.h"
|
|
#include "esp_event.h"
|
|
#include "esp_heap_caps.h"
|
|
#include "esp_littlefs.h"
|
|
#include "esp_log.h"
|
|
#include "esp_netif.h"
|
|
#include "esp_timer.h"
|
|
#include <inttypes.h>
|
|
#ifdef MAZE32_MILESTONE_003_DEVICE_TEST
|
|
#include "esp_system.h"
|
|
#include <string.h>
|
|
#endif
|
|
#include "freertos/FreeRTOS.h"
|
|
#include "freertos/task.h"
|
|
#include "maze32_config_store.h"
|
|
#include "maze32_game.h"
|
|
#include "maze32_health.h"
|
|
#include "maze32_maze.h"
|
|
#include "maze32_network.h"
|
|
#include "maze32_web.h"
|
|
|
|
static const char* TAG = "maze32";
|
|
static maze32_health_t health;
|
|
static httpd_handle_t web_server;
|
|
static maze32_network_t network;
|
|
static maze32_gateway_t gateway;
|
|
|
|
#ifdef MAZE32_MILESTONE_007_DEVICE_TEST
|
|
static maze32_maze_t mechanics_test_maze;
|
|
static maze32_maze_workspace_t mechanics_test_workspace;
|
|
static maze32_game_t mechanics_test_game;
|
|
static maze32_view_t mechanics_test_view;
|
|
static char mechanics_test_payload[MAZE32_GAME_VIEW_CHUNK_BYTES];
|
|
|
|
static int entry_move_for(const maze32_maze_t* maze, maze32_cell_t target,
|
|
maze32_cell_t* starting_position, maze32_game_direction_t* direction) {
|
|
if (target.y > 0U && maze32_maze_has_passage(maze, target, MAZE32_PASSAGE_NORTH)) {
|
|
*starting_position = (maze32_cell_t){target.x, (uint16_t)(target.y - 1U)};
|
|
*direction = MAZE32_GAME_DIRECTION_SOUTH;
|
|
return 1;
|
|
}
|
|
if (target.x + 1U < maze->config.width &&
|
|
maze32_maze_has_passage(maze, target, MAZE32_PASSAGE_EAST)) {
|
|
*starting_position = (maze32_cell_t){(uint16_t)(target.x + 1U), target.y};
|
|
*direction = MAZE32_GAME_DIRECTION_WEST;
|
|
return 1;
|
|
}
|
|
if (target.y + 1U < maze->config.height &&
|
|
maze32_maze_has_passage(maze, target, MAZE32_PASSAGE_SOUTH)) {
|
|
*starting_position = (maze32_cell_t){target.x, (uint16_t)(target.y + 1U)};
|
|
*direction = MAZE32_GAME_DIRECTION_NORTH;
|
|
return 1;
|
|
}
|
|
if (target.x > 0U && maze32_maze_has_passage(maze, target, MAZE32_PASSAGE_WEST)) {
|
|
*starting_position = (maze32_cell_t){(uint16_t)(target.x - 1U), target.y};
|
|
*direction = MAZE32_GAME_DIRECTION_EAST;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static maze32_game_direction_t outward_direction(const maze32_maze_t* maze,
|
|
maze32_cell_t exit_cell) {
|
|
if (exit_cell.x == 0U) {
|
|
return MAZE32_GAME_DIRECTION_WEST;
|
|
}
|
|
if (exit_cell.x + 1U == maze->config.width) {
|
|
return MAZE32_GAME_DIRECTION_EAST;
|
|
}
|
|
if (exit_cell.y == 0U) {
|
|
return MAZE32_GAME_DIRECTION_NORTH;
|
|
}
|
|
return MAZE32_GAME_DIRECTION_SOUTH;
|
|
}
|
|
|
|
static esp_err_t run_mechanics_device_test(void) {
|
|
maze32_maze_config_t maze_config = maze32_maze_default_config();
|
|
maze32_maze_report_t maze_report;
|
|
if (maze32_maze_generate(&mechanics_test_maze, &mechanics_test_workspace, &maze_config,
|
|
UINT32_C(0x5a320007), &maze_report) != MAZE32_MAZE_OK) {
|
|
ESP_LOGE(TAG, "M007 physical mechanics test could not generate its maze");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
maze32_game_config_t game_config = maze32_game_default_config();
|
|
game_config.movement_cooldown_ms = 0U;
|
|
size_t free_before = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
|
int64_t started_us = esp_timer_get_time();
|
|
if (maze32_game_init(&mechanics_test_game, &mechanics_test_maze, &game_config) !=
|
|
MAZE32_GAME_RESULT_ACCEPTED) {
|
|
return ESP_FAIL;
|
|
}
|
|
maze32_game_set_phase(&mechanics_test_game, MAZE32_GAME_PHASE_ACTIVE);
|
|
|
|
maze32_cell_t entry_position;
|
|
maze32_game_direction_t entry_direction;
|
|
uint32_t command_sequence = 0U;
|
|
if (!entry_move_for(&mechanics_test_maze, mechanics_test_maze.artifacts[0][0], &entry_position,
|
|
&entry_direction)) {
|
|
return ESP_FAIL;
|
|
}
|
|
mechanics_test_game.players[0].position = entry_position;
|
|
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, entry_direction,
|
|
100U, &command_sequence);
|
|
maze32_game_command_result_t artifact_result = maze32_game_process_next(&mechanics_test_game);
|
|
|
|
if (!entry_move_for(&mechanics_test_maze, mechanics_test_maze.keys[0], &entry_position,
|
|
&entry_direction)) {
|
|
return ESP_FAIL;
|
|
}
|
|
mechanics_test_game.players[0].position = entry_position;
|
|
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, entry_direction,
|
|
300U, &command_sequence);
|
|
maze32_game_command_result_t key_result = maze32_game_process_next(&mechanics_test_game);
|
|
|
|
if (!entry_move_for(&mechanics_test_maze, mechanics_test_maze.compasses[0], &entry_position,
|
|
&entry_direction)) {
|
|
return ESP_FAIL;
|
|
}
|
|
mechanics_test_game.players[0].position = entry_position;
|
|
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, entry_direction,
|
|
500U, &command_sequence);
|
|
maze32_game_command_result_t compass_result = maze32_game_process_next(&mechanics_test_game);
|
|
|
|
if (!entry_move_for(&mechanics_test_maze, mechanics_test_maze.exits[0], &entry_position,
|
|
&entry_direction)) {
|
|
return ESP_FAIL;
|
|
}
|
|
mechanics_test_game.players[0].position = entry_position;
|
|
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, entry_direction,
|
|
700U, &command_sequence);
|
|
maze32_game_command_result_t exit_result = maze32_game_process_next(&mechanics_test_game);
|
|
maze32_game_enqueue_move(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE,
|
|
outward_direction(&mechanics_test_maze, mechanics_test_maze.exits[0]),
|
|
900U, &command_sequence);
|
|
maze32_game_command_result_t outward_result = maze32_game_process_next(&mechanics_test_game);
|
|
int finalized = maze32_game_advance_time(&mechanics_test_game, 1101U);
|
|
|
|
if (artifact_result.result != MAZE32_GAME_RESULT_ACCEPTED ||
|
|
!artifact_result.activated_artifact || key_result.result != MAZE32_GAME_RESULT_ACCEPTED ||
|
|
!key_result.picked_key || key_result.key != 0U ||
|
|
compass_result.compass_hint == MAZE32_GAME_COMPASS_NONE || !exit_result.opened_exit ||
|
|
!outward_result.outward_exit || !finalized ||
|
|
mechanics_test_game.final_result != MAZE32_GAME_FINAL_PLAYER_ONE ||
|
|
maze32_game_project_view(&mechanics_test_game, MAZE32_GAME_ROLE_PLAYER_ONE,
|
|
&mechanics_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
|
|
ESP_LOGE(TAG, "M007 physical mechanics transitions failed");
|
|
return ESP_FAIL;
|
|
}
|
|
size_t player_chunks = maze32_game_view_chunk_count(&mechanics_test_view);
|
|
size_t payload_bytes = 0U;
|
|
if (maze32_game_serialize_view_chunk(&mechanics_test_view, 0U, mechanics_test_payload,
|
|
sizeof(mechanics_test_payload),
|
|
&payload_bytes) != MAZE32_GAME_RESULT_ACCEPTED ||
|
|
maze32_game_project_view(&mechanics_test_game, MAZE32_GAME_ROLE_OBSERVER,
|
|
&mechanics_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
|
|
return ESP_FAIL;
|
|
}
|
|
uint64_t elapsed_us = (uint64_t)(esp_timer_get_time() - started_us);
|
|
size_t free_after = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
|
size_t fixed_bytes =
|
|
sizeof(mechanics_test_game) + sizeof(mechanics_test_view) + sizeof(mechanics_test_payload);
|
|
ESP_LOGI(TAG,
|
|
"M007 events: artifact=%u, key=%u, hint=%u, opened=%u, outward=%u, result=%u, "
|
|
"sequence=%" PRIu32,
|
|
artifact_result.artifact, key_result.key, compass_result.compass_hint,
|
|
exit_result.opened_exit, outward_result.outward_exit, mechanics_test_game.final_result,
|
|
mechanics_test_game.state_sequence);
|
|
ESP_LOGI(TAG,
|
|
"M007 views: player_chunks=%u, first_chunk=%u bytes, observer_cells=%u, "
|
|
"observer_objects=%u, observer_chunks=%u",
|
|
(unsigned int)player_chunks, (unsigned int)payload_bytes,
|
|
mechanics_test_view.cell_count, mechanics_test_view.object_count,
|
|
(unsigned int)maze32_game_view_chunk_count(&mechanics_test_view));
|
|
ESP_LOGI(TAG, "M007 budget: fixed=%u/%u bytes, runtime=%" PRIu64 "/%u ms, heap_delta=%d",
|
|
(unsigned int)fixed_bytes, (unsigned int)MAZE32_GAME_RAM_BUDGET_BYTES,
|
|
elapsed_us / 1000U, (unsigned int)MAZE32_GAME_RUNTIME_BUDGET_MS,
|
|
(int)(free_before - free_after));
|
|
if (fixed_bytes > MAZE32_GAME_RAM_BUDGET_BYTES ||
|
|
elapsed_us > (uint64_t)MAZE32_GAME_RUNTIME_BUDGET_MS * 1000U || free_before != free_after) {
|
|
ESP_LOGE(TAG, "M007 physical mechanics budget failed");
|
|
return ESP_FAIL;
|
|
}
|
|
ESP_LOGI(TAG, "M007 physical mechanics test: PASS");
|
|
return ESP_OK;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAZE32_MILESTONE_006_DEVICE_TEST
|
|
static maze32_maze_t game_test_maze;
|
|
static maze32_maze_workspace_t game_test_workspace;
|
|
static maze32_game_t device_test_game;
|
|
static maze32_view_t device_test_view;
|
|
static char device_test_payload[MAZE32_GAME_VIEW_CHUNK_BYTES];
|
|
|
|
static maze32_game_direction_t first_open_direction(const maze32_maze_t* maze, maze32_cell_t cell) {
|
|
static const maze32_game_direction_t DIRECTIONS[] = {
|
|
MAZE32_GAME_DIRECTION_NORTH,
|
|
MAZE32_GAME_DIRECTION_EAST,
|
|
MAZE32_GAME_DIRECTION_SOUTH,
|
|
MAZE32_GAME_DIRECTION_WEST,
|
|
};
|
|
static const maze32_passage_t PASSAGES[] = {
|
|
MAZE32_PASSAGE_NORTH,
|
|
MAZE32_PASSAGE_EAST,
|
|
MAZE32_PASSAGE_SOUTH,
|
|
MAZE32_PASSAGE_WEST,
|
|
};
|
|
for (size_t index = 0U; index < sizeof(DIRECTIONS) / sizeof(DIRECTIONS[0]); ++index) {
|
|
if (maze32_maze_has_passage(maze, cell, PASSAGES[index])) {
|
|
return DIRECTIONS[index];
|
|
}
|
|
}
|
|
return (maze32_game_direction_t)UINT8_MAX;
|
|
}
|
|
|
|
static esp_err_t run_game_device_test(void) {
|
|
maze32_maze_config_t maze_config = maze32_maze_default_config();
|
|
maze32_maze_report_t maze_report;
|
|
if (maze32_maze_generate(&game_test_maze, &game_test_workspace, &maze_config,
|
|
UINT32_C(0x5a320006), &maze_report) != MAZE32_MAZE_OK) {
|
|
ESP_LOGE(TAG, "M006 physical game test could not generate its maze");
|
|
return ESP_FAIL;
|
|
}
|
|
|
|
maze32_game_config_t game_config = maze32_game_default_config();
|
|
size_t free_before = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
|
int64_t started_us = esp_timer_get_time();
|
|
if (maze32_game_init(&device_test_game, &game_test_maze, &game_config) !=
|
|
MAZE32_GAME_RESULT_ACCEPTED) {
|
|
return ESP_FAIL;
|
|
}
|
|
maze32_game_set_phase(&device_test_game, MAZE32_GAME_PHASE_ACTIVE);
|
|
maze32_game_direction_t direction =
|
|
first_open_direction(&game_test_maze, device_test_game.players[0].position);
|
|
uint32_t command_sequence = 0U;
|
|
if (direction > MAZE32_GAME_DIRECTION_WEST ||
|
|
maze32_game_enqueue_move(&device_test_game, MAZE32_GAME_ROLE_PLAYER_ONE, direction, 0U,
|
|
&command_sequence) != MAZE32_GAME_RESULT_ACCEPTED) {
|
|
return ESP_FAIL;
|
|
}
|
|
maze32_game_command_result_t command_result = maze32_game_process_next(&device_test_game);
|
|
if (command_result.result != MAZE32_GAME_RESULT_ACCEPTED ||
|
|
command_result.state_sequence != 1U ||
|
|
maze32_game_project_view(&device_test_game, MAZE32_GAME_ROLE_PLAYER_ONE,
|
|
&device_test_view) != MAZE32_GAME_RESULT_ACCEPTED) {
|
|
return ESP_FAIL;
|
|
}
|
|
uint16_t player_cells = device_test_view.cell_count;
|
|
uint8_t player_objects = device_test_view.object_count;
|
|
size_t player_chunks = maze32_game_view_chunk_count(&device_test_view);
|
|
size_t payload_bytes = 0U;
|
|
if (maze32_game_serialize_view_chunk(&device_test_view, 0U, device_test_payload,
|
|
sizeof(device_test_payload),
|
|
&payload_bytes) != MAZE32_GAME_RESULT_ACCEPTED) {
|
|
return ESP_FAIL;
|
|
}
|
|
if (maze32_game_project_view(&device_test_game, MAZE32_GAME_ROLE_OBSERVER, &device_test_view) !=
|
|
MAZE32_GAME_RESULT_ACCEPTED) {
|
|
return ESP_FAIL;
|
|
}
|
|
uint64_t elapsed_us = (uint64_t)(esp_timer_get_time() - started_us);
|
|
size_t free_after = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
|
size_t fixed_bytes =
|
|
sizeof(device_test_game) + sizeof(device_test_view) + sizeof(device_test_payload);
|
|
ESP_LOGI(TAG, "M006 command: command=%" PRIu32 ", state=%" PRIu32 ", result=%s, position=%u,%u",
|
|
command_sequence, command_result.state_sequence,
|
|
maze32_game_result_name(command_result.result), device_test_game.players[0].position.x,
|
|
device_test_game.players[0].position.y);
|
|
ESP_LOGI(TAG,
|
|
"M006 views: player_cells=%u, player_objects=%u, player_chunks=%u, "
|
|
"observer_cells=%u, observer_objects=%u, observer_chunks=%u, first_chunk=%u bytes",
|
|
player_cells, player_objects, (unsigned int)player_chunks, device_test_view.cell_count,
|
|
device_test_view.object_count,
|
|
(unsigned int)maze32_game_view_chunk_count(&device_test_view),
|
|
(unsigned int)payload_bytes);
|
|
ESP_LOGI(TAG, "M006 budget: fixed=%u/%u bytes, runtime=%" PRIu64 "/%u ms, heap_delta=%d",
|
|
(unsigned int)fixed_bytes, (unsigned int)MAZE32_GAME_RAM_BUDGET_BYTES,
|
|
elapsed_us / 1000U, (unsigned int)MAZE32_GAME_RUNTIME_BUDGET_MS,
|
|
(int)(free_before - free_after));
|
|
if (device_test_view.cell_count != MAZE32_MAZE_DEFAULT_WIDTH * MAZE32_MAZE_DEFAULT_HEIGHT ||
|
|
fixed_bytes > MAZE32_GAME_RAM_BUDGET_BYTES ||
|
|
elapsed_us > (uint64_t)MAZE32_GAME_RUNTIME_BUDGET_MS * 1000U || free_before != free_after) {
|
|
ESP_LOGE(TAG, "M006 physical game test failed");
|
|
return ESP_FAIL;
|
|
}
|
|
ESP_LOGI(TAG, "M006 physical game test: PASS");
|
|
return ESP_OK;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAZE32_MILESTONE_005_DEVICE_TEST
|
|
static maze32_maze_t device_test_maze;
|
|
static maze32_maze_workspace_t device_test_workspace;
|
|
|
|
static esp_err_t run_maze_device_test(void) {
|
|
maze32_maze_config_t config = maze32_maze_default_config();
|
|
maze32_maze_report_t report;
|
|
size_t free_before = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
|
int64_t started_us = esp_timer_get_time();
|
|
maze32_maze_result_t result = maze32_maze_generate(&device_test_maze, &device_test_workspace,
|
|
&config, UINT32_C(0x5a320005), &report);
|
|
uint64_t elapsed_us = (uint64_t)(esp_timer_get_time() - started_us);
|
|
size_t free_after = heap_caps_get_free_size(MALLOC_CAP_8BIT);
|
|
size_t fixed_bytes = sizeof(device_test_maze) + sizeof(device_test_workspace);
|
|
ESP_LOGI(TAG, "M005 maze: result=%s, seed=%" PRIu32 ", attempts=%u, hash=%016" PRIx64,
|
|
maze32_maze_result_name(result), device_test_maze.requested_seed, report.attempts,
|
|
maze32_maze_hash(&device_test_maze));
|
|
ESP_LOGI(TAG,
|
|
"M005 topology: cells=%u, reachable=%u, edges=%u, cycles=%u, crossings=%u, "
|
|
"dead_ends=%u, branches=%u",
|
|
(unsigned int)(config.width * config.height), report.metrics.reachable_cells,
|
|
report.metrics.passage_edges, report.metrics.cycle_count,
|
|
report.metrics.cross_half_connections, report.metrics.dead_end_count,
|
|
report.metrics.branching_cells);
|
|
ESP_LOGI(TAG,
|
|
"M005 fairness: start_key=%u/%u, key_exit=%u/%u, start_exit=%u/%u, "
|
|
"compass=%u/%u, landmarks=%" PRIu32 "/%" PRIu32 ", artifacts=%" PRIu32 "/%" PRIu32,
|
|
report.metrics.start_to_key[0], report.metrics.start_to_key[1],
|
|
report.metrics.key_to_exit[0], report.metrics.key_to_exit[1],
|
|
report.metrics.start_to_exit[0], report.metrics.start_to_exit[1],
|
|
report.metrics.start_to_compass[0], report.metrics.start_to_compass[1],
|
|
report.metrics.landmark_distance_total[0], report.metrics.landmark_distance_total[1],
|
|
report.metrics.artifact_distance_total[0], report.metrics.artifact_distance_total[1]);
|
|
ESP_LOGI(TAG, "M005 budget: fixed=%u/%u bytes, runtime=%" PRIu64 "/%u ms, heap_delta=%d",
|
|
(unsigned int)fixed_bytes, (unsigned int)MAZE32_MAZE_RAM_BUDGET_BYTES,
|
|
elapsed_us / 1000U, (unsigned int)MAZE32_MAZE_RUNTIME_BUDGET_MS,
|
|
(int)(free_before - free_after));
|
|
if (result != MAZE32_MAZE_OK || fixed_bytes > MAZE32_MAZE_RAM_BUDGET_BYTES ||
|
|
elapsed_us > (uint64_t)MAZE32_MAZE_RUNTIME_BUDGET_MS * 1000U || free_before != free_after) {
|
|
ESP_LOGE(TAG, "M005 physical maze test failed");
|
|
return ESP_FAIL;
|
|
}
|
|
ESP_LOGI(TAG, "M005 physical maze test: PASS");
|
|
return ESP_OK;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAZE32_MILESTONE_003_DEVICE_TEST
|
|
static int device_test_has_persisted_credentials;
|
|
|
|
static esp_err_t run_device_test_before_network(void) {
|
|
const maze32_wifi_credentials_t expected = {
|
|
.ssid = "maze32-unavailable-test",
|
|
.password = "test-only-password",
|
|
};
|
|
maze32_wifi_credentials_t loaded = {0};
|
|
esp_err_t result = maze32_config_store_load(&loaded);
|
|
if (result == ESP_ERR_NOT_FOUND) {
|
|
result = maze32_config_store_save(&expected);
|
|
if (result == ESP_OK) {
|
|
ESP_LOGI(TAG, "M003 device test: credentials saved; restarting");
|
|
esp_restart();
|
|
}
|
|
return result;
|
|
}
|
|
if (result != ESP_OK || strcmp(loaded.ssid, expected.ssid) != 0 ||
|
|
strcmp(loaded.password, expected.password) != 0) {
|
|
ESP_LOGE(TAG, "M003 device test refused to overwrite unexpected NVS credentials");
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
device_test_has_persisted_credentials = 1;
|
|
ESP_LOGI(TAG, "M003 device test: credentials survived restart");
|
|
return ESP_OK;
|
|
}
|
|
|
|
static esp_err_t finish_device_test(void) {
|
|
if (!device_test_has_persisted_credentials) {
|
|
return ESP_ERR_INVALID_STATE;
|
|
}
|
|
esp_err_t result = maze32_config_store_clear();
|
|
if (result == ESP_OK) {
|
|
ESP_LOGI(TAG, "M003 device test: failed station returned to fallback; credentials cleared");
|
|
}
|
|
return result;
|
|
}
|
|
#endif
|
|
|
|
static int prepare_for_network_reconfiguration(void* context) {
|
|
const maze32_gateway_t* current_gateway = context;
|
|
if (!maze32_gateway_can_reconfigure(current_gateway)) {
|
|
ESP_LOGW(TAG, "Network reconfiguration rejected while lobby is active");
|
|
return -1;
|
|
}
|
|
ESP_LOGW(TAG, "Runtime accepted network reconfiguration while lobby is waiting");
|
|
return 0;
|
|
}
|
|
|
|
static esp_err_t mount_littlefs(void) {
|
|
const esp_vfs_littlefs_conf_t configuration = {
|
|
.base_path = "/littlefs",
|
|
.partition_label = "littlefs",
|
|
.format_if_mount_failed = false,
|
|
.dont_mount = false,
|
|
};
|
|
|
|
esp_err_t result = esp_vfs_littlefs_register(&configuration);
|
|
if (result != ESP_OK) {
|
|
return result;
|
|
}
|
|
|
|
size_t total_bytes = 0;
|
|
size_t used_bytes = 0;
|
|
result = esp_littlefs_info(configuration.partition_label, &total_bytes, &used_bytes);
|
|
if (result != ESP_OK) {
|
|
esp_vfs_littlefs_unregister(configuration.partition_label);
|
|
return result;
|
|
}
|
|
|
|
ESP_LOGI(TAG, "LittleFS mounted: total=%u bytes, used=%u bytes", (unsigned int)total_bytes,
|
|
(unsigned int)used_bytes);
|
|
return ESP_OK;
|
|
}
|
|
|
|
void app_main(void) {
|
|
const esp_app_desc_t* application = esp_app_get_description();
|
|
maze32_health_init(&health, application->version);
|
|
|
|
#ifdef MAZE32_MILESTONE_005_DEVICE_TEST
|
|
if (run_maze_device_test() != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)ESP_FAIL);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAZE32_MILESTONE_006_DEVICE_TEST
|
|
if (run_game_device_test() != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)ESP_FAIL);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
#ifdef MAZE32_MILESTONE_007_DEVICE_TEST
|
|
if (run_mechanics_device_test() != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)ESP_FAIL);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
vTaskDelay(pdMS_TO_TICKS(1500));
|
|
ESP_LOGI(TAG, "maze32 firmware version %s starting", application->version);
|
|
|
|
esp_err_t result = mount_littlefs();
|
|
if (result != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
ESP_LOGE(TAG, "LittleFS mount failed: %s", esp_err_to_name(result));
|
|
return;
|
|
}
|
|
|
|
maze32_health_set(&health, MAZE32_HEALTH_FILESYSTEM_READY, 0);
|
|
|
|
result = maze32_config_store_init();
|
|
if (result != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
ESP_LOGE(TAG, "Persistent configuration initialization failed: %s",
|
|
esp_err_to_name(result));
|
|
return;
|
|
}
|
|
|
|
#ifdef MAZE32_MILESTONE_003_DEVICE_TEST
|
|
result = run_device_test_before_network();
|
|
if (result != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
result = esp_netif_init();
|
|
if (result != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
ESP_LOGE(TAG, "TCP/IP stack initialization failed: %s", esp_err_to_name(result));
|
|
return;
|
|
}
|
|
|
|
result = esp_event_loop_create_default();
|
|
if (result != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
ESP_LOGE(TAG, "Default event loop initialization failed: %s", esp_err_to_name(result));
|
|
return;
|
|
}
|
|
|
|
result = maze32_gateway_init(&gateway);
|
|
if (result != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
ESP_LOGE(TAG, "WebSocket gateway initialization failed: %s", esp_err_to_name(result));
|
|
return;
|
|
}
|
|
|
|
result = maze32_network_init(&network, prepare_for_network_reconfiguration, &gateway);
|
|
if (result == ESP_OK) {
|
|
result = maze32_network_start(&network);
|
|
}
|
|
if (result != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
ESP_LOGE(TAG, "Network initialization failed: %s", esp_err_to_name(result));
|
|
return;
|
|
}
|
|
|
|
#ifdef MAZE32_MILESTONE_003_DEVICE_TEST
|
|
result = finish_device_test();
|
|
if (result != ESP_OK) {
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
return;
|
|
}
|
|
#endif
|
|
|
|
result = maze32_web_start(&web_server, &health, &gateway);
|
|
if (result == ESP_OK) {
|
|
result = maze32_gateway_register(web_server, &gateway);
|
|
}
|
|
if (result == ESP_OK) {
|
|
result = maze32_provisioning_register(web_server, &network);
|
|
}
|
|
if (result != ESP_OK) {
|
|
if (web_server != NULL) {
|
|
httpd_stop(web_server);
|
|
web_server = NULL;
|
|
}
|
|
maze32_health_set(&health, MAZE32_HEALTH_ERROR, (int)result);
|
|
ESP_LOGE(TAG, "HTTP server startup failed: %s", esp_err_to_name(result));
|
|
return;
|
|
}
|
|
|
|
maze32_health_set(&health, MAZE32_HEALTH_SERVING, 0);
|
|
ESP_LOGI(TAG, "Health state: %s", maze32_health_state_name(health.state));
|
|
}
|