feat: services #2

Merged
master merged 9 commits from feature/wifiscan into main 2025-09-13 13:45:24 +02:00
10 changed files with 2 additions and 502 deletions
Showing only changes of commit 5d4d68ca2d - Show all commits

View File

@@ -1,2 +0,0 @@
## Build
- use ./ctl build target <platformio env>

4
ctl.sh
View File

@@ -51,8 +51,4 @@ function cluster {
${@:-info}
}
function monitor {
pio run -t monitor
}
${@:-info}

View File

@@ -1,88 +0,0 @@
# WiFi Scanner Example
This example demonstrates how to use the async WiFi scanning functionality in SPORE with periodic scanning via tasks.
## Features
- **Async WiFi Scanning**: Non-blocking WiFi network discovery via NetworkManager
- **Task-based Periodic Scanning**: Automatically scans for networks every minute using TaskManager
- **Event-driven**: Uses the event system to handle scan events
- **REST API**: HTTP endpoints to manually trigger scans and view results
- **Detailed Results**: Shows SSID, RSSI, channel, and encryption type for each network
## Usage
1. Upload this example to your ESP8266 device
2. Open the Serial Monitor at 115200 baud
3. The device will automatically start scanning for WiFi networks every minute
4. Results will be displayed in the Serial Monitor
5. Use the REST API to manually trigger scans or view current results
## Event System
The WiFi scanner uses the following events:
- `wifi/scan/start`: Fired when scan starts (both manual and periodic)
- `wifi/scan/complete`: Fired when scan completes successfully
- `wifi/scan/error`: Fired when scan fails to start
- `wifi/scan/timeout`: Fired when scan times out (10 seconds)
## Task Management
The example registers a periodic task:
- **Task Name**: `wifi_scan_periodic`
- **Interval**: 60 seconds (60000ms)
- **Function**: Fires `wifi/scan/start` event and starts WiFi scan
## REST API
### Manual Scan Control
- **Start Scan**: `POST /api/wifi/scan`
- **Get Status**: `GET /api/wifi/status`
### Example API Usage
```bash
# Start a manual scan
curl -X POST http://192.168.1.50/api/wifi/scan
# Get current scan status and results
curl http://192.168.1.50/api/wifi/status
# Check task status
curl http://192.168.1.50/api/tasks/status
# Disable periodic scanning
curl -X POST http://192.168.1.50/api/tasks/control \
-d task=wifi_scan_periodic -d action=disable
```
## Architecture
- **WiFiScannerService**: Manages periodic scanning and API endpoints
- **NetworkManager**: Handles WiFi scanning operations and callbacks
- **NodeContext**: Stores the WiFi access points data
- **TaskManager**: Schedules periodic scan tasks
- **Event System**: Provides communication between components
## WiFiAccessPoint Structure
```cpp
struct WiFiAccessPoint {
String ssid; // Network name
int32_t rssi; // Signal strength
uint8_t encryption; // Encryption type
uint8_t* bssid; // MAC address
int32_t channel; // WiFi channel
bool isHidden; // Hidden network flag
};
```
## Task Configuration
The periodic scanning task can be controlled via the standard TaskManager API:
- Enable/disable the task
- Change the scan interval
- Monitor task status
- Start/stop the task
This provides flexibility to adjust scanning behavior based on application needs.

View File

