1. Home
  2. Docs
  3. J10 Document
  4. BLE protocol and client code

BLE protocol and client code

J10 Module  BLE Support ,you can communicate with J10 via BLE

we provide " web based fitting module " ,you can watch the sent/received BLE packet.

UUID definitions

Service UUIDe093f3b5-00a3-a9e5-9eca-40016e0edc24
Characteristic UUIDe093f3b5-00a3-a9e5-9eca-40036e0edc24
Notify UUID e093f3b5-00a3-a9e5-9eca-40026e0edc24

Read from J10

Read from J10 : you will get 100 bytes when you read .
each ble packet has 100 bytes

  • bytes[0]: mem index
  • bytes[1]: volume (0=0db ,1=>-1db ,2=>-2db ,etc)
  • bytes[2]: dac gain
  • bytes[3]: enabled modules
  • bytes[4]: a correct packet should be bytes[1]^bytes[2]^bytes[3]^0xDE
  • bytes[5]: hardware reversion: 0x0A for J10,0x0B for j11
  • bytes[6]: reserved
  • byte[7]: reserved
  • byte[8]: reserved
  • byte[9]: volume change step
  • bytes[10..19]: ble display name
  • bytes[20..67]:WDRC data,total 48 bytes for 8 bands ,so each band has 6 bytes:exp_cr, exp_end_knee,tkgain,tk,cr,bolt
  • byte[70..71]: ADC Value when DIO0 is connected to external power source ,and external power source is 0V
  • byte[72..73]: ADC Value ,Realtime ,to measure the external power source voltage.
  • byte[75]: power on delay ,0/1/2/3/4 => 0 /3/6/9/12 seconds
  • bytes[80..87]:EQ data, total 8 channels
  • byte[92]: ADC low battery threashold ,default is 50, means 50% of maximum battery voltage.
  • byte[93]: Battery Level (0%-100%), but there are some prerequsites to work out a correct battery level ,need calibration of adc value and input voltage on DIO0

Write to J10

Write to J10:

Long Message Format :you can send 100 bytes at one packet to J10 ,this will update .

[Note]: for some android and all IOS devices ,ble MTU size is 27 ,to send 100 bytes at one packet, you need config MTU size>100 when you set BLE properties.

Short Message Format :

if first byte of send command is 0xAA ,it is short message ,otherwise J10 consider it should be a long message (expecting 100 bytes )

Switch memAA 00 00 bytebyte:0-3, the mem idx to switch
update volumeAA 01 00 bytebyte :0-30
update dac_gainAA 02 00 bytebyte:0-30
enable modulesAA 03 00 byte to know byte value ,you can try in web fitting page
update ble dislay nameAA 0A 00 <bytes><bytes> is an array ,the length not exceeding 10.
update WDRC  exp crAA val1 00 val3     if ((val1 >=20) && (val1<28)) {     uint8_t band_idx = val1 - 20;       MCU_WDRC.exp_cr[band_idx] = 0.1f *  val3;      }
update wdrc exp kneeAA val1 00 val3 if ((val1 >=28) && (val1<36)) {        uint8_t band_idx = val1 - 28;          MCU_WDRC.exp_end_knee[band_idx] = 1.0f *  val3;      }
update WDRC linear gainAA val1 00 val3 if ((val1 >=36) && (val1<44)) {            uint8_t band_idx = val1 - 36;              MCU_WDRC.tkgain[band_idx] = 1.0f *  val3;      }
   
update WDRC compression kneeAA val1 00 val3 if ((val1 >=44) && (val1<52)) {                uint8_t band_idx = val1 - 44;                  MCU_WDRC.tk[band_idx] = 1.0f *  val3;          }
update WDRC compression rateAA val1 00 val3if ((val1 >=52) && (val1<60)) { uint8_t band_idx = val1 - 52; MCU_WDRC.cr[band_idx] = 0.1f *  val3;      }
update WDRC limit knee limit kneeAA val1 00 val3 if ((val1 >=60) && (val1<68)) { uint8_t band_idx = val1 - 60; MCU_WDRC.bolt[band_idx] = 1.0f *  val3;      }
update EQAA val1 00 val3 if ((val1 >=80) && (val1<88)) {               uint8_t band_idx = val1 - 80;                MCU_EQ.dB_Gain_float[band_idx] = 0.0f - val3;       }
pure tone test AA AB 03 00 val1 val2 val3val1 val2 is freq, val3 is gain ,
you can check the message format in web fitting
 Backup all parameters AA A1 
Restore current modes parametersAA A0 val1 val2val1 is offset, val2 is length .note our array len is 100 bytes .if you want to restore volume field (which is byte 1 of 100 bytes) . u can send AA A0 1 1
if you want to restore eq fields ( byte[80..87]) ,u can send AA A0 50 8

