Last Updated on : 2024-11-20 08:51:41download
Tuya Bluetooth SDK can send data to the device application by using the following two approaches.
The following framework shows how chips that run an RTOS process events from Tuya Bluetooth SDK. You can also adopt this framework to chips that do not run an RTOS, but do not call tuya_ble_event_response()
to respond to Tuya Bluetooth SDK after an event is processed. This topic describes the concept of different events.
With an RTOS, the application processes an event.
os_msg_queue_create(&tuya_custom_queue_handle,MAX_NUMBER_OF_TUYA_CUSTOM_MESSAGE, sizeof(tuya_ble_cb_evt_param_t));
tuya_ble_callback_queue_register(tuya_custom_queue_handle);
/*Application task to process an event from Tuya Bluetooth SDK*/
void tuya_custom_queue_handle(void *p_param)
{
tuya_ble_cb_evt_param_t event;
while (true)
{
if (os_msg_recv(tuya_custom_queue_handle, &event, 0xFFFFFFFF) == true)
{
switch (event.evt)
{
case TUYA_BLE_CB_EVT_CONNECTE_STATUS:
break;
case TUYA_BLE_CB_EVT_DP_WRITE:
break;
case TUYA_BLE_CB_EVT_DP_DATA_REPORT_RESPONSE:
break;
case TUYA_BLE_CB_EVT_DP_DATA_WTTH_TIME_REPORT_RESPONSE:
break;
case TUYA_BLE_CB_EVT_UNBOUND:
break;
case TUYA_BLE_CB_EVT_ANOMALY_UNBOUND:
break;
case TUYA_BLE_CB_EVT_DEVICE_RESET:
break;
case TUYA_BLE_CB_EVT_DP_QUERY:
break;
case TUYA_BLE_CB_EVT_OTA_DATA:
break;
case TUYA_BLE_CB_EVT_NETWORK_INFO:
break;
case TUYA_BLE_CB_EVT_WIFI_SSID:
break;
case TUYA_BLE_CB_EVT_TIME_STAMP:
break;
case TUYA_BLE_CB_EVT_TIME_NORMAL:
break;
case TUYA_BLE_CB_EVT_DATA_PASSTHROUGH:
break;
default:
break;
}
tuya_ble_event_response(&event);// With an RTOS, the application must call this function to return the result to the Tuya Bluetooth SDK.
}
}
}
Without an RTOS, the application registers a callback.
/*A callback for processing event from Tuya Bluetooth SDK*/
static void tuya_cb_handler(tuya_ble_cb_evt_param_t* event)
{
switch (event-evt)
{
case TUYA_BLE_CB_EVT_CONNECTE_STATUS:
break;
case TUYA_BLE_CB_EVT_DP_WRITE:
break;
case TUYA_BLE_CB_EVT_DP_DATA_REPORT_RESPONSE:
break;
case TUYA_BLE_CB_EVT_DP_DATA_WTTH_TIME_REPORT_RESPONSE:
break;
case TUYA_BLE_CB_EVT_UNBOUND:
break;
case TUYA_BLE_CB_EVT_ANOMALY_UNBOUND:
break;
case TUYA_BLE_CB_EVT_DEVICE_RESET:
break;
case TUYA_BLE_CB_EVT_DP_QUERY:
break;
case TUYA_BLE_CB_EVT_OTA_DATA:
break;
case TUYA_BLE_CB_EVT_NETWORK_INFO:
break;
case TUYA_BLE_CB_EVT_WIFI_SSID:
break;
case TUYA_BLE_CB_EVT_TIME_STAMP:
break;
case TUYA_BLE_CB_EVT_TIME_NORMAL:
break;
case TUYA_BLE_CB_EVT_DATA_PASSTHROUGH:
break;
default:
break;
}
}
void tuya_ble_app_init(void)
{
device_param.p_type = TUYA_BLE_PRODUCT_ID_TYPE_PID;
device_param.product_id_len = 8;
memcpy(device_param.product_id,APP_PRODUCT_ID,8);
device_param.firmware_version = TY_APP_VER_NUM;
device_param.hardware_version = TY_HARD_VER_NUM;
device_param.adv_local_name_len = strlen(device_local_name);
memcpy(device_param.adv_local_name,device_local_name,device_param.adv_local_name_len);
device_param.use_ext_license_key = 1; //If use the license stored by the SDK,initialized to 0, Otherwise 1.
device_param.device_id_len = 16;
if(device_param.use_ext_license_key==1)
{
memcpy(device_param.auth_key,(void *)auth_key_test,AUTH_KEY_LEN);
memcpy(device_param.device_id,(void *)device_id_test,DEVICE_ID_LEN);
memcpy(device_param.mac_addr_string,mac_test,12);
device_param.mac_addr.addr_type = TUYA_BLE_ADDRESS_TYPE_RANDOM;
}
tuya_ble_sdk_init(&device_param); // SDK initialization.
tuya_ble_callback_queue_register(tuya_cb_handler); // Register the callback for processing events.
tuya_ota_init(); // OTA initialization.
}
Event | TUYA_BLE_CB_EVT_CONNECTE_STATUS |
---|---|
Data structure | As shown below |
Description | The status of the Bluetooth connection changes. |
Note | Tuya Bluetooth SDK sends this event to the device application on changes of status of Bluetooth. |
Data structure
typedef enum{
UNBONDING_UNCONN = 0, // The device is unbound and not connected.
UNBONDING_CONN, // The device is unbound, connected, and authorized.
BONDING_UNCONN, // The device is bound and not connected.
BONDING_CONN, // The device is bound, connected, and authorized.
BONDING_UNAUTH_CONN, // The device is bound, connected, and unauthorized.
UNBONDING_UNAUTH_CONN, // The device is unbound, connected, and unauthorized.
UNKNOW_STATUS // Unknown
}tuya_ble_connect_status_t;
If Tuya Bluetooth SDK sends the connection status twice, the application should compare the difference between the current and previous status before it triggers status-based features. The following sample code shows how to deal with this situation.
static tuya_ble_connect_status_t current_connect_status = UNKNOW_STATUS;
static tuya_ble_connect_status_t last_connect_status = UNKNOW_STATUS;
static void tuya_cb_handler(tuya_ble_cb_evt_param_t* event)
{
int16_t result = 0;
tuya_ble_status_t err_code;
switch (event->evt)
{
case TUYA_BLE_CB_EVT_CONNECTE_STATUS: // The status of the Bluetooth connection changes.
TUYA_APP_LOG_INFO("received tuya ble conncet status update event,current connect status = %d",event->connect_status);
if((event->connect_status == BONDING_CONN)&&(last_connect_status!=BONDING_CONN))
{
//Do something
}
last_connect_status = event->connect_status;
break;
default:
break;
}
}
Event | TUYA_BLE_CB_EVT_DP_WRITE |
---|---|
Data structure | - |
Description | Tuya Bluetooth SDK receives data point (DP) data from the mobile app. |
Note | Deprecated |
Event | TUYA_BLE_CB_EVT_DP_DATA_RECEIVED |
---|---|
Data structure | tuya_ble_dp_data_received_data_t; |
Description | Tuya Bluetooth SDK receives data point (DP) data from the mobile app. |
Data structure
typedef struct {
uint32_t sn;
uint8_t *p_data;
uint16_t data_len;
} tuya_ble_dp_data_received_data_t;
Parameters
sn
: the serial number of data sending events from the mobile app, which is incremented by one for each event.
p_data
: the pointer to DP data. The DP data format is specified as follows.
Data of DP 1 | – | Data of DP n | ||||||
---|---|---|---|---|---|---|---|---|
1 | 2 | 3 to 4 | 5 and greater | – | n | n+1 | n+2 to n+3 | n+4 and greater |
Dp_id | Dp_type | Dp_len | Dp_data | – | Dp_id | Dp_type | Dp_len | Dp_data |
For more information, see the description of tuya_ble_dp_data_send() in the API
layer.
data_len
: the length of the DP data.
Event | TUYA_BLE_CB_EVT_DP_QUERY |
---|---|
Data structure | tuya_ble_dp_query_data_t |
Description | Tuya Bluetooth SDK receives an array of DP IDs from the mobile app. |
Note | data_len=0 indicates querying all DPs. Otherwise, each byte that p_data points to indicates a DP to be queried. For example, if data_len=3 and p_data points to {0x01,0x02,0x03} , it indicates that the mobile app queries three DPs, namely, dp_id=1, dp_id=2, and dp_id=3. |
Data structure
/*
* query DP point data, if data_len is 0, means query all DP point data, otherwise query the DP point in p_data buffer.
* */
typedef struct{
uint8_t *p_data;
uint16_t data_len;
}tuya_ble_dp_query_data_t;
Event | TUYA_BLE_CB_EVT_OTA_DATA |
---|---|
Data structure | tuya_ble_ota_data_t |
Description | Tuya Bluetooth SDK receives OTA firmware updates from the mobile app. |
Data structure
typedef struct{
tuya_ble_ota_data_type_t type;
uint16_t data_len;
uint8_t *p_data;
}tuya_ble_ota_data_t;
Event | TUYA_BLE_CB_EVT_BULK_DATA |
---|---|
Data structure | tuya_ble_bulk_data_request_t |
Description | Bulk data transfer. |
Data structure
typedef struct {
tuya_ble_bulk_data_evt_type_t evt;
uint8_t bulk_type;
union
{
tuya_ble_bulk_data_evt_read_block_req_t block_data_req_data;
tuya_ble_bulk_data_evt_send_data_req_t send_data_req_data;
} params;
} tuya_ble_bulk_data_request_t;
Event | TUYA_BLE_CB_EVT_NETWORK_INFO |
---|---|
Data structure | tuya_ble_network_data_t |
Description | Tuya Bluetooth SDK receives data for pairing from the mobile app.{wifi_ssid":"tuya","password":"12345678","token":"xxxxxxxxxx"} |
Note | This event applies to the Wi-Fi and Bluetooth combo module. |
Data structure
/*
* network data,unformatted json data,for example " {"wifi_ssid":"tuya","password":"12345678","token":"xxxxxxxxxx"} "
* */
typedef struct{
uint16_t data_len;//include '\0'
uint8_t *p_data;
}tuya_ble_network_data_t;
Event | TUYA_BLE_CB_EVT_WIFI_SSID |
---|---|
Data structure | tuya_ble_wifi_ssid_data_t |
Description | Tuya Bluetooth SDK receives Wi-Fi information for pairing, such as a string {"wifi_ssid":"tuya","password":"12345678"} . |
Note | This event only applies to the Wi-Fi and Bluetooth combo module. Compared to TUYA_BLE_CB_EVT_NETWORK_INFO , this event does not have the token field and is used for updating the Wi-Fi SSID of paired devices. |
Data structure
/*
* wifi ssid data,unformatted json data,for example " {"wifi_ssid":"tuya","password":"12345678"} "
* */
typedef struct{
uint16_t data_len;//include '\0'
uint8_t *p_data;
}tuya_ble_wifi_ssid_data_t;
Event | TUYA_BLE_CB_EVT_TIME_STAMP |
---|---|
Data structure | tuya_ble_timestamp_data_t |
Description | Tuya Bluetooth SDK receives a string of Unix timestamp from the mobile app. For example, 0000000123456 indicates 123,456 ms. |
Note | The value of time_zone is 100 times the actual time zone. For example, -8 should be represented by -800 . |
Data structure
/*
* Unix timestamp
* */
typedef struct{
uint8_t timestamp_string[14];
int16_t time_zone; // Actual time zone multiplied by 100.
}tuya_ble_timestamp_data_t;
Parameters
timestamp_string
: 13-digit string in milliseconds, such as 0000000123456
indicating 123,456 ms. The string is 14 bytes, including the terminating null character.
time_zone
: The value of this field is 100 times the actual time zone. For example, if this value is -800
, the actual time zone is -8
.
The mobile app will sync time with the Tuya Bluetooth SDK each time the device is connected. The SDK will send the received time data to the device application. The Unix timestamp is from the cloud and the time zone is from the mobile phone system.
The device application calls tuya_ble_time_req(0)
and receives this event. The Unix timestamp is from the cloud and the time zone is from the mobile phone system.
If the device application calls tuya_ble_time_req(1)
and tuya_ble_time_req(2)
, it will receive TUYA_BLE_CB_EVT_TIME_NORMAL
and TUYA_BLE_CB_EVT_APP_LOCAL_TIME_NORMAL
instead of this event.
Event | TUYA_BLE_CB_EVT_TIME_NORMAL |
---|---|
Data structure | tuya_ble_time_noraml_data_t |
Description | Tuya Bluetooth SDK receives time data in general format from the mobile app. Example of date and time: Tuesday, 12:23:25 on April 28, 2019 (UTC+8) Data: 0x13, 0x04, 0x1C, 0x0C, 0x17, 0x19, 0x02, and 0x0320 (time zone) |
Data structure
/*
* normal time formatted
* */
typedef struct{
uint16_t nYear; // Actual year minus 2000
uint8_t nMonth;
uint8_t nDay;
uint8_t nHour;
uint8_t nMin;
uint8_t nSec;
uint8_t DayIndex; /* 0 = Sunday */
int16_t time_zone; //actual time zone Multiply by 100.
}tuya_ble_time_noraml_data_t;
The device application calls tuya_ble_time_req(1)
to receive this event. tuya_ble_time_req(1)
is from the cloud and other data is from the mobile phone system.
Event | TUYA_BLE_CB_EVT_APP_LOCAL_TIME_NORMAL |
---|---|
Data structure | tuya_ble_time_noraml_data_t |
Description | Tuya Bluetooth SDK receives time data in general format from the mobile app. Example of date and time: Tuesday, 12:23:25 on April 28, 2019 (UTC+8) Data: 0x13, 0x04, 0x1C, 0x0C, 0x17, 0x19, 0x02, and 0x0320 (time zone) |
Data structure
typedef struct {
uint16_t nYear; // Actual year minus 2000
uint8_t nMonth;
uint8_t nDay;
uint8_t nHour;
uint8_t nMin;
uint8_t nSec;
uint8_t DayIndex; /* 0 = Sunday */
int16_t time_zone; // Actual time zone multiplied by 100.
} tuya_ble_time_noraml_data_t;
The device application calls tuya_ble_time_req(2)
to receive this event. All the time data is from the mobile phone system.
Event | TUYA_BLE_CB_EVT_TIME_STAMP_WITH_DST |
---|---|
Data structure | - |
Description | - |
Note | Not used |
Event | TUYA_BLE_CB_EVT_DATA_PASSTHROUGH |
---|---|
Data structure | tuya_ble_passthrough_data_t |
Description | Tuya Bluetooth SDK receives raw data from the mobile app. |
Note | Tuya Bluetooth SDK does not parse the data. The data format is specified by the device application and mobile app. |
Data structure
typedef struct{
uint16_t data_len;
uint8_t *p_data;
}tuya_ble_passthrough_data_t;
Raw data transfer channel is used to receive and send the protocol data defined by the device application and the mobile app. Tuya Bluetooth SDK does not parse the data. The raw data from the mobile app is sent to the device application through the event TUYA_BLE_CB_EVT_DATA_PASSTHROUGH
. The device application calls tuya_ble_data_passthrough()
to send data to the mobile app.
Event | TUYA_BLE_CB_EVT_DP_DATA_REPORT_RESPONSE |
---|---|
Data structure | - |
Description | - |
Note | Deprecated |
Event | TUYA_BLE_CB_EVT_DP_DATA_WTTH_TIME_REPORT_RESPONSE |
---|---|
Data structure | - |
Description | - |
Note | Deprecated |
Event | TUYA_BLE_CB_EVT_DP_DATA_WITH_FLAG_REPORT_RESPONSE |
---|---|
Data structure | - |
Description | - |
Note | Deprecated |
Event | TUYA_BLE_CB_EVT_DP_DATA_SEND_RESPONSE |
---|---|
Data structure | tuya_ble_dp_data_send_response_data_t |
Description | The response event of tuya_ble_dp_data_send() . |
Data structure
typedef struct {
uint32_t sn;
tuya_ble_dp_data_send_type_t type;
tuya_ble_dp_data_send_mode_t mode;
tuya_ble_dp_data_send_ack_t ack;
uint8_t status; // 0 - succeed, 1- failed.
} tuya_ble_dp_data_send_response_data_t;
The device application calls tuya_ble_dp_data_send()
to send DP data to the mobile app. If the ACK
parameter is DP_SEND_WITH_RESPONSE
, the mobile app will send a response to Tuya Bluetooth SDK after receiving the data. Tuya Bluetooth SDK sends this event to the device application on receiving a response from the mobile app.
The parameters sn
, type
, mode
, and ack
are the same as these of tuya_ble_dp_data_send()
. If the status
is 0
, it indicates that the mobile app receives the DP data.
If the device application sends DP data multiple times by calling tuya_ble_dp_data_send()
, the device application can determine which operation is successful by parameters sn
and status
.
Event | TUYA_BLE_CB_EVT_DP_DATA_WITH_FLAG_AND_TIME_REPORT_RESPONSE |
---|---|
Data structure | tuya_ble_dp_data_with_time_send_response_data_t |
Description | The response event of tuya_ble_dp_data_with_time_send() . |
Data structure
typedef struct {
uint32_t sn;
tuya_ble_dp_data_send_type_t type;
tuya_ble_dp_data_send_mode_t mode;
tuya_ble_dp_data_send_ack_t ack;
uint8_t status; // 0 - succeed, 1- failed.
} tuya_ble_dp_data_with_time_send_response_data_t;
The device application calls tuya_ble_dp_data_with_time_send()
to send DP data to the mobile app. The app sends a response to Tuya Bluetooth SDK on receiving DP data and then the SDK sends this event to the application on receiving the response.
The parameters sn
, type
, mode
, and ack
are the same as these of tuya_ble_dp_data_with_time_send()
.
If the status
is 0
, it indicates that the mobile app receives the DP data.
If the device application sends DP data multiple times by calling tuya_ble_dp_data_with_time_send()
, the device application can determine which operation is successful by parameters sn
and status
.
Event | TUYA_BLE_CB_EVT_UNBOUND |
---|---|
Data structure | tuya_ble_unbound_data_t |
Description | This event indicates the mobile app sends a command to unbind a device. The data is a reserved field and has no meaning. |
Note |
Data structure
typedef struct {
uint8_t data;
} tuya_ble_unbound_data_t;
Tuya Bluetooth SDK will send this event to the device application after the device is unbound from the mobile app.
Event | TUYA_BLE_CB_EVT_ANOMALY_UNBOUND |
---|---|
Data structure | tuya_ble_anomaly_unbound_data_t |
Description | This event indicates the mobile app sends a command to abnormally unbind a device. The data is a reserved field and has no meaning. |
Data structure
typedef struct{
uint8_t data;
}tuya_ble_anomaly_unbound_data_t;
Tuya Bluetooth SDK will send this event to the device application after the offline device is unbound from the mobile app.
Offline unbinding
Offline unbinding indicates an offline Bluetooth device is removed from the mobile app.
Event | TUYA_BLE_CB_EVT_DEVICE_RESET |
---|---|
Data structure | tuya_ble_device_reset_data_t |
Description | This event indicates the mobile app sends a command to reset a device. The data is a reserved field and has no meaning. |
Note | The application will execute the predefined operations on receiving the reset event. |
Data structure
typedef struct{
uint8_t data;
}tuya_ble_device_reset_data_t;
Tuya Bluetooth SDK will send this event to the device application after the device is unbound from the mobile app and data is cleared.
Event | TUYA_BLE_CB_EVT_UPDATE_LOGIN_KEY_VID |
---|---|
Data structure | tuya_ble_login_key_vid_data_t |
Description | After a device is registered and bound, the mobile app will send the login key and virtual ID to the device. |
Note | This event applies to the Wi-Fi and Bluetooth combo module. |
Data structure
typedef struct {
uint8_t login_key_len;
uint8_t vid_len;
uint8_t beacon_key_len;
uint8_t login_key[LOGIN_KEY_LEN];
uint8_t vid[DEVICE_VIRTUAL_ID_LEN];
uint8_t beacon_key[BEACON_KEY_LEN];
} tuya_ble_login_key_vid_data_t;
Event | TUYA_BLE_CB_EVT_UNBIND_RESET_RESPONSE |
---|---|
Data structure | tuya_ble_unbind_reset_response_data_t |
Description | Unbind a device with local control and respond to a reset event. |
Data structure
typedef enum {
RESET_TYPE_UNBIND,
RESET_TYPE_FACTORY_RESET,
} tuya_ble_reset_type_t;
typedef struct {
tuya_ble_reset_type_t type;
uint8_t status; //0-succeed,1-failed.
} tuya_ble_unbind_reset_response_data_t;
The device application can unbind and reset a device with local control by calling tuya_ble_device_unbind()
and tuya_ble_device_factory_reset()
.
The device ID is used to retrieve the historical data stored in the cloud. Unbinding a device will not clear the device ID, which means the history can be recovered once the device is bound again. However, resetting a device will clear the device ID so the history cannot be recovered.
tuya_ble_device_unbind()
and tuya_ble_device_factory_reset()
are asynchronous APIs. They will return a message to Tuya Bluetooth SDK instead of an immediate command execution when they are called. After executing the command, Tuya Bluetooth SDK will send this event to the device application. Therefore, after the device application calls these two APIs, a blocking delay or immediate reboot is not allowed. Otherwise, the operation might fail.
Event | TUYA_BLE_CB_EVT_WEATHER_DATA_REQ_RESPONSE |
---|---|
Data structure | tuya_ble_weather_data_req_response_t |
Description | Weather service |
Data structure
typedef struct{
uint8_t status;
}tuya_ble_weather_data_req_response_t;
Event | TUYA_BLE_CB_EVT_WEATHER_DATA_RECEIVED |
---|---|
Data structure | tuya_ble_weather_data_received_data_t |
Description | Weather service |
Data structure
typedef struct{
uint16_t object_count; /**< weather data object counts. */
uint8_t location; /**< location. */
uint8_t *p_data; /**< weather data. */
uint16_t data_len; /**< weather data length. */
}tuya_ble_weather_data_received_data_t;
Callback_Event | TUYA_BLE_CB_EVT_SCENE_REQ_RESPONSE |
---|---|
Data structure | tuya_ble_scene_req_response_t |
Description | The response event of tuya_ble_feature_scene_request() . |
Note | This event applies to products with screen displays, such as smartwatches and wrist-worn trackers. |
Data structure
typedef struct {
uint16_t scene_cmd; /**< scene cmd. 1- scene data. 2-scene control */
uint8_t status; /**< response status, 0-success 1-failed. */
uint32_t err_code; /**< err code. */
} tuya_ble_scene_req_response_t;
Callback_Event | TUYA_BLE_CB_EVT_SCENE_DATA_RECEIVED |
---|---|
Data structure | tuya_ble_scene_data_received_data_t |
Description | Returns scene data. |
Note | The SDK provides the method of parsing the returned scene data. For more information, see the code annotation in tuya_ble_feature_scene.h . |
Data structure
/**
* received scene list data
*
*/
typedef struct {
uint8_t status; /**< status, 0-success 1-failed. */
uint32_t err_code; /**< err code. */
bool need_update; /**< need update. */
uint32_t check_code; /**< scene data check code, used crc32. */
uint8_t *p_data; /**< scene data. */
uint16_t data_len; /**< scene data length. */
} tuya_ble_scene_data_received_data_t;
Parameters
status
: 0
indicates success. 1
indicates failure.
err_code
: the error code.
need_update
: indicates whether to update the scene data.
0
: not update.1
: update. The device parses p_data
and saves the latest scene data.p_data
: stores all the returned scene data. The scene data is defined as follows.
Length of scene ID | Scene ID | Length of scene name (Unicode) | Scene name (Unicode) | … | Length of scene ID | Scene ID | Length of scene name (Unicode) | Scene name (Unicode) |
---|---|---|---|---|---|---|---|---|
1 byte | n byte(s) | 2 bytes | m byte(s) | … | 1 byte | n byte(s) | 2 bytes | m byte(s) |
data_len
: the length of the p_data
.
Callback_Event | TUYA_BLE_CB_EVT_SCENE_CTRL_RESULT_RECEIVED |
---|---|
Data structure | tuya_ble_scene_ctrl_received_data_t |
Description | Returns data of scene control. |
Note | The SDK provides the method of parsing the returned data of scene control. For more information, see the code annotation in tuya_ble_feature_scene.h . |
Data structure
/**
* received scene control result
*
*/
typedef struct {
uint8_t status; /**< status, 0-success 1-failed. */
uint32_t err_code; /**< err code. */
uint8_t scene_id_len; /**< scene id length. */
uint8_t *p_scene_id; /**< scene id. */
} tuya_ble_scene_ctrl_received_data_t;
Parameters
status
: 0
indicates success. 1
indicates failure.err_code
: the error code.scene_id_len
: the length of the scene ID.p_scene_id
: the pointer to the scene ID to distinguish between returned data of different scenes.Is this page helpful?
YesFeedbackIs this page helpful?
YesFeedback