diff options
| -rw-r--r-- | minion/.gitignore | 5 | ||||
| -rw-r--r-- | minion/platformio.ini | 36 | ||||
| -rw-r--r-- | minion/scripts/override_compiledb_path.py | 14 | ||||
| -rw-r--r-- | minion/src/util.cpp | 92 | ||||
| -rw-r--r-- | minion/src/util.hpp | 98 | ||||
| -rw-r--r-- | minion/src/wifi_module.cpp | 1302 | ||||
| -rw-r--r-- | minion/src/wifi_module.hpp | 322 | 
7 files changed, 937 insertions, 932 deletions
diff --git a/minion/.gitignore b/minion/.gitignore new file mode 100644 index 0000000..89cc49c --- /dev/null +++ b/minion/.gitignore @@ -0,0 +1,5 @@ +.pio +.vscode/.browse.c_cpp.db* +.vscode/c_cpp_properties.json +.vscode/launch.json +.vscode/ipch diff --git a/minion/platformio.ini b/minion/platformio.ini index 2abc5cf..62d5c1d 100644 --- a/minion/platformio.ini +++ b/minion/platformio.ini @@ -1,18 +1,18 @@ -[platformio] -build_dir = build - -[env:uno] -platform = atmelavr -board = uno -framework = arduino -build_src_flags = -	-Wextra -	-Wpedantic -	-Wshadow -	-Wcast-align -	-Wunused -	-Wold-style-cast -	-Wconversion -	-pedantic -	-felide-constructors -extra_scripts = post:scripts/override_compiledb_path.py +[platformio]
 +build_dir = build
 +
 +[env:uno]
 +platform = atmelavr
 +board = uno
 +framework = arduino
 +build_src_flags =
 +	-Wextra
 +	-Wpedantic
 +	-Wshadow
 +	-Wcast-align
 +	-Wunused
 +	-Wold-style-cast
 +	-Wconversion
 +	-pedantic
 +	-felide-constructors
 +extra_scripts = post:scripts/override_compiledb_path.py
 diff --git a/minion/scripts/override_compiledb_path.py b/minion/scripts/override_compiledb_path.py index 38f52e9..3048b3e 100644 --- a/minion/scripts/override_compiledb_path.py +++ b/minion/scripts/override_compiledb_path.py @@ -1,7 +1,7 @@ -import os - -Import("env") - -path = os.path.join("$BUILD_DIR", "compile_commands.json") - -env.Replace(COMPILATIONDB_PATH=path) +import os
 +
 +Import("env")
 +
 +path = os.path.join("$BUILD_DIR", "compile_commands.json")
 +
 +env.Replace(COMPILATIONDB_PATH=path)
 diff --git a/minion/src/util.cpp b/minion/src/util.cpp index 708e47d..b55a6e8 100644 --- a/minion/src/util.cpp +++ b/minion/src/util.cpp @@ -1,46 +1,46 @@ -#include "util.hpp" - -namespace util -{ - -auto str_ends_with(const char *target, size_t target_end, const char *other) noexcept -	-> bool -{ -	if (target == nullptr || other == nullptr) -	{ -		return false; -	} - -	const auto other_str_length = strlen(other); - -	if (other_str_length > target_end) -	{ -		return false; -	} - -	return strncmp(target + target_end - other_str_length, other, other_str_length) == 0; -} - -void substr(const char *str, const char *end, char *dest) noexcept -{ -	auto *dest_head = dest; - -	for (const char *char_ptr = str; char_ptr != end; ++char_ptr) -	{ -		*dest_head = *char_ptr; -		dest_head++; -	} -} - -auto streq(const char *str_one, const char *str_two) noexcept -> bool -{ -	return strcmp(str_one, str_two) == 0; -} - -void quit() noexcept -{ -	while (true) -		; -} - -} // namespace util +#include "util.hpp"
 +
 +namespace util
 +{
 +
 +auto str_ends_with(const char *target, size_t target_end, const char *other) noexcept
 +	-> bool
 +{
 +	if (target == nullptr || other == nullptr)
 +	{
 +		return false;
 +	}
 +
 +	const auto other_str_length = strlen(other);
 +
 +	if (other_str_length > target_end)
 +	{
 +		return false;
 +	}
 +
 +	return strncmp(target + target_end - other_str_length, other, other_str_length) == 0;
 +}
 +
 +void substr(const char *str, const char *end, char *dest) noexcept
 +{
 +	auto *dest_head = dest;
 +
 +	for (const char *char_ptr = str; char_ptr != end; ++char_ptr)
 +	{
 +		*dest_head = *char_ptr;
 +		dest_head++;
 +	}
 +}
 +
 +auto streq(const char *str_one, const char *str_two) noexcept -> bool
 +{
 +	return strcmp(str_one, str_two) == 0;
 +}
 +
 +void quit() noexcept
 +{
 +	while (true)
 +		;
 +}
 +
 +} // namespace util
 diff --git a/minion/src/util.hpp b/minion/src/util.hpp index 2955cf1..a36dc5b 100644 --- a/minion/src/util.hpp +++ b/minion/src/util.hpp @@ -1,49 +1,49 @@ -#pragma once - -#include <stddef.h> -#include <stdlib.h> -#include <string.h> - -namespace util -{ - -template <typename Type> -auto malloc(size_t size) noexcept -> Type * -{ -	return static_cast<Type *>(::malloc(size)); -} - -/** - * Returns whether or not a string ends with the content of another string. - * - * @param target The string to compare the end of. - * @param target_end The end position of the target string. - * @param other The string to compare with. - */ -auto str_ends_with(const char *target, size_t target_end, const char *other) noexcept -	-> bool; - -/** - * Extracts a portion of a string. - * - * @param str The target string. - * @param end A pointer to a place inside the target string. - * @param dest Output buffer. - */ -void substr(const char *str, const char *end, char *dest) noexcept; - -/** - * Compares two strings. - * - * Wrapper function for strcmp. - * - * @param str_one The first string. - * @param str_two The second string. - * - * @returns Whether or not the two strings contain the same content. - */ -auto streq(const char *str_one, const char *str_two) noexcept -> bool; - -void quit() noexcept; - -} // namespace util +#pragma once
 +
 +#include <stddef.h>
 +#include <stdlib.h>
 +#include <string.h>
 +
 +namespace util
 +{
 +
 +template <typename Type>
 +auto malloc(size_t size) noexcept -> Type *
 +{
 +	return static_cast<Type *>(::malloc(size));
 +}
 +
 +/**
 + * Returns whether or not a string ends with the content of another string.
 + *
 + * @param target The string to compare the end of.
 + * @param target_end The end position of the target string.
 + * @param other The string to compare with.
 + */
 +auto str_ends_with(const char *target, size_t target_end, const char *other) noexcept
 +	-> bool;
 +
 +/**
 + * Extracts a portion of a string.
 + *
 + * @param str The target string.
 + * @param end A pointer to a place inside the target string.
 + * @param dest Output buffer.
 + */
 +void substr(const char *str, const char *end, char *dest) noexcept;
 +
 +/**
 + * Compares two strings.
 + *
 + * Wrapper function for strcmp.
 + *
 + * @param str_one The first string.
 + * @param str_two The second string.
 + *
 + * @returns Whether or not the two strings contain the same content.
 + */
 +auto streq(const char *str_one, const char *str_two) noexcept -> bool;
 +
 +void quit() noexcept;
 +
 +} // namespace util
 diff --git a/minion/src/wifi_module.cpp b/minion/src/wifi_module.cpp index 8a2dd6a..51b22dc 100644 --- a/minion/src/wifi_module.cpp +++ b/minion/src/wifi_module.cpp @@ -1,651 +1,651 @@ -#include "wifi_module.hpp" - -#include "network_connection.hpp" -#include "util.hpp" - -#include <Arduino.h> -#include <string.h> - -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<char>(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<char>(command_length); - -	if (command == nullptr) -	{ -		return; -	} - -	snprintf(command, command_length, "%s=%u", cmd, static_cast<int>(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<char>(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<char>(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<char>(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<char>(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<int>(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<char>(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<int>(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<char>(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<char>(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<char>(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<const __FlashStringHelper *>(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<char>(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<int8_t>(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<size_t>(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<char>(_serial.read()); -} +#include "wifi_module.hpp"
 +
 +#include "network_connection.hpp"
 +#include "util.hpp"
 +
 +#include <Arduino.h>
 +#include <string.h>
 +
 +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<char>(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<char>(command_length);
 +
 +	if (command == nullptr)
 +	{
 +		return;
 +	}
 +
 +	snprintf(command, command_length, "%s=%u", cmd, static_cast<int>(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<char>(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<char>(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<char>(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<char>(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<int>(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<char>(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<int>(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<char>(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<char>(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<char>(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<const __FlashStringHelper *>(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<char>(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<int8_t>(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<size_t>(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<char>(_serial.read());
 +}
 diff --git a/minion/src/wifi_module.hpp b/minion/src/wifi_module.hpp index da4a487..1049c54 100644 --- a/minion/src/wifi_module.hpp +++ b/minion/src/wifi_module.hpp @@ -1,161 +1,161 @@ -#pragma once - -#include "http/request.hpp" -#include "network_connection.hpp" - -#include <SoftwareSerial.h> -#include <avr/pgmspace.h> -#include <stddef.h> -#include <stdint.h> - -constexpr auto MAX_NETWORK_MODULE_RESPONSE_LENGTH = 128U; - -constexpr auto REQUEST_DATA_LENGTH_BUF_SIZE = 16U; -constexpr auto REQUEST_METHOD_STR_MAX_LENGTH = 10U; -constexpr auto REQUEST_PATH_MAX_LENGTH = 64U; -constexpr auto REQUEST_HTTP_VERSION_MAX_LENGTH = 10U; - -constexpr auto WIFI_CONNECT_COMMAND_BASE_LENGTH = 7U; -constexpr auto CREATE_TCP_SERVER_COMMAND_BASE_LENGTH = 8U; -constexpr auto CLOSE_CONNECTION_COMMAND_BASE_LENGTH = 51U; -constexpr auto SEND_RESPONSE_COMMAND_BASE_LENGTH = 51U; - -constexpr auto ASCII_TO_CHAR = 48U; - -constexpr auto TIMEOUT_SHORT = 1500U; -constexpr auto TIMEOUT_MEDIUM = 4000U; -constexpr auto TIMEOUT_LONG = 10000U; - -const char RESPONSE_HTTP_VERSION[] PROGMEM = { "HTTP/1.1" }; -constexpr auto RESPONSE_STATUS_CODE_LENGTH = 3U; - -constexpr auto NUMBER_BASE = 10U; - -enum WifiMode -{ -	Station = 1, -	SoftAP = 2, -	SoftAPAndStation = 3 -}; - -enum WiFiModuleResponseStatus -{ -	OK, -	FAIL, -	ERROR, -	TIMEOUT -}; - -struct WiFiModuleOptions -{ -	uint8_t receive_pin; -	uint8_t transmit_pin; -}; - -class WiFiModule -{ -public: -	explicit WiFiModule(const WiFiModuleOptions &options) noexcept; - -	void begin(size_t baudrate) noexcept; - -	auto get_available() noexcept -> int; - -	void reset() noexcept; - -	/** -	 * Connects to a wifi network. -	 * -	 * @param ssid The service set identifier of a wifi network. -	 * @param password The wifi network password. -	 * -	 * @returns Whether or not it succeeded. -	 */ -	auto connect_to_wifi(const char *ssid, const char *password) noexcept -> bool; - -	void set_wifi_mode(WifiMode wifi_mode) noexcept; - -	void set_multiple_connections_enabled(bool is_enabled) noexcept; - -	void set_echo_enabled(bool is_enabled) noexcept; - -	void create_tcp_server(size_t port) noexcept; - -	/** -	 * Tests the connection to the wifi module. -	 * -	 * @returns Whether or not the test succeeded. -	 */ -	auto test() noexcept -> bool; - -	/** -	 * Gets local IP address of the wifi module. -	 * -	 * @param local_ip_out Local IP output buffer. -	 * -	 * @returns A pointer to the local IP output buffer. -	 */ -	auto get_local_ip(char *local_ip_out) noexcept -> const char *; - -	/** -	 * Gets the MAC address of the wifi module. -	 * -	 * @param mac_address_out MAC address output buffer. -	 * -	 * @returns A pointer to the MAC address output buffer. -	 */ -	auto get_mac_address(char *mac_address_out) noexcept -> const char *; - -	/** -	 * Reads a incoming HTTP request. -	 * -	 * @returns A request. Has a connection with a ID of -1 if reading the request failed. -	 */ -	auto read_incoming_request() noexcept -> HTTPRequest; - -	auto close_connection(NetworkConnection &connection) noexcept -> bool; - -	auto send_response( -		const NetworkConnection &connection, -		size_t status_code, -		const char **headers, -		size_t headers_cnt, -		const char *body -	) noexcept -> bool; - -private: -	SoftwareSerial _serial; - -	/** -	 * Sends a command to the wifi module. -	 * -	 * @param command A command without the "AT+" in the beginning. -	 * -	 * @returns Whether or not it succeeded. -	 */ -	auto _send_serial(const char *command) noexcept -> bool; - -	auto _read_connection_id() noexcept -> int8_t; - -	auto _read_request_data_length() noexcept -> size_t; - -	auto _read_request_method(size_t &request_data_length) noexcept -> HTTPRequestMethod; - -	/** -	 * Reads from the wifi module until it responds with a status. -	 * -	 * @param timeout Timeout in milliseconds. -	 * @param response_out Response output buffer. -	 * -	 * @returns The response status. -	 * -	 */ -	auto _read(uint64_t timeout, char *response_out) noexcept -> WiFiModuleResponseStatus; - -	void -	_read_to(char *buffer_out, size_t length, char stop_char, uint64_t timeout) noexcept; - -	void _read_bytes(char *buffer_out, size_t length, uint64_t timeout) noexcept; - -	auto _read_byte() noexcept -> char; -}; +#pragma once
 +
 +#include "http/request.hpp"
 +#include "network_connection.hpp"
 +
 +#include <SoftwareSerial.h>
 +#include <avr/pgmspace.h>
 +#include <stddef.h>
 +#include <stdint.h>
 +
 +constexpr auto MAX_NETWORK_MODULE_RESPONSE_LENGTH = 128U;
 +
 +constexpr auto REQUEST_DATA_LENGTH_BUF_SIZE = 16U;
 +constexpr auto REQUEST_METHOD_STR_MAX_LENGTH = 10U;
 +constexpr auto REQUEST_PATH_MAX_LENGTH = 64U;
 +constexpr auto REQUEST_HTTP_VERSION_MAX_LENGTH = 10U;
 +
 +constexpr auto WIFI_CONNECT_COMMAND_BASE_LENGTH = 7U;
 +constexpr auto CREATE_TCP_SERVER_COMMAND_BASE_LENGTH = 8U;
 +constexpr auto CLOSE_CONNECTION_COMMAND_BASE_LENGTH = 51U;
 +constexpr auto SEND_RESPONSE_COMMAND_BASE_LENGTH = 51U;
 +
 +constexpr auto ASCII_TO_CHAR = 48U;
 +
 +constexpr auto TIMEOUT_SHORT = 1500U;
 +constexpr auto TIMEOUT_MEDIUM = 4000U;
 +constexpr auto TIMEOUT_LONG = 10000U;
 +
 +const char RESPONSE_HTTP_VERSION[] PROGMEM = { "HTTP/1.1" };
 +constexpr auto RESPONSE_STATUS_CODE_LENGTH = 3U;
 +
 +constexpr auto NUMBER_BASE = 10U;
 +
 +enum WifiMode
 +{
 +	Station = 1,
 +	SoftAP = 2,
 +	SoftAPAndStation = 3
 +};
 +
 +enum WiFiModuleResponseStatus
 +{
 +	OK,
 +	FAIL,
 +	ERROR,
 +	TIMEOUT
 +};
 +
 +struct WiFiModuleOptions
 +{
 +	uint8_t receive_pin;
 +	uint8_t transmit_pin;
 +};
 +
 +class WiFiModule
 +{
 +public:
 +	explicit WiFiModule(const WiFiModuleOptions &options) noexcept;
 +
 +	void begin(size_t baudrate) noexcept;
 +
 +	auto get_available() noexcept -> int;
 +
 +	void reset() noexcept;
 +
 +	/**
 +	 * Connects to a wifi network.
 +	 *
 +	 * @param ssid The service set identifier of a wifi network.
 +	 * @param password The wifi network password.
 +	 *
 +	 * @returns Whether or not it succeeded.
 +	 */
 +	auto connect_to_wifi(const char *ssid, const char *password) noexcept -> bool;
 +
 +	void set_wifi_mode(WifiMode wifi_mode) noexcept;
 +
 +	void set_multiple_connections_enabled(bool is_enabled) noexcept;
 +
 +	void set_echo_enabled(bool is_enabled) noexcept;
 +
 +	void create_tcp_server(size_t port) noexcept;
 +
 +	/**
 +	 * Tests the connection to the wifi module.
 +	 *
 +	 * @returns Whether or not the test succeeded.
 +	 */
 +	auto test() noexcept -> bool;
 +
 +	/**
 +	 * Gets local IP address of the wifi module.
 +	 *
 +	 * @param local_ip_out Local IP output buffer.
 +	 *
 +	 * @returns A pointer to the local IP output buffer.
 +	 */
 +	auto get_local_ip(char *local_ip_out) noexcept -> const char *;
 +
 +	/**
 +	 * Gets the MAC address of the wifi module.
 +	 *
 +	 * @param mac_address_out MAC address output buffer.
 +	 *
 +	 * @returns A pointer to the MAC address output buffer.
 +	 */
 +	auto get_mac_address(char *mac_address_out) noexcept -> const char *;
 +
 +	/**
 +	 * Reads a incoming HTTP request.
 +	 *
 +	 * @returns A request. Has a connection with a ID of -1 if reading the request failed.
 +	 */
 +	auto read_incoming_request() noexcept -> HTTPRequest;
 +
 +	auto close_connection(NetworkConnection &connection) noexcept -> bool;
 +
 +	auto send_response(
 +		const NetworkConnection &connection,
 +		size_t status_code,
 +		const char **headers,
 +		size_t headers_cnt,
 +		const char *body
 +	) noexcept -> bool;
 +
 +private:
 +	SoftwareSerial _serial;
 +
 +	/**
 +	 * Sends a command to the wifi module.
 +	 *
 +	 * @param command A command without the "AT+" in the beginning.
 +	 *
 +	 * @returns Whether or not it succeeded.
 +	 */
 +	auto _send_serial(const char *command) noexcept -> bool;
 +
 +	auto _read_connection_id() noexcept -> int8_t;
 +
 +	auto _read_request_data_length() noexcept -> size_t;
 +
 +	auto _read_request_method(size_t &request_data_length) noexcept -> HTTPRequestMethod;
 +
 +	/**
 +	 * Reads from the wifi module until it responds with a status.
 +	 *
 +	 * @param timeout Timeout in milliseconds.
 +	 * @param response_out Response output buffer.
 +	 *
 +	 * @returns The response status.
 +	 *
 +	 */
 +	auto _read(uint64_t timeout, char *response_out) noexcept -> WiFiModuleResponseStatus;
 +
 +	void
 +	_read_to(char *buffer_out, size_t length, char stop_char, uint64_t timeout) noexcept;
 +
 +	void _read_bytes(char *buffer_out, size_t length, uint64_t timeout) noexcept;
 +
 +	auto _read_byte() noexcept -> char;
 +};
  | 
