Wieder über BLE-, Temperatur- und Xiaomi-Sensoren

Vor nicht allzu langer Zeit habe ich es geschafft, die berühmten Xiaomi Temperatur- und Feuchtigkeitssensoren zu bekommen. Diese Sensoren haben zu Recht große Popularität erlangt, da sie zu ihrem relativ niedrigen Preis sehr bequem zu verwenden sind und auch wissen, wie sie ihre Messwerte über das BLE-Protokoll an dasselbe Mi Home übertragen können. Darüber hinaus ist das gesamte Internet mit Optionen zum Anschließen dieser Sensoren an Home Assistant , MajorDoMo und andere Systeme übersät .


Aber es schien mir nicht genug und ich wollte alles auf meine eigene Weise tun (frag mich nicht warum und warum, ich wollte nur). Ich wollte nämlich Daten von Sensoren lesen, die im Haus hängen und irgendwie interessant sind, mit ihnen zu arbeiten. Deshalb habe ich in meinen elektronischen Behältern gestöbert und dort das ESP32-Modul gefunden.



Ein kurzer Blick auf Google zeigte: ESP32 ist das, was ich brauche. Es weiß, wie man Bluetooth und WiFi verwendet, ist über die Arduino IDE programmiert und ermöglicht es mir, Messwerte vom Sensor abzurufen und über WiFi zu senden, wo immer ich sie benötige (zumindest an den Heimserver, sogar an die Cloud). Außerdem wurde ein sehr schnelles und einfaches Tutorial gefunden , das gerade mein Problem gelöst hat. Aber wie sich herausstellte, ist nicht alles so einfach ...


Erste Probleme


, . … , .


, ESP32, . ( , ) . , . , BLE . , , - .



BLE 2- . (discover mode) (connection mode). , Bluetooth . . - . , .


Xiaomi , . . . , .


- ?


. , (advertising ).


void initBluetooth()
{
    BLEDevice::init("");
    pBLEScan = BLEDevice::getScan(); //create new scan
    pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
    pBLEScan->setActiveScan(true); //active scan uses more power, but get results faster
    pBLEScan->setInterval(0x50);
    pBLEScan->setWindow(0x30);
}

:


 void onResult(BLEAdvertisedDevice advertisedDevice)
    {
        if (advertisedDevice.haveName() && advertisedDevice.haveServiceData() && !advertisedDevice.getName().compare("MJ_HT_V1")) {
            std::string strServiceData = advertisedDevice.getServiceData();
            uint8_t cServiceData[100];
            char charServiceData[100];

            strServiceData.copy((char *)cServiceData, strServiceData.length(), 0);

            Serial.printf("\n\nAdvertised Device: %s\n", advertisedDevice.toString().c_str());

            for (int i=0;i<strServiceData.length();i++) {
                sprintf(&charServiceData[i*2], "%02x", cServiceData[i]);
            }

            std::stringstream ss;
            ss << "fe95" << charServiceData;

            Serial.print("Payload:");
            Serial.println(ss.str().c_str());

            char eventLog[256];
            unsigned long value, value2;
            char charValue[5] = {0,};
            switch (cServiceData[11]) {
                case 0x04:
                    sprintf(charValue, "%02X%02X", cServiceData[15], cServiceData[14]);
                    value = strtol(charValue, 0, 16);
                    if(METRIC)
                    {
                      current_temperature = (float)value/10;
                    }else
                    {
                      current_temperature = CelciusToFahrenheit((float)value/10);
                    }
                    displayTemperature();  
                    break;
                case 0x06:
                    sprintf(charValue, "%02X%02X", cServiceData[15], cServiceData[14]);
                    value = strtol(charValue, 0, 16);  
                    current_humidity = (float)value/10;
                    displayHumidity();                      
                    Serial.printf("HUMIDITY_EVENT: %s, %d\n", charValue, value);
                    break;
                case 0x0A:
                    sprintf(charValue, "%02X", cServiceData[14]);
                    value = strtol(charValue, 0, 16);                    
                    Serial.printf("BATTERY_EVENT: %s, %d\n", charValue, value);
                    break;
                case 0x0D:
                    sprintf(charValue, "%02X%02X", cServiceData[15], cServiceData[14]);
                    value = strtol(charValue, 0, 16);      
                    if(METRIC)
                    {
                      current_temperature = (float)value/10;
                    }else
                    {
                      current_temperature = CelciusToFahrenheit((float)value/10);
                    }
                    displayTemperature();               
                    Serial.printf("TEMPERATURE_EVENT: %s, %d\n", charValue, value);                    
                    sprintf(charValue, "%02X%02X", cServiceData[17], cServiceData[16]);
                    value2 = strtol(charValue, 0, 16);
                    current_humidity = (float)value2/10;
                    displayHumidity();                                        
                    Serial.printf("HUMIDITY_EVENT: %s, %d\n", charValue, value2);
                    break;
            }
        }
    }