@@ -1,183 +0,0 @@
#include <Arduino.h>
#include <functional>
#include "Globals.h"
#include "NodeContext.h"
#include "NetworkManager.h"
#include "ClusterManager.h"
#include "ApiServer.h"
#include "TaskManager.h"
using namespace std;
class WiFiScannerService {
public:
WiFiScannerService(NodeContext& ctx, TaskManager& taskMgr, NetworkManager& networkMgr)
: ctx(ctx), taskManager(taskMgr), network(networkMgr) {
registerTasks();
}
void registerApi(ApiServer& api) {
api.addEndpoint("/api/wifi/scan", HTTP_POST, [this](AsyncWebServerRequest* request) {
if (network.isWiFiScanning()) {
JsonDocument resp;
resp["success"] = false;
resp["message"] = "WiFi scan already in progress";
String json;
serializeJson(resp, json);
request->send(409, "application/json", json);
return;
}
network.startWiFiScan();
JsonDocument resp;
resp["success"] = true;
resp["message"] = "WiFi scan started";
String json;
serializeJson(resp, json);
request->send(200, "application/json", json);
});
api.addEndpoint("/api/wifi/status", HTTP_GET, [this](AsyncWebServerRequest* request) {
JsonDocument doc;
doc["scanning"] = network.isWiFiScanning();
doc["networks_found"] = ctx.wifiAccessPoints->size();
JsonArray networks = doc["networks"].to<JsonArray>();
for (const auto& ap : *ctx.wifiAccessPoints) {
JsonObject network = networks.add<JsonObject>();
network["ssid"] = ap.ssid;
network["rssi"] = ap.rssi;
network["channel"] = ap.channel;
network["encryption"] = ap.encryption;
network["hidden"] = ap.isHidden;
}
String json;
serializeJson(doc, json);
request->send(200, "application/json", json);
});
}
private:
void registerTasks() {
// Register task to start WiFi scan every minute (60000ms)
taskManager.registerTask("wifi_scan_periodic", 60000, [this]() {
if (!network.isWiFiScanning()) {
Serial.println("[WiFiScannerService] Starting periodic WiFi scan...");
ctx.fire("wifi/scan/start", nullptr);
network.startWiFiScan();
} else {
Serial.println("[WiFiScannerService] Skipping scan - already in progress");
}
});
}
NodeContext& ctx;
TaskManager& taskManager;
NetworkManager& network;
};
NodeContext ctx({
{"app", "wifiscan"},
{"role", "demo"}
});
NetworkManager network(ctx);
TaskManager taskManager(ctx);
ClusterManager cluster(ctx, taskManager);
ApiServer apiServer(ctx, taskManager, ctx.config.api_server_port);
WiFiScannerService wifiScanner(ctx, taskManager, network);
// WiFi scan start callback
void onWiFiScanStart(void* data) {
Serial.println("[WiFi] Scan started...");
}
// WiFi scan complete callback
void onWiFiScanComplete(void* data) {
Serial.println("\n=== WiFi Scan Results ===");
std::vector<WiFiAccessPoint>* accessPoints = static_cast<std::vector<WiFiAccessPoint>*>(data);
if (accessPoints->empty()) {
Serial.println("No networks found");
return;
}
Serial.printf("Found %d networks:\n", accessPoints->size());
Serial.println("SSID\t\t\tRSSI\tChannel\tEncryption");
Serial.println("----------------------------------------");
for (const auto& ap : *accessPoints) {
String encryptionStr = "Unknown";
switch (ap.encryption) {
case ENC_TYPE_NONE:
encryptionStr = "Open";
break;
case ENC_TYPE_WEP:
encryptionStr = "WEP";
break;
case ENC_TYPE_TKIP:
encryptionStr = "WPA";
break;
case ENC_TYPE_CCMP:
encryptionStr = "WPA2";
break;
case ENC_TYPE_AUTO:
encryptionStr = "Auto";
break;
}
Serial.printf("%-20s\t%d\t%d\t%s\n",
ap.ssid.c_str(), ap.rssi, ap.channel, encryptionStr.c_str());
}
Serial.println("========================\n");
}
// WiFi scan error callback
void onWiFiScanError(void* data) {
Serial.println("[WiFi] Scan failed - error occurred");
}
// WiFi scan timeout callback
void onWiFiScanTimeout(void* data) {
Serial.println("[WiFi] Scan failed - timeout");
}
void setup() {
Serial.begin(115200);
Serial.println("\n=== WiFi Scanner Example ===");
// Initialize task manager first
taskManager.initialize();
ctx.taskManager = &taskManager;
// Initialize network tasks
network.initTasks();
// Setup WiFi
network.setupWiFi();
// Start the API server
apiServer.begin();
wifiScanner.registerApi(apiServer);
// Register WiFi scan event callbacks
ctx.on("wifi/scan/start", onWiFiScanStart);
ctx.on("wifi/scan/complete", onWiFiScanComplete);
ctx.on("wifi/scan/error", onWiFiScanError);
ctx.on("wifi/scan/timeout", onWiFiScanTimeout);
// Start an initial WiFi scan
Serial.println("Starting initial WiFi scan...");
ctx.fire("wifi/scan/start", nullptr);
network.startWiFiScan();
// Print initial task status
taskManager.printTaskStatus();
}
void loop() {
taskManager.execute();
yield();
}

View File

@@ -7,18 +7,6 @@ public:
NetworkManager(NodeContext& ctx);
void setupWiFi();
void setHostnameFromMac();
void startWiFiScan();
void updateWiFiScan();
bool isWiFiScanning() const;
void processScanResults(int networksFound);
void initTasks(); // Initialize tasks after TaskManager is ready // Public method to process scan results
private:
NodeContext& ctx;
bool wifiScanInProgress;
unsigned long wifiScanStartTime;
static const unsigned long WIFI_SCAN_TIMEOUT_MS = 10000; // 10 seconds timeout
static const char* const WIFI_SCAN_MONITOR_TASK; // Task name for WiFi scan monitoring
void onWiFiScanComplete(int networksFound); // Keep private for internal use
};

