#include "wifi_module.hpp" #include "network_connection.hpp" #include "util.hpp" #include #include WiFiModule::WiFiModule(const WiFiModuleOptions &options) noexcept : _serial(options.receive_pin, options.transmit_pin) { } void WiFiModule::begin(size_t baudrate) noexcept { _serial.begin(baudrate); } auto WiFiModule::get_available() noexcept -> int { return _serial.available(); } void WiFiModule::reset() noexcept { const auto send_success = _send_serial("AT+RST"); if (!send_success) { Serial.println(F("Failed to send command to reset wifi module")); return; } char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; const auto response_status = _read(2000U, response); Serial.println( response_status == WiFiModuleResponseStatus::OK ? F("Reset wifi module") : F("Failed to reset wifi module") ); } auto WiFiModule::connect_to_wifi(const char *ssid, const char *password) noexcept -> bool { const auto cmd = "AT+CWJAP_CUR"; auto command_length = WIFI_CONNECT_COMMAND_BASE_LENGTH + strlen(cmd) + strlen(ssid) + strlen(password); auto *command = util::malloc(command_length); if (command == nullptr) { Serial.println( F("Heap memory allocation failed for creating a wifi connection command") ); return false; } snprintf(command, command_length, "%s=\"%s\",\"%s\"", cmd, ssid, password); const auto send_success = _send_serial(command); if (!send_success) { Serial.println(F("Failed to send wifi connect command")); free(command); return false; } free(command); char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; const auto response_status = _read(40000U, response); return response_status == WiFiModuleResponseStatus::OK; } void WiFiModule::set_wifi_mode(WifiMode wifi_mode) noexcept { const char cmd[] = "AT+CWMODE_CUR"; const auto command_length = strlen(cmd) + 2U + 1U; auto *command = util::malloc(command_length); if (command == nullptr) { return; } snprintf(command, command_length, "%s=%u", cmd, static_cast(wifi_mode)); _send_serial(command); free(command); char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; _read(TIMEOUT_SHORT, response); } void WiFiModule::set_multiple_connections_enabled(bool is_enabled) noexcept { const char cmd[] = "AT+CIPMUX"; auto command_length = strlen(cmd) + 2U + 1U; auto *command = util::malloc(command_length); if (command == nullptr) { return; } snprintf(command, command_length, "%s=%u", cmd, is_enabled ? 1U : 0U); _send_serial(command); free(command); char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; _read(TIMEOUT_SHORT, response); } void WiFiModule::set_echo_enabled(bool is_enabled) noexcept { const char cmd[] = "ATE"; auto command_length = strlen(cmd) + 1U + 1U; auto *command = util::malloc(command_length); if (command == nullptr) { return; } snprintf(command, command_length, "%s%u", cmd, is_enabled ? 1U : 0U); _send_serial(command); free(command); char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; const auto response_status = _read(1500U, response); Serial.print( response_status == WiFiModuleResponseStatus::OK ? F("Turned ") : F("Failed to turn ") ); Serial.print(is_enabled ? F("on") : F("off")); Serial.println(F(" AT commands echo")); } void WiFiModule::create_tcp_server(size_t port) noexcept { const auto *cmd = "AT+CIPSERVER"; auto command_length = CREATE_TCP_SERVER_COMMAND_BASE_LENGTH + strlen(cmd); auto *command = util::malloc(command_length); if (command == nullptr) { return; } snprintf(command, command_length, "%s=1,%u", cmd, port); _send_serial(command); free(command); char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; _read(TIMEOUT_MEDIUM, response); } auto WiFiModule::test() noexcept -> bool { const auto send_success = _send_serial("AT"); if (!send_success) { return false; } char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; const auto response_status = _read(8000U, response); if (response_status == WiFiModuleResponseStatus::TIMEOUT) { return false; } return strcmp(response, "") != 0; } auto WiFiModule::get_local_ip(char *local_ip_out) noexcept -> const char * { const auto send_success = _send_serial("AT+CIFSR"); if (!send_success) { return local_ip_out; } auto *buf = util::malloc(strlen(local_ip_out) + 1U); if (buf == nullptr) { strcpy(local_ip_out, "Memory allocation failure"); return local_ip_out; } strcpy(buf, ""); const auto response_status = _read(10000U, buf); if (response_status != WiFiModuleResponseStatus::OK) { free(buf); sprintf( local_ip_out, "Response status was not OK. Was %d", static_cast(response_status) ); return local_ip_out; } auto local_ip_end = strstr(buf, "\"\r\n+CIFSR:STAMAC"); if (local_ip_end == nullptr) { free(buf); strcpy(local_ip_out, "Response parsing error"); return local_ip_out; } const auto staip_title_length = 16U; // The length of '+CIFSR:STAIP,"' util::substr(buf + staip_title_length, local_ip_end, local_ip_out); free(buf); return local_ip_out; } auto WiFiModule::get_mac_address(char *mac_address_out) noexcept -> const char * { const auto send_success = _send_serial("AT+CIFSR"); if (!send_success) { return mac_address_out; } auto *buf = util::malloc(strlen(mac_address_out) + 1U); if (buf == nullptr) { strcpy(mac_address_out, "Memory allocation failure"); return mac_address_out; } strcpy(buf, ""); const auto response_status = _read(10000U, buf); if (response_status != WiFiModuleResponseStatus::OK) { free(buf); sprintf( mac_address_out, "Response status was not OK. Was %d", static_cast(response_status) ); return mac_address_out; } const auto stamac_title = "CIFSR:STAMAC,\""; auto mac_address_start = strstr(buf, stamac_title); if (mac_address_start == nullptr) { free(buf); strcpy(mac_address_out, "Response parsing error"); return mac_address_out; } mac_address_start += strlen(stamac_title); util::substr(mac_address_start, mac_address_start + 17U, mac_address_out); free(buf); return mac_address_out; } auto WiFiModule::read_incoming_request() noexcept -> HTTPRequest { char request_prefix[] = "+IPD,"; if (get_available() == 0 || !_serial.find(request_prefix)) { return HTTPRequest::create_invalid(); } const auto min_available_bytes = 5; // Wait for the data buffer a bit while (get_available() < min_available_bytes) { } const auto connection = NetworkConnection(_read_connection_id()); auto request_data_length = _read_request_data_length(); const auto request_method = _read_request_method(request_data_length); // Read request path char request_path[REQUEST_PATH_MAX_LENGTH] = ""; _read_to(&request_path[0], sizeof(request_path) - 1U, ' ', TIMEOUT_MEDIUM); request_data_length -= strlen(request_path) + 1U; // Read request HTTP version char http_version[REQUEST_HTTP_VERSION_MAX_LENGTH] = ""; _read_to(&http_version[0], sizeof(http_version) - 1U, '\r', TIMEOUT_MEDIUM); request_data_length -= strlen(http_version) + 1U; // Skip the newline while (get_available() == 0) { } _serial.read(); request_data_length -= 1U; auto request_data = util::malloc(request_data_length + 1U); if (request_data == nullptr) { return HTTPRequest::create_invalid(); } strcpy(request_data, ""); _read_bytes(request_data, request_data_length, TIMEOUT_LONG); free(request_data); return HTTPRequest( connection, request_method, http_version, request_path, 0, // request_data_length, nullptr // request_data ); } auto WiFiModule::close_connection(NetworkConnection &connection) noexcept -> bool { const auto *cmd = "AT+CIPCLOSE"; auto command_length = CLOSE_CONNECTION_COMMAND_BASE_LENGTH + strlen(cmd); auto *command = util::malloc(command_length); if (command == nullptr) { Serial.println( F("Heap memory allocation failed for creating close connection command") ); return false; } snprintf(command, command_length, "%s=%u", cmd, connection.id()); _send_serial(command); free(command); char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; const auto response_status = _read(4000U, response); if (response_status != WiFiModuleResponseStatus::OK) { Serial.print(F("Failed to close connection to ")); Serial.println(connection.id()); Serial.println(response); return false; } connection.set_is_closed(true); Serial.print(F("Closed connection to ")); Serial.println(connection.id()); return true; } auto WiFiModule::send_response( const NetworkConnection &connection, size_t status_code, const char **headers, size_t headers_cnt, const char *body ) noexcept -> bool { const auto *cmd = "AT+CIPSEND"; auto tot_headers_lengths = 0U; for (size_t index = 0U; index < headers_cnt; index++) { tot_headers_lengths += strlen(headers[index]) + 2U; } const auto data_length = strlen_P(RESPONSE_HTTP_VERSION) + 1 + RESPONSE_STATUS_CODE_LENGTH + 4 + strlen(body) + tot_headers_lengths; auto command_length = SEND_RESPONSE_COMMAND_BASE_LENGTH + strlen(cmd) + data_length; auto *command = util::malloc(command_length); if (command == nullptr) { Serial.println( F("Heap memory allocation failed for creating send response command") ); return false; } snprintf(command, command_length, "%s=%u,%u", cmd, connection.id(), data_length); _send_serial(command); free(command); char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; _read(TIMEOUT_MEDIUM, response); _serial.print(reinterpret_cast(RESPONSE_HTTP_VERSION)); _serial.print(F(" ")); _serial.print(status_code); _serial.print(F("\r\n")); // Print headers for (size_t index = 0U; index < headers_cnt; index++) { _serial.print(headers[index]); _serial.print(F("\r\n")); } _serial.print(F("\r\n")); _serial.print(body); strcpy(response, ""); _read(TIMEOUT_MEDIUM, response); return true; } auto WiFiModule::_send_serial(const char *command) noexcept -> bool { auto full_command_length = strlen(command) + 2U + 1U; auto *full_command = util::malloc(full_command_length); if (full_command == nullptr) { // NOLINTNEXTLINE(readability-simplify-boolean-expr) return false; } snprintf(full_command, full_command_length, "%s\r\n", command); _serial.print(full_command); free(full_command); return true; } auto WiFiModule::_read_connection_id() noexcept -> int8_t { const auto connection_id = _serial.read() - ASCII_TO_CHAR; // Skip the comma _serial.read(); return static_cast(connection_id); } auto WiFiModule::_read_request_data_length() noexcept -> size_t { char data_length_buf[REQUEST_DATA_LENGTH_BUF_SIZE] = ""; _read_to(&data_length_buf[0], sizeof(data_length_buf) - 1U, ':', TIMEOUT_SHORT); return static_cast(strtoul(data_length_buf, nullptr, NUMBER_BASE)); } auto WiFiModule::_read_request_method(size_t &request_data_length) noexcept -> HTTPRequestMethod { char request_method_buf[REQUEST_METHOD_STR_MAX_LENGTH] = ""; _read_to( &request_method_buf[0], sizeof(request_method_buf) - 1U, ' ', TIMEOUT_MEDIUM ); const auto request_method = str_to_http_request_method(request_method_buf); request_data_length -= strlen(request_method_buf) + 1U; return request_method; } auto WiFiModule::_read(uint64_t timeout, char *response_out) noexcept -> WiFiModuleResponseStatus { const auto start_time = millis(); size_t index = 0U; bool has_end = false; auto status = WiFiModuleResponseStatus::TIMEOUT; while (!has_end && (start_time + timeout) > millis()) { while (_serial.available() != 0) { char character = _read_byte(); if (has_end) { continue; } const auto end_pos = index + 1U; response_out[index] = character; response_out[end_pos] = '\0'; if (util::str_ends_with(response_out, end_pos, "OK")) { status = WiFiModuleResponseStatus::OK; has_end = true; } if (util::str_ends_with(response_out, end_pos, "ERROR")) { status = WiFiModuleResponseStatus::ERROR; has_end = true; } if (util::str_ends_with(response_out, end_pos, "FAIL")) { status = WiFiModuleResponseStatus::FAIL; has_end = true; } index++; } } return status; } void WiFiModule::_read_to( char *buffer_out, size_t length, // NOLINT(bugprone-easily-swappable-parameters) char stop_char, uint64_t timeout ) noexcept { auto position = 0U; const auto start_time = millis(); while (position < length && (start_time + timeout) > millis()) { if (get_available() == 0) { continue; } auto character = _read_byte(); if (character == stop_char) { break; } buffer_out[position++] = character; } buffer_out[position] = '\0'; } // NOLINTNEXTLINE(bugprone-easily-swappable-parameters) void WiFiModule::_read_bytes(char *buffer_out, size_t length, uint64_t timeout) noexcept { const auto original_length = length; auto position = 0U; const auto start_time = millis(); while (position != length && (start_time + timeout) > millis()) { if (get_available() == 0) { continue; } char character = _read_byte(); buffer_out[position++] = character; if (character == '\n' && position != original_length) { length -= 2U; } } buffer_out[position] = '\0'; } auto WiFiModule::_read_byte() noexcept -> char { return static_cast(_serial.read()); }