Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7b42a93
IPv6 for Arduino 3.0.0
Jason2866 Nov 21, 2023
12be369
Fix warning in WifiUdp
s-hadinger Nov 21, 2023
5987211
remove comment / formating
Jason2866 Nov 22, 2023
b8cc73c
Add zone to IPAddress and update WiFiUDP and WiFiGeneric
me-no-dev Dec 18, 2023
305d276
Add from ip_addr_t conversion and better toString implementation
me-no-dev Dec 18, 2023
41fb1a1
Use constant for IPAddress offset
me-no-dev Dec 18, 2023
b37ce6c
Combine hostByName to support both IPv6 and IPv4 results
me-no-dev Dec 18, 2023
e333afb
implement logic to use v6 dns only when global v6 address is assigned…
me-no-dev Jan 11, 2024
c7e63e6
Rename softAPenableIPv6
me-no-dev Jan 11, 2024
55aaa6f
Rename mDNS methods
me-no-dev Jan 11, 2024
c4f366c
fix IPAddress method to work with const address
me-no-dev Jan 11, 2024
701d35f
Some cleanup and do not print zone in IPAddress
me-no-dev Jan 11, 2024
a1b3f16
Merge branch 'master' into feature/ipv6_support
me-no-dev Jan 11, 2024
cb381f2
rename WiFiMulti method
me-no-dev Jan 11, 2024
726496c
Fix AP DHCPS not properly working on recent IDF
me-no-dev Jan 11, 2024
9012f64
Add option to print the zone at the end of IPv6
me-no-dev Jan 11, 2024
58520a7
remove log prints from hostByName
me-no-dev Jan 11, 2024
aad1041
Use correct array length for listing IPv6 addresses
me-no-dev Jan 12, 2024
04a2034
Merge branch 'master' into feature/ipv6_support
me-no-dev Jan 12, 2024
a2a6bd8
Implement some Tasmota requirements
me-no-dev Jan 14, 2024
1fb442d
add 'const' to IPAddress::addr_type()
me-no-dev Jan 14, 2024
0df3aaa
Fix WiFiUdp not updating mapped v4 address
me-no-dev Jan 15, 2024
4161873
Update WiFiServer.cpp
me-no-dev Jan 15, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Some cleanup and do not print zone in IPAddress
  • Loading branch information
me-no-dev committed Jan 11, 2024
commit 701d35f20e587f1e874794b08bf630ba86eb636c
12 changes: 6 additions & 6 deletions cores/esp32/IPAddress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -355,12 +355,12 @@ size_t IPAddress::printTo(Print& p) const
}
}
// add a zone if zone-id is non-zero
if(_zone > 0){
n += p.print('%');
char if_name[NETIF_NAMESIZE];
netif_index_to_name(_zone, if_name);
n += p.print(if_name);
}
// if(_zone > 0){
// n += p.print('%');
// char if_name[NETIF_NAMESIZE];
// netif_index_to_name(_zone, if_name);
// n += p.print(if_name);
// }
return n;
}

Expand Down
2 changes: 1 addition & 1 deletion docs/source/api/wifi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ Function used to enable the IPv6 support.

.. code-block:: arduino

bool softAPenableIPv6();
bool softAPenableIPv6(bool enable=true);

The function will return ``true`` if the configuration is successful.

