Skip to content

Commit

Permalink
feat(device provisioning): Implement Improv Wi-Fi protocol using seri…
Browse files Browse the repository at this point in the history
…al interface (Wi-Fi config via web installer) (#183)
  • Loading branch information
Slider0007 authored Oct 21, 2024
1 parent 1f68acb commit c1fa372
Show file tree
Hide file tree
Showing 32 changed files with 1,243 additions and 236 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ jobs:
cp -f "./code/.pio/build/${{ matrix.plat }}/firmware.bin" "./artifact/firmware.bin"
cp -f "./code/.pio/build/${{ matrix.plat }}/bootloader.bin" "./artifact/bootloader.bin"
cp -f "./code/.pio/build/${{ matrix.plat }}/partitions.bin" "./artifact/partitions.bin"
cp -f "./docs/Installation/Firmware/${{ matrix.plat }}.md" "./artifact/readme.md"
cp -f "./docs/Installation/DeviceReadme/${{ matrix.plat }}.md" "./artifact/README.md"
cp -r ./html ./artifact/
Expand Down
47 changes: 4 additions & 43 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,53 +61,14 @@ A possibly already available development version (upcoming release version) can

### Option 1: Web Installer (Only For Released Versions)

#### Step 1: Installation Of MCU Part Of Firmware
Follow the instructions listed at [Web Installer](https://slider0007.github.io/AI-on-the-edge-device/) page.<br>
Further details can be found in [Web Installer Provisioning Documentation](docs/Installation/DeviceProvisioning/WebInstaller.md).

Follow the instructions listed at [Web Installer](https://slider0007.github.io/AI-on-the-edge-device/) page:

<img src="images/webinstaller_home.jpg">

#### Step 2: Installation Of SD Card Content

Please follow the instructions in the following chapter ["Installation Of SD Card Content"](#step-2-installation-of-sd-card-content).
<img src="images/webinstaller_home.jpg" width="800">

---
### Option 2: Manual Installation (MCU + SD Card)

#### Step 1: Installation Of MCU Part Of Firmware

Initially the MCU of the device has to be flashed via a USB / serial connection.<br>
Use content of `AI-on-the-edge-device__{Board Type}__*.zip`.

<b>IMPORTANT:</b> Make sure to use correct firmware package for your board type.

There are different ways to flash the microcontroller:
- [Espressif Flash Tool](https://www.espressif.com/sites/default/files/tools/flash_download_tool_3.9.5.zip)<br>
- [ESPtool (command-line tool)](https://docs.espressif.com/projects/esptool/en/latest/esp32/esptool/index.html)

Check readme file in firmware package and [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#manual-flashing) for further details.


#### Step 2: Installation Of SD Card Content
A SD card is mandatory to operate the device because of internal device memory is insufficient to handle all necessary files. Therefore the SD card needs to be preloaded with some file content to be able to operate the device.<br>

⚠️ Make sure, SD card is formated properly (FAT or FAT32 file system).<br>

Use firmware package `AI-on-the-edge-device__{Board Type}__*.zip` for installation process.<br>
⚠️ Do not use github source files, use only release related zip package. Otherwise functionality cannot be fully guaranteed or is limited!<br>

##### Option 1: Manual SD Card Installation
- Copy complete `config` and `html` folder of `AI-on-the-edge-device__{Board Type}__*.zip` to SD card root folder
- Copy file `config/template/config.json` to `config` folder
- Configure WLAN and credentials
- Insert SD-card to device and boot device

##### Option 2: Access Point --> See [documentation](https://jomjol.github.io/AI-on-the-edge-device-docs/Installation/#remote-setup-using-the-built-in-access-point) for details
- Connect to device's WLAN hotspot [http://192.168.4.1](http://192.168.4.1) (Channel 11 | Open network)
- Configure WLAN and credentials
- Upload firmware package (Use `AI-on-the-edge-device__{Board Type}__*.zip`)
- Reboot device

Further details can be found in [Manual Provisioning Documentation](docs/Installation/DeviceProvisioning/Manual.md).

## API Description
### REST API
Expand Down
72 changes: 39 additions & 33 deletions code/components/webserver_softap/softAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,14 +38,13 @@ static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t e

void wifiInitAP(void)
{
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
esp_netif_create_default_wifi_ap();

wifi_init_config_t wifiInitCfg = WIFI_INIT_CONFIG_DEFAULT();
ESP_ERROR_CHECK(esp_wifi_init(&wifiInitCfg));

ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL, NULL));
ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler, NULL));

wifi_config_t wifiConfig = { };
strcpy((char*)wifiConfig.ap.ssid, (const char*) AP_ESP_WIFI_SSID);
Expand All @@ -67,25 +66,34 @@ void wifiInitAP(void)
}


void wifiDeinitAP(void)
{
esp_wifi_disconnect();
esp_wifi_stop();
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &wifi_event_handler);
esp_wifi_deinit();
setStatusLedOff();
credentialsSet = true;
}


esp_err_t main_handler_AP(httpd_req_t *req)
{
std::string message = "<h1>AI-on-the-edge - BASIC SETUP</h1><p>This is an access point to setup ";
message += "the minimum required files and information on the device and the SD-card.<br><br>";
message += "The initial setup is performed in 3 steps:<br>1. Set WLAN credentials<br>";
message += "2. Upload ZIP package to flash SD card content<br>3. Reboot<br>";
std::string message = "<h1>AI on the Edge Device | Device Provisioning</h1>";
message += "<p>Setup the minimum required content on the device and the SD card.<br><br>";
message += "The provisioning is performed into 3 steps:<br>1. Configure WLAN network and set credentials<br>";
message += "2. Upload firmware package<br>3. Install firmware package<br>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));

if (!credentialsSet) {
message = "<h3>1. Set WLAN credentials</h3><p>";
message = "<h3>1. Configure WLAN network and set credentials</h3>";
message += "<table>";
message += "<tr><td>SSID</td><td><input type=\"text\" name=\"ssid\" id=\"ssid\"></td><td> ";
message += "Enter the SSID name of WLAN network</td></tr>";
message += "<tr><td>Password</td><td><input type=\"text\" name=\"password\" id=\"password\"></td><td> ";
message += "Enter the WLAN network password (ATTENTION: The password will be transmitted unencrypted!)</td><tr>";
message += "</table><p>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));

message = "<button class=\"button\" type=\"button\" onclick=\"wr()\">Submit</button>";
message += "<tr><td>Network Name (SSID)</td><td style=\"padding:10px\"><input style=\"width:200px\" type=\"text\" name=\"ssid\" id=\"ssid\"></td>";
message += "<td>Enter the name of WLAN network</td></tr>";
message += "<tr><td>Network Password</td><td style=\"padding:10px\"><input style=\"width:200px\" type=\"text\" name=\"password\" id=\"password\"></td>";
message += "<td>Enter the WLAN network password (NOTE: The password is transmitted to the device as plain text!)</td><tr>";
message += "</table><br><br>";
message += "<button style=\"width:150px; padding:5px\" class=\"button\" type=\"button\" onclick=\"wr()\">Save config</button>";
message += "<script language=\"JavaScript\">async function wr(){";
message += "api = \"/config?\"+\"ssid=\"+document.getElementById(\"ssid\").value+\"&pwd=\"+document.getElementById(\"password\").value;";
message += "fetch(api);await new Promise(resolve => setTimeout(resolve, 1000));location.reload();}</script>";
Expand All @@ -95,38 +103,36 @@ esp_err_t main_handler_AP(httpd_req_t *req)
}

if (!SDCardContentExisting) {
message = "<h3>2. Upload ZIP package to flash SD card content</h3><p>";
message += "After initial flashing of the firmware the the device sd-card is still empty.<br>";
message += "Please upload \"AI-on-the-edge-device__{Board Type}__*.zip\", which installs the SD card content.<p>";
message = "<h3>2. Upload firmware package</h3><p>";
message += "Upload a firmware package \"AI-on-the-edge-device__{Board Type}__*.zip\" to install the SD card content.<p>";
message += "<input id=\"newfile\" type=\"file\"><br><br>";
message += "<button class=\"button\" style=\"width:300px\" id=\"doUpdate\" type=\"button\" onclick=\"upload()\">Upload File</button><p>";
message += "The upload might take up to 60s. After a succesfull upload the page will be reloaded.";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));

message = "<script language=\"JavaScript\">";
message += "<button style=\"width:150px; padding:5px\" class=\"button\" type=\"button\" id=\"doUpdate\" onclick=\"upload()\">Upload File</button><p>";
message += "The upload might take up to 60s. After the package has been successfully uploaded, the page is automatically reloaded.";
message += "<script language=\"JavaScript\">";
message += "function upload() {";
message += "let xhttp = new XMLHttpRequest();";
message += "xhttp.onreadystatechange = function() {if (xhttp.readyState == 4) {if (xhttp.status == 200) {location.reload();}}};";
message += "let filePath = document.getElementById(\"newfile\").value.split(/[\\\\/]/).pop();";
message += "let file = document.getElementById(\"newfile\").files[0];";
message += "if (!file.name.includes(\"AI-on-the-edge-device__\")){if (!confirm(\"The zip file name should contain 'AI-on-the-edge-device__'. ";
message += "Are you sure that you have downloaded the correct file?\"))return;};";
message += "Are you sure that you have chosen the correct file?\"))return;};";
message += "let upload_path = \"/upload/firmware/\" + filePath; xhttp.open(\"POST\", upload_path, true); xhttp.send(file);";
message += "document.getElementById(\"doUpdate\").disabled = true;}";
message += "</script>";
message += "document.getElementById(\"doUpdate\").disabled = true;}</script>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
return ESP_OK;
}

message = "<h3>3. Reboot</h3><p>";
message += "Reboot to proceed the update process.<br>The device is going restart twice ";
message += "and then connect to configured access point.<br>Please find the IP in your router settings and access it with the new IP address.<p>";
message += "The first update and initialization process can take up to 3 minutes.<br>Error logs can be found ";
message += "using UART / serial console.<p>Have fun!<p>";
message += "<button class=\"button\" type=\"button\" onclick=\"rb()\")>Reboot</button>";
message = "<h3>3. Install firmware package</h3><p>";
message += "The firmware package has been successfully uploaded to the device.<br>";
message += "The device is going to reboot and install the provided package. This process can take up to 3 minutes.<br>";
message += "The installation process can be controlled using serial console connection (e.g. via web installer web interface).<br>";
message += "If device is provisioned using web installer, just wait until installation is completed and refresh browser window.<br>";
message += "Switch WLAN network and access the device using device name (default: watermeter) or IP address (check router logs).<br><br>";
message += "<button style=\"width:150px; padding:5px\" class=\"button\" type=\"button\" id=\"doReboot\" onclick=\"rb()\")>Reboot To Proceed</button>";
message += "<script language=\"JavaScript\">async function rb(){";
message += "api = \"/reboot\";";
message += "fetch(api);await new Promise(resolve => setTimeout(resolve, 1000));location.reload();}</script>";
message += "fetch(api);await new Promise(resolve => setTimeout(resolve, 1000));location.reload();";
message += "document.getElementById(\"doReboot\").disabled = true;}</script>";
httpd_resp_send_chunk(req, message.c_str(), strlen(message.c_str()));
httpd_resp_send_chunk(req, NULL, 0);
return ESP_OK;
Expand Down
3 changes: 2 additions & 1 deletion code/components/webserver_softap/softAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@

#include <esp_http_server.h>

void checkStartAPMode();
void checkStartAPMode(void);
void wifiDeinitAP(void);

#endif //SOFTAP_H
#endif //#ifdef ENABLE_SOFTAP
63 changes: 23 additions & 40 deletions code/components/wlan_ctrl/connect_wlan.cpp
Original file line number Diff line number Diff line change
@@ -1,26 +1,16 @@
#include "connect_wlan.h"
#include "../../include/defines.h"

#include <stdlib.h>
#include <fstream>
#include <vector>
#include <sstream>
#include <iostream>

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"

#include "esp_system.h"
#include <esp_wifi.h>
#include "esp_wnm.h"
#include "esp_rrm.h"
#include "esp_mbo.h"
#include "esp_mac.h"
#include "esp_netif.h"
#include <netdb.h>
#include <esp_system.h>
#include <esp_wifi.h>
#include <esp_wnm.h>
#include <esp_rrm.h>
#include <esp_mbo.h>
#include <esp_mac.h>
#include <esp_log.h>
#include "esp_netif_sntp.h"
#include <esp_netif.h>
#include <esp_netif_sntp.h>

#ifdef ENABLE_MQTT
#include "interface_mqtt.h"
Expand Down Expand Up @@ -587,15 +577,9 @@ esp_err_t initWifiStation(void)
{
cfgDataPtr = &ConfigClass::getInstance()->get()->sectionNetwork;

esp_err_t retVal = esp_netif_init();
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_init: Error: " + std::to_string(retVal));
return retVal;
}

retVal = esp_event_loop_create_default();
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_event_loop_create_default: Error: " + std::to_string(retVal));
esp_err_t retVal = esp_event_loop_create_default();
if (retVal != ESP_OK && retVal != ESP_ERR_INVALID_STATE) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_event_loop_create_default: Error: " + intToHexString(retVal));
return retVal;
}

