Cannot scan for devices over BLE using Garmin simulator + nRF52840 DK and the sample NordicThingy52 app

Hello, I am using an nrf52840 DK and trying to interface with the Garmin simulator to scan for devices over BLE. I am using the sample NordicThingy52 app and onScanResults() seems to never get triggered (added a print statement but never printed). I followed the instructions here: https://forums.garmin.com/developer/connect-iq/w/wiki/19/getting-started-with-connect-iq-ble-development and do not get an error when setting the COM port that the dev kit is connected to in BLE settings. I am using mac OS 14. 

Would anyone have any suggestions? Thanks in advance!

  • the nrf52840 and  nrf52 DK are different things.  Which do you have?  Is anything in range you'd see with a scan?

    • Firmware for the nRF52 DK or nRF52840 Dongle - The memory layout of the development board will need to be flashed to a different firmware for use with the Connect IQ SDK: nRF52 DK firmware or nRF52840 Dongle firmware

    Did you flash the correct hex file?

  • Depending on where you have the println, it may only be looking for thingy52 device.  In the delegate, there is a function called "contains"

    Try putting the println there:

        private function contains( iter, obj ) {
            for( var uuid = iter.next(); uuid != null; uuid = iter.next() ) {
    System.println("uuid="+uuid);    // <-------- here               
                if( uuid.equals( obj ) ) {
                    return true;
                }
            }
            return false;
        }  

  • Hello, thank you very much for your responses! I am able to connect to a device now (ESP32 MCU) but when I request the device's Service or Characteristic, it returns null. Specifically, "onConnectedStateChanged()" is reached and after I check that CONNECTION_STATE_CONNECTED is enabled, I try to get the specific service and it returns null (UUIDs match - device has 1 service and 1 characteristic). I am attaching the code below that I have adjusted on the NordicThingy52 sample app. Please let me know if you have any suggestions or if you would like me to provide more information. Thanks again!

    // ThingyDelegate.mc
    
    import Toybox.Lang;
    import Toybox.BluetoothLowEnergy;
    import Toybox.System;
    
    class ThingyDelegate extends BluetoothLowEnergy.BleDelegate {
        private var _profileManager as ProfileManager;
    
        private var _onScanResult as WeakReference?;
        private var _onConnection as WeakReference?;
        private var _onDescriptorWrite as WeakReference?;
        private var _onCharChanged as WeakReference?;
    
        public function initialize(profileManager as ProfileManager) {
            BleDelegate.initialize();
            _profileManager = profileManager;
            _profileManager.registerProfiles();
            System.println("ThingyDelegate initialize");
        }
    
        public function onScanResults(scanResults as Iterator) as Void {
            var result = scanResults.next();
            while (result != null) {
                if (result instanceof ScanResult) {
                    var serviceUuids = result.getServiceUuids();
                    var i = 0;
                    var uuidString = serviceUuids.next();
                    while (uuidString != null) {
                        var uuid = BluetoothLowEnergy.stringToUuid(uuidString.toString());
                        // System.println("found: " + uuidString);
                        if (contains(uuid, _profileManager.THINGY_CONFIGURATION_SERVICE)) {
                            System.println("Service found!");
                            // broadcastScanResult(result);
                            BluetoothLowEnergy.setScanState(BluetoothLowEnergy.SCAN_STATE_OFF);
    					    BluetoothLowEnergy.pairDevice(result);
                            broadcastScanResult(result);
                            break;
                        }
                        i++;
                        uuidString = serviceUuids.next();
                    }
                }
                result = scanResults.next();
            }
        }
    
    
        public function onConnectedStateChanged(device, state as Toybox.BluetoothLowEnergy.ConnectionState) as Void {
            var onConnection = _onConnection;
            System.println("onConnectedStateChanged() device:" + device.getName());
    
    		if(state==Toybox.BluetoothLowEnergy.CONNECTION_STATE_CONNECTED) {
    
                System.println("connected -get data");	
                System.println(_profileManager.THINGY_CONFIGURATION_SERVICE);		
    		    var service = device.getService(stringToUuid(_profileManager.THINGY_CONFIGURATION_SERVICE));                
                if (service == null) {
                    System.println("service is NULL");
                } else {
                    System.println("service NOT NULL");
                }
                doReadChar(service,_profileManager.TEMPERATURE_CHARACTERISTIC);   	 
    		}
        }
    
        function doReadChar(service,thisOne) {       
    	    	var char = (service!=null) ? service.getCharacteristic(thisOne) : null;   	 	
    	    	if(char!=null) {
                System.println("do read for "+char.getUuid()); 	    	
    				try {    	   	 	
    					char.requestRead();
    				} catch(e) {
    					System.println("do read="+e.getErrorMessage());
    				}
    			}		
    	    	return char;
    	    }  
    
    
        function onCharacteristicRead(characteristic, status, value) {
    		System.println("BleDelegate - onCharacteristicRead() value:" + value);
    		characteristic.requestRead();
    	}
    
        public function onDescriptorWrite(descriptor as Descriptor, status as Status) as Void {
            var onDescriptorWrite = _onDescriptorWrite;
            if (null != onDescriptorWrite) {
                if (onDescriptorWrite.stillAlive()) {
                    (onDescriptorWrite.get() as EnvironmentProfileModel).onDescriptorWrite(descriptor, status);
                }
            }
        }
    
        public function onCharacteristicChanged(char as Characteristic, value as ByteArray) as Void {
            System.println("onCharacteristicChanged");
            var message = "";
            for (var i = 0; i < getSize(value); i++) {
                message += byteToChar(value[i]);
            }
            System.println("Received message: " + message);
        }
    
        private function byteToChar(byteValue as Number) as Char {
            switch(byteValue) {
                case 65: return 'A';
                case 66: return 'B';
                default: return '?';
            }
        }
    
        private function getSize(arr as ByteArray) as Number {
            var size = 0;
            while (arr[size] != 0) {
                size++;
            }
            return size;
        }
    
        public function notifyScanResult(model as ScanDataModel) as Void {
            _onScanResult = model.weak();
        }
    
        public function notifyConnection(model as DeviceDataModel) as Void {
            _onConnection = model.weak();
        }
    
        public function notifyDescriptorWrite(model as EnvironmentProfileModel) as Void {
            _onDescriptorWrite = model.weak();
        }
    
        public function notifyCharacteristicChanged(model as EnvironmentProfileModel) as Void {
            _onCharChanged = model.weak();
        }
    
        private function broadcastScanResult(scanResult as ScanResult) as Void {
            var onScanResult = _onScanResult;
            if (null != onScanResult) {
                if (onScanResult.stillAlive()) {
                    (onScanResult.get() as ScanDataModel).procScanResult(
                        scanResult
                    );
                }
            }
        }
    
        private function contains(uuid as Toybox.BluetoothLowEnergy.Uuid, targetUuidString as String) as Boolean {
        var targetUuid = BluetoothLowEnergy.stringToUuid(targetUuidString);
        return uuid.equals(targetUuid);
    }
    
    }

  • Things aren't learned over the connection with CIQ, so all the UUIDS must be defined when you register the profiles.  Also, you are limited to a max of 3 services.

  • Thanks for the quick response! I only have 1 service and I think I am registering the profiles correctly, which happens at the initialize() function of the class ThingyDelegate. Is this correct? Here is the ProfileManager class as well for your reference too. Not sure what I am doing wrong.

    // ProfileManager.mc
    
    import Toybox.BluetoothLowEnergy;
    
    class ProfileManager {
        public const TEMPERATURE_CHARACTERISTIC = "19B10001-E8F2-537E-4F6C-D104768A1214";
    
        public const THINGY_CONFIGURATION_SERVICE = "19B10000-E8F2-537E-4F6C-D104768A1214";
    
        private const _profileManager = {
            :uuid => BluetoothLowEnergy.stringToUuid(THINGY_ENVIRONMENTAL_SERVICE),
            :characteristics => [
                {
                    :uuid => BluetoothLowEnergy.stringToUuid(TEMPERATURE_CHARACTERISTIC),
                    :descriptors => [BluetoothLowEnergy.cccdUuid()],
                },
            ],
        };
    
        public function registerProfiles() as Void {
            BluetoothLowEnergy.registerProfile(_profileManager);
        }
    }
     

  • But do the UUIDS match what is on the sensor?  I wrote this up a few years about interfacing CIQ with a Raspberry Pi.  You may find something there

    https://forums.garmin.com/developer/connect-iq/b/news-announcements/posts/would-you-like-some-raspberry-pi-with-your-connect-iq

  • Thanks a lot, will check it out! Yes the UUIDs match, here is the Arduino code that I am using for the device I am connecting with. Not sure how I can connect successfully (simulator and the device connect) and get null for the service after that.

    #include <ArduinoBLE.h>
    
    BLEService echoService("19B10000-E8F2-537E-4F6C-D104768A1214");
    BLECharacteristic txCharacteristic("19B10001-E8F2-537E-4F6C-D104768A1214", BLERead | BLENotify, 20);
    
    int messageCount = 0;
    
    void setup() {
      Serial.begin(115200);
      while (!Serial);
    
      if (!BLE.begin()) {
        Serial.println("Starting BLE failed!");
        while (1);
      }
      
      BLE.setLocalName("EchoServer");
      BLE.setAdvertisedService(echoService);
      echoService.addCharacteristic(txCharacteristic);
      BLE.addService(echoService);
    
      txCharacteristic.setValue("Hello");
    
      BLE.advertise();
    
      Serial.println("BLE server is up and running!");
    }
    
    void loop() {
      BLEDevice central = BLE.central();
    
      if (central) {
        Serial.print("Connected to central: ");
        Serial.println(central.address());
    
        while (central.connected()) {
          // Continuously update the message with the new count
          String message = "Hello " + String(messageCount++);
          const char* messageToSend = message.c_str();
          txCharacteristic.writeValue(messageToSend);
    
          Serial.println("Sent message: " + message);
    
          // Delay to avoid flooding the central device with messages
          delay(1000);
        }
    
        Serial.print("Disconnected from central: ");
        Serial.println(central.address());
      }
    }
    
    
    
    

  • I tried running PiGD from what you sent, and I am facing the same issue. It seems like it connects to the device but I get the following in the debug console:

    not queued: null char for 19B10000-E8F2-537E-4F6C-D104768A1247 fun=1
    not queued: null char for 19B10000-E8F2-537E-4F6C-D104768A1247 fun=2
    not queued: null char for 19B10000-E8F2-537E-4F6C-D104768A1248 fun=1
    not queued: null char for 19B10000-E8F2-537E-4F6C-D104768A1248 fun=2



  • Thanks for sending the app, it works now just had to not include characteristics that don't exist for my device. One last question. Is there a possibility to somehow connect 2 devices to a Garmin over BLE?  

  • You can register at most 3 ble profiles.  You should be able to pair one while scanning for the second one and then pairing that.