[realtek-ambz] Support WiFi events
This commit is contained in:
5
arduino/libretuya/api/Events.cpp
Normal file
5
arduino/libretuya/api/Events.cpp
Normal file
@@ -0,0 +1,5 @@
|
||||
/* Copyright (c) Kuba Szczodrzyński 2022-05-17. */
|
||||
|
||||
#include "Events.h"
|
||||
|
||||
uint16_t EventHandler_s::lastId = 1;
|
||||
117
arduino/libretuya/api/Events.h
Normal file
117
arduino/libretuya/api/Events.h
Normal file
@@ -0,0 +1,117 @@
|
||||
/*
|
||||
ESP8266WiFiGeneric.h - esp8266 Wifi support.
|
||||
Based on WiFi.h from Ardiono WiFi shield library.
|
||||
Copyright (c) 2011-2014 Arduino. All right reserved.
|
||||
Modified by Ivan Grokhotkov, December 2014
|
||||
Reworked by Markus Sattler, December 2015
|
||||
|
||||
This library is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU Lesser General Public
|
||||
License as published by the Free Software Foundation; either
|
||||
version 2.1 of the License, or (at your option) any later version.
|
||||
|
||||
This library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with this library; if not, write to the Free Software
|
||||
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include "WiFiEvents.h"
|
||||
|
||||
typedef enum {
|
||||
ARDUINO_EVENT_WIFI_READY = 0, /**< ESP32 WiFi ready */
|
||||
ARDUINO_EVENT_WIFI_SCAN_DONE, /**< ESP32 finish scanning AP */
|
||||
ARDUINO_EVENT_WIFI_STA_START, /**< ESP32 station start */
|
||||
ARDUINO_EVENT_WIFI_STA_STOP, /**< ESP32 station stop */
|
||||
ARDUINO_EVENT_WIFI_STA_CONNECTED, /**< ESP32 station connected to AP */
|
||||
ARDUINO_EVENT_WIFI_STA_DISCONNECTED, /**< ESP32 station disconnected from AP */
|
||||
ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE, /**< the auth mode of AP connected by ESP32 station changed */
|
||||
ARDUINO_EVENT_WIFI_STA_GOT_IP,
|
||||
ARDUINO_EVENT_WIFI_STA_GOT_IP6,
|
||||
ARDUINO_EVENT_WIFI_STA_LOST_IP,
|
||||
ARDUINO_EVENT_WIFI_AP_START, /**< ESP32 soft-AP start */
|
||||
ARDUINO_EVENT_WIFI_AP_STOP, /**< ESP32 soft-AP stop */
|
||||
ARDUINO_EVENT_WIFI_AP_STACONNECTED, /**< a station connected to ESP32 soft-AP */
|
||||
ARDUINO_EVENT_WIFI_AP_STADISCONNECTED, /**< a station disconnected from ESP32 soft-AP */
|
||||
ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED,
|
||||
ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED, /**< Receive probe request packet in soft-AP interface */
|
||||
ARDUINO_EVENT_WIFI_AP_GOT_IP6,
|
||||
ARDUINO_EVENT_WIFI_FTM_REPORT, /**< Receive report of FTM procedure */
|
||||
ARDUINO_EVENT_ETH_START,
|
||||
ARDUINO_EVENT_ETH_STOP,
|
||||
ARDUINO_EVENT_ETH_CONNECTED,
|
||||
ARDUINO_EVENT_ETH_DISCONNECTED,
|
||||
ARDUINO_EVENT_ETH_GOT_IP,
|
||||
ARDUINO_EVENT_ETH_GOT_IP6,
|
||||
ARDUINO_EVENT_WPS_ER_SUCCESS, /**< ESP32 station wps succeeds in enrollee mode */
|
||||
ARDUINO_EVENT_WPS_ER_FAILED, /**< ESP32 station wps fails in enrollee mode */
|
||||
ARDUINO_EVENT_WPS_ER_TIMEOUT, /**< ESP32 station wps timeout in enrollee mode */
|
||||
ARDUINO_EVENT_WPS_ER_PIN, /**< ESP32 station wps pin code in enrollee mode */
|
||||
ARDUINO_EVENT_WPS_ER_PBC_OVERLAP, /**< ESP32 station wps overlap in enrollee mode */
|
||||
ARDUINO_EVENT_SC_SCAN_DONE,
|
||||
ARDUINO_EVENT_SC_FOUND_CHANNEL,
|
||||
ARDUINO_EVENT_SC_GOT_SSID_PSWD,
|
||||
ARDUINO_EVENT_SC_SEND_ACK_DONE,
|
||||
ARDUINO_EVENT_PROV_INIT,
|
||||
ARDUINO_EVENT_PROV_DEINIT,
|
||||
ARDUINO_EVENT_PROV_START,
|
||||
ARDUINO_EVENT_PROV_END,
|
||||
ARDUINO_EVENT_PROV_CRED_RECV,
|
||||
ARDUINO_EVENT_PROV_CRED_FAIL,
|
||||
ARDUINO_EVENT_PROV_CRED_SUCCESS,
|
||||
ARDUINO_EVENT_MAX
|
||||
} arduino_event_id_t;
|
||||
|
||||
typedef union {
|
||||
wifi_event_sta_scan_done_t wifi_scan_done;
|
||||
wifi_event_sta_authmode_change_t wifi_sta_authmode_change;
|
||||
wifi_event_sta_connected_t wifi_sta_connected;
|
||||
wifi_event_sta_disconnected_t wifi_sta_disconnected;
|
||||
wifi_event_sta_wps_er_pin_t wps_er_pin;
|
||||
wifi_event_sta_wps_fail_reason_t wps_fail_reason;
|
||||
wifi_event_ap_probe_req_rx_t wifi_ap_probereqrecved;
|
||||
wifi_event_ap_staconnected_t wifi_ap_staconnected;
|
||||
wifi_event_ap_stadisconnected_t wifi_ap_stadisconnected;
|
||||
wifi_event_ftm_report_t wifi_ftm_report;
|
||||
ip_event_ap_staipassigned_t wifi_ap_staipassigned;
|
||||
ip_event_got_ip_t got_ip;
|
||||
ip_event_got_ip6_t got_ip6;
|
||||
// smartconfig_event_got_ssid_pswd_t sc_got_ssid_pswd;
|
||||
// esp_eth_handle_t eth_connected;
|
||||
// wifi_sta_config_t prov_cred_recv;
|
||||
// wifi_prov_sta_fail_reason_t prov_fail_reason;
|
||||
} arduino_event_info_t;
|
||||
|
||||
typedef struct {
|
||||
arduino_event_id_t event_id;
|
||||
arduino_event_info_t event_info;
|
||||
} arduino_event_t;
|
||||
|
||||
#define EventId arduino_event_id_t
|
||||
#define EventId_t arduino_event_id_t
|
||||
#define EventInfo arduino_event_info_t
|
||||
#define EventInfo_t arduino_event_info_t
|
||||
#define Event_t arduino_event_t
|
||||
|
||||
typedef void (*EventCb)(EventId event);
|
||||
typedef std::function<void(EventId event, EventInfo info)> EventFuncCb;
|
||||
typedef void (*EventSysCb)(Event_t *event);
|
||||
|
||||
typedef struct EventHandler_s {
|
||||
static uint16_t lastId;
|
||||
uint16_t id;
|
||||
EventCb cb;
|
||||
EventFuncCb fcb;
|
||||
EventSysCb scb;
|
||||
EventId eventId;
|
||||
|
||||
EventHandler_s() : id(lastId++), cb(NULL), fcb(NULL), scb(NULL) {}
|
||||
} EventHandler;
|
||||
84
arduino/libretuya/api/WiFi.cpp
Normal file
84
arduino/libretuya/api/WiFi.cpp
Normal file
@@ -0,0 +1,84 @@
|
||||
/* Copyright (c) Kuba Szczodrzyński 2022-05-17. */
|
||||
|
||||
#include "WiFi.h"
|
||||
|
||||
std::vector<EventHandler> IWiFiGenericClass::handlers;
|
||||
|
||||
uint16_t IWiFiGenericClass::onEvent(EventCb callback, EventId eventId) {
|
||||
if (!callback)
|
||||
return 0;
|
||||
EventHandler handler;
|
||||
handler.cb = callback;
|
||||
handler.eventId = eventId;
|
||||
handlers.push_back(handler);
|
||||
return handler.id;
|
||||
}
|
||||
|
||||
uint16_t IWiFiGenericClass::onEvent(EventFuncCb callback, EventId eventId) {
|
||||
if (!callback)
|
||||
return 0;
|
||||
EventHandler handler;
|
||||
handler.fcb = callback;
|
||||
handler.eventId = eventId;
|
||||
handlers.push_back(handler);
|
||||
return handler.id;
|
||||
}
|
||||
|
||||
uint16_t IWiFiGenericClass::onEvent(EventSysCb callback, EventId eventId) {
|
||||
if (!callback)
|
||||
return 0;
|
||||
EventHandler handler;
|
||||
handler.scb = callback;
|
||||
handler.eventId = eventId;
|
||||
handlers.push_back(handler);
|
||||
return handler.id;
|
||||
}
|
||||
|
||||
void IWiFiGenericClass::removeEvent(EventCb callback, EventId eventId) {
|
||||
if (!callback)
|
||||
return;
|
||||
for (uint16_t i = 0; i < handlers.size(); i++) {
|
||||
EventHandler handler = handlers[i];
|
||||
if (handler.cb == callback && handler.eventId == eventId) {
|
||||
handlers.erase(handlers.begin() + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IWiFiGenericClass::removeEvent(EventSysCb callback, EventId eventId) {
|
||||
if (!callback)
|
||||
return;
|
||||
for (uint16_t i = 0; i < handlers.size(); i++) {
|
||||
EventHandler handler = handlers[i];
|
||||
if (handler.scb == callback && handler.eventId == eventId) {
|
||||
handlers.erase(handlers.begin() + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IWiFiGenericClass::removeEvent(uint16_t id) {
|
||||
for (uint16_t i = 0; i < handlers.size(); i++) {
|
||||
EventHandler handler = handlers[i];
|
||||
if (handler.id == id) {
|
||||
handlers.erase(handlers.begin() + i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IWiFiGenericClass::postEvent(EventId eventId, EventInfo eventInfo) {
|
||||
for (auto handler : handlers) {
|
||||
if (handler.eventId != ARDUINO_EVENT_MAX && handler.eventId != eventId)
|
||||
continue;
|
||||
if (handler.cb) {
|
||||
handler.cb(eventId);
|
||||
} else if (handler.fcb) {
|
||||
handler.fcb(eventId, eventInfo);
|
||||
} else if (handler.scb) {
|
||||
Event_t event = {
|
||||
.event_id = eventId,
|
||||
.event_info = eventInfo,
|
||||
};
|
||||
handler.scb(&event);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,11 @@
|
||||
#include <api/IPAddress.h>
|
||||
#include <api/IPv6Address.h>
|
||||
#include <api/Print.h>
|
||||
#include <vector>
|
||||
|
||||
#include "Events.h"
|
||||
#include "WiFiType.h"
|
||||
|
||||
// TODO wifi events
|
||||
|
||||
class IWiFiClass {
|
||||
public:
|
||||
virtual void printDiag(Print &dest) = 0;
|
||||
@@ -63,6 +63,20 @@ class IWiFiGenericClass {
|
||||
static IPAddress calculateBroadcast(IPAddress ip, IPAddress subnet);
|
||||
static uint8_t calculateSubnetCIDR(IPAddress subnetMask);
|
||||
static String macToString(uint8_t *mac);
|
||||
|
||||
protected:
|
||||
static std::vector<EventHandler> handlers;
|
||||
|
||||
public:
|
||||
uint16_t onEvent(EventCb callback, EventId eventId = ARDUINO_EVENT_MAX);
|
||||
uint16_t onEvent(EventFuncCb callback, EventId eventId = ARDUINO_EVENT_MAX);
|
||||
uint16_t onEvent(EventSysCb callback, EventId eventId = ARDUINO_EVENT_MAX);
|
||||
void removeEvent(EventCb callback, EventId eventId);
|
||||
void removeEvent(EventSysCb callback, EventId eventId);
|
||||
void removeEvent(uint16_t id);
|
||||
|
||||
protected:
|
||||
static void postEvent(EventId eventId, EventInfo eventInfo);
|
||||
};
|
||||
|
||||
class IWiFiSTAClass {
|
||||
|
||||
173
arduino/libretuya/api/WiFiEvents.h
Normal file
173
arduino/libretuya/api/WiFiEvents.h
Normal file
@@ -0,0 +1,173 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2015-2021 Espressif Systems (Shanghai) CO LTD
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "WiFiType.h"
|
||||
|
||||
/** Argument structure for WIFI_EVENT_SCAN_DONE event */
|
||||
typedef struct {
|
||||
uint32_t status; /**< status of scanning APs: 0 - success, 1 - failure */
|
||||
uint8_t number; /**< number of scan results */
|
||||
uint8_t scan_id; /**< scan sequence number, used for block scan */
|
||||
} wifi_event_sta_scan_done_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_STA_CONNECTED event */
|
||||
typedef struct {
|
||||
uint8_t ssid[32]; /**< SSID of connected AP */
|
||||
uint8_t ssid_len; /**< SSID length of connected AP */
|
||||
uint8_t bssid[6]; /**< BSSID of connected AP*/
|
||||
uint8_t channel; /**< channel of connected AP*/
|
||||
wifi_auth_mode_t authmode; /**< authentication mode used by AP*/
|
||||
} wifi_event_sta_connected_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_STA_DISCONNECTED event */
|
||||
typedef struct {
|
||||
uint8_t ssid[32]; /**< SSID of disconnected AP */
|
||||
uint8_t ssid_len; /**< SSID length of disconnected AP */
|
||||
uint8_t bssid[6]; /**< BSSID of disconnected AP */
|
||||
uint8_t reason; /**< reason of disconnection */
|
||||
} wifi_event_sta_disconnected_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_STA_AUTHMODE_CHANGE event */
|
||||
typedef struct {
|
||||
wifi_auth_mode_t old_mode; /**< the old auth mode of AP */
|
||||
wifi_auth_mode_t new_mode; /**< the new auth mode of AP */
|
||||
} wifi_event_sta_authmode_change_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_STA_WPS_ER_PIN event */
|
||||
typedef struct {
|
||||
uint8_t pin_code[8]; /**< PIN code of station in enrollee mode */
|
||||
} wifi_event_sta_wps_er_pin_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_STA_WPS_ER_FAILED event */
|
||||
typedef enum {
|
||||
WPS_FAIL_REASON_NORMAL = 0, /**< ESP32 WPS normal fail reason */
|
||||
WPS_FAIL_REASON_RECV_M2D, /**< ESP32 WPS receive M2D frame */
|
||||
WPS_FAIL_REASON_MAX
|
||||
} wifi_event_sta_wps_fail_reason_t;
|
||||
|
||||
#define MAX_SSID_LEN 32
|
||||
#define MAX_PASSPHRASE_LEN 64
|
||||
#define MAX_WPS_AP_CRED 3
|
||||
|
||||
/** Argument structure for WIFI_EVENT_STA_WPS_ER_SUCCESS event */
|
||||
typedef struct {
|
||||
uint8_t ap_cred_cnt; /**< Number of AP credentials received */
|
||||
|
||||
struct {
|
||||
uint8_t ssid[MAX_SSID_LEN]; /**< SSID of AP */
|
||||
uint8_t passphrase[MAX_PASSPHRASE_LEN]; /**< Passphrase for the AP */
|
||||
} ap_cred[MAX_WPS_AP_CRED]; /**< All AP credentials received from WPS handshake */
|
||||
} wifi_event_sta_wps_er_success_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_AP_STACONNECTED event */
|
||||
typedef struct {
|
||||
uint8_t mac[6]; /**< MAC address of the station connected to ESP32 soft-AP */
|
||||
uint8_t aid; /**< the aid that ESP32 soft-AP gives to the station connected to */
|
||||
bool is_mesh_child; /**< flag to identify mesh child */
|
||||
} wifi_event_ap_staconnected_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_AP_STADISCONNECTED event */
|
||||
typedef struct {
|
||||
uint8_t mac[6]; /**< MAC address of the station disconnects to ESP32 soft-AP */
|
||||
uint8_t aid; /**< the aid that ESP32 soft-AP gave to the station disconnects to */
|
||||
bool is_mesh_child; /**< flag to identify mesh child */
|
||||
} wifi_event_ap_stadisconnected_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_AP_PROBEREQRECVED event */
|
||||
typedef struct {
|
||||
int rssi; /**< Received probe request signal strength */
|
||||
uint8_t mac[6]; /**< MAC address of the station which send probe request */
|
||||
} wifi_event_ap_probe_req_rx_t;
|
||||
|
||||
/**
|
||||
* @brief FTM operation status types
|
||||
*
|
||||
*/
|
||||
typedef enum {
|
||||
FTM_STATUS_SUCCESS = 0, /**< FTM exchange is successful */
|
||||
FTM_STATUS_UNSUPPORTED, /**< Peer does not support FTM */
|
||||
FTM_STATUS_CONF_REJECTED, /**< Peer rejected FTM configuration in FTM Request */
|
||||
FTM_STATUS_NO_RESPONSE, /**< Peer did not respond to FTM Requests */
|
||||
FTM_STATUS_FAIL, /**< Unknown error during FTM exchange */
|
||||
} wifi_ftm_status_t;
|
||||
|
||||
/** Argument structure for */
|
||||
typedef struct {
|
||||
uint8_t dlog_token; /**< Dialog Token of the FTM frame */
|
||||
int8_t rssi; /**< RSSI of the FTM frame received */
|
||||
uint32_t rtt; /**< Round Trip Time in pSec with a peer */
|
||||
uint64_t t1; /**< Time of departure of FTM frame from FTM Responder in pSec */
|
||||
uint64_t t2; /**< Time of arrival of FTM frame at FTM Initiator in pSec */
|
||||
uint64_t t3; /**< Time of departure of ACK from FTM Initiator in pSec */
|
||||
uint64_t t4; /**< Time of arrival of ACK at FTM Responder in pSec */
|
||||
} wifi_ftm_report_entry_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_FTM_REPORT event */
|
||||
typedef struct {
|
||||
uint8_t peer_mac[6]; /**< MAC address of the FTM Peer */
|
||||
wifi_ftm_status_t status; /**< Status of the FTM operation */
|
||||
uint32_t rtt_raw; /**< Raw average Round-Trip-Time with peer in Nano-Seconds */
|
||||
uint32_t rtt_est; /**< Estimated Round-Trip-Time with peer in Nano-Seconds */
|
||||
uint32_t dist_est; /**< Estimated one-way distance in Centi-Meters */
|
||||
wifi_ftm_report_entry_t
|
||||
*ftm_report_data; /**< Pointer to FTM Report with multiple entries, should be freed after use */
|
||||
uint8_t ftm_report_num_entries; /**< Number of entries in the FTM Report data */
|
||||
} wifi_event_ftm_report_t;
|
||||
|
||||
#define WIFI_STATIS_BUFFER (1 << 0)
|
||||
#define WIFI_STATIS_RXTX (1 << 1)
|
||||
#define WIFI_STATIS_HW (1 << 2)
|
||||
#define WIFI_STATIS_DIAG (1 << 3)
|
||||
#define WIFI_STATIS_PS (1 << 4)
|
||||
#define WIFI_STATIS_ALL (-1)
|
||||
|
||||
/** Argument structure for WIFI_EVENT_ACTION_TX_STATUS event */
|
||||
typedef struct {
|
||||
int ifx; /**< WiFi interface to send request to */
|
||||
uint32_t context; /**< Context to identify the request */
|
||||
uint8_t da[6]; /**< Destination MAC address */
|
||||
uint8_t status; /**< Status of the operation */
|
||||
} wifi_event_action_tx_status_t;
|
||||
|
||||
/** Argument structure for WIFI_EVENT_ROC_DONE event */
|
||||
typedef struct {
|
||||
uint32_t context; /**< Context to identify the request */
|
||||
} wifi_event_roc_done_t;
|
||||
|
||||
/** Event structure for IP_EVENT_STA_GOT_IP, IP_EVENT_ETH_GOT_IP events */
|
||||
typedef struct {
|
||||
esp_ip4_addr_t ip; /**< Interface IPV4 address */
|
||||
esp_ip4_addr_t netmask; /**< Interface IPV4 netmask */
|
||||
esp_ip4_addr_t gw; /**< Interface IPV4 gateway address */
|
||||
} esp_netif_ip_info_t;
|
||||
|
||||
/** @brief IPV6 IP address information
|
||||
*/
|
||||
typedef struct {
|
||||
esp_ip6_addr_t ip; /**< Interface IPV6 address */
|
||||
} esp_netif_ip6_info_t;
|
||||
|
||||
typedef struct {
|
||||
int if_index; /*!< Interface index for which the event is received (left for legacy compilation) */
|
||||
void *esp_netif; /*!< Pointer to corresponding esp-netif object */
|
||||
esp_netif_ip_info_t ip_info; /*!< IP address, netmask, gatway IP address */
|
||||
bool ip_changed; /*!< Whether the assigned IP has changed or not */
|
||||
} ip_event_got_ip_t;
|
||||
|
||||
/** Event structure for IP_EVENT_GOT_IP6 event */
|
||||
typedef struct {
|
||||
int if_index; /*!< Interface index for which the event is received (left for legacy compilation) */
|
||||
void *esp_netif; /*!< Pointer to corresponding esp-netif object */
|
||||
esp_netif_ip6_info_t ip6_info; /*!< IPv6 address of the interface */
|
||||
int ip_index; /*!< IPv6 address index */
|
||||
} ip_event_got_ip6_t;
|
||||
|
||||
/** Event structure for IP_EVENT_AP_STAIPASSIGNED event */
|
||||
typedef struct {
|
||||
esp_ip4_addr_t ip; /*!< IP address which was assigned to the station */
|
||||
} ip_event_ap_staipassigned_t;
|
||||
@@ -34,12 +34,28 @@
|
||||
#define WIFI_AP WIFI_MODE_AP
|
||||
#define WIFI_AP_STA WIFI_MODE_APSTA
|
||||
|
||||
#define WiFiEvent_t arduino_event_id_t
|
||||
#define WiFiEventInfo_t arduino_event_info_t
|
||||
#define WiFiEventId_t uint16_t
|
||||
|
||||
struct esp_ip6_addr {
|
||||
uint32_t addr[4];
|
||||
uint8_t zone;
|
||||
};
|
||||
|
||||
struct esp_ip4_addr {
|
||||
uint32_t addr;
|
||||
};
|
||||
|
||||
typedef struct esp_ip4_addr esp_ip4_addr_t;
|
||||
typedef struct esp_ip6_addr esp_ip6_addr_t;
|
||||
|
||||
typedef enum {
|
||||
WIFI_MODE_NULL = 0,
|
||||
WIFI_MODE_STA,
|
||||
WIFI_MODE_AP,
|
||||
WIFI_MODE_APSTA,
|
||||
WIFI_MODE_MAX,
|
||||
WIFI_MODE_NULL = 0, /**< null mode */
|
||||
WIFI_MODE_STA, /**< WiFi station mode */
|
||||
WIFI_MODE_AP, /**< WiFi soft-AP mode */
|
||||
WIFI_MODE_APSTA, /**< WiFi station + soft-AP mode */
|
||||
WIFI_MODE_MAX
|
||||
} wifi_mode_t;
|
||||
|
||||
typedef enum {
|
||||
@@ -54,13 +70,54 @@ typedef enum {
|
||||
} wl_status_t;
|
||||
|
||||
typedef enum {
|
||||
WIFI_AUTH_INVALID = 255,
|
||||
WIFI_AUTH_AUTO = 200,
|
||||
WIFI_AUTH_OPEN = 0,
|
||||
WIFI_AUTH_WEP = 1,
|
||||
WIFI_AUTH_WPA = 5,
|
||||
WIFI_AUTH_WPA2 = 6,
|
||||
WIFI_AUTH_WPA_PSK = 2,
|
||||
WIFI_AUTH_WPA2_PSK = 3,
|
||||
WIFI_AUTH_WPA_WPA2_PSK = 4,
|
||||
WIFI_AUTH_OPEN = 0, /**< authenticate mode : open */
|
||||
WIFI_AUTH_WEP, /**< authenticate mode : WEP */
|
||||
WIFI_AUTH_WPA_PSK, /**< authenticate mode : WPA_PSK */
|
||||
WIFI_AUTH_WPA2_PSK, /**< authenticate mode : WPA2_PSK */
|
||||
WIFI_AUTH_WPA_WPA2_PSK, /**< authenticate mode : WPA_WPA2_PSK */
|
||||
WIFI_AUTH_WPA2_ENTERPRISE, /**< authenticate mode : WPA2_ENTERPRISE */
|
||||
WIFI_AUTH_WPA3_PSK, /**< authenticate mode : WPA3_PSK */
|
||||
WIFI_AUTH_WPA2_WPA3_PSK, /**< authenticate mode : WPA2_WPA3_PSK */
|
||||
WIFI_AUTH_WAPI_PSK, /**< authenticate mode : WAPI_PSK */
|
||||
WIFI_AUTH_WPA,
|
||||
WIFI_AUTH_WPA2,
|
||||
WIFI_AUTH_AUTO = 200,
|
||||
WIFI_AUTH_INVALID = 255,
|
||||
WIFI_AUTH_MAX
|
||||
} wifi_auth_mode_t;
|
||||
|
||||
typedef enum {
|
||||
WIFI_REASON_UNSPECIFIED = 1,
|
||||
WIFI_REASON_AUTH_EXPIRE = 2,
|
||||
WIFI_REASON_AUTH_LEAVE = 3,
|
||||
WIFI_REASON_ASSOC_EXPIRE = 4,
|
||||
WIFI_REASON_ASSOC_TOOMANY = 5,
|
||||
WIFI_REASON_NOT_AUTHED = 6,
|
||||
WIFI_REASON_NOT_ASSOCED = 7,
|
||||
WIFI_REASON_ASSOC_LEAVE = 8,
|
||||
WIFI_REASON_ASSOC_NOT_AUTHED = 9,
|
||||
WIFI_REASON_DISASSOC_PWRCAP_BAD = 10,
|
||||
WIFI_REASON_DISASSOC_SUPCHAN_BAD = 11,
|
||||
WIFI_REASON_BSS_TRANSITION_DISASSOC = 12,
|
||||
WIFI_REASON_IE_INVALID = 13,
|
||||
WIFI_REASON_MIC_FAILURE = 14,
|
||||
WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT = 15,
|
||||
WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT = 16,
|
||||
WIFI_REASON_IE_IN_4WAY_DIFFERS = 17,
|
||||
WIFI_REASON_GROUP_CIPHER_INVALID = 18,
|
||||
WIFI_REASON_PAIRWISE_CIPHER_INVALID = 19,
|
||||
WIFI_REASON_AKMP_INVALID = 20,
|
||||
WIFI_REASON_UNSUPP_RSN_IE_VERSION = 21,
|
||||
WIFI_REASON_INVALID_RSN_IE_CAP = 22,
|
||||
WIFI_REASON_802_1X_AUTH_FAILED = 23,
|
||||
WIFI_REASON_CIPHER_SUITE_REJECTED = 24,
|
||||
WIFI_REASON_INVALID_PMKID = 53,
|
||||
WIFI_REASON_BEACON_TIMEOUT = 200,
|
||||
WIFI_REASON_NO_AP_FOUND = 201,
|
||||
WIFI_REASON_AUTH_FAIL = 202,
|
||||
WIFI_REASON_ASSOC_FAIL = 203,
|
||||
WIFI_REASON_HANDSHAKE_TIMEOUT = 204,
|
||||
WIFI_REASON_CONNECTION_FAIL = 205,
|
||||
WIFI_REASON_AP_TSF_RESET = 206,
|
||||
WIFI_REASON_ROAMING = 207,
|
||||
} wifi_err_reason_t;
|
||||
|
||||
@@ -44,6 +44,11 @@
|
||||
#define LT_LOGLEVEL LT_LEVEL_INFO
|
||||
#endif
|
||||
|
||||
// Free heap size debugging
|
||||
#ifndef LT_LOG_HEAP
|
||||
#define LT_LOG_HEAP 0
|
||||
#endif
|
||||
|
||||
// Per-module debugging
|
||||
#ifndef LT_DEBUG_WIFI
|
||||
#define LT_DEBUG_WIFI 0
|
||||
|
||||
@@ -51,6 +51,12 @@ void lt_log(const uint8_t level, const char *format, ...);
|
||||
#define LT_F(...)
|
||||
#endif
|
||||
|
||||
#if LT_LOG_HEAP
|
||||
#define LT_HEAP_I() LT_I("Free heap: %u", LT_HEAP_FUNC());
|
||||
#else
|
||||
#define LT_HEAP_I()
|
||||
#endif
|
||||
|
||||
// ESP32 compat
|
||||
#define log_printf(...) LT_I(__VA_ARGS__)
|
||||
#define log_v(...) LT_V(__VA_ARGS__)
|
||||
|
||||
@@ -17,3 +17,5 @@ extern void vPortFree(void *pv);
|
||||
#define calloc pvPortCalloc
|
||||
#define realloc pvPortReAlloc
|
||||
#define free vPortFree
|
||||
|
||||
#define LT_HEAP_FUNC xPortGetFreeHeapSize
|
||||
|
||||
@@ -31,7 +31,7 @@ static __inline uint32_t __get_ipsr__(void) {
|
||||
return (__regIPSR);
|
||||
}
|
||||
|
||||
void init(void) {
|
||||
__attribute__((weak)) void init(void) {
|
||||
// nop
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,8 @@ class WiFiClass : public IWiFiClass,
|
||||
static uint8_t calculateSubnetCIDR(IPAddress subnetMask);
|
||||
static String macToString(uint8_t *mac);
|
||||
|
||||
static void handleRtwEvent(uint16_t event, char *data, int len, int flags);
|
||||
|
||||
public:
|
||||
// IWiFiSTAClass
|
||||
WiFiStatus begin(
|
||||
|
||||
@@ -7,6 +7,8 @@ bool WiFiClass::softAP(const char *ssid, const char *passphrase, int channel, bo
|
||||
if (!enableAP(true))
|
||||
return false;
|
||||
|
||||
LT_HEAP_I();
|
||||
|
||||
vTaskDelay(20);
|
||||
|
||||
if (!ssid || *ssid == 0x00 || strlen(ssid) > 32) {
|
||||
|
||||
204
arduino/realtek-ambz/libraries/WiFi/WiFiEvents.cpp
Normal file
204
arduino/realtek-ambz/libraries/WiFi/WiFiEvents.cpp
Normal file
@@ -0,0 +1,204 @@
|
||||
/* Copyright (c) Kuba Szczodrzyński 2022-05-16. */
|
||||
|
||||
#include "WiFi.h"
|
||||
#include "WiFiPriv.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#define WIFI_EVENT_MAX_ROW 3
|
||||
|
||||
static xQueueHandle wifiEventQueueHandle = NULL;
|
||||
static xTaskHandle wifiEventTaskHandle = NULL;
|
||||
|
||||
// C code to support SDK-defined events (in wifi_conf.c)
|
||||
extern "C" {
|
||||
// SDK events
|
||||
static event_list_elem_t event_callback_list[WIFI_EVENT_MAX][WIFI_EVENT_MAX_ROW];
|
||||
|
||||
typedef struct {
|
||||
rtw_event_indicate_t event;
|
||||
char *buf;
|
||||
int buf_len;
|
||||
int flags;
|
||||
} rtw_event_t;
|
||||
|
||||
// reset callbacks
|
||||
void init_event_callback_list() {
|
||||
memset(event_callback_list, 0, sizeof(event_callback_list));
|
||||
}
|
||||
|
||||
// dummy
|
||||
int wifi_manager_init() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void wifi_reg_event_handler(unsigned int event_cmds, rtw_event_handler_t handler_func, void *handler_user_data) {
|
||||
int i = 0, j = 0;
|
||||
if (event_cmds < WIFI_EVENT_MAX) {
|
||||
for (i = 0; i < WIFI_EVENT_MAX_ROW; i++) {
|
||||
if (event_callback_list[event_cmds][i].handler == NULL) {
|
||||
for (j = 0; j < WIFI_EVENT_MAX_ROW; j++) {
|
||||
if (event_callback_list[event_cmds][j].handler == handler_func) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
event_callback_list[event_cmds][i].handler = handler_func;
|
||||
event_callback_list[event_cmds][i].handler_user_data = handler_user_data;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void wifi_unreg_event_handler(unsigned int event_cmds, rtw_event_handler_t handler_func) {
|
||||
int i;
|
||||
if (event_cmds < WIFI_EVENT_MAX) {
|
||||
for (i = 0; i < WIFI_EVENT_MAX_ROW; i++) {
|
||||
if (event_callback_list[event_cmds][i].handler == handler_func) {
|
||||
event_callback_list[event_cmds][i].handler = NULL;
|
||||
event_callback_list[event_cmds][i].handler_user_data = NULL;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} // extern "C"
|
||||
|
||||
// function called by wext_wlan_indicate
|
||||
void wifi_indication(rtw_event_indicate_t event, char *buf, int buf_len, int flags) {
|
||||
LT_HEAP_I();
|
||||
if (event >= WIFI_EVENT_MAX)
|
||||
return;
|
||||
if (wifiEventQueueHandle && wifiEventTaskHandle) {
|
||||
rtw_event_t *ev = (rtw_event_t *)malloc(sizeof(rtw_event_t));
|
||||
if (buf_len > 0) {
|
||||
// copy data to allow freeing from calling scopes
|
||||
char *bufCopy = (char *)malloc(buf_len);
|
||||
memcpy(bufCopy, buf, buf_len);
|
||||
ev->buf = bufCopy;
|
||||
} else {
|
||||
ev->buf = NULL;
|
||||
}
|
||||
ev->event = event;
|
||||
ev->buf_len = buf_len;
|
||||
ev->flags = flags;
|
||||
xQueueSend(wifiEventQueueHandle, &ev, portMAX_DELAY);
|
||||
} else {
|
||||
WiFiClass::handleRtwEvent(event, buf, buf_len, flags);
|
||||
}
|
||||
}
|
||||
|
||||
static void wifiEventTask(void *arg) {
|
||||
rtw_event_t *data = NULL;
|
||||
for (;;) {
|
||||
if (xQueueReceive(wifiEventQueueHandle, &data, portMAX_DELAY) == pdTRUE) {
|
||||
WiFiClass::handleRtwEvent(data->event, data->buf, data->buf_len, data->flags);
|
||||
if (data->buf) {
|
||||
// free memory allocated in wifi_indication
|
||||
free(data->buf);
|
||||
}
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void startWifiTask() {
|
||||
if (!wifiEventQueueHandle) {
|
||||
LT_HEAP_I();
|
||||
wifiEventQueueHandle = xQueueCreate(32, sizeof(Event_t *));
|
||||
LT_HEAP_I();
|
||||
}
|
||||
if (!wifiEventTaskHandle) {
|
||||
LT_HEAP_I();
|
||||
xTaskCreate(wifiEventTask, "wifievent", 512, NULL, 4, &wifiEventTaskHandle);
|
||||
LT_HEAP_I();
|
||||
}
|
||||
}
|
||||
|
||||
void WiFiClass::handleRtwEvent(uint16_t event, char *data, int len, int flags) {
|
||||
if (flags == -2) {
|
||||
// already an Arduino event, just pass it
|
||||
EventId eventId = (EventId)len;
|
||||
EventInfo *eventInfo = (EventInfo *)data;
|
||||
postEvent(eventId, *eventInfo);
|
||||
free(eventInfo);
|
||||
return;
|
||||
}
|
||||
|
||||
// send to SDK listeners
|
||||
for (uint8_t i = 0; i < WIFI_EVENT_MAX_ROW; i++) {
|
||||
rtw_event_handler_t handler = event_callback_list[event][i].handler;
|
||||
if (!handler)
|
||||
continue;
|
||||
handler(data, len, flags, event_callback_list[event][i].handler_user_data);
|
||||
}
|
||||
|
||||
EventId eventId;
|
||||
EventInfo eventInfo;
|
||||
String ssid;
|
||||
|
||||
memset(&eventInfo, 0, sizeof(EventInfo));
|
||||
|
||||
switch (event) {
|
||||
case WIFI_EVENT_CONNECT:
|
||||
eventId = ARDUINO_EVENT_WIFI_STA_START;
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_DISCONNECT:
|
||||
case WIFI_EVENT_RECONNECTION_FAIL:
|
||||
eventId = ARDUINO_EVENT_WIFI_STA_DISCONNECTED;
|
||||
eventInfo.wifi_sta_disconnected.ssid_len = 0;
|
||||
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_UNSPECIFIED;
|
||||
if (event == WIFI_EVENT_RECONNECTION_FAIL)
|
||||
eventInfo.wifi_sta_disconnected.reason = WIFI_REASON_CONNECTION_FAIL;
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_FOURWAY_HANDSHAKE_DONE:
|
||||
eventId = ARDUINO_EVENT_WIFI_STA_CONNECTED;
|
||||
ssid = WiFi.SSID();
|
||||
eventInfo.wifi_sta_connected.ssid_len = ssid.length();
|
||||
eventInfo.wifi_sta_connected.channel = WiFi.channel();
|
||||
eventInfo.wifi_sta_connected.authmode = WiFi.getEncryption();
|
||||
memcpy(eventInfo.wifi_sta_connected.ssid, ssid.c_str(), eventInfo.wifi_sta_connected.ssid_len + 1);
|
||||
memcpy(eventInfo.wifi_sta_connected.bssid, WiFi.BSSID(), 6);
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_SCAN_DONE:
|
||||
eventId = ARDUINO_EVENT_WIFI_SCAN_DONE;
|
||||
eventInfo.wifi_scan_done.status = 0;
|
||||
eventInfo.wifi_scan_done.number = WiFi._netCount;
|
||||
eventInfo.wifi_scan_done.scan_id = 0;
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_STA_ASSOC:
|
||||
// data(124) has MAC at 0x0A
|
||||
if (len != 124)
|
||||
return;
|
||||
eventId = ARDUINO_EVENT_WIFI_AP_STACONNECTED;
|
||||
memcpy(eventInfo.wifi_ap_staconnected.mac, (const char *)data[10], 6);
|
||||
break;
|
||||
|
||||
case WIFI_EVENT_STA_DISASSOC:
|
||||
// data(6) is MAC
|
||||
eventId = ARDUINO_EVENT_WIFI_AP_STADISCONNECTED;
|
||||
memcpy(eventInfo.wifi_ap_stadisconnected.mac, (const char *)data, 6);
|
||||
break;
|
||||
|
||||
// case WIFI_EVENT_SCAN_RESULT_REPORT:
|
||||
// case WIFI_EVENT_SEND_ACTION_DONE:
|
||||
// case WIFI_EVENT_RX_MGNT:
|
||||
// case WIFI_EVENT_STA_WPS_START:
|
||||
// case WIFI_EVENT_WPS_FINISH:
|
||||
// case WIFI_EVENT_EAPOL_START:
|
||||
// case WIFI_EVENT_EAPOL_RECVD:
|
||||
// case WIFI_EVENT_NO_NETWORK:
|
||||
// case WIFI_EVENT_BEACON_AFTER_DHCP:
|
||||
// case WIFI_EVENT_IP_CHANGED:
|
||||
// case WIFI_EVENT_ICV_ERROR:
|
||||
// case WIFI_EVENT_CHALLENGE_FAIL:
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
postEvent(eventId, eventInfo);
|
||||
}
|
||||
@@ -9,20 +9,24 @@ int32_t WiFiClass::channel() {
|
||||
return channel;
|
||||
}
|
||||
|
||||
extern void startWifiTask();
|
||||
|
||||
bool WiFiClass::mode(WiFiMode mode) {
|
||||
WiFiMode currentMode = getMode();
|
||||
LT_D_WG("Mode changing %u -> %u", currentMode, mode);
|
||||
if (mode == currentMode)
|
||||
return true;
|
||||
LT_HEAP_I();
|
||||
startWifiTask();
|
||||
|
||||
if (!currentMode && mode && !_initialized) {
|
||||
// initialize wifi first
|
||||
LT_I("Initializing LwIP");
|
||||
LwIP_Init();
|
||||
reset_wifi_struct();
|
||||
// wifi_manager_init(); // these are events!
|
||||
_initialized = true;
|
||||
}
|
||||
LT_HEAP_I();
|
||||
if (currentMode) {
|
||||
// stop wifi to change mode
|
||||
LT_D_WG("Stopping WiFi to change mode");
|
||||
@@ -37,6 +41,7 @@ bool WiFiClass::mode(WiFiMode mode) {
|
||||
LT_E("Error while changing mode(%u)", mode);
|
||||
return false;
|
||||
}
|
||||
LT_HEAP_I();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,8 @@ WiFiClass::begin(const char *ssid, const char *passphrase, int32_t channel, cons
|
||||
if (!enableSTA(true))
|
||||
return WL_CONNECT_FAILED;
|
||||
|
||||
LT_HEAP_I();
|
||||
|
||||
if (!ssid || *ssid == 0x00 || strlen(ssid) > 32) {
|
||||
LT_W("SSID not specified or too long");
|
||||
return WL_CONNECT_FAILED;
|
||||
@@ -47,18 +49,26 @@ WiFiClass::begin(const char *ssid, const char *passphrase, int32_t channel, cons
|
||||
bool WiFiClass::config(IPAddress localIP, IPAddress gateway, IPAddress subnet, IPAddress dns1, IPAddress dns2) {
|
||||
if (!enableSTA(true))
|
||||
return false;
|
||||
struct netif *ifs = NETIF_RTW_STA;
|
||||
struct ip_addr ipaddr, netmask, gw, d1, d2;
|
||||
ipaddr.addr = localIP;
|
||||
netmask.addr = subnet;
|
||||
gw.addr = gateway;
|
||||
d1.addr = dns1;
|
||||
d2.addr = dns2;
|
||||
netif_set_addr(ifs, &ipaddr, &netmask, &gw);
|
||||
|
||||
struct ip_addr d1, d2;
|
||||
d1.addr = dns1;
|
||||
d2.addr = dns2;
|
||||
if (dns1[0])
|
||||
dns_setserver(0, &d1);
|
||||
if (dns2[0])
|
||||
dns_setserver(0, &d2);
|
||||
|
||||
if (!localIP[0]) {
|
||||
LwIP_DHCP(0, DHCP_START);
|
||||
return true;
|
||||
}
|
||||
struct netif *ifs = NETIF_RTW_STA;
|
||||
struct ip_addr ipaddr, netmask, gw;
|
||||
ipaddr.addr = localIP;
|
||||
netmask.addr = subnet;
|
||||
gw.addr = gateway;
|
||||
netif_set_addr(ifs, &ipaddr, &netmask, &gw);
|
||||
LwIP_DHCP(0, DHCP_STOP);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -94,8 +104,21 @@ bool WiFiClass::reconnect(const uint8_t *bssid) {
|
||||
|
||||
if (ret == RTW_SUCCESS) {
|
||||
dhcpRet = LwIP_DHCP(0, DHCP_START);
|
||||
if (dhcpRet == DHCP_ADDRESS_ASSIGNED)
|
||||
if (dhcpRet == DHCP_ADDRESS_ASSIGNED) {
|
||||
LT_HEAP_I();
|
||||
EventInfo *eventInfo = (EventInfo *)zalloc(sizeof(EventInfo));
|
||||
eventInfo->got_ip.if_index = 0;
|
||||
eventInfo->got_ip.esp_netif = NULL;
|
||||
eventInfo->got_ip.ip_info.ip.addr = localIP();
|
||||
eventInfo->got_ip.ip_info.gw.addr = gatewayIP();
|
||||
eventInfo->got_ip.ip_info.netmask.addr = subnetMask();
|
||||
eventInfo->got_ip.ip_changed = true;
|
||||
// pass the event through the queue
|
||||
wifi_indication(WIFI_EVENT_CONNECT, (char *)eventInfo, ARDUINO_EVENT_WIFI_STA_GOT_IP, -2);
|
||||
// free memory as wifi_indication creates a copy
|
||||
free(eventInfo);
|
||||
return true;
|
||||
}
|
||||
LT_E("DHCP failed; dhcpRet=%d", dhcpRet);
|
||||
wifi_disconnect();
|
||||
return false;
|
||||
|
||||
@@ -6,7 +6,13 @@ from SCons.Script import DefaultEnvironment
|
||||
|
||||
env = DefaultEnvironment()
|
||||
|
||||
# SDK options
|
||||
env.Replace(AMBZ_NO_POLARSSL=True)
|
||||
env.Replace(
|
||||
LIB_AMBZ_SDK_SKIP=[
|
||||
"component/common/api/wifi/wifi_ind.c",
|
||||
]
|
||||
)
|
||||
|
||||
env.SConscript("realtek-ambz-sdk.py", exports="env")
|
||||
env.SConscript("../arduino-common.py", exports="env")
|
||||
|
||||
@@ -73,6 +73,13 @@ def env_add_library(
|
||||
expr = join(base_dir, src[2:-1])
|
||||
|
||||
sources.append(src[0] + "<" + expr + ">")
|
||||
|
||||
# allow removing sources from parent builders
|
||||
key = f"LIB_{name.upper()}_SKIP"
|
||||
if key in env:
|
||||
for expr in env[key]:
|
||||
sources.append("-<" + expr + ">")
|
||||
|
||||
# queue library for further env clone and build
|
||||
env.Prepend(LIBQUEUE=[[join("$BUILD_DIR", name), base_dir, sources]])
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ extern uint32_t SystemCoreClock;
|
||||
#define configSYSTICK_CLOCK_HZ 32768
|
||||
#define configMINIMAL_STACK_SIZE ( ( unsigned short ) 512 )
|
||||
#ifdef CONFIG_WIFI_EN
|
||||
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 110 * 1024 ) )
|
||||
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 160 * 1024 ) )
|
||||
#else
|
||||
#define configTOTAL_HEAP_SIZE ( ( size_t ) ( 20 * 1024 ) )
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user