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 UUID | e093f3b5-00a3-a9e5-9eca-40016e0edc24 |
Characteristic UUID | e093f3b5-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 mem | AA 00 00 byte | byte:0-3, the mem idx to switch |
update volume | AA 01 00 byte | byte :0-30 |
update dac_gain | AA 02 00 byte | byte:0-30 |
enable modules | AA 03 00 byte | to know byte value ,you can try in web fitting page |
update ble dislay name | AA 0A 00 <bytes> | <bytes> is an array ,the length not exceeding 10. |
update WDRC exp cr | AA 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 knee | AA 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 gain | AA 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 knee | AA 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 rate | AA val1 00 val3 | if ((val1 >=52) && (val1<60)) { uint8_t band_idx = val1 - 52; MCU_WDRC.cr[band_idx] = 0.1f * val3; } |
update WDRC limit knee limit knee | AA val1 00 val3 | if ((val1 >=60) && (val1<68)) { uint8_t band_idx = val1 - 60; MCU_WDRC.bolt[band_idx] = 1.0f * val3; } |
update EQ | AA 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 val3 | val1 val2 is freq, val3 is gain , you can check the message format in web fitting |
Backup all parameters | AA A1 | |
Restore current modes parameters | AA A0 val1 val2 | val1 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
- you can view the ble js code in https://yp.jhearing.com/webdetail_2022.php ,the view source code
- our mini app j10miniapp/web at main · hfvoip/j10miniapp (github.com)
- android app ,please check hfvoip/j10: android fitting software for our J10 hearing aids (github.com)