Anrijs / Aranet4-ESP32

Aranet4 ESP32 client
MIT License
15 stars 1 forks source link

why app won't connect to my ESP32(as aranet device) #2

Closed KASSIMSAMJI closed 7 months ago

KASSIMSAMJI commented 1 year ago

Hello There

all these examples here

work with my ESP32 ( as server device )

but, when I try it on the app ( both Aranet Home and Aranet4 Display), the app scan returns No device Found

Any help is hugely appreciated

the code below runs on the ESP32 acting as Aranet4 Device

`

define ARANET4_MANUFACTURER_ID 0x0702

const char* deviceName = "Aranet4 00407";

include

static NimBLEServer* pServer;

NimBLEAdvertising* pAdvertising; BLEAdvertisementData advData;

NimBLECharacteristic pShortReadingsCharacteristic; NimBLECharacteristic pLongReadingsCharacteristic; NimBLECharacteristic pTotalReadingsCharacteristic; NimBLECharacteristic pReadIntervalCharacteristic;

define USE_ARANET4

// Service UUIDs //static NimBLEUUID UUID_Aranet4_Old ("f0cd1400-95da-4f4b-9ac8-aa55d312af0c"); static NimBLEUUID UUID_Aranet4 ("0000FCE0-0000-1000-8000-00805f9b34fb");

static NimBLEUUID UUID_Aranet_short_CurrentReadings ("f0cd1503-95da-4f4b-9ac8-aa55d312af0c"); static NimBLEUUID UUID_Aranet_long_CurrentReadings ("f0cd3001-95da-4f4b-9ac8-aa55d312af0c"); static NimBLEUUID UUID_Aranet_total_readings ("f0cd2001-95da-4f4b-9ac8-aa55d312af0c"); static NimBLEUUID UUID_Aranet_interval ("f0cd2002-95da-4f4b-9ac8-aa55d312af0c");

static NimBLEUUID generic_service_uuid ("00001800-0000-1000-8000-00805f9b34fb"); static NimBLEUUID device_name_char ("00002a00-0000-1000-8000-00805f9b34fb");

static NimBLEUUID common_service_uuid ("0000180a-0000-1000-8000-00805f9b34fb");

static NimBLEUUID batt_level_char ("00002a19-0000-1000-8000-00805f9b34fb"); static NimBLEUUID manufacturer_name_char ("00002a29-0000-1000-8000-00805f9b34fb"); static NimBLEUUID model_number_char ("00002a24-0000-1000-8000-00805f9b34fb"); static NimBLEUUID serial_number_char ("00002a25-0000-1000-8000-00805f9b34fb"); static NimBLEUUID hw_rev_char ("00002a27-0000-1000-8000-00805f9b34fb"); static NimBLEUUID sw_rev_char ("00002a28-0000-1000-8000-00805f9b34fb");

int batt = 0;

bool client_connected = false;

/ None of these are required as they will be handled by the library with defaults. Remove as you see fit for your needs / class ServerCallbacks: public NimBLEServerCallbacks { void onConnect(NimBLEServer pServer) { Serial.println("Client connected"); Serial.println("Multi-connect support: start advertising"); client_connected = true; NimBLEDevice::startAdvertising(); }; / Alternative onConnect() method to extract details of the connection. See: src/ble_gap.h for the details of the ble_gap_conn_desc struct. / void onConnect(NimBLEServer pServer, ble_gap_conn_desc* desc) { Serial.print("Client address: "); Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str()); /* We can use the connection handle here to ask for different connection parameters. Args: connection handle, min connection interval, max connection interval latency, supervision timeout. Units; Min/Max Intervals: 1.25 millisecond increments. Latency: number of intervals allowed to skip. Timeout: 10 millisecond increments, try for 5x interval time for best results. / pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60); }; void onDisconnect(NimBLEServer pServer) { Serial.println("Client disconnected - start advertising"); NimBLEDevice::startAdvertising(); }; void onMTUChange(uint16_t MTU, ble_gap_conn_desc desc) { Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle); };

uint32_t onPassKeyRequest() {
  Serial.println("Server Passkey Request");
  /** This should return a random 6 digit number for security
      or make your own static passkey as done here.
  */
  return 123456;
};

bool onConfirmPIN(uint32_t pass_key) {
  Serial.print("The passkey YES/NO number: "); Serial.println(pass_key);
  /** Return false if passkeys don't match. */
  return true;
};

void onAuthenticationComplete(ble_gap_conn_desc* desc) {
  /** Check that encryption was successful, if not we disconnect the client */
  if (!desc->sec_state.encrypted) {
    NimBLEDevice::getServer()->disconnect(desc->conn_handle);
    Serial.println("Encrypt connection failed - disconnecting client");
    return;
  }
  Serial.println("Starting BLE work!");
};

};

/* Handler class for characteristic actions / class CharacteristicCallbacks: public NimBLECharacteristicCallbacks { void onRead(NimBLECharacteristic* pCharacteristic) { Serial.print(pCharacteristic->getUUID().toString().c_str()); Serial.print(": onRead(), value: "); Serial.println(pCharacteristic->getValue().c_str()); };

void onWrite(NimBLECharacteristic* pCharacteristic) {
  Serial.print(pCharacteristic->getUUID().toString().c_str());
  Serial.print(": onWrite(), value: ");
  Serial.println(pCharacteristic->getValue().c_str());
};
/** Called before notification or indication is sent,
    the value can be changed here before sending if desired.
*/
void onNotify(NimBLECharacteristic* pCharacteristic) {
  Serial.println("Sending notification to clients");
};

/** The status returned in status is defined in NimBLECharacteristic.h.
    The value returned in code is the NimBLE host return code.
*/
void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) {
  String str = ("Notification/Indication status code: ");
  str += status;
  str += ", return code: ";
  str += code;
  str += ", ";
  str += NimBLEUtils::returnCodeToString(code);
  Serial.println(str);
};

void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) {
  String str = "Client ID: ";
  str += desc->conn_handle;
  str += " Address: ";
  str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
  if (subValue == 0) {
    str += " Unsubscribed to ";
  } else if (subValue == 1) {
    str += " Subscribed to notfications for ";
  } else if (subValue == 2) {
    str += " Subscribed to indications for ";
  } else if (subValue == 3) {
    str += " Subscribed to notifications and indications for ";
  }
  str += std::string(pCharacteristic->getUUID()).c_str();

  Serial.println(str);
};

};

/* Handler class for descriptor actions / class DescriptorCallbacks : public NimBLEDescriptorCallbacks { void onWrite(NimBLEDescriptor* pDescriptor) { std::string dscVal = pDescriptor->getValue(); Serial.print("Descriptor witten value:"); Serial.println(dscVal.c_str()); };

void onRead(NimBLEDescriptor* pDescriptor) {
  Serial.print(pDescriptor->getUUID().toString().c_str());
  Serial.println(" Descriptor read");
};

};

/* Define callback instances globally to use for multiple Charateristics \ Descriptors / static DescriptorCallbacks dscCallbacks; static CharacteristicCallbacks chrCallbacks;

void setup() { Serial.begin(115200); Serial.println("Starting NimBLE Server");

/* sets device name / NimBLEDevice::init(deviceName);

/* Optional: set the transmit power, default is 3db /

ifdef ESP_PLATFORM

NimBLEDevice::setPower(ESP_PWR_LVL_P9); /* +9db /

else

NimBLEDevice::setPower(9); /* +9db /

endif

/* Set the IO capabilities of the device, each option will trigger a different pairing method. BLE_HS_IO_DISPLAY_ONLY - Passkey pairing BLE_HS_IO_DISPLAY_YESNO - Numeric comparison pairing BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing / // NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); // use passkey // NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison

/** 2 different ways to set security - both calls achieve the same result. no bonding, no man in the middle protection, secure connections.

  These are the default values, only shown here for demonstration.

/ //NimBLEDevice::setSecurityAuth(false, false, true); // NimBLEDevice::setSecurityAuth(/BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);

pServer = NimBLEDevice::createServer(); pServer->setCallbacks(new ServerCallbacks());

// long readings

NimBLEService* pShortLongReadingsService = pServer->createService(UUID_Aranet4);

pLongReadingsCharacteristic = pShortLongReadingsService->createCharacteristic( UUID_Aranet_long_CurrentReadings, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY /* Require a secure connection for read and write access / // NIMBLE_PROPERTY::READ_ENC | // only allow reading if paired / encrypted // NIMBLE_PROPERTY::WRITE_ENC // only allow writing if paired / encrypted );

pLongReadingsCharacteristic->setCallbacks(&chrCallbacks);

// short readings

// NimBLEService* pShortReadingsService = pServer->createService(UUID_Aranet4); pShortReadingsCharacteristic = pShortLongReadingsService->createCharacteristic( UUID_Aranet_short_CurrentReadings, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY /* Require a secure connection for read and write access / // NIMBLE_PROPERTY::READ_ENC | // only allow reading if paired / encrypted // NIMBLE_PROPERTY::WRITE_ENC // only allow writing if paired / encrypted );

pShortReadingsCharacteristic->setCallbacks(&chrCallbacks);

// total readings

pTotalReadingsCharacteristic = pShortLongReadingsService->createCharacteristic( UUID_Aranet_total_readings, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY /* Require a secure connection for read and write access / // NIMBLE_PROPERTY::READ_ENC | // only allow reading if paired / encrypted // NIMBLE_PROPERTY::WRITE_ENC // only allow writing if paired / encrypted );

pTotalReadingsCharacteristic->setCallbacks(&chrCallbacks);

// read interval

pReadIntervalCharacteristic = pShortLongReadingsService->createCharacteristic( UUID_Aranet_interval, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY /* Require a secure connection for read and write access / // NIMBLE_PROPERTY::READ_ENC | // only allow reading if paired / encrypted // NIMBLE_PROPERTY::WRITE_ENC // only allow writing if paired / encrypted );

pReadIntervalCharacteristic->setCallbacks(&chrCallbacks);

/* 2904 descriptors are a special case, when createDescriptor is called with 0x2904 a NimBLE2904 class is created with the correct properties and sizes. However we must cast the returned reference to the correct type as the method only returns a pointer to the base NimBLEDescriptor class. / //// NimBLE2904 pBeef2904 = (NimBLE2904)pGetCurrentReadingsCharacteristic->createDescriptor("2904"); // pBeef2904->setFormat(NimBLE2904::FORMAT_UTF8); // pBeef2904->setCallbacks(&dscCallbacks);

// device name

NimBLEService pAllGenericService = pServer->createService(generic_service_uuid); NimBLECharacteristic pDeviceNameCharacteristic = pAllGenericService->createCharacteristic( device_name_char, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY );

pDeviceNameCharacteristic->setValue(deviceName); pDeviceNameCharacteristic->setCallbacks(&chrCallbacks);

// battery

NimBLEService* pAllCommonService = pServer->createService(common_service_uuid);

NimBLECharacteristic* pBattLevelCharacteristic = pAllCommonService->createCharacteristic( batt_level_char, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY );

pBattLevelCharacteristic->setValue(80); pBattLevelCharacteristic->setCallbacks(&chrCallbacks);

// manufacturer name NimBLECharacteristic* pManufacturerNameCharacteristic = pAllCommonService->createCharacteristic( manufacturer_name_char, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY );

pManufacturerNameCharacteristic->setValue("Aranet"); pManufacturerNameCharacteristic->setCallbacks(&chrCallbacks);

// model number NimBLECharacteristic* pModelNumberCharacteristic = pAllCommonService->createCharacteristic( model_number_char, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY );

pModelNumberCharacteristic->setValue("KSM-SAM"); pModelNumberCharacteristic->setCallbacks(&chrCallbacks);

// serial number NimBLECharacteristic* pSerialNumberCharacteristic = pAllCommonService->createCharacteristic( serial_number_char, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY );

pSerialNumberCharacteristic->setValue("1234567"); pSerialNumberCharacteristic->setCallbacks(&chrCallbacks);

// hardware revision NimBLECharacteristic* pHWRevCharacteristic = pAllCommonService->createCharacteristic( hw_rev_char, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY );

pHWRevCharacteristic->setValue("2234567"); pHWRevCharacteristic->setCallbacks(&chrCallbacks);

// software revision NimBLECharacteristic* pSWRevCharacteristic = pAllCommonService->createCharacteristic( sw_rev_char, NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::NOTIFY );

pSWRevCharacteristic->setValue("9934567"); pSWRevCharacteristic->setCallbacks(&chrCallbacks);

/* Note a 0x2902 descriptor MUST NOT be created as NimBLE will create one automatically if notification or indication properties are assigned to a characteristic. /

/* Custom descriptor: Arguments are UUID, Properties, max length in bytes of the value / / NimBLEDescriptor pC01Ddsc = pDeviceNameCharacteristic->createDescriptor( "C01D", NIMBLE_PROPERTY::READ | NIMBLE_PROPERTY::WRITE | NIMBLE_PROPERTY::WRITE_ENC, // only allow writing if paired / encrypted 20 ); pC01Ddsc->setValue("Send it back!"); pC01Ddsc->setCallbacks(&dscCallbacks); */

/* Start the services when finished creating all Characteristics and Descriptors /

pShortLongReadingsService->start(); // pShortReadingsService->start();

pAllGenericService->start(); pAllCommonService->start();

pAdvertising = NimBLEDevice::getAdvertising(); / Add the services to the advertisment data /

pAdvertising->addServiceUUID(pShortLongReadingsService->getUUID()); // pAdvertising->addServiceUUID(pShortReadingsService->getUUID());

pAdvertising->addServiceUUID(pAllGenericService->getUUID()); pAdvertising->addServiceUUID(pAllCommonService->getUUID()); /* If your device is battery powered you may consider setting scan response to false as it will extend battery life at the expense of less data sent. / // setManData(); // pAdvertising->setName("Aranet 01789"); advData.setName(deviceName); pAdvertising->setAdvertisementData(advData); // pAdvertising->setScanResponseData(advData);

// pAdvertising->setScanResponse(true); pAdvertising->start();

Serial.println("Advertising Started"); }

void update_manufacturer_data(uint8t *data, sizet size) {

}

void loop() { //batt = random(0, 99);

static uint32_t prev = millis(); if ( millis() - prev >= 2000 ) { prev = millis(); setManData(); ShortReadingsNotify(); LongReadingsNotify(); totalReadingsNotify(); }

}

//function takes String and adds manufacturer code at the beginning

void setManData() { Serial.println("Setting Manufactured Data");

struct kassim_data {

uint16_t manufacturer_id = ARANET4_MANUFACTURER_ID;
uint8_t disconnected : 1,
        __unknown1   : 1,
        calib_state  : 8,
        dfu_mode     : 8,
        integrations : 1,
        __unknown2   : 2;

uint8_t patch = 3;
uint8_t minor = 4;
uint16_t major = 128;  // 2432

uint8_t hw_rev = 99;
uint8_t __unknown = 77;
uint8_t packing = 21;

uint8_t packing2 = 28;
uint16_t co2 = 21;
uint16_t temperature = 22;
uint16_t pressure = 23;
uint16_t humidity = 287;
uint8_t  battery = batt;
uint8_t status_ = 27;
uint16_t interval_ = 6;
uint16_t ago = 5;

} attribute((packed)) device_data;

// this is was it attribute((packed))

BLEAdvertisementData scan_response;

std::string manufacturerData((char *)&device_data, sizeof(device_data));

scan_response.setManufacturerData(manufacturerData);

pAdvertising->stop(); pAdvertising->setScanResponseData(scan_response); pAdvertising->start();

}

void LongReadingsNotify() { Serial.println("Long notifying");

ifdef USE_ARANET4

struct kassim_2data { // this is for aranet4 uint16_t co2 = 91; uint16_t temperature = 22; uint16_t pressure = 23; uint8_t humidity = 12; uint8_t battery = 65; uint8t status = 2; uint16t interval = 6; uint16_t ago = 5; } attribute((packed)) device_2data;

else

struct kassim_2data { // this is for aranet2 uint16_t co2 = 91; uint16t interval = 6; uint16_t ago = 5; uint8_t battery = 65; uint16_t temperature = 22; uint16_t humidity = 12; uint8t status = 27; uint16_t pressure = 23; } attribute((packed)) device_2data;

endif

std::string sensor2Readings((char *)&device_2data, sizeof(device_2data)); pLongReadingsCharacteristic->setValue(sensor2Readings); pLongReadingsCharacteristic->notify(); }

void totalReadingsNotify() {

uint16_t total_reads = 78; pTotalReadingsCharacteristic->setValue(total_reads); pTotalReadingsCharacteristic->notify();

uint16_t interval_read = 80; pReadIntervalCharacteristic->setValue(interval_read); pReadIntervalCharacteristic->notify(); }

void ShortReadingsNotify() { Serial.println("short notifying");

ifdef USE_ARANET4

struct kassim_data { // this is for aranet4 uint16_t co2 = 91; uint16_t temperature = 22; uint16_t pressure = 23; uint8_t humidity = 12; uint8_t battery = 65; uint8t status = 2; } attribute((packed)) device_data;

else

struct kassim_data { // this is for aranet2 uint16_t co2 = 91; uint16t interval = 6; uint16_t ago = 5; uint8_t battery = 65; uint16_t temperature = 22; uint16_t humidity = 12; uint8t status = 27; uint16_t pressure = 23; } attribute((packed)) device_data;

endif

// attribute((packed)) device_data;

// 28,91,0,22,0,23,0,12,0,65,27,6,0,5,0,0,0,0,255,0, with packed

// 28,0,91,0,22,0,23,0,12,0,65,27,6,0,5,0,0,0,255,0,

std::string sensorReadings((char *)&device_data, sizeof(device_data)); pShortReadingsCharacteristic->setValue(sensorReadings); pShortReadingsCharacteristic->notify(); }

// end code here`