Code in J10

To let you understand how J10 parse the received ble packet, we provide the relative code in J10

valptr: received ble data ptr

lenData: received data length.

void Readfrom_SmData_Buffer(uint8_t* valptr) {

valptr[0] = cs_env[0].arr_params[0];
valptr[1] = (uint8_t)MCU_MULTI_GAIN.ADC_GAIN;
valptr[1] = (uint8_t) (0 -MCU_VOLUME.Volume);
valptr[2] = (uint8_t)MCU_MULTI_GAIN.DAC_GAIN;

valptr[3] = 0x0;
if(SM_Ptr->Control&MASK16(WDRC)) valptr[3] |= 0x10;
if(SM_Ptr->Control&MASK16(EQ)) valptr[3] |= 0x8;
if(SM_Ptr->Control&MASK16(AFC)) valptr[3] |= 0x4;

// 我们固定valptr作用只用于检查flash loade的时候

 #  if  0
 uint8_t  result_xor = valptr[1]  ^ valptr[2];
 result_xor = result_xor ^valptr[3];
 result_xor = result_xor ^ 0xDE;
 valptr[4] = result_xor;
#  endif 
valptr[7] = app_env.max_volume;
valptr[8] =  app_env.min_volume  ;
valptr[9] =  app_env.volume_step ;

 memcpy((uint8_t*)&valptr[10],ble_dispname,10);



//valptr[3..19]  空下来
int base_offset = 20;
//WDRC  这里6*8=48
for (uint8_t band_idx=0;band_idx <8;band_idx++)
    valptr[base_offset++] = (uint8_t)(MCU_WDRC.exp_cr[band_idx]*10);
for (uint8_t band_idx=0;band_idx <8;band_idx++)
    valptr[base_offset++] = (uint8_t)(MCU_WDRC.exp_end_knee[band_idx]*1);
for (uint8_t band_idx=0;band_idx <8;band_idx++)
    valptr[base_offset++] = (uint8_t)(MCU_WDRC.tkgain[band_idx]*1);
for (uint8_t band_idx=0;band_idx <8;band_idx++)
    valptr[base_offset++] = (uint8_t)(MCU_WDRC.tk[band_idx]*1);
for (uint8_t band_idx=0;band_idx <8;band_idx++)
    valptr[base_offset++] = (uint8_t)(MCU_WDRC.cr[band_idx]*10);
for (uint8_t band_idx=0;band_idx <8;band_idx++)
    valptr[base_offset++] = (uint8_t)(MCU_WDRC.bolt[band_idx]*1);

//valptr[68..79] 空下来
base_offset = 75;
valptr[base_offset++] = app_env.poweron_delay;

//EQ 以后再说
base_offset = 80;
for (uint8_t band_idx=0;band_idx <8;band_idx++)
        valptr[base_offset++] = (uint8_t)(0-MCU_EQ.dB_Gain_float[band_idx]);

}

