Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(esp-modem): Extend CMUX mode to support manual transitions #169

Merged
merged 2 commits into from
Nov 23, 2022

Conversation

david-cermak
Copy link
Collaborator

  • Adds manual transitions to CMUX
  • Adds C-API that allows for user defined AT command with own callback

@david-cermak david-cermak force-pushed the feat/modem_dce_on_read branch from 28de345 to 1f0e863 Compare November 7, 2022 15:06
@david-cermak david-cermak force-pushed the feat/modem_dce_on_read branch 2 times, most recently from a6fc861 to 53cc06f Compare November 21, 2022 11:55
@david-cermak david-cermak force-pushed the feat/modem_dce_on_read branch from 53cc06f to 2180ab1 Compare November 21, 2022 13:58
@diplfranzhoepfinger
Copy link
Contributor

https://github.com/espressif/esp-protocols/actions/runs/3515038680/jobs/5890591395

@david-cermak why is this failing ? also to where does it download ? 

has Espressif a own build-Server, where some ESP Boards are connected, where they download the code and run it?

@david-cermak
Copy link
Collaborator Author

https://github.com/espressif/esp-protocols/actions/runs/3515038680/jobs/5890591395

@david-cermak why is this failing ? also to where does it download ? 

has Espressif a own build-Server, where some ESP Boards are connected, where they download the code and run it?

This is an unrelated error. Yes, we have a self-hosted runner, but that's used only for test execution on target. Build tests and host test are executed on GitHub runners.

@diplfranzhoepfinger
Copy link
Contributor

so, did a first Test:

    /* Close multiplexed command/data mode */
#if CONFIG_EXAMPLE_CLOSE_CMUX_AT_END == 1
    if (dce->set_mode(esp_modem::modem_mode::COMMAND_MODE)) {
        std::cout << "Modem has correctly entered command mode" << std::endl;
    } else {
        ESP_LOGE(TAG, "Failed to configure desired mode... exiting");
        return;
    }
#endif

    if (dce->set_mode(esp_modem::modem_mode::CMUX_MODE)) {
        std::cout << "Modem has correctly entered multiplexed command/data mode" << std::endl;
    } else {
        ESP_LOGE(TAG, "Failed to configure multiplexed command mode... exiting");
        return;
    }
}

Goal is after Exit CMUX Mode enter CMUX Mode again.

@diplfranzhoepfinger
Copy link
Contributor

but this brings up:

Modem has correctly entered multiplexed command/data mode
Operator name:vodafone.de
I (11646) esp-netif_lwip-ppp: Connected
I (11646) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (11646) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (11646) cmux_example: IP          : 100.82.79.92
I (11656) cmux_example: Netmask     : 255.255.255.255
I (11666) cmux_example: Gateway     : 10.64.64.64
Got IP address
I (11666) system_api: Base MAC address is not set
I (11676) system_api: read default base MAC address from EFUSE
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (25356) esp-netif_lwip-ppp: User interrupt
I (25356) esp_modem_netif: PPP state changed event 5
Modem has correctly entered command mode
E (35616) cmux_example: Failed to configure multiplexed command mode... exiting

@diplfranzhoepfinger
Copy link
Contributor

now try:

void report(bool a)
{
	if (a) {
		std::cout << "Modem has correctly entered mode" << std::endl;
	} else {
		ESP_LOGE(TAG, "Failed to configure desired mode... exiting");
		return;
	}
}

and

    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE));
    vTaskDelay( 500 / portTICK_PERIOD_MS );
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_EXIT));

}

this does work !

I (5219) cmux_example: set_flow_control OK
I (5319) cmux_example: sync Success after 0 try
Modem has correctly entered mode
Modem has correctly entered mode

@david-cermak
Copy link
Collaborator Author

so, did a first Test:
}
}

Goal is after Exit CMUX Mode enter CMUX Mode again.

Could you please try to send ATH\r command after exiting the (auto) CMUX? This usually helps (some devices) with restarting the data mode.

@franz-ms-muc
Copy link
Contributor

Could you please try to send ATH\r command after exiting the (auto) CMUX? This usually helps (some devices) with restarting the data mode.

will do. kepp you posted !

@diplfranzhoepfinger
Copy link
Contributor

so, ATH\r is the command "hang_up"

