From 3a11866cb6a8fd46d86cb6e04dc1022344ea8d45 Mon Sep 17 00:00:00 2001 From: HampusM Date: Sun, 8 May 2022 18:40:14 +0200 Subject: feat: add connecting to wifi --- .gitignore | 1 + src/gymnasiearbete.cpp | 69 +++++++++++ src/util.cpp | 35 ++++++ src/util.hpp | 27 +++++ src/wifi_module.cpp | 312 +++++++++++++++++++++++++++++++++++++++++++++++++ src/wifi_module.hpp | 93 +++++++++++++++ 6 files changed, 537 insertions(+) create mode 100644 src/gymnasiearbete.cpp create mode 100644 src/util.cpp create mode 100644 src/util.hpp create mode 100644 src/wifi_module.cpp create mode 100644 src/wifi_module.hpp diff --git a/.gitignore b/.gitignore index 4dec283..c108c04 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ build .vscode .pio +src/secrets.hpp diff --git a/src/gymnasiearbete.cpp b/src/gymnasiearbete.cpp new file mode 100644 index 0000000..0e1adb5 --- /dev/null +++ b/src/gymnasiearbete.cpp @@ -0,0 +1,69 @@ +#include "secrets.hpp" +#include "wifi_module.hpp" + +#include +#include +#include + +constexpr auto BAUDRATE = 9600U; + +constexpr auto NETWORK_MODULE_RX_PIN = 3; +constexpr auto NETWORK_MODULE_TX_PIN = 2; + +auto wifi_module = WiFiModule(NETWORK_MODULE_RX_PIN, NETWORK_MODULE_TX_PIN); + +void setup() +{ + Serial.begin(BAUDRATE); + + pinMode(NETWORK_MODULE_RX_PIN, INPUT); + pinMode(NETWORK_MODULE_TX_PIN, OUTPUT); + + wifi_module.begin(BAUDRATE); + + wifi_module.reset(); + + delay(1000); + + auto wifi_module_connected = wifi_module.test(); + + Serial.print("Wifi module connected: "); + Serial.println(wifi_module_connected ? "Yes" : "No"); + + if (!wifi_module_connected) + { + while (true) + { + } + } + + wifi_module.set_echo_enabled(false); + + delay(1000); + + const auto wifi_connect_success = wifi_module.connect(WIFI_SSID, WIFI_PASSWORD); + + if (wifi_connect_success) + { + Serial.print("Connected to wifi network '"); + Serial.print(WIFI_SSID); + Serial.println("' successfully"); + } + else + { + Serial.print("Failed to connect to wifi network '"); + Serial.print(WIFI_SSID); + Serial.println("'"); + } + + wifi_module.set_wifi_mode(WifiMode::Station); + + delay(3000); + + char local_ip[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; + + Serial.print("IP address: "); + Serial.println(wifi_module.get_local_ip(local_ip)); +} + +void loop() {} diff --git a/src/util.cpp b/src/util.cpp new file mode 100644 index 0000000..f587781 --- /dev/null +++ b/src/util.cpp @@ -0,0 +1,35 @@ +#include "util.hpp" + +namespace util +{ + +bool str_ends_with(const char *str, const char *other_str) noexcept +{ + if (str == nullptr || other_str == nullptr) + { + return false; + } + + auto str_length = strlen(str); + auto other_str_length = strlen(other_str); + + if (other_str_length > str_length) + { + return false; + } + + return strncmp(str + str_length - other_str_length, other_str, 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 + 1 != end; ++char_ptr) + { + *dest_head = *char_ptr; + dest_head++; + } +} + +} // namespace util diff --git a/src/util.hpp b/src/util.hpp new file mode 100644 index 0000000..d6f7f1c --- /dev/null +++ b/src/util.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +namespace util +{ + +template +Type *malloc(size_t size) noexcept +{ + return static_cast(::malloc(size)); +} + +bool str_ends_with(const char *str, const char *other_str) noexcept; + +/** + * 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; + +} // namespace util diff --git a/src/wifi_module.cpp b/src/wifi_module.cpp new file mode 100644 index 0000000..2e25dc9 --- /dev/null +++ b/src/wifi_module.cpp @@ -0,0 +1,312 @@ +#include "wifi_module.hpp" + +#include "util.hpp" + +#include +#include + +WiFiModule::WiFiModule(uint8_t receive_pin, uint8_t transmit_pin) noexcept + : _serial(receive_pin, transmit_pin) +{ +} + +void WiFiModule::begin(size_t baudrate) noexcept +{ + _serial.begin(baudrate); +} + +int WiFiModule::get_available() noexcept +{ + return _serial.available(); +} + +void WiFiModule::reset() noexcept +{ + _send_serial("AT+RST"); + + char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; + + const auto response_status = _read(2000U, response); + + Serial.println( + response_status == ResponseStatus::OK ? "Reset wifi module" + : "Failed to reset wifi module" + ); +} + +bool WiFiModule::connect(const char *ssid, const char *password) noexcept +{ + const char cmd[] = "AT+CWJAP"; + + auto command_length = strlen(cmd) + strlen(ssid) + strlen(password) + 6U + 1U; + + auto *command = util::malloc(command_length); + + if (command == nullptr) + { + Serial.println("Memory allocation failed for creating full 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("Failed to send connection command"); + return false; + } + + free(command); + + char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; + + const auto response_status = _read(20000U, response); + + if (response_status != ResponseStatus::OK) + { + Serial.print("Connection command responded with status "); + Serial.println(response_status); + return false; + } + + return true; +} + +void WiFiModule::set_wifi_mode(WifiMode wifi_mode) noexcept +{ + const char cmd[] = "AT+CWMODE_CUR"; + + auto command_length = strlen(cmd) + 2U + 1U; + + auto *command = util::malloc(command_length); + + snprintf(command, command_length, "%s=%u", cmd, static_cast(wifi_mode)); + + _send_serial(command); + + free(command); + + char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; + + _read(1500U, 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); + + snprintf(command, command_length, "%s=%u", cmd, is_enabled ? 1U : 0U); + + _send_serial(command); + + free(command); + + char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; + + _read(1500U, response); + + // Serial.println(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); + + 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); + + if (response_status == ResponseStatus::OK) + { + Serial.print("Turned "); + Serial.print(is_enabled ? "on" : "off"); + Serial.println(" AT commands echo"); + } + else + { + Serial.print("Failed to turn "); + Serial.print(is_enabled ? "on" : "off"); + Serial.println(" AT commands echo"); + } +} + +void WiFiModule::create_tcp_server(size_t port) noexcept +{ + const auto cmd = "AT+CIPSERVER"; + + auto command_length = strlen(cmd) + 7U + 1U; + + auto *command = util::malloc(command_length); + + snprintf(command, command_length, "%s=1,%u", cmd, port); + + _send_serial(command); + + free(command); + + char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; + + _read(1500U, response); + + // Serial.println(response); +} + +bool WiFiModule::test() noexcept +{ + const auto send_success = _send_serial("AT"); + + if (!send_success) + { + return false; + } + + char response[MAX_NETWORK_MODULE_RESPONSE_LENGTH] = ""; + + const auto response_status = _read(5000U, response); + + if (response_status == ResponseStatus::TIMEOUT) + { + return false; + } + + return strcmp(response, "") != 0; +} + +const char *WiFiModule::get_local_ip(char *local_ip_out) noexcept +{ + const auto command = "AT+CIFSR"; + + const auto send_success = _send_serial(command); + + if (!send_success) + { + return local_ip_out; + } + + auto *buf = util::malloc(strlen(local_ip_out)); + + if (buf == nullptr) + { + strcpy(local_ip_out, "Memory allocation failure"); + return local_ip_out; + } + + const auto response_status = _read(10000U, buf); + + if (response_status != ResponseStatus::OK) + { + free(buf); + + sprintf( + local_ip_out, + "Response status was not OK. Was %d", + static_cast(response_status) + ); + + return local_ip_out; + } + + const 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; +} + +bool WiFiModule::_send_serial(const char *command) noexcept +{ + auto full_command_length = strlen(command) + 2U + 1U; + + auto *full_command = util::malloc(full_command_length); + + if (full_command == nullptr) + { + return false; + } + + snprintf(full_command, full_command_length, "%s\r\n", command); + + _serial.print(full_command); + + // Serial.print("Sent command: "); + // Serial.println(full_command); + + free(full_command); + + return true; +} + +ResponseStatus WiFiModule::_read(uint64_t timeout, char *response_out) noexcept +{ + const auto start_time = millis(); + + bool has_end = false; + auto status = ResponseStatus::TIMEOUT; + + // auto *response_out_head = response_out; + + while (!has_end && (start_time + timeout) > millis()) + { + while (_serial.available() != 0) + { + char character = _read_byte(); + + strncat(response_out, &character, 1U); + + // *response_out_head = character; + // response_out_head++; + + if (util::str_ends_with(response_out, "OK")) + { + status = ResponseStatus::OK; + has_end = true; + } + + if (util::str_ends_with(response_out, "ERROR")) + { + status = ResponseStatus::ERROR; + has_end = true; + } + + if (util::str_ends_with(response_out, "FAIL")) + { + status = ResponseStatus::FAIL; + has_end = true; + } + } + } + + return status; +} + +char WiFiModule::_read_byte() noexcept +{ + return static_cast(_serial.read()); +} diff --git a/src/wifi_module.hpp b/src/wifi_module.hpp new file mode 100644 index 0000000..5a4148c --- /dev/null +++ b/src/wifi_module.hpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include + +constexpr auto MAX_NETWORK_MODULE_RESPONSE_LENGTH = 128U; + +enum WifiMode +{ + Station = 1, + SoftAP = 2, + SoftAPAndStation = 3 +}; + +enum ResponseStatus +{ + OK, + FAIL, + ERROR, + TIMEOUT +}; + +class WiFiModule +{ +public: + WiFiModule(uint8_t receive_pin, uint8_t transmit_pin) noexcept; + + void begin(size_t baudrate) noexcept; + + int get_available() noexcept; + + 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. + */ + bool connect(const char *ssid, const char *password) noexcept; + + 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. + */ + bool test() noexcept; + + /** + * 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. + */ + const char *get_local_ip(char *local_ip_out) noexcept; + +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. + */ + bool _send_serial(const char *command) noexcept; + + /** + * 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. + * + */ + ResponseStatus _read(uint64_t timeout, char *response_out) noexcept; + + char _read_byte() noexcept; +}; -- cgit v1.2.3-18-g5258