Anrijs commented 1 year ago

You also need to add Aranet service UUID to advertisement in void setManData().

scan_response.setCompleteServices(BLEUUID("FCE0"));

I also had to modify struct kassim_data to get it working. This should work:

void setManData() {
  Serial.println("Setting Manufactured Data");

  struct kassim_data {
    uint16_t manufacturer_id = ARANET4_MANUFACTURER_ID;

    uint8_t disconnected : 1,
            __unknown1   : 1,
            calib_state  : 2,
            dfu_mode     : 1,
            integrations : 1,
            __unknown2   : 2;

    uint8_t patch = 0x05;
    uint8_t minor = 0x03;
    uint16_t major = 0x01;  // v1.3.5

    uint8_t hw_rev =  5;
    uint8_t __unknown3;
    uint8_t packing;

    uint16_t co2 = 900;
    uint16_t temperature = 497;
    uint16_t pressure = 9983;
    uint8_t  humidity = 54;
    uint8_t  battery = 100;
    uint8_t status_ = 0x01;
    uint16_t interval_ = 60;
    uint16_t ago = 30;
  } __attribute__((packed)) device_data;

  // this is was it __attribute__((packed))

  BLEAdvertisementData scan_response;

  std::string manufacturerData((char *)&device_data, sizeof(device_data));

  scan_response.setManufacturerData(manufacturerData);
  scan_response.setCompleteServices(BLEUUID("FCE0"));

  pAdvertising->stop();
  pAdvertising->setScanResponseData(scan_response);
  pAdvertising->start();
}
KASSIMSAMJI commented 1 year ago

