Login
1 branch 0 tags
Ben (Desktop/Arch) AGENTS.md b60dcff 29 days ago 82 Commits
moon / esp32 / main / network_esp32.c
#include <stdlib.h>
#include <string.h>
#include "../../src/config.h"
#include "../../src/moon.h"
#include "../../src/network.h"
#include "../../src/storage.h"
#include "esp_crt_bundle.h"
#include "esp_http_client.h"
#include "esp_log.h"
#include "esp_netif.h"
#include "esp_wifi.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"

static const char* TAG = "NET";

#define WIFI_CONNECTED_BIT BIT0
#define WIFI_FAIL_BIT BIT1
#define DOWNLOAD_BUF_SIZE 4096

static EventGroupHandle_t wifi_event_group;
static bool wifi_stack_initialized = false;
static bool wifi_connected = false;

static void wifi_event_handler(void* arg,
                               esp_event_base_t event_base,
                               int32_t event_id,
                               void* event_data) {
	(void)arg;
	(void)event_data;
	if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) {
		xEventGroupSetBits(wifi_event_group, WIFI_FAIL_BIT);
	} else if (event_base == IP_EVENT && event_id == IP_EVENT_STA_GOT_IP) {
		ip_event_got_ip_t* event = (ip_event_got_ip_t*)event_data;
		ESP_LOGI(TAG, "Got IP: " IPSTR, IP2STR(&event->ip_info.ip));
		xEventGroupSetBits(wifi_event_group, WIFI_CONNECTED_BIT);
	}
}

void network_init(void) {
	if (wifi_stack_initialized) {
		return;
	}

	wifi_event_group = xEventGroupCreate();

	esp_netif_init();
	esp_event_loop_create_default();
	esp_netif_create_default_wifi_sta();

	wifi_init_config_t init_cfg = WIFI_INIT_CONFIG_DEFAULT();
	esp_wifi_init(&init_cfg);

	esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, wifi_event_handler,
	                           NULL);
	esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP,
	                           wifi_event_handler, NULL);

	esp_wifi_set_mode(WIFI_MODE_STA);

	wifi_stack_initialized = true;
	ESP_LOGI(TAG, "WiFi stack initialized");
}

static bool wifi_connect(void) {
	config_t cfg;
	if (!config_load(&cfg)) {
		ESP_LOGI(TAG, "No WiFi config found");
		return false;
	}

	// Try each configured network
	for (int i = 0; i < cfg.wifi_count; i++) {
		ESP_LOGI(TAG, "Trying SSID: %s", cfg.wifi[i].ssid);

		wifi_config_t wifi_cfg = {0};
		strncpy((char*)wifi_cfg.sta.ssid, cfg.wifi[i].ssid,
		        sizeof(wifi_cfg.sta.ssid) - 1);
		strncpy((char*)wifi_cfg.sta.password, cfg.wifi[i].psk,
		        sizeof(wifi_cfg.sta.password) - 1);

		xEventGroupClearBits(wifi_event_group,
		                     WIFI_CONNECTED_BIT | WIFI_FAIL_BIT);

		esp_wifi_set_config(WIFI_IF_STA, &wifi_cfg);
		esp_wifi_start();
		esp_wifi_connect();

		EventBits_t bits = xEventGroupWaitBits(
		    wifi_event_group, WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE,
		    pdFALSE, pdMS_TO_TICKS(10000));

		if (bits & WIFI_CONNECTED_BIT) {
			ESP_LOGI(TAG, "Connected to %s", cfg.wifi[i].ssid);
			wifi_connected = true;
			return true;
		}

		ESP_LOGI(TAG, "Failed to connect to %s", cfg.wifi[i].ssid);
		esp_wifi_disconnect();
		esp_wifi_stop();
	}

	ESP_LOGI(TAG, "Failed to connect to any WiFi network");
	return false;
}

static void wifi_disconnect(void) {
	if (!wifi_connected) {
		return;
	}
	esp_wifi_disconnect();
	esp_wifi_stop();
	wifi_connected = false;
}

bool network_set_enabled(bool enabled) {
	if (enabled) {
		bool ok = wifi_connect();
		wifi_enabled = ok;
		return ok;
	} else {
		wifi_disconnect();
		wifi_enabled = false;
		return true;
	}
}

typedef struct {
	storage_file_t file;
	bool ok;
} download_ctx_t;