, - .


switch, 11 service data . , 11 . .


advertising (payload). , , . . payload ( ):


020106121695fe5020aa01ab9f0231342d580a10014309094d4a5f48545f563105030f180a180916ffffc8b33f8a48db

. ( 0x02) . , ( ). . ( ) .


0x16, service data, .. , . 2:


121695fe5020aa01ab9f0231342d580a100143
0916ffffc8b33f8a48db

, , 11 , , switch (0x0A). , , . . , , .


?


- , , , . ESP32 Arduino. , , getServiceData , . .. , payload service data. , . , ( 1.0.4). Arduino IDE ESP32 Boards Manager . getServiceData() service data. , . , .



. . , , . , payload service data ( findServiceData).


class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {

    uint8_t* findServiceData(uint8_t* data, size_t length, uint8_t* foundBlockLength) {
        //      [ ][ ][]
        //      0x16,     0x95 0xfe
        //   ,       
        //         
        uint8_t* rightBorder = data + length;
        while (data < rightBorder) {
            uint8_t blockLength = *data;
            if (blockLength < 5) { //      
                data += (blockLength+1);
                continue;
            }
            uint8_t blockType = *(data+1);
            uint16_t serviceType = *(uint16_t*)(data + 2);
            if (blockType == 0x16 && serviceType == 0xfe95) { //    
                *foundBlockLength = blockLength-3; //    
                return data+4; //     
            }
            data += (blockLength+1);
        }   
        return nullptr;
    }

    void onResult(BLEAdvertisedDevice advertisedDevice) {
        if (!advertisedDevice.haveName() || advertisedDevice.getName().compare("MJ_HT_V1"))
            return; //    ,     

        uint8_t* payload = advertisedDevice.getPayload();
        size_t payloadLength = advertisedDevice.getPayloadLength();
        Serial.printf("\n\nAdvertised Device: %s\n", advertisedDevice.toString().c_str());
        printBuffer(payload, payloadLength);
        uint8_t serviceDataLength=0;
        uint8_t* serviceData = findServiceData(payload, payloadLength, &serviceDataLength);

        if (serviceData == nullptr) {
            return; //      
        }

        Serial.printf("Found service data len: %d\n", serviceDataLength);
        printBuffer(serviceData, serviceDataLength);

        // 11      
        // 0x0D -   
        // 0x0A - 
        // 0x06 - 
        // 0x04 - 

        switch (serviceData[11])
        {
            case 0x0D:
            {
                float temp = *(uint16_t*)(serviceData + 11 + 3) / 10.0;
                float humidity = *(uint16_t*)(serviceData + 11 + 5) / 10.0;
                Serial.printf("Temp: %f Humidity: %f\n", temp, humidity);
            }
            break;
            case 0x04:
            {
                float temp = *(uint16_t*)(serviceData + 11 + 3) / 10.0;
                Serial.printf("Temp: %f\n", temp);
            }
            break;
            case 0x06:
            {
                float humidity = *(uint16_t*)(serviceData + 11 + 3) / 10.0;
                Serial.printf("Humidity: %f\n", humidity);
            }
            break;
            case 0x0A:
            {
                int battery = *(serviceData + 11 + 3);
                Serial.printf("Battery: %d\n", battery);
            }
            break;
        default:
            break;
        } 
    }
};


, . - ESP32 StackOverflow, . , . , -, , , . , .


- , , - . , , , .


.


UPD:


All Articles