initial commmit

This commit is contained in:
Sam
2025-05-22 16:26:07 +10:00
commit 9ba0ff268f
1292 changed files with 225422 additions and 0 deletions

View File

@@ -0,0 +1 @@
eabf03fe3cb1bdb5325d2725f420c6324c3e150033d893ce3737222929455a55

View File

@@ -0,0 +1,21 @@
## 1.0.3
- Fixed a bug with interface mismatch on EP IN transfer complete while several HID devices are present.
- Fixed a bug during device freeing, while detaching one of several attached HID devices.
## 1.0.2
- Added support for ESP32-P4
- Fixed device open procedure for HID devices with multiple non-sequential interfaces.
## 1.0.1
- Fixed a bug where configuring the driver with `create_background_task = false` did not properly initialize the driver. This lead to `the hid_host_uninstall()` hang-up.
- Fixed a bug where `hid_host_uninstall()` would cause a crash during the call while USB device has not been removed.
- Added `hid_host_get_device_info()` to get the basic information of a connected USB HID device.
## 1.0.0
- Initial version

View File

@@ -0,0 +1,3 @@
idf_component_register( SRCS "hid_host.c"
INCLUDE_DIRS "include"
PRIV_REQUIRES usb )

View File

@@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@@ -0,0 +1,52 @@
# USB Host HID (Human Interface Device) Driver
[![Component Registry](https://components.espressif.com/components/espressif/usb_host_hid/badge.svg)](https://components.espressif.com/components/espressif/usb_host_hid)
This directory contains an implementation of a USB HID Driver implemented on top of the [USB Host Library](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-reference/peripherals/usb_host.html).
HID driver allows access to HID devices.
## Usage
The following steps outline the typical API call pattern of the HID Class Driver:
1. Install the USB Host Library via 'usb_host_install()'
2. Install the HID driver via 'hid_host_install()'
3. The HID Host driver device callback provide the following events (via two callbacks):
- HID_HOST_DRIVER_EVENT_CONNECTED
- HID_HOST_INTERFACE_EVENT_INPUT_REPORT
- HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR
- HID_HOST_INTERFACE_EVENT_DISCONNECTED
4. Specific HID device can be opened or closed with:
- 'hid_host_device_open()'
- 'hid_host_device_close()'
5. To enable / disable data receiving in case of event (keyboard key was pressed or mouse device was moved e.t.c) use:
- 'hid_host_device_start()'
- 'hid_host_device_stop()'
6. HID Class specific device requests:
- 'hid_host_interface_get_report_descriptor()'
- 'hid_class_request_get_report()'
- 'hid_class_request_get_idle()'
- 'hid_class_request_get_protocol()'
- 'hid_class_request_set_report()'
- 'hid_class_request_set_idle()'
- 'hid_class_request_set_protocol()'
7. When HID device event occurs the driver call an interface callback with events:
- HID_HOST_INTERFACE_EVENT_INPUT_REPORT
- HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR
- HID_HOST_INTERFACE_EVENT_DISCONNECTED
8. The HID driver can be uninstalled via 'hid_host_uninstall()'
## Known issues
- Empty
## Examples
- For an example, refer to [hid_host_example](https://github.com/espressif/esp-idf/tree/master/examples/peripherals/usb/host/hid)
## Supported Devices
- HID Driver support any HID compatible device with a USB bIterfaceClass 0x03 (Human Interface Device).
- There are two options to handle HID device input data: either in RAW format or via special event handlers (which are available only for HID Devices which support Boot Protocol).

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,13 @@
dependencies:
idf: '>=4.4'
description: USB Host HID driver
repository: git://github.com/espressif/esp-usb.git
repository_info:
commit_sha: 39b0de39a90c9e391db26f06d54e4b10f58be231
path: host/class/hid/usb_host_hid
targets:
- esp32s2
- esp32s3
- esp32p4
url: https://github.com/espressif/esp-usb/tree/master/host/class/hid/usb_host_hid
version: 1.0.3

View File

@@ -0,0 +1,147 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief HID Subclass
*
* @see 4.2 Subclass, p.8 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_SUBCLASS_NO_SUBCLASS = 0x00,
HID_SUBCLASS_BOOT_INTERFACE = 0x01
} __attribute__((packed)) hid_subclass_t;
/**
* @brief HID Protocols
*
* @see 4.3 Protocols, p.9 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_PROTOCOL_NONE = 0x00,
HID_PROTOCOL_KEYBOARD = 0x01,
HID_PROTOCOL_MOUSE = 0x02,
HID_PROTOCOL_MAX
} __attribute__((packed)) hid_protocol_t;
/**
* @brief HID Descriptor
*
* @see 6.2.1 HID Descriptor, p.22 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
uint8_t bLength; // Numeric expression that is the total size of the HID descriptor
uint8_t bDescriptorType; // Constant name specifying type of HID descriptor
uint16_t bcdHID; // Numeric expression identifying the HIDClass Specification release
uint8_t bCountryCode; // Numeric expression identifying country code of the localized hardware
uint8_t bNumDescriptors; // Numeric expression specifying the number of class descriptors (always at least one i.e. Report descriptor.)
uint8_t bReportDescriptorType; // Constant name identifying type of class descriptor. See Section 7.1.2: Set_Descriptor Request for a table of class descriptor constants
uint16_t wReportDescriptorLength; // Numeric expression that is the total size of the Report descriptor
// Optional descriptors may follow further
} __attribute__((packed)) hid_descriptor_t;
/**
* @brief HID Country Codes
*
* @see 6.2.1 HID Descriptor, p.23 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_COUNTRY_CODE_NOT_SUPPORTED = 0x00,
HID_COUNTRY_CODE_ARABIC = 0x01,
HID_COUNTRY_CODE_BELGIAN = 0x02,
HID_COUNTRY_CODE_CANADIAN_BILINGUAL = 0x03,
HID_COUNTRY_CODE_CANADIAN_FRENCH = 0x04,
HID_COUNTRY_CODE_CZECH = 0x05,
HID_COUNTRY_CODE_DANISH = 0x06,
HID_COUNTRY_CODE_FINNISH = 0x07,
HID_COUNTRY_CODE_FRENCH = 0x08,
HID_COUNTRY_CODE_GERMAN = 0x09,
HID_COUNTRY_CODE_GREEK = 0x0A,
HID_COUNTRY_CODE_HEBREW = 0x0B,
HID_COUNTRY_CODE_HUNGARY = 0x0C,
HID_COUNTRY_CODE_ISO = 0x0D,
HID_COUNTRY_CODE_ITALIAN = 0x0E,
HID_COUNTRY_CODE_JAPAN = 0x0F,
HID_COUNTRY_CODE_KOREAN = 0x10,
HID_COUNTRY_CODE_LATIN_AMERICAN = 0x11,
HID_COUNTRY_CODE_NETHERLANDS = 0x12,
HID_COUNTRY_CODE_NORWEGIAN = 0x13,
HID_COUNTRY_CODE_PERSIAN = 0x14,
HID_COUNTRY_CODE_POLAND = 0x15,
HID_COUNTRY_CODE_PORTUGUESE = 0x16,
HID_COUNTRY_CODE_RUSSIA = 0x17,
HID_COUNTRY_CODE_SLOVAKIA = 0x18,
HID_COUNTRY_CODE_SPANISH = 0x19,
HID_COUNTRY_CODE_SWEDISH = 0x1A,
HID_COUNTRY_CODE_SWISS_F = 0x1B,
HID_COUNTRY_CODE_SWISS_G = 0x1C,
HID_COUNTRY_CODE_SWITZERLAND = 0x1D,
HID_COUNTRY_CODE_TAIWAN = 0x1E,
HID_COUNTRY_CODE_TURKISH_Q = 0x1F,
HID_COUNTRY_CODE_UK = 0x20,
HID_COUNTRY_CODE_US = 0x21,
HID_COUNTRY_CODE_YUGOSLAVIA = 0x22,
HID_COUNTRY_CODE_TURKISH_F = 0x23
} __attribute__((packed)) hid_country_code_t;
/**
* @brief HID Class Descriptor Types
*
* @see 7.1, p.49 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_CLASS_DESCRIPTOR_TYPE_HID = 0x21,
HID_CLASS_DESCRIPTOR_TYPE_REPORT = 0x22,
HID_CLASS_DESCRIPTOR_TYPE_PHYSICAL = 0x23
} __attribute__((packed)) hid_class_descritpor_type_t;
/**
* @brief HID Class-Specific Requests
*
* @see 7.2, p.50 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_CLASS_SPECIFIC_REQ_GET_REPORT = 0x01,
HID_CLASS_SPECIFIC_REQ_GET_IDLE = 0x02,
HID_CLASS_SPECIFIC_REQ_GET_PROTOCOL = 0x03,
HID_CLASS_SPECIFIC_REQ_SET_REPORT = 0x09,
HID_CLASS_SPECIFIC_REQ_SET_IDLE = 0x0A,
HID_CLASS_SPECIFIC_REQ_SET_PROTOCOL = 0x0B
} __attribute__((packed)) hid_class_specific_req_t;
/**
* @brief HID Report Types
*
* @see 7.2.1, p.51 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_REPORT_TYPE_INPUT = 0x01,
HID_REPORT_TYPE_OUTPUT = 0x02,
HID_REPORT_TYPE_FEATURE = 0x03,
} __attribute__((packed)) hid_report_type_t;
/**
* @brief HID Report protocol
*
* @see 7.2.5/7.2.6, p.54 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_REPORT_PROTOCOL_BOOT = 0x00,
HID_REPORT_PROTOCOL_REPORT = 0x01,
HID_REPORT_PROTOCOL_MAX
} __attribute__((packed)) hid_report_protocol_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,313 @@
/*
* SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <wchar.h>
#include <stdint.h>
#include "esp_err.h"
#include <freertos/FreeRTOS.h>
#include "hid.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief USB HID HOST string descriptor maximal length
*
* The maximum possible number of characters in an embedded string is device specific.
* For USB devices, the maximum string length is 126 wide characters (not including the terminating NULL character).
* This is a length, which is available to upper level application during getting information
* of HID Device with 'hid_host_get_device_info' call.
*
* To decrease memory usage 32 wide characters (64 bytes per every string) is used.
*/
#define HID_STR_DESC_MAX_LENGTH 32
typedef struct hid_interface *hid_host_device_handle_t; /**< Device Handle. Handle to a particular HID interface */
// ------------------------ USB HID Host events --------------------------------
/**
* @brief USB HID HOST Device event id
*/
typedef enum {
HID_HOST_DRIVER_EVENT_CONNECTED = 0x00, /**< HID Device has been found in connected USB device (at least one) */
} hid_host_driver_event_t;
/**
* @brief USB HID HOST Interface event id
*/
typedef enum {
HID_HOST_INTERFACE_EVENT_INPUT_REPORT = 0x00, /**< HID Device input report */
HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR, /**< HID Device transfer error */
HID_HOST_INTERFACE_EVENT_DISCONNECTED, /**< HID Device has been disconnected */
} hid_host_interface_event_t;
/**
* @brief HID device descriptor common data.
*/
typedef struct {
uint16_t VID;
uint16_t PID;
wchar_t iManufacturer[HID_STR_DESC_MAX_LENGTH];
wchar_t iProduct[HID_STR_DESC_MAX_LENGTH];
wchar_t iSerialNumber[HID_STR_DESC_MAX_LENGTH];
} hid_host_dev_info_t;
/**
* @brief USB HID Host device parameters
*/
typedef struct {
uint8_t addr; /**< USB Address of connected HID device */
uint8_t iface_num; /**< HID Interface Number */
uint8_t sub_class; /**< HID Interface SubClass */
uint8_t proto; /**< HID Interface Protocol */
} hid_host_dev_params_t;
// ------------------------ USB HID Host callbacks -----------------------------
/**
* @brief USB HID driver event callback.
*
* @param[in] hid_handle HID device handle (HID Interface)
* @param[in] event HID driver event
* @param[in] arg User argument from HID driver configuration structure
*/
typedef void (*hid_host_driver_event_cb_t)(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg);
/**
* @brief USB HID Interface event callback.
*
* @param[in] hid_device_handle HID device handle (HID Interface)
* @param[in] event HID Interface event
* @param[in] arg User argument
*/
typedef void (*hid_host_interface_event_cb_t)(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg);
// ----------------------------- Public ---------------------------------------
/**
* @brief HID configuration structure.
*/
typedef struct {
bool create_background_task; /**< When set to true, background task handling USB events is created.
Otherwise user has to periodically call hid_host_handle_events function */
size_t task_priority; /**< Task priority of created background task */
size_t stack_size; /**< Stack size of created background task */
BaseType_t core_id; /**< Select core on which background task will run or tskNO_AFFINITY */
hid_host_driver_event_cb_t callback; /**< Callback invoked when HID driver event occurs. Must not be NULL. */
void *callback_arg; /**< User provided argument passed to callback */
} hid_host_driver_config_t;
/**
* @brief HID device configuration structure (HID Interface)
*/
typedef struct {
hid_host_interface_event_cb_t callback; /**< Callback invoked when HID Interface event occurs */
void *callback_arg; /**< User provided argument passed to callback */
} hid_host_device_config_t;
/**
* @brief USB HID Host install USB Host HID Class driver
*
* @param[in] config configuration structure HID to create
* @return esp_err_r
*/
esp_err_t hid_host_install(const hid_host_driver_config_t *config);
/**
* @brief USB HID Host uninstall HID Class driver
* @return esp_err_t
*/
esp_err_t hid_host_uninstall(void);
/**
* @brief USB HID Host open a device with specific device parameters
*
* @param[in] iface_handle Handle of the HID device to open
* @param[in] config Configuration structure HID device to open
* @return esp_err_t
*/
esp_err_t hid_host_device_open(hid_host_device_handle_t hid_dev_handle,
const hid_host_device_config_t *config);
/**
* @brief USB HID Host close device
*
* @param[in] hid_dev_handle Handle of the HID device to close
* @return esp_err_t
*/
esp_err_t hid_host_device_close(hid_host_device_handle_t hid_dev_handle);
/**
* @brief HID Host USB event handler
*
* If HID Host install was made with create_background_task=false configuration,
* application needs to handle USB Host events itself.
* Do not used if HID host install was made with create_background_task=true configuration
*
* @param[in] timeout Timeout in ticks. For milliseconds, please use 'pdMS_TO_TICKS()' macros
* @return esp_err_t
*/
esp_err_t hid_host_handle_events(uint32_t timeout);
/**
* @brief HID Device get parameters by handle.
*
* @param[in] hid_dev_handle HID Device handle
* @param[out] dev_params Pointer to a dev_params struct to fill
*
* @return esp_err_t
*/
esp_err_t hid_host_device_get_params(hid_host_device_handle_t hid_dev_handle,
hid_host_dev_params_t *dev_params);
/**
* @brief HID Host get device raw input report data pointer by handle
*
* This functions should be called after HID Interface device event HID_HOST_INTERFACE_EVENT_INPUT_REPORT
* to get the actual raw data of input report.
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] data Pointer to buffer where the input data will be copied
* @param[in] data_length_max Max length of data can be copied to data buffer
* @param[out] data_length Length of input report
*
* @return esp_err_t
*/
esp_err_t hid_host_device_get_raw_input_report_data(hid_host_device_handle_t hid_dev_handle,
uint8_t *data,
size_t data_length_max,
size_t *data_length);
// ------------------------ USB HID Host driver API ----------------------------
/**
* @brief HID Host start awaiting event from a device by handle
*
* Calls a callback when the HID Interface event has occurred.
*
* @param[in] hid_dev_handle HID Device handle
* @return esp_err_t
*/
esp_err_t hid_host_device_start(hid_host_device_handle_t hid_dev_handle);
/**
* @brief HID Host stop device
*
* @param[in] hid_dev_handle HID Device handle
*
* @return esp_err_t
*/
esp_err_t hid_host_device_stop(hid_host_device_handle_t hid_dev_handle);
/**
* @brief HID Host Get Report Descriptor
*
* @param[in] hid_dev_handle HID Device handle
* @param[out] report_desc_len Length of report descriptor
*
* @return a uint8_t pointer to report descriptor data
*/
uint8_t *hid_host_get_report_descriptor(hid_host_device_handle_t hid_dev_handle,
size_t *report_desc_len);
/**
* @brief HID Host Get device information
*
* @param[in] hid_dev_handle HID Device handle
*/
esp_err_t hid_host_get_device_info(hid_host_device_handle_t hid_dev_handle,
hid_host_dev_info_t *hid_dev_info);
/**
* @brief HID class specific request GET REPORT
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] report_id Report ID
* @param[out] report Pointer to buffer for a report data
* @param[in/out] report_length Report data length, before the get report contain the maximum value of a report buffer.
* After get report there is a value of actual data in report buffer.
*
* @return esp_err_t
*/
esp_err_t hid_class_request_get_report(hid_host_device_handle_t hid_dev_handle,
uint8_t report_type,
uint8_t report_id,
uint8_t *report,
size_t *report_length);
/**
* @brief HID class specific request GET IDLE
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] report_id ReportID
* @param[out] idle_rate Idle rate [ms]
*
* @return esp_err_t
*/
esp_err_t hid_class_request_get_idle(hid_host_device_handle_t hid_dev_handle,
uint8_t report_id,
uint8_t *idle_rate);
/**
* @brief HID class specific request GET PROTOCOL
*
* @param[in] hid_dev_handle HID Device handle
* @param[out] protocol Pointer to HID report protocol (boot or report) of device
*
* @return esp_err_t
*/
esp_err_t hid_class_request_get_protocol(hid_host_device_handle_t hid_dev_handle,
hid_report_protocol_t *protocol);
/**
* @brief HID class specific request SET REPORT
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] report_type Report type
* @param[in] report_id Report ID
* @param[in] report Pointer to a buffer with report data
* @param[in] report_length Report data length
*
* @return esp_err_t
*/
esp_err_t hid_class_request_set_report(hid_host_device_handle_t hid_dev_handle,
uint8_t report_type,
uint8_t report_id,
uint8_t *report,
size_t report_length);
/**
* @brief HID class specific request SET IDLE
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] duration 0 (zero) for the indefinite duration, non-zero, then a fixed duration used.
* @param[in] report_id If 0 (zero) the idle rate applies to all input reports generated by the device, otherwise ReportID
* @return esp_err_t
*/
esp_err_t hid_class_request_set_idle(hid_host_device_handle_t hid_dev_handle,
uint8_t duration,
uint8_t report_id);
/**
* @brief HID class specific request SET PROTOCOL
*
* @param[in] hid_dev_handle HID Device handle
* @param[in] protocol HID report protocol (boot or report)
* @return esp_err_t
*/
esp_err_t hid_class_request_set_protocol(hid_host_device_handle_t hid_dev_handle,
hid_report_protocol_t protocol);
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,292 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
//------------------------------------------ HID usage keys ---------------------------------------------------------------
/**
* @brief HID Keys
*
*/
typedef enum {
HID_KEY_NO_PRESS = 0x00,
HID_KEY_ROLLOVER = 0x01,
HID_KEY_POST_FAIL = 0x02,
HID_KEY_ERROR_UNDEFINED = 0x03,
HID_KEY_A = 0x04,
HID_KEY_B = 0x05,
HID_KEY_C = 0x06,
HID_KEY_D = 0x07,
HID_KEY_E = 0x08,
HID_KEY_F = 0x09,
HID_KEY_G = 0x0A,
HID_KEY_H = 0x0B,
HID_KEY_I = 0x0C,
HID_KEY_J = 0x0D,
HID_KEY_K = 0x0E,
HID_KEY_L = 0x0F,
HID_KEY_M = 0x10,
HID_KEY_N = 0x11,
HID_KEY_O = 0x12,
HID_KEY_P = 0x13,
HID_KEY_Q = 0x14,
HID_KEY_R = 0x15,
HID_KEY_S = 0x16,
HID_KEY_T = 0x17,
HID_KEY_U = 0x18,
HID_KEY_V = 0x19,
HID_KEY_W = 0x1A,
HID_KEY_X = 0x1B,
HID_KEY_Y = 0x1C,
HID_KEY_Z = 0x1D,
HID_KEY_1 = 0x1E,
HID_KEY_2 = 0x1F,
HID_KEY_3 = 0x20,
HID_KEY_4 = 0x21,
HID_KEY_5 = 0x22,
HID_KEY_6 = 0x23,
HID_KEY_7 = 0x24,
HID_KEY_8 = 0x25,
HID_KEY_9 = 0x26,
HID_KEY_0 = 0x27,
HID_KEY_ENTER = 0x28,
HID_KEY_ESC = 0x29,
HID_KEY_DEL = 0x2A,
HID_KEY_TAB = 0x2B,
HID_KEY_SPACE = 0x2C,
HID_KEY_MINUS = 0x2D,
HID_KEY_EQUAL = 0x2E,
HID_KEY_OPEN_BRACKET = 0x2F,
HID_KEY_CLOSE_BRACKET = 0x30,
HID_KEY_BACK_SLASH = 0x31,
HID_KEY_SHARP = 0x32,
HID_KEY_COLON = 0x33,
HID_KEY_QUOTE = 0x34,
HID_KEY_TILDE = 0x35,
HID_KEY_LESS = 0x36,
HID_KEY_GREATER = 0x37,
HID_KEY_SLASH = 0x38,
HID_KEY_CAPS_LOCK = 0x39,
HID_KEY_F1 = 0x3A,
HID_KEY_F2 = 0x3B,
HID_KEY_F3 = 0x3C,
HID_KEY_F4 = 0x3D,
HID_KEY_F5 = 0x3E,
HID_KEY_F6 = 0x3F,
HID_KEY_F7 = 0x40,
HID_KEY_F8 = 0x41,
HID_KEY_F9 = 0x42,
HID_KEY_F10 = 0x43,
HID_KEY_F11 = 0x44,
HID_KEY_F12 = 0x45,
HID_KEY_PRINT_SCREEN = 0x46,
HID_KEY_SCROLL_LOCK = 0x47,
HID_KEY_PAUSE = 0x48,
HID_KEY_INSERT = 0x49,
HID_KEY_HOME = 0x4A,
HID_KEY_PAGEUP = 0x4B,
HID_KEY_DELETE = 0x4C,
HID_KEY_END = 0x4D,
HID_KEY_PAGEDOWN = 0x4E,
HID_KEY_RIGHT = 0x4F,
HID_KEY_LEFT = 0x50,
HID_KEY_DOWN = 0x51,
HID_KEY_UP = 0x52,
HID_KEY_NUM_LOCK = 0x53,
HID_KEY_KEYPAD_DIV = 0x54,
HID_KEY_KEYPAD_MUL = 0x55,
HID_KEY_KEYPAD_SUB = 0x56,
HID_KEY_KEYPAD_ADD = 0x57,
HID_KEY_KEYPAD_ENTER = 0x58,
HID_KEY_KEYPAD_1 = 0x59,
HID_KEY_KEYPAD_2 = 0x5A,
HID_KEY_KEYPAD_3 = 0x5B,
HID_KEY_KEYPAD_4 = 0x5C,
HID_KEY_KEYPAD_5 = 0x5D,
HID_KEY_KEYPAD_6 = 0x5E,
HID_KEY_KEYPAD_7 = 0x5F,
HID_KEY_KEYPAD_8 = 0x60,
HID_KEY_KEYPAD_9 = 0x61,
HID_KEY_KEYPAD_0 = 0x62,
HID_KEY_KEYPAD_DELETE = 0x63,
HID_KEY_KEYPAD_SLASH = 0x64,
HID_KEY_APPLICATION = 0x65,
HID_KEY_POWER = 0x66,
HID_KEY_KEYPAD_EQUAL = 0x67,
HID_KEY_F13 = 0x68,
HID_KEY_F14 = 0x69,
HID_KEY_F15 = 0x6A,
HID_KEY_F16 = 0x6B,
HID_KEY_F17 = 0x6C,
HID_KEY_F18 = 0x6D,
HID_KEY_F19 = 0x6E,
HID_KEY_F20 = 0x6F,
HID_KEY_F21 = 0x70,
HID_KEY_F22 = 0x71,
HID_KEY_F23 = 0x72,
HID_KEY_F24 = 0x73,
HID_KEY_EXECUTE = 0x74,
HID_KEY_HELP = 0x75,
HID_KEY_MENU = 0x76,
HID_KEY_SELECT = 0x77,
HID_KEY_STOP = 0x78,
HID_KEY_AGAIN = 0x79,
HID_KEY_UNDO = 0x7A,
HID_KEY_CUT = 0x7B,
HID_KEY_COPY = 0x7C,
HID_KEY_PASTE = 0x7D,
HID_KEY_FIND = 0x7E,
HID_KEY_MUTE = 0x7F,
HID_KEY_VOLUME_UP = 0x80,
HID_KEY_VOLUME_DOWN = 0x81,
HID_KEY_LOCKING_CAPS_LOCK = 0x82,
HID_KEY_LOCKING_NUM_LOCK = 0x83,
HID_KEY_LOCKING_SCROLL_LOCK = 0x84,
HID_KEY_KEYPAD_COMMA = 0x85,
HID_KEY_KEYPAD_EQUAL_SIGN = 0x86,
HID_KEY_INTERNATIONAL_1 = 0x87,
HID_KEY_INTERNATIONAL_2 = 0x88,
HID_KEY_INTERNATIONAL_3 = 0x89,
HID_KEY_INTERNATIONAL_4 = 0x8A,
HID_KEY_INTERNATIONAL_5 = 0x8B,
HID_KEY_INTERNATIONAL_6 = 0x8C,
HID_KEY_INTERNATIONAL_7 = 0x8D,
HID_KEY_INTERNATIONAL_8 = 0x8E,
HID_KEY_INTERNATIONAL_9 = 0x8F,
HID_KEY_LANG_1 = 0x90,
HID_KEY_LANG_2 = 0x91,
HID_KEY_LANG_3 = 0x92,
HID_KEY_LANG_4 = 0x93,
HID_KEY_LANG_5 = 0x94,
HID_KEY_LANG_6 = 0x95,
HID_KEY_LANG_7 = 0x96,
HID_KEY_LANG_8 = 0x97,
HID_KEY_LANG_9 = 0x98,
HID_KEY_ALTERNATE_ERASE = 0x99,
HID_KEY_SYSREQ = 0x9A,
HID_KEY_CANCEL = 0x9B,
HID_KEY_CLEAR = 0x9C,
HID_KEY_PRIOR = 0x9D,
HID_KEY_RETURN = 0x9E,
HID_KEY_SEPARATOR = 0x9F,
HID_KEY_OUT = 0xA0,
HID_KEY_OPER = 0xA1,
HID_KEY_CLEAR_AGAIN = 0xA2,
HID_KEY_CRSEL = 0xA3,
HID_KEY_EXSEL = 0xA4,
HID_KEY_KEYPAD_00 = 0xB0,
HID_KEY_KEYPAD_000 = 0xB1,
HID_KEY_THOUSANDS_SEPARATOR = 0xB2,
HID_KEY_DECIMAL_SEPARATOR = 0xB3,
HID_KEY_CURRENCY_UNIT = 0xB4,
HID_KEY_CURRENCY_SUB_UNIT = 0xB5,
HID_KEY_KEYPAD_OPEN_PARENTHESIS = 0xB6,
HID_KEY_KEYPAD_CLOSE_PARENTHESIS = 0xB7,
HID_KEY_KEYPAD_OPEN_BRACE = 0xB8,
HID_KEY_KEYPAD_CLOSE_BRACE = 0xB9,
HID_KEY_KEYPAD_TAB = 0xBA,
HID_KEY_KEYPAD_BACKSPACE = 0xBB,
HID_KEY_KEYPAD_A = 0xBC,
HID_KEY_KEYPAD_B = 0xBD,
HID_KEY_KEYPAD_C = 0xBE,
HID_KEY_KEYPAD_D = 0xBF,
HID_KEY_KEYPAD_E = 0xC0,
HID_KEY_KEYPAD_F = 0xC1,
HID_KEY_KEYPAD_XOR = 0xC2,
HID_KEY_KEYPAD_CARET = 0xC3,
HID_KEY_KEYPAD_PERCENT = 0xC4,
HID_KEY_KEYPAD_LESSER = 0xC5,
HID_KEY_KEYPAD_GREATER = 0xC6,
HID_KEY_KEYPAD_AND = 0xC7,
HID_KEY_KEYPAD_LOGICAL_AND = 0xC8,
HID_KEY_KEYPAD_OR = 0xC9,
HID_KEY_KEYPAD_LOGICAL_OR = 0xCA,
HID_KEY_KEYPAD_COLON = 0xCB,
HID_KEY_KEYPAD_SHARP = 0xCC,
HID_KEY_KEYPAD_SPACE = 0xCD,
HID_KEY_KEYPAD_AT = 0xCE,
HID_KEY_KEYPAD_BANG = 0xCF,
HID_KEY_KEYPAD_MEMORY_STORE = 0xD0,
HID_KEY_KEYPAD_MEMORY_RECALL = 0xD1,
HID_KEY_KEYPAD_MEMORY_CLEAD = 0xD2,
HID_KEY_KEYPAD_MEMORY_ADD = 0xD3,
HID_KEY_KEYPAD_MEMORY_SUBSTRACT = 0xD4,
HID_KEY_KEYPAD_MEMORY_MULTIPLY = 0xD5,
HID_KEY_KEYPAD_MEMORY_DIVIDE = 0xD6,
HID_KEY_KEYPAD_SIGN = 0xD7,
HID_KEY_KEYPAD_CLEAR = 0xD8,
HID_KEY_KEYPAD_CLEAR_ENTRY = 0xD9,
HID_KEY_KEYPAD_BINARY = 0xDA,
HID_KEY_KEYPAD_OCTAL = 0xDB,
HID_KEY_KEYPAD_DECIMAL = 0xDC,
HID_KEY_KEYPAD_HEXADECIMAL = 0xDD,
HID_KEY_LEFT_CONTROL = 0xE0,
HID_KEY_LEFT_SHIFT = 0xE1,
HID_KEY_LEFT_ALT = 0xE2,
HID_KEY_LEFT_GUI = 0xE3,
HID_KEY_RIGHT_CONTROL = 0xE0,
HID_KEY_RIGHT_SHIFT = 0xE1,
HID_KEY_RIGHT_ALT = 0xE2,
HID_KEY_RIGHT_GUI = 0xE3
} __attribute__((packed)) hid_key_t;
// Modifier bit mask
#define HID_LEFT_CONTROL (1 << 0)
#define HID_LEFT_SHIFT (1 << 1)
#define HID_LEFT_ALT (1 << 2)
#define HID_LEFT_GUI (1 << 3)
#define HID_RIGHT_CONTROL (1 << 4)
#define HID_RIGHT_SHIFT (1 << 5)
#define HID_RIGHT_ALT (1 << 6)
#define HID_RIGHT_GUI (1 << 7)
/**
* @brief HID Keyboard Key number for Boot Interface
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef enum {
HID_KEYBOARD_KEY_NUMBER0 = 0,
HID_KEYBOARD_KEY_NUMBER1,
HID_KEYBOARD_KEY_NUMBER2,
HID_KEYBOARD_KEY_NUMBER3,
HID_KEYBOARD_KEY_NUMBER4,
HID_KEYBOARD_KEY_NUMBER5,
HID_KEYBOARD_KEY_MAX,
} hid_keyboard_key_number_t;
/**
* @brief HID Keyboard Input Report for Boot Interfaces
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
union {
struct {
uint8_t left_ctr: 1;
uint8_t left_shift: 1;
uint8_t left_alt: 1;
uint8_t left_gui: 1;
uint8_t rigth_ctr: 1;
uint8_t right_shift: 1;
uint8_t right_alt: 1;
uint8_t right_gui: 1;
};
uint8_t val;
} modifier;
uint8_t reserved;
uint8_t key[HID_KEYBOARD_KEY_MAX];
} __attribute__((packed)) hid_keyboard_input_report_boot_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,36 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief HID Mouse Input Report for Boot Interfaces
*
* @see B.1, p.60 of Device Class Definition for Human Interface Devices (HID) Version 1.11
*/
typedef struct {
union {
struct {
uint8_t button1: 1;
uint8_t button2: 1;
uint8_t button3: 1;
uint8_t reserved: 5;
};
uint8_t val;
} buttons;
int8_t x_displacement;
int8_t y_displacement;
} __attribute__((packed)) hid_mouse_input_report_boot_t;
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,15 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(EXTRA_COMPONENT_DIRS
../../usb_host_hid
)
# Set the components to include the tests for.
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
list(APPEND EXTRA_COMPONENT_DIRS ../../../../../device/esp_tinyusb)
endif()
project(test_app_usb_host_hid)

View File

@@ -0,0 +1,4 @@
| Supported Targets | ESP32-S2 | ESP32-S3 |
| ----------------- | -------- | -------- |
# USB: HID Class test application

View File

@@ -0,0 +1,25 @@
include($ENV{IDF_PATH}/tools/cmake/version.cmake)
set (TINYUSB_LIB)
if("${IDF_VERSION_MAJOR}.${IDF_VERSION_MINOR}" VERSION_GREATER_EQUAL "5.0")
set(TINYUSB_LIB "esp_tinyusb")
else()
set(TINYUSB_LIB "tinyusb")
endif()
# TODO: once IDF_v4.4 is at the EOL support, use WHOLE_ARCHIVE
idf_component_register(SRC_DIRS .
INCLUDE_DIRS .
REQUIRES unity usb usb_host_hid ${TINYUSB_LIB})
# In order for the cases defined by `TEST_CASE` to be linked into the final elf,
# the component can be registered as WHOLE_ARCHIVE
# Due to the backward compatibility to IDFv4.4 (in which WHOLE_ARCHIVE is not implemented) we use following approach:
# Any non-static function test_app/main/*.c (apart from test_app_main.c) file is added as an undefined symbol
# because otherwise the linker will ignore test_app/main/*.c as it has no other files depending on any
# symbols in it.
# force-link test_hid_basic.c
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u test_hid_setup")
# force-link test_hid_err_handling.c
set_property(TARGET ${COMPONENT_LIB} APPEND PROPERTY INTERFACE_LINK_LIBRARIES "-u test_interface_callback_handler")

View File

@@ -0,0 +1,204 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdint.h>
#include "sdkconfig.h"
#include "tinyusb.h"
#include "class/hid/hid_device.h"
#include "esp_idf_version.h"
#include "hid_mock_device.h"
static tusb_iface_count_t tusb_iface_count = 0;
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
/************* TinyUSB descriptors ****************/
#define TUSB_DESC_TOTAL_LEN (TUD_CONFIG_DESC_LEN + CFG_TUD_HID * TUD_HID_DESC_LEN)
/**
* @brief HID report descriptor
*
* In this example we implement Keyboard + Mouse HID device,
* so we must define both report descriptors
*/
const uint8_t hid_report_descriptor[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD) ),
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE) )
};
const uint8_t hid_keyboard_report_descriptor[] = {
TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(HID_ITF_PROTOCOL_KEYBOARD) )
};
const uint8_t hid_mouse_report_descriptor[] = {
TUD_HID_REPORT_DESC_MOUSE(HID_REPORT_ID(HID_ITF_PROTOCOL_MOUSE) )
};
/**
* @brief String descriptor
*/
const char *hid_string_descriptor[5] = {
// array of pointer to string descriptors
(char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"TinyUSB", // 1: Manufacturer
"TinyUSB Device", // 2: Product
"123456", // 3: Serials, should use chip ID
"Example HID interface", // 4: HID
};
/**
* @brief Configuration descriptor
*
* This is a simple configuration descriptor that defines 1 configuration and 1 HID interface
*/
static const uint8_t hid_configuration_descriptor_one_iface[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_HID, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(0, 4, false, sizeof(hid_report_descriptor), 0x81, 16, 10),
TUD_HID_DESCRIPTOR(1, 4, false, sizeof(hid_report_descriptor), 0x82, 16, 10),
};
static const uint8_t hid_configuration_descriptor_two_ifaces[] = {
// Configuration number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1, CFG_TUD_HID, 0, TUSB_DESC_TOTAL_LEN, TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP, 100),
// Interface number, string index, boot protocol, report descriptor len, EP In address, size & polling interval
TUD_HID_DESCRIPTOR(0, 4, HID_ITF_PROTOCOL_KEYBOARD, sizeof(hid_keyboard_report_descriptor), 0x81, 16, 10),
TUD_HID_DESCRIPTOR(2, 4, HID_ITF_PROTOCOL_MOUSE, sizeof(hid_mouse_report_descriptor), 0x82, 16, 10),
};
static const uint8_t *hid_configuration_descriptor_list[TUSB_IFACE_COUNT_MAX] = {
hid_configuration_descriptor_one_iface,
hid_configuration_descriptor_two_ifaces
};
#endif // // esp idf >= v5.0.0
/********* TinyUSB HID callbacks ***************/
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
// Invoked when received GET HID REPORT DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
uint8_t const *tud_hid_descriptor_report_cb(uint8_t instance)
{
switch (tusb_iface_count) {
case TUSB_IFACE_COUNT_ONE:
return hid_report_descriptor;
case TUSB_IFACE_COUNT_TWO:
return (!!instance) ? hid_mouse_report_descriptor : hid_keyboard_report_descriptor;
default:
break;
}
return NULL;
}
#endif // esp idf >= v5.0.0
#if ((ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)) && (ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)))
#define HID_ITF_PROTOCOL_KEYBOARD HID_PROTOCOL_KEYBOARD
#define HID_ITF_PROTOCOL_MOUSE HID_PROTOCOL_MOUSE
#endif // 4.4.0 <= esp idf < v5.0.0
/**
* @brief Get Keyboard report
*
* Fill buffer with test Keyboard report data
*
* @param[in] buffer Pointer to a buffer for filling
* @return uint16_t Length of copied data to buffer
*/
static inline uint16_t get_keyboard_report(uint8_t *buffer)
{
hid_keyboard_report_t kb_report = {
0, // Keyboard modifier
0, // Reserved
{ HID_KEY_M, HID_KEY_N, HID_KEY_O, HID_KEY_P, HID_KEY_Q, HID_KEY_R }
};
memcpy(buffer, &kb_report, sizeof(kb_report));
return sizeof(kb_report);
}
/**
* @brief Get Mouse report
*
* Fill buffer with test Mouse report data
*
* @param[in] buffer Pointer to a buffer for filling
* @return uint16_t Length of copied data to buffer
*/
static inline uint16_t get_mouse_report(uint8_t *buffer)
{
hid_mouse_report_t mouse_report = {
MOUSE_BUTTON_LEFT | MOUSE_BUTTON_RIGHT, // buttons
-1, // x
127, // y
0, // wheel
0 // pan
};
memcpy(buffer, &mouse_report, sizeof(mouse_report));
return sizeof(mouse_report);
}
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen)
{
switch (report_id) {
case HID_ITF_PROTOCOL_KEYBOARD:
return get_keyboard_report(buffer);
case HID_ITF_PROTOCOL_MOUSE:
return get_mouse_report(buffer);
default:
printf("HID mock device, Unhandled ReportID %d\n", report_id);
break;
}
return 0;
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize)
{
}
/**
* @brief HID Mock device start
*
* @param[in] iface_count Interface count, when TUSB_IFACE_COUNT_ONE then there is two Interfaces, but equal (Protocol=None).
* when TUSB_IFACE_COUNT_TWO then HID device mocked with two independent Interfaces (Protocol=BootKeyboard, Protocol=BootMouse).
*/
void hid_mock_device(tusb_iface_count_t iface_count)
{
if (iface_count > TUSB_IFACE_COUNT_MAX) {
printf("UHID mock device, wrong iface_count paramteter (%d)\n",
iface_count);
return;
}
// Global Interfaces count value
tusb_iface_count = iface_count;
const tinyusb_config_t tusb_cfg = {
.external_phy = false,
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
.device_descriptor = NULL,
.string_descriptor = hid_string_descriptor,
.string_descriptor_count = sizeof(hid_string_descriptor) / sizeof(hid_string_descriptor[0]),
.configuration_descriptor = hid_configuration_descriptor_list[tusb_iface_count],
#endif // esp idf >= v5.0.0
};
ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg));
printf("HID mock device with %s has been started\n",
(TUSB_IFACE_COUNT_ONE == tusb_iface_count)
? "1xInterface (Protocol=None)"
: "2xInterfaces (Protocol=BootKeyboard, Protocol=BootMouse)");
}

