ver 1.5 - add BindKey, ccm aes

This commit is contained in:
pvvx 2024-03-01 15:58:00 +03:00
parent b6a00cf514
commit 5ee8555cdc
45 changed files with 17060 additions and 34150 deletions

View file

@ -15,12 +15,12 @@ Custom firmware for Tuya devices on the PHY622x2 chipset
| Устройство | Файл Boot | Файл OTA | Маркировка на печатной плате |
|:---:|:---:|:---:|:---:|
| [THB1](https://pvvx.github.io/THB1) | BOOT_THB1_v14.hex | THB1_v14.bin | нет |
| [THB2](https://pvvx.github.io/THB2) | BOOT_THB2_v14.hex | THB2_v14.bin | нет |
| [BTH01](https://pvvx.github.io/BTH01) | BOOT_BTH01_v14.hex | BTH01_v14.bin | нет |
| [TH05_V1.4](https://pvvx.github.io/TH-05) | BOOT_TH05_v14.hex | TH05_v1.4.bin | TH05_V1.4, TH05_V1.5, TH05_V1.6 с чипом BL55028 |
| [TH05_V1.3](https://pvvx.github.io/TH05-v1.3) | BOOT_TH05D_v14.hex | TH05D_v14.bin | RSH-TH05-V1.3 с чипом BL55072 |
| [TH05F](https://pvvx.github.io/TH05F) | BOOT_TH05F_v14.hex | TH05F_v14.bin | TH05Y_V1.1, TH05Y_V1.2 с чипом QD01 2332 NT |
| [THB1](https://pvvx.github.io/THB1) | BOOT_THB1_v15.hex | THB1_v15.bin | нет |
| [THB2](https://pvvx.github.io/THB2) | BOOT_THB2_v15.hex | THB2_v15.bin | нет |
| [BTH01](https://pvvx.github.io/BTH01) | BOOT_BTH01_v15.hex | BTH01_v15.bin | нет |
| [TH05_V1.4](https://pvvx.github.io/TH-05) | BOOT_TH05_v15.hex | TH05_v1.4.bin | TH05_V1.4, TH05_V1.5, TH05_V1.6 с чипом BL55028 |
| [TH05_V1.3](https://pvvx.github.io/TH05-v1.3) | BOOT_TH05D_v15.hex | TH05D_v15.bin | RSH-TH05-V1.3 с чипом BL55072 |
| [TH05F](https://pvvx.github.io/TH05F) | BOOT_TH05F_v15.hex | TH05F_v15.bin | TH05Y_V1.1, TH05Y_V1.2 с чипом QD01 2332 NT |
Основные файлы прошивок, BOOT_xxx_vxx.hex для программирования через USB-COM адаптер и xxx_vxx.bin для OTA, находятся в директории [bin](https://github.com/pvvx/THB2/tree/master/bin).
@ -50,6 +50,7 @@ Custom firmware for Tuya devices on the PHY622x2 chipset
| 1.2 | Обработка и передача событий open/close со счетчиком с вывода маркированного "RX2" (для THB2 - "RX1"). |
| 1.3 | Добавлен THB1 и TH05V1.3. Следующий этап уменьшения потребления для версий с LCD дисплеем и опция отключения дисплея. |
| 1.4 | Стабилизация соединения для всех вариантов устройств. Добавлен [TH05F](https://pvvx.github.io/TH05F). Коррекция хода RTC. Изменено BLE имя для TH05_V1.3 на "TH05D". Добавлены файлы для обновления Boot по OTA. |
| 1.5 | Добавлен вариант шифрования BLE рекламы с помощью BindKey. |
## Прошивка

3003
bin/BOOT_BTH01_v15.hex Normal file

File diff suppressed because it is too large Load diff

3074
bin/BOOT_TH05D_v15.hex Normal file

File diff suppressed because it is too large Load diff

3070
bin/BOOT_TH05F_v15.hex Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

3065
bin/BOOT_THB1_v15.hex Normal file

File diff suppressed because it is too large Load diff

3001
bin/BOOT_THB2_v15.hex Normal file

File diff suppressed because it is too large Load diff

BIN
bin/BTH01_v15.bin Normal file

Binary file not shown.

BIN
bin/TH05D_v15.bin Normal file

Binary file not shown.

BIN
bin/TH05F_v15.bin Normal file

Binary file not shown.

BIN
bin/TH05_v15.bin Normal file

Binary file not shown.

BIN
bin/THB1_v15.bin Normal file

Binary file not shown.

BIN
bin/THB2_v15.bin Normal file

Binary file not shown.

View file

@ -9,6 +9,7 @@ SRC_PATH = ./source
SRC_PRJ = main.c
SRC_PRJ += battery.c
SRC_PRJ += battservice.c
SRC_PRJ += ccm.c
SRC_PRJ += bthome_beacon.c
SRC_PRJ += osal_peripheral.c
SRC_PRJ += peripheral_main.c

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -1,5 +1,5 @@
@set PATH=D:\MCU\GNU_Tools_ARM_Embedded\13.2.rel1\bin;%PATH%
@set SWVER=_v14
@set SWVER=_v15
@del /Q "build\THB2%SWVER%.hex"
@del /Q "build\THB2%SWVER%.bin"
@mkdir .\bin

View file

@ -12,8 +12,98 @@
#include "sensors.h"
#include "bthome_beacon.h"
#if (DEV_SERVICES & SERVICE_BINDKEY)
#include "thb2_main.h"
#include "ccm.h"
#include "flash_eep.h"
#include "ll.h"
/* Encrypted bthome nonce */
typedef struct __attribute__((packed)) _bthome_beacon_nonce_t{
uint8_t mac[6];
uint16_t uuid16; // = 0xfcd2
uint8_t info; // = 0x41
uint32_t cnt32;
} bthome_beacon_nonce_t, * pbthome_beacon_nonce_t;
bthome_beacon_nonce_t bthome_nonce;
uint8_t bindkey[16];
void bthome_beacon_init(void) {
// SwapMacAddress(bthome_nonce.mac, ownPublicAddr);
bthome_nonce.mac[0] = ownPublicAddr[5];
bthome_nonce.mac[1] = ownPublicAddr[4];
bthome_nonce.mac[2] = ownPublicAddr[3];
bthome_nonce.mac[3] = ownPublicAddr[2];
bthome_nonce.mac[4] = ownPublicAddr[1];
bthome_nonce.mac[5] = ownPublicAddr[0];
bthome_nonce.uuid16 = ADV_BTHOME_UUID16;
bthome_nonce.info = BtHomeID_Info_Encrypt;
if (flash_read_cfg(bindkey, EEP_ID_KEY, sizeof(bindkey))
!= sizeof(bindkey)) {
LL_Rand(bindkey, sizeof(bindkey));
flash_write_cfg(bindkey, EEP_ID_KEY, sizeof(bindkey));
}
}
uint8_t adv_encrypt(uint8_t * p, uint8_t data_size) {
uint8_t *pmic = &p[data_size];
bthome_nonce.cnt32 = measured_data.count;
*pmic++ = (uint8_t)measured_data.count;
*pmic++ = (uint8_t)(measured_data.count>>8);
*pmic++ = (uint8_t)(measured_data.count>>16);
*pmic++ = (uint8_t)(measured_data.count>>24);
ccm_auth_crypt(CCM_ENCRYPT, (const unsigned char *)&bindkey,
(uint8_t*)&bthome_nonce, sizeof(bthome_nonce),
(const unsigned char *)p, data_size,
p,
pmic, 4);
return data_size + 4 + 4; // + mic + count
}
#endif // (DEV_SERVICES & SERVICE_BINDKEY)
#if (DEV_SERVICES & SERVICE_THS)
uint8_t adv_set_data(void * pd) {
padv_bthome_data1_t p = (padv_bthome_data1_t)pd;
p->b_id = BtHomeID_battery;
p->battery_level = measured_data.battery;
p->t_id = BtHomeID_temperature;
p->temperature = measured_data.temp; // x0.01 C
p->h_id = BtHomeID_humidity;
p->humidity = measured_data.humi; // x0.01 %
p->v_id = BtHomeID_voltage;
p->battery_mv = measured_data.battery_mv; // x mV
return sizeof(adv_bthome_data1_t);
}
#else
uint8_t adv_set_data(void * pd) {
padv_bthome_data2_t p = (padv_bthome_data2_t)pd;
p->b_id = BtHomeID_battery;
p->battery_level = measured_data.battery;
p->v_id = BtHomeID_voltage;
p->battery_mv = measured_data.battery_mv; // x mV
return sizeof(adv_bthome_data2_t);
}
#endif
#if (DEV_SERVICES & SERVICE_RDS)
uint8_t adv_set_event(void * ped) {
padv_bthome_event1_t p = (padv_bthome_event1_t)ped;
p->o_id = BtHomeID_opened;
p->opened = measured_data.flg.pin_input;
p->c_id = BtHomeID_count32;
p->counter = adv_wrk.rds_count;
return sizeof(adv_bthome_event1_t);
}
#endif
uint8_t bthome_data_beacon(void * padbuf) {
padv_bthome_ns1_t p = (padv_bthome_ns1_t)padbuf;
padv_bthome_noencrypt_t p = (padv_bthome_noencrypt_t)padbuf;
p->flag[0] = 0x02; // size
p->flag[1] = GAP_ADTYPE_FLAGS; // type
/* Flags:
@ -27,45 +117,34 @@ uint8_t bthome_data_beacon(void * padbuf) {
p->flag[2] = GAP_ADTYPE_FLAGS_BREDR_NOT_SUPPORTED | GAP_ADTYPE_FLAGS_GENERAL; // Flags
p->head.type = GAP_ADTYPE_SERVICE_DATA; // 16-bit UUID
p->head.UUID = ADV_BTHOME_UUID16;
p->info = BtHomeID_Info;
p->p_id = BtHomeID_PacketId;
p->pid = (uint8)measured_data.count;
#if (DEV_SERVICES & SERVICE_BINDKEY)
if (cfg.flg & FLG_ADV_CRYPT) {
padv_bthome_encrypt_t pe = (padv_bthome_encrypt_t)p;
pe->info = BtHomeID_Info_Encrypt;
#if (DEV_SERVICES & SERVICE_RDS)
if(adv_wrk.adv_event) {
padv_bthome_evns1_t pe = (padv_bthome_evns1_t)p;
pe->data.o_id = BtHomeID_opened;
pe->data.opened = measured_data.flg.pin_input;
pe->data.c_id = BtHomeID_count32;
pe->data.counter = adv_wrk.rds_count;
pe->head.size = sizeof(adv_bthome_evns1_t) - sizeof(pe->head.size) - sizeof(pe->flag);
return sizeof(adv_bthome_ns1_t);
if(adv_wrk.adv_event) {
p->head.size = adv_encrypt(pe->data, adv_set_event(pe->data)) + sizeof(pe->head) - sizeof(pe->head.size) + sizeof(pe->info);
} else
#endif
{
p->head.size = adv_encrypt(pe->data, adv_set_data(pe->data)) + sizeof(pe->head) - sizeof(pe->head.size) + sizeof(pe->info);
}
} else
#endif
#if (DEV_SERVICES & SERVICE_THS)
#endif // (DEV_SERVICES & SERVICE_BINDKEY)
{
p->data.b_id = BtHomeID_battery;
p->data.battery_level = measured_data.battery;
p->data.t_id = BtHomeID_temperature;
p->data.temperature = measured_data.temp; // x0.01 C
p->data.h_id = BtHomeID_humidity;
p->data.humidity = measured_data.humi; // x0.01 %
p->data.v_id = BtHomeID_voltage;
p->data.battery_mv = measured_data.battery_mv; // x mV
p->head.size = sizeof(adv_bthome_ns1_t) - sizeof(p->head.size) - sizeof(p->flag);
return sizeof(adv_bthome_ns1_t);
p->info = BtHomeID_Info;
p->p_id = BtHomeID_PacketId;
p->pid = (uint8)measured_data.count;
#if (DEV_SERVICES & SERVICE_RDS)
if(adv_wrk.adv_event) {
p->head.size = adv_set_event(p->data) + sizeof(p->head) - sizeof(p->head.size) + sizeof(p->info) + sizeof(p->p_id) + sizeof(p->pid);
} else
#endif
{
p->head.size = adv_set_data(p->data) + sizeof(p->head) - sizeof(p->head.size) + sizeof(p->info) + sizeof(p->p_id) + sizeof(p->pid);
}
}
#else
{
padv_bthome_ns2_t pe = (padv_bthome_ns2_t)p;
pe->data.b_id = BtHomeID_battery;
pe->data.battery_level = measured_data.battery;
pe->data.v_id = BtHomeID_voltage;
pe->data.battery_mv = measured_data.battery_mv; // x mV
pe->head.size = sizeof(adv_bthome_ns2_t) - sizeof(pe->head.size) - sizeof(pe->flag);
return sizeof(adv_bthome_ns2_t);
}
#endif
return p->head.size + sizeof(p->flag) + 1;
}

View file

@ -15,7 +15,6 @@
#define BtHomeID_Info 0x40
#define BtHomeID_Info_Encrypt 0x41
// https://github.com/custom-components/ble_monitor/issues/548
typedef enum {
BtHomeID_PacketId = 0, //0x00, uint8
@ -109,7 +108,7 @@ typedef struct __attribute__((packed)) _adv_head_bth_t {
uint8 size; // =
uint8 type; // = 0x16, 16-bit UUID
uint16 UUID; // = 0xFCD2, GATT Service BTHome
} adv_head_bth_t, * padv_head_bth_t;
} adv_head_bth_t, * padv_head_bth_t; // size: 4
typedef struct __attribute__((packed)) _adv_bthome_data1_t {
uint8 b_id; // = BtHomeID_battery
@ -120,55 +119,108 @@ typedef struct __attribute__((packed)) _adv_bthome_data1_t {
uint16 humidity; // x 0.01 %
uint8 v_id; // = BtHomeID_voltage
uint16 battery_mv; // x 0.001 V
} adv_bthome_data1_t, * padv_bthome_data1_t;
} adv_bthome_data1_t, * padv_bthome_data1_t; // size: 11
typedef struct __attribute__((packed)) _adv_bthome_data2_t {
uint8 b_id; // = BtHomeID_battery
uint8 battery_level; // 0..100 %
uint8 v_id; // = BtHomeID_voltage
uint16 battery_mv; // x 0.001 V
} adv_bthome_data2_t, * padv_bthome_data2_t;
} adv_bthome_data2_t, * padv_bthome_data2_t; // size: 5
typedef struct __attribute__((packed)) _adv_bthome_event1_t {
uint8_t o_id; // = BtHomeID_opened ?
uint8_t opened;
uint8_t c_id; // = BtHomeID_count32
uint32_t counter;
} adv_bthome_event1_t, * padv_bthome_event1_t;
#define ADV_BUFFER_SIZE (31-3)
} adv_bthome_event1_t, * padv_bthome_event1_t; // size: 7
// BTHOME data1, no security
typedef struct __attribute__((packed)) _adv_bthome_ns1_t {
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head;
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head; // [4]
uint8 info; // = 0x40 BtHomeID_Info
uint8 p_id; // = BtHomeID_PacketId
uint8 pid; // PacketId (measurement count)
adv_bthome_data1_t data;
} adv_bthome_ns1_t, * padv_bthome_ns1_t;
} adv_bthome_ns1_t, * padv_bthome_ns1_t; // size: 21
// BTHOME data2, no security
typedef struct __attribute__((packed)) _adv_bthome_ns2_t {
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head;
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head; // [4]
uint8 info; // = 0x40 BtHomeID_Info
uint8 p_id; // = BtHomeID_PacketId
uint8 pid; // PacketId (measurement count)
adv_bthome_data2_t data;
} adv_bthome_ns2_t, * padv_bthome_ns2_t;
} adv_bthome_ns2_t, * padv_bthome_ns2_t; // size: 15
// BTHOME event1, no security
typedef struct __attribute__((packed)) _adv_bthome_evns1_t {
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head;
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head; // [4]
uint8 info; // = 0x40 BtHomeID_Info
uint8 p_id; // = BtHomeID_PacketId
uint8 pid; // PacketId (measurement count)
adv_bthome_event1_t data;
} adv_bthome_evns1_t, * padv_bthome_evns1_t;
} adv_bthome_evns1_t, * padv_bthome_evns1_t; // size: 17
#if (DEV_SERVICES & SERVICE_BINDKEY)
// BTHOME data1, security
typedef struct __attribute__((packed)) _adv_bthome_d1_t {
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head; // [4]
uint8_t info; // = 0x41 BtHomeID_Info_Encrypt
adv_bthome_data1_t data;
uint32_t count_id;
uint8_t mic[4];
} adv_bthome_1_t, * padv_bthome_1_t; // size: 27
// BTHOME data2, security
typedef struct __attribute__((packed)) _adv_bthome_d2_t {
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head; // [4]
uint8_t info; // = 0x41 BtHomeID_Info_Encrypt
adv_bthome_data2_t data;
uint32_t count_id;
uint8_t mic[4];
} adv_bthome_2_t, * padv_bthome_2_t; // size: 21
// BTHOME event1, security
typedef struct __attribute__((packed)) _adv_bthome_ev1_t {
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head; // [4]
uint8_t info; // = 0x41 BtHomeID_Info_Encrypt
adv_bthome_event1_t data;
uint32_t count_id;
uint8_t mic[4];
} adv_bthome_ev1_t, * padv_bthome_ev1_t; // size: 23
// BTHOME adv security
typedef struct __attribute__((packed)) _adv_bthome_encrypt_t {
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head;
uint8_t info;
uint8_t data[30-4];
} adv_bthome_encrypt_t, * padv_bthome_encrypt_t;
extern uint8_t bindkey[16];
void bthome_beacon_init(void);
#endif // (DEV_SERVICES & SERVICE_BINDKEY)
// BTHOME adv no security
typedef struct __attribute__((packed)) _adv_bthome_noencrypt_t {
uint8 flag[3]; // Advertise type flags
adv_head_bth_t head; // [4]
uint8 info; // = 0x40 BtHomeID_Info
uint8 p_id; // = BtHomeID_PacketId
uint8 pid; // PacketId (measurement count)
uint8_t data[30-4-2];
} adv_bthome_noencrypt_t, * padv_bthome_noencrypt_t;
uint8_t bthome_data_beacon(void * padbuf);

133
bthome_phy6222/source/ccm.c Normal file
View file

@ -0,0 +1,133 @@
/*
* ccm.c
*/
#include <stdint.h>
#include <config.h>
#if (DEV_SERVICES & SERVICE_BINDKEY)
#include "ccm.h"
extern void LL_ENC_AES128_Encrypt1( unsigned char * key,
unsigned char * plaintext,
unsigned char * ciphertext );
/*
* Macros for common operations.
* Results in smaller compiled code than static inline functions.
*/
/*
* Update the CBC-MAC state in y using a block in b
* (Always using b as the source helps the compiler optimise a bit better.)
*/
#define UPDATE_CBC_MAC \
for (i = 0; i < 16; i++) \
y[i] ^= b[i]; \
LL_ENC_AES128_Encrypt1((unsigned char *)key, y, y);
/*
* Encrypt or decrypt a partial block with CTR
* Warning: using b for temporary storage! src and dst must not be b!
* This avoids allocating one more 16 bytes buffer while allowing src == dst.
*/
#define CTR_CRYPT(dst, src, len) \
LL_ENC_AES128_Encrypt1((unsigned char *)key, ctr, b); \
for (i = 0; i < len; i++) \
dst[i] = src[i] ^ b[i];
/*
* Authenticated encryption or decryption
*/
int ccm_auth_crypt( int mode, const unsigned char *key,
const unsigned char *iv, size_t iv_len,
const unsigned char *input, size_t length,
unsigned char *output,
unsigned char *tag, size_t tag_len )
{
unsigned char i;
unsigned char q;
size_t len_left;
unsigned char b[16];
unsigned char y[16];
unsigned char ctr[16];
const unsigned char *src;
unsigned char *dst;
q = 16 - 1 - (unsigned char) iv_len;
b[0] = ((tag_len - 2) / 2) << 3;
b[0] |= q - 1;
memcpy(b + 1, iv, iv_len);
for (i = 0, len_left = length; i < q; i++, len_left >>= 8)
b[15 - i] = (unsigned char) (len_left & 0xFF);
if (len_left > 0)
return (-1);
memset(y, 0, 16);
UPDATE_CBC_MAC;
ctr[0] = q - 1;
memcpy( ctr + 1, iv, iv_len );
memset( ctr + 1 + iv_len, 0, q );
ctr[15] = 1;
len_left = length;
src = input;
dst = output;
while (len_left > 0) {
size_t use_len = len_left > 16 ? 16 : len_left;
if (mode == CCM_ENCRYPT) {
memset(b, 0, 16);
memcpy(b, src, use_len);
UPDATE_CBC_MAC;
}
CTR_CRYPT( dst, src, use_len );
if (mode == CCM_DECRYPT) {
memset(b, 0, 16);
memcpy(b, dst, use_len);
UPDATE_CBC_MAC;
}
dst += use_len;
src += use_len;
len_left -= use_len;
for (i = 0; i < q; i++)
if (++ctr[15 - i] != 0)
break;
}
for (i = 0; i < q; i++)
ctr[15 - i] = 0;
CTR_CRYPT( y, y, 16 );
memcpy(tag, y, tag_len);
return (0);
}
/*
* Authenticated decryption
*/
int aes_ccm_decrypt( const unsigned char *key,
const unsigned char *iv, size_t iv_len,
const unsigned char *input, size_t length,
unsigned char *output,
const unsigned char *tag, size_t tag_len )
{
int ret;
unsigned char check_tag[16];
unsigned char i;
int diff;
if( ( ret = ccm_auth_crypt( CCM_DECRYPT, key,
iv, iv_len,
input, length,
output,
check_tag, tag_len ) ) != 0 )
{
return(ret);
}
for( diff = 0, i = 0; i < tag_len; i++ )
diff |= tag[i] ^ check_tag[i];
if( diff != 0 )
{
volatile unsigned char *p = output; while(length-- ) *p++ = 0;
return(-1);
}
return(0);
}
#endif // (DEV_SERVICES & SERVICE_BINDKEY)

View file

@ -0,0 +1,47 @@
/**
* \file ccm.h
*
*/
#ifndef _CCM_H_
#define _CCM_H_
#ifdef __cplusplus
extern "C" {
#endif
#define CCM_ENCRYPT 0
#define CCM_DECRYPT 1
/*
* Authenticated encryption or decryption
*/
int ccm_auth_crypt( int mode, const unsigned char *key,
const unsigned char *iv, size_t iv_len,
const unsigned char *input, size_t length,
unsigned char *output,
unsigned char *tag, size_t tag_len );
/**
* \brief CCM buffer authenticated decryption
*
* \param key key must be 16 bytes
* \param length length of the input data
* \param iv initialization vector
* \param iv_len length of IV
* \param input buffer holding the input data
* \param output buffer for holding the output data
* \param tag buffer holding the tag
* \param tag_len length of the tag
*
* \return 0 if successful and authenticated,
* MBEDTLS_ERR_CCM_AUTH_FAILED if tag does not match
*/
int aes_ccm_decrypt( const unsigned char *key,
const unsigned char *iv, size_t iv_len,
const unsigned char *input, size_t length,
unsigned char *output,
const unsigned char *tag, size_t tag_len );
#ifdef __cplusplus
}
#endif
#endif /* _CCM_H_ */

View file

@ -24,11 +24,13 @@
#include "sensors.h"
#include "cmd_parser.h"
#include "devinfoservice.h"
#include "gapgattserver.h"
#include "ble_ota.h"
#include "thb2_peripheral.h"
#include "lcd.h"
#include "logger.h"
#include "trigger.h"
#include "bthome_beacon.h"
/*********************************************************************/
extern gapPeriConnectParams_t periConnParameters;
@ -181,6 +183,23 @@ int cmd_parser(uint8_t * obuf, uint8_t * ibuf, uint32_t len) {
clear_memo();
olen = 2;
}
#endif
#if (DEV_SERVICES & SERVICE_BINDKEY)
} else if (cmd == CMD_ID_BKEY) { // Get/set beacon bindkey
if (len == sizeof(bindkey) + 1) {
if(memcmp(bindkey, &ibuf[1], sizeof(bindkey))) {
memcpy(bindkey, &ibuf[1], sizeof(bindkey));
flash_write_cfg(&bindkey, EEP_ID_KEY, sizeof(bindkey));
bthome_beacon_init();
}
}
if (flash_read_cfg(&bindkey, EEP_ID_KEY, sizeof(bindkey)) == sizeof(bindkey)) {
memcpy(&obuf[1], bindkey, sizeof(bindkey));
olen = sizeof(bindkey) + 1;
} else { // No bindkey in EEP!
obuf[1] = 0xff;
olen = 2;
}
#endif
} else if (cmd == CMD_ID_SERIAL) {
memcpy(&obuf[1], devInfoSerialNumber, sizeof(devInfoSerialNumber)-1);
@ -258,7 +277,7 @@ int cmd_parser(uint8_t * obuf, uint8_t * ibuf, uint32_t len) {
olen = 2;
#endif
} else if (cmd == CMD_ID_DNAME) {
if (len > 1 && len < B_MAX_ADV_LEN - 2) {
if (len > 1 && len <= GAP_DEVICE_NAME_LEN) {
if(ibuf[1] == 0)
set_def_name();
else {

View file

@ -13,10 +13,10 @@
// #include "bus_dev.h"
#ifndef APP_VERSION
#define APP_VERSION 0x14 // BCD
#define APP_VERSION 0x15 // BCD
#endif
/*
/* rf_phy_ana_cfg
#define BOARD_LYWSD03MMC_B14 0 // number used for BLE firmware
#define BOARD_MHO_C401 1
#define BOARD_CGG1 2
@ -34,7 +34,7 @@
#define BOARD_TNK 16 // Water tank controller (not yet published at the moment)
#define BOARD_TS0201_TZ3000 17
#define BOARD_TS0202_TZ3000 18
#define BOARD__TH03Z 22 // ZigBee TH03Z
#define BOARD_TH03Z 22 // ZigBee TH03Z
*/
#define DEVICE_THB2 19
#define DEVICE_BTH01 20
@ -45,7 +45,7 @@
//#define DEVICE_THB3 26
#ifndef DEVICE
#define DEVICE DEVICE_THB2
#define DEVICE DEVICE_BTH01
#endif
// supported services by the device (bits)
@ -93,6 +93,7 @@
| SERVICE_HISTORY \
| SERVICE_TH_TRG \
| SERVICE_RDS \
| SERVICE_BINDKEY \
)
#endif
@ -128,6 +129,7 @@
| SERVICE_HISTORY \
| SERVICE_TH_TRG \
| SERVICE_RDS \
| SERVICE_BINDKEY \
)
#endif
@ -166,6 +168,7 @@
| SERVICE_HISTORY \
| SERVICE_TH_TRG \
| SERVICE_RDS \
| SERVICE_BINDKEY \
)
#endif
@ -213,6 +216,7 @@
| SERVICE_HISTORY \
| SERVICE_TH_TRG \
| SERVICE_RDS \
| SERVICE_BINDKEY \
)
#endif
@ -254,6 +258,7 @@
| SERVICE_HISTORY \
| SERVICE_TH_TRG \
| SERVICE_RDS \
| SERVICE_BINDKEY \
)
#endif
@ -299,6 +304,7 @@
| SERVICE_HISTORY \
| SERVICE_TH_TRG \
| SERVICE_RDS \
| SERVICE_BINDKEY \
)
#endif
@ -368,7 +374,7 @@ extern const cfg_t def_cfg;
#define FLG_SHOW_SMILEY 0x00000004 // включить показ смайлика
#define FLG_SHOW_TRG 0x00000008 // смайлик поаказывает TRG
#define FLG_DISPLAY_OFF 0x00000010 // отключить дисплей
//#define FLG_ADV_CRYPT 0x00000100 // Зашифрованная BLE реклама (bindkey)
#define FLG_ADV_CRYPT 0x00000020 // Зашифрованная BLE реклама (bindkey)
typedef struct _adv_work_t {
uint32_t measure_interval_ms;
@ -376,7 +382,7 @@ typedef struct _adv_work_t {
#if (DEV_SERVICES & SERVICE_RDS)
uint32_t rds_count;
#endif
uint8_t adv_count;
uint8_t adv_meas_count;
uint8_t adv_reload_count;
uint8_t adv_batt_count;
uint8_t adv_event;

View file

@ -417,10 +417,10 @@ signed short flash_read_cfg(void *ptr, unsigned short id, unsigned short maxsize
#if CONFIG_DEBUG_LOG > 3
DBG_FEEP_INFO("obj not found\n");
#endif
rets = -faddr-1;
rets = -faddr - 1;
}
}
else rets = -faddr-1;
else rets = -faddr - 1;
_flash_mutex_unlock();
}
return rets;

View file

@ -21,7 +21,7 @@ extern "C" {
//#define EEP_ID_CMF (0x0FCC) // EEP ID comfort data
#define EEP_ID_DVN (0xDEAE) // EEP ID device name
#define EEP_ID_TIM (0x0ADA) // EEP ID time adjust
//#define EEP_ID_KEY (0xBC0D) // EEP ID bkey
#define EEP_ID_KEY (0xBC0D) // EEP ID bindkey
#define EEP_ID_VER (0x5555) // EEP ID blk: unsigned int = minimum supported version
//-----------------------------------------------------------------------------
#ifndef FLASH_BASE_ADDR

View file

@ -119,20 +119,21 @@ void set_def_name(void)
gapRole_ScanRspDataLen = sizeof(DEF_MODEL_NUMBER_STR) + 8;
*p++ = sizeof(DEF_MODEL_NUMBER_STR) + 7;
*p++ = GAP_ADTYPE_LOCAL_NAME_COMPLETE;
memcpy(p, devInfoModelNumber, sizeof(DEF_MODEL_NUMBER_STR)-1);
memcpy(p, devInfoModelNumber, sizeof(DEF_MODEL_NUMBER_STR) - 1);
p += sizeof(DEF_MODEL_NUMBER_STR) - 1;
*p++ = '-';
p = str_bin2hex(p, pmac+2, 1);
p = str_bin2hex(p, pmac+1, 1);
str_bin2hex(p, pmac, 1);
p = str_bin2hex(p, pmac, 1);
*p++ = 0;
flash_write_cfg(NULL, EEP_ID_DVN, 0);
}
void set_dev_name(void)
{
uint8_t * p = gapRole_ScanRspData;
int len = flash_read_cfg(&p[2], EEP_ID_DVN, 19);
if(len > 0 && p[2] != 0) {
int len = flash_read_cfg(&p[2], EEP_ID_DVN, GAP_DEVICE_NAME_LEN - 1);
if(len > 0 && len < GAP_DEVICE_NAME_LEN && p[2] != 0) {
p[0] = (uint8_t)len + 1;
p[1] = GAP_ADTYPE_LOCAL_NAME_COMPLETE;
p[len + 2] = 0;
@ -158,6 +159,9 @@ static void set_mac(void)
pGlobal_config[MAC_ADDRESS_LOC] = (uint32_t)ownPublicAddr;
// device name
set_dev_name();
#if (DEV_SERVICES & SERVICE_BINDKEY)
bthome_beacon_init();
#endif
}
static void set_serial_number(void)
@ -241,14 +245,14 @@ static void adv_measure(void) {
#endif
}
#if (DEV_SERVICES & SERVICE_THS)
if(adv_wrk.adv_count == (uint8_t)(cfg.measure_interval - 1)) {
if(adv_wrk.adv_meas_count == (uint8_t)(cfg.measure_interval - 1)) {
start_measure();
#if (DEV_SERVICES & SERVICE_SCREEN)
chow_lcd(0);
#endif
} else {
if(adv_wrk.adv_count >= cfg.measure_interval) {
adv_wrk.adv_count = 0;
if(adv_wrk.adv_meas_count >= cfg.measure_interval) {
adv_wrk.adv_meas_count = 0;
read_sensor();
#if (DEV_SERVICES & SERVICE_SCREEN)
chow_lcd(1);
@ -283,7 +287,7 @@ static void adv_measure(void) {
set_new_adv_interval(cfg.advertising_interval * 100);
}
}
adv_wrk.adv_count++;
adv_wrk.adv_meas_count++;
}
}
@ -576,7 +580,7 @@ uint16_t BLEPeripheral_ProcessEvent( uint8_t task_id, uint16_t events )
VOID task_id; // OSAL required parameter that isn't used in this function
if ( events & ADV_BROADCAST_EVT) {
adv_measure();
LOG("advN%u\n", adv_wrk.adv_count);
LOG("advN%u\n", adv_wrk.adv_meas_count);
// return unprocessed events
return (events ^ ADV_BROADCAST_EVT);
}
@ -598,7 +602,7 @@ uint16_t BLEPeripheral_ProcessEvent( uint8_t task_id, uint16_t events )
// enable adv (from gaprole start)
if ( events & SBP_RESET_ADV_EVT ) {
LOG("SBP_RESET_ADV_EVT\n");
adv_wrk.adv_count = 0;
adv_wrk.adv_meas_count = 0;
// set_new_adv_interval(DEF_ADV_INERVAL); // actual time = advInt * 625us
gatrole_advert_enable(TRUE);
return ( events ^ SBP_RESET_ADV_EVT );
@ -657,7 +661,7 @@ uint16_t BLEPeripheral_ProcessEvent( uint8_t task_id, uint16_t events )
#ifdef GPIO_LED
hal_gpio_write(GPIO_LED, LED_OFF);
#endif
//adv_wrk.adv_count = 0;
//adv_wrk.adv_meas_count = 0;
#if (DEV_SERVICES & SERVICE_SCREEN)
lcd_show_version();
#endif
@ -780,13 +784,13 @@ static void peripheralStateReadRssiCB( int8_t rssi )
{
LOG("Gaprole_adversting\n");
osal_stop_timerEx(simpleBLEPeripheral_TaskID, TIMER_BATT_EVT);
adv_wrk.adv_count = 0;
adv_wrk.adv_meas_count = 0;
}
break;
case GAPROLE_CONNECTED:
adv_wrk.adv_event = 0;
adv_wrk.adv_count = 0;
adv_wrk.adv_meas_count = 0;
adv_wrk.adv_reload_count = 1;
#if (DEV_SERVICES & SERVICE_THS)
osal_start_reload_timer(simpleBLEPeripheral_TaskID, TIMER_BATT_EVT, adv_wrk.measure_interval_ms); // 10000 ms
@ -815,7 +819,7 @@ static void peripheralStateReadRssiCB( int8_t rssi )
gapRole_SlaveLatency = cfg.connect_latency;
#endif
adv_wrk.adv_event = 0;
adv_wrk.adv_count = 0;
adv_wrk.adv_meas_count = 0;
adv_wrk.adv_reload_count = 1;
#if (DEV_SERVICES & SERVICE_SCREEN)
show_ble_symbol(0);

View file

@ -1,6 +1,6 @@
<html class="phy6222Class"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PHY62x2 BTHome v1.4</title>
<title>PHY62x2 BTHome v1.5</title>
<!--<link rel="stylesheet" type="text/css" href="styles.css" />
<link rel="stylesheet" type="text/css" href="chart.css" />
<script type="text/javascript" src="dygraph.min.js" /></script> /-->
@ -485,6 +485,7 @@ var devCfg = {};
var devSens = {};
var devTrig = {};
var devTime = {};
var devKeys = {};
var devName = "";
var isConnected = false;
@ -549,12 +550,8 @@ function connect() {
deviceOptions.acceptAllDevices = false;
deviceOptions.filters = namePrefix.split(",")
.map((x) => ({ namePrefix: x }));
} else {
deviceOptions.acceptAllDevices = false;
deviceOptions.filters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyz_#@!*0123456789';,.<>{}[]"
.split("")
.map((x) => ({ namePrefix: x }));
}
console.log(deviceOptions);
if (bluetoothDevice != null) bluetoothDevice.gatt.disconnect();
chartData.length = 0;
resetVariables();
@ -746,12 +743,17 @@ function auxControls(state)
if ( devSrv.services & SERVICE_SCREEN ) {
$('tblChkCfg').style.display = "block";
$('tblComfort').style.display = "block";
} else {
$('tblChkCfg').style.display = "none";
$('tblComfort').style.display = "none";
}
if ( devSrv.services & SERVICE_BINDKEY ) {
$('labBindKey').style.display = "block";
$('divBindKey').style.display = "block";
} else {
$('labBindKey').style.display = "none";
$('divBindKey').style.display = "none";
}
if ( devSrv.services & SERVICE_TH_TRG )
$('tblTrigger').style.display = "block";
else
@ -1252,6 +1254,7 @@ function showConfig() {
$('chkCfgSmiley').checked = (devCfg.flg & 4) != 0;
$('chkCfgTrg').checked = (devCfg.flg & 8) != 0;
$('chkCfgLcdOff').checked = (devCfg.flg & 16) != 0;
$('chkCfgBindKey').checked = (devCfg.flg & 32) != 0;
let txPwr = 31;
el = $('selTxPwr');
for(let n = 0; n < el.options.length; n++) if(el.options[n].value >= devCfg.tx_power) txPwr = el.options[n].value;
@ -1458,19 +1461,15 @@ function parseBlkCustom(value) {
s = 'LCD data # '+bytesToHex(value.buffer.slice(1));
addLog(s);
} else if(blkId == 0x18 && len >= 1) { // Get/set beacon bkey in EEP
if(len >= 16) {
devkeys.cbindkey = value.buffer.slice(1);
let s = bytesToHex(devkeys.cbindkey,16);
if(len > 16) {
devKeys.cbindkey = value.buffer.slice(1,17);
let s = bytesToHex(devKeys.cbindkey,16);
addLog("Bindkey # "+ s);
//if($("cbind_key"))
// $("cbind_key").value = s;
$("inpBindKey").value = s;
} else {
if(len == 1 && value.getUint8(1) == 0xff)
addLog("Bindkey не запсан!");
else
addAlog("Ошибка чтения Bindkey!");
//if($("cbind_key"))
// $("cbind_key").value = '?';
addLog("Ошибка чтения Bindkey!");
console.log('blk: ' + dump8(value, value.byteLength));
$("inpBindKey").value = 'Не поддерживается!';
}
} else if(blkId == 0x23 && len >= 4) {
devTime.cur = value.getUint32(1,true);
@ -1576,56 +1575,58 @@ function getMemo(num) {
//addLog("getSensCfg...");
isMemoActive = true;
memoCount = 0;
cmdCharacteristic.writeValue(new Uint8Array([0x35, num&0xff, (num>>8)&0xff])).catch(error => { console.log(error); addLog("getMemo() error!"); });
cmdCharacteristic.writeValue(new Uint8Array([0x35, num&0xff, (num>>8)&0xff])).catch(error => { addLog("getMemo() Error: " + error); });
}
}
function getDevCfg() {
if(cmdCharacteristic != null) {
//addLog("getDevCfg...");
cmdCharacteristic.writeValue(new Uint8Array([0x55])).catch(error => { console.log(error); addLog("getDevCfg() error!") });
cmdCharacteristic.writeValue(new Uint8Array([0x55])).catch(error => { addLog("getDevCfg() Error: " + error) });
}
}
function getSensCfg() {
if(cmdCharacteristic != null) {
//addLog("getSensCfg...");
cmdCharacteristic.writeValue(new Uint8Array([0x25])).catch(error => { console.log(error); addLog("getSensCfg() error!"); });
cmdCharacteristic.writeValue(new Uint8Array([0x25])).catch(error => { addLog("getSensCfg() Error: " + error); });
}
}
function getDevTime() {
if(cmdCharacteristic != null) {
// addLog("Получить время от устройства...");
cmdCharacteristic.writeValue(new Uint8Array([0x23])).then(_ => { console.log('Send GetDevTime ok'); });
cmdCharacteristic.writeValue(new Uint8Array([0x23])).then(_ => { console.log('Send GetDevTime ok'); })
.catch(error => { addLog("getDevTime() Error: " + error); });
}
}
function getDevMAC() {
if(cmdCharacteristic != null) {
cmdCharacteristic.writeValue(new Uint8Array([0x10])).catch(error => { console.log(error); addLog("getMAC() error!"); });
cmdCharacteristic.writeValue(new Uint8Array([0x10])).catch(error => { addLog("getMAC() Error: " + error); });
}
}
function getTrgCfg() {
if(cmdCharacteristic != null) {
cmdCharacteristic.writeValue(new Uint8Array([0x44])).catch(error => { console.log(error); addLog("getTrgCfg() error!"); });
cmdCharacteristic.writeValue(new Uint8Array([0x44])).catch(error => { addLog("getTrgCfg() Error: " + error); });
}
}
function getDevName() {
if(cmdCharacteristic != null) {
cmdCharacteristic.writeValue(new Uint8Array([0x01])).catch(error => { console.log(error); addLog("getDevName() error!"); });
cmdCharacteristic.writeValue(new Uint8Array([0x01])).catch(error => { addLog("getDevName() Error: " + error); });
}
}
function chkDevCfg() {
if(devCfg.flg == null) return;
devCfg.flg &= 0xffffffe0;
devCfg.flg &= 0xffffffc0;
devCfg.flg |= ($('chkCfgNotify').checked) ? 1 : 0;
devCfg.flg |= ($('chkCfgClock').checked) ? 2 : 0;
devCfg.flg |= ($('chkCfgSmiley').checked) ? 4 : 0;
devCfg.flg |= ($('chkCfgTrg').checked) ? 8 : 0;
devCfg.flg |= ($('chkCfgLcdOff').checked) ? 16 : 0;
devCfg.flg |= ($('chkCfgBindKey').checked) ? 32 : 0;
devCfg.tx_power = $('selTxPwr').value & 0x3f; // 0..0x1f -> -20..+5 dBm ? нелинейное 0x1f = +0 дБм
let connect_latency = parseInt($('inpLat').value); // = (connect_latency + 1)*30 ms
if (connect_latency < 0) {
@ -1675,7 +1676,7 @@ function setDevCfg() {
devCfg.averaging_measurements,
devCfg.reserved2
]);
cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setDevCfg() error!");});
cmdCharacteristic.writeValue(blk).catch(error => { addLog("setDevCfg() Error: " + error);});
}
}
@ -1694,7 +1695,7 @@ function setSensCfg() {
devSens.temp_z & 0xff, (devSens.temp_z >> 8) & 0xff,
devSens.humi_z & 0xff, (devSens.humi_z >> 8) & 0xff
]);
cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setSensCfg() error!"); });
cmdCharacteristic.writeValue(blk).catch(error => { addLog("setSensCfg() Error: " + error); });
}
}
@ -1710,7 +1711,7 @@ function setDevTime() {
addLog("Установка времени на устройстве (" + dump(blk, blk.length) + ")...");
cmdCharacteristic.writeValue(blk).then(_ => {
console.log('Время на устройстве синхронизировано');
});
}).catch(error => { addLog("setDevTime() Error: " + error); });
}
function setDevMAC() {
@ -1788,7 +1789,7 @@ function setTrgCfg() {
blk[idx+7] = (devTrig.humi_hysteresis >> 8) & 0xff;
blk[idx+8] = devTrig.flg & 0xff;
}
cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setTrgCfg() error!"); });
cmdCharacteristic.writeValue(blk).catch(error => { addLog("setTrgCfg() Error: " + error); });
}
}
}
@ -1802,30 +1803,59 @@ function setDevName(flg) {
if(flg) {
let eltxt = $("inpDevName").value;
let len = eltxt.length;
if(len > 1 && len < 20) {
if(len > 0 && len < 20) {
let nm = new Uint8Array((new TextEncoder()).encode(eltxt));
len = nm.length;
if(len > 0 && len < 20) {
addLog("Имя устройства должно быть от 1 до 19 символов, включая кодирование UTF-8!");
}
let blk = new Uint8Array(1 + len);
blk[0] = 0x01;
blk.set((new TextEncoder()).encode(eltxt), 1);
blk.set(nm, 1);
console.log(blk);
cmdCharacteristic.writeValue(blk).catch(error => { console.log(error); addLog("setDevName() error!"); });
cmdCharacteristic.writeValue(blk).catch(error => { addLog("setDevName() Error: " + error); });
} else
addLog("Имя устройства должно быть от 1 до 19 символов!");
} else
cmdCharacteristic.writeValue(new Uint8Array([0x01, 0])).catch(error => { console.log(error); addLog("setDevName() error!"); });
cmdCharacteristic.writeValue(new Uint8Array([0x01, 0])).catch(error => { addLog("setDevName() Error: "+ error); });
}
}
function resetDevCfg() {
if(cmdCharacteristic != null) {
addLog("Restore connection default settings...");
cmdCharacteristic.writeValue(new Uint8Array([0x56])).catch(error => { console.log(error); addLog("resetDevCfg() error!"); });
cmdCharacteristic.writeValue(new Uint8Array([0x56])).catch(error => { addLog("resetDevCfg() Error: " + error); });
}
}
function resetSensCfg() {
if(cmdCharacteristic != null) {
addLog("Restore sensor default settings...");
cmdCharacteristic.writeValue(new Uint8Array([0x26])).catch(error => { console.log(error); addLog("resetSensCfg() error!"); });
cmdCharacteristic.writeValue(new Uint8Array([0x26])).catch(error => { addLog("resetSensCfg() Error: " + error); });
}
}
function getGetBindKey() {
if(cmdCharacteristic != null) {
//addLog("Get bindkey...");
cmdCharacteristic.writeValue(new Uint8Array([0x18])).catch(error => { addLog("getGetBindKey() Error: " + error); });
}
}
function setBindKey() {
if(cmdCharacteristic != null) {
let bk = $("inpBindKey").value;
if(bk.length == 32) {
let bkey = hexToBytes(bk);
if(bkey.length == 16) {
let blk = new Uint8Array(17);
blk.set(bkey,1);
blk[0] = 0x18;
//console.log("Send bindkey...");
addLog("Сохранить новый BindKey: " + bytesToHex(blk.slice(1)));
cmdCharacteristic.writeValue(blk).catch(error => { addLog("setBindKey() Error: " + error); });
return;
}
}
addLog("BindKey должен соднержать 16 байт в HEX виде (32 символа)!")
}
}
@ -2161,7 +2191,7 @@ window.onload = function() {
</table>
<table>
<tr>
<td><label><input type="checkbox" id="chkCfgNotify" title="Передача измерений при соединении и включении Notification"/>Notification</label></td>
<td><label><input type="checkbox" id="chkCfgNotify" title="Передача измерений при соединении и включении Notification"/>Notification</label> <label id="labBindKey"><input type="checkbox" id="chkCfgBindKey" title="Используется BindKey"/>Шифрованная реклама</label></td>
</tr>
</table>
<table>
@ -2319,6 +2349,17 @@ window.onload = function() {
<td><button type="button" id="btnRstName" onclick="setDevName(false)">По умолчанию</button></td>
</tr>
</table>
<div id="divBindKey">
<hr>
<table>
<tr>
<td style="width:100px;">BindKey:</td>
<td><button type="button" id="btnGetBindKeyy" onclick="getGetBindKey()">Прочитать</button></td>
<td><input size="40" maxlength="32" title="Bind Key должен содержать 16 байт в hex виде" type="text" id="inpBindKey" value="?"></td>
<td><button type="button" id="btnBindKey" onclick="setBindKey()">Записать</button></td>
</tr>
</table>
</div>
</div>
<hr>
<button type="button" onclick="clearLog()">Очистить лог</button>

25
fw.json
View file

@ -1,16 +1,17 @@
{"custom":[
"bin/THB2_v14.bin",
"bin/BTH01_v14.bin",
"bin/TH05_v14.bin",
"bin/THB2_v15.bin",
"bin/BTH01_v15.bin",
"bin/TH05_v15.bin",
"?",
"bin/THB1_v14.bin",
"bin/TH05D_v14.bin",
"bin/TH05F_v14.bin"],
"bin/THB1_v15.bin",
"bin/TH05D_v15.bin",
"bin/TH05F_v15.bin"],
"updateboot":[
"update_boot/BOOT_THB2_v14.bin",
"update_boot/BOOT_BTH01_v14.bin",
"update_boot/BOOT_TH05_v14.bin",
"update_boot/BOOT_THB2_v15.bin",
"update_boot/BOOT_BTH01_v15.bin",
"update_boot/BOOT_TH05_v15.bin",
"?",
"update_boot/BOOT_TH05D_v14.bin",
"update_boot/BOOT_TH05F_v14.bin",
"update_boot/BOOT_THB1_v14.bin"]}
"update_boot/BOOT_TH05D_v15.bin",
"update_boot/BOOT_TH05F_v15.bin",
"update_boot/BOOT_THB1_v15.bin"]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.