You also need to add Aranet service UUID to advertisement in void setManData().

scan_response.setCompleteServices(BLEUUID("FCE0"));

I also had to modify struct kassim_data to get it working. This should work:

void setManData() {
  Serial.println("Setting Manufactured Data");

  struct kassim_data {
    uint16_t manufacturer_id = ARANET4_MANUFACTURER_ID;

    uint8_t disconnected : 1,
            __unknown1   : 1,
            calib_state  : 2,
            dfu_mode     : 1,
            integrations : 1,
            __unknown2   : 2;

    uint8_t patch = 0x05;
    uint8_t minor = 0x03;
    uint16_t major = 0x01;  // v1.3.5

    uint8_t hw_rev =  5;
    uint8_t __unknown3;
    uint8_t packing;

    uint16_t co2 = 900;
    uint16_t temperature = 497;
    uint16_t pressure = 9983;
    uint8_t  humidity = 54;
    uint8_t  battery = 100;
    uint8_t status_ = 0x01;
    uint16_t interval_ = 60;
    uint16_t ago = 30;
  } __attribute__((packed)) device_data;

  // this is was it __attribute__((packed))

  BLEAdvertisementData scan_response;

  std::string manufacturerData((char *)&device_data, sizeof(device_data));

  scan_response.setManufacturerData(manufacturerData);
  scan_response.setCompleteServices(BLEUUID("FCE0"));

  pAdvertising->stop();
  pAdvertising->setScanResponseData(scan_response);
  pAdvertising->start();
}