View File

@@ -0,0 +1,13 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
typedef enum {
TUSB_IFACE_COUNT_ONE = 0x00,
TUSB_IFACE_COUNT_TWO = 0x01,
TUSB_IFACE_COUNT_MAX
} tusb_iface_count_t;
void hid_mock_device(tusb_iface_count_t iface_count);

View File

@@ -0,0 +1,57 @@
/*
* SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdio.h>
#include <string.h>
#include "unity.h"
#include "esp_heap_caps.h"
static size_t before_free_8bit;
static size_t before_free_32bit;
#define TEST_MEMORY_LEAK_THRESHOLD (-530)
static void check_leak(size_t before_free, size_t after_free, const char *type)
{
ssize_t delta = after_free - before_free;
printf("MALLOC_CAP_%s: Before %u bytes free, After %u bytes free (delta %d)\n", type, before_free, after_free, delta);
TEST_ASSERT_MESSAGE(delta >= TEST_MEMORY_LEAK_THRESHOLD, "memory leak");
}
void app_main(void)
{
// ____ ___ ___________________ __ __
// | | \/ _____/\______ \ _/ |_ ____ _______/ |_
// | | /\_____ \ | | _/ \ __\/ __ \ / ___/\ __\.
// | | / / \ | | \ | | \ ___/ \___ \ | |
// |______/ /_______ / |______ / |__| \___ >____ > |__|
// \/ \/ \/ \/
printf(" ____ ___ ___________________ __ __ \r\n");
printf("| | \\/ _____/\\______ \\ _/ |_ ____ _______/ |_ \r\n");
printf("| | /\\_____ \\ | | _/ \\ __\\/ __ \\ / ___/\\ __\\\r\n");
printf("| | / / \\ | | \\ | | \\ ___/ \\___ \\ | | \r\n");
printf("|______/ /_______ / |______ / |__| \\___ >____ > |__| \r\n");
printf(" \\/ \\/ \\/ \\/ \r\n");
UNITY_BEGIN();
unity_run_menu();
UNITY_END();
}
/* setUp runs before every test */
void setUp(void)
{
before_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
before_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
}
/* tearDown runs after every test */
void tearDown(void)
{
size_t after_free_8bit = heap_caps_get_free_size(MALLOC_CAP_8BIT);
size_t after_free_32bit = heap_caps_get_free_size(MALLOC_CAP_32BIT);
check_leak(before_free_8bit, after_free_8bit, "8BIT");
check_leak(before_free_32bit, after_free_32bit, "32BIT");
}

