SPP расшифровывается как Serial Port Protocol, в данном контексте это библиотека Espressif для ESP32, которая позволяет организовать поддержку последовательного порта по протоколу Classic Bluetooth. С помощью этой библиотеки можно создать устройство Bluetooth, которое после успешного соединения (pairing) на компьютере будет представляться как COM-порт.
Примечание: в этой статье приведен перевод главы, посвященной SPP из документации [1]. Незнакомые термины и сокращения см. в Словарике, в конце статьи.
Сначала немного разберемся с терминологией. Классическое устройство Bluetooth может работать либо как инициирующее соединение устройство (обычно это смартфон или компьютер), тогда оно обозначается как INITIATOR, и является клиентом для сервера. Либо оно работает как принимающее соединение устройство (клавиатура, последовательный порт, аудиоустройство и т. д.), тогда оно обозначается как ACCEPTOR, прослушивает входящие соединения и является сервером. Модуль SPP может работать либо в режиме передачи данных ESP_SPP_MODE_CB, либо в режиме ESP_SPP_MODE_VFS, виртуальной файловой системы [3].
Пример см. в папке bluetooth/bluedroid/classic_bt среди примеров ESP-IDF [4], где содержится пример приложения SPP demo. Этот пример позволяет обнаружить службу (service) подключиться, отправить и принять данные SPP. Поставляется 2 основных примера:
Прием данных осуществляется в обработке события ESP_SPP_DATA_IND_EVT. Передача данных не демонстрируется, но её можно легко осуществить с помощью функции esp_spp_write и обработки событий ESP_SPP_WRITE_EVT и ESP_SPP_CONG_EVT.
Функции, которые возвращают значения типа esp_err_t, в случае успеха возвратят ESP_OK, иначе будет возвращен код ошибки. Базовые коды ошибок определены в заголовке esp_err.h, другие коды ошибок определяются в заголовочных файлах отдельных модулей библиотеки.
Эта функция вызывается для инициализации модуля SPP. Когда операция завершена, будет вызвана callback-функция с событием ESP_SPP_INIT_EVT. Эта функция должна быть вызвана после успешного завершения esp_bluedroid_enable().
Параметры
[in] mode: выбор режима SPP, ESP_SPP_MODE_CB или ESP_SPP_MODE_VFS.
Эта функция вызывается для отмены инициализации модуля SPP. Операция сначала закроет активное соединение SPP, затем будет вызвана callback-функция с событием ESP_SPP_CLOSE_EVT, и значение handle в параметре события будет равно дескриптору соединения. Когда операция завершена, будет вызвана callback-функция с событием ESP_SPP_UNINIT_EVT. Эта функция должна быть вызвана после успешного завершения esp_spp_init().
Эта функция вызывается для распознавания службы (service discovery) для служб, которые предоставлены имеющимся устройством пира. Когда операция завершена, будет вызвана callback-функция с событием ESP_SPP_DISCOVERY_COMP_EVT. Эта функция должна быть вызвана после успешного вызова esp_spp_init() и перед esp_spp_deinit().
Параметры
[in] bd_addr: адрес внешнего удаленного устройства bluetooth.
Эта функция выполняет соединение SPP с внешним удаленным устройством (пиром) по указанному адресу. Когда соединение инициировано или не получилось инициировать соединение, будет вызвана callback-функция с событием ESP_SPP_CL_INIT_EVT. Когда соединение было установлено или потерпело неудачу, будет вызвана callback-функция с событием ESP_SPP_OPEN_EVT. Эта функция должна быть вызвана после успешного esp_spp_init() successful и перед esp_spp_deinit().
Параметры
[in] sec_mask: Security Setting Mask. Рекомендуется использовать только ESP_SPP_SEC_NONE, ESP_SPP_SEC_AUTHORIZE или ESP_SPP_SEC_AUTHENTICATE. [in] role: master или slave. [in] remote_scn: SCN для удаленного внешнего устройства bluetooth. [in] peer_bd_addr: адрес для удаленного внешнего устройства bluetooth.
Эта функция закроет соединение SPP. Когда операция завершена, будет вызвана callback-функция с событием ESP_SPP_CLOSE_EVT. Эта функция должна быть вызвана после успеха esp_spp_init() и перед esp_spp_deinit().
Эта функция создает сервер SPP, и начинает прослушивание радиоканала на предмет поступления запроса соединения (SPP connection request) от удаленного устройства Bluetooth. Когда сервер успешно запустится, будет вызвана callback-функция с событием ESP_SPP_START_EVT. Когда установится соединение, будет вызвана callback-функция с событием ESP_SPP_SRV_OPEN_EVT. Эта функция должна быть вызвана после успеха esp_spp_init() и перед esp_spp_deinit().
Параметры
[in] sec_mask: Security Setting Mask. Рекомендуется использовать только ESP_SPP_SEC_NONE, ESP_SPP_SEC_AUTHORIZE или ESP_SPP_SEC_AUTHENTICATE. [in] role: master или slave. [in] local_scn: определенный канал, который ходите получить. Если указан канал 0, то это значит любой канал. [in] name: имя сервера.
Останавливает все серверы SPP. Операция закроет сначала все активные соединения SPP, затем будет вызвана callback-функция с событием ESP_SPP_CLOSE_EVT, параметр handle будет соответствовать дескриптору соединения (если соединений было несколько, то callback-функция будет вызвана соответствующее количество раз). Когда операция завершится, будет вызвана callback-функция с событием ESP_SPP_SRV_STOP_EVT. Эта функция должна быть вызвана после успеха esp_spp_init() и перед esp_spp_deinit().
Остановит определенный сервер SPP. Операция закроет сначала все активные соединения SPP, затем будет вызвана callback-функция с событием ESP_SPP_CLOSE_EVT, параметр handle будет соответствовать дескриптору соединений. Когда операция завершится, будет вызвана callback-функция с событием ESP_SPP_SRV_STOP_EVT. Эта функция должна быть вызвана после успеха esp_spp_init() и перед esp_spp_deinit().
Параметры
[in] scn: номер канала сервера (server channel number).
esp_err_tesp_spp_write (uint32_t handle, int len, uint8_t*p_data);
Записывает данные, только для режима ESP_SPP_MODE_CB. Когда эту функцию нужно вызывать несколько раз, строго рекомендуется делать повторные вызовы после получения предыдущего события ESP_SPP_WRITE_EVT с параметром cong равным false (что означает отсутствие перегрузки канала данными). Если предыдущее событие ESP_SPP_WRITE_EVT было с параметром cong равным true (означает перегрузку канала данными), то функция может быть вызвана повторно, когда принято событие ESP_SPP_CONG_EVT с параметром cong равным false. Эта функция должна вызываться после установки соединения между initiator и acceptor.
Параметры
[in] handle: дескриптор соединения. [in] len: длина записываемых данных. [in] p_data: записываемые данные.
// Событие SPP_DISCOVERY_COMP_EVTstruct spp_discovery_comp_evt_param {
esp_spp_status_t status; /*!< статус */uint8_t scn_num; /*!< номер канала сервера */uint8_t scn[ESP_SPP_MAX_SCN]; /*!< channel # */constchar*service_name[ESP_SPP_MAX_SCN]; /*!< имя сервиса */
} disc_comp;
// Событие ESP_SPP_OPEN_EVTstruct spp_open_evt_param {
esp_spp_status_t status; /*!< статус */uint32_t handle; /*!< дескриптор соединения */int fd; /*!< дескриптор файла, только для ESP_SPP_MODE_VFS */esp_bd_addr_t rem_bda; /*!< адрес пира */
} open;
// Событие ESP_SPP_SRV_OPEN_EVTstruct spp_srv_open_evt_param {
esp_spp_status_t status; /*!< статус */uint32_t handle; /*!< дескриптор соединения */uint32_t new_listen_handle; /*!< новый дескриптор прослушивания */int fd; /*!< дескриптор файла, только для ESP_SPP_MODE_VFS */esp_bd_addr_t rem_bda; /*!< адрес пира */
} srv_open;
// Событие ESP_SPP_CLOSE_EVTstruct spp_close_evt_param {
esp_spp_status_t status; /*!< статус */uint32_t port_status; /*!< состояние порта PORT */uint32_t handle; /*!< дескриптор соединения */bool async; /*!< FALSE, если отключение инициировано локально */
} close;
// Событие ESP_SPP_START_EVTstruct spp_start_evt_param {
esp_spp_status_t status; /*!< статус */uint32_t handle; /*!< дескриптор соединения */uint8_t sec_id; /*!< security ID, используемый этим сервером */uint8_t scn; /*!< номер канала сервера */bool use_co; /*!< TRUE для использования co_rfc_data */
} start;
// Событие ESP_SPP_SRV_STOP_EVTstruct spp_srv_stop_evt_param {
esp_spp_status_t status; /*!< статус */uint8_t scn; /*!< номер канала сервера */
} srv_stop;
// Событие ESP_SPP_CL_INIT_EVTstruct spp_cl_init_evt_param {
esp_spp_status_t status; /*!< статус */uint32_t handle; /*!< дескриптор соединения */uint8_t sec_id; /*!< security ID, используемый этим сервером */bool use_co; /*!< TRUE для использования co_rfc_data */
} cl_init;
// Событие ESP_SPP_WRITE_EVTstruct spp_write_evt_param {
esp_spp_status_t status; /*!< статус */uint32_t handle; /*!< дескриптор соединения */int len; /*!< длина записанных данных */bool cong; /*!< статус перегруженности */
} write;
// Событие ESP_SPP_DATA_IND_EVTstruct spp_data_ind_evt_param {
esp_spp_status_t status; /*!< статус */uint32_t handle; /*!< дескриптор соединения */uint16_t len; /*!< длина данных */uint8_t*data; /*!< принятые данные */
} data_ind;
// Событие ESP_SPP_CONG_EVTstruct spp_cong_evt_param {
esp_spp_status_t status; /*!< статус */uint32_t handle; /*!< дескриптор соединения */bool cong; /*!< TRUE была перегрузка данными,
FALSE перегрузки не было */
} cong;
} esp_spp_cb_param_t;
[Макросы]
ESP_SPP_SEC_NONE Безопасность не используется. Соответствует BTA_SEC_NONE в bta/bta_api.h.
ESP_SPP_SEC_AUTHORIZE Требуется авторизация (только в процессе установки соединения). Соответствует BTA_SEC_AUTHORIZE в bta/bta_api.h
ESP_SPP_SEC_AUTHENTICATE Требуется аутентификация. Соответствует BTA_SEC_AUTHENTICATE в bta/bta_api.h.
ESP_SPP_SEC_ENCRYPT Требуется шифрование. Соответствует BTA_SEC_ENCRYPT в bta/bta_api.h.
ESP_SPP_SEC_MODE4_LEVEL4 Mode 4 level 4 service, например поддержка защиты incoming/outgoing MITM и шифрования P-256. Соответствует BTA_SEC_MODE4_LEVEL4 в bta/bta_api.h.
ESP_SPP_SEC_MITM Защита MITM, соответствует BTA_SEC_MITM в bta/bta_api.h.
ESP_SPP_SEC_IN_16_DIGITS Минимум 16 цифр для pin-кода. Соответствует BTA_SEC_IN_16_DIGITS в bta/bta_api.h.
Тип для callback-функции SPP. Когда обрабатывается событие ESP_SPP_DATA_IND_EVT, настоятельно рекомендуется кешировать приходящие данные, и обрабатывать их в задаче приложения с более низким приоритетом вместо того, чтобы обрабатывать их непосредственно в callback-функции.
Параметры
event: тип события. param: указывает на параметр для callback-функции. В настоящее время для этого используется объединение.
Это перечисление используется для обозначения событий callback-функции.
typedefenum {
ESP_SPP_INIT_EVT =0, /*!< При инициализации SPP. */
ESP_SPP_UNINIT_EVT =1, /*!< При отмене инициализации SPP. */
ESP_SPP_DISCOVERY_COMP_EVT =8, /*!< При завершении SDP. */
ESP_SPP_OPEN_EVT =26, /*!< Когда открыто соединение с клиентом SPP. */
ESP_SPP_CLOSE_EVT =27, /*!< Когда соединение SPP закрыто. */
ESP_SPP_START_EVT =28, /*!< Когда запустился сервер SPP. */
ESP_SPP_CL_INIT_EVT =29, /*!< Когда клиент SPP инициировал соединение. */
ESP_SPP_DATA_IND_EVT =30, /*!< Когда на соединение SPP поступили данные.
Только для режима ESP_SPP_MODE_CB. */
ESP_SPP_CONG_EVT =31, /*!< Когда поменялся статус перегруженности
данными соединения SPP.
Только для режима ESP_SPP_MODE_CB. */
ESP_SPP_WRITE_EVT =33, /*!< Когда завершилась операция записи SPP.
Только для режима ESP_SPP_MODE_CB. */
ESP_SPP_SRV_OPEN_EVT =34, /*!< Когда открыто соединение сервера SPP. */
ESP_SPP_SRV_STOP_EVT =35, /*!< Когда остановлен сервер SPP. */
} esp_spp_cb_event_t;
[Словарик]
acceptor устройство Bluietooth, ожидающее поступление запроса на соединение от устройства initiator.
BD Address BD_ADDR, Bluetooth Device Address.
BTA Bluetooth Application Layer.
BTE Bluetooth Embedded System.
initiator устройство Bluetooth, которое инициирует соединение с устройством acceptor.
LMP Link Manager Protocol, протокол управления соединением.
MITM Man-In-The_Middle, защита от атаки "человек посередине".
MTU Maximum Transfer Unit, максимально допустимый размер для передаваемого/принимаемого блока данных.
PDU Protocol Data Unit.
peer пир, противоположный участник соединения Bluetooth.
SCN Service Channel Number.
SDP Service Discovery Protocol, протокол распознавания поддерживаемой службы.