test like this:

    /* Close multiplexed command/data mode */
#if CONFIG_EXAMPLE_CLOSE_CMUX_AT_END == 1
    report(dce->set_mode(esp_modem::modem_mode::COMMAND_MODE));
    //Could you please try to send ATH\r command after exiting the (auto) CMUX? This usually helps (some devices) with restarting the data mode.
    vTaskDelay( 500 / portTICK_PERIOD_MS );
    dce->hang_up();
    vTaskDelay( 500 / portTICK_PERIOD_MS );

#endif

    report(dce->set_mode(esp_modem::modem_mode::CMUX_MODE));

@diplfranzhoepfinger
Copy link
Contributor

Still Error:

Modem has correctly entered mode
Operator name:vodafone.de
I (11647) esp-netif_lwip-ppp: Connected
I (11647) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (11647) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (11657) cmux_example: IP          : 100.82.79.92
I (11657) cmux_example: Netmask     : 255.255.255.255
I (11667) cmux_example: Gateway     : 10.64.64.64
Got IP address
I (11667) system_api: Base MAC address is not set
I (11677) system_api: read default base MAC address from EFUSE
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (26047) esp-netif_lwip-ppp: User interrupt
I (26047) esp_modem_netif: PPP state changed event 5
Modem has correctly entered mode
E (37217) cmux_example: Failed to configure desired mode... exiting

@diplfranzhoepfinger
Copy link
Contributor

i will try with extended logging.

1 similar comment
@diplfranzhoepfinger
Copy link
Contributor

i will try with extended logging.

@diplfranzhoepfinger
Copy link
Contributor

the Error is again "NO CARRIER"

@diplfranzhoepfinger
Copy link
Contributor

good thing:

we already get the Disconnect Events we were so hard waiting for:

--> 

CONFIG_LWIP_PPP_NOTIFY_PHASE_SUPPORT=y
D (66631) esp-netif_lwip-ppp: Phase Disconnect
D (66631) esp-netif_lwip-ppp: Phase Dead
I (66631) esp-netif_lwip-ppp: Connection lost

@diplfranzhoepfinger
Copy link
Contributor

@david-cermak 

can you give some more information how it is thought ? 

Enter CMUX:

    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE));       /*!< Enter CMUX mode manually -- just creates two virtual terminals */
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_COMMAND));    // Primary Terminal to Command
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP));       // swap
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA));       // second Terminal to Data
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP));       // swap again so we have Command Terminal in Front

Exit only Data Mode on one Channel:

    /* Close multiplexed command/data mode */
#if CONFIG_EXAMPLE_CLOSE_CMUX_AT_END == 1
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP));
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_COMMAND)); //second Channel to Command
    //Could you please try to send ATH\r command after exiting the (auto) CMUX? This usually helps (some devices) with restarting the data mode.
    vTaskDelay( 500 / portTICK_PERIOD_MS );
    switch (dce->hang_up())
    {
    case command_result::OK:             /*!< The command completed successfully */
    	std::cout << "hang_up OK" << std::endl;
    	break;
    case command_result::FAIL:           /*!< The command explicitly failed */
    	std::cout << "hang_up fail" << std::endl;
    	break;
    case command_result::TIMEOUT:        /*!< The device didn't respond in the specified timeline */
    	std::cout << "hang_up timeout" << std::endl;
    	break;
    }
    vTaskDelay( 500 / portTICK_PERIOD_MS );

#endif

    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA)); // second Channel to Data again
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_SWAP)); // swap again so we have Command Terminal in Front

@diplfranzhoepfinger
Copy link
Contributor

By analyzing your code, i see once you enter Data Mode in MANUAL CMUX, 

you automatically swap Terminals.

@diplfranzhoepfinger
Copy link
Contributor

SUCCESS !!! got it:

    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_MODE));       /*!< Enter CMUX mode manually -- just creates two virtual terminals */
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA));       // second Terminal to Data (Swap is implicit)
    /* Close multiplexed command/data mode */
