293 lines
9.3 KiB
C
293 lines
9.3 KiB
C
#include "remote_ble.h"
|
|
|
|
#include <inttypes.h>
|
|
#include <stdbool.h>
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
#include "FreeRTOS.h"
|
|
#include "event_groups.h"
|
|
#include "cmsis_os2.h"
|
|
|
|
#include "w6x_api.h"
|
|
#include "logging.h"
|
|
#include "remote_params.h"
|
|
|
|
/* Identifiants GATT : UUID 128 bits en notation canonique, sans tirets (format du module). */
|
|
#define SERVICE_UUID "c31100007e8a4b1c9d52f0a1e4b7c600"
|
|
#define CHAR_CONTROL_UUID "c31100017e8a4b1c9d52f0a1e4b7c600"
|
|
#define CHAR_STATE_UUID "c31100027e8a4b1c9d52f0a1e4b7c600"
|
|
|
|
#define SERVICE_INDEX 0U
|
|
#define CHAR_CONTROL_INDEX 0U
|
|
#define CHAR_STATE_INDEX 1U
|
|
|
|
#define DEVICE_NAME "CelloFX"
|
|
|
|
/* Donnees d'annonce BLE brutes (hexadecimal) :
|
|
* annonce : flags (LE General Discoverable, BR/EDR non supporte) + nom complet "CelloFX"
|
|
* reponse au scan : liste complete de l'UUID 128 bits du service, en little-endian */
|
|
#define ADV_DATA "020106" "0809" "43656C6C6F4658"
|
|
#define SCAN_RESP_DATA "1107" "00C6B7E4A1F0529D1C4B8A7E000011C3"
|
|
|
|
#define RECORD_SIZE 3U
|
|
#define CMD_SNAPSHOT 0xFFU
|
|
/* 6 enregistrements = 18 octets : tient dans une notification avec le MTU ATT par defaut (23). */
|
|
#define NOTIFY_MAX_RECORDS 6U
|
|
#define RX_BUFFER_SIZE 247U
|
|
|
|
#define INIT_RETRIES 3U
|
|
#define INIT_RETRY_DELAY_MS 3000U
|
|
|
|
#define EVT_CONNECTED (1UL << 0)
|
|
#define EVT_DISCONNECTED (1UL << 1)
|
|
#define EVT_SNAPSHOT (1UL << 2)
|
|
#define EVT_ALL (EVT_CONNECTED | EVT_DISCONNECTED | EVT_SNAPSHOT)
|
|
|
|
static uint8_t rx_buffer[RX_BUFFER_SIZE]; /* le module y copie les donnees ecrites par le client */
|
|
static EventGroupHandle_t events;
|
|
static volatile uint32_t conn_handle = 0xFFU;
|
|
static volatile bool connected;
|
|
static bool started;
|
|
|
|
static const osThreadAttr_t task_attr = {
|
|
.name = "remoteBle",
|
|
.stack_size = 3072,
|
|
.priority = (osPriority_t)osPriorityBelowNormal,
|
|
};
|
|
|
|
/* Les callbacks du driver s'executent dans son propre contexte : ils doivent rester courts et
|
|
* n'appellent aucune fonction W6X_* (l'envoi des notifications est fait par la tache). */
|
|
static void on_ble_data(const W6X_Ble_CbParamData_t *p)
|
|
{
|
|
uint32_t len = p->available_data_length;
|
|
if (len > RX_BUFFER_SIZE)
|
|
{
|
|
len = RX_BUFFER_SIZE;
|
|
}
|
|
|
|
for (uint32_t i = 0; i + RECORD_SIZE <= len; i += RECORD_SIZE)
|
|
{
|
|
uint8_t id = rx_buffer[i];
|
|
int16_t value = (int16_t)((uint16_t)rx_buffer[i + 1] | ((uint16_t)rx_buffer[i + 2] << 8));
|
|
|
|
if (id == CMD_SNAPSHOT)
|
|
{
|
|
(void)xEventGroupSetBits(events, EVT_SNAPSHOT);
|
|
}
|
|
else if (!RemoteParams_Set(id, value))
|
|
{
|
|
LogWarn("BLE: identifiant de parametre inconnu 0x%02X\n", (unsigned)id);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void app_ble_cb(W6X_event_id_t event_id, void *event_args)
|
|
{
|
|
const W6X_Ble_CbParamData_t *p = (const W6X_Ble_CbParamData_t *)event_args;
|
|
|
|
switch (event_id)
|
|
{
|
|
case W6X_BLE_EVT_CONNECTED_ID:
|
|
conn_handle = p->remote_ble_device.conn_handle;
|
|
connected = true;
|
|
LogInfo("BLE: client connecte (handle %" PRIu32 ")\n", conn_handle);
|
|
(void)xEventGroupSetBits(events, EVT_CONNECTED);
|
|
break;
|
|
|
|
case W6X_BLE_EVT_DISCONNECTED_ID:
|
|
connected = false;
|
|
conn_handle = 0xFFU;
|
|
LogInfo("BLE: client deconnecte\n");
|
|
(void)xEventGroupSetBits(events, EVT_DISCONNECTED);
|
|
break;
|
|
|
|
case W6X_BLE_EVT_WRITE_ID:
|
|
if (p->service_idx == SERVICE_INDEX && p->charac_idx == CHAR_CONTROL_INDEX)
|
|
{
|
|
on_ble_data(p);
|
|
}
|
|
break;
|
|
|
|
case W6X_BLE_EVT_NOTIFICATION_STATUS_ENABLED_ID:
|
|
/* le client s'abonne : on lui envoie l'etat complet pour qu'il se synchronise */
|
|
(void)xEventGroupSetBits(events, EVT_SNAPSHOT);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Le driver exige des callbacks valides pour toutes les familles d'evenements. */
|
|
static void app_unused_cb(W6X_event_id_t event_id, void *event_args)
|
|
{
|
|
(void)event_id;
|
|
(void)event_args;
|
|
}
|
|
|
|
static void app_error_cb(W6X_Status_t status, char const *func_name)
|
|
{
|
|
LogError("ST67: erreur %" PRIi32 " dans %s\n", (int32_t)status, func_name);
|
|
}
|
|
|
|
static W6X_Status_t configure_server(void)
|
|
{
|
|
W6X_Status_t ret;
|
|
char name[W6X_BLE_DEVICE_NAME_SIZE] = DEVICE_NAME; /* l'API lit un tableau de taille fixe */
|
|
|
|
ret = W6X_Ble_SetDeviceName(name);
|
|
if (ret != W6X_STATUS_OK) { LogError("BLE: nom\n"); return ret; }
|
|
|
|
ret = W6X_Ble_SetTxPower(0);
|
|
if (ret != W6X_STATUS_OK) { LogError("BLE: puissance d'emission\n"); return ret; }
|
|
|
|
ret = W6X_Ble_SetAdvData(ADV_DATA);
|
|
if (ret != W6X_STATUS_OK) { LogError("BLE: donnees d'annonce\n"); return ret; }
|
|
|
|
ret = W6X_Ble_SetScanRespData(SCAN_RESP_DATA);
|
|
if (ret != W6X_STATUS_OK) { LogError("BLE: reponse au scan\n"); return ret; }
|
|
|
|
ret = W6X_Ble_CreateService(SERVICE_INDEX, SERVICE_UUID, W6X_BLE_UUID_TYPE_128);
|
|
if (ret != W6X_STATUS_OK) { LogError("BLE: creation du service\n"); return ret; }
|
|
|
|
ret = W6X_Ble_CreateCharacteristic(SERVICE_INDEX, CHAR_CONTROL_INDEX, CHAR_CONTROL_UUID,
|
|
W6X_BLE_UUID_TYPE_128,
|
|
W6X_BLE_CHAR_PROP_WRITE_WITHOUT_RESP | W6X_BLE_CHAR_PROP_WRITE_WITH_RESP,
|
|
W6X_BLE_CHAR_PERM_WRITE);
|
|
if (ret != W6X_STATUS_OK) { LogError("BLE: caracteristique Control\n"); return ret; }
|
|
|
|
ret = W6X_Ble_CreateCharacteristic(SERVICE_INDEX, CHAR_STATE_INDEX, CHAR_STATE_UUID,
|
|
W6X_BLE_UUID_TYPE_128,
|
|
W6X_BLE_CHAR_PROP_NOTIFY,
|
|
W6X_BLE_CHAR_PERM_READ);
|
|
if (ret != W6X_STATUS_OK) { LogError("BLE: caracteristique State\n"); return ret; }
|
|
|
|
ret = W6X_Ble_RegisterCharacteristics();
|
|
if (ret != W6X_STATUS_OK) { LogError("BLE: enregistrement des caracteristiques\n"); return ret; }
|
|
|
|
ret = W6X_Ble_SetSecurityParam(W6X_BLE_SEC_IO_NO_INPUT_OUTPUT);
|
|
if (ret != W6X_STATUS_OK) { LogWarn("BLE: parametres de securite\n"); }
|
|
|
|
return W6X_Ble_AdvStart();
|
|
}
|
|
|
|
static void send_snapshot(void)
|
|
{
|
|
if (!connected)
|
|
{
|
|
return;
|
|
}
|
|
|
|
uint8_t buffer[NOTIFY_MAX_RECORDS * RECORD_SIZE];
|
|
uint8_t count = RemoteParams_Count();
|
|
uint8_t index = 0;
|
|
|
|
while (index < count && connected)
|
|
{
|
|
uint32_t used = 0;
|
|
while (index < count && used < sizeof(buffer))
|
|
{
|
|
uint8_t id = RemoteParams_IdAt(index);
|
|
int16_t value = 0;
|
|
(void)RemoteParams_Get(id, &value);
|
|
buffer[used++] = id;
|
|
buffer[used++] = (uint8_t)((uint16_t)value & 0xFFU);
|
|
buffer[used++] = (uint8_t)(((uint16_t)value >> 8) & 0xFFU);
|
|
index++;
|
|
}
|
|
|
|
uint32_t sent = 0;
|
|
W6X_Status_t ret = W6X_Ble_ServerNotify((uint8_t)conn_handle, SERVICE_INDEX, CHAR_STATE_INDEX,
|
|
buffer, used, &sent, 1000);
|
|
if (ret != W6X_STATUS_OK)
|
|
{
|
|
LogError("BLE: echec de notification (%" PRIi32 ")\n", (int32_t)ret);
|
|
return;
|
|
}
|
|
osDelay(20); /* laisse le temps au lien radio d'ecouler la notification */
|
|
}
|
|
}
|
|
|
|
static bool start_module(void)
|
|
{
|
|
W6X_App_Cb_t cb = {0};
|
|
cb.APP_wifi_cb = app_unused_cb;
|
|
cb.APP_net_cb = app_unused_cb;
|
|
cb.APP_mqtt_cb = app_unused_cb;
|
|
cb.APP_ble_cb = app_ble_cb;
|
|
cb.APP_error_cb = app_error_cb;
|
|
(void)W6X_RegisterAppCb(&cb);
|
|
|
|
for (uint32_t attempt = 1; attempt <= INIT_RETRIES; attempt++)
|
|
{
|
|
W6X_Status_t ret = W6X_Init();
|
|
if (ret == W6X_STATUS_OK)
|
|
{
|
|
ret = W6X_Ble_Init(W6X_BLE_MODE_SERVER, rx_buffer, sizeof(rx_buffer) - 1U);
|
|
if (ret == W6X_STATUS_OK)
|
|
{
|
|
ret = configure_server();
|
|
if (ret == W6X_STATUS_OK)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
LogError("ST67: initialisation impossible (essai %" PRIu32 "/%u, code %" PRIi32 ")\n",
|
|
attempt, (unsigned)INIT_RETRIES, (int32_t)ret);
|
|
W6X_DeInit();
|
|
osDelay(INIT_RETRY_DELAY_MS);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static void remote_ble_task(void *argument)
|
|
{
|
|
(void)argument;
|
|
|
|
if (!start_module())
|
|
{
|
|
LogError("ST67: module absent ou muet, controle a distance desactive\n");
|
|
osThreadExit();
|
|
}
|
|
|
|
LogInfo("BLE: annonce \"" DEVICE_NAME "\" demarree\n");
|
|
LogInfo("tas FreeRTOS libre: %u octets (minimum atteint: %u)\n",
|
|
(unsigned)xPortGetFreeHeapSize(), (unsigned)xPortGetMinimumEverFreeHeapSize());
|
|
|
|
for (;;)
|
|
{
|
|
EventBits_t bits = xEventGroupWaitBits(events, EVT_ALL, pdTRUE, pdFALSE, portMAX_DELAY);
|
|
|
|
if ((bits & EVT_DISCONNECTED) != 0U)
|
|
{
|
|
W6X_Status_t ret = W6X_Ble_AdvStart(); /* redevient visible apres une deconnexion */
|
|
if (ret != W6X_STATUS_OK)
|
|
{
|
|
LogWarn("BLE: relance de l'annonce (%" PRIi32 ")\n", (int32_t)ret);
|
|
}
|
|
}
|
|
if ((bits & EVT_SNAPSHOT) != 0U)
|
|
{
|
|
send_snapshot();
|
|
}
|
|
}
|
|
}
|
|
|
|
void RemoteBle_Start(void)
|
|
{
|
|
if (started)
|
|
{
|
|
return;
|
|
}
|
|
started = true;
|
|
|
|
events = xEventGroupCreate();
|
|
if (events == NULL || osThreadNew(remote_ble_task, NULL, &task_attr) == NULL)
|
|
{
|
|
LogError("ST67: creation de la tache de controle impossible\n");
|
|
}
|
|
}
|