static esp_err_t http_event_handler(esp_http_client_event_t* evt) {
	download_ctx_t* ctx = (download_ctx_t*)evt->user_data;
	if (evt->event_id == HTTP_EVENT_ON_DATA && ctx->file) {
		size_t written = storage_write(ctx->file, evt->data, evt->data_len);
		if (written != (size_t)evt->data_len) {
			ctx->ok = false;
		}
	}
	return ESP_OK;
}

bool network_download_file(const char* url, const char* destination_path) {
	if (!url || !destination_path) {
		return false;
	}

	ESP_LOGI(TAG, "Downloading %s -> %s", url, destination_path);

	storage_file_t f = storage_open(destination_path, "w");
	if (!f) {
		return false;
	}

	download_ctx_t ctx = {.file = f, .ok = true};

	esp_http_client_config_t config = {
	    .url = url,
	    .event_handler = http_event_handler,
	    .user_data = &ctx,
	    .timeout_ms = 30000,
	    .buffer_size = DOWNLOAD_BUF_SIZE,
	    .crt_bundle_attach = esp_crt_bundle_attach,
	};

	esp_http_client_handle_t client = esp_http_client_init(&config);
	esp_err_t err = esp_http_client_perform(client);
	int status = esp_http_client_get_status_code(client);
	esp_http_client_cleanup(client);
	storage_close(f);

	if (err != ESP_OK || status != 200 || !ctx.ok) {
		ESP_LOGI(TAG, "Download failed: err=%d status=%d", err, status);
		storage_remove(destination_path);
		return false;
	}

	return true;
}

bool network_is_connected(void) {
	return wifi_connected;
}

int network_get_rssi(void) {
	if (!wifi_connected) {
		return 0;
	}
	wifi_ap_record_t ap_info;
	if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
		return ap_info.rssi;
	}
	return 0;
}

bool network_test_url(const char* url) {
	if (!url || !wifi_connected) {
		return false;
	}

	esp_http_client_config_t config = {
	    .url = url,
	    .timeout_ms = 10000,
	    .buffer_size = DOWNLOAD_BUF_SIZE,
	    .crt_bundle_attach = esp_crt_bundle_attach,
	};

	esp_http_client_handle_t client = esp_http_client_init(&config);
	esp_err_t err = esp_http_client_perform(client);
	int status = esp_http_client_get_status_code(client);
	esp_http_client_cleanup(client);

	return (err == ESP_OK && status == 200);
}

int network_scan(char ssids[][33], int max_results) {
	if (!wifi_stack_initialized) {
		ESP_LOGI(TAG, "Scan: stack not initialized");
		return -1;
	}

	// Ensure clean state for scanning
	esp_wifi_disconnect();
	esp_wifi_stop();

	esp_err_t err = esp_wifi_start();
	if (err != ESP_OK) {
		ESP_LOGI(TAG, "Scan: wifi_start: %s", esp_err_to_name(err));
		return -2;
	}

	wifi_scan_config_t scan_cfg = {
	    .show_hidden = false,
	    .scan_type = WIFI_SCAN_TYPE_ACTIVE,
	    .scan_time.active.min = 100,
	    .scan_time.active.max = 500,
	};

	err = esp_wifi_scan_start(&scan_cfg, true);
	if (err != ESP_OK) {
		ESP_LOGI(TAG, "Scan: scan_start failed: %s", esp_err_to_name(err));
		return -3;
	}

	uint16_t ap_count = 0;
	esp_wifi_scan_get_ap_num(&ap_count);
	ESP_LOGI(TAG, "Scan: found %d APs", ap_count);

	if (ap_count == 0) {
		return 0;
	}

	uint16_t fetch = ap_count;
	if (fetch > (uint16_t)max_results) {
		fetch = (uint16_t)max_results;
	}

	wifi_ap_record_t* ap_list = malloc(fetch * sizeof(wifi_ap_record_t));
	if (!ap_list) {
		return -4;
	}

	esp_wifi_scan_get_ap_records(&fetch, ap_list);

	for (int i = 0; i < fetch; i++) {
		strncpy(ssids[i], (const char*)ap_list[i].ssid, 32);
		ssids[i][32] = '\0';
		ESP_LOGI(TAG, "  AP[%d]: %s (rssi %d)", i, ssids[i], ap_list[i].rssi);
	}

	free(ap_list);
	return (int)fetch;
}