diff --git a/Client/dependencies.lock b/Client/dependencies.lock index bd4e2f7..1ac495f 100644 --- a/Client/dependencies.lock +++ b/Client/dependencies.lock @@ -121,6 +121,24 @@ dependencies: registry_url: https://components.espressif.com/ type: service version: 3.0.3 + espressif/mdns: + component_hash: 1ebe3bd675bb9d1c58f52bc0b609b32f74e572b01c328f9e61282040c775495c + dependencies: + - name: idf + require: private + version: '>=5.0' + source: + registry_url: https://components.espressif.com/ + type: service + version: 1.11.0 + http_client: + dependencies: + - name: idf + version: '>=5.0.0' + source: + path: C:\Dev\Classified\Calendink\components\http_client + type: local + version: 1.0.0 idf: source: type: idf @@ -146,9 +164,11 @@ dependencies: type: local version: 1.0.0 direct_dependencies: +- espressif/mdns +- http_client - idf - led - network -manifest_hash: 82be8e1c72b7c09d777d347050aba296d424314ce0b7f7642b5bd08a5e276e54 +manifest_hash: 097f2b35eb7bfe4a05687f2391f9fd078cab6437b8459fd420c47d1fa7a3c3d4 target: esp32c6 version: 2.0.0 diff --git a/Client/main/CMakeLists.txt b/Client/main/CMakeLists.txt index 671cea9..65789fc 100644 --- a/Client/main/CMakeLists.txt +++ b/Client/main/CMakeLists.txt @@ -1,4 +1,5 @@ -idf_component_register(SRCS "main.cpp" - PRIV_REQUIRES driver nvs_flash driver - esp_event esp_timer led network +idf_component_register(SRCS "main.cpp" "provider.cpp" + PRIV_REQUIRES driver nvs_flash + esp_event esp_timer + led network http_client mdns INCLUDE_DIRS ".") diff --git a/Client/main/Kconfig.projbuild b/Client/main/Kconfig.projbuild new file mode 100644 index 0000000..b927632 --- /dev/null +++ b/Client/main/Kconfig.projbuild @@ -0,0 +1,23 @@ +menu "Calendink Client" + + config CALENDINK_PROVIDER_MDNS_HOSTNAME + string "Provider mDNS Hostname" + default "calendink" + help + The mDNS hostname of the Provider device. + The Client resolves .local to find the Provider IP. + + config CALENDINK_PROVIDER_PORT + int "Provider HTTP Port" + default 80 + help + The HTTP port of the Calendink Provider device. + + config CALENDINK_PROVIDER_FALLBACK_IP + string "Provider Fallback IP (if mDNS fails)" + default "" + help + Static IP to use if mDNS resolution fails. + Leave empty to disable fallback. + +endmenu diff --git a/Client/main/idf_component.yml b/Client/main/idf_component.yml index b0b3336..dce7cdf 100644 --- a/Client/main/idf_component.yml +++ b/Client/main/idf_component.yml @@ -2,7 +2,10 @@ dependencies: idf: version: '>=4.1.0' + espressif/mdns: ^1.4.1 network: path: "../../components/network" led: - path: "../../components/led" \ No newline at end of file + path: "../../components/led" + http_client: + path: "../../components/http_client" diff --git a/Client/main/main.cpp b/Client/main/main.cpp index 9ebed98..121cd00 100644 --- a/Client/main/main.cpp +++ b/Client/main/main.cpp @@ -1,19 +1,17 @@ -#include +#include #include "esp_event.h" #include "esp_log.h" -#include "esp_pm.h" #include "esp_system.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "nvs.h" #include "nvs_flash.h" #include "sdkconfig.h" -#include "soc/gpio_num.h" #include "led.hpp" #include "network.hpp" -#include "types.hpp" +#include "provider.hpp" static const char *TAG = "ClientMain"; @@ -21,8 +19,7 @@ extern "C" void app_main() { ESP_LOGI(TAG, "Hello, Calendink Client!"); - // Initialize NVS (required for some Wi-Fi configurations and network - // features) + // Initialize NVS esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { @@ -35,7 +32,8 @@ extern "C" void app_main() setup_led(); - // Connect to WiFi ESP_LOGI(TAG, "Initializing WiFi connection"); + // Connect to WiFi + ESP_LOGI(TAG, "Initializing WiFi connection"); initialize_network(); esp_err_t err = connect_wifi(CONFIG_CALENDINK_WIFI_SSID, @@ -51,7 +49,6 @@ extern "C" void app_main() { ESP_LOGW(TAG, "WiFi connection check timeout, retrying... (%d)", retries); - led_blink_number(3, 255, 0, 0); } retries++; @@ -62,6 +59,7 @@ extern "C" void app_main() if (err == ESP_OK) { ESP_LOGI(TAG, "Successfully connected to WiFi!"); + test_provider_communication(); } else { diff --git a/Client/main/provider.cpp b/Client/main/provider.cpp new file mode 100644 index 0000000..82898e6 --- /dev/null +++ b/Client/main/provider.cpp @@ -0,0 +1,142 @@ +// Provider communication — mDNS discovery, device registration, screen fetch. + +#include "provider.hpp" + +#include +#include + +#include "esp_log.h" +#include "mdns.h" +#include "sdkconfig.h" + +#include "http_client.hpp" +#include "network.hpp" + +static const char *TAG = "Provider"; + +// ── mDNS Provider Discovery ──────────────────────────────────────────────── + +// Resolve the Provider's IP via mDNS. Returns true and fills `out_ip` on +// success. Falls back to CONFIG_CALENDINK_PROVIDER_FALLBACK_IP if set. +static bool resolve_provider_ip(char *out_ip, size_t out_ip_len) +{ + ESP_LOGI(TAG, "Resolving Provider via mDNS: %s.local", + CONFIG_CALENDINK_PROVIDER_MDNS_HOSTNAME); + + esp_err_t err = mdns_init(); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "mDNS init failed: %s", esp_err_to_name(err)); + goto fallback; + } + + { + esp_ip4_addr_t addr = {}; + err = mdns_query_a(CONFIG_CALENDINK_PROVIDER_MDNS_HOSTNAME, 5000, &addr); + if (err == ESP_OK) + { + snprintf(out_ip, out_ip_len, IPSTR, IP2STR(&addr)); + ESP_LOGI(TAG, "Provider resolved: %s", out_ip); + return true; + } + + ESP_LOGW(TAG, "mDNS resolution failed: %s", esp_err_to_name(err)); + } + +fallback: + if (strlen(CONFIG_CALENDINK_PROVIDER_FALLBACK_IP) > 0) + { + strlcpy(out_ip, CONFIG_CALENDINK_PROVIDER_FALLBACK_IP, out_ip_len); + ESP_LOGW(TAG, "Using fallback IP: %s", out_ip); + return true; + } + + ESP_LOGE(TAG, "No fallback IP configured. Cannot reach Provider."); + return false; +} + +// ── Provider Communication Test ───────────────────────────────────────────── + +void test_provider_communication(void) +{ + // 1. Resolve Provider IP + char provider_ip[16] = {}; + if (!resolve_provider_ip(provider_ip, sizeof(provider_ip))) + { + return; + } + + uint16_t provider_port = CONFIG_CALENDINK_PROVIDER_PORT; + + // 2. Get our own MAC address + uint8_t mac_bytes[6] = {}; + esp_err_t err = get_mac_address(mac_bytes); + if (err != ESP_OK) + { + ESP_LOGE(TAG, "Failed to get WiFi MAC: %s", esp_err_to_name(err)); + return; + } + + char mac_str[18] = {}; + snprintf(mac_str, sizeof(mac_str), "%02X:%02X:%02X:%02X:%02X:%02X", + mac_bytes[0], mac_bytes[1], mac_bytes[2], mac_bytes[3], mac_bytes[4], + mac_bytes[5]); + ESP_LOGI(TAG, "Client MAC: %s", mac_str); + + // 3. Register with Provider: POST /api/devices/register + { + char *url = + http_build_url(provider_ip, provider_port, "/api/devices/register"); + if (url == nullptr) + { + return; + } + + char json_body[64] = {}; + snprintf(json_body, sizeof(json_body), "{\"mac\":\"%s\"}", mac_str); + + http_text_response_t resp = {}; + err = http_post_json(url, json_body, &resp); + + if (err == ESP_OK) + { + ESP_LOGI(TAG, "Register response (%d): %s", resp.status_code, + resp.body ? resp.body : "(empty)"); + } + else + { + ESP_LOGE(TAG, "Register request failed: %s", esp_err_to_name(err)); + } + + free(resp.body); + free(url); + } + + // 4. Fetch screen image: GET /api/devices/screen.png?mac=XX:XX:XX:XX:XX:XX + { + char path[80] = {}; + snprintf(path, sizeof(path), "/api/devices/screen.png?mac=%s", mac_str); + + char *url = http_build_url(provider_ip, provider_port, path); + if (url == nullptr) + { + return; + } + + http_binary_response_t resp = {}; + err = http_get_binary(url, &resp); + + if (err == ESP_OK) + { + ESP_LOGI(TAG, "Screen image response (%d): %zu bytes", resp.status_code, + resp.data_len); + } + else + { + ESP_LOGE(TAG, "Screen image request failed: %s", esp_err_to_name(err)); + } + + free(resp.data); + free(url); + } +} diff --git a/Client/main/provider.hpp b/Client/main/provider.hpp new file mode 100644 index 0000000..413f914 --- /dev/null +++ b/Client/main/provider.hpp @@ -0,0 +1,5 @@ +#pragma once + +// Resolve the Provider's IP and run the device registration + screen fetch +// test flow. Call once after WiFi is connected. +void test_provider_communication(void); diff --git a/components/http_client/idf_component.yml b/components/http_client/idf_component.yml index bfa13bc..009d05d 100644 --- a/components/http_client/idf_component.yml +++ b/components/http_client/idf_component.yml @@ -3,4 +3,4 @@ description: "Calendink HTTP Client Component" dependencies: idf: - version: '>=5.0.0' + version: '>=5.0.0' \ No newline at end of file diff --git a/components/network/network.cpp b/components/network/network.cpp index 6ec9d42..85e02fc 100644 --- a/components/network/network.cpp +++ b/components/network/network.cpp @@ -452,3 +452,22 @@ internal void blink_last_ip_octet() } } #endif + +// === MAC Address === + +esp_err_t get_mac_address(uint8_t *mac_out) +{ + // Prefer Ethernet if connected, fall back to WiFi + if (s_eth_netif != nullptr) + { + return esp_netif_get_mac(s_eth_netif, mac_out); + } + + if (s_wifi_netif != nullptr) + { + return esp_netif_get_mac(s_wifi_netif, mac_out); + } + + ESP_LOGE("NET", "No active network interface for MAC address"); + return ESP_ERR_INVALID_STATE; +} diff --git a/components/network/network.hpp b/components/network/network.hpp index d9d306c..22ceaf6 100644 --- a/components/network/network.hpp +++ b/components/network/network.hpp @@ -15,3 +15,7 @@ esp_err_t connect_wifi(const char *ssid, const char *password, bool blockUntilIPAcquired); void disconnect_wifi(); esp_err_t check_wifi_connection(uint32_t timeoutSeconds); + +// Get the MAC address of the active network interface (WiFi STA). +// mac_out must point to a buffer of at least 6 bytes. +esp_err_t get_mac_address(uint8_t *mac_out);