View File

@@ -0,0 +1,684 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/event_groups.h"
#include "freertos/queue.h"
#include "freertos/semphr.h"
#include "esp_private/usb_phy.h"
#include "usb/usb_host.h"
#include "usb/hid_host.h"
#include "usb/hid_usage_keyboard.h"
#include "usb/hid_usage_mouse.h"
#include "test_hid_basic.h"
#include "hid_mock_device.h"
// USB PHY for device discinnection emulation
static usb_phy_handle_t phy_hdl = NULL;
// Global variable to verify user arg passing through callbacks
static uint32_t user_arg_value = 0x8A53E0A4; // Just a constant renadom number
// Queue and task for possibility to interact with USB device
// IMPORTANT: Interaction is not possible within device/interface callback
static bool time_to_shutdown = false;
static bool time_to_stop_polling = false;
QueueHandle_t hid_host_test_event_queue;
TaskHandle_t hid_test_task_handle;
// Multiple tasks testing
static hid_host_device_handle_t global_hdl;
static int test_num_passed;
static const char *test_hid_sub_class_names[] = {
"NO_SUBCLASS",
"BOOT_INTERFACE",
};
static const char *test_hid_proto_names[] = {
"NONE",
"KEYBOARD",
"MOUSE"
};
typedef struct {
hid_host_device_handle_t hid_device_handle;
hid_host_driver_event_t event;
void *arg;
} hid_host_test_event_queue_t;
typedef enum {
HID_HOST_TEST_TOUCH_WAY_ASSERT = 0x00,
HID_HOST_TEST_TOUCH_WAY_SUDDEN_DISCONNECT = 0x01,
} hid_host_test_touch_way_t;
static void force_conn_state(bool connected, TickType_t delay_ticks)
{
TEST_ASSERT_NOT_NULL(phy_hdl);
if (delay_ticks > 0) {
//Delay of 0 ticks causes a yield. So skip if delay_ticks is 0.
vTaskDelay(delay_ticks);
}
ESP_ERROR_CHECK(usb_phy_action(phy_hdl, (connected) ? USB_PHY_ACTION_HOST_ALLOW_CONN : USB_PHY_ACTION_HOST_FORCE_DISCONN));
}
void hid_host_test_interface_callback(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg)
{
uint8_t data[64] = { 0 };
size_t data_length = 0;
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params));
TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost");
switch (event) {
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
printf("USB port %d, Interface num %d: ",
dev_params.addr,
dev_params.iface_num);
hid_host_device_get_raw_input_report_data(hid_device_handle,
data,
64,
&data_length);
for (int i = 0; i < data_length; i++) {
printf("%02x ", data[i]);
}
printf("\n");
break;
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
printf("USB port %d, iface num %d removed\n",
dev_params.addr,
dev_params.iface_num);
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_close(hid_device_handle) );
break;
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
printf("USB Host transfer error\n");
break;
default:
TEST_FAIL_MESSAGE("HID Interface unhandled event");
break;
}
}
void hid_host_test_callback(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params));
TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost");
switch (event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
printf("USB port %d, interface %d, '%s', '%s'\n",
dev_params.addr,
dev_params.iface_num,
test_hid_sub_class_names[dev_params.sub_class],
test_hid_proto_names[dev_params.proto]);
const hid_host_device_config_t dev_config = {
.callback = hid_host_test_interface_callback,
.callback_arg = (void *) &user_arg_value
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) );
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) );
break;
default:
TEST_FAIL_MESSAGE("HID Driver unhandled event");
break;
}
}
void hid_host_test_concurrent(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params));
TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost");
switch (event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
printf("USB port %d, interface %d, '%s', '%s'\n",
dev_params.addr,
dev_params.iface_num,
test_hid_sub_class_names[dev_params.sub_class],
test_hid_proto_names[dev_params.proto]);
const hid_host_device_config_t dev_config = {
.callback = hid_host_test_interface_callback,
.callback_arg = &user_arg_value
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) );
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) );
global_hdl = hid_device_handle;
break;
default:
TEST_FAIL_MESSAGE("HID Driver unhandled event");
break;
}
}
void hid_host_test_device_callback_to_queue(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
const hid_host_test_event_queue_t evt_queue = {
.hid_device_handle = hid_device_handle,
.event = event,
.arg = arg
};
xQueueSend(hid_host_test_event_queue, &evt_queue, 0);
}
void hid_host_test_requests_callback(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(hid_device_handle, &dev_params));
TEST_ASSERT_EQUAL_PTR_MESSAGE(&user_arg_value, arg, "User argument has lost");
uint8_t *test_buffer = NULL; // for report descriptor
unsigned int test_length = 0;
uint8_t tmp[10] = { 0 }; // for input report
size_t rep_len = 0;
switch (event) {
case HID_HOST_DRIVER_EVENT_CONNECTED:
printf("USB port %d, interface %d, '%s', '%s'\n",
dev_params.addr,
dev_params.iface_num,
test_hid_sub_class_names[dev_params.sub_class],
test_hid_proto_names[dev_params.proto]);
const hid_host_device_config_t dev_config = {
.callback = hid_host_test_interface_callback,
.callback_arg = &user_arg_value
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) );
// Class device requests
// hid_host_get_report_descriptor
test_buffer = hid_host_get_report_descriptor(hid_device_handle, &test_length);
TEST_ASSERT_NOT_NULL(test_buffer);
printf("HID Report descriptor length: %d\n", test_length);
// // HID Device info
hid_host_dev_info_t hid_dev_info;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_get_device_info(hid_device_handle,
&hid_dev_info) );
printf("\t VID: 0x%04X\n", hid_dev_info.VID);
printf("\t PID: 0x%04X\n", hid_dev_info.PID);
wprintf(L"\t iProduct: %S \n", hid_dev_info.iProduct);
wprintf(L"\t iManufacturer: %S \n", hid_dev_info.iManufacturer);
wprintf(L"\t iSerialNumber: %S \n", hid_dev_info.iSerialNumber);
if (dev_params.proto == HID_PROTOCOL_NONE) {
// If Protocol NONE, based on hid1_11.pdf, p.78, all other devices should support
rep_len = sizeof(tmp);
// For testing with ESP32 we used ReportID = 0x01 (Keyboard ReportID)
if (ESP_OK == hid_class_request_get_report(hid_device_handle,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len)) {
printf("HID Get Report, type %d, id %d, length: %d:\n",
HID_REPORT_TYPE_INPUT, 0, rep_len);
for (int i = 0; i < rep_len; i++) {
printf("%02X ", tmp[i]);
}
printf("\n");
}
rep_len = sizeof(tmp);
// For testing with ESP32 we used ReportID = 0x02 (Mouse ReportID)
if (ESP_OK == hid_class_request_get_report(hid_device_handle,
HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len)) {
printf("HID Get Report, type %d, id %d, length: %d:\n",
HID_REPORT_TYPE_INPUT, 0, rep_len);
for (int i = 0; i < rep_len; i++) {
printf("%02X ", tmp[i]);
}
printf("\n");
}
} else {
// hid_class_request_get_protocol
hid_report_protocol_t proto;
if (ESP_OK == hid_class_request_get_protocol(hid_device_handle, &proto)) {
printf("HID protocol: %d\n", proto);
}
if (dev_params.proto == HID_PROTOCOL_KEYBOARD) {
uint8_t idle_rate;
// hid_class_request_get_idle
if (ESP_OK == hid_class_request_get_idle(hid_device_handle,
0, &idle_rate)) {
printf("HID idle rate: %d\n", idle_rate);
}
// hid_class_request_set_idle
if (ESP_OK == hid_class_request_set_idle(hid_device_handle,
0, 0)) {
printf("HID idle rate set to 0\n");
}
// hid_class_request_get_report
rep_len = sizeof(tmp);
if (ESP_OK == hid_class_request_get_report(hid_device_handle,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len)) {
printf("HID get report type %d, id %d, length: %d\n",
HID_REPORT_TYPE_INPUT, 0x00, rep_len);
}
// hid_class_request_set_report
uint8_t rep[1] = { 0x00 };
if (ESP_OK == hid_class_request_set_report(hid_device_handle,
HID_REPORT_TYPE_OUTPUT, 0x01, rep, 1)) {
printf("HID set report type %d, id %d\n", HID_REPORT_TYPE_OUTPUT, 0x00);
}
}
if (dev_params.proto == HID_PROTOCOL_MOUSE) {
// hid_class_request_get_report
rep_len = sizeof(tmp);
if (ESP_OK == hid_class_request_get_report(hid_device_handle,
HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len)) {
printf("HID get report type %d, id %d, length: %d\n",
HID_REPORT_TYPE_INPUT, 0x00, rep_len);
}
}
// hid_class_request_set_protocol
if (ESP_OK == hid_class_request_set_protocol(hid_device_handle,
HID_REPORT_PROTOCOL_BOOT)) {
printf("HID protocol change to BOOT: %d\n", proto);
}
}
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_start(hid_device_handle) );
break;
default:
TEST_FAIL_MESSAGE("HID Driver unhandled event");
break;
}
}
void hid_host_test_task(void *pvParameters)
{
hid_host_test_event_queue_t evt_queue;
// Create queue
hid_host_test_event_queue = xQueueCreate(10, sizeof(hid_host_test_event_queue_t));
// Wait queue
while (!time_to_shutdown) {
if (xQueueReceive(hid_host_test_event_queue, &evt_queue, pdMS_TO_TICKS(50))) {
hid_host_test_requests_callback(evt_queue.hid_device_handle,
evt_queue.event,
evt_queue.arg);
}
}
xQueueReset(hid_host_test_event_queue);
vQueueDelete(hid_host_test_event_queue);
vTaskDelete(NULL);
}
void hid_host_test_polling_task(void *pvParameters)
{
// Wait queue
while (!time_to_stop_polling) {
hid_host_handle_events(portMAX_DELAY);
}
vTaskDelete(NULL);
}
static void test_hid_host_device_touch(hid_host_dev_params_t *dev_params,
hid_host_test_touch_way_t touch_way)
{
uint8_t tmp[10] = { 0 }; // for input report
size_t rep_len = 0;
hid_report_protocol_t proto;
if (dev_params->proto == HID_PROTOCOL_NONE) {
rep_len = sizeof(tmp);
// For testing with ESP32 we used ReportID = 0x01 (Keyboard ReportID)
if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) {
TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len));
} else {
hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len);
}
} else {
// Get Protocol
TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_protocol(global_hdl, &proto));
// Get Report for Keyboard protocol, ReportID = 0x00 (Boot Keyboard ReportID)
if (dev_params->proto == HID_PROTOCOL_KEYBOARD) {
rep_len = sizeof(tmp);
if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) {
TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len));
} else {
hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x01, tmp, &rep_len);
}
}
if (dev_params->proto == HID_PROTOCOL_MOUSE) {
rep_len = sizeof(tmp);
if (HID_HOST_TEST_TOUCH_WAY_ASSERT == touch_way) {
TEST_ASSERT_EQUAL(ESP_OK, hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len));
} else {
hid_class_request_get_report(global_hdl,
HID_REPORT_TYPE_INPUT, 0x02, tmp, &rep_len);
}
}
}
}
#define MULTIPLE_TASKS_TASKS_NUM 10
void concurrent_task(void *arg)
{
uint8_t *test_buffer = NULL;
unsigned int test_length = 0;
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(global_hdl, &dev_params));
// Get Report descriptor
test_buffer = hid_host_get_report_descriptor(global_hdl, &test_length);
TEST_ASSERT_NOT_NULL(test_buffer);
test_hid_host_device_touch(&dev_params, HID_HOST_TEST_TOUCH_WAY_ASSERT);
test_num_passed++;
vTaskDelete(NULL);
}
void access_task(void *arg)
{
uint8_t *test_buffer = NULL;
unsigned int test_length = 0;
hid_host_dev_params_t dev_params;
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_get_params(global_hdl, &dev_params));
// Get Report descriptor
test_buffer = hid_host_get_report_descriptor(global_hdl, &test_length);
TEST_ASSERT_NOT_NULL(test_buffer);
while (!time_to_shutdown) {
test_hid_host_device_touch(&dev_params, HID_HOST_TEST_TOUCH_WAY_ASSERT/* HID_HOST_TEST_TOUCH_WAY_SUDDEN_DISCONNECT */);
}
vTaskDelete(NULL);
}
/**
* @brief Creates MULTIPLE_TASKS_TASKS_NUM to get report descriptor and get protocol from HID device.
* After test_num_passed - is a global static variable that increases during every successful task test.
*/
void test_multiple_tasks_access(void)
{
// Create tasks that will try to access HID dev with global hdl
for (int i = 0; i < MULTIPLE_TASKS_TASKS_NUM; i++) {
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(concurrent_task, "HID multi touch", 4096, NULL, i + 3, NULL));
}
// Wait until all tasks finish
vTaskDelay(pdMS_TO_TICKS(500));
}
void test_task_access(void)
{
// Create task which will be touching the device with control requests, while device is present
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(access_task, "HID touch", 4096, NULL, 3, NULL));
}
/**
* @brief Start USB Host and handle common USB host library events while devices/clients are present
*
* @param[in] arg Main task handle
*/
static void usb_lib_task(void *arg)
{
// Initialize the internal USB PHY to connect to the USB OTG peripheral.
// We manually install the USB PHY for testing
usb_phy_config_t phy_config = {
.controller = USB_PHY_CTRL_OTG,
.target = USB_PHY_TARGET_INT,
.otg_mode = USB_OTG_MODE_HOST,
.otg_speed = USB_PHY_SPEED_UNDEFINED, //In Host mode, the speed is determined by the connected device
};
TEST_ASSERT_EQUAL(ESP_OK, usb_new_phy(&phy_config, &phy_hdl));
const usb_host_config_t host_config = {
.skip_phy_setup = true,
.intr_flags = ESP_INTR_FLAG_LEVEL1,
};
TEST_ASSERT_EQUAL(ESP_OK, usb_host_install(&host_config) );
printf("USB Host installed\n");
xTaskNotifyGive(arg);
bool all_clients_gone = false;
bool all_dev_free = false;
while (!all_clients_gone || !all_dev_free) {
uint32_t event_flags;
usb_host_lib_handle_events(portMAX_DELAY, &event_flags);
// Release devices once all clients has deregistered
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_NO_CLIENTS) {
usb_host_device_free_all();
printf("USB Event flags: NO_CLIENTS\n");
all_clients_gone = true;
}
// All devices were removed
if (event_flags & USB_HOST_LIB_EVENT_FLAGS_ALL_FREE) {
printf("USB Event flags: ALL_FREE\n");
all_dev_free = true;
time_to_stop_polling = true;
// Notify that device was being disconnected
xTaskNotifyGive(arg);
}
}
// Change global flag for all tasks still running
time_to_shutdown = true;
// Clean up USB Host
vTaskDelay(10); // Short delay to allow clients clean-up
TEST_ASSERT_EQUAL(ESP_OK, usb_host_uninstall());
TEST_ASSERT_EQUAL(ESP_OK, usb_del_phy(phy_hdl)); //Tear down USB PHY
phy_hdl = NULL;
vTaskDelete(NULL);
}
// ----------------------- Public -------------------------
/**
* @brief Setups HID testing
*
* - Create USB lib task
* - Install HID Host driver
*/
void test_hid_setup(hid_host_driver_event_cb_t device_callback,
hid_test_event_handle_t hid_test_event_handle)
{
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreatePinnedToCore(usb_lib_task,
"usb_events",
4096,
xTaskGetCurrentTaskHandle(),
2, NULL, 0));
// Wait for notification from usb_lib_task
ulTaskNotifyTake(false, 1000);
// HID host driver config
const hid_host_driver_config_t hid_host_driver_config = {
.create_background_task = (hid_test_event_handle == HID_TEST_EVENT_HANDLE_IN_DRIVER)
? true
: false,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = device_callback,
.callback_arg = (void *) &user_arg_value
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_install(&hid_host_driver_config) );
}
/**
* @brief Teardowns HID testing
* - Disconnect connected USB device manually by PHY triggering
* - Wait for USB lib task was closed
* - Uninstall HID Host driver
* - Clear the notification value to 0
* - Short delay to allow task to be cleaned up
*/
void test_hid_teardown(void)
{
force_conn_state(false, pdMS_TO_TICKS(1000));
vTaskDelay(50);
TEST_ASSERT_EQUAL(ESP_OK, hid_host_uninstall() );
ulTaskNotifyValueClear(NULL, 1);
vTaskDelay(20);
}
// ------------------------- HID Test ------------------------------------------
static void test_setup_hid_task(void)
{
// Task is working until the devices are gone
time_to_shutdown = false;
// Create process
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(&hid_host_test_task,
"hid_task",
4 * 1024,
NULL,
3,
&hid_test_task_handle));
}
static void test_setup_hid_polling_task(void)
{
time_to_stop_polling = false;
TEST_ASSERT_EQUAL(pdTRUE, xTaskCreate(&hid_host_test_polling_task,
"hid_task_polling",
4 * 1024,
NULL, 2, NULL));
}
TEST_CASE("memory_leakage", "[hid_host]")
{
// Install USB and HID driver with the regular 'hid_host_test_callback'
test_hid_setup(hid_host_test_callback, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Tear down test
test_hid_teardown();
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("multiple_task_access", "[hid_host]")
{
// Install USB and HID driver with 'hid_host_test_concurrent'
test_hid_setup(hid_host_test_concurrent, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Wait for USB device appearing for 250 msec
vTaskDelay(250);
// Refresh the num passed test value
test_num_passed = 0;
// Start multiple task access to USB device with control requests
test_multiple_tasks_access();
// Tear down test
test_hid_teardown();
// Verify how much tests was done
TEST_ASSERT_EQUAL(MULTIPLE_TASKS_TASKS_NUM, test_num_passed);
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("class_specific_requests", "[hid_host]")
{
// Create external HID events task
test_setup_hid_task();
// Install USB and HID driver with 'hid_host_test_device_callback_to_queue'
test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// All specific control requests will be verified during device connection callback 'hid_host_test_requests_callback'
// Wait for test completed for 250 ms
vTaskDelay(250);
// Tear down test
test_hid_teardown();
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("class_specific_requests_with_external_polling", "[hid_host]")
{
// Create external HID events task
test_setup_hid_task();
// Install USB and HID driver with 'hid_host_test_device_callback_to_queue'
test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_EXTERNAL);
// Create HID Driver events polling task
test_setup_hid_polling_task();
// All specific control requests will be verified during device connection callback 'hid_host_test_requests_callback'
// Wait for test completed for 250 ms
vTaskDelay(250);
// Tear down test
test_hid_teardown();
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("class_specific_requests_with_external_polling_without_polling", "[hid_host]")
{
// Create external HID events task
test_setup_hid_task();
// Install USB and HID driver with 'hid_host_test_device_callback_to_queue'
test_hid_setup(hid_host_test_device_callback_to_queue, HID_TEST_EVENT_HANDLE_EXTERNAL);
// Do not create HID Driver events polling task to eliminate events polling
// ...
// Wait for 250 ms
vTaskDelay(250);
// Tear down test
test_hid_teardown();
// Verify the memory leackage during test environment tearDown()
}
TEST_CASE("sudden_disconnect", "[hid_host]")
{
// Install USB and HID driver with 'hid_host_test_concurrent'
test_hid_setup(hid_host_test_concurrent, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Wait for USB device appearing for 250 msec
vTaskDelay(250);
// Start task to access USB device with control requests
test_task_access();
// Tear down test during thr task_access stress the HID device
test_hid_teardown();
}
TEST_CASE("mock_hid_device", "[hid_device][ignore]")
{
hid_mock_device(TUSB_IFACE_COUNT_ONE);
while (1) {
vTaskDelay(10);
}
}
TEST_CASE("mock_hid_device_with_two_ifaces", "[hid_device2][ignore]")
{
hid_mock_device(TUSB_IFACE_COUNT_TWO);
while (1) {
vTaskDelay(10);
}
}

View File

@@ -0,0 +1,29 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#pragma once
#include "usb/hid_host.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef enum {
HID_TEST_EVENT_HANDLE_IN_DRIVER = 0,
HID_TEST_EVENT_HANDLE_EXTERNAL
} hid_test_event_handle_t;
// ------------------------ HID Test -------------------------------------------
void test_hid_setup(hid_host_driver_event_cb_t device_callback,
hid_test_event_handle_t hid_test_event_handle);
void test_hid_teardown(void);
#ifdef __cplusplus
}
#endif //__cplusplus

View File

@@ -0,0 +1,213 @@
/*
* SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include "unity.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "usb/hid_host.h"
#include "test_hid_basic.h"
// ----------------------- Private -------------------------
/**
* @brief USB HID Host interface callback.
*
* Handle close event only.
*
* @param[in] event HID Host device event
* @param[in] arg Pointer to arguments, does not used
*
*/
static void test_hid_host_interface_event_close(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg)
{
switch (event) {
case HID_HOST_INTERFACE_EVENT_INPUT_REPORT:
case HID_HOST_INTERFACE_EVENT_TRANSFER_ERROR:
break;
case HID_HOST_INTERFACE_EVENT_DISCONNECTED:
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_close(hid_device_handle) );
break;
}
}
/**
* @brief USB HID Host event callback stub.
*
* Does not handle anything.
*
* @param[in] event HID Host device event
* @param[in] arg Pointer to arguments, does not used
*
*/
static void test_hid_host_event_callback_stub(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
if (event == HID_HOST_DRIVER_EVENT_CONNECTED) {
// Device connected
}
}
/**
* @brief USB HID Host event callback.
*
* Handle connected event and open a device.
*
* @param[in] event HID Host device event
* @param[in] arg Pointer to arguments, does not used
*
*/
static void test_hid_host_event_callback_open(hid_host_device_handle_t hid_device_handle,
const hid_host_driver_event_t event,
void *arg)
{
if (event == HID_HOST_DRIVER_EVENT_CONNECTED) {
const hid_host_device_config_t dev_config = {
.callback = test_hid_host_interface_event_close,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_OK, hid_host_device_open(hid_device_handle, &dev_config) );
}
}
// Install HID driver without USB Host and without configuration
static void test_install_hid_driver_without_config(void)
{
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(NULL));
}
// Install HID driver without USB Host and with configuration
static void test_install_hid_driver_with_wrong_config(void)
{
const hid_host_driver_config_t hid_host_config_callback_null = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = NULL, /* error expected */
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_callback_null));
const hid_host_driver_config_t hid_host_config_stack_size_null = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 0, /* error expected */
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_stack_size_null));
const hid_host_driver_config_t hid_host_config_task_priority_null = {
.create_background_task = true,
.task_priority = 0,/* error expected */
.stack_size = 4096,
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_ARG, hid_host_install(&hid_host_config_task_priority_null));
const hid_host_driver_config_t hid_host_config_correct = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
// Invalid state without USB Host installed
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_install(&hid_host_config_correct));
}
void test_interface_callback_handler(hid_host_device_handle_t hid_device_handle,
const hid_host_interface_event_t event,
void *arg)
{
// ...
}
// Open device without installed driver
static void test_claim_interface_without_driver(void)
{
hid_host_device_handle_t hid_dev_handle = NULL;
const hid_host_device_config_t dev_config = {
.callback = test_interface_callback_handler,
.callback_arg = NULL
};
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE,
hid_host_device_open(hid_dev_handle, &dev_config) );
}
static void test_install_hid_driver_when_already_installed(void)
{
// Install USB and HID driver with the stub test_hid_host_event_callback_stub
test_hid_setup(test_hid_host_event_callback_stub, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Try to install HID driver again
const hid_host_driver_config_t hid_host_config = {
.create_background_task = true,
.task_priority = 5,
.stack_size = 4096,
.core_id = 0,
.callback = test_hid_host_event_callback_stub,
.callback_arg = NULL
};
// Verify error code
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_install(&hid_host_config));
// Tear down test
test_hid_teardown();
}
static void test_uninstall_hid_driver_while_device_was_not_opened(void)
{
// Install USB and HID driver with the stub test_hid_host_event_callback_stub
test_hid_setup(test_hid_host_event_callback_stub, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Tear down test
test_hid_teardown();
}
static void test_uninstall_hid_driver_while_device_is_present(void)
{
// Install USB and HID driver with the stub test_hid_host_event_callback_stub
test_hid_setup(test_hid_host_event_callback_open, HID_TEST_EVENT_HANDLE_IN_DRIVER);
// Wait for USB device appearing for 250 msec
vTaskDelay(250);
// Uninstall HID Driver while device is still connected and verify a result
printf("HID Driver uninstall attempt while HID Device is still present ...\n");
TEST_ASSERT_EQUAL(ESP_ERR_INVALID_STATE, hid_host_uninstall());
// Tear down test
test_hid_teardown();
}
// ----------------------- Public --------------------------
/**
* @brief HID Error handling test
*
* There are multiple erroneous scenarios checked in this test.
*
*/
TEST_CASE("error_handling", "[hid_host]")
{
test_install_hid_driver_without_config();
test_install_hid_driver_with_wrong_config();
test_claim_interface_without_driver();
test_install_hid_driver_when_already_installed();
test_uninstall_hid_driver_while_device_was_not_opened();
test_uninstall_hid_driver_while_device_is_present();
}

View File

@@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
# SPDX-License-Identifier: Apache-2.0
from typing import Tuple
import pytest
from pytest_embedded_idf.dut import IdfDut
@pytest.mark.esp32s2
@pytest.mark.esp32s3
@pytest.mark.usb_host
@pytest.mark.parametrize('count', [
2,
], indirect=True)
def test_usb_host_hid(dut: Tuple[IdfDut, IdfDut]) -> None:
device = dut[0]
host = dut[1]
# 3.1 Prepare USB device with one Interface for HID tests
device.expect_exact('Press ENTER to see the list of tests.')
device.write('[hid_device]')
device.expect_exact('HID mock device with 1xInterface (Protocol=None) has been started')
# 3.2 Run HID tests
host.run_all_single_board_cases(group='hid_host')
# 3.3 Prepare USB device with two Interfaces for HID tests
device.serial.hard_reset()
device.expect_exact('Press ENTER to see the list of tests.')
device.write('[hid_device2]')
device.expect_exact('HID mock device with 2xInterfaces (Protocol=BootKeyboard, Protocol=BootMouse) has been started')
# 3.4 Run HID tests
host.run_all_single_board_cases(group='hid_host')

View File

@@ -0,0 +1,19 @@
# Configure TinyUSB, it will be used to mock USB devices
CONFIG_TINYUSB=y
CONFIG_TINYUSB_MSC_ENABLED=n
CONFIG_TINYUSB_CDC_ENABLED=n
CONFIG_TINYUSB_CDC_COUNT=0
CONFIG_TINYUSB_HID_COUNT=2
# Disable watchdogs, they'd get triggered during unity interactive menu
CONFIG_ESP_INT_WDT=n
CONFIG_ESP_TASK_WDT=n
# Run-time checks of Heap and Stack
CONFIG_HEAP_POISONING_COMPREHENSIVE=y
CONFIG_COMPILER_STACK_CHECK_MODE_STRONG=y
CONFIG_COMPILER_STACK_CHECK=y
CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL=y
CONFIG_COMPILER_CXX_EXCEPTIONS=y