From 6ea0bb85254b493da74285d5f742e7d2c9fff92a Mon Sep 17 00:00:00 2001 From: rbaron Date: Sun, 28 Aug 2022 18:58:16 +0200 Subject: [PATCH 1/3] First stab at an alternative, BTHome compatible BLE protocol This protococol (https://bthome.io/) allows for automatic sensor detection in Home Assistant, as long as the device broadcasts its sensors in the expected format. --- code/b-parasite/config/prst_config.h | 12 ++- code/b-parasite/src/main.c | 14 ++- code/b-parasite/src/prst/ble.c | 149 +++++++++++++++++++-------- code/b-parasite/src/prst/ble.h | 6 +- code/b-parasite/src/prst/data.h | 15 +++ 5 files changed, 146 insertions(+), 50 deletions(-) create mode 100644 code/b-parasite/src/prst/data.h diff --git a/code/b-parasite/config/prst_config.h b/code/b-parasite/config/prst_config.h index faba1e1..8ea2dd6 100644 --- a/code/b-parasite/config/prst_config.h +++ b/code/b-parasite/config/prst_config.h @@ -27,9 +27,15 @@ // BLE. // Prints out BLE debug info, such as the final encoded advertisement packet. #define PRST_BLE_DEBUG 0 -// The BLE protocol version defines how the sensors' data is encoded inside the -// BLE advertisement packet. Possible values are 1 and 2. -#define PRST_BLE_PROTOCOL_VERSION 1 + +// Supported BLE protocols. +// Default, custom BLE protocol. +#define PRST_BLE_PROTOCOL_BPARASITE_V2 0x01 +// BTHome BLE protocol - https://bthome.io. +#define PRST_BLE_PROTOCOL_BTHOME 0x02 + +// Chosen BLE protocol. +#define PRST_BLE_PROTOCOL PRST_BLE_PROTOCOL_BPARASITE_V2 // There are two options for configuring the MAC address of b-parasites: // 1. Comment out the PRST_BLE_MAC_ADDR to use a random static MAC address that diff --git a/code/b-parasite/src/main.c b/code/b-parasite/src/main.c index b7becdf..5620feb 100644 --- a/code/b-parasite/src/main.c +++ b/code/b-parasite/src/main.c @@ -9,6 +9,7 @@ #include "nrf_pwr_mgmt.h" #include "prst/adc.h" #include "prst/ble.h" +#include "prst/data.h" #include "prst/pwm.h" #include "prst/rtc.h" #include "prst/shtc3.h" @@ -86,9 +87,16 @@ static void rtc_callback() { nrf_gpio_pin_clear(PRST_PHOTO_V_PIN); #endif - prst_ble_update_adv_data(batt_read.millivolts, temp_humi.temp_celsius, - temp_humi.humidity, soil_read.relative, lux, - run_counter); + prst_sensor_data_t sensors = { + .batt_mv = batt_read.millivolts, + .temp_c = temp_humi.temp_celsius, + .humi = temp_humi.humidity, + .soil_moisture = soil_read.relative, + .lux = lux, + .run_counter = run_counter, + }; + + prst_ble_update_adv_data(&sensors); state = ADVERTISING; prst_adv_start(); diff --git a/code/b-parasite/src/prst/ble.c b/code/b-parasite/src/prst/ble.c index 3ac15cf..bd1c4ce 100644 --- a/code/b-parasite/src/prst/ble.c +++ b/code/b-parasite/src/prst/ble.c @@ -3,16 +3,13 @@ #include #include #include +#include #include #include #include #include "prst_config.h" -// We need to pick a service UUID for broadcasting our sensor data. -// 0x181a is defined as "environmental sensing", which seems appopriate. -#define SERVICE_UUID 0x181a - // The connection to configure. We only have the one. #define PRST_CONN_CFG_TAG 1 @@ -46,7 +43,16 @@ whether or not they have added the LDR by setting the PRST_HAS_LDR to 1 in prst_config.h. */ +#if PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BPARASITE_V2 +#define SERVICE_UUID 0x181a #define SERVICE_DATA_LEN 18 +#elif PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME +#define SERVICE_UUID 0x181c +#define SERVICE_DATA_LEN 21 +#else +#error "PRST_BLE_PROTOCOL is not properly configured" +#endif + static uint8_t service_data[SERVICE_DATA_LEN]; // Stores the encoded advertisement data. As per BLE spec, 31 bytes max. @@ -112,26 +118,12 @@ static void init_advertisement_data() { ble_gap_conn_sec_mode_t sec_mode; BLE_GAP_CONN_SEC_MODE_SET_OPEN(&sec_mode); - sd_ble_gap_device_name_set(&sec_mode, (const uint8_t *)PRST_BLE_ADV_NAME, + sd_ble_gap_device_name_set(&sec_mode, (const uint8_t*)PRST_BLE_ADV_NAME, strlen(PRST_BLE_ADV_NAME)); uint32_t err_code = sd_ble_gap_adv_set_configure(&adv_handle_, &gap_adv_data_, &adv_params_); APP_ERROR_CHECK(err_code); - - // Four bits for the protocol version. - service_data[0] |= (PRST_BLE_PROTOCOL_VERSION << 4) & 0xf0; - - // Bit 0 of byte 0 specifies whether or not ambient light data exists in the - // payload. -#if PRST_HAS_LDR || PRST_HAS_PHOTOTRANSISTOR - service_data[0] |= 1; -#endif - - // Bytes 10-15 (inclusive) contain the whole MAC address in big-endian. - for (int i = 0; i < 6; i++) { - service_data[10 + i] = gap_addr_.addr[5 - i]; - } } void prst_ble_init() { @@ -169,37 +161,111 @@ void prst_ble_init() { init_advertisement_data(); } -void prst_ble_update_adv_data(uint16_t batt_millivolts, float temp_celsius, - uint16_t humidity, uint16_t soil_moisture, - uint16_t brightness, uint8_t run_counter) { +#if PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BPARASITE_V2 +static void set_service_data_bparasite_protocol( + const prst_sensor_data_t* sensors) { + // Four bits for the protocol version. + service_data[0] |= (2 << 4) & 0xf0; + + // Bit 0 of byte 0 specifies whether or not ambient light data exists in the + // payload. +#if PRST_HAS_LDR || PRST_HAS_PHOTOTRANSISTOR + service_data[0] |= 1; +#endif + // 4 bits for a small wrap-around counter for deduplicating messages on the // receiver. - service_data[1] = run_counter & 0x0f; + service_data[1] = sensors->run_counter & 0x0f; - service_data[2] = batt_millivolts >> 8; - service_data[3] = batt_millivolts & 0xff; + service_data[2] = sensors->batt_mv >> 8; + service_data[3] = sensors->batt_mv & 0xff; -#if PRST_BLE_PROTOCOL_VERSION == 1 - uint16_t temp_millicelsius = temp_celsius * 1000; - service_data[4] = temp_millicelsius >> 8; - service_data[5] = temp_millicelsius & 0xff; -#elif PRST_BLE_PROTOCOL_VERSION == 2 - int16_t temp_centicelsius = temp_celsius * 100; + int16_t temp_centicelsius = 100 * sensors->temp_c; service_data[4] = temp_centicelsius >> 8; service_data[5] = temp_centicelsius & 0xff; -#else -#error "[ble] Unsupported BLE protocol version" -#endif // PRST_BLE_PROTOCOL_VERSION - service_data[6] = humidity >> 8; - service_data[7] = humidity & 0xff; + service_data[6] = sensors->humi >> 8; + service_data[7] = sensors->humi & 0xff; - service_data[8] = soil_moisture >> 8; - service_data[9] = soil_moisture & 0xff; + service_data[8] = sensors->soil_moisture >> 8; + service_data[9] = sensors->soil_moisture & 0xff; + + // Bytes 10-15 (inclusive) contain the whole MAC address in big-endian. + for (int i = 0; i < 6; i++) { + service_data[10 + i] = gap_addr_.addr[5 - i]; + } #if PRST_HAS_LDR || PRST_HAS_PHOTOTRANSISTOR - service_data[16] = brightness >> 8; - service_data[17] = brightness & 0xff; + service_data[16] = sensors->lux >> 8; + service_data[17] = sensors->lux & 0xff; +#endif +} +#endif // PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BPARASITE_V2 + +#if PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME +static void set_service_data_bthome_protocol( + const prst_sensor_data_t* sensors) { + // See values in https://bthome.io/. + + // 1. Soil moisture. + // uint16_t. + service_data[0] = (0b000 << 5) | 2; + // Type of measurement. Temporarily using humidity. + service_data[1] = 0x03; + // Value. Factor of 0.01, so we need to multiply our the value in 100% by + // 1/0.01 = 100. + uint16_t soil_val = (10000 * sensors->soil_moisture) / UINT16_MAX; + service_data[2] = soil_val & 0xff; + service_data[3] = soil_val >> 8; + + // 2. Temp. + // int16_t. + service_data[4] = (0b001 << 5) | 2; + // Type of measurement - temperature. + service_data[5] = 0x02; + // Value. Factor 0.01. + int16_t temp_val = 100 * sensors->temp_c; + service_data[6] = temp_val & 0xff; + service_data[7] = temp_val >> 8; + + // 3. Humidity + // uint16_t. + service_data[8] = (0b000 << 5) | 2; + // Type - humidity. + service_data[9] = 0x03; + // Value. Factor 0.01. + uint16_t humi_val = (100 * sensors->humi) / UINT16_MAX; + service_data[10] = humi_val & 0xff; + service_data[11] = humi_val >> 8; + + // 4. Battery voltage. + // uint16_t. + service_data[12] = (0b000 << 5) | 2; + // Type - voltage. + service_data[13] = 0x0c; + // Value. Factor of 0.001. + uint16_t batt_val = sensors->batt_mv; + service_data[14] = batt_val & 0xff; + service_data[15] = batt_val >> 8; + + // 5. Illuminance + // uint24_t. + service_data[16] = (0b000 << 5) | 2; + // Type - illuminance. + service_data[17] = 0x05; + // Value. Factor 0.01. + uint32_t illu_value = 100 * sensors->lux; + service_data[18] = illu_value & 0xff; + service_data[19] = (illu_value >> 8) & 0xff; + service_data[20] = (illu_value >> 16) & 0xff; +} +#endif // PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME + +void prst_ble_update_adv_data(const prst_sensor_data_t* sensors) { +#if PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BPARASITE_V2 + set_service_data_bparasite_protocol(sensors); +#elif PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME + set_service_data_bthome_protocol(sensors); #endif // Encodes adv_data_ into .gap_adv_data_. @@ -210,7 +276,8 @@ void prst_ble_update_adv_data(uint16_t batt_millivolts, float temp_celsius, #if PRST_BLE_DEBUG NRF_LOG_INFO("[ble] Encoded BLE adv packet:"); for (int i = 0; i < sizeof(encoded_adv_data_); i++) { - NRF_LOG_INFO("[ble] 0x%02x", encoded_adv_data_[i]); + NRF_LOG_INFO("[ble] byte %02d: 0x%02x", i, encoded_adv_data_[i]); + nrf_delay_ms(50); } #endif } diff --git a/code/b-parasite/src/prst/ble.h b/code/b-parasite/src/prst/ble.h index 04409d1..85b215c 100644 --- a/code/b-parasite/src/prst/ble.h +++ b/code/b-parasite/src/prst/ble.h @@ -4,6 +4,8 @@ #include #include +#include "prst/data.h" + // Initializes SoftDevice. void prst_ble_init(); @@ -11,8 +13,6 @@ void prst_adv_start(); void prst_adv_stop(); -void prst_ble_update_adv_data(uint16_t batt_millivolts, float temp_celsius, - uint16_t humidity, uint16_t soil_moisture, - uint16_t brightness, uint8_t run_counter); +void prst_ble_update_adv_data(const prst_sensor_data_t* sensors); #endif // _PRST_BLE_H_ \ No newline at end of file diff --git a/code/b-parasite/src/prst/data.h b/code/b-parasite/src/prst/data.h new file mode 100644 index 0000000..8cbfc88 --- /dev/null +++ b/code/b-parasite/src/prst/data.h @@ -0,0 +1,15 @@ +#ifndef _PRST_DATA_H_ +#define _PRST_DATA_H_ + +#include + +typedef struct { + uint16_t batt_mv; + float temp_c; + uint16_t humi; + uint16_t soil_moisture; + uint16_t lux; + uint8_t run_counter; +} prst_sensor_data_t; + +#endif // _PRST_DATA_H_ \ No newline at end of file From ce4c496f4d247d851bf91f5d3aed59cc0847976c Mon Sep 17 00:00:00 2001 From: rbaron Date: Sun, 28 Aug 2022 22:47:21 +0200 Subject: [PATCH 2/3] Uses newly minted 0x14 sensor type for moisture I also comment out the illiminance sensor, since we need the space for encoding the device's name - "prst". --- code/b-parasite/src/prst/ble.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/code/b-parasite/src/prst/ble.c b/code/b-parasite/src/prst/ble.c index bd1c4ce..41a08c1 100644 --- a/code/b-parasite/src/prst/ble.c +++ b/code/b-parasite/src/prst/ble.c @@ -210,8 +210,8 @@ static void set_service_data_bthome_protocol( // 1. Soil moisture. // uint16_t. service_data[0] = (0b000 << 5) | 2; - // Type of measurement. Temporarily using humidity. - service_data[1] = 0x03; + // Type of measurement - Moisture. + service_data[1] = 0x14; // Value. Factor of 0.01, so we need to multiply our the value in 100% by // 1/0.01 = 100. uint16_t soil_val = (10000 * sensors->soil_moisture) / UINT16_MAX; @@ -248,16 +248,16 @@ static void set_service_data_bthome_protocol( service_data[14] = batt_val & 0xff; service_data[15] = batt_val >> 8; - // 5. Illuminance - // uint24_t. - service_data[16] = (0b000 << 5) | 2; - // Type - illuminance. - service_data[17] = 0x05; - // Value. Factor 0.01. - uint32_t illu_value = 100 * sensors->lux; - service_data[18] = illu_value & 0xff; - service_data[19] = (illu_value >> 8) & 0xff; - service_data[20] = (illu_value >> 16) & 0xff; + // // 5. Illuminance + // // uint24_t. + // service_data[16] = (0b000 << 5) | 2; + // // Type - illuminance. + // service_data[17] = 0x05; + // // Value. Factor 0.01. + // uint32_t illu_value = 100 * sensors->lux; + // service_data[18] = illu_value & 0xff; + // service_data[19] = (illu_value >> 8) & 0xff; + // service_data[20] = (illu_value >> 16) & 0xff; } #endif // PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME From 81f0af3cf25c5784d010970495932237082cf040 Mon Sep 17 00:00:00 2001 From: rbaron Date: Mon, 29 Aug 2022 21:17:17 +0200 Subject: [PATCH 3/3] Fixes humidity factor & removes illumminance The reason for removing illuminance is to leave enough space for the full name AD element "prst". There may be options for also sending the illuminance (extended adv, scan response etc), with different power consumption/overhead/complexity tradeoffs. For now let's keep it simple. --- code/b-parasite/src/prst/ble.c | 17 +++-------------- 1 file changed, 3 insertions(+), 14 deletions(-) diff --git a/code/b-parasite/src/prst/ble.c b/code/b-parasite/src/prst/ble.c index 41a08c1..d263701 100644 --- a/code/b-parasite/src/prst/ble.c +++ b/code/b-parasite/src/prst/ble.c @@ -48,7 +48,7 @@ prst_config.h. #define SERVICE_DATA_LEN 18 #elif PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME #define SERVICE_UUID 0x181c -#define SERVICE_DATA_LEN 21 +#define SERVICE_DATA_LEN 16 #else #error "PRST_BLE_PROTOCOL is not properly configured" #endif @@ -233,8 +233,8 @@ static void set_service_data_bthome_protocol( service_data[8] = (0b000 << 5) | 2; // Type - humidity. service_data[9] = 0x03; - // Value. Factor 0.01. - uint16_t humi_val = (100 * sensors->humi) / UINT16_MAX; + // Value. Factor 0.01, over 100%. + uint16_t humi_val = (10000 * sensors->humi) / UINT16_MAX; service_data[10] = humi_val & 0xff; service_data[11] = humi_val >> 8; @@ -247,17 +247,6 @@ static void set_service_data_bthome_protocol( uint16_t batt_val = sensors->batt_mv; service_data[14] = batt_val & 0xff; service_data[15] = batt_val >> 8; - - // // 5. Illuminance - // // uint24_t. - // service_data[16] = (0b000 << 5) | 2; - // // Type - illuminance. - // service_data[17] = 0x05; - // // Value. Factor 0.01. - // uint32_t illu_value = 100 * sensors->lux; - // service_data[18] = illu_value & 0xff; - // service_data[19] = (illu_value >> 8) & 0xff; - // service_data[20] = (illu_value >> 16) & 0xff; } #endif // PRST_BLE_PROTOCOL == PRST_BLE_PROTOCOL_BTHOME