/**************************************************************************** * 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 #include #include #include #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(); blectl_config.enable_on_standby = doc["enable_on_standby"].as(); } doc.clear(); } file.close(); } }