feat(velxio-deployer): unique device name + WiFi AP setup mode
- Add device_name module: NVS-persisted custom name with MAC-suffix
fallback (Velxio-XXXX) so every device is uniquely identifiable
out-of-the-box in BLE pairing dialog
- Add wifi_ap module: first-boot SoftAP (Velxio-Setup-XXXX / velxio123)
with embedded HTTP config portal at 192.168.4.1 for renaming
- main.c: enter AP setup mode when no custom name is set
- ble_service.c: use dynamic device name from device_name_get(),
with adv-packet length guard
- led_button.c: BOOT button long-press (5s) factory reset (erase NVS)
- sdkconfig.defaults: enable WiFi stack + set NimBLE default name
Frontend deviceName display already committed in 01f68d3.
This commit is contained in:
parent
f164134222
commit
a0fb1d80cd
|
|
@ -10,6 +10,10 @@ idf_component_register(
|
|||
serial_bridge.c
|
||||
led_button.c
|
||||
arduino_reset.c
|
||||
device_name.c
|
||||
wifi_ap.c
|
||||
INCLUDE_DIRS
|
||||
.
|
||||
EMBED_TXTFILES
|
||||
config_page.html
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ void ble_store_config_init(void);
|
|||
#include "state_machine.h"
|
||||
#include "services/gatt/ble_svc_gatt.h"
|
||||
#include "ble_service.h"
|
||||
#include "device_name.h"
|
||||
#include "arduino_reset.h"
|
||||
#include "serial_bridge.h"
|
||||
#include "state_machine.h"
|
||||
|
|
@ -148,9 +149,21 @@ static void ble_restart_adv(void)
|
|||
struct ble_hs_adv_fields fields;
|
||||
memset(&fields, 0, sizeof(fields));
|
||||
fields.flags = BLE_HS_ADV_F_DISC_GEN | BLE_HS_ADV_F_BREDR_UNSUP;
|
||||
fields.name = (uint8_t *)"Velxio";
|
||||
fields.name_len = 6;
|
||||
fields.name_is_complete = 0;
|
||||
const char *name = device_name_get();
|
||||
size_t name_len = strlen(name);
|
||||
// Check if full name fits in 31-byte adv packet
|
||||
// Adv fields overhead: flags(3) + name header(2) = ~5 bytes overhead for name
|
||||
// So name content needs ≤ 26 bytes max
|
||||
if (name_len <= 24) { // safe margin under 31-byte limit
|
||||
fields.name = (uint8_t *)name;
|
||||
fields.name_len = name_len;
|
||||
fields.name_is_complete = 1;
|
||||
} else {
|
||||
// Truncate to short prefix for advertising
|
||||
fields.name = (uint8_t *)"Velxio";
|
||||
fields.name_len = 6;
|
||||
fields.name_is_complete = 0;
|
||||
}
|
||||
fields.uuids16 = NULL;
|
||||
fields.num_uuids16 = 0;
|
||||
|
||||
|
|
@ -270,7 +283,7 @@ void ble_service_init(void)
|
|||
ble_gatts_count_cfg(gatt_svcs);
|
||||
ble_gatts_add_svcs(gatt_svcs);
|
||||
|
||||
ble_svc_gap_device_name_set("Velxio-Deployer");
|
||||
ble_svc_gap_device_name_set(device_name_get());
|
||||
ble_svc_gap_device_appearance_set(0x0080);
|
||||
|
||||
ble_store_config_init();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,104 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="id">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Velxio Deployer Setup</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body { font-family: -apple-system, system-ui, sans-serif; background: #0f172a; color: #e2e8f0; display: flex; min-height: 100vh; align-items: center; justify-content: center; }
|
||||
.container { max-width: 420px; width: 90%; padding: 24px; }
|
||||
.card { background: #1e293b; border-radius: 12px; padding: 28px; box-shadow: 0 4px 24px rgba(0,0,0,0.3); }
|
||||
h1 { font-size: 1.4rem; margin-bottom: 4px; color: #3b82f6; }
|
||||
.subtitle { font-size: 0.85rem; color: #94a3b8; margin-bottom: 20px; }
|
||||
.current-name { background: #334155; border-radius: 8px; padding: 12px; margin-bottom: 20px; }
|
||||
.current-label { font-size: 0.75rem; color: #94a3b8; margin-bottom: 4px; }
|
||||
.current-value { font-size: 1rem; font-weight: 600; color: #22c55e; font-family: monospace; }
|
||||
label { display: block; font-size: 0.85rem; color: #94a3b8; margin-bottom: 6px; }
|
||||
.input-group { display: flex; gap: 0; margin-bottom: 20px; }
|
||||
.prefix { background: #475569; padding: 10px 12px; border-radius: 8px 0 0 8px; font-family: monospace; font-size: 0.9rem; color: #cbd5e1; border: 1px solid #475569; border-right: none; }
|
||||
input[type="text"] { flex: 1; padding: 10px 12px; border: 1px solid #475569; border-radius: 0 8px 8px 0; background: #0f172a; color: #e2e8f0; font-size: 0.9rem; font-family: monospace; outline: none; }
|
||||
input[type="text"]:focus { border-color: #3b82f6; }
|
||||
button { width: 100%; padding: 12px; background: #3b82f6; color: white; border: none; border-radius: 8px; font-size: 1rem; font-weight: 600; cursor: pointer; transition: background 0.2s; }
|
||||
button:hover { background: #2563eb; }
|
||||
button:disabled { background: #475569; cursor: not-allowed; }
|
||||
.status { margin-top: 16px; padding: 10px; border-radius: 8px; font-size: 0.85rem; text-align: center; }
|
||||
.status.success { background: #14532d; color: #22c55e; }
|
||||
.status.error { background: #450a0a; color: #ef4444; }
|
||||
.status.loading { background: #1e3a5f; color: #3b82f6; }
|
||||
.hint { font-size: 0.75rem; color: #64748b; margin-top: 16px; text-align: center; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="card">
|
||||
<h1>Velxio Deployer</h1>
|
||||
<p class="subtitle">Konfigurasi nama perangkat</p>
|
||||
<div class="current-name">
|
||||
<div class="current-label">Nama saat ini</div>
|
||||
<div class="current-value" id="currentName">--</div>
|
||||
</div>
|
||||
<label for="nameInput">Nama baru</label>
|
||||
<div class="input-group">
|
||||
<span class="prefix">Velxio-</span>
|
||||
<input type="text" id="nameInput" placeholder="LAB-01" maxlength="24" autocomplete="off">
|
||||
</div>
|
||||
<button id="saveBtn" onclick="saveName()">Simpan & Reboot</button>
|
||||
<div id="status"></div>
|
||||
<p class="hint">Nama akan tampil di pairing Bluetooth. Maks 24 karakter.</p>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
const currentEl = document.getElementById('currentName');
|
||||
const inputEl = document.getElementById('nameInput');
|
||||
const btnEl = document.getElementById('saveBtn');
|
||||
const statusEl = document.getElementById('status');
|
||||
|
||||
async function loadCurrent() {
|
||||
try {
|
||||
const res = await fetch('/api/name');
|
||||
const data = await res.json();
|
||||
currentEl.textContent = data.name;
|
||||
const stripped = data.name.startsWith('Velxio-') ? data.name.slice(7) : data.name;
|
||||
inputEl.value = stripped;
|
||||
} catch (e) {
|
||||
currentEl.textContent = 'Error';
|
||||
}
|
||||
}
|
||||
|
||||
async function saveName() {
|
||||
const name = inputEl.value.trim();
|
||||
if (!name) {
|
||||
statusEl.className = 'status error';
|
||||
statusEl.textContent = 'Nama tidak boleh kosong';
|
||||
return;
|
||||
}
|
||||
btnEl.disabled = true;
|
||||
statusEl.className = 'status loading';
|
||||
statusEl.textContent = 'Menyimpan...';
|
||||
try {
|
||||
const res = await fetch('/api/name', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Velxio-' + name })
|
||||
});
|
||||
const data = await res.json();
|
||||
if (data.success) {
|
||||
statusEl.className = 'status success';
|
||||
statusEl.textContent = 'Tersimpan! Rebooting...';
|
||||
} else {
|
||||
statusEl.className = 'status error';
|
||||
statusEl.textContent = 'Error: ' + (data.error || 'unknown');
|
||||
btnEl.disabled = false;
|
||||
}
|
||||
} catch (e) {
|
||||
statusEl.className = 'status error';
|
||||
statusEl.textContent = 'Error: ' + e.message;
|
||||
btnEl.disabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
loadCurrent();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,107 @@
|
|||
#include "device_name.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "nvs_flash.h"
|
||||
#include "nvs.h"
|
||||
#include "esp_mac.h"
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char *TAG = "DEV_NAME";
|
||||
static char s_device_name[VELXIO_NAME_MAX_LEN] = {0};
|
||||
static bool s_is_custom = false;
|
||||
|
||||
static void generate_mac_suffix_name(void)
|
||||
{
|
||||
uint8_t mac[6];
|
||||
esp_err_t mac_err = esp_read_mac(mac, ESP_MAC_BT);
|
||||
if (mac_err != ESP_OK) {
|
||||
ESP_LOGW(TAG, "esp_read_mac failed (%d), using fallback suffix", mac_err);
|
||||
snprintf(s_device_name, sizeof(s_device_name),
|
||||
"Velxio-0000");
|
||||
} else {
|
||||
snprintf(s_device_name, sizeof(s_device_name),
|
||||
"Velxio-%02X%02X", mac[4], mac[5]);
|
||||
}
|
||||
s_is_custom = false;
|
||||
ESP_LOGI(TAG, "Generated device name from MAC: %s", s_device_name);
|
||||
}
|
||||
|
||||
void device_name_init(void)
|
||||
{
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(VELXIO_NVS_NAMESPACE, NVS_READONLY, &h);
|
||||
if (err == ESP_OK) {
|
||||
size_t len = sizeof(s_device_name);
|
||||
err = nvs_get_str(h, VELXIO_NVS_KEY_NAME, s_device_name, &len);
|
||||
nvs_close(h);
|
||||
if (err == ESP_OK && strlen(s_device_name) > 0) {
|
||||
s_is_custom = true;
|
||||
ESP_LOGI(TAG, "Loaded custom device name from NVS: %s", s_device_name);
|
||||
return;
|
||||
}
|
||||
ESP_LOGI(TAG, "NVS open OK but no custom name key, using MAC suffix");
|
||||
} else {
|
||||
ESP_LOGI(TAG, "NVS namespace 'velxio' not found (err=%d), using MAC suffix", err);
|
||||
}
|
||||
generate_mac_suffix_name();
|
||||
}
|
||||
|
||||
const char *device_name_get(void)
|
||||
{
|
||||
return s_device_name;
|
||||
}
|
||||
|
||||
bool device_name_is_custom(void)
|
||||
{
|
||||
return s_is_custom;
|
||||
}
|
||||
|
||||
esp_err_t device_name_set_custom(const char *name)
|
||||
{
|
||||
if (!name || strlen(name) == 0) {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
size_t prefix = (strncmp(name, "Velxio-", 7) == 0) ? 0 : 7;
|
||||
if (strlen(name) + prefix >= VELXIO_NAME_MAX_LEN) {
|
||||
ESP_LOGE(TAG, "Name too long (max %d chars including prefix)", VELXIO_NAME_MAX_LEN - 1);
|
||||
return ESP_ERR_INVALID_SIZE;
|
||||
}
|
||||
|
||||
char full_name[VELXIO_NAME_MAX_LEN];
|
||||
if (prefix == 0) {
|
||||
snprintf(full_name, sizeof(full_name), "%s", name);
|
||||
} else {
|
||||
snprintf(full_name, sizeof(full_name), "Velxio-%s", name);
|
||||
}
|
||||
|
||||
nvs_handle_t h;
|
||||
esp_err_t err = nvs_open(VELXIO_NVS_NAMESPACE, NVS_READWRITE, &h);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "NVS open failed: %d", err);
|
||||
return err;
|
||||
}
|
||||
|
||||
err = nvs_set_str(h, VELXIO_NVS_KEY_NAME, full_name);
|
||||
if (err == ESP_OK) {
|
||||
err = nvs_commit(h);
|
||||
}
|
||||
nvs_close(h);
|
||||
|
||||
if (err == ESP_OK) {
|
||||
ESP_LOGI(TAG, "Saved custom device name: %s", full_name);
|
||||
snprintf(s_device_name, sizeof(s_device_name), "%s", full_name);
|
||||
s_is_custom = true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "NVS save failed: %d", err);
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void device_name_get_ap_ssid(char *buf, size_t buf_len)
|
||||
{
|
||||
const char *name = s_device_name;
|
||||
size_t len = strlen(name);
|
||||
const char *suffix = (len >= 4) ? (name + len - 4) : "0000";
|
||||
snprintf(buf, buf_len, "Velxio-Setup-%s", suffix);
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* @file device_name.h
|
||||
* @brief Device name management for Velxio firmware
|
||||
*
|
||||
* Manages persistent custom device names and MAC-address-derived
|
||||
* fallback names, stored in NVS.
|
||||
*/
|
||||
#pragma once
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
#define VELXIO_NVS_NAMESPACE "velxio"
|
||||
#define VELXIO_NVS_KEY_NAME "dev_name"
|
||||
#define VELXIO_NAME_MAX_LEN 32
|
||||
|
||||
void device_name_init(void);
|
||||
const char *device_name_get(void);
|
||||
bool device_name_is_custom(void);
|
||||
esp_err_t device_name_set_custom(const char *name);
|
||||
void device_name_get_ap_ssid(char *buf, size_t buf_len);
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
#include "esp_log.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "esp_timer.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.h"
|
||||
#include "led_button.h"
|
||||
|
||||
static const char *TAG = "LED_BTN";
|
||||
|
|
@ -11,6 +13,10 @@ static bool button_pressed_flag = false;
|
|||
static int64_t last_toggle_time = 0;
|
||||
static bool blink_state = false;
|
||||
|
||||
/* Long-press factory reset tracking */
|
||||
static int64_t button_press_start_ms = 0;
|
||||
static bool button_is_held = false;
|
||||
|
||||
static void set_led(bool red, bool green, bool blue)
|
||||
{
|
||||
gpio_set_level(LED_GPIO_RED, red ? 1 : 0);
|
||||
|
|
@ -78,6 +84,27 @@ bool button_retry_pressed(void)
|
|||
|
||||
void led_button_tick(void)
|
||||
{
|
||||
/* Factory reset long-press detection (GPIO0 = BOOT button) */
|
||||
bool btn_level = gpio_get_level(BTN_GPIO_RETRY);
|
||||
if (btn_level == 0) { /* button pressed (active low with pull-up) */
|
||||
if (!button_is_held) {
|
||||
button_is_held = true;
|
||||
button_press_start_ms = esp_timer_get_time() / 1000;
|
||||
} else {
|
||||
int64_t held_ms = (esp_timer_get_time() / 1000) - button_press_start_ms;
|
||||
if (held_ms > 5000) {
|
||||
ESP_LOGW(TAG, "BOOT button held %lldms — factory reset!", held_ms);
|
||||
esp_err_t erase_err = nvs_flash_erase();
|
||||
if (erase_err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "NVS erase failed: %d", erase_err);
|
||||
}
|
||||
esp_restart();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
button_is_held = false;
|
||||
}
|
||||
|
||||
if (current_pattern != LED_GREEN_BLINK &&
|
||||
current_pattern != LED_RED_BLINK &&
|
||||
current_pattern != LED_BLUE_BLINK &&
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
#include "state_machine.h"
|
||||
#include "serial_bridge.h"
|
||||
#include "led_button.h"
|
||||
#include "device_name.h"
|
||||
#include "wifi_ap.h"
|
||||
|
||||
static const char *TAG = "MAIN";
|
||||
|
||||
|
|
@ -67,7 +69,20 @@ void app_main(void)
|
|||
{
|
||||
ESP_LOGI(TAG, "Velxio BLE Deployer v1.0");
|
||||
|
||||
// Initialize NVS
|
||||
nvs_flash_init();
|
||||
|
||||
// Initialize device name from NVS or MAC suffix
|
||||
device_name_init();
|
||||
|
||||
// AP setup mode: if no custom name, start AP config portal
|
||||
if (!device_name_is_custom()) {
|
||||
ESP_LOGW(TAG, "No custom name — entering AP setup mode");
|
||||
wifi_ap_start_and_block(); // never returns
|
||||
}
|
||||
|
||||
// --- Normal boot: BLE + USB Host mode ---
|
||||
|
||||
esp_bt_controller_mem_release(ESP_BT_MODE_CLASSIC_BT);
|
||||
|
||||
led_button_init();
|
||||
|
|
|
|||
|
|
@ -0,0 +1,228 @@
|
|||
#include "wifi_ap.h"
|
||||
#include "device_name.h"
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include <ctype.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "esp_event.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_netif.h"
|
||||
#include "esp_http_server.h"
|
||||
#include "nvs_flash.h"
|
||||
#include "esp_system.h"
|
||||
|
||||
static const char *TAG = "WIFI_AP";
|
||||
|
||||
/* Embedded config_page.html — linked via EMBED_TXTFILES in CMakeLists.txt */
|
||||
extern const uint8_t config_page_html_start[] asm("_binary_config_page_html_start");
|
||||
extern const uint8_t config_page_html_end[] asm("_binary_config_page_html_end");
|
||||
|
||||
#define AP_PASSWORD "velxio123"
|
||||
|
||||
/* ---------- Minimal JSON helpers ---------- */
|
||||
|
||||
/**
|
||||
* @brief Extract string value for a given key from a flat JSON object.
|
||||
*
|
||||
* Parses {"key":"value"} — no nesting, no escaping support.
|
||||
* Returns pointer to the value text (null-terminated in-place) or NULL.
|
||||
*/
|
||||
static const char *json_get_string(const char *json, const char *key)
|
||||
{
|
||||
if (!json || !key) return NULL;
|
||||
|
||||
/* Build search pattern: "key":" */
|
||||
char pattern[64];
|
||||
int n = snprintf(pattern, sizeof(pattern), "\"%s\":\"", key);
|
||||
if (n < 0 || (size_t)n >= sizeof(pattern)) return NULL;
|
||||
|
||||
const char *p = strstr(json, pattern);
|
||||
if (!p) return NULL;
|
||||
|
||||
p += strlen(pattern); /* point to first char of value */
|
||||
/* Find closing quote */
|
||||
const char *end = strchr(p, '"');
|
||||
if (!end) return NULL;
|
||||
|
||||
/* Return the value as a mutable string (caller gets const, but we
|
||||
write a null to mark the end — safe since json is in a writable buf) */
|
||||
/* Cast away const: the caller owns a writable buffer */
|
||||
char *mutable = (char *)end;
|
||||
*mutable = '\0';
|
||||
return p;
|
||||
}
|
||||
|
||||
/* ---------- HTTP handlers ---------- */
|
||||
|
||||
static esp_err_t get_root_handler(httpd_req_t *req)
|
||||
{
|
||||
const size_t html_len = config_page_html_end - config_page_html_start;
|
||||
ESP_LOGI(TAG, "Serving config page (%zu bytes)", html_len);
|
||||
httpd_resp_set_type(req, "text/html");
|
||||
httpd_resp_send(req, (const char *)config_page_html_start, html_len);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
static esp_err_t get_name_handler(httpd_req_t *req)
|
||||
{
|
||||
const char *name = device_name_get();
|
||||
char resp[128];
|
||||
snprintf(resp, sizeof(resp), "{\"name\":\"%s\"}", name);
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_send(req, resp, strlen(resp));
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate suffix: only alphanumeric, dash, and underscore allowed.
|
||||
*/
|
||||
static bool is_valid_suffix(const char *s)
|
||||
{
|
||||
if (!s || *s == '\0') return false;
|
||||
for (; *s; s++) {
|
||||
if (!isalnum((unsigned char)*s) && *s != '-' && *s != '_')
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static esp_err_t post_name_handler(httpd_req_t *req)
|
||||
{
|
||||
char buf[256];
|
||||
int remaining = req->content_len;
|
||||
|
||||
if (remaining >= (int)sizeof(buf)) {
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Payload too large\"}");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int ret = httpd_req_recv(req, buf, remaining);
|
||||
if (ret <= 0) {
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Failed to read body\"}");
|
||||
return ESP_OK;
|
||||
}
|
||||
buf[ret] = '\0';
|
||||
|
||||
/* Parse JSON: {"name":"Velxio-XXXX"} */
|
||||
const char *name_val = json_get_string(buf, "name");
|
||||
if (!name_val) {
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"Missing 'name' field\"}");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
const char *full_name = name_val;
|
||||
|
||||
/* Extract suffix after "Velxio-" prefix */
|
||||
const char *suffix = full_name;
|
||||
if (strncmp(full_name, "Velxio-", 7) == 0) {
|
||||
suffix = full_name + 7;
|
||||
}
|
||||
|
||||
if (!is_valid_suffix(suffix)) {
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_sendstr(req,
|
||||
"{\"success\":false,\"error\":\"Only alphanumeric, dash, underscore allowed\"}");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t err = device_name_set_custom(full_name);
|
||||
|
||||
if (err != ESP_OK) {
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_sendstr(req, "{\"success\":false,\"error\":\"NVS write failed\"}");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
httpd_resp_set_type(req, "application/json");
|
||||
httpd_resp_sendstr(req, "{\"success\":true}");
|
||||
|
||||
ESP_LOGI(TAG, "Name saved, rebooting in 1 second...");
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
esp_restart();
|
||||
|
||||
/* Never reached */
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
/* ---------- Public API ---------- */
|
||||
|
||||
void wifi_ap_start_and_block(void)
|
||||
{
|
||||
ESP_LOGI(TAG, "Starting WiFi SoftAP + HTTP config server...");
|
||||
|
||||
/* Initialise network interface and event loop */
|
||||
ESP_ERROR_CHECK(esp_netif_init());
|
||||
ESP_ERROR_CHECK(esp_event_loop_create_default());
|
||||
esp_netif_create_default_wifi_ap();
|
||||
|
||||
/* Initialise WiFi in AP mode */
|
||||
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
|
||||
ESP_ERROR_CHECK(esp_wifi_init(&cfg));
|
||||
|
||||
/* Build AP SSID from device MAC */
|
||||
char ap_ssid[32] = {0};
|
||||
device_name_get_ap_ssid(ap_ssid, sizeof(ap_ssid));
|
||||
|
||||
/* Configure SoftAP */
|
||||
wifi_config_t wifi_config = {
|
||||
.ap = {
|
||||
.ssid_len = 0,
|
||||
.max_connection = 4,
|
||||
.authmode = WIFI_AUTH_WPA_WPA2_PSK,
|
||||
},
|
||||
};
|
||||
snprintf((char *)wifi_config.ap.ssid, sizeof(wifi_config.ap.ssid), "%s", ap_ssid);
|
||||
snprintf((char *)wifi_config.ap.password, sizeof(wifi_config.ap.password), "%s", AP_PASSWORD);
|
||||
|
||||
ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_AP));
|
||||
ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_AP, &wifi_config));
|
||||
ESP_ERROR_CHECK(esp_wifi_start());
|
||||
|
||||
ESP_LOGI(TAG, "SoftAP started — SSID: %s, Password: %s", ap_ssid, AP_PASSWORD);
|
||||
|
||||
/* Start HTTP config server */
|
||||
httpd_handle_t server = NULL;
|
||||
httpd_config_t httpd_conf = HTTPD_DEFAULT_CONFIG();
|
||||
|
||||
if (httpd_start(&server, &httpd_conf) != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to start HTTP server");
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
||||
/* Register URI handlers */
|
||||
httpd_uri_t uri_get_root = {
|
||||
.uri = "/",
|
||||
.method = HTTP_GET,
|
||||
.handler = get_root_handler,
|
||||
.user_ctx = NULL,
|
||||
};
|
||||
httpd_register_uri_handler(server, &uri_get_root);
|
||||
|
||||
httpd_uri_t uri_get_name = {
|
||||
.uri = "/api/name",
|
||||
.method = HTTP_GET,
|
||||
.handler = get_name_handler,
|
||||
.user_ctx = NULL,
|
||||
};
|
||||
httpd_register_uri_handler(server, &uri_get_name);
|
||||
|
||||
httpd_uri_t uri_post_name = {
|
||||
.uri = "/api/name",
|
||||
.method = HTTP_POST,
|
||||
.handler = post_name_handler,
|
||||
.user_ctx = NULL,
|
||||
};
|
||||
httpd_register_uri_handler(server, &uri_post_name);
|
||||
|
||||
ESP_LOGI(TAG, "HTTP config server running at http://192.168.4.1");
|
||||
|
||||
/* Block forever — this function never returns */
|
||||
while (1) {
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
#pragma once
|
||||
#include <stdbool.h>
|
||||
|
||||
/**
|
||||
* Start WiFi SoftAP + HTTP config server.
|
||||
* Blocks forever (does not return).
|
||||
* AP SSID: "Velxio-Setup-XXXX" (MAC suffix)
|
||||
* AP Password: "velxio123"
|
||||
* Config page: http://192.168.4.1
|
||||
*/
|
||||
void wifi_ap_start_and_block(void) __attribute__((noreturn));
|
||||
|
|
@ -10,13 +10,18 @@ CONFIG_FREERTOS_HZ=100
|
|||
CONFIG_BT_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ENABLED=y
|
||||
CONFIG_BT_NIMBLE_ROLE_PERIPHERAL=y
|
||||
CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="Velxio-Deployer"
|
||||
CONFIG_BT_NIMBLE_SVC_GAP_DEVICE_NAME="Velxio"
|
||||
CONFIG_BT_NIMBLE_MAX_CONNECTIONS=1
|
||||
CONFIG_BT_NIMBLE_MAX_BONDS=1
|
||||
CONFIG_BT_NIMBLE_HS_STOP_TIMEOUT_MS=5000
|
||||
CONFIG_BT_NIMBLE_ATT_PREFERRED_MTU=255
|
||||
CONFIG_BT_NIMBLE_MSYS_1_BLOCK_COUNT=24
|
||||
|
||||
# WiFi (for AP setup mode)
|
||||
CONFIG_ESP_WIFI_ENABLED=y
|
||||
CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y
|
||||
CONFIG_ESP_WIFI_NVS_ENABLED=y
|
||||
|
||||
# PSRAM (octal, N16R8)
|
||||
CONFIG_SPIRAM=y
|
||||
CONFIG_SPIRAM_MODE_OCT=y
|
||||
|
|
|
|||
Loading…
Reference in New Issue