#if CONFIG_EXAMPLE_CLOSE_CMUX_AT_END == 1
    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_COMMAND)); //second Channel to Command
    //Could you please try to send ATH\r command after exiting the (auto) CMUX? This usually helps (some devices) with restarting the data mode.
    vTaskDelay( 500 / portTICK_PERIOD_MS );
    switch (dce->hang_up())
    {
    case command_result::OK:             /*!< The command completed successfully */
    	std::cout << "hang_up OK" << std::endl;
    	break;
    case command_result::FAIL:           /*!< The command explicitly failed */
    	std::cout << "hang_up fail" << std::endl;
    	break;
    case command_result::TIMEOUT:        /*!< The device didn't respond in the specified timeline */
    	std::cout << "hang_up timeout" << std::endl;
    	break;
    }
    vTaskDelay( 500 / portTICK_PERIOD_MS );

#endif

    report(dce->set_mode(esp_modem::modem_mode::CMUX_MANUAL_DATA)); // second Channel to Data again
}
I (446) gpio: GPIO[11]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (446) gpio: GPIO[10]| InputEn: 0| OutputEn: 0| OpenDrain: 0| Pullup: 1| Pulldown: 0| Intr:0 
I (2456) modem_pwkey: status = 1
I (2456) uart: queue free spaces: 30
W (2956) cmux_example: sync no Success after 0 try
I (3056) cmux_example: sync Success after 1 try
I (3156) cmux_example: set_flow_control OK
I (3256) cmux_example: sync Success after 0 try
Modem has correctly entered mode
Modem has correctly entered mode
Operator name:vodafone.de
I (9546) esp-netif_lwip-ppp: Connected
I (9546) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (9546) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (9546) cmux_example: IP          : 100.82.79.92
I (9556) cmux_example: Netmask     : 255.255.255.255
I (9556) cmux_example: Gateway     : 10.64.64.64
Got IP address
I (9566) system_api: Base MAC address is not set
I (9576) system_api: read default base MAC address from EFUSE
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (24186) esp-netif_lwip-ppp: User interrupt
I (24186) esp_modem_netif: PPP state changed event 5
Modem has correctly entered mode
hang_up OK
Modem has correctly entered mode
I (37196) esp-netif_lwip-ppp: User interrupt
I (37196) esp_modem_netif: PPP state changed event 5

@diplfranzhoepfinger
Copy link
Contributor

so, 

this ways seems running:

Got IP address
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (726987) esp-netif_lwip-ppp: User interrupt
I (726987) esp_modem_netif: PPP state changed event 5
Modem has correctly entered mode
hang_up OK
Modem IMEI number:865456057017046
Modem has correctly entered mode
Operator name:vodafone.de
I (734057) esp-netif_lwip-ppp: Connected
I (734057) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (734057) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (734057) cmux_example: IP          : 100.82.79.92
I (734067) cmux_example: Netmask     : 255.255.255.255
I (734077) cmux_example: Gateway     : 10.64.64.64
Got IP address
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (748257) esp-netif_lwip-ppp: User interrupt
I (748257) esp_modem_netif: PPP state changed event 5
Modem has correctly entered mode
hang_up OK
Modem IMEI number:865456057017046
Modem has correctly entered mode
Operator name:vodafone.de
I (755327) esp-netif_lwip-ppp: Connected
I (755327) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (755327) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (755327) cmux_example: IP          : 100.82.79.92
I (755337) cmux_example: Netmask     : 255.255.255.255
I (755347) cmux_example: Gateway     : 10.64.64.64
Got IP address
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (769547) esp-netif_lwip-ppp: User interrupt
I (769547) esp_modem_netif: PPP state changed event 5
Modem has correctly entered mode
hang_up OK
Modem IMEI number:865456057017046
Modem has correctly entered mode
Operator name:vodafone.de
I (776617) esp-netif_lwip-ppp: Connected
I (776617) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (776617) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (776617) cmux_example: IP          : 100.82.79.92
I (776627) cmux_example: Netmask     : 255.255.255.255
I (776637) cmux_example: Gateway     : 10.64.64.64
Got IP address
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920

@diplfranzhoepfinger
Copy link
Contributor

from a fresh boot:

