Use BLE on mobile to control the ESP32
Bluetooth has a range of 10 meters and can be transmitted and received with a
mobile phone.
This page describes how to use a phone to communicate with an ESP32 program to send instructions and receive results.
The menu to display in the terminal
The menu is a character array with text to be displayed.
In this example a menu line can not be longer than 40 characters.
When you send the character i the menu is displayed in the app.
//-------------------------------------------- // Menu //0 1 2 3 4 //1234567890123456789012345678901234567890---- char menu[][40] = { "A SSID", "B Password", "C BLE beacon name", "I Print this menu", "R Reset settings" };
The subroutine below translates the received command line to an action;a subroutine to be executed.
//-------------------------------------------- // CLOCK Input from Bluetooth or Serial //-------------------------------------------- void ReworkInputString(String InputString) { char ff[50]; InputString.toCharArray(ff,InputString.length()); // Convert a String to char array sprintf(sptext,"Inputstring: %s Lengte : %d\n", ff,InputString.length()-1); // Tekstprint(sptext); if(InputString.length()> 40){Serial.printf("Input string too long (max40)\n"); return;} // If garbage return sptext[0] = 0; // Empty the sptext string if(InputString[0] > 31 && InputString[0] <127) // Does the string start with a letter? { switch (InputString[0]) { case 'A': case 'a': if (InputString.length() >5 ) { InputString.substring(1).toCharArray(Mem.Ssid,InputString.length()-1); sprintf(sptext,"SSID set: %s", Mem.Ssid); } else sprintf(sptext,"**** Length fault. Use between 4 and 30 characters ****"); break; case 'B': case 'b': if (InputString.length() >5 ) { InputString.substring(1).toCharArray(Mem.Password,InputString.length()-1); sprintf(sptext,"Password set: %s\n Enter @ to reset ESP32 and connect to WIFI and NTP", Mem.Password); } else sprintf(sptext,"%s,**** Length fault. Use between 4 and 40 characters ****",Mem.Password); break; case 'C': case 'c': if (InputString.length() >5 ) { InputString.substring(1).toCharArray(Mem.BLEbroadcastName,InputString.length()-1); sprintf(sptext,"BLE broadcast name set: %s", Mem.BLEbroadcastName); } else sprintf(sptext,"**** Length fault. Use between 4 and 30 characters ****"); break; case 'I': case 'i': SWversion(); break; case 'R': case 'r': if (InputString.length() == 2) { Reset(); sprintf(sptext,"\nReset to default values: Done"); } else sprintf(sptext,"**** Length fault. Enter R ****"); break;default: break; }
}
Tekstprintln(sptext);
StoreStructInFlashMemory();
InputString = ""; }
<class="auto-style1">A working INO file for an Arduino can be found at the
bottom of this page and as file here: BLEHM-10Menu.ino
if the menu program is used in nRF equipped MCU's like the RP2040 Connect or
ESP32 variants use the free IOS nRF app.
The priced app BLEserial Pro app.
has more capabilities and connect to both CC25xx and nRF chipsets.
// ============================================================================================================================= //-------------------------------------------- // ESP32-C3 /S3 Includes defines and initialisations //--------------------------------------------#include <NimBLEDevice.h> // For BLE communication https://github.com/h2zero/NimBLE-Arduino #include <Preferences.h>
Preferences FLASHSTOR; static uint32_t msTick; // Number of millisecond ticks since we last incremented the second counter
//-------------------------------------------- // BLE //#include <NimBLEDevice.h> //-------------------------------------------- BLEServer *pServer = NULL; BLECharacteristic * pTxCharacteristic; bool deviceConnected = false; bool oldDeviceConnected = false; std::string ReceivedMessageBLE;
#define SERVICE_UUID "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID #define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E" #define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"
//---------------------------------------- // Common //----------------------------------------
char sptext[120]; // For common print use struct EEPROMstorage { // Data storage in EEPROM to maintain them after power loss char Ssid[30]; char Password[40]; char BLEbroadcastName[30]; // Name of the BLE beacon byte UseBLELongString = 0; // Send strings longer than 20 bytes per message. Possible in IOS app BLEserial Pro int Checksum = 0; } Mem;
//-------------------------------------------- // Menu //0 1 2 3 4 //1234567890123456789012345678901234567890----
char menu[][40] = { "A SSID", "B Password", "C BLE beacon name", "I Print this menu", "R Reset settings" };// ------------------------------------- End Definitions --------------------------------------- // //
//-------------------------------------------- // ARDUINO Setup //-------------------------------------------- void setup() { Serial.begin(115200); Tekstprintln("Serial started"); // Setup the serial port to 115200 baud //
InitStorage(); Tekstprintln("Setting loaded"); // Load settings from storage and check validity StartBLEService(); Tekstprintln("BLE started"); // Start BLE service // Print the tekst time in the display SWversion(); } // // //-------------------------------------------- // ARDUINO Loop //-------------------------------------------- void loop() { EverySecondCheck();
CheckDevices(); } //-------------------------------------------- // Common Check connected input devices //-------------------------------------------- void CheckDevices(void) { CheckBLE(); // Something with BLE to do? SerialCheck(); // Check serial port every second } // // //-------------------------------------------- // CLOCK Update routine to run something every second //-------------------------------------------- void EverySecondCheck(void) { uint32_t msLeap = millis() - msTick; if (msLeap >999) // Every second enter the loop { Serial.println(millis()); msTick = millis(); }
}//-------------------------------------------- // Common check for serial input //-------------------------------------------- void SerialCheck(void) { String SerialString; while (Serial.available()) { char c = Serial.read(); // Serial.write(c); if (c>31 && c<128) SerialString += c; // Allow input from Space - Del else c = 0; // Delete a CR } if (SerialString.length()>0) { ReworkInputString(SerialString+"\n"); // Rework ReworkInputString(); SerialString = ""; } }
//-------------------------------------------- // Common common print routines //-------------------------------------------- void Tekstprint(char const *tekst) { if(Serial) Serial.print(tekst); SendMessageBLE(tekst);sptext[0]=0; } void Tekstprintln(char const *tekst) { sprintf(sptext,"%s\n",tekst); Tekstprint(sptext); } void TekstSprint(char const *tekst) { printf(tekst); sptext[0]=0;} // printing for Debugging purposes in serial monitor void TekstSprintln(char const *tekst) { sprintf(sptext,"%s\n",tekst); TekstSprint(sptext); }
//-------------------------------------------- // CLOCK Input from Bluetooth or Serial //-------------------------------------------- void ReworkInputString(String InputString) { char ff[50]; InputString.toCharArray(ff,InputString.length()); // Convert a String to char array sprintf(sptext,"Inputstring: %s Lengte : %d\n", ff,InputString.length()-1); // Tekstprint(sptext); if(InputString.length()> 40){Serial.printf("Input string too long (max40)\n"); return;} // If garbage return sptext[0] = 0; // Empty the sptext string if(InputString[0] > 31 && InputString[0] <127) // Does the string start with a letter? { switch (InputString[0]) { case 'A': case 'a': if (InputString.length() >5 ) { InputString.substring(1).toCharArray(Mem.Ssid,InputString.length()-1); sprintf(sptext,"SSID set: %s", Mem.Ssid);
} else sprintf(sptext,"Length fault. Use between 4 and 30 characters"); break; case 'B': case 'b': if (InputString.length() >5 ) {
InputString.substring(1).toCharArray(Mem.Password,InputString.length()-1); sprintf(sptext,"Password set: %s\n Enter @ to reset ESP32 and connect to WIFI and NTP", Mem.Password); } else sprintf(sptext,"%s," Length fault. Use between 4 and 40 characters ",Mem.Password); break;
case 'C': case 'c': if (InputString.length() >5 ) {
InputString.substring(1).toCharArray(Mem.BLEbroadcastName,InputString.length()-1); sprintf(sptext,"BLE broadcast name set: %s", Mem.BLEbroadcastName); } else sprintf(sptext,"Length fault. Use between 4 and 30 characters"); break; case 'I': case 'i': SWversion(); break; case 'R': case 'r': if (InputString.length() == 2) {
Reset(); sprintf(sptext,"\nReset to default values: Done"); }
else sprintf(sptext,"Length fault. Enter R"); break;default: break; }
}
Tekstprintln(sptext);
StoreStructInFlashMemory();
InputString = ""; }//-------------------------------------------- // Print Menu and Version info //-------------------------------------------- void SWversion(void) { #define FILENAAM (strrchr(FILE, '\') ? strrchr(FILE, '\') + 1 : FILE) PrintLine(35); for (uint8_t i = 0; i < sizeof(menu) / sizeof(menu[0]); Tekstprintln(menu[i++])); sprintf(sptext,"SSID: %s", Mem.Ssid); Tekstprintln(sptext); // sprintf(sptext,"Password: %s", Mem.Password); Tekstprintln(sptext); sprintf(sptext,"BLE name: %s", Mem.BLEbroadcastName); Tekstprintln(sptext); sprintf(sptext,"%s",Mem.UseBLELongString? "FastBLE=On":"FastBLE=Off" ); Tekstprintln(sptext); sprintf(sptext,"Software: %s",FILENAAM); Tekstprintln(sptext); //VERSION); PrintLine(35); } void PrintLine(byte Lengte) { for(int n=0; n<Lengte; n++) sptext[n]='_'; sptext[Lengte] = 0; Tekstprintln(sptext); } //----------------------------- // BLE // SendMessage by BLE Slow in packets of 20 chars or fast in one long string. // Fast can be used in IOS app BLESerial Pro //------------------------------ void SendMessageBLE(std::string Message) { if(deviceConnected) { if (Mem.UseBLELongString) // If Fast transmission is possible { pTxCharacteristic->setValue(Message); pTxCharacteristic->notify(); delay(10); // Bluetooth stack will go into congestion, if too many packets are sent } else // Packets of max 20 bytes {
int parts = (Message.length()/20) + 1; for(int n=0;n<parts;n++) {
pTxCharacteristic->setValue(Message.substr(n*20, 20)); pTxCharacteristic->notify(); delay(40); // Bluetooth stack will go into congestion, if too many packets are sent } } } }//----------------------------- // BLE Start BLE Classes //------------------------------ class MyServerCallbacks: public BLEServerCallbacks { void onConnect(BLEServer* pServer) {deviceConnected = true; }; void onDisconnect(BLEServer* pServer) {deviceConnected = false;} };
class MyCallbacks: public BLECharacteristicCallbacks { void onWrite(BLECharacteristic *pCharacteristic) { std::string rxValue = pCharacteristic->getValue(); ReceivedMessageBLE = rxValue + "\n"; // if (rxValue.length() > 0) {for (int i = 0; i < rxValue.length(); i++) printf("%c",rxValue[i]); } // printf("\n"); }
};//----------------------------- // BLE Start BLE Service //------------------------------ void StartBLEService(void) { BLEDevice::init(Mem.BLEbroadcastName); // Create the BLE Device pServer = BLEDevice::createServer(); // Create the BLE Server pServer->setCallbacks(new MyServerCallbacks()); BLEService *pService = pServer->createService(SERVICE_UUID); // Create the BLE Service pTxCharacteristic = // Create a BLE Characteristic pService->createCharacteristic(CHARACTERISTIC_UUID_TX, NIMBLE_PROPERTY::NOTIFY);
BLECharacteristic * pRxCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, NIMBLE_PROPERTY::WRITE); pRxCharacteristic->setCallbacks(new MyCallbacks()); pService->start(); BLEAdvertising *pAdvertising = BLEDevice::getAdvertising(); pAdvertising->addServiceUUID(SERVICE_UUID); pServer->start(); // Start the server
pServer->getAdvertising()->start(); // Start advertising TekstSprint("BLE Waiting a client connection to notify ...\n"); } // // //----------------------------- // BLE CheckBLE //------------------------------ void CheckBLE(void) { if(!deviceConnected && oldDeviceConnected) // Disconnecting { delay(300); // Give the bluetooth stack the chance to get things ready pServer->startAdvertising(); // Restart advertising TekstSprint("Start advertising\n"); oldDeviceConnected = deviceConnected; } if(deviceConnected && !oldDeviceConnected) // Connecting { oldDeviceConnected = deviceConnected; SWversion(); } if(ReceivedMessageBLE.length()>0) { SendMessageBLE(ReceivedMessageBLE); String BLEtext = ReceivedMessageBLE.c_str(); ReceivedMessageBLE = ""; ReworkInputString(BLEtext); } } // ////-------------------------------------------- // Common Init and check contents of EEPROM //-------------------------------------------- void InitStorage(void) { GetStructFromFlashMemory(); if( Mem.Checksum != 25065) { sprintf(sptext,"Checksum (25065) invalid: %d\n Resetting to default values",Mem.Checksum); Tekstprintln(sptext); Reset(); // If the checksum is NOK the Settings were not set } StoreStructInFlashMemory(); } //-------------------------------------------- // COMMON Store mem.struct in FlashStorage or SD // Preferences.h
//-------------------------------------------- void StoreStructInFlashMemory(void) { FLASHSTOR.begin("Mem",false); // delay(100); FLASHSTOR.putBytes("Mem", &Mem , sizeof(Mem) ); FLASHSTOR.end();
} //-------------------------------------------- // COMMON Get data from FlashStorage // Preferences.h //-------------------------------------------- void GetStructFromFlashMemory(void) { FLASHSTOR.begin("Mem", false); FLASHSTOR.getBytes("Mem", &Mem, sizeof(Mem) ); FLASHSTOR.end();
sprintf(sptext,"Mem.Checksum = %d",Mem.Checksum);Tekstprintln(sptext); }//------------------------------------------------------------------------------ // Common Reset to default settings //------------------------------------------------------------------------------ void Reset(void) { Mem.Checksum = 25065; // Mem.UseBLELongString = 0; strcpy(Mem.Ssid,"MySSID"); // Default SSID strcpy(Mem.Password,"MyPass"); // Default password strcpy(Mem.BLEbroadcastName,"ESP32Menu"); Tekstprintln("**** Reset of preferences ****"); StoreStructInFlashMemory(); // Update Mem struct
SWversion(); // Display the version number of the software }