Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add runtime configuration mode using soft ap #6

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@
[submodule "Support/esp-idf"]
path = Support/esp-idf
url = https://github.com/WiseLabCMU/esp-idf.git
[submodule "Source/framework/components/http-parser/http-parser"]
path = Source/framework/components/http-parser/http-parser
url = https://github.com/nodejs/http-parser.git
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,3 +56,16 @@ This small accessory board holds the connector to support CTA2045 and other RS48
```
$ make flash
```

# Runtime Configuration
The WiFi SSID and password can be configured at runtime (without recompiling and reflashing the module). Other parameters could be added as necessary.
Configuration parameters are stored in flash using the [NVS Library](http://esp-idf.readthedocs.io/en/latest/api-reference/storage/nvs_flash.html).
On initialization of the wifi module, if stored wifi configuration parameters exist, they are loaded and used to connect to wifi.
If no parameters exist, the module launches configuration mode.

In configuration mode, the module broadcasts a wifi network named "gridballast". A user can connect to the gridballast network and open
http://[module-ip]/ (by default 192.168.1.4, can be found programatically with `tcpip_adapter_get_ip_info`) in a web browser. The module will present a webpage where the SSID and password can be configured.
To exit configuration mode, the module must be rebooted.

TODO: Gridballast should also support entering configuration mode by pushing a button.
This can be done by calling `wifi_enter_config_mode()` (see `Source/framework/main/include/wifi_module.h`).
3 changes: 3 additions & 0 deletions Source/framework/components/http-parser/component.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
COMPONENT_ADD_INCLUDEDIRS = http-parser
COMPONENT_SRCDIRS = http-parser
COMPONENT_OBJS = http-parser/http_parser.o
1 change: 1 addition & 0 deletions Source/framework/components/http-parser/http-parser
Submodule http-parser added at 54f55a
53 changes: 53 additions & 0 deletions Source/framework/main/config.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/**
* @file config.c
*
* @brief manage config data saved in nonvolatile storage
*
* @author Aaron Perley <[email protected]>
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_log.h"
#include "nvs_flash.h"
#include "nvs.h"
#include "config.h"

static const char *TAG = "config";
static nvs_handle handle = 0;

void config_init() {
esp_err_t err = nvs_flash_init();
if (err == ESP_ERR_NVS_NO_FREE_PAGES) {
// NVS partition was truncated and needs to be erased
ESP_LOGI(TAG, "Erasing and reinitializing NVS partition");
ESP_ERROR_CHECK(nvs_flash_erase());
err = nvs_flash_init();
}
ESP_ERROR_CHECK(err);

ESP_ERROR_CHECK(nvs_open("config", NVS_READWRITE, &handle));
ESP_LOGI(TAG, "NVS handle opened");
}

esp_err_t config_get_wifi(char *ssid, size_t ssid_len, char *password, size_t password_len) {
esp_err_t err = nvs_get_str(handle, "ssid", ssid, &ssid_len);
if (err != ESP_OK) {
return err;
}

err = nvs_get_str(handle, "pass", password, &password_len);
return err;
}

esp_err_t config_set(const char *field, const char *val) {
ESP_LOGI(TAG, "config setting %s = %s", field, val);
return nvs_set_str(handle, field, val);
}

esp_err_t config_commit() {
return nvs_commit(handle);
}
115 changes: 115 additions & 0 deletions Source/framework/main/config_server.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/**
* @file config_server.h
*
* @brief serve configuration webpage
*
* @author Aaron Perley <[email protected]>
*/

#include <string.h>
#include <stdlib.h>
#include "config.h"
#include "esp_log.h"
#include "http_server.h"
#include "http_parser.h"
#include "nvs.h"

static const char *TAG = "config_server";

static const char * const config_html_fmt =
"<!DOCTYPE html>"
"<html>"
"<body>"
"<form action=\"/\" method=\"post\">"
"<label>SSID</label><input type=\"text\" name=\"ssid\" value=\"%s\"><br>"
"<label>Password</label><input type=\"text\" name=\"pass\" value=\"%s\"><br>"
"<input type=\"submit\" value=\"Submit\">"
"</form>"
"</body>"
"</html>";

static void send_html(http_client_conn_t *conn) {
// try to read saved wifi config
char ssid[ssid_maxlen] = "";
char pass[pass_maxlen] = "";
esp_err_t err = config_get_wifi(ssid, ssid_maxlen, pass, pass_maxlen);
if (err != ESP_ERR_NVS_NOT_FOUND) {
ESP_ERROR_CHECK(err);
}

int buf_len = snprintf(NULL, 0, config_html_fmt, ssid, pass);
char *buf = malloc(buf_len + 1);
if (buf == NULL) {
return;
}
snprintf(buf, buf_len + 1, config_html_fmt, ssid, pass);

http_client_send(conn, buf);
free(buf);
}

static void handle_get(http_client_conn_t *conn) {
ESP_LOGI(TAG, "Got GET request");
send_html(conn);
}

static void parse_body(char *body) {
char *field = strtok(body, "=");
int fields_set = 0;
while (field != NULL) {
if (strcmp(field, "ssid") != 0 && strcmp(field, "pass") != 0) {
ESP_LOGE(TAG, "Unknown form field %s", field);
return;
}

// copy from body into field
char *val = strtok(NULL, "&");
if (val == NULL) {
return;
}

// support spaces in value (which get encoded into +s)
for (char *chr = val; *chr != '\0'; chr++) {
if (*chr == '+') {
*chr = ' ';
}
}
ESP_ERROR_CHECK(config_set(field, val));
fields_set++;

// advance to next field
field = strtok(NULL, "=");
}

if (fields_set == 2) {
ESP_ERROR_CHECK(config_commit());
}
}

static void handle_post(http_client_conn_t *conn, char *body) {
ESP_LOGI(TAG, "Got POST request %s", body);
parse_body(body);
send_html(conn);
}

static void req_handler(http_client_conn_t *client_conn, http_req_t *req) {
if (strcmp(req->url, "/") == 0) {
// only serve index route
switch(req->method) {
case HTTP_GET:
handle_get(client_conn);
break;
case HTTP_POST:
handle_post(client_conn, req->body);
break;
default:
ESP_LOGE(TAG, "Unsupported HTTP method %d", req->method);
break;
}
}
}

// Interface functions
void config_server_run() {
http_server_run(80, req_handler);
}
Loading