View File

@@ -2,33 +2,12 @@
#include <WiFiUdp.h>
#include <map>
#include <vector>
#include "NodeInfo.h"
#include <functional>
#include <string>
#include <initializer_list>
#include "Config.h"
// Forward declaration
class TaskManager;
// WiFi access point structure
struct WiFiAccessPoint {
String ssid;
int32_t rssi;
uint8_t encryption;
uint8_t* bssid;
int32_t channel;
bool isHidden;
WiFiAccessPoint() : rssi(0), encryption(0), bssid(nullptr), channel(0), isHidden(false) {}
~WiFiAccessPoint() {
if (bssid) {
delete[] bssid;
}
}
};
class NodeContext {
public:
NodeContext();
@@ -40,14 +19,10 @@ public:
NodeInfo self;
std::map<String, NodeInfo>* memberList;
Config config;
TaskManager* taskManager;
using EventCallback = std::function<void(void*)>;
std::map<std::string, std::vector<EventCallback>> eventRegistry;
void on(const std::string& event, EventCallback cb);
void fire(const std::string& event, void* data);
// WiFi access points storage
std::vector<WiFiAccessPoint>* wifiAccessPoints;
};

View File

@@ -4,11 +4,9 @@
#include <functional>
#include <string>
#include <map>
#include "NodeContext.h"
#include <ArduinoJson.h>
// Forward declaration
class NodeContext;
// Define our own callback type to avoid conflict with TaskScheduler
using TaskFunction = std::function<void()>;

View File