Expand All @@ -606,7 +590,7 @@ esp_err_t initWifiStation(void)

retVal = esp_netif_dhcpc_stop(wifiStation); // Stop DHCP service
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_dhcpc_stop: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_dhcpc_stop: Error: " + intToHexString(retVal));
return retVal;
}

Expand All @@ -624,7 +608,7 @@ esp_err_t initWifiStation(void)

retVal = esp_netif_set_ip_info(wifiStation, &ipInfo);
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_set_ip_info: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_set_ip_info: Error: " + intToHexString(retVal));
return retVal;
}

Expand All @@ -641,7 +625,7 @@ esp_err_t initWifiStation(void)

retVal = esp_netif_set_dns_info(wifiStation, ESP_NETIF_DNS_MAIN, &dnsInfo);
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_set_dns_info: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_set_dns_info: Error: " + intToHexString(retVal));
return retVal;
}
}
Expand All @@ -652,19 +636,19 @@ esp_err_t initWifiStation(void)
wifi_init_config_t wifiInitCfg = WIFI_INIT_CONFIG_DEFAULT();
retVal = esp_wifi_init(&wifiInitCfg);
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_init: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_init: Error: " + intToHexString(retVal));
return retVal;
}

retVal = esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &event_handler, NULL, NULL);
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_event_handler_instance_register - WIFI_ANY: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_event_handler_instance_register - WIFI_ANY: Error: " + intToHexString(retVal));
return retVal;
}