Expand Down
47 changes: 24 additions & 23 deletions libraries/Ethernet/src/ETH.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -401,13 +401,13 @@ bool ETHClass::beginSPI(eth_phy_type_t type, uint8_t phy_addr, int cs, int irq,

// Init SPI bus
if(_pin_sck >= 0 && _pin_miso >= 0 && _pin_mosi >= 0){
spi_bus_config_t buscfg = {
.mosi_io_num = _pin_mosi,
.miso_io_num = _pin_miso,
.sclk_io_num = _pin_sck,
.quadwp_io_num = -1,
.quadhd_io_num = -1,
};
spi_bus_config_t buscfg;
memset(&buscfg, 0, sizeof(spi_bus_config_t));
buscfg.mosi_io_num = _pin_mosi;
buscfg.miso_io_num = _pin_miso;
buscfg.sclk_io_num = _pin_sck;
buscfg.quadwp_io_num = -1;
buscfg.quadhd_io_num = -1;
ret = spi_bus_initialize(spi_host, &buscfg, SPI_DMA_CH_AUTO);
if(ret != ESP_OK){
log_e("SPI bus initialize failed: %d", ret);
Expand All @@ -433,13 +433,13 @@ bool ETHClass::beginSPI(eth_phy_type_t type, uint8_t phy_addr, int cs, int irq,
phy_config.reset_gpio_num = _pin_rst;

// Configure SPI interface for specific SPI module
spi_device_interface_config_t spi_devcfg = {
.mode = 0,
.clock_speed_hz = _spi_freq_mhz * 1000 * 1000,
.input_delay_ns = 20,
.spics_io_num = _pin_cs,
.queue_size = 20,
};
spi_device_interface_config_t spi_devcfg;
memset(&spi_devcfg, 0, sizeof(spi_device_interface_config_t));
spi_devcfg.mode = 0;
spi_devcfg.clock_speed_hz = _spi_freq_mhz * 1000 * 1000;
spi_devcfg.input_delay_ns = 20;
spi_devcfg.spics_io_num = _pin_cs;
spi_devcfg.queue_size = 20;

esp_eth_mac_t *mac = NULL;
esp_eth_phy_t *phy = NULL;
Expand Down Expand Up @@ -863,15 +863,16 @@ bool ETHClass::setHostname(const char * hostname)

bool ETHClass::enableIPv6(bool en)
{
if(_esp_netif == NULL){
return false;
// if(_esp_netif == NULL){
// return false;
// }
// return esp_netif_create_ip6_linklocal(_esp_netif) == 0;
if (en) {
WiFiGenericClass::setStatusBits(ETH_WANT_IP6_BIT);
} else {
WiFiGenericClass::clearStatusBits(ETH_WANT_IP6_BIT);
}
return esp_netif_create_ip6_linklocal(_esp_netif) == 0;
// if (en)
// WiFiGenericClass::setStatusBits(ETH_WANT_IP6_BIT);
// else
// WiFiGenericClass::clearStatusBits(ETH_WANT_IP6_BIT);
// return true;
return true;
}

IPAddress ETHClass::localIPv6()
Expand Down Expand Up @@ -1048,7 +1049,7 @@ void ETHClass::printInfo(Print & out){
out.print(dnsIP());
out.println();

const char * types[] = { "UNKNOWN", "GLOBAL", "LINK_LOCAL", "SITE_LOCAL", "UNIQUE_LOCAL", "IPV4_MAPPED_IPV6" };
static const char * types[] = { "UNKNOWN", "GLOBAL", "LINK_LOCAL", "SITE_LOCAL", "UNIQUE_LOCAL", "IPV4_MAPPED_IPV6" };
esp_ip6_addr_t if_ip6[5];
int v6addrs = esp_netif_get_all_ip6(_esp_netif, if_ip6);
for (int i = 0; i < v6addrs; ++i){
Expand Down
4 changes: 2 additions & 2 deletions libraries/WiFi/examples/WiFiIPv6/WiFiIPv6.ino
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ void WiFiEvent(WiFiEvent_t event){
case ARDUINO_EVENT_WIFI_AP_START:
//can set ap hostname here
WiFi.softAPsetHostname(AP_SSID);
//enable ap ipv6 here
WiFi.softAPenableIPv6();
break;
case ARDUINO_EVENT_WIFI_STA_START:
//set sta hostname here
Expand Down Expand Up @@ -105,6 +103,8 @@ void setup(){
WiFi.disconnect(true);
WiFi.onEvent(WiFiEvent); // Will call WiFiEvent() from another thread.
WiFi.mode(WIFI_MODE_APSTA);
//enable ap ipv6 here
WiFi.softAPenableIPv6();
WiFi.softAP(AP_SSID);
//enable sta ipv6 here
WiFi.enableIPv6();
Expand Down
15 changes: 11 additions & 4 deletions libraries/WiFi/src/WiFiAP.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -411,12 +411,18 @@ bool WiFiAPClass::softAPsetHostname(const char * hostname)
* Enable IPv6 on the softAP interface.
* @return true on success
*/
bool WiFiAPClass::softAPenableIPv6()
bool WiFiAPClass::softAPenableIPv6(bool enable)
{
if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){
return false;
if (enable) {
WiFiGenericClass::setStatusBits(AP_WANT_IP6_BIT);
} else {
WiFiGenericClass::clearStatusBits(AP_WANT_IP6_BIT);
}
return esp_netif_create_ip6_linklocal(get_esp_interface_netif(ESP_IF_WIFI_AP)) == ESP_OK;
return true;
// if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){
// return false;
// }
// return esp_netif_create_ip6_linklocal(get_esp_interface_netif(ESP_IF_WIFI_AP)) == ESP_OK;
}

/**
Expand All @@ -430,6 +436,7 @@ IPAddress WiFiAPClass::softAPIPv6()
if(WiFiGenericClass::getMode() == WIFI_MODE_NULL){
return IPAddress(IPv6);
}

if(esp_netif_get_ip6_linklocal(get_esp_interface_netif(ESP_IF_WIFI_STA), &addr)){
return IPAddress(IPv6);
}
Expand Down
2 changes: 1 addition & 1 deletion libraries/WiFi/src/WiFiAP.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class WiFiAPClass
IPAddress softAPSubnetMask();
uint8_t softAPSubnetCIDR();

bool softAPenableIPv6();
bool softAPenableIPv6(bool enable=true);
IPAddress softAPIPv6();

const char * softAPgetHostname();
Expand Down
55 changes: 39 additions & 16 deletions libraries/WiFi/src/WiFiGeneric.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ extern "C" {
#include "lwip/opt.h"
#include "lwip/err.h"
#include "lwip/dns.h"
#include "lwip/netif.h"
#include "dhcpserver/dhcpserver.h"
#include "dhcpserver/dhcpserver_options.h"

Expand Down Expand Up @@ -459,7 +460,13 @@ static void _arduino_event_cb(void* arg, esp_event_base_t event_base, int32_t ev
} else if (event_base == IP_EVENT && event_id == IP_EVENT_GOT_IP6) {
ip_event_got_ip6_t * event = (ip_event_got_ip6_t*)event_data;
esp_interface_t iface = get_esp_netif_interface(event->esp_netif);
log_v("IF[%d] Got IPv6: IP Index: %d, Zone: %d, " IPV6STR, iface, event->ip_index, event->ip6_info.ip.zone, IPV62STR(event->ip6_info.ip));
#if ARDUHAL_LOG_LEVEL >= ARDUHAL_LOG_LEVEL_VERBOSE
char if_name[NETIF_NAMESIZE] = {0,};
netif_index_to_name(event->ip6_info.ip.zone, if_name);
esp_ip6_addr_type_t addr_type = esp_netif_ip6_get_addr_type(&event->ip6_info.ip);
static const char * addr_types[] = { "UNKNOWN", "GLOBAL", "LINK_LOCAL", "SITE_LOCAL", "UNIQUE_LOCAL", "IPV4_MAPPED_IPV6" };
log_v("IF %s Got IPv6: Interface: %d, IP Index: %d, Type: %s, Zone: %d (%s), Address: " IPV6STR, esp_netif_get_desc(event->esp_netif), iface, event->ip_index, addr_types[addr_type], event->ip6_info.ip.zone, if_name, IPV62STR(event->ip6_info.ip));
#endif
memcpy(&arduino_event.event_info.got_ip6, event_data, sizeof(ip_event_got_ip6_t));
if(iface == ESP_IF_WIFI_STA){
arduino_event.event_id = ARDUINO_EVENT_WIFI_STA_GOT_IP6;
Expand Down Expand Up @@ -554,14 +561,16 @@ static void _arduino_event_cb(void* arg, esp_event_base_t event_base, int32_t ev
}
}

static uint32_t _initial_bits = NET_DNS_IDLE_BIT;

static bool _start_network_event_task(){
if(!_arduino_event_group){
_arduino_event_group = xEventGroupCreate();
if(!_arduino_event_group){
log_e("Network Event Group Create Failed!");
return false;
}
xEventGroupSetBits(_arduino_event_group, WIFI_DNS_IDLE_BIT);
xEventGroupSetBits(_arduino_event_group, _initial_bits);
}
if(!_arduino_event_queue){
_arduino_event_queue = xQueueCreate(32, sizeof(arduino_event_t*));
Expand Down Expand Up @@ -909,21 +918,23 @@ bool WiFiGenericClass::setHostname(const char * hostname)

int WiFiGenericClass::setStatusBits(int bits){
if(!_arduino_event_group){
return 0;
_initial_bits |= bits;
return _initial_bits;
}
return xEventGroupSetBits(_arduino_event_group, bits);
}

int WiFiGenericClass::clearStatusBits(int bits){
if(!_arduino_event_group){
return 0;
_initial_bits &= ~bits;
return _initial_bits;
}
return xEventGroupClearBits(_arduino_event_group, bits);
}

int WiFiGenericClass::getStatusBits(){
if(!_arduino_event_group){
return 0;
return _initial_bits;
}
return xEventGroupGetBits(_arduino_event_group);
}
Expand Down Expand Up @@ -1114,6 +1125,9 @@ esp_err_t WiFiGenericClass::_eventCallback(arduino_event_t *event)

} else if(event->event_id == ARDUINO_EVENT_WIFI_AP_START) {
setStatusBits(AP_STARTED_BIT);
if (getStatusBits() & AP_WANT_IP6_BIT){
esp_netif_create_ip6_linklocal(get_esp_interface_netif(ESP_IF_WIFI_AP));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm seeing a race condition here. esp_netif_create_ip6_linklocal returns -1 because:

get_esp_interface_netif(ESP_IF_WIFI_AP)->lwip_netif->flags has the flag NETIF_FLAG_UP not set when it is called. However if I add
delay(1)
the flag NETIF_FLAG_UP is set and the link-local address is successfully assigned.

I have no clue why and I have never seen this behavior with our previous code.

Any ideas?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also noticed some weir stuff on the AP interface. I will report those and see what is causing it. On our end, it's as it should. We need to do this on START for the AP and CONNECT on the rest

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, sorry. The race condition is at ARDUINO_EVENT_WIFI_STA_CONNECTED, the two codes look alike, I mixed up both. Let me copy it at the right place.

}
} else if(event->event_id == ARDUINO_EVENT_WIFI_AP_STOP) {
clearStatusBits(AP_STARTED_BIT | AP_HAS_CLIENT_BIT);
} else if(event->event_id == ARDUINO_EVENT_WIFI_AP_STACONNECTED) {
Expand Down Expand Up @@ -1150,16 +1164,25 @@ esp_err_t WiFiGenericClass::_eventCallback(arduino_event_t *event)
clearStatusBits(ETH_HAS_IP_BIT);

} else if(event->event_id == ARDUINO_EVENT_WIFI_STA_GOT_IP6) {
setStatusBits(STA_CONNECTED_BIT | STA_HAS_IP6_BIT);
if(esp_netif_ip6_get_addr_type((esp_ip6_addr_t*)&(event->event_info.got_ip6.ip6_info.ip)) == ESP_IP6_ADDR_IS_GLOBAL){
setStatusBits(STA_CONNECTED_BIT);
esp_ip6_addr_type_t addr_type = esp_netif_ip6_get_addr_type((esp_ip6_addr_t*)&(event->event_info.got_ip6.ip6_info.ip));
if(addr_type == ESP_IP6_ADDR_IS_GLOBAL){
setStatusBits(STA_HAS_IP6_GLOBAL_BIT);
} else if(addr_type == ESP_IP6_ADDR_IS_LINK_LOCAL){
setStatusBits(STA_HAS_IP6_BIT);
}
} else if(event->event_id == ARDUINO_EVENT_WIFI_AP_GOT_IP6) {
setStatusBits(AP_HAS_IP6_BIT);
esp_ip6_addr_type_t addr_type = esp_netif_ip6_get_addr_type((esp_ip6_addr_t*)&(event->event_info.got_ip6.ip6_info.ip));
if(addr_type == ESP_IP6_ADDR_IS_LINK_LOCAL){
setStatusBits(AP_HAS_IP6_BIT);
}
} else if(event->event_id == ARDUINO_EVENT_ETH_GOT_IP6) {
setStatusBits(ETH_CONNECTED_BIT | ETH_HAS_IP6_BIT);
if(esp_netif_ip6_get_addr_type((esp_ip6_addr_t*)&(event->event_info.got_ip6.ip6_info.ip)) == ESP_IP6_ADDR_IS_GLOBAL){
setStatusBits(ETH_CONNECTED_BIT);
esp_ip6_addr_type_t addr_type = esp_netif_ip6_get_addr_type((esp_ip6_addr_t*)&(event->event_info.got_ip6.ip6_info.ip));
if(addr_type == ESP_IP6_ADDR_IS_GLOBAL){
setStatusBits(ETH_HAS_IP6_GLOBAL_BIT);
} else if(addr_type == ESP_IP6_ADDR_IS_LINK_LOCAL){
setStatusBits(ETH_HAS_IP6_BIT);
}
} else if(event->event_id == ARDUINO_EVENT_SC_GOT_SSID_PSWD) {
WiFi.begin(
Expand Down Expand Up @@ -1589,7 +1612,7 @@ static void wifi_dns_found_callback(const char *name, const ip_addr_t *ipaddr, v
} else {
parameters->result = -1;
}
xEventGroupSetBits(_arduino_event_group, WIFI_DNS_DONE_BIT);
xEventGroupSetBits(_arduino_event_group, NET_DNS_DONE_BIT);
}

/**
Expand Down Expand Up @@ -1632,8 +1655,8 @@ int WiFiGenericClass::hostByName(const char* aHostname, IPAddress& aResult, bool
aResult.to_ip_addr_t(&(params.addr));

if (!aResult.fromString(aHostname)) {
waitStatusBits(WIFI_DNS_IDLE_BIT, 16000);
clearStatusBits(WIFI_DNS_IDLE_BIT | WIFI_DNS_DONE_BIT);
waitStatusBits(NET_DNS_IDLE_BIT, 16000);
clearStatusBits(NET_DNS_IDLE_BIT | NET_DNS_DONE_BIT);

err = esp_netif_tcpip_exec(wifi_gethostbyname_tcpip_ctx, &params);
if (err == ERR_OK) {
Expand All @@ -1646,14 +1669,14 @@ int WiFiGenericClass::hostByName(const char* aHostname, IPAddress& aResult, bool
}
Serial.println();
} else if (err == ERR_INPROGRESS) {
waitStatusBits(WIFI_DNS_DONE_BIT, 15000); //real internal timeout in lwip library is 14[s]
clearStatusBits(WIFI_DNS_DONE_BIT);
waitStatusBits(NET_DNS_DONE_BIT, 15000); //real internal timeout in lwip library is 14[s]
clearStatusBits(NET_DNS_DONE_BIT);
if (params.result == 1) {
aResult.from_ip_addr_t(&(params.addr));
err = ERR_OK;
}
}
setStatusBits(WIFI_DNS_IDLE_BIT);
setStatusBits(NET_DNS_IDLE_BIT);
}
if (err == ERR_OK) {
return 1;
Expand Down
52 changes: 28 additions & 24 deletions libraries/WiFi/src/WiFiGeneric.h
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,34 @@ typedef void (*WiFiEventSysCb)(arduino_event_t *event);

typedef size_t wifi_event_id_t;

// General Flags
static const int NET_DNS_IDLE_BIT = BIT0;
static const int NET_DNS_DONE_BIT = BIT1;
// WiFi Scan Flags
static const int WIFI_SCANNING_BIT = BIT2;
static const int WIFI_SCAN_DONE_BIT = BIT3;
// AP Flags
static const int AP_STARTED_BIT = BIT4;
static const int AP_HAS_IP6_BIT = BIT5;
static const int AP_HAS_CLIENT_BIT = BIT6;
static const int AP_WANT_IP6_BIT = BIT7;
// STA Flags
static const int STA_STARTED_BIT = BIT8;
static const int STA_CONNECTED_BIT = BIT9;
static const int STA_HAS_IP_BIT = BIT10;
static const int STA_HAS_IP6_BIT = BIT11;
static const int STA_HAS_IP6_GLOBAL_BIT = BIT12;
static const int STA_WANT_IP6_BIT = BIT13;
// ETH Flags
static const int ETH_STARTED_BIT = BIT14;
static const int ETH_CONNECTED_BIT = BIT15;
static const int ETH_HAS_IP_BIT = BIT16;
static const int ETH_HAS_IP6_BIT = BIT17;
static const int ETH_HAS_IP6_GLOBAL_BIT = BIT18;
static const int ETH_WANT_IP6_BIT = BIT19;
// Masks
static const int NET_HAS_IP6_GLOBAL_BIT = STA_HAS_IP6_GLOBAL_BIT | ETH_HAS_IP6_GLOBAL_BIT;

typedef enum {
WIFI_POWER_19_5dBm = 78,// 19.5dBm
WIFI_POWER_19dBm = 76,// 19dBm
Expand All @@ -127,30 +155,6 @@ typedef enum {
WIFI_POWER_MINUS_1dBm = -4// -1dBm
} wifi_power_t;

static const int AP_STARTED_BIT = BIT0;
static const int AP_HAS_IP6_BIT = BIT1;
static const int AP_HAS_CLIENT_BIT = BIT2;
static const int STA_STARTED_BIT = BIT3;
static const int STA_CONNECTED_BIT = BIT4;
static const int STA_HAS_IP_BIT = BIT5;
static const int STA_HAS_IP6_BIT = BIT6;
static const int ETH_STARTED_BIT = BIT7;
static const int ETH_CONNECTED_BIT = BIT8;
static const int ETH_HAS_IP_BIT = BIT9;
static const int ETH_HAS_IP6_BIT = BIT10;

static const int WIFI_SCANNING_BIT = BIT11;
static const int WIFI_SCAN_DONE_BIT= BIT12;
static const int WIFI_DNS_IDLE_BIT = BIT13;
static const int WIFI_DNS_DONE_BIT = BIT14;

static const int STA_WANT_IP6_BIT = BIT15;
static const int ETH_WANT_IP6_BIT = BIT16;

static const int STA_HAS_IP6_GLOBAL_BIT = BIT17;
static const int ETH_HAS_IP6_GLOBAL_BIT = BIT18;
static const int NET_HAS_IP6_GLOBAL_BIT = STA_HAS_IP6_GLOBAL_BIT | ETH_HAS_IP6_GLOBAL_BIT;

typedef enum {
WIFI_RX_ANT0 = 0,
WIFI_RX_ANT1,
Expand Down
3 changes: 2 additions & 1 deletion libraries/WiFi/src/WiFiMulti.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,9 @@ uint8_t WiFiMulti::run(uint32_t connectTimeout)
if(bestNetwork.ssid) {
log_i("[WIFI] Connecting BSSID: %02X:%02X:%02X:%02X:%02X:%02X SSID: %s Channel: %d (%d)", bestBSSID[0], bestBSSID[1], bestBSSID[2], bestBSSID[3], bestBSSID[4], bestBSSID[5], bestNetwork.ssid, bestChannel, bestNetworkDb);

if (ipv6_support == true)
if (ipv6_support == true) {
WiFi.enableIPv6();
}
WiFi.begin(bestNetwork.ssid, bestNetwork.passphrase, bestChannel, bestBSSID);
status = WiFi.status();

Expand Down
Loading