diff --git a/arduino/libretuya/api/WiFiClient.h b/arduino/libretuya/api/WiFiClient.h index 5a0c22a..32dcb0d 100644 --- a/arduino/libretuya/api/WiFiClient.h +++ b/arduino/libretuya/api/WiFiClient.h @@ -24,41 +24,46 @@ class IWiFiClient : public Client { public: - IWiFiClient(int fd) {} + IWiFiClient() {} - int connect(IPAddress ip, uint16_t port, int32_t timeout); - int connect(const char *host, uint16_t port, int32_t timeout); + IWiFiClient(int sock) {} - size_t write(Stream &stream); + virtual int connect(IPAddress ip, uint16_t port, int32_t timeout) = 0; + virtual int connect(const char *host, uint16_t port, int32_t timeout) = 0; - int fd() const; - int socket(); - int setTimeout(uint32_t seconds); + virtual size_t write(Stream &stream) = 0; - IWiFiClient &operator=(const WiFiClient &other); + virtual int fd() const = 0; + virtual int socket() = 0; + virtual int setTimeout(uint32_t seconds) = 0; - bool operator==(const bool value) { + virtual IWiFiClient &operator=(const IWiFiClient &other) = 0; + virtual bool operator==(const IWiFiClient &other) const = 0; + + operator bool() { + return connected(); + } + + virtual bool operator==(const bool value) { return bool() == value; } - bool operator!=(const bool value) { + virtual bool operator!=(const bool value) { return bool() != value; } - bool operator==(const IWiFiClient &); - - bool operator!=(const IWiFiClient &rhs) { - return !this->operator==(rhs); + virtual bool operator!=(const IWiFiClient &other) { + return !this->operator==(other); }; - IPAddress remoteIP() const; - IPAddress remoteIP(int fd) const; - uint16_t remotePort() const; - uint16_t remotePort(int fd) const; - IPAddress localIP() const; - IPAddress localIP(int fd) const; - uint16_t localPort() const; - uint16_t localPort(int fd) const; + virtual IPAddress remoteIP() const = 0; + virtual IPAddress remoteIP(int sock) const = 0; + virtual uint16_t remotePort() const = 0; + virtual uint16_t remotePort(int sock) const = 0; + virtual IPAddress localIP() const = 0; + virtual IPAddress localIP(int sock) const = 0; + virtual uint16_t localPort() const = 0; + virtual uint16_t localPort(int sock) const = 0; using Print::write; }; diff --git a/arduino/realtek-ambz/libraries/Flash/Flash.cpp b/arduino/realtek-ambz/libraries/Flash/Flash.cpp index 5c6f01a..d921d9e 100644 --- a/arduino/realtek-ambz/libraries/Flash/Flash.cpp +++ b/arduino/realtek-ambz/libraries/Flash/Flash.cpp @@ -1,5 +1,17 @@ #include "Flash.h" +#include + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +#include + +#ifdef __cplusplus +} // extern "C" +#endif + FlashClass::FlashClass() { flash = NULL; } diff --git a/arduino/realtek-ambz/libraries/Flash/Flash.h b/arduino/realtek-ambz/libraries/Flash/Flash.h index 3dd1c08..25b5ec1 100644 --- a/arduino/realtek-ambz/libraries/Flash/Flash.h +++ b/arduino/realtek-ambz/libraries/Flash/Flash.h @@ -2,15 +2,8 @@ #include "api/Flash.h" -#ifdef __cplusplus -extern "C" { -#endif // __cplusplus - -#include - -#ifdef __cplusplus -} // extern "C" -#endif +struct flash_s; +typedef struct flash_s flash_t; class FlashClass : public IFlashClass { private: diff --git a/arduino/realtek-ambz/libraries/WiFi/WiFi.h b/arduino/realtek-ambz/libraries/WiFi/WiFi.h index 80f32f5..136b7b8 100644 --- a/arduino/realtek-ambz/libraries/WiFi/WiFi.h +++ b/arduino/realtek-ambz/libraries/WiFi/WiFi.h @@ -15,6 +15,8 @@ extern "C" { } // extern "C" #endif +#include "WiFiClient.h" + class WiFiClass : public IWiFiClass, public IWiFiGenericClass, public IWiFiSTAClass, diff --git a/arduino/realtek-ambz/libraries/WiFi/WiFiClient.cpp b/arduino/realtek-ambz/libraries/WiFi/WiFiClient.cpp new file mode 100644 index 0000000..4d2b074 --- /dev/null +++ b/arduino/realtek-ambz/libraries/WiFi/WiFiClient.cpp @@ -0,0 +1,341 @@ +#include "WiFiClient.h" +#include "WiFiPriv.h" + +// I think I don't understand how that works. +// For some reason, LwIP uses a different (extern) errno, +// while this macro refers to a function __errno, which +// reads a totally different variable. +#undef errno +#include + +// disable #defines removing lwip_ prefix +#undef LWIP_COMPAT_SOCKETS +#define LWIP_COMPAT_SOCKETS 0 +#include + +WiFiClient::WiFiClient() : _sock(-1), _connected(false) { + _timeout = WIFI_CLIENT_CONNECT_TIMEOUT; +} + +WiFiClient::WiFiClient(int sock) { + _sock = sock; + _connected = true; + _rxBuffer.reset(new LwIPRxBuffer(sock)); + _timeout = WIFI_CLIENT_CONNECT_TIMEOUT; +} + +WiFiClient::~WiFiClient() { + stop(); +} + +WiFiClient &WiFiClient::operator=(const IWiFiClient &other) { + stop(); + // _sock = other._sock; + // _connected = other._connected; + // _rxBuffer = other._rxBuffer; + return *this; +} + +bool WiFiClient::operator==(const IWiFiClient &other) const { + return _sock == other.fd() && remoteIP() == other.remoteIP() && remotePort() == other.remotePort(); +} + +int WiFiClient::connect(IPAddress ip, uint16_t port) { + return connect(ip, port, _timeout); +} + +int WiFiClient::connect(const char *host, uint16_t port) { + return connect(host, port, _timeout); +} + +int WiFiClient::connect(const char *host, uint16_t port, int32_t timeout) { + IPAddress ip = WiFi.hostByName(host); + if (!ip) + return 0; + return connect(ip, port, timeout); +} + +int WiFiClient::connect(IPAddress ip, uint16_t port, int32_t timeout) { + int sock = lwip_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + return -1; + } + + lwip_fcntl(sock, F_SETFL, lwip_fcntl(sock, F_GETFL, 0) | O_NONBLOCK); + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = ip; + addr.sin_port = htons(port); + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + FD_SET(sock, &fdset); + tv.tv_sec = 0; + tv.tv_usec = timeout * 1000; + + int res = lwip_connect(sock, (struct sockaddr *)&addr, sizeof(addr)); + if (res < 0 && errno != EINPROGRESS) { + printf("lwip_connect() errno %d\r\n", errno); + lwip_close(sock); + return -1; + } + + res = lwip_select(sock + 1, NULL, &fdset, NULL, timeout < 0 ? NULL : &tv); + if (res < 0) { + printf("lwip_select() errno %d\r\n", errno); + lwip_close(sock); + return 0; + } + if (res == 0) { + printf("lwip_select() timeout errno %d\r\n", errno); + lwip_close(sock); + return 0; + } + + int sockerr; + socklen_t len = (socklen_t)sizeof(sockerr); + res = lwip_getsockopt(sock, SOL_SOCKET, SO_ERROR, &sockerr, &len); + + if (res < 0 || sockerr != 0) { + lwip_close(sock); + return 0; + } + + int enable = 1; + lwip_setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)); + lwip_setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout)); + lwip_setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, &enable, sizeof(enable)); + lwip_setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &enable, sizeof(enable)); + + lwip_fcntl(sock, F_SETFL, lwip_fcntl(sock, F_GETFL, 0) & ~O_NONBLOCK); + + if (_sock != -1) + lwip_close(_sock); + _sock = sock; + _connected = true; + _rxBuffer.reset(new LwIPRxBuffer(_sock)); + return 1; +} + +size_t WiFiClient::write(uint8_t data) { + return write(&data, 1); +} + +size_t WiFiClient::write(Stream &stream) { + uint8_t *buf = (uint8_t *)malloc(1360); + if (!buf) { + return 0; + } + size_t toRead = 0, toWrite = 0, written = 0; + size_t available = stream.available(); + while (available) { + toRead = (available > 1360) ? 1360 : available; + toWrite = stream.readBytes(buf, toRead); + written += write(buf, toWrite); + available = stream.available(); + } + free(buf); + return written; +} + +size_t WiFiClient::write(const uint8_t *buf, size_t size) { + if (_sock < 0 || !_connected || !size) { + setWriteError(); + return 0; + } + + int retry = WIFI_CLIENT_WRITE_RETRY; + int written = 0; + while (retry) { + fd_set fdset; + struct timeval tv; + FD_ZERO(&fdset); + FD_SET(_sock, &fdset); + tv.tv_sec = 0; + tv.tv_usec = WIFI_CLIENT_SELECT_TIMEOUT * 1000; + retry--; + + if (lwip_select(_sock + 1, NULL, &fdset, NULL, &tv) < 0) { + return 0; + } + + if (FD_ISSET(_sock, &fdset)) { + int res = lwip_send(_sock, buf, size, MSG_DONTWAIT); + if (res > 0) { + written += res; + if (res >= size) { + retry = 0; + } else { + buf += res; + size -= res; + retry = WIFI_CLIENT_WRITE_RETRY; + } + } else if (res < 0 && errno != EAGAIN) { + setWriteError(res); + _connected = false; + retry = 0; + } else { + // Try again + } + } + } + return written; +} + +int WiFiClient::available() { + if (!_connected) + return 0; + int res = _rxBuffer->available(); + if (_rxBuffer->failed()) { + stop(); + } + return res; +} + +int WiFiClient::fd() const { + return _sock; +} + +int WiFiClient::socket() { + return _sock; +} + +int WiFiClient::setTimeout(uint32_t seconds) { + Client::setTimeout(seconds * 1000); + lwip_setsockopt(_sock, SOL_SOCKET, SO_RCVTIMEO, &_timeout, sizeof(_timeout)); + return lwip_setsockopt(_sock, SOL_SOCKET, SO_SNDTIMEO, &_timeout, sizeof(_timeout)); +} + +int WiFiClient::read() { + uint8_t data; + int res = read(&data, 1); + if (res < 0) + return res; + if (res == 0) + return -1; + return data; +} + +int WiFiClient::read(uint8_t *buf, size_t size) { + int res = -1; + if (_rxBuffer) { + res = _rxBuffer->read(buf, size); + if (_rxBuffer->failed()) { + stop(); + } + } + return res; +} + +int WiFiClient::peek() { + int res = -1; + if (_rxBuffer) { + res = _rxBuffer->peek(); + if (_rxBuffer->failed()) { + stop(); + } + } + return res; +} + +void WiFiClient::flush() { + int res; + size_t len = available(); + if (!len) + return; + uint8_t *buf = (uint8_t *)malloc(WIFI_CLIENT_FLUSH_BUF_SIZE); + if (!buf) + return; + while (len) { + res = lwip_recv(_sock, buf, LWIP_MIN(len, WIFI_CLIENT_FLUSH_BUF_SIZE), MSG_DONTWAIT); + if (res < 0) { + stop(); + break; + } + len -= res; + } + free(buf); +} + +void WiFiClient::stop() { + if (_sock != -1) + lwip_close(_sock); + _sock = -1; + _connected = false; + _rxBuffer = NULL; +} + +uint8_t WiFiClient::connected() { + if (_connected) { + uint8_t dummy; + if (lwip_recv(_sock, &dummy, 0, MSG_DONTWAIT) < 0) { + switch (errno) { + case EWOULDBLOCK: + case ENOENT: // caused by vfs + _connected = true; + break; + case ENOTCONN: + case EPIPE: + case ECONNRESET: + case ECONNREFUSED: + case ECONNABORTED: + _connected = false; + break; + default: + _connected = true; + break; + } + } + } + return _connected; +} + +IPAddress getaddr(int sock, int (*func)(int, struct sockaddr *, socklen_t *)) { + struct sockaddr addr; + socklen_t len = sizeof(addr); + func(sock, &addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return IPAddress((uint32_t)(s->sin_addr.s_addr)); +} + +uint16_t getport(int sock, int (*func)(int, struct sockaddr *, socklen_t *)) { + struct sockaddr addr; + socklen_t len = sizeof(addr); + func(sock, &addr, &len); + struct sockaddr_in *s = (struct sockaddr_in *)&addr; + return ntohs(s->sin_port); +} + +IPAddress WiFiClient::remoteIP() const { + return getaddr(_sock, lwip_getpeername); +} + +IPAddress WiFiClient::remoteIP(int fd) const { + return getaddr(fd, lwip_getpeername); +} + +uint16_t WiFiClient::remotePort() const { + return getport(_sock, lwip_getpeername); +} + +uint16_t WiFiClient::remotePort(int fd) const { + return getport(fd, lwip_getpeername); +} + +IPAddress WiFiClient::localIP() const { + return getaddr(_sock, lwip_getsockname); +} + +IPAddress WiFiClient::localIP(int fd) const { + return getaddr(fd, lwip_getsockname); +} + +uint16_t WiFiClient::localPort() const { + return getport(_sock, lwip_getsockname); +} + +uint16_t WiFiClient::localPort(int fd) const { + return getport(fd, lwip_getsockname); +} diff --git a/arduino/realtek-ambz/libraries/WiFi/WiFiClient.h b/arduino/realtek-ambz/libraries/WiFi/WiFiClient.h new file mode 100644 index 0000000..bfe4607 --- /dev/null +++ b/arduino/realtek-ambz/libraries/WiFi/WiFiClient.h @@ -0,0 +1,51 @@ +#pragma once + +#include +#include +#include + +class WiFiClient : public IWiFiClient { + private: + int _sock; + bool _connected; + std::shared_ptr _rxBuffer; + + public: + WiFiClient(); + WiFiClient(int sock); + ~WiFiClient(); + + int connect(IPAddress ip, uint16_t port); + int connect(const char *host, uint16_t port); + int connect(IPAddress ip, uint16_t port, int32_t timeout); + int connect(const char *host, uint16_t port, int32_t timeout); + + size_t write(uint8_t data); + size_t write(const uint8_t *buf, size_t size); + size_t write(Stream &stream); + + int available(); + int fd() const; + int socket(); + int setTimeout(uint32_t seconds); + + int read(); + int read(uint8_t *buf, size_t size); + int peek(); + void flush(); + void stop(); + uint8_t connected(); + + WiFiClient &operator=(const IWiFiClient &other); + + bool operator==(const IWiFiClient &other) const; + + IPAddress remoteIP() const; + IPAddress remoteIP(int sock) const; + uint16_t remotePort() const; + uint16_t remotePort(int sock) const; + IPAddress localIP() const; + IPAddress localIP(int sock) const; + uint16_t localPort() const; + uint16_t localPort(int sock) const; +}; diff --git a/arduino/realtek-ambz/libraries/WiFi/WiFiPriv.h b/arduino/realtek-ambz/libraries/WiFi/WiFiPriv.h index 09b512c..b8072aa 100644 --- a/arduino/realtek-ambz/libraries/WiFi/WiFiPriv.h +++ b/arduino/realtek-ambz/libraries/WiFi/WiFiPriv.h @@ -4,7 +4,19 @@ extern "C" { #endif // __cplusplus -#define LWIP_NETIF_HOSTNAME 1 // this is defined in PIO builder +// these are defined in PIO builder (for IDE to understand) +#define LWIP_TIMEVAL_PRIVATE 0 +#define LWIP_NETIF_HOSTNAME 1 +// this should define all errno values, as well as the extern int +#define LWIP_PROVIDE_ERRNO 1 +#define LWIP_SO_RCVBUF 1 + +#define MAX_SOCK_NUM 4 +#define WIFI_CLIENT_CONNECT_TIMEOUT 3000 +#define WIFI_CLIENT_READ_TIMEOUT 3000 +#define WIFI_CLIENT_WRITE_RETRY 10 +#define WIFI_CLIENT_SELECT_TIMEOUT 1000 +#define WIFI_CLIENT_FLUSH_BUF_SIZE 1024 #include #include @@ -12,6 +24,7 @@ extern "C" { #include #include #include +#include #include #include #include diff --git a/builder/arduino-common.py b/builder/arduino-common.py index 7c6315d..ae9a9f2 100644 --- a/builder/arduino-common.py +++ b/builder/arduino-common.py @@ -16,7 +16,7 @@ env.Prepend( CPPPATH=[ # fmt: off join(API_DIR), - join(LT_API_DIR), # for libretuya-api + join(LT_API_DIR), # fmt: on ] ) diff --git a/builder/frameworks/realtek-ambz-arduino.py b/builder/frameworks/realtek-ambz-arduino.py index 59dbc9d..d92f3ed 100644 --- a/builder/frameworks/realtek-ambz-arduino.py +++ b/builder/frameworks/realtek-ambz-arduino.py @@ -55,9 +55,10 @@ env.Append( "ARDUINO_SDK", "ARDUINO_ARCH_AMBZ", f"BOARD_{family}", - # the SDK declares bool and #ifndef somehow doesn't work... + # the SDK declares bool if not defined before + # which conflicts with C++ built-in bool # so it's either -fpermissive or this: - ("bool", "unsigned char"), + ("bool", "bool"), # enable LwIPRxBuffer "LT_HAS_LWIP", ], @@ -110,8 +111,6 @@ env["CPPPATH"].remove( # Sources sources_core = [ # fmt: off - "+<" + CORE_DIR + "/cores/arduino/ard_socket.c>", - "+<" + CORE_DIR + "/cores/arduino/ard_ssl.c>", "+<" + CORE_DIR + "/cores/arduino/avr/dtostrf.c>", "+<" + CORE_DIR + "/cores/arduino/b64.cpp>", "+<" + CORE_DIR + "/cores/arduino/cxxabi-compat.cpp>", @@ -123,13 +122,11 @@ sources_core = [ "+<" + CORE_DIR + "/cores/arduino/PowerManagement.cpp>", "+<" + CORE_DIR + "/cores/arduino/RingBuffer.cpp>", "+<" + CORE_DIR + "/cores/arduino/rtl_sys.cpp>", - "+<" + CORE_DIR + "/cores/arduino/server_drv.cpp>", "+<" + CORE_DIR + "/cores/arduino/spiffs/spiffs_cache.c>", "+<" + CORE_DIR + "/cores/arduino/spiffs/spiffs_check.c>", "+<" + CORE_DIR + "/cores/arduino/spiffs/spiffs_gc.c>", "+<" + CORE_DIR + "/cores/arduino/spiffs/spiffs_hydrogen.c>", "+<" + CORE_DIR + "/cores/arduino/spiffs/spiffs_nucleus.c>", - "+<" + CORE_DIR + "/cores/arduino/ssl_drv.cpp>", "+<" + CORE_DIR + "/cores/arduino/Tone.cpp>", "+<" + CORE_DIR + "/cores/arduino/WebSocketClient.cpp>", "+<" + CORE_DIR + "/cores/arduino/WInterrupts.c>", @@ -170,6 +167,7 @@ sources_libs = [ "+<" + CORE_DIR +"/libraries/Flash/Flash.cpp>", "+<" + CORE_DIR +"/libraries/WiFi/WiFi.cpp>", "+<" + CORE_DIR +"/libraries/WiFi/WiFiAP.cpp>", + "+<" + CORE_DIR +"/libraries/WiFi/WiFiClient.cpp>", "+<" + CORE_DIR +"/libraries/WiFi/WiFiGeneric.cpp>", "+<" + CORE_DIR +"/libraries/WiFi/WiFiScan.cpp>", "+<" + CORE_DIR +"/libraries/WiFi/WiFiSTA.cpp>", diff --git a/builder/frameworks/realtek-ambz-sdk.py b/builder/frameworks/realtek-ambz-sdk.py index 1646e23..8e3d364 100644 --- a/builder/frameworks/realtek-ambz-sdk.py +++ b/builder/frameworks/realtek-ambz-sdk.py @@ -70,7 +70,11 @@ env.Replace( "CONFIG_PLATFORM_8711B", ("F_CPU", "166000000L"), ("LWIP_TIMEVAL_PRIVATE", "0"), - ("LWIP_NETIF_HOSTNAME", "1"), + ("LWIP_NETIF_HOSTNAME", "1"), # to support hostname changing + ("LWIP_PROVIDE_ERRNO", "1"), # for extern int errno and codes + ("LWIP_SO_RCVBUF", "1"), # for ioctl(FIONREAD) + ("INT_MAX", "2147483647"), # for RECV_BUFSIZE_DEFAULT + ("ERRNO", "1"), # for LwIP ], LINKFLAGS=[ "-mcpu=cortex-m4",