I (2457) modem_pwkey: status = 0
I (2557) modem_pwkey: POWER ON
I (3557) modem_pwkey: POWER ON OK
I (4557) modem_pwkey: status = 1
I (4557) uart: queue free spaces: 30
W (5057) cmux_example: sync no Success after 0 try
I (5157) cmux_example: sync Success after 1 try
I (5257) cmux_example: set_flow_control OK
I (5357) cmux_example: sync Success after 0 try
Modem has correctly entered mode
Modem has correctly entered mode
Operator name:vodafone.de
I (11647) esp-netif_lwip-ppp: Connected
I (11647) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (11647) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (11657) cmux_example: IP          : 100.82.79.92
I (11657) cmux_example: Netmask     : 255.255.255.255
I (11667) cmux_example: Gateway     : 10.64.64.64
Got IP address
I (11667) system_api: Base MAC address is not set
I (11677) system_api: read default base MAC address from EFUSE
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (25367) esp-netif_lwip-ppp: User interrupt
I (25367) esp_modem_netif: PPP state changed event 5
Modem has correctly entered mode
hang_up OK
Modem IMEI number:865456057017046
Modem has correctly entered mode
Operator name:vodafone.de
I (32437) esp-netif_lwip-ppp: Connected
I (32437) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (32437) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (32437) cmux_example: IP          : 100.82.79.92
I (32447) cmux_example: Netmask     : 255.255.255.255
I (32457) cmux_example: Gateway     : 10.64.64.64
Got IP address
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (46557) esp-netif_lwip-ppp: User interrupt
I (46557) esp_modem_netif: PPP state changed event 5
Modem has correctly entered mode
hang_up OK
Modem IMEI number:865456057017046
Modem has correctly entered mode
Operator name:vodafone.de
I (53627) esp-netif_lwip-ppp: Connected
I (53627) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (53627) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (53627) cmux_example: IP          : 100.82.79.92
I (53637) cmux_example: Netmask     : 255.255.255.255
I (53647) cmux_example: Gateway     : 10.64.64.64
Got IP address
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920

@diplfranzhoepfinger
Copy link
Contributor

even nicer:

I (2457) modem_pwkey: status = 0
I (2557) modem_pwkey: POWER ON
I (3557) modem_pwkey: POWER ON OK
I (4557) modem_pwkey: status = 1
I (4557) uart: queue free spaces: 30
W (5057) cmux_example: sync no Success after 0 try
I (5157) cmux_example: sync Success after 1 try
I (5257) cmux_example: set_flow_control OK
I (5357) cmux_example: sync Success after 0 try
Modem has correctly entered mode CMUX_MANUAL_MODE
Modem has correctly entered mode CMUX_MANUAL_DATA
Operator name:vodafone.de
I (11647) esp-netif_lwip-ppp: Connected
I (11647) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (11647) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (11657) cmux_example: IP          : 100.82.79.92
I (11657) cmux_example: Netmask     : 255.255.255.255
I (11667) cmux_example: Gateway     : 10.64.64.64
Got IP address
I (11667) system_api: Base MAC address is not set
I (11677) system_api: read default base MAC address from EFUSE
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920
I (25707) esp-netif_lwip-ppp: User interrupt
I (25707) esp_modem_netif: PPP state changed event 5
Modem has correctly entered mode CMUX_MANUAL_COMMAND
hang_up OK
Modem IMEI number:865456057017046
Modem has correctly entered mode CMUX_MANUAL_DATA
Operator name:vodafone.de
I (32777) esp-netif_lwip-ppp: Connected
I (32777) esp-netif_lwip-ppp: Name Server1: 217.14.160.130
I (32777) esp-netif_lwip-ppp: Name Server2: 217.14.164.35
I (32787) cmux_example: IP          : 100.82.79.92
I (32787) cmux_example: Netmask     : 255.255.255.255
I (32797) cmux_example: Gateway     : 10.64.64.64
Got IP address
 TOPIC:/topic/esp-modem
 DATA:Hello modem
Data received
Modem IMSI number:901288005681920

with

/**
 *
 * @param dce
 * very relevant here to have "& dce" instead of "dce"
 * see https://stackoverflow.com/questions/30905487/how-can-i-pass-stdunique-ptr-into-a-function
 *
 * @param m
 */