Hi Anrijis, Thanks for your reply, I have made changes as per your suggestion

and now Aranet Home app (latest one from Play Store) can see my device, BUT after pairing here what I see in a Serial monitor

-> f0cd1503-95da-4f4b-9ac8-aa55d312af0c: onRead(), value: [ 10:51:40.463 -> f0cd2002-95da-4f4b-9ac8-aa55d312af0c: onRead(), value:

the app requests for the values belonging to these UUIDs here

f0cd1503-95da-4f4b-9ac8-aa55d312af0c f0cd2002-95da-4f4b-9ac8-aa55d312af0c

but the Serial monitor prints nothing for the values when ON READ

error

The values for the UUIDs are set (and notified) in a loop after every 2 seconds

see my updated code ( I had to post the code in raw form since It gets messed up If posted in tags )


#define ARANET4_MANUFACTURER_ID 0x0702

const char* deviceName = "Aranet4 00407";

#include <NimBLEDevice.h>

static NimBLEServer* pServer;

NimBLEAdvertising* pAdvertising;
BLEAdvertisementData advData;

NimBLECharacteristic* pShortReadingsCharacteristic;
NimBLECharacteristic* pLongReadingsCharacteristic;
NimBLECharacteristic* pTotalReadingsCharacteristic;
NimBLECharacteristic* pReadIntervalCharacteristic;

#define USE_ARANET4

// Service UUIDs
//static NimBLEUUID UUID_Aranet4_Old  ("f0cd1400-95da-4f4b-9ac8-aa55d312af0c");
static NimBLEUUID UUID_Aranet4      ("0000FCE0-0000-1000-8000-00805f9b34fb");

static NimBLEUUID UUID_Aranet_short_CurrentReadings     ("f0cd1503-95da-4f4b-9ac8-aa55d312af0c");
static NimBLEUUID UUID_Aranet_long_CurrentReadings      ("f0cd3001-95da-4f4b-9ac8-aa55d312af0c");
static NimBLEUUID UUID_Aranet_total_readings            ("f0cd2001-95da-4f4b-9ac8-aa55d312af0c");
static NimBLEUUID UUID_Aranet_interval                  ("f0cd2002-95da-4f4b-9ac8-aa55d312af0c");

static NimBLEUUID generic_service_uuid    ("00001800-0000-1000-8000-00805f9b34fb");
static NimBLEUUID device_name_char        ("00002a00-0000-1000-8000-00805f9b34fb");

static NimBLEUUID common_service_uuid     ("0000180a-0000-1000-8000-00805f9b34fb");

static NimBLEUUID batt_level_char          ("00002a19-0000-1000-8000-00805f9b34fb");
static NimBLEUUID manufacturer_name_char   ("00002a29-0000-1000-8000-00805f9b34fb");
static NimBLEUUID model_number_char        ("00002a24-0000-1000-8000-00805f9b34fb");
static NimBLEUUID serial_number_char       ("00002a25-0000-1000-8000-00805f9b34fb");
static NimBLEUUID hw_rev_char              ("00002a27-0000-1000-8000-00805f9b34fb");
static NimBLEUUID sw_rev_char              ("00002a28-0000-1000-8000-00805f9b34fb");

int batt = 0;

bool client_connected = false;

/**  None of these are required as they will be handled by the library with defaults. **
 **                       Remove as you see fit for your needs                        */
class ServerCallbacks: public NimBLEServerCallbacks {
    void onConnect(NimBLEServer* pServer) {
      Serial.println("Client connected");
      Serial.println("Multi-connect support: start advertising");
      client_connected = true;
      NimBLEDevice::startAdvertising();
    };
    /** Alternative onConnect() method to extract details of the connection.
        See: src/ble_gap.h for the details of the ble_gap_conn_desc struct.
    */
    void onConnect(NimBLEServer* pServer, ble_gap_conn_desc* desc) {
      Serial.print("Client address: ");
      Serial.println(NimBLEAddress(desc->peer_ota_addr).toString().c_str());
      /** We can use the connection handle here to ask for different connection parameters.
          Args: connection handle, min connection interval, max connection interval
          latency, supervision timeout.
          Units; Min/Max Intervals: 1.25 millisecond increments.
          Latency: number of intervals allowed to skip.
          Timeout: 10 millisecond increments, try for 5x interval time for best results.
      */
      pServer->updateConnParams(desc->conn_handle, 24, 48, 0, 60);
    };
    void onDisconnect(NimBLEServer* pServer) {
      Serial.println("Client disconnected - start advertising");
      NimBLEDevice::startAdvertising();
    };
    void onMTUChange(uint16_t MTU, ble_gap_conn_desc* desc) {
      Serial.printf("MTU updated: %u for connection ID: %u\n", MTU, desc->conn_handle);
    };

    uint32_t onPassKeyRequest() {
      Serial.println("Server Passkey Request");
      /** This should return a random 6 digit number for security
          or make your own static passkey as done here.
      */
      return 123456;
    };

    bool onConfirmPIN(uint32_t pass_key) {
      Serial.print("The passkey YES/NO number: "); Serial.println(pass_key);
      /** Return false if passkeys don't match. */
      return true;
    };

    void onAuthenticationComplete(ble_gap_conn_desc* desc) {
      /** Check that encryption was successful, if not we disconnect the client */
      if (!desc->sec_state.encrypted) {
        NimBLEDevice::getServer()->disconnect(desc->conn_handle);
        Serial.println("Encrypt connection failed - disconnecting client");
        return;
      }
      Serial.println("Starting BLE work!");
    };
};

/** Handler class for characteristic actions */
class CharacteristicCallbacks: public NimBLECharacteristicCallbacks {
    void onRead(NimBLECharacteristic* pCharacteristic) {
      Serial.print(pCharacteristic->getUUID().toString().c_str());
      Serial.print(": onRead(), value: ");
      Serial.println(pCharacteristic->getValue().c_str());
    };

    void onWrite(NimBLECharacteristic* pCharacteristic) {
      Serial.print(pCharacteristic->getUUID().toString().c_str());
      Serial.print(": onWrite(), value: ");
      Serial.println(pCharacteristic->getValue().c_str());
    };
    /** Called before notification or indication is sent,
        the value can be changed here before sending if desired.
    */
    void onNotify(NimBLECharacteristic* pCharacteristic) {
      Serial.println("Sending notification to clients");
    };

    /** The status returned in status is defined in NimBLECharacteristic.h.
        The value returned in code is the NimBLE host return code.
    */
    void onStatus(NimBLECharacteristic* pCharacteristic, Status status, int code) {
      String str = ("Notification/Indication status code: ");
      str += status;
      str += ", return code: ";
      str += code;
      str += ", ";
      str += NimBLEUtils::returnCodeToString(code);
      Serial.println(str);
    };

    void onSubscribe(NimBLECharacteristic* pCharacteristic, ble_gap_conn_desc* desc, uint16_t subValue) {
      String str = "Client ID: ";
      str += desc->conn_handle;
      str += " Address: ";
      str += std::string(NimBLEAddress(desc->peer_ota_addr)).c_str();
      if (subValue == 0) {
        str += " Unsubscribed to ";
      } else if (subValue == 1) {
        str += " Subscribed to notfications for ";
      } else if (subValue == 2) {
        str += " Subscribed to indications for ";
      } else if (subValue == 3) {
        str += " Subscribed to notifications and indications for ";
      }
      str += std::string(pCharacteristic->getUUID()).c_str();

      Serial.println(str);
    };
};

/** Handler class for descriptor actions */
class DescriptorCallbacks : public NimBLEDescriptorCallbacks {
    void onWrite(NimBLEDescriptor* pDescriptor) {
      std::string dscVal = pDescriptor->getValue();
      Serial.print("Descriptor witten value:");
      Serial.println(dscVal.c_str());
    };

    void onRead(NimBLEDescriptor* pDescriptor) {
      Serial.print(pDescriptor->getUUID().toString().c_str());
      Serial.println(" Descriptor read");
    };
};

/** Define callback instances globally to use for multiple Charateristics \ Descriptors */
static DescriptorCallbacks dscCallbacks;
static CharacteristicCallbacks chrCallbacks;

void setup() {
  Serial.begin(115200);
  Serial.println("Starting NimBLE Server");

  /** sets device name */
  NimBLEDevice::init(deviceName);

  /** Optional: set the transmit power, default is 3db */
#ifdef ESP_PLATFORM
  NimBLEDevice::setPower(ESP_PWR_LVL_P9); /** +9db */
#else
  NimBLEDevice::setPower(9); /** +9db */
#endif

  /** Set the IO capabilities of the device, each option will trigger a different pairing method.
      BLE_HS_IO_DISPLAY_ONLY    - Passkey pairing
      BLE_HS_IO_DISPLAY_YESNO   - Numeric comparison pairing
      BLE_HS_IO_NO_INPUT_OUTPUT - DEFAULT setting - just works pairing
  */
  // NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_ONLY); // use passkey
  //  NimBLEDevice::setSecurityIOCap(BLE_HS_IO_DISPLAY_YESNO); //use numeric comparison

  /** 2 different ways to set security - both calls achieve the same result.
      no bonding, no man in the middle protection, secure connections.

      These are the default values, only shown here for demonstration.
  */
  //NimBLEDevice::setSecurityAuth(false, false, true);
  // NimBLEDevice::setSecurityAuth(/*BLE_SM_PAIR_AUTHREQ_BOND | BLE_SM_PAIR_AUTHREQ_MITM |*/ BLE_SM_PAIR_AUTHREQ_SC);

  pServer = NimBLEDevice::createServer();
  pServer->setCallbacks(new ServerCallbacks());

  // long readings

  NimBLEService* pShortLongReadingsService = pServer->createService(UUID_Aranet4);

  pLongReadingsCharacteristic = pShortLongReadingsService->createCharacteristic(
                                  UUID_Aranet_long_CurrentReadings,
                                  NIMBLE_PROPERTY::READ |
                                  NIMBLE_PROPERTY::WRITE |
                                  NIMBLE_PROPERTY::NOTIFY
                                  /** Require a secure connection for read and write access */
                                  // NIMBLE_PROPERTY::READ_ENC |  // only allow reading if paired / encrypted
                                  // NIMBLE_PROPERTY::WRITE_ENC   // only allow writing if paired / encrypted
                                );

  pLongReadingsCharacteristic->setCallbacks(&chrCallbacks);

  // short readings

  // NimBLEService* pShortReadingsService = pServer->createService(UUID_Aranet4);
  pShortReadingsCharacteristic = pShortLongReadingsService->createCharacteristic(
                                   UUID_Aranet_short_CurrentReadings,
                                   NIMBLE_PROPERTY::READ |
                                   NIMBLE_PROPERTY::WRITE |
                                   NIMBLE_PROPERTY::NOTIFY
                                   /** Require a secure connection for read and write access */
                                   // NIMBLE_PROPERTY::READ_ENC |  // only allow reading if paired / encrypted
                                   // NIMBLE_PROPERTY::WRITE_ENC   // only allow writing if paired / encrypted
                                 );

  pShortReadingsCharacteristic->setCallbacks(&chrCallbacks);

  // total readings

  pTotalReadingsCharacteristic = pShortLongReadingsService->createCharacteristic(
                                   UUID_Aranet_total_readings,
                                   NIMBLE_PROPERTY::READ |
                                   NIMBLE_PROPERTY::WRITE |
                                   NIMBLE_PROPERTY::NOTIFY
                                   /** Require a secure connection for read and write access */
                                   // NIMBLE_PROPERTY::READ_ENC |  // only allow reading if paired / encrypted
                                   // NIMBLE_PROPERTY::WRITE_ENC   // only allow writing if paired / encrypted
                                 );

  pTotalReadingsCharacteristic->setCallbacks(&chrCallbacks);

  // read interval

  pReadIntervalCharacteristic = pShortLongReadingsService->createCharacteristic(
                                  UUID_Aranet_interval,
                                  NIMBLE_PROPERTY::READ |
                                  NIMBLE_PROPERTY::WRITE |
                                  NIMBLE_PROPERTY::NOTIFY
                                  /** Require a secure connection for read and write access */
                                  // NIMBLE_PROPERTY::READ_ENC |  // only allow reading if paired / encrypted
                                  // NIMBLE_PROPERTY::WRITE_ENC   // only allow writing if paired / encrypted
                                );

  pReadIntervalCharacteristic->setCallbacks(&chrCallbacks);

  /** 2904 descriptors are a special case, when createDescriptor is called with
      0x2904 a NimBLE2904 class is created with the correct properties and sizes.
      However we must cast the returned reference to the correct type as the method
      only returns a pointer to the base NimBLEDescriptor class.
  */
  //// NimBLE2904* pBeef2904 = (NimBLE2904*)pGetCurrentReadingsCharacteristic->createDescriptor("2904");
  // pBeef2904->setFormat(NimBLE2904::FORMAT_UTF8);
  // pBeef2904->setCallbacks(&dscCallbacks);

  // device name

  NimBLEService* pAllGenericService = pServer->createService(generic_service_uuid);
  NimBLECharacteristic* pDeviceNameCharacteristic = pAllGenericService->createCharacteristic(
        device_name_char,
        NIMBLE_PROPERTY::READ |
        NIMBLE_PROPERTY::WRITE |
        NIMBLE_PROPERTY::NOTIFY
      );

  pDeviceNameCharacteristic->setValue(deviceName);
  pDeviceNameCharacteristic->setCallbacks(&chrCallbacks);

  // battery

  NimBLEService* pAllCommonService = pServer->createService(common_service_uuid);

  NimBLECharacteristic* pBattLevelCharacteristic = pAllCommonService->createCharacteristic(
        batt_level_char,
        NIMBLE_PROPERTY::READ |
        NIMBLE_PROPERTY::WRITE |
        NIMBLE_PROPERTY::NOTIFY
      );

  pBattLevelCharacteristic->setValue(80);
  pBattLevelCharacteristic->setCallbacks(&chrCallbacks);

  // manufacturer name
  NimBLECharacteristic* pManufacturerNameCharacteristic = pAllCommonService->createCharacteristic(
        manufacturer_name_char,
        NIMBLE_PROPERTY::READ |
        NIMBLE_PROPERTY::WRITE |
        NIMBLE_PROPERTY::NOTIFY
      );

  pManufacturerNameCharacteristic->setValue("Aranet");
  pManufacturerNameCharacteristic->setCallbacks(&chrCallbacks);

  // model number
  NimBLECharacteristic* pModelNumberCharacteristic = pAllCommonService->createCharacteristic(
        model_number_char,
        NIMBLE_PROPERTY::READ |
        NIMBLE_PROPERTY::WRITE |
        NIMBLE_PROPERTY::NOTIFY
      );

  pModelNumberCharacteristic->setValue("KSM-SAM");
  pModelNumberCharacteristic->setCallbacks(&chrCallbacks);

  // serial number
  NimBLECharacteristic* pSerialNumberCharacteristic = pAllCommonService->createCharacteristic(
        serial_number_char,
        NIMBLE_PROPERTY::READ |
        NIMBLE_PROPERTY::WRITE |
        NIMBLE_PROPERTY::NOTIFY
      );

  pSerialNumberCharacteristic->setValue("1234567");
  pSerialNumberCharacteristic->setCallbacks(&chrCallbacks);

  // hardware revision
  NimBLECharacteristic* pHWRevCharacteristic = pAllCommonService->createCharacteristic(
        hw_rev_char,
        NIMBLE_PROPERTY::READ |
        NIMBLE_PROPERTY::WRITE |
        NIMBLE_PROPERTY::NOTIFY
      );

  pHWRevCharacteristic->setValue("2234567");
  pHWRevCharacteristic->setCallbacks(&chrCallbacks);

  // software revision
  NimBLECharacteristic* pSWRevCharacteristic = pAllCommonService->createCharacteristic(
        sw_rev_char,
        NIMBLE_PROPERTY::READ |
        NIMBLE_PROPERTY::WRITE |
        NIMBLE_PROPERTY::NOTIFY
      );

  pSWRevCharacteristic->setValue("9934567");
  pSWRevCharacteristic->setCallbacks(&chrCallbacks);

  /** Note a 0x2902 descriptor MUST NOT be created as NimBLE will create one automatically
      if notification or indication properties are assigned to a characteristic.
  */

  /** Custom descriptor: Arguments are UUID, Properties, max length in bytes of the value */
  /* NimBLEDescriptor* pC01Ddsc = pDeviceNameCharacteristic->createDescriptor(
                                  "C01D",
                                  NIMBLE_PROPERTY::READ |
                                  NIMBLE_PROPERTY::WRITE |
                                  NIMBLE_PROPERTY::WRITE_ENC, // only allow writing if paired / encrypted
                                  20
                                );
    pC01Ddsc->setValue("Send it back!");
    pC01Ddsc->setCallbacks(&dscCallbacks);
  */

  /** Start the services when finished creating all Characteristics and Descriptors */

  pShortLongReadingsService->start();
  //  pShortReadingsService->start();

  pAllGenericService->start();
  pAllCommonService->start();

  pAdvertising = NimBLEDevice::getAdvertising();
  /** Add the services to the advertisment data **/

  pAdvertising->addServiceUUID(pShortLongReadingsService->getUUID());
  // pAdvertising->addServiceUUID(pShortReadingsService->getUUID());

  pAdvertising->addServiceUUID(pAllGenericService->getUUID());
  pAdvertising->addServiceUUID(pAllCommonService->getUUID());
  /** If your device is battery powered you may consider setting scan response
      to false as it will extend battery life at the expense of less data sent.
  */
  // setManData();
  // pAdvertising->setName("Aranet 01789");
  advData.setName(deviceName);
  pAdvertising->setAdvertisementData(advData);
  //  pAdvertising->setScanResponseData(advData);

  // pAdvertising->setScanResponse(true);
  pAdvertising->start();

  Serial.println("Advertising Started");
}

void update_manufacturer_data(uint8_t *data_, size_t size_) {

}

void loop() {
  //batt = random(0, 99);

  static uint32_t prev = millis();
  if ( millis() - prev >= 2000 )  {
    prev = millis();
    setManData();
    ShortReadingsNotify();
    LongReadingsNotify();
    totalReadingsNotify();
  }

}

//function takes String and adds manufacturer code at the beginning

void setManData() {
  Serial.println("Setting Manufactured Data");

  struct kassim_data {

    uint16_t manufacturer_id = ARANET4_MANUFACTURER_ID;

    uint8_t disconnected : 1,
            __unknown1   : 1,
            calib_state  : 2,
            dfu_mode     : 1,
            integrations : 1,
            __unknown2   : 2;

    uint8_t patch = 0x05;
    uint8_t minor = 0x03;
    uint16_t major = 0x01;  // v1.3.5

    uint8_t hw_rev =  5;
    uint8_t __unknown3;
    uint8_t packing;

    uint16_t co2 = 900;
    uint16_t temperature = 497;
    uint16_t pressure = 9983;
    uint8_t  humidity = 54;
    uint8_t  battery = 100;
    uint8_t status_ = 0x01;
    uint16_t interval_ = 6;
    uint16_t ago = 30;

  } __attribute__((packed)) device_data;

  // this is was it __attribute__((packed))

  BLEAdvertisementData scan_response;

  std::string manufacturerData((char *)&device_data, sizeof(device_data));

  scan_response.setManufacturerData(manufacturerData);
  scan_response.setCompleteServices(BLEUUID("FCE0"));

  pAdvertising->stop();
  pAdvertising->setScanResponseData(scan_response);
  pAdvertising->start();

}

void LongReadingsNotify() {
  Serial.println("Long notifying");

#ifdef USE_ARANET4

  struct kassim_2data { // this is for aranet4
    uint16_t co2 = 91;
    uint16_t temperature = 22;
    uint16_t pressure = 23;
    uint8_t humidity = 12;
    uint8_t  battery = 65;
    uint8_t status_ = 2;
    uint16_t interval_ = 6;
    uint16_t ago = 5;
  }  __attribute__((packed)) device_2data;

#else

  struct kassim_2data { // this is for aranet2
    uint16_t co2 = 91;
    uint16_t interval_ = 6;
    uint16_t ago = 5;
    uint8_t  battery = 65;
    uint16_t temperature = 22;
    uint16_t humidity = 12;
    uint8_t status_ = 27;
    uint16_t pressure = 23;
  }  __attribute__((packed)) device_2data;

#endif

  std::string sensor2Readings((char *)&device_2data, sizeof(device_2data));
  pLongReadingsCharacteristic->setValue(sensor2Readings);
  pLongReadingsCharacteristic->notify();
}

void totalReadingsNotify() {
  Serial.println("setting values");
  uint16_t total_reads = 78;
  pTotalReadingsCharacteristic->setValue(total_reads);
  pTotalReadingsCharacteristic->notify();

  uint16_t interval_read = 2;
  pReadIntervalCharacteristic->setValue(interval_read);
  pReadIntervalCharacteristic->notify();
}

void ShortReadingsNotify() {
  Serial.println("short notifying");

#ifdef USE_ARANET4

  struct kassim_data { // this is for aranet4
    uint16_t co2 = 91;
    uint16_t temperature = 22;
    uint16_t pressure = 23;
    uint8_t humidity = 12;
    uint8_t  battery = 65;
    uint8_t status_ = 2;
  }  __attribute__((packed)) device_data;

#else

  struct kassim_data { // this is for aranet2
    uint16_t co2 = 91;
    uint16_t interval_ = 6;
    uint16_t ago = 5;
    uint8_t  battery = 65;
    uint16_t temperature = 22;
    uint16_t humidity = 12;
    uint8_t status_ = 27;
    uint16_t pressure = 23;
  }  __attribute__((packed)) device_data;

#endif
  //  __attribute__((packed)) device_data;

  // 28,91,0,22,0,23,0,12,0,65,27,6,0,5,0,0,0,0,255,0, with packed

  // 28,0,91,0,22,0,23,0,12,0,65,27,6,0,5,0,0,0,255,0,

  std::string sensorReadings((char *)&device_data, sizeof(device_data));
  pShortReadingsCharacteristic->setValue(sensorReadings);
  pShortReadingsCharacteristic->notify();
}

// end code here
Anrijs commented 1 year ago

The data is there. onRead() callback you have, is trying to print value as ASCII string, but most of bytes in those values are non-printable. You could try printing them as hex strings. Something like this:

    void onRead(NimBLECharacteristic* pCharacteristic) {
      Serial.print(pCharacteristic->getUUID().toString().c_str());
      Serial.print(": onRead(), value: ");

      const uint8_t* ptr = pCharacteristic->getValue().data();
      uint16_t len = pCharacteristic->getValue().size();

      for (uint16_t i=0;i<len;i++) {
        Serial.printf("%02X:", ptr[i]);
      }

      Serial.println();
    };
KASSIMSAMJI commented 1 year ago

The data is there. onRead() callback you have, is trying to print value as ASCII string, but most of bytes in those values are non-printable. You could try printing them as hex strings. Something like this:

    void onRead(NimBLECharacteristic* pCharacteristic) {
      Serial.print(pCharacteristic->getUUID().toString().c_str());
      Serial.print(": onRead(), value: ");

      const uint8_t* ptr = pCharacteristic->getValue().data();
      uint16_t len = pCharacteristic->getValue().size();

      for (uint16_t i=0;i<len;i++) {
        Serial.printf("%02X:", ptr[i]);
      }

      Serial.println();
    };

Thanks again for quick reply

UPDATE:

The app can get the data the very first time after pairing, then nothing works

But If I delete and pair the device again, the data is read only once, then nothing works

data

even If I dynamically change the value

` device_data.co2 = random(12, 99);

std::string sensorReadings((char *)&device_data, sizeof(device_data)); pShortReadingsCharacteristic->setValue(sensorReadings); pShortReadingsCharacteristic->notify();`

the app displays the data ONCE

Anrijs commented 1 year ago

I won't be able to help you with app side, as I don't precisely know how it works. It may be storing some measurement information which will become invalid after some time, if it hasn't changed.

KASSIMSAMJI commented 12 months ago

Hi Anrijis, Thanks for all your help, but then again I've overcame the last error, where the data never kept refreshing on the app by calling this

static NimBLEUUID UUID_secs_since_update ("f0cd2004-95da-4f4b-9ac8-aa55d312af0c");

the app reads from that characteristic looking for a uin16_t value I just set

I am facing some new issue here, which looks kind of un-documented in your UUIDS.md file

when I click on the graph icon, then hit the CO2 Tab, the app WRITES on this characteristic f0cd1402-95da-4f4b-9ac8-aa55d312af0c with some value I could understand like 0x61,0x04,0x00,0x00 the 0x04 stands for a CO2 index,

the app then constantly READS from this characteristic f0cd2005-95da-4f4b-9ac8-aa55d312af0c expecting some CO2 data logs to be populated in it

my question is, how should I format the data to be read so the app could understand it ? , the data is supposedly needed to be rendered on the graph on the app

Cheers

Anrijs commented 12 months ago

Python version has implemented this way of reading history: https://github.com/Anrijs/Aranet4-Python/blob/f2115767660e2397012f6268df83b9e7293aaa46/aranet4/client.py#L471

KASSIMSAMJI commented 11 months ago

Python version has implemented this way of reading history: https://github.com/Anrijs/Aranet4-Python/blob/f2115767660e2397012f6268df83b9e7293aaa46/aranet4/client.py#L471

Thanks for the input, looks like I'm halfwway there but there is something I am missing

as soon as I hit, graph icon on the app, the app sends this back

f0cd1402-95da-4f4b-9ac8-aa55d312af0c: onWrite(), value: 0x61,0x04,0x00,0x00,

I am aware that the 0x04 equals CO2 index

so, I reply with this, may be I am wrongly formatting the packed up data here ?

struct data_struct {
    uint8_t measure_type = measurement_type;
    uint16_t measurement_interval_sec = 10;
    uint16_t total_measurement_stored = 30;
    uint16_t time_since_last_measure_sec = 5;
    uint16_t index_of_first_measurement_in_payload = 1;  
    uint8_t number_of_measurement_in_payload = 30;     

    uint8_t a1 = random(400, 999); 
    uint8_t b1 = random(400, 999);
    uint8_t c1 = random(400, 999);
    uint8_t d1 = random(400, 999);
    uint8_t e1 = random(400, 999);
    uint8_t f1 = random(400, 999);
    uint8_t g1 = random(400, 999);
    uint8_t h1 = random(400, 999);
    uint8_t i1 = random(400, 999);
    uint8_t j1 = random(400, 999);

    uint8_t a2 = random(400, 999);
    uint8_t b2 = random(400, 999);
    uint8_t c2 = random(400, 999);
    uint8_t d2 = random(400, 999);
    uint8_t e2 = random(400, 999);
    uint8_t f2 = random(400, 999);
    uint8_t g2 = random(400, 999);
    uint8_t h2 = random(400, 999);
    uint8_t i2 = random(400, 999);
    uint8_t j2 = random(400, 999);

    uint8_t a3 = random(400, 999);    
    uint8_t b3 = random(400, 999);
    uint8_t c3 = random(400, 999);
    uint8_t d3 = random(400, 999);
    uint8_t e3 = random(400, 999);
    uint8_t f3 = random(400, 999);
    uint8_t g3 = random(400, 999);
    uint8_t h3 = random(400, 999);
    uint8_t i3 = random(400, 999);
    uint8_t j3 = random(400, 999);
 }  __attribute__((packed)) data_to_send;

~NO IDEA WHY CODE isn't properly formatted when pasted here , but you can see few lines of code are out of the code tags~

the app then reads that packet of data above from this f0cd2005-95da-4f4b-9ac8-aa55d312af0c as I could see this on serial monitor f0cd2005-95da-4f4b-9ac8-aa55d312af0c: onRead(), value: 0x04,0x0A,0x00,0x1E,0x00,0x05,0x00,0x01,0x00,0x1E,0xDC,0x93,0x70,0xCA,0xC7,0xCB,0x76,0x2F,0xF5,0xA8,0x52,0xEA,0x66,0xBB,0xA1,0x63,0x84,0x01,0x0B,0x53,0xE2,0x9D,0x62,0x4B,0xD5,0xEF,0x09,0x4E,0x27,0xBB,

then, I can see a toast on the app saying "Loading Carbondioxide 100%" , but I see no graph rendered

but then , f0cd1402-95da-4f4b-9ac8-aa55d312af0c: onWrite(), value: 0x61,0x04,0x00,0x00 keeps printing on serial monitor over and over, I don't know what to send this time

and the top of the app screen keeps saying "Failed to Load the Data, Please try again"

Any light shed on this is highly appreciated

Thanks

Anrijs commented 11 months ago

You can format code block by using 3` at start and end of code block ( ``` ).

You are trying to store CO2 values in 8 bit integer, but it should be using 16bit integer (uint16_t). Only humidity will use 8 bit values. At the moment you are telling there will be 30 data points, that should take up 2x 30 = 60 bytes, but you send only 30 bytes.

Instead of uint8_t a1 = random(400, 999); use uint16_t a1 = random(400, 999);