1

I am currently trying to manipulate a multimeter (Zoyi ZT-5BQ) using an Arduino board, but I don't know the protocol that uses my multimeter to change the reading mode (in example, from ohmeter to termometer). I have tried to pair my AT-09 module to the manufacturer's app (Bluetooth DMM) and see what does it sends when I try to change the reading mode from the smartphone, but AT-09 is not detected by the phone, and I guess it's due to the MAC address of my module. Is there any way I can sniff between the communication of my smarthphone and multimeter?

Thanks in advance!

2 Answers2

2

The manufacturers' app can't find the AT-09 most likely because the app searches for devices that advertise a specific service that the module does not offer.

Start your research by installing a generic BLE scanner app such as nRF Connect. Connect to your multimeter and look at the services and characteristics it discovers. Try reading and/or writing from/to them, sometimes this is enough to figure out the easier protocolls.

nRF Connect also offers to debug the connection if nRF Connetc is open in the background and you connect to your device using the manufacturers' app. This can already give some insights to the messages sent and received.

The last resort would be to use a real BLE sniffer. There are multiple options, I personally have great experience using the one from Nordic Semiconductor. You would need a bit of hardware and can use Wireshark with an extension to see everything. The cheapest option for the hardware would be the nRF52840-Dongle.

Michael Kotzjan
  • 2,093
  • 2
  • 14
  • 23
2

Finally, I found a solution thanks to Michael Kotzjan. I searched about sniffing and native ways to do it from my phone, and I found the "enable Bluetooth HCI Snoop solution". It was not trivial, at least not for a MIUI based smartphone because I had to enable that option in developer's options and find a way to see those logs, in my case I had to reboot my Redmi Note 8 Pro and then I went to "Xiaomi Services and Feedback" app, where I enabled the logs of the phone's bluetooth, then I started a communication between manufacturer's app (Bluetooth DMM) and my multimeter (Zoyi ZT-5BQ), after that I had to find the folder where logs are stored, in my case: debuglogger->connyslog->bthci->CsLog_2022...

I downloaded the folder to my PC and finally using Wireshark I was able to see the commands that I sent from the destination (Redmi Note...) to my source (Shenzen__88...). Commands shown in Wireshark

This way I was able to note the commands down, and I made this list: List of commands

Using NRF Connect app, I was able to test the commands, updating the 0xFFF4 characteristic of the multimeter through BLE technology.

Finally, I made this code in Arduino to connect my ESP32 to the multimeter and test the commands, and I can read and write the 0xFFF4 characteristic under 0xFFF0 service.

    /**
   A BLE client example that is rich in capabilities.
   There is a lot new capabilities implemented.
   author unknown
   updated by chegewara
*/

#include "BLEDevice.h"
//#include "BLEScan.h"

//String serverUUID = "FC:58:FA:88:56:59";
// The remote service we wish to connect to.
static BLEUUID serviceUUID("fff0");
// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID("fff4");

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;
//BLEAddress address(serverUUID.c_str());

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
  Serial.print("Notify callback for characteristic ");
  Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
  Serial.print(" of data length ");
  Serial.println(length);
  Serial.print("data: ");
  Serial.println((char*)pData);
}

class MyClientCallback : public BLEClientCallbacks {
    void onConnect(BLEClient* pclient) {
    }

    void onDisconnect(BLEClient* pclient) {
      connected = false;
      Serial.println("onDisconnect");
    }
};

bool connectToServer() {
  Serial.print("Forming a connection to ");
  Serial.println(myDevice->getAddress().toString().c_str());

  BLEClient*  pClient  = BLEDevice::createClient();
  Serial.println(" - Created client");

  pClient->setClientCallbacks(new MyClientCallback());

  // Connect to the remove BLE Server.
  pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
  Serial.println(" - Connected to server");
  pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)

  // Obtain a reference to the service we are after in the remote BLE server.
  BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
  if (pRemoteService == nullptr) {
    Serial.print("Failed to find our service UUID: ");
    Serial.println(serviceUUID.toString().c_str());
    pClient->disconnect();
    return false;
  }
  Serial.println(" - Found our service");


  // Obtain a reference to the characteristic in the service of the remote BLE server.
  pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
  if (pRemoteCharacteristic == nullptr) {
    Serial.print("Failed to find our characteristic UUID: ");
    Serial.println(charUUID.toString().c_str());
    pClient->disconnect();
    return false;
  }
  Serial.println(" - Found our characteristic");

  // Read the value of the characteristic.
  if (pRemoteCharacteristic->canRead()) {
    std::string value = pRemoteCharacteristic->readValue();
    Serial.print("The characteristic value was: ");
    Serial.println(value.c_str());
  }

  if (pRemoteCharacteristic->canNotify())
    pRemoteCharacteristic->registerForNotify(notifyCallback);

  connected = true;
  return true;
}
/**
   Scan for BLE servers and find the first one that advertises the service we are looking for.
*/
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
    /**
        Called for each advertising BLE server.
    */
    void onResult(BLEAdvertisedDevice advertisedDevice) {
      Serial.print("BLE Advertised Device found: ");
      Serial.println(advertisedDevice.toString().c_str());

      // We have found a device, let us now see if it contains the service we are looking for.
      if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

        BLEDevice::getScan()->stop();
        myDevice = new BLEAdvertisedDevice(advertisedDevice);
        doConnect = true;
        doScan = true;

      } // Found our server
    } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothing more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {
    String newValue = "Time since boot: " + String(millis() / 1000);
    Serial.println("Setting new characteristic value to \"" + newValue + "\"");

    // Set the characteristic's value to be the array of bytes that is actually a string.
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());

    byte Command[2][10] = {
      {0xea, 0xec, 0x70, 0xe3, 0xa2, 0xc1, 0x32, 0x71, 0x64, 0x9b}, // Celsius
      {0xea, 0xec, 0x70, 0xe2, 0xa2, 0xc1, 0x32, 0x71, 0x64, 0x98}  // Fahr
    };

    Serial.println("Celsius");
    pRemoteCharacteristic->writeValue(Command[0], sizeof(colors[0]));
    delay(2000);

    Serial.println("Fahr");
    pRemoteCharacteristic->writeValue(Command[1], sizeof(colors[1]));
    delay(2000);

  } else if (doScan) {
    BLEDevice::getScan()->start(0);  // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
  }

  delay(1000); // Delay a second between loops.
} // End of loop