1919
2020 NMEA GGA and RMC are shared with apps like Maps, over the Control Session
2121 as Location Information.
22- NMEA GGA, RMC and GST are shared with apps like Field Maps, over a dedicated
23- External Accessory protocol session.
22+ NMEA GGA, RMC, GST, VTG, GSA and GSV are shared with apps like Field Maps,
23+ over a dedicated External Accessory protocol session.
2424
2525 This example contains a copy of Espressif's BluetoothSerial code, with a
2626 modified ESP_BT_GAP_ACL_CONN_CMPL_STAT_EVT event.
5050 (SDP is disabled in the standard libbt.a)
5151 It also corrects the UUID byte-order reversal in add_raw_sdp
5252
53- Tried and tested on: Arduino esp32 v3.0.7 (IDF 5.1)
53+ Written for and tested on: Arduino esp32 v3.0.7 (IDF 5.1)
5454
5555*/
5656
57+ // ===================================================================================================================
58+ // ESP32 Service Discovery Protocol
59+
60+ #include " esp_sdp_api.h"
61+
5762// ===================================================================================================================
5863// GNSS
5964
6065#include < SparkFun_u-blox_GNSS_v3.h> // Click here to get the library: http://librarymanager/All#SparkFun_u-blox_GNSS_v3
6166SFE_UBLOX_GNSS myGNSS;
6267char latestGPGGA[SFE_UBLOX_MAX_NMEA_BYTE_COUNT] = { 0 };
6368char latestGPRMC[SFE_UBLOX_MAX_NMEA_BYTE_COUNT] = { 0 };
64- char latestGPGST[SFE_UBLOX_MAX_NMEA_BYTE_COUNT] = { 0 };
69+ const size_t additionalNmeaBufferSize = 4096 ;
70+ char additionalNmeaBuffer[additionalNmeaBufferSize] = { 0 };
6571
6672void newGPGGA (NMEA_GGA_data_t *nmeaData)
6773{
@@ -83,25 +89,15 @@ void newGPRMC(NMEA_RMC_data_t *nmeaData)
8389 }
8490}
8591
86- void newGPGST (NMEA_GST_data_t *nmeaData)
87- {
88- if (nmeaData->length < SFE_UBLOX_MAX_NMEA_BYTE_COUNT)
89- {
90- strncpy (latestGPGST, (const char *)nmeaData->nmea , nmeaData->length );
91- // Serial.print(latestGPGST); // .nmea is printable (NULL-terminated) and already has \r\n on the end
92- latestGPGST[nmeaData->length - 2 ] = 0 ; // Truncate after checksum. Remove CR LF
93- }
94- }
95-
9692// ===================================================================================================================
9793// Apple Accessory
9894
9995#include < SparkFun_Apple_Accessory.h>
10096
10197SparkFunAppleAccessoryDriver appleAccessory;
10298
103- const char *accessoryName = " SparkFun MFi Test " ;
104- const char *modelIdentifier = " SparkFun MFi Test " ;
99+ const char *accessoryName = " SparkPNT RTK Flex " ;
100+ const char *modelIdentifier = " SparkPNT RTK Flex " ;
105101const char *manufacturer = " SparkFun Electronics" ;
106102const char *serialNumber = " 123456" ;
107103const char *firmwareVersion = " 1.0.0" ;
@@ -117,10 +113,10 @@ const char *productPlanUID = "0123456789ABCDEF"; // This comes from the MFi Port
117113#include " src/BluetoothSerial/BluetoothSerial.h" // Local copy for modifying ESP_BT_GAP_ACL_CONN_CMPL_STAT_EVT event
118114BluetoothSerial SerialBT;
119115
120- #include " esp_sdp_api.h"
121-
122116const char *sdp_service_name = " iAP2" ;
123117
118+ const int rfcommChanneliAP2 = 2 ; // We can use RFCOMM channel 2 for iAP2
119+
124120// With the byte-order correction in libbt.a btc_sdp.c add_raw_sdp() (ARRAY_TO_BE_STREAM_REVERSE replaces ARRAY_TO_BE_STREAM),
125121// we need to provide the UUID as little-endian:
126122// UUID : Little-Endian
@@ -138,6 +134,52 @@ void transportDisconnect(bool *disconnected)
138134 *disconnected = SerialBT.disconnect ();
139135}
140136
137+ // ===================================================================================================================
138+ // Callback for Service Discovery Protocol
139+ // This allows the iAP2 record to be created _after_ SDP is initialized
140+ // and the Serial Port (SPP) record has been created
141+
142+ volatile bool sdpCreateRecordEvent = false ; // Flag to indicate when the iAP2 record has been created
143+
144+ static void esp_sdp_callback (esp_sdp_cb_event_t event, esp_sdp_cb_param_t *param)
145+ {
146+ switch (event) {
147+ case ESP_SDP_INIT_EVT:
148+ Serial.printf (" ESP_SDP_INIT_EVT: status: %d\r\n " , param->init .status );
149+ if (param->init .status == ESP_SDP_SUCCESS) {
150+ // SDP has been initialized. _Now_ we can create the iAP2 record!
151+ esp_bluetooth_sdp_hdr_overlay_t record = {(esp_bluetooth_sdp_types_t )0 };
152+ record.type = ESP_SDP_TYPE_RAW;
153+ record.uuid .len = sizeof (UUID_IAP2);
154+ memcpy (record.uuid .uuid .uuid128 , UUID_IAP2, sizeof (UUID_IAP2));
155+ // The service_name isn't critical. But we can't not set one.
156+ // (If we don't set a name, the record doesn't get created.)
157+ record.service_name_length = strlen (sdp_service_name) + 1 ;
158+ record.service_name = (char *)sdp_service_name;
159+ record.rfcomm_channel_number = rfcommChanneliAP2; // RFCOMM channel
160+ record.l2cap_psm = -1 ;
161+ record.profile_version = -1 ;
162+ esp_sdp_create_record ((esp_bluetooth_sdp_record_t *)&record);
163+ }
164+ break ;
165+ case ESP_SDP_DEINIT_EVT:
166+ Serial.printf (" ESP_SDP_DEINIT_EVT: status: %d\r\n " , param->deinit .status );
167+ break ;
168+ case ESP_SDP_SEARCH_COMP_EVT:
169+ Serial.printf (" ESP_SDP_SEARCH_COMP_EVT: status: %d\r\n " , param->search .status );
170+ break ;
171+ case ESP_SDP_CREATE_RECORD_COMP_EVT:
172+ Serial.printf (" ESP_SDP_CREATE_RECORD_COMP_EVT: status: %d\r\n " , param->create_record .status );
173+ sdpCreateRecordEvent = true ; // Flag that the iAP2 record has been created
174+ break ;
175+ case ESP_SDP_REMOVE_RECORD_COMP_EVT:
176+ Serial.printf (" ESP_SDP_REMOVE_RECORD_COMP_EVT: status: %d\r\n " , param->remove_record .status );
177+ break ;
178+ default :
179+ break ;
180+ }
181+ }
182+
141183// ===================================================================================================================
142184
143185void setup ()
@@ -150,9 +192,10 @@ void setup()
150192 // ==============================================================================================================
151193 // Setup Bluetooth
152194
153- SerialBT.enableSSP (false , true ); // Enable secure pairing with PIN
195+ SerialBT.enableSSP (false , false ); // Enable secure pairing
154196
155- SerialBT.begin (accessoryName, true , true ); // Bluetooth device name, start in master mode, disable BLE
197+ // Bluetooth device name, master mode, disable BLE, rxQueueSize, txQueueSize
198+ SerialBT.begin (accessoryName, false , true , 2048 , 512 );
156199
157200 Serial.println (" The BT device was started." );
158201
@@ -166,16 +209,10 @@ void setup()
166209 // (But, deleting them is essential if you have changed the secure pairing mode)
167210 Serial.println (" Deleting all previous bonded devices." );
168211 SerialBT.deleteAllBondedDevices (); // Must be called after begin
169-
170- esp_sdp_init ();
171212
172- esp_bluetooth_sdp_hdr_overlay_t record = {(esp_bluetooth_sdp_types_t )0 };
173- record.type = ESP_SDP_TYPE_RAW;
174- record.uuid .len = sizeof (UUID_IAP2);
175- memcpy (record.uuid .uuid .uuid128 , UUID_IAP2, sizeof (UUID_IAP2));
176- record.service_name_length = strlen (accessoryName) + 1 ;
177- record.service_name = (char *)accessoryName;
178- esp_sdp_create_record ((esp_bluetooth_sdp_record_t *)&record);
213+ // The SDP callback will create the iAP2 record
214+ esp_sdp_register_callback (esp_sdp_callback);
215+ esp_sdp_init ();
179216
180217 // ==============================================================================================================
181218 // Setup Apple Accessory and Authentication Coprocessor
@@ -204,8 +241,13 @@ void setup()
204241 appleAccessory.setLocationInfoComponentName (LIComponentName);
205242 appleAccessory.setProductPlanUID (productPlanUID);
206243
207- // Pass the pointers for the latest NMEA data into the Accessory driver
208- appleAccessory.setNMEApointers (latestGPGGA, latestGPRMC, latestGPGST);
244+ // Pass the pointers for the latest NMEA GGA and RMC data into the Accessory driver
245+ // GGA and RMC are passed to apps over the iAP2 Control Session
246+ appleAccessory.setNMEApointers (latestGPGGA, latestGPRMC);
247+
248+ // Pass the pointer for additional NMEA GST, VTG, GSA and GSV data
249+ // GGA, RMC, GST, VTG, GSA and GSV are passed to apps over an iAP2 EA Session
250+ appleAccessory.setEASessionPointer (additionalNmeaBuffer);
209251
210252 // Pass the transport connected and disconnect methods into the accessory driver
211253 appleAccessory.setTransportConnectedMethod (&transportConnected);
@@ -216,6 +258,9 @@ void setup()
216258
217259 // myGNSS.enableDebugging(); // Uncomment this line to enable debug messages on Serial
218260
261+ // Storage for NMEA GST, VTG, GSA and GSV. setFileBufferSize must be called _before_ .begin
262+ myGNSS.setFileBufferSize (additionalNmeaBufferSize);
263+
219264 while (!myGNSS.begin ())
220265 {
221266 Serial.println (F (" u-blox GNSS not detected at default I2C address. Please check wiring." ));
@@ -224,11 +269,11 @@ void setup()
224269 // Disable or enable various NMEA sentences over the I2C interface
225270 myGNSS.setI2COutput (COM_TYPE_NMEA | COM_TYPE_UBX); // Turn on both UBX and NMEA sentences on I2C. (Turn off RTCM and SPARTN)
226271 myGNSS.newCfgValset (VAL_LAYER_RAM_BBR); // Use cfgValset to disable / enable individual NMEA messages
227- myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_GLL_I2C, 0 ); // Disable all NMEA messages except GGA and RMC. Enable GST
228- myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_GSA_I2C, 0 );
229- myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_GSV_I2C, 0 );
272+ myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_GLL_I2C, 0 ); // Enable required NMEA messages
273+ myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_GSA_I2C, 1 );
274+ myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_GSV_I2C, 1 );
230275 myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_RMC_I2C, 1 );
231- myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_VTG_I2C, 0 );
276+ myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_VTG_I2C, 1 );
232277 myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_GGA_I2C, 1 );
233278 myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_ZDA_I2C, 0 );
234279 myGNSS.addCfgValset (UBLOX_CFG_MSGOUT_NMEA_ID_GST_I2C, 1 );
@@ -250,9 +295,9 @@ void setup()
250295 // Set up the callback for GPRMC
251296 myGNSS.setNMEAGPRMCcallbackPtr (&newGPRMC);
252297
253- // Set up the callback for GPGST
254- myGNSS.setNMEAGPGSTcallbackPtr (&newGPGST);
255-
298+ // Log NMEA GST, VTG, GSA and GSV in the GNSS library file buffer
299+ myGNSS.setNMEALoggingMask (SFE_UBLOX_FILTER_NMEA_GST | SFE_UBLOX_FILTER_NMEA_VTG
300+ | SFE_UBLOX_FILTER_NMEA_GSA | SFE_UBLOX_FILTER_NMEA_GSV);
256301}
257302
258303// ===================================================================================================================
@@ -271,20 +316,62 @@ void loop()
271316 myGNSS.checkCallbacks (); // Check if any callbacks are waiting to be processed.
272317
273318 // ==============================================================================================================
274- // Check for a new device connection
319+ // Copy latest GST, VTG, GSA and GSV into additionalNmeaBuffer
275320
276- if (SerialBT.aclConnected () == true )
321+ // If the Apple Accessory is not sending data to the EA Session, copy the data into additionalNmeaBuffer.
322+ // Bad things would happen if we were to manipulate additionalNmeaBuffer while appleAccessory is using it.
323+ if (appleAccessory.latestEASessionDataIsBlocking () == false )
277324 {
278- Serial.println (" Apple Device found, connecting..." );
325+ size_t spaceAvailable = additionalNmeaBufferSize - strlen (additionalNmeaBuffer);
326+ if (spaceAvailable >= 1 )
327+ spaceAvailable -= 1 ; // Leave room for the NULL
328+ uint16_t fileBufferAvailable = myGNSS.fileBufferAvailable (); // Check how much data is available
329+ while (spaceAvailable < fileBufferAvailable) // If the buffer is full, delete the oldest message(s)
330+ {
331+ const char *lfPtr = strstr (additionalNmeaBuffer, " \n " ); // Find the first LF
332+ if (lfPtr == nullptr )
333+ break ; // Something has gone badly wrong...
334+ lfPtr++; // Point at the byte after the LF
335+ size_t oldLen = lfPtr - additionalNmeaBuffer; // This much data is old
336+ size_t newLen = strlen (additionalNmeaBuffer) - oldLen; // This much is new (not old)
337+ for (size_t i = 0 ; i <= newLen; i++) // Move the new data over the old. Include the NULL
338+ additionalNmeaBuffer[i] = additionalNmeaBuffer[oldLen + i];
339+ spaceAvailable += oldLen;
340+ }
341+ size_t dataLen = strlen (additionalNmeaBuffer);
342+ myGNSS.extractFileBufferData ((uint8_t *)&additionalNmeaBuffer[dataLen], fileBufferAvailable); // Add the new NMEA data
343+ dataLen += fileBufferAvailable;
344+ additionalNmeaBuffer[dataLen] = 0 ; // NULL terminate
345+ }
279346
280- delay (2 );
347+ // ==============================================================================================================
348+ // Check if the iAP2 SDP record has been created
349+ // If it has, restart the SPP Server
281350
282- SerialBT.connect (SerialBT.aclGetAddress (), 1 ); // Connect on channel 1
351+ if (sdpCreateRecordEvent)
352+ {
353+ sdpCreateRecordEvent = false ;
354+ esp_spp_stop_srv ();
355+ esp_spp_start_srv (ESP_SPP_SEC_NONE, ESP_SPP_ROLE_SLAVE, rfcommChanneliAP2, " ESP32SPP2" );
356+ }
357+
358+ // ==============================================================================================================
359+ // Check for a new device connection
360+
361+ if (SerialBT.aclConnected () == true )
362+ {
363+ Serial.println (" Apple Device found. Waiting for connection..." );
283364
284- if (SerialBT.connected ())
365+ unsigned long connectStart = millis ();
366+ while ((millis () - connectStart) < 5000 )
285367 {
286- Serial.println (" Sending handshake..." );
287- appleAccessory.startHandshake (&SerialBT);
368+ if (SerialBT.connected ())
369+ {
370+ Serial.println (" Device connected. Sending handshake..." );
371+ appleAccessory.startHandshake (&SerialBT);
372+ break ;
373+ }
374+ delay (10 );
288375 }
289376 }
290377
0 commit comments