@@ -124,32 +124,3 @@ build_src_filter =
+<examples/neopattern/main.cpp>
+<src/*.c>
+<src/*.cpp>
[env:esp01_1m_wifiscan]
platform = platformio/espressif8266@^4.2.1
board = esp01_1m
framework = arduino
upload_speed = 115200
monitor_speed = 115200
board_build.partitions = partitions_ota_1M.csv
board_build.flash_mode = dout
board_build.flash_size = 1M
lib_deps = ${common.lib_deps}
build_src_filter =
+<examples/wifiscan/*.cpp>
+<src/*.c>
+<src/*.cpp>
[env:d1_mini_wifiscan]
platform = platformio/espressif8266@^4.2.1
board = d1_mini
framework = arduino
upload_speed = 115200
monitor_speed = 115200
board_build.flash_mode = dio
board_build.flash_size = 4M
lib_deps = ${common.lib_deps}
build_src_filter =
+<examples/wifiscan/*.cpp>
+<src/*.c>
+<src/*.cpp>

View File

@@ -1,29 +1,8 @@
#include "NetworkManager.h"
#include "TaskManager.h"
// SSID and password are now configured via Config class
const char* const NetworkManager::WIFI_SCAN_MONITOR_TASK = "wifi_scan_monitor";
NetworkManager::NetworkManager(NodeContext& ctx) : ctx(ctx), wifiScanInProgress(false), wifiScanStartTime(0) {
// Register scan process callback
ctx.on("wifi/scan/process", [this](void* data) {
int networksFound = reinterpret_cast<intptr_t>(data);
this->processScanResults(networksFound);
});
}
void NetworkManager::initTasks() {
if (!ctx.taskManager) {
Serial.println("[WiFi] Error: TaskManager not initialized");
return;
}
// Register task to check scan status every 100ms (initially disabled)
ctx.taskManager->registerTask(WIFI_SCAN_MONITOR_TASK, 100, [this]() {
this->updateWiFiScan();
}, false); // false = disabled by default
}
NetworkManager::NetworkManager(NodeContext& ctx) : ctx(ctx) {}
void NetworkManager::setHostnameFromMac() {
uint8_t mac[6];
@@ -86,133 +65,3 @@ void NetworkManager::setupWiFi() {
// Notify listeners that the node is (re)discovered
ctx.fire("node_discovered", &ctx.self);
}
void NetworkManager::startWiFiScan() {
if (wifiScanInProgress) {
Serial.println("[WiFi] Scan already in progress");
return;
}
// Check if we're in AP mode only
if (WiFi.getMode() == WIFI_AP) {
// Enable STA mode while keeping AP mode
WiFi.mode(WIFI_AP_STA);
delay(100); // Give some time for mode change
}
// Ensure we have STA mode enabled
if (WiFi.getMode() != WIFI_STA && WiFi.getMode() != WIFI_AP_STA) {
Serial.println("[WiFi] Error: Cannot scan without STA mode enabled");
ctx.fire("wifi/scan/error", nullptr);
return;
}
Serial.println("[WiFi] Starting WiFi scan...");
wifiScanInProgress = true;
wifiScanStartTime = millis();
// Enable the monitoring task if TaskManager is available
if (ctx.taskManager) {
ctx.taskManager->enableTask(WIFI_SCAN_MONITOR_TASK);
}
// Clear previous results safely
if (ctx.wifiAccessPoints) {
ctx.wifiAccessPoints->clear();
}
// Disable interrupts briefly during scan start
noInterrupts();
// Start the scan
WiFi.scanNetworksAsync([this](int networksFound) {
// Schedule callback in main loop
ctx.fire("wifi/scan/process", reinterpret_cast<void*>(networksFound));
}, true); // Show hidden networks
interrupts();
}
void NetworkManager::updateWiFiScan() {
if (!wifiScanInProgress) {
return;
}
// Check for timeout
if (millis() - wifiScanStartTime > WIFI_SCAN_TIMEOUT_MS) {
Serial.println("[WiFi] WiFi scan timeout");
wifiScanInProgress = false;
if (ctx.taskManager) {
ctx.taskManager->disableTask(WIFI_SCAN_MONITOR_TASK);
}
ctx.fire("wifi/scan/timeout", nullptr);
}
}
void NetworkManager::processScanResults(int networksFound) {
// This is called from the main loop context via the event system
if (!wifiScanInProgress) {
return; // Ignore if we're not expecting results
}
wifiScanInProgress = false;
// Disable the monitoring task if TaskManager is available
if (ctx.taskManager) {
ctx.taskManager->disableTask(WIFI_SCAN_MONITOR_TASK);
}
Serial.printf("[WiFi] Processing scan results, found %d networks\n", networksFound);
// Create a temporary vector to hold new results
std::vector<WiFiAccessPoint> newAccessPoints;
if (networksFound > 0) {
newAccessPoints.reserve(networksFound); // Pre-allocate space
// Process each found network
for (int i = 0; i < networksFound; i++) {
WiFiAccessPoint ap;
ap.ssid = WiFi.SSID(i);
ap.rssi = WiFi.RSSI(i);
ap.encryption = WiFi.encryptionType(i);
ap.channel = WiFi.channel(i);
ap.isHidden = WiFi.isHidden(i);
// Copy BSSID - with null check and bounds protection
uint8_t* bssid = WiFi.BSSID(i);
ap.bssid = nullptr; // Initialize to null first
if (bssid != nullptr) {
ap.bssid = new (std::nothrow) uint8_t[6]; // Use nothrow to prevent exceptions
if (ap.bssid != nullptr) {
memcpy(ap.bssid, bssid, 6);
}
}
newAccessPoints.push_back(ap);
Serial.printf("[WiFi] %d: %s (RSSI: %d, Ch: %d, Enc: %d)\n",
i, ap.ssid.c_str(), ap.rssi, ap.channel, ap.encryption);
}
}
// Free the scan results from WiFi to prevent memory leaks
WiFi.scanDelete();
// Safely swap the new results with the stored results
if (ctx.wifiAccessPoints) {
ctx.wifiAccessPoints->swap(newAccessPoints);
// Fire the scan complete event with a copy of the pointer
auto accessPoints = ctx.wifiAccessPoints;
ctx.fire("wifi/scan/complete", accessPoints);
} else {
Serial.println("[WiFi] Error: wifiAccessPoints is null");
ctx.fire("wifi/scan/error", nullptr);
}
}
void NetworkManager::onWiFiScanComplete(int networksFound) {
// Internal callback that schedules processing in the main loop
ctx.fire("wifi/scan/process", reinterpret_cast<void*>(networksFound));
}
bool NetworkManager::isWiFiScanning() const {
return wifiScanInProgress;
}

View File

@@ -1,11 +1,8 @@
#include "NodeContext.h"
#include "TaskManager.h"
NodeContext::NodeContext() {
udp = new WiFiUDP();
memberList = new std::map<String, NodeInfo>();
wifiAccessPoints = new std::vector<WiFiAccessPoint>();
taskManager = nullptr; // Will be set by TaskManager constructor
hostname = "";
self.hostname = "";
self.ip = IPAddress();
@@ -22,7 +19,6 @@ NodeContext::NodeContext(std::initializer_list<std::pair<String, String>> initia
NodeContext::~NodeContext() {
delete udp;
delete memberList;
delete wifiAccessPoints;
}
void NodeContext::on(const std::string& event, EventCallback cb) {