Skip to content

Commit

Permalink
register device while in AP mode
Browse files Browse the repository at this point in the history
  • Loading branch information
tcsullivan committed May 19, 2024
1 parent a106bc7 commit bef1cf2
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 51 deletions.
31 changes: 19 additions & 12 deletions noisemeter-device/access-point.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"<meta charset='utf-8'>" \
"<meta name='viewport' content='width=device-width,initial-scale=1'/>" \
"</head>" \
"<body>"
"<body>" \
"<h1>Noise Meter Setup</h1>"

#define HTML_FOOTER \
"</body>" \
Expand All @@ -40,7 +41,6 @@ const IPAddress AccessPoint::Netmask (255, 255, 255, 0);
// Main webpage HTML with form to collect WiFi credentials.
const char *AccessPoint::htmlSetup =
HTML_HEADER
"<h1>Noise Meter Setup</h1>"
"<form method='POST' action='' enctype='multipart/form-data'>"
"<p>SSID:</p>"
"<input type='text' name='ssid' required>"
Expand All @@ -55,16 +55,20 @@ const char *AccessPoint::htmlSetup =
// HTML to show after credentials are submitted.
const char *AccessPoint::htmlSubmit =
HTML_HEADER
"<h1>Noise Meter Setup</h1>"
"<p>Connecting...</p>"
"<p>Connected and registered! Restarting...</p>"
HTML_FOOTER;

// HTML to show when a submission is rejected.
const char *AccessPoint::htmlSubmitFailed =
HTML_HEADER
"<h1>Noise Meter Setup</h1>"
"<p>Invalid setup form input! Please <a href='/'>go back and try again</a>.</p>"
HTML_FOOTER;
String AccessPoint::htmlFromMsg(const char *msg)
{
String html (HTML_HEADER);
html += "<p>";
html += msg;
html += "</p>";
html += "<p>Please <a href='/'>go back and try again</a>.</p>";
html += HTML_FOOTER;
return html;
}

[[noreturn]]
void AccessPoint::run()
Expand Down Expand Up @@ -99,12 +103,15 @@ bool AccessPoint::handle(WebServer& server, HTTPMethod method, String uri)
server.client().setNoDelay(true);

