Skip to content

Commit

Permalink
SSID WiFi lookup (#679)
Browse files Browse the repository at this point in the history
* init

* fix: filter out poor networks
  • Loading branch information
WantClue authored Feb 1, 2025
1 parent c98ac81 commit 980d213
Show file tree
Hide file tree
Showing 7 changed files with 214 additions and 6 deletions.
31 changes: 31 additions & 0 deletions components/connect/connect.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
#include "connect.h"
#include "main.h"

// Maximum number of access points to scan
#define MAX_AP_COUNT 20

#if CONFIG_ESP_WPA3_SAE_PWE_HUNT_AND_PECK
#define ESP_WIFI_SAE_MODE WPA3_SAE_PWE_HUNT_AND_PECK
#define EXAMPLE_H2E_IDENTIFIER ""
Expand Down Expand Up @@ -51,6 +54,34 @@ static EventGroupHandle_t s_wifi_event_group;

static const char * TAG = "wifi_station";

// Function to scan for available WiFi networks
esp_err_t wifi_scan(wifi_ap_record_simple_t *ap_records, uint16_t *ap_count) {
wifi_scan_config_t scan_config = {
.ssid = 0,
.bssid = 0,
.channel = 0,
.show_hidden = false
};

// Start WiFi scan
ESP_ERROR_CHECK(esp_wifi_scan_start(&scan_config, true));

// Get scan results
uint16_t number = MAX_AP_COUNT;
wifi_ap_record_t ap_info[MAX_AP_COUNT];
ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&number, ap_info));

// Store results in simplified structure
*ap_count = number;
for (int i = 0; i < number; i++) {
memcpy(ap_records[i].ssid, ap_info[i].ssid, sizeof(ap_records[i].ssid));
ap_records[i].rssi = ap_info[i].rssi;
ap_records[i].authmode = ap_info[i].authmode;
}

return ESP_OK;
}

static int s_retry_num = 0;

static char * _ip_addr_str;
Expand Down
9 changes: 9 additions & 0 deletions components/connect/include/connect.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@
#include <lwip/netdb.h>

#include "freertos/event_groups.h"
#include "esp_wifi_types.h"

// Structure to hold WiFi scan results
typedef struct {
char ssid[33]; // 32 chars + null terminator
int8_t rssi;
wifi_auth_mode_t authmode;
} wifi_ap_record_simple_t;

#define WIFI_SSID CONFIG_ESP_WIFI_SSID
#define WIFI_PASS CONFIG_ESP_WIFI_PASSWORD
Expand Down Expand Up @@ -32,3 +40,4 @@ void wifi_softap_off(void);
void wifi_init(const char * wifi_ssid, const char * wifi_pass, const char * hostname, char * ip_addr_str);
EventBits_t wifi_connect(void);
void generate_ssid(char * ssid);
esp_err_t wifi_scan(wifi_ap_record_simple_t *ap_records, uint16_t *ap_count);
12 changes: 10 additions & 2 deletions main/http_server/axe-os/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ import { HashSuffixPipe } from './pipes/hash-suffix.pipe';
import { PrimeNGModule } from './prime-ng.module';
import { MessageModule } from 'primeng/message';
import { TooltipModule } from 'primeng/tooltip';
import { DialogModule } from 'primeng/dialog';
import { DynamicDialogModule, DialogService as PrimeDialogService } from 'primeng/dynamicdialog';
import { DialogService, DialogListComponent } from './services/dialog.service';