retVal = esp_event_handler_instance_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &event_handler, NULL, NULL);
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_event_handler_instance_register - GOT_IP: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_event_handler_instance_register - GOT_IP: Error: " + intToHexString(retVal));
return retVal;
}

Expand All @@ -691,40 +675,39 @@ esp_err_t initWifiStation(void)
#endif

if (cfgDataPtr->wlan.ssid.empty()) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "SSID empty");
return ESP_ERR_NOT_FOUND;
LogFile.writeToFile(ESP_LOG_WARN, TAG, "SSID empty");
}

strcpy((char*)wifiConfig.sta.ssid, (const char*)cfgDataPtr->wlan.ssid.c_str());
strcpy((char*)wifiConfig.sta.password, (const char*)cfgDataPtr->wlan.password.c_str());

retVal = esp_wifi_set_mode(WIFI_MODE_STA);
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_mode: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_mode: Error: " + intToHexString(retVal));
return retVal;
}

retVal = esp_wifi_set_config(WIFI_IF_STA, &wifiConfig);
if (retVal != ESP_OK) {
if (retVal == ESP_ERR_WIFI_PASSWORD) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_config: Password invalid | Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_config: Password invalid | Error: " + intToHexString(retVal));
}
else {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_config: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_set_config: Error: " + intToHexString(retVal));
}
return retVal;
}

retVal = esp_wifi_start();
if (retVal != ESP_OK) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_start: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_wifi_start: Error: " + intToHexString(retVal));
return retVal;
}

if (!cfgDataPtr->wlan.hostname.empty()) {
retVal = esp_netif_set_hostname(wifiStation, cfgDataPtr->wlan.hostname.c_str());
if(retVal != ESP_OK ) {
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_set_hostname: Error: " + std::to_string(retVal));
LogFile.writeToFile(ESP_LOG_ERROR, TAG, "esp_netif_set_hostname: Error: " + intToHexString(retVal));
}
else {
LogFile.writeToFile(ESP_LOG_INFO, TAG, "Assigned hostname: " + cfgDataPtr->wlan.hostname);
Expand Down
Loading

0 comments on commit c1fa372

Please sign in to comment.