424 lines
14 KiB
C++
424 lines
14 KiB
C++
/****************************************************************************
|
|
* Aug 11 17:13:51 2020
|
|
* Copyright 2020 Dirk Brosswick
|
|
* Email: dirk.brosswick@googlemail.com
|
|
****************************************************************************/
|
|
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*/
|
|
|
|
/*
|
|
* inspire by https://github.com/bburky/t-watch-2020-project
|
|
*
|
|
*/
|
|
#include "config.h"
|
|
#include "Arduino.h"
|
|
|
|
#include <BLEDevice.h>
|
|
#include <BLEServer.h>
|
|
#include <BLEUtils.h>
|
|
#include <BLE2902.h>
|
|
|
|
#include "blectl.h"
|
|
#include "json_psram_allocator.h"
|
|
|
|
#include "gui/statusbar.h"
|
|
|
|
EventGroupHandle_t blectl_status = NULL;
|
|
portMUX_TYPE blectlMux = portMUX_INITIALIZER_UNLOCKED;
|
|
|
|
blectl_config_t blectl_config;
|
|
|
|
blectl_event_t *blectl_event_cb_table = NULL;
|
|
uint32_t blectl_event_cb_entrys = 0;
|
|
void blectl_send_event_cb( EventBits_t event, char *msg );
|
|
|
|
BLEServer *pServer = NULL;
|
|
BLECharacteristic *pTxCharacteristic;
|
|
uint8_t txValue = 0;
|
|
|
|
char *gadgetbridge_msg = NULL;
|
|
uint32_t gadgetbridge_msg_size = 0;
|
|
|
|
/*
|
|
*
|
|
*/
|
|
class BleCtlServerCallbacks: public BLEServerCallbacks {
|
|
void onConnect(BLEServer* pServer) {
|
|
blectl_set_event( BLECTL_CONNECT );
|
|
blectl_clear_event( BLECTL_DISCONNECT );
|
|
blectl_send_event_cb( BLECTL_CONNECT, (char*)"connected" );
|
|
log_i("BLE connected");
|
|
};
|
|
|
|
void onDisconnect(BLEServer* pServer) {
|
|
blectl_set_event( BLECTL_DISCONNECT );
|
|
blectl_clear_event( BLECTL_CONNECT );
|
|
blectl_send_event_cb( BLECTL_DISCONNECT, (char*)"disconnected" );
|
|
log_i("BLE disconnected");
|
|
delay(500);
|
|
if ( blectl_get_advertising() ) {
|
|
pServer->getAdvertising()->start();
|
|
log_i("BLE advertising...");
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
*
|
|
*/
|
|
class BtlCtlSecurity : public BLESecurityCallbacks {
|
|
|
|
uint32_t onPassKeyRequest(){
|
|
return 123456;
|
|
}
|
|
void onPassKeyNotify(uint32_t pass_key){
|
|
char pin[16]="";
|
|
snprintf( pin, sizeof( pin ), "%06d", pass_key );
|
|
blectl_set_event( BLECTL_PIN_AUTH );
|
|
blectl_send_event_cb( BLECTL_PIN_AUTH, pin );
|
|
log_i("Bluetooth Pairing Request\r\nPIN: %s", pin );
|
|
}
|
|
bool onConfirmPIN(uint32_t pass_key){
|
|
return false;
|
|
}
|
|
bool onSecurityRequest(){
|
|
return true;
|
|
}
|
|
|
|
void onAuthenticationComplete( esp_ble_auth_cmpl_t cmpl ){
|
|
log_i("Bluetooth pairing %s", cmpl.success ? "successful" : "unsuccessful");
|
|
|
|
if( cmpl.success ){
|
|
if ( blectl_get_event( BLECTL_PIN_AUTH ) ) {
|
|
blectl_send_event_cb( BLECTL_PAIRING_SUCCESS, (char*)"success" );
|
|
}
|
|
}
|
|
else {
|
|
if ( blectl_get_event( BLECTL_PIN_AUTH ) ) {
|
|
blectl_send_event_cb( BLECTL_PAIRING_ABORT, (char*)"abort" );
|
|
}
|
|
pServer->startAdvertising();
|
|
}
|
|
|
|
if ( blectl_get_event( BLECTL_PIN_AUTH ) ) {
|
|
blectl_clear_event( BLECTL_PIN_AUTH );
|
|
}
|
|
}
|
|
};
|
|
|
|
void blectl_add_char_to_gadgetbridge_msg( char msg_char ) {
|
|
gadgetbridge_msg_size++;
|
|
|
|
if ( gadgetbridge_msg == NULL ) {
|
|
gadgetbridge_msg = (char *)ps_calloc( gadgetbridge_msg_size + 1, 1 );
|
|
if ( gadgetbridge_msg == NULL ) {
|
|
log_e("gadgetbridge_msg alloc fail");
|
|
while(true);
|
|
}
|
|
}
|
|
else {
|
|
char *new_gadgetbridge_msg = NULL;
|
|
new_gadgetbridge_msg = (char *)ps_realloc( gadgetbridge_msg, gadgetbridge_msg_size + 1 );
|
|
if ( new_gadgetbridge_msg == NULL ) {
|
|
log_e("gadgetbridge_msg realloc fail");
|
|
while(true);
|
|
}
|
|
gadgetbridge_msg = new_gadgetbridge_msg;
|
|
}
|
|
gadgetbridge_msg[ gadgetbridge_msg_size - 1 ] = msg_char;
|
|
gadgetbridge_msg[ gadgetbridge_msg_size ] = '\0';
|
|
}
|
|
|
|
void blectl_delete_gadgetbridge_msg ( void ) {
|
|
gadgetbridge_msg_size = 0;
|
|
|
|
if ( gadgetbridge_msg == NULL ) {
|
|
gadgetbridge_msg = (char *)ps_calloc( gadgetbridge_msg_size + 1, 1 );
|
|
if ( gadgetbridge_msg == NULL ) {
|
|
log_e("gadgetbridge_msg alloc fail");
|
|
while(true);
|
|
}
|
|
}
|
|
else {
|
|
char *new_gadgetbridge_msg = NULL;
|
|
new_gadgetbridge_msg = (char *)ps_realloc( gadgetbridge_msg, gadgetbridge_msg_size + 1 );
|
|
if ( new_gadgetbridge_msg == NULL ) {
|
|
log_e("gadgetbridge_msg realloc fail");
|
|
while(true);
|
|
}
|
|
gadgetbridge_msg = new_gadgetbridge_msg;
|
|
}
|
|
gadgetbridge_msg[ gadgetbridge_msg_size ] = '\0';
|
|
}
|
|
|
|
class BleCtlCallbacks : public BLECharacteristicCallbacks
|
|
{
|
|
void onWrite(BLECharacteristic *pCharacteristic)
|
|
{
|
|
char *msg = (char *)ps_calloc( pCharacteristic->getValue().length() + 1, 1 );
|
|
if ( msg == NULL ) {
|
|
Serial.printf("ps_calloc fail\r\n");
|
|
return;
|
|
}
|
|
else {
|
|
strlcpy( msg, pCharacteristic->getValue().c_str(), pCharacteristic->getValue().length() + 1 );
|
|
for ( int i = 0 ; i < pCharacteristic->getValue().length(); i++ ) {
|
|
switch( msg[ i ] ) {
|
|
case EndofText: blectl_delete_gadgetbridge_msg();
|
|
log_i("attention, new link establish");
|
|
break;
|
|
case DataLinkEscape: blectl_delete_gadgetbridge_msg();
|
|
log_i("attention, new message");
|
|
break;
|
|
case LineFeed: log_i("message complete, fire BLTCTL_MSG callback");
|
|
if( gadgetbridge_msg[ 0 ] == 'G' && gadgetbridge_msg[ 1 ] == 'B' ) {
|
|
log_i("gadgetbridge message identified, cut down to json");
|
|
gadgetbridge_msg[ gadgetbridge_msg_size - 1 ] = '\0';
|
|
log_i("msg: %s", &gadgetbridge_msg[ 3 ] );
|
|
blectl_send_event_cb( BLECTL_MSG, &gadgetbridge_msg[ 3 ] );
|
|
}
|
|
else {
|
|
log_i("msg: %s", gadgetbridge_msg );
|
|
blectl_send_event_cb( BLECTL_MSG, gadgetbridge_msg );
|
|
}
|
|
break;
|
|
default: blectl_add_char_to_gadgetbridge_msg( msg[ i ] );
|
|
}
|
|
}
|
|
free(msg);
|
|
}
|
|
}
|
|
};
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void blectl_setup( void ) {
|
|
|
|
blectl_status = xEventGroupCreate();
|
|
|
|
esp_bt_controller_enable( ESP_BT_MODE_BLE );
|
|
esp_bt_controller_mem_release( ESP_BT_MODE_CLASSIC_BT );
|
|
esp_bt_mem_release( ESP_BT_MODE_CLASSIC_BT );
|
|
esp_bt_controller_mem_release( ESP_BT_MODE_IDLE );
|
|
esp_bt_mem_release( ESP_BT_MODE_IDLE );
|
|
|
|
// Create the BLE Device
|
|
// Name needs to match filter in Gadgetbridge's banglejs getSupportedType() function.
|
|
// This is too long I think:
|
|
// BLEDevice::init("Espruino Gadgetbridge Compatible Device");
|
|
BLEDevice::init("Espruino (T-Watch2020)");
|
|
// The minimum power level (-12dbm) ESP_PWR_LVL_N12 was too low
|
|
BLEDevice::setPower( ESP_PWR_LVL_N9 );
|
|
|
|
// Enable encryption
|
|
BLEServer* pServer = BLEDevice::createServer();
|
|
BLEDevice::setEncryptionLevel( ESP_BLE_SEC_ENCRYPT_NO_MITM );
|
|
BLEDevice::setSecurityCallbacks( new BtlCtlSecurity() );
|
|
|
|
// Enable authentication
|
|
BLESecurity *pSecurity = new BLESecurity();
|
|
pSecurity->setAuthenticationMode(ESP_LE_AUTH_REQ_SC_BOND);
|
|
pSecurity->setCapability(ESP_IO_CAP_OUT);
|
|
pSecurity->setInitEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
|
|
pSecurity->setRespEncryptionKey(ESP_BLE_ENC_KEY_MASK | ESP_BLE_ID_KEY_MASK);
|
|
|
|
// Create the BLE Server
|
|
pServer = BLEDevice::createServer();
|
|
pServer->setCallbacks( new BleCtlServerCallbacks() );
|
|
|
|
// Create the BLE Service
|
|
BLEService *pService = pServer->createService(SERVICE_UUID);
|
|
|
|
// Create a BLE Characteristic
|
|
pTxCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY );
|
|
pTxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
|
|
pTxCharacteristic->addDescriptor( new BLE2902() );
|
|
|
|
BLECharacteristic *pRxCharacteristic = pService->createCharacteristic( CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE );
|
|
pRxCharacteristic->setAccessPermissions(ESP_GATT_PERM_READ_ENCRYPTED | ESP_GATT_PERM_WRITE_ENCRYPTED);
|
|
pRxCharacteristic->setCallbacks( new BleCtlCallbacks() );
|
|
|
|
// Start the service
|
|
pService->start();
|
|
|
|
// Start advertising
|
|
pServer->getAdvertising()->addServiceUUID( pService->getUUID() );
|
|
// Slow advertising interval for battery life
|
|
// The maximum 0x4000 interval of ~16 sec was too slow, I could not reliably connect
|
|
pServer->getAdvertising()->setMinInterval( 100 );
|
|
pServer->getAdvertising()->setMaxInterval( 200 );
|
|
|
|
if ( blectl_get_advertising() ) {
|
|
pServer->getAdvertising()->start();
|
|
log_i("BLE advertising...");
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void blectl_set_event( EventBits_t bits ) {
|
|
portENTER_CRITICAL(&blectlMux);
|
|
xEventGroupSetBits( blectl_status, bits );
|
|
portEXIT_CRITICAL(&blectlMux);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void blectl_clear_event( EventBits_t bits ) {
|
|
portENTER_CRITICAL(&blectlMux);
|
|
xEventGroupClearBits( blectl_status, bits );
|
|
portEXIT_CRITICAL(&blectlMux);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
bool blectl_get_event( EventBits_t bits ) {
|
|
portENTER_CRITICAL(&blectlMux);
|
|
EventBits_t temp = xEventGroupGetBits( blectl_status ) & bits;
|
|
portEXIT_CRITICAL(&blectlMux);
|
|
if ( temp )
|
|
return( true );
|
|
|
|
return( false );
|
|
}
|
|
|
|
void blectl_register_cb( EventBits_t event, BLECTL_CALLBACK_FUNC blectl_event_cb ) {
|
|
blectl_event_cb_entrys++;
|
|
|
|
if ( blectl_event_cb_table == NULL ) {
|
|
blectl_event_cb_table = ( blectl_event_t * )ps_malloc( sizeof( blectl_event_t ) * blectl_event_cb_entrys );
|
|
if ( blectl_event_cb_table == NULL ) {
|
|
log_e("blectl_event_cb_table malloc faild");
|
|
while(true);
|
|
}
|
|
}
|
|
else {
|
|
blectl_event_t *new_blectl_event_cb_table = NULL;
|
|
|
|
new_blectl_event_cb_table = ( blectl_event_t * )ps_realloc( blectl_event_cb_table, sizeof( blectl_event_t ) * blectl_event_cb_entrys );
|
|
if ( new_blectl_event_cb_table == NULL ) {
|
|
log_e("blectl_event_cb_table realloc faild");
|
|
while(true);
|
|
}
|
|
blectl_event_cb_table = new_blectl_event_cb_table;
|
|
}
|
|
|
|
blectl_event_cb_table[ blectl_event_cb_entrys - 1 ].event = event;
|
|
blectl_event_cb_table[ blectl_event_cb_entrys - 1 ].event_cb = blectl_event_cb;
|
|
log_i("register event_cb success");
|
|
}
|
|
/*
|
|
*
|
|
*/
|
|
void blectl_send_event_cb( EventBits_t event, char *msg ) {
|
|
for ( int entry = 0 ; entry < blectl_event_cb_entrys ; entry++ ){
|
|
if ( event & blectl_event_cb_table[ entry ].event ) {
|
|
log_i("call event_cb");
|
|
blectl_event_cb_table[ entry ].event_cb( event, msg );
|
|
}
|
|
}
|
|
}
|
|
|
|
void blectl_standby( void ) {
|
|
/*
|
|
*/
|
|
}
|
|
|
|
void blectl_wakeup( void ) {
|
|
/*
|
|
*/
|
|
}
|
|
|
|
void blectl_set_enable_on_standby( bool enable_on_standby ) {
|
|
blectl_config.enable_on_standby = enable_on_standby;
|
|
blectl_save_config();
|
|
}
|
|
|
|
void blectl_set_advertising( bool advertising ) {
|
|
blectl_config.advertising = advertising;
|
|
blectl_save_config();
|
|
if ( blectl_get_event( BLECTL_CONNECT ) )
|
|
return;
|
|
|
|
if ( advertising ) {
|
|
pServer->getAdvertising()->start();
|
|
}
|
|
else {
|
|
pServer->getAdvertising()->stop();
|
|
}
|
|
}
|
|
|
|
bool blectl_get_enable_on_standby( void ) {
|
|
return( blectl_config.enable_on_standby );
|
|
}
|
|
|
|
bool blectl_get_advertising( void ) {
|
|
return( blectl_config.enable_on_standby );
|
|
}
|
|
/*
|
|
*
|
|
*/
|
|
void blectl_save_config( void ) {
|
|
fs::File file = SPIFFS.open( BLECTL_JSON_COFIG_FILE, FILE_WRITE );
|
|
|
|
if (!file) {
|
|
log_e("Can't open file: %s!", BLECTL_JSON_COFIG_FILE );
|
|
}
|
|
else {
|
|
SpiRamJsonDocument doc( 1000 );
|
|
|
|
doc["advertising"] = blectl_config.advertising;
|
|
doc["enable_on_standby"] = blectl_config.enable_on_standby;
|
|
|
|
if ( serializeJsonPretty( doc, file ) == 0) {
|
|
log_e("Failed to write config file");
|
|
}
|
|
doc.clear();
|
|
}
|
|
file.close();
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void blectl_read_config( void ) {
|
|
if ( SPIFFS.exists( BLECTL_JSON_COFIG_FILE ) ) {
|
|
fs::File file = SPIFFS.open( BLECTL_JSON_COFIG_FILE, FILE_READ );
|
|
if (!file) {
|
|
log_e("Can't open file: %s!", BLECTL_JSON_COFIG_FILE );
|
|
}
|
|
else {
|
|
int filesize = file.size();
|
|
SpiRamJsonDocument doc( filesize * 2 );
|
|
|
|
DeserializationError error = deserializeJson( doc, file );
|
|
if ( error ) {
|
|
log_e("blectl deserializeJson() failed: %s", error.c_str() );
|
|
}
|
|
else {
|
|
blectl_config.advertising = doc["advertising"].as<bool>();
|
|
blectl_config.enable_on_standby = doc["enable_on_standby"].as<bool>();
|
|
}
|
|
doc.clear();
|
|
}
|
|
file.close();
|
|
}
|
|
} |