const components = [
AppComponent,
Expand All @@ -52,7 +55,8 @@ const components = [
HashSuffixPipe,
ThemeConfigComponent,
DesignComponent,
PoolComponent
PoolComponent,
DialogListComponent
],
imports: [
BrowserModule,
Expand All @@ -68,10 +72,14 @@ const components = [
PrimeNGModule,
AppLayoutModule,
MessageModule,
TooltipModule
TooltipModule,
DialogModule,
DynamicDialogModule
],
providers: [
{ provide: LocationStrategy, useClass: HashLocationStrategy },
DialogService,
PrimeDialogService
],
bootstrap: [AppComponent]
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@
</div>
<div class="field grid p-fluid">
<label htmlFor="ssid" class="col-12 mb-2 md:col-2 md:mb-0">WiFi SSID:</label>
<div class="col-12 md:col-10">
<div class="col-12 md:col-10 p-inputgroup">
<input pInputText id="ssid" type="text" formControlName="ssid" />
<button pButton type="button" icon="pi pi-search" (click)="scanWifi()" [loading]="scanning"></button>
</div>
</div>
<div class="field grid p-fluid">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
import { HttpErrorResponse } from '@angular/common/http';
import { HttpClient, HttpErrorResponse } from '@angular/common/http';
import { Component, Input, OnInit } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { ToastrService } from 'ngx-toastr';
import { startWith } from 'rxjs';
import { finalize } from 'rxjs/operators';
import { DialogService } from 'src/app/services/dialog.service';
import { LoadingService } from 'src/app/services/loading.service';
import { SystemService } from 'src/app/services/system.service';

interface WifiNetwork {
ssid: string;
rssi: number;
authmode: number;
}

@Component({
selector: 'app-network-edit',
templateUrl: './network.edit.component.html',
Expand All @@ -15,6 +22,7 @@ export class NetworkEditComponent implements OnInit {

public form!: FormGroup;
public savedChanges: boolean = false;
public scanning: boolean = false;

@Input() uri = '';

Expand All @@ -23,7 +31,9 @@ export class NetworkEditComponent implements OnInit {
private systemService: SystemService,
private toastr: ToastrService,
private toastrService: ToastrService,
private loadingService: LoadingService
private loadingService: LoadingService,
private http: HttpClient,
private dialogService: DialogService
) {

}
Expand Down Expand Up @@ -71,6 +81,52 @@ export class NetworkEditComponent implements OnInit {
this.showWifiPassword = !this.showWifiPassword;
}

public scanWifi() {
this.scanning = true;
this.http.get<{networks: WifiNetwork[]}>('/api/system/wifi/scan')
.pipe(
finalize(() => this.scanning = false)
)
.subscribe({
next: (response) => {
// Sort networks by signal strength (highest first)
const networks = response.networks.sort((a, b) => b.rssi - a.rssi);

// filter out poor wifi connections
const poorNetworks = networks.filter(network => network.rssi >= -80);

// Remove duplicate Network Names and show highest signal strength only
const uniqueNetworks = poorNetworks.reduce((acc, network) => {
if (!acc[network.ssid] || acc[network.ssid].rssi < network.rssi) {
acc[network.ssid] = network;
}
return acc;
}, {} as { [key: string]: WifiNetwork });

// Convert the object back to an array
const filteredNetworks = Object.values(uniqueNetworks);

// Create dialog data
const dialogData = filteredNetworks.map(n => ({
label: `${n.ssid} (${n.rssi}dBm)`,
value: n.ssid
}));

// Show dialog with network list
this.dialogService.open('Select WiFi Network', dialogData)
.subscribe((selectedSsid: string) => {
if (selectedSsid) {
this.form.patchValue({ ssid: selectedSsid });
this.form.markAsDirty();
}
});
},
error: (err) => {
this.toastr.error('Failed to scan WiFi networks', 'Error');
}
});
}

public restart() {
this.systemService.restart()
.pipe(this.loadingService.lockUIUntilComplete())
Expand Down
58 changes: 58 additions & 0 deletions main/http_server/axe-os/src/app/services/dialog.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Component, Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs';
import { DialogService as PrimeDialogService, DynamicDialogConfig } from 'primeng/dynamicdialog';

interface DialogOption {
label: string;
value: string;
}

@Injectable({
providedIn: 'root'
})
export class DialogService {
constructor(private primeDialogService: PrimeDialogService) {}

open(title: string, options: DialogOption[]): Observable<string> {
const result = new Subject<string>();

const ref = this.primeDialogService.open(DialogListComponent, {
header: title,
width: '400px',
data: {
options: options,
onSelect: (value: string) => {
result.next(value);
ref.close();
}
}
});

ref.onClose.subscribe(() => {
result.complete();
});

return result.asObservable();
}
}

@Component({
template: `
<style>
::ng-deep .p-button:focus {
box-shadow: none !important;
background-color: var(--primary-color) !important;
}
</style>
<div class="flex flex-column gap-2">
<p-button *ngFor="let option of config.data.options"
[label]="option.label"
(onClick)="config.data.onSelect(option.value)"
styleClass="w-full text-left"
></p-button>
</div>
`
})
export class DialogListComponent {
constructor(public config: DynamicDialogConfig) {}
}
45 changes: 45 additions & 0 deletions main/http_server/http_server.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "global_state.h"
#include "nvs_config.h"
#include "vcore.h"
#include "connect.h"
#include <fcntl.h>
#include <string.h>
#include <sys/param.h>
Expand All @@ -35,6 +36,41 @@
static const char * TAG = "http_server";
static const char * CORS_TAG = "CORS";

/* Handler for WiFi scan endpoint */
static esp_err_t GET_wifi_scan(httpd_req_t *req)
{
httpd_resp_set_type(req, "application/json");

wifi_ap_record_simple_t ap_records[20];
uint16_t ap_count = 0;

esp_err_t err = wifi_scan(ap_records, &ap_count);
if (err != ESP_OK) {
httpd_resp_send_err(req, HTTPD_500_INTERNAL_SERVER_ERROR, "WiFi scan failed");
return ESP_OK;
}

cJSON *root = cJSON_CreateObject();
cJSON *networks = cJSON_CreateArray();

for (int i = 0; i < ap_count; i++) {
cJSON *network = cJSON_CreateObject();
cJSON_AddStringToObject(network, "ssid", (char *)ap_records[i].ssid);
cJSON_AddNumberToObject(network, "rssi", ap_records[i].rssi);
cJSON_AddNumberToObject(network, "authmode", ap_records[i].authmode);
cJSON_AddItemToArray(networks, network);
}

cJSON_AddItemToObject(root, "networks", networks);

const char *response = cJSON_Print(root);
httpd_resp_sendstr(req, response);

free((void *)response);
cJSON_Delete(root);
return ESP_OK;
}

static GlobalState * GLOBAL_STATE;
static httpd_handle_t server = NULL;
QueueHandle_t log_queue = NULL;
Expand Down Expand Up @@ -857,6 +893,15 @@ esp_err_t start_rest_server(void * pvParameters)
};
httpd_register_uri_handler(server, &system_info_get_uri);

/* URI handler for WiFi scan */
httpd_uri_t wifi_scan_get_uri = {
.uri = "/api/system/wifi/scan",
.method = HTTP_GET,
.handler = GET_wifi_scan,
.user_ctx = rest_context
};
httpd_register_uri_handler(server, &wifi_scan_get_uri);

httpd_uri_t swarm_options_uri = {
.uri = "/api/swarm",
.method = HTTP_OPTIONS,
Expand Down

0 comments on commit 980d213

Please sign in to comment.