В программе клиента формируется событие когда придёт ответ сервера на запрос. Дождитесь его и потом посылайте следующий.
Имеется в виду событие sl_bt_evt_gatt_procedure_completed_id:? Хорошо, допустим, я получил информацию о завершении и послал следующий запрос, потом снова получил и снова послал. Т.е. весь characteristic discovery я могу делать в этом case. Но из какого события посылать первый запрос? Хотелось бы юниформности. Хотя мне еще надо над этим подумать, с учетом моего желания распихать обработчики сервисов по разным файлам, так, чтобы они без нужды не пересекались.
Еще изучал (немного) код из KBA_BT_0903: SPP-over-BLE example. И мне показалось, что там клиент узнаёт о сервисах сервера еще до соединения в событии gecko_evt_le_gap_scan_response_id:. Возможно ли это или я не понимаю? Хотя EFR Connect тоже показывает некоторые сервисы еще до соединения с сервером. Хотя, это позволит мне только отфильтровать устройства с требуемым UUID. Handle я могу получить только после соединения. Пока я фильтрую просто по "таблице моих устройств".
Еще есть непонятность по функции write_without_response:
Не смог понять чем value_len, отличается от sent_len. Я так понимаю, что в sent_len будет записано сколько из заданных value_len отослано. Но вот в какой момент это значение там оказывается? Оно там меняется сразу или там надо указатель на статическую переменную? А то я укажу динамическую, а она испарится, когда программа выйдет из функции, где я делаю этот вызов и потом кто-то что-то запишет не туда.
Прошивку донгла я брал из каталога CySmart v1.3 "C:\Program Files (x86)\Cypress\CySmart\1.3\dongle\CY5672\BLE_HID_CySmart_Dongle.hex" и оно мне зашило это: Спойлер
Сегодня и завтра у меня тяжёлые дни, подумаю над вопросами и напишу позже. Кое-что мне в памяти освежить нужно. Насчёт прошивки донгла, следует использовать PSOC Programmer, a саму прошивку самому скачать с сайта. Естественно, следует выбирать прошивку для Вашей модели донгла (их несколько). Если пытаться загрузить чужую прошивку, Programmer этого не допустит, так что ничего испортить не удастся.
В программе клиента первым делом следует установить соединение с сервером и получить connection handle. При соединении генерируется специальное событие и первый запрос на чтение сервисов/характеристик можно сделать оттуда. Вообще, есть очень полезный документ с примером чтения клиентом сервисов и характеристик сервера. Думаю, он ответит на все Ваши вопросы выше. Он, правда, под версию стека 2.х, но для современной версии стека изменения минимальны.
Цитата:
клиент узнаёт о сервисах сервера еще до соединения в событии gecko_evt_le_gap_scan_response_id:. Возможно ли это или я не понимаю?
Конечно, возможно. Для этого и существуют advertisements и scan response. Вы можете там сконфигурировать передаваемую информацию о поддерживаемых сервером сервисах (или их части). Как это сделать - см. серию статей в категории KBA_BT_020x. Они, правда, тоже под старый стек и частично переписаны под новый в docs.silabs.com, однако не всегда с прежней подробностью. Если хотите, там-же в полях можно передавать какие handles у требуемых характеристик в BT DB для ускорения процедуры их чтения клиентом. Точнее, при этом можно вообще без этой процедуры обойтись.
Цитата:
Пока я фильтрую просто по "таблице моих устройств".
Если делаете это для поиска своего сервера при сканировании, то можно , например, в scan response передавать уникальное поле для вашего сервера и искать устройства, содержащие это поле в scan response.
Цитата:
Не смог понять чем value_len, отличается от sent_len
value_len - это входной параметр API и он должен отражать реальную длину посылаемых данных. sent_len - выходной параметр (pointer на статичекую переменную). Поскольку подавляющее большинство BT API non-blocking, то значение переменной по ссылке будет установлено только по фактическому завершению передачи данных.
Спасибо за подсказки. Вроде, разобрался и даже удалось что-то сделать.
Применил машину состояний, которая работает в каждом модуле.
Код:
typedef enum { idle, discover_service, discover_charactristic, enable_notification, active } bt_state;
По коннекту в app.c посылаю просто поиск всех серсисов, а в "подчинённых" файлах меняю машину состяний на discover_service и по нахождению своих сервисов каждый модуль запоминает его хендл. А когда наступит событие, что операция завершена - каждый модуль попытается искать характеристики по UUID. Конечно, первому передавшему команду это удастся (и он изменит статус на discover_characteristic), а остальные получат ошибку gatt_operation_in_progress (или что-то в этом роде). Короче, им придётся ждать, пока первый модуль не выполнит свои операции и не успокоится. Тогда эти же процедуры сможет выполнить следующий модуль, затем следующий итд.
Вот, если кого интересует, идентификация последовательного порта модуля HM-11 (хотя достаточно поменять строчку uint8_t service_UUID[] = {0xe0, 0xff}, characteristic_UUID[] = {0xe1, 0xff} и искать любые другие характеристики): Спойлер
Код:
/* * serial.c * * Created on: 18 февр. 2021 г. * Author: wl */ #include "sl_bluetooth.h" #include "gatt_db.h" #include "app.h" #include "sl_app_assert.h" #include "sl_iostream_uart.h" #include "sl_iostream_init_usart_instances.h" #include "printf.h"
case sl_bt_evt_gatt_procedure_completed_id: gatt_result = evt->data.evt_gatt_procedure_completed.result; sl_app_assert(gatt_result == 0,"[E: 0x%04x] gatt_procedure_completed_id\r\n",(int)gatt_result); switch (serial_state){ case discover_service: if (service_discovered){ sc = sl_bt_gatt_discover_characteristics_by_uuid(connection_handler, service_handle, sizeof(characteristic_UUID), characteristic_UUID); if (sc == SL_STATUS_OK) serial_state = discover_charactristic; // sl_app_assert(, "[E: 0x%04x] gatt_discover_characteristics_by_uuid\r\n", (int)sc); } break;
case discover_charactristic: if (char_handle_defined){ serial_state = enable_notification; sl_bt_gatt_set_characteristic_notification(connection_handler, char_handle, sl_bt_gatt_notification); } break;
case enable_notification: serial_state = active; break;
default: break; } break;
// ---------- // This event indicates that a connection was closed. case sl_bt_evt_connection_closed_id: char_handle_defined=0; service_discovered=0; char_handle = 0xFFFF; serial_state = idle; break;
Похоже, Вы во всём разобральсь. Поздравляю! Кстати, недавно общался со своим контактом с фирмы - они признали и осознали, что при обработке внешних прерываний sleep manager-ом иногда происходит какая-то фигня (как у нас). Контакт оформил ticket в группу развития стека. Будем надеяться, что ситуация с этим улучшится в следующих релизах. Нет книг без опечаток как и программ без багов.
Тогда, хочу вернуться к вопросу о разделении процессов. В AN1255 я что-то не нашел... Вот допустим, я хочу сделать отдельный процесс для кнопок и в нём делаю функцию обрабатывающую события БТ стека: button_on_event(evt). Как сделать, чтобы эта функция вошла в цепочку вызовов?
Вот в проекте iBeacon там такая конструкция в sl_bluetooth.c :
именно этого ответа нет. Но есть обходной вариант. Как я писал, его можно вызывать из "своего" on_event. Я чуть выше приводил свой serial.c, в котором обрабатываются события и там я "выбираю и обрабатываю" только то, что интересно тому модулю. И этот вот serial_on_event тривиально вызываю из sl_bt_on_event, который находится в app.c. Вот фрагмент. Я попытался "перевести" на SDK3 код из статьи. Почти всему нашел эквивалентные функции, кроме одной (она закомментирована). Хотя и без неё нормально работает.
Вопрос. А кто должен вызывать функцию sl_bt_sm_increase_security клиент или сервер?
И еще вопрос... вот всюду сервисы, характеристики. А про дескрипторы что-то ничего не пишут. Конкретно меня интересует сервис Environmental и его характеристики, на вроде температуры. Вот они всегда передаются как 16 бит и с двумя десятичными знаками за запятой или это может зависеть от дескрипторов?
кто должен вызывать функцию sl_bt_sm_increase_security клиент или сервер?
Зависит от того, чего хотите добиться. Вызывать её могут один или оба девайса, но в большинстве случаев вызывать её вообще не требуется.
Цитата:
они всегда передаются как 16 бит и с двумя десятичными знаками за запятой или это может зависеть от дескрипторов?
В каком формате передаётся характеристика от дескрипторов вообще не зависит. Они лишь информируют клиента в каком формате она передаётся сервером, что в свою очередь определяется разработчиком. Однако, если приложениям клиента и сервера этот формат известен, соответствующий дескриптор вообще можно не использовать. Для SIG сервисов и характеристик/дескрипторов описание их имеется на сайте bluetooth.com. В частности, для Environmental Sensing Service, см. здесь.
В каком формате передаётся характеристика от дескрипторов вообще не зависит.
Ммм, умнее не стал... Вроде в дескрипторах ничего такого найти не могу. Только точность, периодичность и диапазон. Но... вот запись чата с предыдущего воркшопа:
Цитата:
19:00:44 From Alvin Schatte to Everyone : If I wanted to format temperature in deg F, I would modify that value where you showed, but would the deg C in the display have to be changed in the EFR Connect app? ... 19:03:39 From Mike Glazebrook to Everyone : @Alvin, the Unit Attribute of the characteristic would need to be modified as well. This will let the phone know that the data is in C or F
просто я делаю очередной термометр (клиент) и хочу заложиться на стандартый сервис. А вдруг, у меня будет стороннее устройсво (сервер) сообщающее температуру и я хочу, чтобы моё его правильно понимало. И вот тут я не понял, где находится этот "Unit Attribute".
Полагаю, там речь шла про EFR Connect. Насколько я помню, она использует проприетарный сервис для данных сенсоров, т.е. нужно знать как устроено приложение, чтобы заставить его через дескриптор показывать температуру по желаемой шкале. Именно, что передать в этом дескрипторе. Стандарта даже в характеристиках с 16-биным UUID нет, если не оговорено в документах на сайте SIG.
Ну вот опять... перенёс свой проект с отладочной платы на свою, компилирую и получаю толпу варнингов: 'sl_bt_system_set_soft_timer' is deprecated. На отладочной плате проект делал еще в SDK 3.1.2, а новый проект уже в 3.2.0... когда пару месяцев назад так же сделали с assert и log - там не страшно: просто поменялось название с sl_ на app_. А теперь предлагают пользоваться каким-то слиптаймером? А события? теперь весь отлаженный проект снова перелопачивать?
Да, это бич всех систем большой сложности - постоянные "усовершенствования" и "улучшения", рано или поздно делающие проекты несовместимыми. Насчёт soft таймера - я согласен, что это дублирование соответствующих API слип-таймера, так что я приветствую его деприкацию. К тому-же у меня с soft таймером были проблемы надёжности, особенно с задержками менее 1 сек, так что я даже отказался от его использования в пользу аппаратного LETIMER-a. Ну а для отлаженного проекта - всегда можно оставить предыдущую версию системы.
Ну я был не рад... так как это поломало мне всё, а первая попытка чтения документации на слип-таймер у меня вызвало недоумение. Читаю описание полей хендла таймера и тихо потухаю от информативности описаний полей: priority - Priority of timer; option_flags - Option flags; next - Pointer to next element in list.. что за список? Совершенно без объяснения. Только позже дошло, что мне эту структуру не нужно заполнять самому. Ну ладно, вроде работает.
А вот есть стратегический вопрос. Как лучше, с точки энергоэффективности сервера, который является датчиком температуры. Чтобы клиент по включении питания соединился нашел хендлы и держал соединение, и, скажем, раз в минуту опрашивал температуру или раз в минуту установить соединение, найти хендлы, считать температуру, разорвать соединение и через минуту всё по-новой?
И вдогонку... сейчас у меня реализован первый вариант, но я наблюдаю постоянную потерю соединения через неопределённый интервал. Может раз в минуту, может и 10 минут продержаться соединение. Просто появляется event sl_bt_evt_connection_closed_id: reason = 0x1008 - SL_STATUS_BT_CTRL_CONNECTION_TIMEOUT. И пока не могу придумать, кто в этом виноват.
Если речь идёт только о передаче температуры, оба решения некудышние. Держать соединение энергозатратно, т.к. максимальный интервал соединения 4 сек и при обращении к серверу с периодом 60 сек будет много пустых пакетов, на передачу и приём которых тратится энергия. Кроме того, велика вероятность разрыва соединения. Второе решение в этом плане лучше, но если база данных сервера (хендлы) не обновляется в процессе работы, лучше считать её один раз и держать на стороне клиента. Потом просто читать интересующую характеристику по хендлу. Если заранее известен UUID характеристики, то проще найти только её хендл один раз и потом использовать. А ещё лучше в плане потребления забить в клиент хендл интересующей характеристики и читать только её.
Я тут рассуждаю если да кабы без информации что делает система. Если назначение её просто читать температуру из сервера, лучший в плане потребления способ организовать сервер как бикон и передавать температуру в поле User Defined Data или прото как часть имени. Вот для примера так организованы данные в моём гаражном биконе, передающим положение двери и температуру в имени и его потребление. Период трансляции 1 сек, период измерений 10 сек. Работает от CR2032. Бикон не включает радио на приём, что также способствует снижению потребления.
Цитата:
я наблюдаю постоянную потерю соединения
Всё правильно и виновников много, в том числе внешних и неконтролируемых. BLE изначально не предназначен для постоянного соединения. Как известно периодически с периодом connection interval производится переход на другую частоту, и если на ней помеха, то создаётся угроза соединению. С этим можно бороться, поиграв с параметрами соединения (latency и timeout), или просто пересоединиться.
Система - просто показометр тмпературы с двумя шкалами (дом/улица). Поэтому хочется подключаться к двум датчикам. И мне пока хотелось избежать изготовления серверной части. Поэтому в данный момент серверами у меня работают платы Thunderboard Sense и TB sense 2 с родной аппликацией, которая вещает сервис Environment sensing с UUID=0x181a и хараектеристикой Temperature. Соединение создаётся по BT адресу, потом выясняются хэндлы (хэндл сервиса всё-равно надо выяснить иначе хэндл характеристи нельзя узнать) и дальше идёт работа. При отвале соединения, программа ждёт адвертисмента (сканирование пассивное) и подключается по-новой.
Вот после последнего воркшопа, когда я посмотрел Network Analyzer-ом на пакеты, я так понял что пакеты всё равно там носятся толпами вне зависимости от передачи данных - поэтому такой вопрос и возник. Еще, для того чтобы к одному сенсору я мог подключиться несколькими устройствами, я возобновляю адвертисмент после установления соединения - но это тоже должно повышать расход энергии?
Beacon - это просто сервер который делает адвертисмент, но который non-connectable? И так понимаю, нет каког-то стандарта, на вещание температуры среды? Собственно, я свою конструкцию решил перелопатить, потому готов изменить всё принципиально.
В теории ДА, вопрос насколько. Тут всё зависит от временных параметров соединения и advertisement, а также от длины advertisement пакета. Типично, период advertisement можно установить 1 сек для уменьшения нагрузки на батарею, в то время как connection interval типично около 100мс. В дефолтном приложении сенсоров, залитых в демо-платы, оба периода раз в 10 меньше, и длина advertisement близка к максимальной. Поэтому потребление больше, чем следовало-бы. Если хотите питать серверы от CR-ки, лучше увеличить оба периода как я сказал (если бикон - то только один период) и сократить длину пакета до минимума.
Цитата:
Beacon - это просто сервер который делает адвертисмент, но который non-connectable?
Да. В Студио есть демо-проект iBeacon, его можно взять за основу. Для примера код app.c моего бикона. Разрабатывал его давно, поэтому там встречаются depricated API. Для дальнейшего улучшения следовало-бы изменять имя только если текущее измерение отличается от прежнего. Спойлер
typedef struct { uint8_t flags_len; // Length of the Flags field. uint8_t flags_type; // Type of the Flags field. uint8_t flags; // Flags field. uint8_t field_len; // Length of device name. uint8_t field_type; // Device name (9). char beacon_data[8]; } beacon_adv_data_t;
static void bcn_setup_adv_beaconing(void);
// The advertising set handle allocated from Bluetooth stack. static uint8_t advertising_set_handle = 0xff; int8_t zOut; static beacon_adv_data_t beacon_adv_data;
// Start advertising in user mode and disable connections. sl_bt_advertiser_start(advertising_set_handle, advertiser_user_data, advertiser_non_connectable); }
Сделал маяк... Правда, я данные о температуре решил запихнуть поглубже, а не в названии. Взял структуру как в последнем воркшопе и температуру передаю в том поле, где в воркшопе передавалась энкриптнутая строка. Поначалу у меня мой клиент адвертисменты видел, но EFR Connect при сканировании BT устройств его не показывал, пока не сообразил, что раз я укоротил строку, то и длина manuf_len тоже уменьшилась. Вторая проблема была в том, что я в плату Thunderboard Sense вставил батарейку CR2032 и... ничего. Тестером смотрю, что напряжение на ней упало до 1.7в. Тут я тоже подумал, что значительное потребление было из-за того, что я вставил отладочную распечатку. Удалил из компонентов IO USART, app_log, app_assert, OTA DFU. Теперь, вроде напряжение не просаживается и от батарейки работает. Правда нет идей как измерить потребление. На приобретение SLWSTK еще не созрел.
typedef struct{ uint8_t flags_len;// 0 Length of the Flags field uint8_t flags_type;// 1 Type of the Flags field uint8_t flags;// 2 Flags uint8_t name_len;// **3** Length of the Name field uint8_t name_type;// Type of the Name field uint8_t name[ADVERTISING_NAME_SIZE];// Name uint8_t manuf_len;// 4 Length of the Manufacturer ID field uint8_t manuf_type;// 5 Type of the Manufacturer ID field uint8_t company_LO;// 6 Manufacturer ID lower byte uint8_t company_HI;// 7 Manufacturer ID higher byte uint8_t adv_data[CIPHER_MSG_SIZE];// 8 User data field }custom_adv_t;
switch (SL_BT_MSG_ID(evt->header)){ case sl_bt_evt_system_boot_id:
// Extract unique ID from BT Address. sl_bt_system_get_identity_address(&address,&address_type);
// Pad and reverse unique ID to get System ID. system_id[0]= address.addr[5]; system_id[1]= address.addr[4]; system_id[2]= address.addr[3]; system_id[3]= 0xFF; system_id[4]= 0xFE; system_id[5]= address.addr[2]; system_id[6]= address.addr[1]; system_id[7]= address.addr[0];
Если проект для имеющейся у меня демо платы, пришлите его мне и я измерю его токопотребление с помощью WSTK. Из серии Thunderboard у меня есть Thunderboard Sense 2 и Thunderboard BG22, но не старая Thunderboard Sense. Желательно получить .sls файл, а не только HEX, чтобы я смог подправить его если нужно.
Проект вот. Он под плату на которой сейчас идёт воркшоп BRD4184A Thunderboard Sense. Пробовал мультиметром измерить - в самом начале было неколько сотен микроампер, а потом только всплески единиц микроампер. Ну, я задал вещать с периодом от 2.5 до 5 секунд. Тут я думаю всё в порядке. А вот в функции запроса температуры от датчика раз в минуту - там у сеня есть нехорошее подозрение, так как применил встроенную функцию и за неё не уверен. Но тут погонял пару дней с батарейкой - на холостом ходу на батарейке 2,97, в схеме 2,85, а через некоторое время 2,75в. Но и батарейка СR2032 не первой свежести.
Вы не можете начинать темы Вы не можете отвечать на сообщения Вы не можете редактировать свои сообщения Вы не можете удалять свои сообщения Вы не можете добавлять вложения