void Update_SMData_RX(uint8_t* valptr, uint16_t lenData) {
if (valptr[0] == 0xAA) {
Update_ShortSMData_RX(valptr,lenData);
return ;
}

 uint8_t  result_xor = valptr[1]  ^ valptr[2];
 result_xor = result_xor ^valptr[3];
 result_xor = result_xor ^ 0xDE;
 //we use XOR to make sure correct data format,这两行应该加的,当时因为和web 端没测试好,先注释掉吧
 //2023-07-15 加上这行
// if (valptr[4] != result_xor)
//   return ;
 //我们的新版每次都是发送100个字节的,而老版本都比较短,用lenData 也可以起到一点作用
// if  (lenData <30)  return ;

 uint8_t mem_idx = valptr[0];
 
 MCU_VOLUME.Volume = 0 - valptr[1];
 MCU_MULTI_GAIN.DAC_GAIN = valptr[2];

 uint8_t wdrc_mask =valptr[3];
 cs_env[0].wdrc_mask = wdrc_mask;

 app_env.max_volume = valptr[7];
 app_env.min_volume = valptr[8];
 app_env.volume_step = valptr[9];
 if (app_env.max_volume <=0 || app_env.max_volume >50)
     app_env.max_volume = 50;

 if (app_env.min_volume <=0 || app_env.min_volume >50)
         app_env.min_volume = 0;

 //允许 0
 if (app_env.volume_step <0 || app_env.volume_step >5)
             app_env.volume_step = 4;


 //从10到19是10个字节的,BLE名字
 if (valptr[10]>0)
     memcpy(ble_dispname,(uint8_t*)&valptr[10],10);


 int base_offset = 20;
//WDRC  这里6*8=48

for (uint8_t band_idx=0;band_idx <8;band_idx++)
    MCU_WDRC.exp_cr[band_idx] = 0.1f * valptr[base_offset++];

for (uint8_t band_idx=0;band_idx <8;band_idx++)
    MCU_WDRC.exp_end_knee[band_idx] = 1.0f * valptr[base_offset++];

for (uint8_t band_idx=0;band_idx <8;band_idx++)
    MCU_WDRC.tkgain[band_idx] = 1.0f * valptr[base_offset++];

for (uint8_t band_idx=0;band_idx <8;band_idx++)
    MCU_WDRC.tk[band_idx] = 1.0f * valptr[base_offset++];

for (uint8_t band_idx=0;band_idx <8;band_idx++)
    MCU_WDRC.cr[band_idx] = 0.1f * valptr[base_offset++];

for (uint8_t band_idx=0;band_idx <8;band_idx++)
    MCU_WDRC.bolt[band_idx] = 1.0f * valptr[base_offset++];

//70,71 用于记录电压为0时候的adc 值 (ADC Value when DIO0 is 0V )
base_offset = 70;
valptr[base_offset++]  = (uint8_t)(app_env.sum_batt_lvl_0v/256);
valptr[base_offset++]  = (uint8_t)(app_env.sum_batt_lvl_0v %256 );


//byte 72,73 (ADC Realtime Value)
valptr[base_offset++]  = (uint8_t)(app_env.sum_batt_lvl/256);
valptr[base_offset++]  = (uint8_t)(app_env.sum_batt_lvl %256);

base_offset =74;
valptr[base_offset++]   =app_env.sleep_mode;

base_offset = 75;
valptr[base_offset++] = app_env.poweron_delay;
 
//EQ 以后再说
base_offset = 80;
for (uint8_t band_idx=0;band_idx <8;band_idx++)
        MCU_EQ.dB_Gain_float[band_idx] = 0.0f - valptr[base_offset++] ;

//不要再这里进行浮点运算,我们放到while()

}

void Update_ShortSMData_RX(uint8_t* valptr, uint16_t lenData) {
if (valptr[0] != 0xAA) {
return ;
}
uint8_t val1 = valptr[1];
uint8_t val2 = valptr[2];
uint8_t val3 = valptr[3];
if (val1 == 0) {
uint8_t mem_idx = val3;
if ((mem_idx >=0 ) && (mem_idx <4)) {
for (int i=0;i<100;i++)
cs_env[0].arr_params[i] = cs_env[0].arr_flash_params[128*mem_idx+i];

         //确保第一个字节不是0xAA,否则会陷入循环
         cs_env[0].arr_params[0] = mem_idx;
         Update_SMData_RX(cs_env[0].arr_params, 100);

    }


}
if (val1 >0 && val1 <100){

    uint8_t ilen = val2;
    uint8_t start_offset=val1;
    if (ilen ==0)  ilen =1;
    if (ilen <=10) {
         memcpy(&cs_env[0].arr_params[start_offset],&valptr[3],ilen);
         Update_SMData_RX(cs_env[0].arr_params, 100);

    }



}
//0xAA CC 0A [10个byte] 表示更新license
if (val1 ==0xCC &&val2 == 10)  {
    memcpy(&app_env.ble_key[0],&valptr[3],10);
    app_env.upd_ble_key = 1;
}
//0XAA AB 03 00 [3 个byte] 表示纯音测听
if (val1 ==0xAB && val2 ==3) {
    uint8_t val4 = valptr[4];
    uint8_t val5 = valptr[5];
    uint8_t val6 = valptr[6];
    int freq = val4*256 +val5;
    uint8_t gain = val6;
    app_env.playtone_freq = freq;
    app_env.playtone_gain = 0- gain;




}

}

Notification from J10

Notify Functions:

the notify message has 3 bytes:b1 b2 b3

b1: current mem  b2: current volume  b3:battery power (percentage )

when you press key to change volume or memory , you will get notify message from J10

here is screenshot ,bluetooth low enegy explore +usb dongle ,when you press key to change volume, notify data will change

FAQ:

Q: how to implement change volume function

A: the ble format is 0xAA 0x01 0x00 <volume_data>

Q: how to implement change memory

A: the ble format is : 0xAA 0x00 0x00 <mem_idx>

Q: what about the low battery notification

A: see notification part about it ,currently if battery <50%(configurable) ,hearing aid will play indication every 10 minutes . and you can also read current battery ADC value (connected to DIO0) by reading uuid data byte[72..73]

Sample Code

  1. you can view the ble js code in https://yp.jhearing.com/webdetail_2022.php ,the view source code
  2. our mini app j10miniapp/web at main · hfvoip/j10miniapp (github.com)
  3. android app ,please check hfvoip/j10: android fitting software for our J10 hearing aids (github.com)

Articles

How can we help?