#include "aes67_net.h" #include #include #include #include "aes67_cfg.h" #include "aes67_web.h" #include "esp_eth.h" #include "esp_event.h" #include "esp_log.h" #include "esp_netif.h" #include "mdns.h" static const char *TAG = "net"; // AES67 / untagged interface. static const char NET_DEFAULTS[] = "{\"hostname\":\"aes67\",\"vlan\":false,\"web_on\":\"both\",\"dhcp\":true," "\"ip\":\"\",\"mask\":\"\",\"gw\":\"\",\"dns\":\"\"}"; // Internet / tagged interface, only used with net.vlan. static const char INET_DEFAULTS[] = "{\"vlan_id\":10,\"pcp\":0,\"dhcp\":true,\"ip\":\"\",\"mask\":\"\",\"gw\":\"\",\"dns\":\"\"}"; static esp_netif_t *s_netif; static char s_link[24] = "down"; static void eth_event(void *arg, esp_event_base_t base, int32_t id, void *data) { esp_eth_handle_t eth = *(esp_eth_handle_t *)data; switch (id) { case ETHERNET_EVENT_CONNECTED: { uint8_t mac[6]; eth_speed_t speed; eth_duplex_t duplex; esp_eth_ioctl(eth, ETH_CMD_G_MAC_ADDR, mac); esp_eth_ioctl(eth, ETH_CMD_G_SPEED, &speed); esp_eth_ioctl(eth, ETH_CMD_G_DUPLEX_MODE, &duplex); ESP_LOGI(TAG, "link up: %s Mbps %s duplex, MAC %02x:%02x:%02x:%02x:%02x:%02x", speed == ETH_SPEED_100M ? "100" : "10", duplex == ETH_DUPLEX_FULL ? "full" : "half", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); snprintf(s_link, sizeof(s_link), "up, %s Mbps %s", speed == ETH_SPEED_100M ? "100" : "10", duplex == ETH_DUPLEX_FULL ? "full" : "half"); break; } case ETHERNET_EVENT_DISCONNECTED: ESP_LOGW(TAG, "link down"); snprintf(s_link, sizeof(s_link), "down"); break; default: break; } } static void ip_event(void *arg, esp_event_base_t base, int32_t id, void *data) { const ip_event_got_ip_t *ev = data; ESP_LOGI(TAG, "got IP " IPSTR " mask " IPSTR " gw " IPSTR, IP2STR(&ev->ip_info.ip), IP2STR(&ev->ip_info.netmask), IP2STR(&ev->ip_info.gw)); } // Static addressing needs ip + mask; gw/dns are optional (none on the AES67 side with VLAN split). static bool check_addr(const cJSON *g, char *err, size_t n) { bool dhcp = cJSON_IsTrue(cJSON_GetObjectItemCaseSensitive(g, "dhcp")); return cfg_check_ipv4(g, "ip", dhcp, err, n) && cfg_check_ipv4(g, "mask", dhcp, err, n) && cfg_check_ipv4(g, "gw", true, err, n) && cfg_check_ipv4(g, "dns", true, err, n); } // RFC 1123 label: letters, digits, hyphen; not starting or ending with a hyphen. static bool check_hostname(const cJSON *g, char *err, size_t n) { if (!cfg_check_str(g, "hostname", 1, 63, err, n)) { return false; } const char *h = cJSON_GetObjectItemCaseSensitive(g, "hostname")->valuestring; size_t len = strlen(h); for (size_t i = 0; i < len; i++) { if (!isalnum((unsigned char)h[i]) && h[i] != '-') { snprintf(err, n, "hostname: only letters, digits and '-'"); return false; } } if (h[0] == '-' || h[len - 1] == '-') { snprintf(err, n, "hostname: must not start or end with '-'"); return false; } return true; } static bool net_validate(const cJSON *g, char *err, size_t n) { static const char *const web_on[] = { "both", "inet", "aes67", NULL }; return check_hostname(g, err, n) && cfg_check_enum(g, "web_on", web_on, err, n) && check_addr(g, err, n); } static bool inet_validate(const cJSON *g, char *err, size_t n) { return cfg_check_int(g, "vlan_id", 1, 4094, err, n) && cfg_check_int(g, "pcp", 0, 7, err, n) && check_addr(g, err, n); } // mDNS: .local plus the web UI as _http._tcp. static void mdns_setup(void) { cJSON *net = cfg_get("net"); const char *host = cJSON_GetObjectItemCaseSensitive(net, "hostname")->valuestring; esp_err_t err = mdns_init(); if (err == ESP_OK) { mdns_hostname_set(host); mdns_instance_name_set(host); mdns_service_add(NULL, "_http", "_tcp", 80, NULL, 0); ESP_LOGI(TAG, "mDNS: %s.local, _http._tcp port 80", host); } else { ESP_LOGE(TAG, "mDNS init failed: %s", esp_err_to_name(err)); } cJSON_Delete(net); } static void net_apply(const cJSON *g) { const char *host = cJSON_GetObjectItemCaseSensitive(g, "hostname")->valuestring; if (mdns_hostname_set(host) == ESP_OK && mdns_instance_name_set(host) == ESP_OK) { ESP_LOGI(TAG, "mDNS: now %s.local", host); } } static void net_status(cJSON *st) { char ip[16] = "", mac_s[18] = ""; esp_netif_ip_info_t info; if (esp_netif_get_ip_info(s_netif, &info) == ESP_OK && info.ip.addr) { snprintf(ip, sizeof(ip), IPSTR, IP2STR(&info.ip)); } uint8_t mac[6]; if (esp_netif_get_mac(s_netif, mac) == ESP_OK) { snprintf(mac_s, sizeof(mac_s), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } // Single untagged interface until the VLAN split: AES67 and web share it. cJSON_AddStringToObject(st, "ip", ip); cJSON_AddStringToObject(st, "aes67_ip", ip); cJSON_AddStringToObject(st, "mac", mac_s); cJSON_AddStringToObject(st, "link", s_link); } esp_err_t aes67_net_init(esp_eth_handle_t eth) { // Applied so far: hostname (mDNS). Static IP and VLAN come later. ESP_ERROR_CHECK(cfg_register("net", NET_DEFAULTS, net_validate, net_apply)); ESP_ERROR_CHECK(cfg_register("inet", INET_DEFAULTS, inet_validate, NULL)); ESP_ERROR_CHECK(esp_netif_init()); esp_err_t err = esp_event_loop_create_default(); if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { return err; } esp_netif_config_t netif_cfg = ESP_NETIF_DEFAULT_ETH(); s_netif = esp_netif_new(&netif_cfg); ESP_ERROR_CHECK(esp_netif_attach(s_netif, esp_eth_new_netif_glue(eth))); ESP_ERROR_CHECK(esp_event_handler_register(ETH_EVENT, ESP_EVENT_ANY_ID, eth_event, NULL)); ESP_ERROR_CHECK(esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, ip_event, NULL)); mdns_setup(); status_register(net_status); return esp_eth_start(eth); }