diff --git a/examples/wifi_over_bridge/wifi_over_bridge.ino b/examples/wifi_over_bridge/wifi_over_bridge.ino new file mode 100644 index 0000000..a49aee1 --- /dev/null +++ b/examples/wifi_over_bridge/wifi_over_bridge.ino @@ -0,0 +1,165 @@ +/* + WiFi Bridge Example + + This example demonstrates how to use the BridgeWiFi class to: + - Scan for available networks + - Connect to a WiFi network + - Display connection information + + Created 2025 +*/ + +#include "Arduino_RouterBridge.h" + +const char* ssid = "YourNetworkName"; +const char* password = "YourPassword"; + +BridgeWiFi wifi(Bridge); + + +void printConnectionInfo() { + Monitor.println("Connection Information:"); + Monitor.println("----------------------"); + + Monitor.print("SSID: "); + Monitor.println(wifi.SSID()); + + Monitor.print("IP Address: "); + Monitor.println(wifi.localIP()); + + Monitor.print("Subnet Mask: "); + Monitor.println(wifi.subnetMask()); + + Monitor.print("Gateway IP: "); + Monitor.println(wifi.gatewayIP()); + + Monitor.print("Signal Strength (RSSI): "); + Monitor.print(wifi.RSSI()); + Monitor.println(" dBm"); + + uint8_t mac[6]; + wifi.macAddress(mac); + Monitor.print("MAC Address: "); + for (int i = 0; i < 6; i++) { + if (mac[i] < 0x10) Monitor.print("0"); + Monitor.print(mac[i], HEX); + if (i < 5) Monitor.print(":"); + } + Monitor.println("\n"); + + uint8_t bssid[6]; + wifi.BSSID(bssid); + Monitor.print("BSSID: "); + for (int i = 0; i < 6; i++) { + if (bssid[i] < 0x10) Monitor.print("0"); + Monitor.print(bssid[i], HEX); + if (i < 5) Monitor.print(":"); + } + Monitor.println("\n"); +} + +void printStatus(uint8_t status) { + switch (status) { + case WL_IDLE_STATUS: + Monitor.println("Idle"); + break; + case WL_NO_SSID_AVAIL: + Monitor.println("No SSID available"); + break; + case WL_SCAN_COMPLETED: + Monitor.println("Scan completed"); + break; + case WL_CONNECTED: + Monitor.println("Connected"); + break; + case WL_CONNECT_FAILED: + Monitor.println("Connection failed"); + break; + case WL_CONNECTION_LOST: + Monitor.println("Connection lost"); + break; + case WL_DISCONNECTED: + Monitor.println("Disconnected"); + break; + case WL_NO_SHIELD: + Monitor.println("No WiFi adapter/shield"); + break; + default: + Monitor.print("Unknown status: "); + Monitor.println(status); + break; + } +} + +void setup() { + + Bridge.begin(); + + Monitor.begin(); + + Monitor.println("\n=== WiFi Bridge Example ===\n"); + + // Scan for available networks + Monitor.println("Scanning for WiFi networks..."); + int8_t numNetworks = wifi.scanNetworks(); + + if (numNetworks == -1) { + Monitor.println("Error: Failed to scan networks"); + } else if (numNetworks == 0) { + Monitor.println("No networks found"); + } else { + Monitor.print("Found "); + Monitor.print(numNetworks); + Monitor.println(" networks:\n"); + + for (int i = 0; i < numNetworks; i++) { + Monitor.print(i + 1); + Monitor.print(": "); + Monitor.println(wifi.SSID(i)); + } + } + + Monitor.println("\n----------------------------\n"); + + // Connect to WiFi + Monitor.print("Connecting to "); + Monitor.print(ssid); + Monitor.println("..."); + + uint8_t status = wifi.begin(ssid, password); + + if (status == WL_CONNECTED) { + Monitor.println("Connected successfully!\n"); + printConnectionInfo(); + } else { + Monitor.print("Connection failed with status: "); + Monitor.println(status); + printStatus(status); + } +} + +void loop() { + // Check connection status every 10 seconds + static unsigned long lastCheck = 0; + unsigned long currentTime = millis(); + + if (currentTime - lastCheck >= 10000) { + lastCheck = currentTime; + + if (wifi.isConnected()) { + Monitor.println("WiFi still connected"); + Monitor.print("Signal strength (RSSI): "); + Monitor.print(wifi.RSSI()); + Monitor.println(" dBm"); + } else { + Monitor.println("WiFi disconnected!"); + Monitor.print("Status: "); + printStatus(wifi.status()); + + // Attempt to reconnect + Monitor.println("Attempting to reconnect..."); + wifi.begin(ssid, password); + } + Monitor.println(); + } +} diff --git a/examples/wifi_test/python/main.py b/examples/wifi_test/python/main.py new file mode 100644 index 0000000..30b1419 --- /dev/null +++ b/examples/wifi_test/python/main.py @@ -0,0 +1,447 @@ +""" +BridgeWiFi Python Server Implementation + +This module provides the server-side RPC methods for testing the BridgeWiFi class. +It simulates WiFi operations including connection, scanning, and network information. +""" + +import time +import random +from arduino.app_utils import * + + +# WiFi status codes (matching Arduino) +WL_NO_SHIELD = 255 +WL_IDLE_STATUS = 0 +WL_NO_SSID_AVAIL = 1 +WL_SCAN_COMPLETED = 2 +WL_CONNECTED = 3 +WL_CONNECT_FAILED = 4 +WL_CONNECTION_LOST = 5 +WL_DISCONNECTED = 6 + +# Global WiFi state +wifi_state = { + 'status': WL_IDLE_STATUS, + 'ssid': '', + 'password': '', + 'connected': False, + 'local_ip': '0.0.0.0', + 'subnet_mask': '0.0.0.0', + 'gateway_ip': '0.0.0.0', + 'mac_address': [0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED], + 'bssid': [0x00, 0x00, 0x00, 0x00, 0x00, 0x00], + 'rssi': 0, + 'last_scan': [] +} + +# Simulated available networks +# Format: (SSID, BSSID, RSSI, encrypted) +SIMULATED_NETWORKS = [ + ("TestNetwork", [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x01], -45, True), + ("OpenNetwork", [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x02], -55, False), + ("Office_WiFi", [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x03], -62, True), + ("Guest_Network", [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x04], -70, True), + ("MyHomeWiFi", [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x05], -48, True), + ("CoffeeShop_Free", [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0x06], -75, False), +] + +# Valid credentials for testing +VALID_CREDENTIALS = { + "TestNetwork": "TestPassword123", + "Office_WiFi": "office2024", + "MyHomeWiFi": "myhome!23", +} + + +def wifi_begin(ssid: str, password: str = ""): + """ + Start WiFi connection + Returns: status code (WL_CONNECTED, WL_CONNECT_FAILED, WL_NO_SSID_AVAIL) + """ + global wifi_state + + print(f"WiFi begin: SSID='{ssid}', Password={'***' if password else '(none)'}") + + # Check if SSID exists in simulated networks + network_found = None + for net in SIMULATED_NETWORKS: + if net[0] == ssid: + network_found = net + break + + if not network_found: + print(f" → SSID '{ssid}' not found") + wifi_state['status'] = WL_NO_SSID_AVAIL + wifi_state['connected'] = False + return WL_NO_SSID_AVAIL + + # Check if network requires password + network_ssid, network_bssid, network_rssi, is_encrypted = network_found + + if is_encrypted: + # Check if password is correct + if ssid in VALID_CREDENTIALS: + if password == VALID_CREDENTIALS[ssid]: + # Successful connection + wifi_state['status'] = WL_CONNECTED + wifi_state['connected'] = True + wifi_state['ssid'] = ssid + wifi_state['password'] = password + wifi_state['bssid'] = network_bssid + wifi_state['rssi'] = network_rssi + + # Assign IP address (simulate DHCP) + wifi_state['local_ip'] = f"192.168.1.{random.randint(100, 200)}" + wifi_state['subnet_mask'] = "255.255.255.0" + wifi_state['gateway_ip'] = "192.168.1.1" + + print(f" → Connected successfully!") + print(f" IP: {wifi_state['local_ip']}") + return WL_CONNECTED + else: + print(f" → Wrong password") + wifi_state['status'] = WL_CONNECT_FAILED + wifi_state['connected'] = False + return WL_CONNECT_FAILED + else: + # Network not in our test set + print(f" → Network requires password but not configured in test") + wifi_state['status'] = WL_CONNECT_FAILED + wifi_state['connected'] = False + return WL_CONNECT_FAILED + else: + # Open network - connect without password + wifi_state['status'] = WL_CONNECTED + wifi_state['connected'] = True + wifi_state['ssid'] = ssid + wifi_state['password'] = "" + wifi_state['bssid'] = network_bssid + wifi_state['rssi'] = network_rssi + + # Assign IP address + wifi_state['local_ip'] = f"192.168.1.{random.randint(100, 200)}" + wifi_state['subnet_mask'] = "255.255.255.0" + wifi_state['gateway_ip'] = "192.168.1.1" + + print(f" → Connected to open network!") + print(f" IP: {wifi_state['local_ip']}") + return WL_CONNECTED + + +def wifi_disconnect(): + """ + Disconnect from WiFi network + Returns: 1 for success, 0 for failure + """ + global wifi_state + + print("WiFi disconnect") + + if wifi_state['connected']: + wifi_state['status'] = WL_DISCONNECTED + wifi_state['connected'] = False + wifi_state['ssid'] = '' + wifi_state['password'] = '' + wifi_state['local_ip'] = '0.0.0.0' + wifi_state['subnet_mask'] = '0.0.0.0' + wifi_state['gateway_ip'] = '0.0.0.0' + wifi_state['bssid'] = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + wifi_state['rssi'] = 0 + + print(" → Disconnected successfully") + return 1 + else: + print(" → Already disconnected") + return 1 + + +def wifi_status(): + """ + Get current WiFi status + Returns: status code + """ + status = wifi_state['status'] + status_names = { + WL_NO_SHIELD: "WL_NO_SHIELD", + WL_IDLE_STATUS: "WL_IDLE_STATUS", + WL_NO_SSID_AVAIL: "WL_NO_SSID_AVAIL", + WL_SCAN_COMPLETED: "WL_SCAN_COMPLETED", + WL_CONNECTED: "WL_CONNECTED", + WL_CONNECT_FAILED: "WL_CONNECT_FAILED", + WL_CONNECTION_LOST: "WL_CONNECTION_LOST", + WL_DISCONNECTED: "WL_DISCONNECTED", + } + + print(f"WiFi status: {status} ({status_names.get(status, 'UNKNOWN')})") + return status + + +def wifi_scan(): + """ + Scan for available WiFi networks + Returns: number of networks found + """ + global wifi_state + + print("WiFi scan started...") + + # Simulate scan delay + time.sleep(0.5) + + # Store scan results + wifi_state['last_scan'] = SIMULATED_NETWORKS.copy() + + # Add some random variation to RSSI + varied_networks = [] + for ssid, bssid, rssi, encrypted in wifi_state['last_scan']: + varied_rssi = rssi + random.randint(-3, 3) + varied_networks.append((ssid, bssid, varied_rssi, encrypted)) + wifi_state['last_scan'] = varied_networks + + num_networks = len(wifi_state['last_scan']) + + print(f" → Found {num_networks} networks:") + for i, (ssid, bssid, rssi, encrypted) in enumerate(wifi_state['last_scan']): + security = "WPA2" if encrypted else "Open" + print(f" {i}: {ssid} ({rssi} dBm, {security})") + + return num_networks + + +def wifi_ssid_by_index(network_item: int): + """ + Get SSID of scanned network by index + Returns: SSID string + """ + if 'last_scan' not in wifi_state or len(wifi_state['last_scan']) == 0: + print(f"WiFi SSID[{network_item}]: No scan results") + return "" + + if network_item < 0 or network_item >= len(wifi_state['last_scan']): + print(f"WiFi SSID[{network_item}]: Index out of range") + return "" + + ssid = wifi_state['last_scan'][network_item][0] + print(f"WiFi SSID[{network_item}]: {ssid}") + return ssid + + +def wifi_bssid(): + """ + Get BSSID of currently connected network + Returns: array of 6 bytes + """ + if not wifi_state['connected']: + print("WiFi BSSID: Not connected") + return [0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + + bssid = wifi_state['bssid'] + bssid_str = ":".join([f"{b:02X}" for b in bssid]) + print(f"WiFi BSSID: {bssid_str}") + return bssid + + +def wifi_rssi(): + """ + Get RSSI (signal strength) of currently connected network + Returns: RSSI in dBm (negative value) + """ + if not wifi_state['connected']: + print("WiFi RSSI: Not connected") + return 0 + + # Add slight variation to simulate real conditions + rssi = wifi_state['rssi'] + random.randint(-2, 2) + print(f"WiFi RSSI: {rssi} dBm") + return rssi + + +def wifi_local_ip(): + """ + Get local IP address + Returns: IP address string + """ + ip = wifi_state['local_ip'] + print(f"WiFi Local IP: {ip}") + return ip + + +def wifi_subnet_mask(): + """ + Get subnet mask + Returns: subnet mask string + """ + mask = wifi_state['subnet_mask'] + print(f"WiFi Subnet Mask: {mask}") + return mask + + +def wifi_gateway_ip(): + """ + Get gateway IP address + Returns: gateway IP string + """ + gateway = wifi_state['gateway_ip'] + print(f"WiFi Gateway IP: {gateway}") + return gateway + + +def wifi_mac_address(): + """ + Get MAC address of WiFi interface + Returns: array of 6 bytes + """ + mac = wifi_state['mac_address'] + mac_str = ":".join([f"{b:02X}" for b in mac]) + print(f"WiFi MAC Address: {mac_str}") + return mac + + +# Test helper functions +def add_test_network(ssid: str, password: str = None, rssi: int = -50): + """ + Add a custom network for testing + """ + global SIMULATED_NETWORKS, VALID_CREDENTIALS + + bssid = [0xAA, 0xBB, 0xCC, 0xDD, 0xEE, random.randint(0x10, 0xFF)] + encrypted = password is not None + + SIMULATED_NETWORKS.append((ssid, bssid, rssi, encrypted)) + + if password: + VALID_CREDENTIALS[ssid] = password + + print(f"Added test network: {ssid} ({'encrypted' if encrypted else 'open'})") + + +def remove_test_network(ssid: str): + """ + Remove a test network + """ + global SIMULATED_NETWORKS, VALID_CREDENTIALS + + SIMULATED_NETWORKS = [net for net in SIMULATED_NETWORKS if net[0] != ssid] + + if ssid in VALID_CREDENTIALS: + del VALID_CREDENTIALS[ssid] + + print(f"Removed test network: {ssid}") + + +def simulate_connection_loss(): + """ + Simulate losing WiFi connection + """ + global wifi_state + + if wifi_state['connected']: + print("Simulating connection loss...") + wifi_state['status'] = WL_CONNECTION_LOST + wifi_state['connected'] = False + wifi_state['local_ip'] = '0.0.0.0' + print(" → Connection lost!") + + +def set_signal_strength(rssi: int): + """ + Set the RSSI of current connection + """ + global wifi_state + + if wifi_state['connected']: + wifi_state['rssi'] = rssi + print(f"Set RSSI to {rssi} dBm") + else: + print("Cannot set RSSI - not connected") + + +def get_wifi_info(): + """ + Print complete WiFi state information + """ + print("\n" + "=" * 50) + print("WiFi State Information") + print("=" * 50) + print(f"Status: {wifi_state['status']}") + print(f"Connected: {wifi_state['connected']}") + print(f"SSID: {wifi_state['ssid']}") + print(f"Local IP: {wifi_state['local_ip']}") + print(f"Subnet Mask: {wifi_state['subnet_mask']}") + print(f"Gateway: {wifi_state['gateway_ip']}") + + mac_str = ":".join([f"{b:02X}" for b in wifi_state['mac_address']]) + print(f"MAC Address: {mac_str}") + + if wifi_state['connected']: + bssid_str = ":".join([f"{b:02X}" for b in wifi_state['bssid']]) + print(f"BSSID: {bssid_str}") + print(f"RSSI: {wifi_state['rssi']} dBm") + + print("=" * 50 + "\n") + + +def list_networks(): + """ + List all simulated networks + """ + print("\n" + "=" * 50) + print("Available Networks") + print("=" * 50) + + for i, (ssid, bssid, rssi, encrypted) in enumerate(SIMULATED_NETWORKS): + security = "WPA2" if encrypted else "Open" + password_info = "" + if encrypted and ssid in VALID_CREDENTIALS: + password_info = f" (Password: {VALID_CREDENTIALS[ssid]})" + + print(f"{i}: {ssid:20} {rssi:4} dBm {security:8}{password_info}") + + print("=" * 50 + "\n") + + +if __name__ == "__main__": + # Register RPC methods + Bridge.provide("wifi/begin", wifi_begin) + Bridge.provide("wifi/disconnect", wifi_disconnect) + Bridge.provide("wifi/status", wifi_status) + Bridge.provide("wifi/scan", wifi_scan) + Bridge.provide("wifi/SSID", wifi_ssid_by_index) + Bridge.provide("wifi/BSSID", wifi_bssid) + Bridge.provide("wifi/RSSI", wifi_rssi) + Bridge.provide("wifi/localIP", wifi_local_ip) + Bridge.provide("wifi/subnetMask", wifi_subnet_mask) + Bridge.provide("wifi/gatewayIP", wifi_gateway_ip) + Bridge.provide("wifi/macAddress", wifi_mac_address) + + print("=" * 60) + print("WiFi Bridge Server ready") + print("=" * 60) + print("\nAvailable RPC methods:") + print(" - wifi/begin") + print(" - wifi/disconnect") + print(" - wifi/status") + print(" - wifi/scan") + print(" - wifi/SSID") + print(" - wifi/BSSID") + print(" - wifi/RSSI") + print(" - wifi/localIP") + print(" - wifi/subnetMask") + print(" - wifi/gatewayIP") + print(" - wifi/macAddress") + print() + + # List available test networks + list_networks() + + print("Helper functions available:") + print(" - add_test_network(ssid, password, rssi)") + print(" - remove_test_network(ssid)") + print(" - simulate_connection_loss()") + print(" - set_signal_strength(rssi)") + print(" - get_wifi_info()") + print(" - list_networks()") + print() + + App.run() diff --git a/examples/wifi_test/wifi_test.ino b/examples/wifi_test/wifi_test.ino new file mode 100644 index 0000000..c970b7c --- /dev/null +++ b/examples/wifi_test/wifi_test.ino @@ -0,0 +1,607 @@ +/* + * BridgeWiFi Test Suite + * + * This test suite validates the BridgeWiFi class functionality + * including connection, scanning, status checking, and network information. + */ + +#include +#include "Arduino_RouterBridge.h" + + +// Test configuration +#define TEST_SSID "TestNetwork" +#define TEST_PASSWORD "TestPassword123" +#define TEST_SSID_OPEN "OpenNetwork" +#define TEST_TIMEOUT 30000 // 30 seconds for connection attempts + +// Test results tracking +struct TestResults { + int passed = 0; + int failed = 0; + int total = 0; +}; + +TestResults results; + +// Helper macros +#define TEST_ASSERT(condition, test_name) \ + do { \ + results.total++; \ + if (condition) { \ + Monitor.print("✓ PASS: "); \ + Monitor.println(test_name); \ + results.passed++; \ + } else { \ + Monitor.print("✗ FAIL: "); \ + Monitor.println(test_name); \ + results.failed++; \ + } \ + } while(0) + +#define TEST_START(name) \ + Monitor.println(""); \ + Monitor.println("=========================================="); \ + Monitor.print("TEST: "); \ + Monitor.println(name); \ + Monitor.println("==========================================") + +// Global wifi object +BridgeWiFi wifi(Bridge); + +void printTestSummary() { + Monitor.println(""); + Monitor.println("=========================================="); + Monitor.println("TEST SUMMARY"); + Monitor.println("=========================================="); + Monitor.print("Total Tests: "); Monitor.println(results.total); + Monitor.print("Passed: "); Monitor.println(results.passed); + Monitor.print("Failed: "); Monitor.println(results.failed); + Monitor.print("Success Rate: "); + if (results.total > 0) { + Monitor.print((results.passed * 100) / results.total); + } else { + Monitor.print("0"); + } + Monitor.println("%"); + Monitor.println("=========================================="); +} + +void printWiFiStatus(uint8_t status) { + Monitor.print("WiFi Status: "); + switch (status) { + case WL_NO_SHIELD: + Monitor.println("WL_NO_SHIELD (255) - No WiFi shield"); + break; + case WL_IDLE_STATUS: + Monitor.println("WL_IDLE_STATUS (0) - Idle"); + break; + case WL_NO_SSID_AVAIL: + Monitor.println("WL_NO_SSID_AVAIL (1) - SSID not available"); + break; + case WL_SCAN_COMPLETED: + Monitor.println("WL_SCAN_COMPLETED (2) - Scan completed"); + break; + case WL_CONNECTED: + Monitor.println("WL_CONNECTED (3) - Connected"); + break; + case WL_CONNECT_FAILED: + Monitor.println("WL_CONNECT_FAILED (4) - Connection failed"); + break; + case WL_CONNECTION_LOST: + Monitor.println("WL_CONNECTION_LOST (5) - Connection lost"); + break; + case WL_DISCONNECTED: + Monitor.println("WL_DISCONNECTED (6) - Disconnected"); + break; + default: + Monitor.print("UNKNOWN ("); Monitor.print(status); Monitor.println(")"); + break; + } +} + +void test_initial_status() { + TEST_START("Initial WiFi Status"); + + uint8_t status = wifi.status(); + printWiFiStatus(status); + + TEST_ASSERT(status == WL_IDLE_STATUS || status == WL_DISCONNECTED, + "Initial status should be IDLE or DISCONNECTED"); + TEST_ASSERT(!wifi.isConnected(), "Should not be connected initially"); + + delay(100); +} + +void test_begin_with_password() { + TEST_START("WiFi Begin with Password"); + + Monitor.print("Attempting to connect to: "); + Monitor.println(TEST_SSID); + + uint8_t result = wifi.begin(TEST_SSID, TEST_PASSWORD); + printWiFiStatus(result); + + TEST_ASSERT(result == WL_CONNECTED || result == WL_CONNECT_FAILED || result == WL_NO_SSID_AVAIL, + "begin should return valid status code"); + + // Wait for connection if connecting + if (result == WL_IDLE_STATUS) { + unsigned long startTime = millis(); + while (millis() - startTime < TEST_TIMEOUT) { + uint8_t status = wifi.status(); + if (status == WL_CONNECTED) { + Monitor.println("Connection successful!"); + break; + } else if (status == WL_CONNECT_FAILED || status == WL_NO_SSID_AVAIL) { + Monitor.println("Connection failed"); + break; + } + delay(500); + } + } + + if (wifi.isConnected()) { + Monitor.println("✓ Successfully connected to WiFi"); + } else { + Monitor.println("⚠ Could not connect (network may not exist)"); + } + + delay(100); +} + +void test_begin_open_network() { + TEST_START("WiFi Begin Open Network"); + + // First disconnect if connected + if (wifi.isConnected()) { + wifi.disconnect(); + delay(1000); + } + + Monitor.print("Attempting to connect to open network: "); + Monitor.println(TEST_SSID_OPEN); + + uint8_t result = wifi.begin(TEST_SSID_OPEN); + printWiFiStatus(result); + + TEST_ASSERT(result == WL_CONNECTED || result == WL_CONNECT_FAILED || result == WL_NO_SSID_AVAIL, + "begin without password should return valid status code"); + + delay(100); +} + +void test_disconnect() { + TEST_START("WiFi Disconnect"); + + // Ensure we're connected first + if (!wifi.isConnected()) { + wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(2000); + } + + bool wasConnected = wifi.isConnected(); + uint8_t result = wifi.disconnect(); + + printWiFiStatus(result); + + if (wasConnected) { + TEST_ASSERT(result == WL_DISCONNECTED, "disconnect should return WL_DISCONNECTED"); + TEST_ASSERT(!wifi.isConnected(), "Should not be connected after disconnect"); + } else { + TEST_ASSERT(true, "Disconnect called (was not connected)"); + } + + delay(100); +} + +void test_status_check() { + TEST_START("WiFi Status Check"); + + uint8_t status = wifi.status(); + printWiFiStatus(status); + + TEST_ASSERT(status >= 0 && status <= 255, "Status should be valid"); + + // Check consistency with isConnected + bool connected = wifi.isConnected(); + if (status == WL_CONNECTED) { + TEST_ASSERT(connected, "isConnected should be true when status is WL_CONNECTED"); + } else { + TEST_ASSERT(!connected, "isConnected should be false when status is not WL_CONNECTED"); + } + + delay(100); +} + +void test_scan_networks() { + TEST_START("WiFi Scan Networks"); + + Monitor.println("Scanning for networks..."); + int8_t numNetworks = wifi.scanNetworks(); + + Monitor.print("Networks found: "); + Monitor.println(numNetworks); + + TEST_ASSERT(numNetworks >= -1 && numNetworks <= 127, + "scanNetworks should return valid count (-1 to 127)"); + + if (numNetworks > 0) { + Monitor.println("✓ Found networks"); + TEST_ASSERT(true, "Network scan completed successfully"); + } else if (numNetworks == 0) { + Monitor.println("⚠ No networks found"); + TEST_ASSERT(true, "Scan completed but no networks found"); + } else { + Monitor.println("✗ Scan failed"); + } + + delay(100); +} + +void test_ssid_from_scan() { + TEST_START("WiFi SSID from Scan"); + + int8_t numNetworks = wifi.scanNetworks(); + + if (numNetworks > 0) { + Monitor.println("\nScanned Networks:"); + Monitor.println("------------------"); + + for (int i = 0; i < min(numNetworks, 5); i++) { + String ssid = wifi.SSID(i); + Monitor.print(i); + Monitor.print(": "); + Monitor.println(ssid); + + TEST_ASSERT(ssid.length() >= 0, "SSID should be retrievable"); + } + + // Test first network SSID + String firstSSID = wifi.SSID(0); + TEST_ASSERT(firstSSID.length() > 0, "First network should have valid SSID"); + } else { + Monitor.println("⚠ No networks to test SSID retrieval"); + TEST_ASSERT(true, "Skipped - no networks found"); + } + + delay(100); +} + +void test_connected_ssid() { + TEST_START("WiFi Connected SSID"); + + // Ensure connected + if (!wifi.isConnected()) { + wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(3000); + } + + String ssid = wifi.SSID(); + Monitor.print("Current SSID: "); + Monitor.println(ssid); + + if (wifi.isConnected()) { + TEST_ASSERT(ssid.length() > 0, "Connected SSID should not be empty"); + TEST_ASSERT(ssid == TEST_SSID, "SSID should match connected network"); + } else { + TEST_ASSERT(ssid.length() == 0, "SSID should be empty when not connected"); + } + + delay(100); +} + +void test_bssid() { + TEST_START("WiFi BSSID"); + + // Ensure connected + if (!wifi.isConnected()) { + wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(3000); + } + + uint8_t bssid[6]; + wifi.BSSID(bssid); + + Monitor.print("BSSID: "); + for (int i = 0; i < 6; i++) { + if (bssid[i] < 16) Monitor.print("0"); + Monitor.print(bssid[i], HEX); + if (i < 5) Monitor.print(":"); + } + Monitor.println(); + + if (wifi.isConnected()) { + bool hasValidBSSID = false; + for (int i = 0; i < 6; i++) { + if (bssid[i] != 0) { + hasValidBSSID = true; + break; + } + } + TEST_ASSERT(hasValidBSSID, "BSSID should be valid when connected"); + } else { + TEST_ASSERT(true, "Skipped - not connected"); + } + + delay(100); +} + +void test_rssi() { + TEST_START("WiFi RSSI"); + + // Ensure connected + if (!wifi.isConnected()) { + wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(3000); + } + + int32_t rssi = wifi.RSSI(); + Monitor.print("RSSI: "); + Monitor.print(rssi); + Monitor.println(" dBm"); + + if (wifi.isConnected()) { + TEST_ASSERT(rssi < 0 && rssi >= -100, "RSSI should be between -100 and 0 dBm"); + + // Quality indicator + if (rssi > -50) { + Monitor.println("Signal: Excellent"); + } else if (rssi > -60) { + Monitor.println("Signal: Good"); + } else if (rssi > -70) { + Monitor.println("Signal: Fair"); + } else { + Monitor.println("Signal: Weak"); + } + } else { + TEST_ASSERT(true, "Skipped - not connected"); + } + + delay(100); +} + +void test_local_ip() { + TEST_START("WiFi Local IP"); + + // Ensure connected + if (!wifi.isConnected()) { + wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(3000); + } + + IPAddress ip = wifi.localIP(); + Monitor.print("Local IP: "); + Monitor.println(ip); + + if (wifi.isConnected()) { + TEST_ASSERT(ip != IPAddress(0, 0, 0, 0), "Local IP should be valid when connected"); + TEST_ASSERT(ip[0] != 0, "IP should have valid first octet"); + } else { + TEST_ASSERT(ip == IPAddress(0, 0, 0, 0), "Local IP should be 0.0.0.0 when not connected"); + } + + delay(100); +} + +void test_subnet_mask() { + TEST_START("WiFi Subnet Mask"); + + // Ensure connected + if (!wifi.isConnected()) { + wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(3000); + } + + IPAddress mask = wifi.subnetMask(); + Monitor.print("Subnet Mask: "); + Monitor.println(mask); + + if (wifi.isConnected()) { + TEST_ASSERT(mask != IPAddress(0, 0, 0, 0), "Subnet mask should be valid when connected"); + // Common subnet masks start with 255 + TEST_ASSERT(mask[0] == 255, "Subnet mask should start with 255"); + } else { + TEST_ASSERT(true, "Skipped - not connected"); + } + + delay(100); +} + +void test_gateway_ip() { + TEST_START("WiFi Gateway IP"); + + // Ensure connected + if (!wifi.isConnected()) { + wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(3000); + } + + IPAddress gateway = wifi.gatewayIP(); + Monitor.print("Gateway IP: "); + Monitor.println(gateway); + + if (wifi.isConnected()) { + TEST_ASSERT(gateway != IPAddress(0, 0, 0, 0), "Gateway IP should be valid when connected"); + TEST_ASSERT(gateway[0] != 0, "Gateway should have valid first octet"); + } else { + TEST_ASSERT(true, "Skipped - not connected"); + } + + delay(100); +} + +void test_mac_address() { + TEST_START("WiFi MAC Address"); + + uint8_t mac[6]; + wifi.macAddress(mac); + + Monitor.print("MAC Address: "); + for (int i = 0; i < 6; i++) { + if (mac[i] < 16) Monitor.print("0"); + Monitor.print(mac[i], HEX); + if (i < 5) Monitor.print(":"); + } + Monitor.println(); + + // MAC address should not be all zeros + bool hasValidMAC = false; + for (int i = 0; i < 6; i++) { + if (mac[i] != 0) { + hasValidMAC = true; + break; + } + } + + TEST_ASSERT(hasValidMAC, "MAC address should be valid (not all zeros)"); + + delay(100); +} + +void test_connection_cycle() { + TEST_START("WiFi Connection Cycle"); + + // Disconnect if connected + if (wifi.isConnected()) { + wifi.disconnect(); + delay(1000); + } + + TEST_ASSERT(!wifi.isConnected(), "Should be disconnected at start"); + + // Connect + uint8_t result = wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(3000); + + if (wifi.isConnected()) { + TEST_ASSERT(wifi.status() == WL_CONNECTED, "Status should be WL_CONNECTED"); + + // Get network info + IPAddress ip = wifi.localIP(); + String ssid = wifi.SSID(); + + TEST_ASSERT(ip != IPAddress(0, 0, 0, 0), "Should have valid IP when connected"); + TEST_ASSERT(ssid.length() > 0, "Should have valid SSID when connected"); + + // Disconnect + wifi.disconnect(); + delay(1000); + + TEST_ASSERT(!wifi.isConnected(), "Should be disconnected after disconnect"); + TEST_ASSERT(wifi.status() == WL_DISCONNECTED, "Status should be WL_DISCONNECTED"); + } else { + Monitor.println("⚠ Could not connect (network may not exist)"); + TEST_ASSERT(true, "Connection cycle test skipped"); + } + + delay(100); +} + +void test_multiple_status_calls() { + TEST_START("WiFi Multiple Status Calls"); + + uint8_t status1 = wifi.status(); + delay(100); + uint8_t status2 = wifi.status(); + delay(100); + uint8_t status3 = wifi.status(); + + Monitor.print("Status calls: "); + Monitor.print(status1); + Monitor.print(", "); + Monitor.print(status2); + Monitor.print(", "); + Monitor.println(status3); + + // Status should be consistent (unless network conditions changed) + TEST_ASSERT(true, "Multiple status calls completed"); + + delay(100); +} + +void test_network_info_consistency() { + TEST_START("WiFi Network Info Consistency"); + + if (!wifi.isConnected()) { + wifi.begin(TEST_SSID, TEST_PASSWORD); + delay(3000); + } + + if (wifi.isConnected()) { + // Get info multiple times + IPAddress ip1 = wifi.localIP(); + delay(100); + IPAddress ip2 = wifi.localIP(); + + String ssid1 = wifi.SSID(); + delay(100); + String ssid2 = wifi.SSID(); + + TEST_ASSERT(ip1 == ip2, "Local IP should be consistent"); + TEST_ASSERT(ssid1 == ssid2, "SSID should be consistent"); + + Monitor.print("IP: "); Monitor.println(ip1); + Monitor.print("SSID: "); Monitor.println(ssid1); + } else { + Monitor.println("⚠ Not connected - skipping consistency test"); + TEST_ASSERT(true, "Test skipped - not connected"); + } + + delay(100); +} + +void setup() { + + Bridge.begin(); + + Monitor.begin(); + + Monitor.println(""); + Monitor.println("=========================================="); + Monitor.println("BridgeWiFi Test Suite"); + Monitor.println("=========================================="); + Monitor.print("Test SSID: "); Monitor.println(TEST_SSID); + Monitor.print("Test Password: "); Monitor.println(TEST_PASSWORD); + Monitor.println("=========================================="); + + Monitor.println("Bridge initialized successfully"); + + Monitor.println("Waiting 5s for the other side"); + delay(5000); + + // Run all tests + test_initial_status(); + test_begin_open_network(); + test_status_check(); + test_disconnect(); + test_begin_with_password(); + test_multiple_status_calls(); + + // Network scanning tests + test_scan_networks(); + test_ssid_from_scan(); + + // Connection info tests (require connection) + test_connected_ssid(); + test_bssid(); + test_rssi(); + test_local_ip(); + test_subnet_mask(); + test_gateway_ip(); + test_mac_address(); + + // Advanced tests + test_connection_cycle(); + test_network_info_consistency(); + + // Print summary + printTestSummary(); + + Monitor.println("\n✓ Test suite completed!"); +} + +void loop() { + // Test suite runs once in setup() + delay(1000); +} \ No newline at end of file diff --git a/src/Arduino_RouterBridge.h b/src/Arduino_RouterBridge.h index e2dcbf7..4139238 100644 --- a/src/Arduino_RouterBridge.h +++ b/src/Arduino_RouterBridge.h @@ -18,5 +18,6 @@ #include "tcp_client.h" #include "tcp_server.h" #include "hci.h" +#include "wifi_bridge.h" #endif //ARDUINO_ROUTER_BRIDGE_H diff --git a/src/wifi_bridge.h b/src/wifi_bridge.h new file mode 100644 index 0000000..3c9c22d --- /dev/null +++ b/src/wifi_bridge.h @@ -0,0 +1,355 @@ +/* + This file is part of the Arduino_RouterBridge library. + + Copyright (c) 2025 Arduino SA + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +*/ + +#pragma once + +#ifndef WIFI_BRIDGE_H +#define WIFI_BRIDGE_H + +#define WIFI_BEGIN_METHOD "wifi/begin" +#define WIFI_DISCONNECT_METHOD "wifi/disconnect" +#define WIFI_STATUS_METHOD "wifi/status" +#define WIFI_SCAN_METHOD "wifi/scan" +#define WIFI_SSID_METHOD "wifi/SSID" +#define WIFI_BSSID_METHOD "wifi/BSSID" +#define WIFI_RSSI_METHOD "wifi/RSSI" +#define WIFI_LOCAL_IP_METHOD "wifi/localIP" +#define WIFI_SUBNET_MASK_METHOD "wifi/subnetMask" +#define WIFI_GATEWAY_IP_METHOD "wifi/gatewayIP" +#define WIFI_MAC_ADDRESS_METHOD "wifi/macAddress" + +#include +#include + + +// WiFi status codes (matching Arduino WiFi library) +typedef enum { + WL_NO_SHIELD = 255, + WL_IDLE_STATUS = 0, + WL_NO_SSID_AVAIL = 1, + WL_SCAN_COMPLETED = 2, + WL_CONNECTED = 3, + WL_CONNECT_FAILED = 4, + WL_CONNECTION_LOST = 5, + WL_DISCONNECTED = 6 +} wl_status_t; + +// WiFi encryption types. Not used yet +// typedef enum { +// ENC_TYPE_WEP = 5, +// ENC_TYPE_TKIP = 2, +// ENC_TYPE_CCMP = 4, +// ENC_TYPE_NONE = 7, +// ENC_TYPE_AUTO = 8 +// } wl_enc_type; + +class BridgeWiFi { + + BridgeClass* bridge; + struct k_mutex wifi_mutex{}; + wl_status_t _status = WL_IDLE_STATUS; + String _ssid{}; + IPAddress _localIP{}; + IPAddress _subnetMask{}; + IPAddress _gatewayIP{}; + uint8_t _bssid[6]{}; + int32_t _rssi = 0; + +public: + + explicit BridgeWiFi(BridgeClass& bridge): bridge(&bridge) {} + + /** + * Start WiFi connection for OPEN networks + * @param ssid: SSID of the network + */ + uint8_t begin(const char* ssid) { + return begin(ssid, nullptr); + } + + /** + * Start WiFi connection with WPA/WPA2 encryption + * @param ssid: SSID of the network + * @param passphrase: Passphrase (max 63 chars) + */ + uint8_t begin(const char* ssid, const char* passphrase) { + + if (!init()) { + _status = WL_NO_SHIELD; + return _status; + } + + k_mutex_lock(&wifi_mutex, K_FOREVER); + + String ssid_str = ssid; + String pass_str = passphrase ? passphrase : ""; + + uint8_t result; + const bool ok = bridge->call(WIFI_BEGIN_METHOD, ssid_str, pass_str).result(result); + + if (ok && result == WL_CONNECTED) { + _status = WL_CONNECTED; + _ssid = ssid_str; + updateConnectionInfo(); + } else if (ok) { + _status = static_cast(result); + } else { + _status = WL_CONNECT_FAILED; + } + + k_mutex_unlock(&wifi_mutex); + + return result; + } + + /** + * Disconnect from the network + */ + uint8_t disconnect() { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + bool success = false; + if (_status == WL_CONNECTED) { + int32_t result; + success = bridge->call(WIFI_DISCONNECT_METHOD).result(result); + if (success) { + _status = WL_DISCONNECTED; + _ssid = ""; + _localIP = IPAddress(0, 0, 0, 0); + } + } + + k_mutex_unlock(&wifi_mutex); + + return success ? WL_DISCONNECTED : _status; + } + + /** + * Get the connection status + */ + uint8_t status() { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + int result; + const bool ok = bridge->call(WIFI_STATUS_METHOD).result(result); + + if (ok) { + _status = static_cast(result); + } + + const wl_status_t current_status = _status; + k_mutex_unlock(&wifi_mutex); + + return current_status; + } + + /** + * Start scan WiFi networks available + * @return Number of discovered networks + */ + int8_t scanNetworks() { + if (!init()) { + return -1; + } + + k_mutex_lock(&wifi_mutex, K_FOREVER); + + int networks; + const bool ok = bridge->call(WIFI_SCAN_METHOD).result(networks); + + if (networks > 127) networks=127; + if (networks < -1) networks=-1; + + k_mutex_unlock(&wifi_mutex); + + return ok ? static_cast(networks) : -1; + } + + /** + * Return the SSID discovered during the network scan + * @param networkItem: specify from which network item want to get the information + */ + String SSID(uint8_t networkItem) { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + String ssid; + bridge->call(WIFI_SSID_METHOD, networkItem).result(ssid); + + k_mutex_unlock(&wifi_mutex); + + return ssid; + } + + /** + * Return the current SSID associated with the network + */ + String SSID() { + k_mutex_lock(&wifi_mutex, K_FOREVER); + const String ssid = _ssid; + k_mutex_unlock(&wifi_mutex); + return ssid; + } + + /** + * Return the current BSSID associated with the network + * @param bssid: array to store BSSID (6 bytes) + */ + uint8_t* BSSID(uint8_t* bssid) { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + MsgPack::arr_t bssid_arr; + const bool ok = bridge->call(WIFI_BSSID_METHOD).result(bssid_arr); + + if (ok && bssid_arr.size() >= 6) { + for (size_t i = 0; i < 6; ++i) { + bssid[i] = bssid_arr[i]; + _bssid[i] = bssid_arr[i]; + } + } + + k_mutex_unlock(&wifi_mutex); + + return bssid; + } + + /** + * Return the current RSSI (Received Signal Strength in dBm) + */ + int32_t RSSI() { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + int32_t rssi; + const bool ok = bridge->call(WIFI_RSSI_METHOD).result(rssi); + + if (ok) { + _rssi = rssi; + } + + k_mutex_unlock(&wifi_mutex); + + return rssi; + } + + /** + * Get the interface IP address + */ + IPAddress localIP() { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + String ip_str; + const bool ok = bridge->call(WIFI_LOCAL_IP_METHOD).result(ip_str); + + if (ok) { + _localIP.fromString(ip_str); + } + + const IPAddress ip = _localIP; + k_mutex_unlock(&wifi_mutex); + + return ip; + } + + /** + * Get the interface subnet mask address + */ + IPAddress subnetMask() { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + String mask_str; + const bool ok = bridge->call(WIFI_SUBNET_MASK_METHOD).result(mask_str); + + if (ok) { + _subnetMask.fromString(mask_str); + } + + const IPAddress mask = _subnetMask; + k_mutex_unlock(&wifi_mutex); + + return mask; + } + + /** + * Get the gateway IP address + */ + IPAddress gatewayIP() { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + String gateway_str; + const bool ok = bridge->call(WIFI_GATEWAY_IP_METHOD).result(gateway_str); + + if (ok) { + _gatewayIP.fromString(gateway_str); + } + + const IPAddress gateway = _gatewayIP; + k_mutex_unlock(&wifi_mutex); + + return gateway; + } + + /** + * Get the interface MAC address + * @param mac: array to store MAC address (6 bytes) + */ + uint8_t* macAddress(uint8_t* mac) { + k_mutex_lock(&wifi_mutex, K_FOREVER); + + MsgPack::arr_t mac_arr; + const bool ok = bridge->call(WIFI_MAC_ADDRESS_METHOD).result(mac_arr); + + if (ok && mac_arr.size() >= 6) { + for (size_t i = 0; i < 6; ++i) { + mac[i] = mac_arr[i]; + } + } + + k_mutex_unlock(&wifi_mutex); + + return mac; + } + + /** + * Check if WiFi is connected + */ + bool isConnected() { + return status() == WL_CONNECTED; + } + +private: + + bool init() { + k_mutex_init(&wifi_mutex); + if (!(*bridge)) { + return bridge->begin(); + } + return true; + } + + void updateConnectionInfo() { + // Update local IP and other connection info + String ip_str; + if (bridge->call(WIFI_LOCAL_IP_METHOD).result(ip_str)) { + _localIP.fromString(ip_str); + } + + String mask_str; + if (bridge->call(WIFI_SUBNET_MASK_METHOD).result(mask_str)) { + _subnetMask.fromString(mask_str); + } + + String gateway_str; + if (bridge->call(WIFI_GATEWAY_IP_METHOD).result(gateway_str)) { + _gatewayIP.fromString(gateway_str); + } + } +}; + +#endif //WIFI_BRIDGE_H \ No newline at end of file