if (onCredentialsReceived) {
if (onCredentialsReceived(server)) {
auto msg = onCredentialsReceived(server);
if (!msg) {
server.send_P(200, PSTR("text/html"), htmlSubmit);
delay(2000);
delay(3000);
ESP.restart(); // Software reset.
} else {
server.send_P(200, PSTR("text/html"), htmlSubmitFailed);
auto msgStr = htmlFromMsg(*msg);
server.send_P(200, PSTR("text/html"), msgStr.c_str());
WiFi.mode(WIFI_AP);
}
}
} else {
Expand Down
12 changes: 7 additions & 5 deletions noisemeter-device/access-point.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
#include <DNSServer.h>
#include <WebServer.h>

#include <optional>

/**
* @brief Manages the WiFi access point, captive portal, and user setup form.
*
Expand All @@ -45,10 +47,10 @@ class AccessPoint : public RequestHandler

public:
/**
* Submission handler receives WebServer for input data and returns true
* on success, false to reject bad submission.
* Submission handler receives WebServer for input data and returns an
* error message on failure.
*/
using SubmissionHandler = bool (*)(WebServer&);
using SubmissionHandler = std::optional<const char *> (*)(WebServer&);

/**
* Starts the WiFi access point using the fixed credentials.
Expand Down Expand Up @@ -79,8 +81,8 @@ class AccessPoint : public RequestHandler
static const char *htmlSetup;
/** Hard-coded HTML for the page shown after completing the form. */
static const char *htmlSubmit;
/** Hard-coded HTML for the page shown when rejecting a form submission. */
static const char *htmlSubmitFailed;
/** Provides HTML for an error page with the given message. */
static String htmlFromMsg(const char *msg);

/** Determines which HTTP requests should be handled. */
bool canHandle(HTTPMethod, String) override;
Expand Down
66 changes: 34 additions & 32 deletions noisemeter-device/noisemeter-device.ino
Original file line number Diff line number Diff line change
Expand Up @@ -69,11 +69,11 @@ static Timestamp lastOTACheck = Timestamp::invalidTimestamp();
void printReadingToConsole(double reading);

/**
* Callback for AccessPoint that verifies and stores the submitted credentials.
* Callback for AccessPoint that verifies credentials and attempts registration.
* @param httpServer HTTP server which served the setup form
* @return True if successful
* @return An error message if not successful
*/
bool saveNetworkCreds(WebServer& httpServer);
std::optional<const char *> saveNetworkCreds(WebServer& httpServer);

/**
* Generates a UUID that is unique to the hardware running this firmware.
Expand All @@ -83,9 +83,11 @@ UUID buildDeviceId();

/**
* Attempt to establish a WiFi connected using the stored credentials.
* @param mode WiFi mode to run in (e.g. WIFI_STA or WIFI_AP_STA)
* @param timout Connection timeout in milliseconds
* @return Zero on success or a negative number on failure
*/
int tryWifiConnection();
int tryWifiConnection(wifi_mode_t mode = WIFI_STA, int timeout = WIFI_CONNECT_TIMEOUT_MS);

/**
* Firmware entry point and initialization routine.
Expand Down Expand Up @@ -123,6 +125,8 @@ void setup() {
Creds.clear();
SERIAL.println(" done.");

isAPNeeded = true;
} else if (Creds.get(Storage::Entry::Token).length() == 0) {
isAPNeeded = true;
} else if (tryWifiConnection() < 0 || Timestamp::synchronize() < 0) {
isAPNeeded = true;
Expand All @@ -136,25 +140,6 @@ void setup() {
ap.run(); // does not return
}

if (const auto email = Creds.get(Storage::Entry::Email); email.length() > 0) {
API api (buildDeviceId());
const auto registration = api.sendRegister(email);

if (registration) {
Creds.set(Storage::Entry::Email, {});
Creds.set(Storage::Entry::Token, *registration);
Creds.commit();

SERIAL.print("Registered! ");
SERIAL.println(*registration);
} else {
SERIAL.println("Failed to register!");
Creds.clear();
delay(2000);
ESP.restart();
}
}

Timestamp now;
lastUpload = now;
lastOTACheck = now;
Expand Down Expand Up @@ -266,7 +251,8 @@ void printReadingToConsole(double reading) {
SERIAL.print(output);
}

bool saveNetworkCreds(WebServer& httpServer) {
std::optional<const char *> saveNetworkCreds(WebServer& httpServer)
{
// Confirm that the form was actually submitted.
if (httpServer.hasArg("ssid") && httpServer.hasArg("psk")) {
const auto ssid = httpServer.arg("ssid");
Expand All @@ -277,15 +263,29 @@ bool saveNetworkCreds(WebServer& httpServer) {
if (!ssid.isEmpty() && Creds.canStore(ssid) && Creds.canStore(psk) && Creds.canStore(email)) {
Creds.set(Storage::Entry::SSID, ssid);
Creds.set(Storage::Entry::Passkey, psk);
Creds.set(Storage::Entry::Email, email);
Creds.set(Storage::Entry::Token, {});
Creds.commit();

return true;
if (tryWifiConnection(WIFI_AP_STA) == 0 && Timestamp::synchronize() == 0) {
API api (buildDeviceId());
const auto registration = api.sendRegister(email);

if (registration) {
SERIAL.println("Registered!");
Creds.set(Storage::Entry::Token, *registration);
Creds.commit();

return {};
} else {
return "Device registration failed!";
}
} else {
return "Failed to connect to the internet!";
}
}
}

SERIAL.println("Error: Invalid network credentials!");
return false;
return "Invalid network credentials!";
}

UUID buildDeviceId()
Expand All @@ -295,10 +295,12 @@ UUID buildDeviceId()
return UUID(mac[0] | (mac[1] << 8) | (mac[2] << 16), mac[3] | (mac[4] << 8) | (mac[5] << 16));
}

int tryWifiConnection()
int tryWifiConnection(wifi_mode_t mode, int timeout)
{
WiFi.mode(WIFI_STA);
const auto stat = WiFi.begin(Creds.get(Storage::Entry::SSID).c_str(), Creds.get(Storage::Entry::Passkey).c_str());
WiFi.mode(mode);
const auto stat = WiFi.begin(
Creds.get(Storage::Entry::SSID).c_str(),
Creds.get(Storage::Entry::Passkey).c_str());
if (stat == WL_CONNECT_FAILED)
return -1;

Expand All @@ -311,7 +313,7 @@ int tryWifiConnection()
connected = WiFi.status() == WL_CONNECTED;
SERIAL.print(".");
delay(500);
} while (!connected && millis() - start < WIFI_CONNECT_TIMEOUT_MS);
} while (!connected && millis() - start < timeout);

return connected ? 0 : -1;
}
Expand Down
3 changes: 1 addition & 2 deletions noisemeter-device/storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,7 @@ class Storage : protected EEPROMClass
SSID = Checksum + sizeof(uint32_t), /** User's WiFi SSID */
Passkey = SSID + StringSize, /** User's WiFi passkey */
Token = Passkey + StringSize, /** Device API token */
Email = Token + StringSize, /** Temporary storage of user's email */
TotalSize = Email + StringSize /** Marks storage end address */
TotalSize = Token + StringSize /** Marks storage end address */
};

/**
Expand Down

0 comments on commit bef1cf2

Please sign in to comment.