void set_mode_and_report(std::unique_ptr<esp_modem::DCE> & dce, modem_mode m)
{
	std::string str;
	switch(m)
	{
	case modem_mode::UNDEF:               str = "UNDEF";               break;
	case modem_mode::COMMAND_MODE:        str = "COMMAND_MODE";        break;
	case modem_mode::DATA_MODE:           str = "DATA_MODE";           break;
	case modem_mode::CMUX_MODE:           str = "CMUX_MODE";           break;
	case modem_mode::CMUX_MANUAL_MODE:    str = "CMUX_MANUAL_MODE";    break;
	case modem_mode::CMUX_MANUAL_EXIT:    str = "CMUX_MANUAL_EXIT";    break;
	case modem_mode::CMUX_MANUAL_DATA:    str = "CMUX_MANUAL_DATA";    break;
	case modem_mode::CMUX_MANUAL_COMMAND: str = "CMUX_MANUAL_COMMAND"; break;
	case modem_mode::CMUX_MANUAL_SWAP:    str = "CMUX_MANUAL_SWAP";    break;
	}

	if (dce->set_mode(m)) {
		std::cout << "Modem has correctly entered mode " << str << std::endl;
	} else {
		ESP_LOGE(TAG, "Failed to configure desired mode... exiting");
		return;
	}
}

and:

    set_mode_and_report(dce, esp_modem::modem_mode::CMUX_MANUAL_MODE);       /*!< Enter CMUX mode manually -- just creates two virtual terminals */
    set_mode_and_report(dce, esp_modem::modem_mode::CMUX_MANUAL_DATA);       // second Terminal to Data (Swap is implicit)


    label_loop:


    /* Close multiplexed command/data mode */
#if CONFIG_EXAMPLE_CLOSE_CMUX_AT_END == 1
    set_mode_and_report(dce, esp_modem::modem_mode::CMUX_MANUAL_COMMAND); //second Channel to Command
    //Could you please try to send ATH\r command after exiting the (auto) CMUX? This usually helps (some devices) with restarting the data mode.
    vTaskDelay( 500 / portTICK_PERIOD_MS );
    switch (dce->hang_up())
    {
    case command_result::OK:             /*!< The command completed successfully */
    	std::cout << "hang_up OK" << std::endl;
    	break;
    case command_result::FAIL:           /*!< The command explicitly failed */
    	std::cout << "hang_up fail" << std::endl;
    	break;
    case command_result::TIMEOUT:        /*!< The device didn't respond in the specified timeline */
    	std::cout << "hang_up timeout" << std::endl;
    	break;
    }
    vTaskDelay( 500 / portTICK_PERIOD_MS );

    /* Again reading some data from the modem */
    if (dce->get_imei(str) == esp_modem::command_result::OK) {
        std::cout << "Modem IMEI number:" << str << std::endl;
    }


#endif

    set_mode_and_report(dce, esp_modem::modem_mode::CMUX_MANUAL_DATA); // second Channel to Data again
    goto label_loop;
}

@david-cermak
Copy link
Collaborator Author

Thanks for the review @euripedesrocha !

@david-cermak david-cermak merged commit 457f833 into espressif:master Nov 23, 2022
@david-cermak
Copy link
Collaborator Author

@diplfranzhoepfinger Hi Franz!
I'm afraid you have to exchanges terminals, (command CMUX_MANUAL_SWAP) in the manual CMUX mode, as otherwise you won't be able to send commands while playing in data mode. The correct sequence should be:

  • CMUX_MANUAL_MODE
  • CMUX_MANUAL_SWAP
  • CMUX_MANUAL_DATA / CMUX_MANUAL_COMMAND
  • CMUX_MANUAL_EXIT

Suggest using the console example to checkout the mode transitions, created additional states, such as CMUX1, CMUX2 ... CMUX5

Thanks for the thorough testing, btw. It seems that the tricky part is the exit of the CMUX mode and apparently it behaves differently on different devices, e.g. on 7600 it also hangs-up (destroys the carrier); in your device though it looks like it's not able to re-establish the connection for some reason. Also noticed some inconsistencies with other devices, like A7670 which doesn't support exiting CMUX at all. I'm planning on adding some notes to the docs about these "known issues"

@diplfranzhoepfinger
Copy link
Contributor

BUT:

do we need really to exit CMUX ? 

i did not test, i only switched the Channel to DATA and back to console, and this works perfect. 

i will veryfy with my SIM7070 and some GPS Receive. 

Thanks,

@mallaprashant
Copy link

C APIs for CMUX manual transitions are not working.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants