diff --git a/examples/protocols/mdns/README.md b/examples/protocols/mdns/README.md index 8cc56d835c..b4745a1115 100644 --- a/examples/protocols/mdns/README.md +++ b/examples/protocols/mdns/README.md @@ -10,12 +10,88 @@ Shows how to use mDNS to advertise lookup services and hosts - GPIO0 (BOOT Button) is initialized as pulled-up input that can be monitored for button press - Example task is started to check if the button is pressed so it can execute the mDNS queries defined -## Running the example +### Configure the project -- Run `make menuconfig` to configure the access point's SSID and Password and the default device mDNS host name and instance name -- Run `make flash monitor` to build and upload the example to your board and connect to it's serial terminal -- Wait for WiFi to connec to your access point -- You can now ping the device at `[hostname].local` and browse for `_http._tcp` on the same network to find the advertised service -- Pressing the BOOT button will start quring the local network for the predefined in `check_button` hosts and services +``` +make menuconfig +``` + +* Set `Default serial port` under `Serial flasher config`. +* Set `WiFi SSID` and `WiFi Password` for the board to connect to AP. +* Set `mDNS Hostname` as host name prefix for the device and its instance name in `mDNS Instance Name` +* Disable `Resolve test services` to prevent the example from querying defined names/services on startup (cause warnings in example logs, as illustrated below) + +### Build and Flash + +Build the project and flash it to the board, then run monitor tool to view serial output: + +``` +make -j4 flash monitor +``` +- Wait for WiFi to connect to your access point +- You can now ping the device at `[board-hostname].local`, where `[board-hostname]` is a string created from preconfigured hostname (`esp32-mdns` by default) and last 3 bytes from device MAC address. Please check the serial output log for the specific board-hostname (`esp32-mdns_80FFFF` in the log below) +- You can also browse for `_http._tcp` on the same network to find the advertised service +- Pressing the BOOT button will start querying the local network for the predefined in `check_button` hosts and services + + +(To exit the serial monitor, type ``Ctrl-]``.) + +See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + +## Example Output +``` +I (0) cpu_start: Starting scheduler on APP CPU. +I (276) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE +I (276) mdns-test: mdns hostname set to: [esp32-mdns_80FFFF] +I (286) wifi: wifi driver task: 3ffc2fa4, prio:23, stack:3584, core=0 +I (286) wifi: wifi firmware version: a3be639 +I (286) wifi: config NVS flash: enabled +I (296) wifi: config nano formating: disabled +I (296) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE +I (306) system_api: Base MAC address is not set, read default base MAC address from BLK0 of EFUSE +I (336) wifi: Init dynamic tx buffer num: 32 +I (336) wifi: Init data frame dynamic rx buffer num: 32 +I (336) wifi: Init management frame dynamic rx buffer num: 32 +I (346) wifi: Init static rx buffer size: 1600 +I (346) wifi: Init static rx buffer num: 10 +I (346) wifi: Init dynamic rx buffer num: 32 +I (356) mdns-test: Setting WiFi configuration SSID myssid... +I (426) phy: phy_version: 4000, b6198fa, Sep 3 2018, 15:11:06, 0, 0 +I (426) wifi: mode : sta (30:ae:a4:80:FF:FF) +I (426) gpio: GPIO[0]| InputEn: 1| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 +I (1756) wifi: n:11 0, o:1 0, ap:255 255, sta:11 0, prof:1 +I (2736) wifi: state: init -> auth (b0) +I (2756) wifi: state: auth -> assoc (0) +I (2766) wifi: state: assoc -> run (10) +I (2786) wifi: connected with myssid, channel 11 +I (2786) wifi: pm start, type: 1 + +I (4786) event: sta ip: 192.168.0.139, mask: 255.255.255.0, gw: 192.168.0.2 +I (4786) mdns-test: Query A: tinytester.local +W (6876) mdns-test: ESP_ERR_NOT_FOUND: Host was not found! +I (6876) mdns-test: Query PTR: _tiny._tcp.local +W (9976) mdns-test: No results found! +I (21126) mdns-test: Query A: esp32.local +W (23176) mdns-test: ESP_ERR_NOT_FOUND: Host was not found! +I (23176) mdns-test: Query PTR: _arduino._tcp.local +W (26276) mdns-test: No results found! +I (26276) mdns-test: Query PTR: _http._tcp.local +1: Interface: STA, Type: V6 + PTR : HP Color LaserJet MFP M277dw (7C2E10) + SRV : NPI7C2E10.local:80 + A : 254.128.0.0 +2: Interface: STA, Type: V4 + PTR : switch4e4919 + SRV : switch4e4919.local:80 + TXT : [1] path=/config/authentication_page.htm; + A : 192.168.0.118 +I (29396) mdns-test: Query PTR: _printer._tcp.local +1: Interface: STA, Type: V6 + PTR : HP Color LaserJet MFP M277dw (7C2E10) + SRV : NPI7C2E10.local:515 + A : 254.128.0.0 +2: Interface: STA, Type: V4 + PTR : HP Color LaserJet MFP M277dw (7C2E10) +``` See the README.md file in the upper level 'examples' directory for more information about examples. diff --git a/examples/protocols/mdns/main/Kconfig.projbuild b/examples/protocols/mdns/main/Kconfig.projbuild index a55b0bf4ca..9cbca2f6bc 100644 --- a/examples/protocols/mdns/main/Kconfig.projbuild +++ b/examples/protocols/mdns/main/Kconfig.projbuild @@ -2,28 +2,37 @@ menu "Example Configuration" config WIFI_SSID string "WiFi SSID" - default "myssid" - help - SSID (network name) for the example to connect to. + default "myssid" + help + SSID (network name) for the example to connect to. config WIFI_PASSWORD string "WiFi Password" - default "mypassword" - help - WiFi password (WPA or WPA2) for the example to use. + default "mypassword" + help + WiFi password (WPA or WPA2) for the example to use. - Can be left blank if the network has no security set. + Can be left blank if the network has no security set. config MDNS_HOSTNAME string "mDNS Hostname" - default "esp32-mdns" - help - mDNS Hostname for example to use + default "esp32-mdns" + help + mDNS Hostname for example to use config MDNS_INSTANCE string "mDNS Instance Name" - default "ESP32 with mDNS" - help - mDNS Instance Name for example to use + default "ESP32 with mDNS" + help + mDNS Instance Name for example to use + +config RESOLVE_TEST_SERVICES + bool "Resolve test services" + default y + help + Enable resolving test services on startup. + These services are advertized and evaluated in automated tests. + When executed locally, these will not be resolved and warnings appear in the log. + Please set to false to disable initial querying to avoid warnings. endmenu diff --git a/examples/protocols/mdns/main/mdns_example_main.c b/examples/protocols/mdns/main/mdns_example_main.c index fb805545ab..ad9b6f275e 100644 --- a/examples/protocols/mdns/main/mdns_example_main.c +++ b/examples/protocols/mdns/main/mdns_example_main.c @@ -29,11 +29,12 @@ #define EXAMPLE_WIFI_SSID CONFIG_WIFI_SSID #define EXAMPLE_WIFI_PASS CONFIG_WIFI_PASSWORD -#define EXAMPLE_MDNS_HOSTNAME CONFIG_MDNS_HOSTNAME #define EXAMPLE_MDNS_INSTANCE CONFIG_MDNS_INSTANCE + /* FreeRTOS event group to signal when we are connected & ready to make a request */ static EventGroupHandle_t wifi_event_group; +static const char c_config_hostname[] = CONFIG_MDNS_HOSTNAME; /* The event group allows multiple bits for each event, but we only care about one event - are we connected @@ -97,10 +98,20 @@ static void initialise_wifi(void) static void initialise_mdns(void) { + _Static_assert(sizeof(c_config_hostname) < CONFIG_MAIN_TASK_STACK_SIZE/2, "Configured mDNS name consumes more than half of the stack. Please select a shorter host name or extend the main stack size please."); + const size_t config_hostname_len = sizeof(c_config_hostname) - 1; // without term char + char hostname[config_hostname_len + 1 + 3*2 + 1]; // adding underscore + 3 digits + term char + uint8_t mac[6]; + + // adding 3 LSBs from mac addr to setup a board specific name + esp_read_mac(mac, ESP_MAC_WIFI_STA); + snprintf(hostname, sizeof(hostname), "%s_%02x%02X%02X", c_config_hostname, mac[3], mac[4], mac[5]); + //initialize mDNS ESP_ERROR_CHECK( mdns_init() ); //set mDNS hostname (required if you want to advertise services) - ESP_ERROR_CHECK( mdns_hostname_set(EXAMPLE_MDNS_HOSTNAME) ); + ESP_ERROR_CHECK( mdns_hostname_set(hostname) ); + ESP_LOGI(TAG, "mdns hostname set to: [%s]", hostname); //set default mDNS instance name ESP_ERROR_CHECK( mdns_instance_name_set(EXAMPLE_MDNS_INSTANCE) ); @@ -191,7 +202,7 @@ static void query_mdns_host(const char * host_name) return; } - ESP_LOGI(TAG, IPSTR, IP2STR(&addr)); + ESP_LOGI(TAG, "Query A: %s.local resolved to: " IPSTR, host_name, IP2STR(&addr)); } static void initialise_button(void) @@ -228,6 +239,12 @@ static void mdns_example_task(void *pvParameters) /* Wait for the callback to set the CONNECTED_BIT in the event group. */ xEventGroupWaitBits(wifi_event_group, IP4_CONNECTED_BIT | IP6_CONNECTED_BIT, false, true, portMAX_DELAY); + +#if CONFIG_RESOLVE_TEST_SERVICES == 1 + /* Send initial queries that are started by CI tester */ + query_mdns_host("tinytester"); +#endif + while(1) { check_button(); vTaskDelay(50 / portTICK_PERIOD_MS); diff --git a/examples/protocols/mdns/mdns_example_test.py b/examples/protocols/mdns/mdns_example_test.py new file mode 100644 index 0000000000..1cd1877d63 --- /dev/null +++ b/examples/protocols/mdns/mdns_example_test.py @@ -0,0 +1,117 @@ +import re +import os +import sys +import socket +import time +import imp +import struct +import dpkt, dpkt.dns +from threading import Thread + + + +# this is a test case write with tiny-test-fw. +# to run test cases outside tiny-test-fw, +# we need to set environment variable `TEST_FW_PATH`, +# then get and insert `TEST_FW_PATH` to sys path before import FW module + +test_fw_path = os.getenv("TEST_FW_PATH") +if test_fw_path and test_fw_path not in sys.path: + sys.path.insert(0, test_fw_path) + +import TinyFW +import IDF + +g_run_server = True +g_done = False + +def mdns_server(esp_host): + global g_run_server + global g_done + UDP_IP="0.0.0.0" + UDP_PORT=5353 + MCAST_GRP = '224.0.0.251' + sock = socket.socket( socket.AF_INET, socket.SOCK_DGRAM ) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEPORT, 1) + sock.bind( (UDP_IP,UDP_PORT) ) + mreq = struct.pack("4sl", socket.inet_aton(MCAST_GRP), socket.INADDR_ANY) + sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) + dns = dpkt.dns.DNS(b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01') + # sock.sendto(dns.pack(),(MCAST_GRP,UDP_PORT)) + sock.settimeout(30) + resp_dns = dpkt.dns.DNS(b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00') + resp_dns.op = dpkt.dns.DNS_QR | dpkt.dns.DNS_AA + resp_dns.rcode = dpkt.dns.DNS_RCODE_NOERR + arr = dpkt.dns.DNS.RR() + arr.cls = dpkt.dns.DNS_IN + arr.type = dpkt.dns.DNS_A + arr.name = u'tinytester.local' + arr.ip =socket.inet_aton('127.0.0.1') + resp_dns. an.append(arr) + sock.sendto(resp_dns.pack(),(MCAST_GRP,UDP_PORT)) + while g_run_server: + try: + m=sock.recvfrom( 1024 ); + dns = dpkt.dns.DNS(m[0]) + if len(dns.qd)>0 and dns.qd[0].type == dpkt.dns.DNS_A: + if dns.qd[0].name == u'tinytester.local': + print (dns.__repr__(),dns.qd[0].name) + sock.sendto(resp_dns.pack(),(MCAST_GRP,UDP_PORT)) + if len(dns.an)>0 and dns.an[0].type == dpkt.dns.DNS_A: + if dns.an[0].name == esp_host + u'.local': + print("Received answer esp32-mdns query") + g_done = True + print (dns.an[0].name) + dns = dpkt.dns.DNS(b'\x00\x00\x01\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x01\x00\x01') + dns.qd[0].name= esp_host + u'.local' + sock.sendto(dns.pack(),(MCAST_GRP,UDP_PORT)) + print("Sending esp32-mdns query") + time.sleep(0.5) + except socket.timeout: + break + +@IDF.idf_example_test(env_tag="Example_WIFI") +def test_examples_protocol_mdns(env, extra_data): + global g_run_server + global g_done + """ + steps: | + 1. join AP + init mdns example + 2. get the dut host name (and IP address) + 3. check the mdns name is accessible + 4. check DUT output if mdns advertized host is resolved + """ + dut1 = env.get_dut("mdns-test", "examples/protocols/mdns") + # check and log bin size + binary_file = os.path.join(dut1.app.binary_path, "mdns-test.bin") + bin_size = os.path.getsize(binary_file) + IDF.log_performance("mdns-test_bin_size", "{}KB".format(bin_size//1024)) + IDF.check_performance("mdns-test_bin_size", bin_size//1024) + # 1. start mdns application + dut1.start_app() + # 2. get the dut host name (and IP address) + specific_host = dut1.expect(re.compile(r"mdns hostname set to: \[([^\]]+)\]"), timeout=30) + specific_host = str(specific_host[0]) + dut_ip = "" + try: + dut_ip = dut1.expect(re.compile(r" sta ip: ([^,]+),"), timeout=30) + except DUT.ExpectTimeout: + raise ValueError('ENV_TEST_FAILURE: Cannot connect to AP') + # 3. check the mdns name is accessible + thread1 = Thread(target = mdns_server, args = (specific_host,)) + thread1.start() + start = time.time() + while (time.time() - start) <= 60: + if g_done: + print("Test passed") + break + g_run_server = False + thread1.join() + if g_done == False: + raise ValueError('Test has failed: did not receive mdns answer within timeout') + # 4. check DUT output if mdns advertized host is resolved + dut1.expect(re.compile(r"mdns-test: Query A: tinytester.local resolved to: 127.0.0.1"), timeout=30) + +if __name__ == '__main__': + test_examples_protocol_mdns()