Add battery percentage computation

This commit is contained in:
rbaron 2022-12-09 19:02:55 +01:00
parent 676b525bd9
commit cb6ed73691
11 changed files with 156 additions and 173 deletions

View file

@ -9,6 +9,11 @@ typedef struct {
float voltage;
} prst_adc_read_t;
typedef struct {
prst_adc_read_t adc_read;
float percentage;
} prst_batt_t;
typedef struct {
prst_adc_read_t adc_read;
// A value from 0 (completely dry) to 2^10 (completely wet).
@ -25,7 +30,7 @@ typedef struct prst_adc_photo_sensor {
int prst_adc_init();
int prst_adc_batt_read(prst_adc_read_t* out);
int prst_adc_batt_read(prst_batt_t* out);
int prst_adc_soil_read(float battery_voltage, prst_adc_soil_moisture_t* out);

View file

@ -7,7 +7,7 @@
typedef struct {
prst_adc_soil_moisture_t soil;
prst_adc_photo_sensor_t photo;
prst_adc_read_t batt;
prst_batt_t batt;
prst_shtc3_read_t shtc3;
} prst_sensors_t;

View file

@ -48,6 +48,37 @@ struct gpio_dt_spec ldr_enable_dt =
#endif
typedef struct {
// High (h) and low (p) voltage (v) and % (p) points.
float vh, vl, ph, pl;
} batt_disch_linear_section_t;
static void set_battery_percent(const prst_adc_read_t* read, prst_batt_t* out) {
// Must be sorted by .vh.
static const batt_disch_linear_section_t sections[] = {
{.vh = 3.00f, .vl = 2.90f, .ph = 1.00f, .pl = 0.42f},
{.vh = 2.90f, .vl = 2.74f, .ph = 0.42f, .pl = 0.18f},
{.vh = 2.74f, .vl = 2.44f, .ph = 0.18f, .pl = 0.06f},
{.vh = 2.44f, .vl = 2.01f, .ph = 0.06f, .pl = 0.00f},
};
const float v = read->voltage;
if (v > sections[0].vh) {
out->percentage = 1.0f;
return;
}
for (int i = 0; i < ARRAY_SIZE(sections); i++) {
const batt_disch_linear_section_t* s = &sections[i];
if (v > s->vl) {
out->percentage = s->pl + (v - s->vl) * ((s->ph - s->pl) / (s->vh - s->vl));
return;
}
}
out->percentage = 0.0f;
return;
}
static inline float get_soil_moisture_percent(float battery_voltage,
int16_t raw_adc_output) {
const double x = battery_voltage;
@ -93,8 +124,9 @@ int prst_adc_init() {
return 0;
}
int prst_adc_batt_read(prst_adc_read_t* out) {
RET_IF_ERR(read_adc_spec(&adc_batt_spec, out));
int prst_adc_batt_read(prst_batt_t* out) {
RET_IF_ERR(read_adc_spec(&adc_batt_spec, &out->adc_read));
set_battery_percent(&out->adc_read, out);
return 0;
}

View file

@ -10,17 +10,18 @@ LOG_MODULE_REGISTER(sensors, LOG_LEVEL_WRN);
int prst_sensors_read_all(prst_sensors_t *sensors) {
RET_IF_ERR(prst_adc_batt_read(&sensors->batt));
RET_IF_ERR(prst_adc_soil_read(sensors->batt.voltage, &sensors->soil));
RET_IF_ERR(prst_adc_photo_read(sensors->batt.voltage, &sensors->photo));
RET_IF_ERR(prst_adc_soil_read(sensors->batt.adc_read.voltage, &sensors->soil));
RET_IF_ERR(prst_adc_photo_read(sensors->batt.adc_read.voltage, &sensors->photo));
RET_IF_ERR(prst_shtc3_read(&sensors->shtc3))
LOG_DBG("Batt: %d mV", sensors->batt.millivolts);
LOG_DBG("Batt: %d mV (%.2f%%)", sensors->batt.adc_read.millivolts,
100 * sensors->batt.percentage);
LOG_DBG("Soil: %.0f %% (%.3f mV)", 100 * sensors->soil.percentage,
sensors->soil.adc_read.voltage);
LOG_DBG("Photo: %u lx (%.3f mV)", sensors->photo.brightness,
sensors->soil.adc_read.voltage);
LOG_DBG("Temp: %f oC", sensors->shtc3.temp_c);
LOG_DBG("Humi: %.0f %%", 100.0 * sensors->shtc3.rel_humi);
LOG_DBG("Humi: %.0f %%", 100 * sensors->shtc3.rel_humi);
LOG_DBG("--------------------------------------------------");
return 0;

View file

@ -25,8 +25,8 @@ int prst_ble_encode_service_data(const prst_sensors_t* sensors,
// 4 bits for a small wrap-around counter for deduplicating messages on the
// receiver.
// out[3] = sensors->run_counter & 0x0f;
out[4] = sensors->batt.millivolts >> 8;
out[5] = sensors->batt.millivolts & 0xff;
out[4] = sensors->batt.adc_read.millivolts >> 8;
out[5] = sensors->batt.adc_read.millivolts & 0xff;
int16_t temp_centicelsius = 100 * sensors->shtc3.temp_c;
out[6] = temp_centicelsius >> 8;
out[7] = temp_centicelsius & 0xff;
@ -82,7 +82,7 @@ int prst_ble_encode_service_data(const prst_sensors_t* sensors,
// Type - voltage.
out[15] = 0x0c;
// Value. Factor of 0.001.
uint16_t batt_val = sensors->batt.millivolts;
uint16_t batt_val = sensors->batt.adc_read.millivolts;
out[16] = batt_val & 0xff;
out[17] = batt_val >> 8;
@ -116,7 +116,7 @@ int prst_ble_encode_service_data(const prst_sensors_t* sensors,
// Battery voltage.
out[13] = 0x0c;
// Value. Factor of 0.001.
uint16_t batt_val = sensors->batt.millivolts;
uint16_t batt_val = sensors->batt.adc_read.millivolts;
out[14] = batt_val & 0xff;
out[15] = batt_val >> 8;
// Soil moisture.

View file

@ -12,6 +12,7 @@ include_directories(src)
target_sources(app PRIVATE
src/main.c
src/prst_zb_attrs.c
src/prst_zb_soil_moisture_defs.c
)

View file

@ -1,12 +1,5 @@
#
# Copyright (c) 2021 Nordic Semiconductor ASA
#
# SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
#
CONFIG_NCS_SAMPLES_DEFAULTS=y
CONFIG_CBPRINTF_FP_SUPPORT=y
CONFIG_LOG=y
CONFIG_PWM=y
CONFIG_I2C=y
CONFIG_ADC=y
@ -18,12 +11,7 @@ CONFIG_PM_DEVICE=y
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_UART_INTERRUPT_DRIVEN=y
CONFIG_SERIAL=y
# Make sure printk is not printing to the UART console
CONFIG_CONSOLE=y
CONFIG_UART_CONSOLE=y
CONFIG_SERIAL=n
CONFIG_HEAP_MEM_POOL_SIZE=2048
CONFIG_MAIN_THREAD_PRIORITY=7
@ -32,9 +20,6 @@ CONFIG_ZIGBEE=y
CONFIG_ZIGBEE_APP_UTILS=y
CONFIG_ZIGBEE_ROLE_END_DEVICE=y
# Enable DK LED and Buttons library
CONFIG_DK_LIBRARY=y
# This example requires more workqueue stack
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
@ -51,4 +36,8 @@ CONFIG_NET_IP_ADDR_CHECK=n
CONFIG_NET_UDP=n
# Get Zigbee to scan every channel.
CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI=y
CONFIG_ZIGBEE_CHANNEL_SELECTION_MODE_MULTI=y
# Enable API for powering down unused RAM parts.
# https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.7.1/nrf/ug_zigbee_configuring.html#power-saving-during-sleep
CONFIG_RAM_POWER_DOWN_LIBRARY=y

View file

@ -1,14 +1,3 @@
/*
* Copyright (c) 2021 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: LicenseRef-Nordic-5-Clause
*/
/** @file
*
* @brief Zigbee application template.
*/
#include <dk_buttons_and_leds.h>
#include <prstlib/adc.h>
#include <prstlib/button.h>
@ -29,8 +18,7 @@
#include "prst_zb_endpoint_defs.h"
#include "prst_zb_soil_moisture_defs.h"
#define IDENTIFY_MODE_BUTTON DK_BTN4_MSK
#define FACTORY_RESET_BUTTON IDENTIFY_MODE_BUTTON
#define FACTORY_RESET_BUTTON DK_BTN4_MSK
LOG_MODULE_REGISTER(app, LOG_LEVEL_INF);
@ -92,7 +80,7 @@ PRST_ZB_ZCL_DECLARE_SOIL_MOISTURE_ATTRIB_LIST(
soil_moisture_attr_list,
&dev_ctx.soil_moisture_attrs.percentage);
ZB_DECLARE_RANGE_EXTENDER_CLUSTER_LIST(
PRST_ZB_DECLARE_CLUSTER_LIST(
app_template_clusters,
basic_attr_list,
identify_attr_list,
@ -101,7 +89,7 @@ ZB_DECLARE_RANGE_EXTENDER_CLUSTER_LIST(
basic_attr_list,
soil_moisture_attr_list);
ZB_DECLARE_RANGE_EXTENDER_EP(
PRST_ZB_DECLARE_ENDPOINT(
app_template_ep,
PRST_ZIGBEE_ENDPOINT,
app_template_clusters);
@ -110,79 +98,6 @@ ZBOSS_DECLARE_DEVICE_CTX_1_EP(
app_template_ctx,
app_template_ep);
static void app_clusters_attr_init(void) {
dev_ctx.basic_attr.zcl_version = ZB_ZCL_VERSION;
dev_ctx.basic_attr.power_source = ZB_ZCL_BASIC_POWER_SOURCE_BATTERY;
ZB_ZCL_SET_STRING_VAL(
dev_ctx.basic_attr.mf_name,
PRST_BASIC_MANUF_NAME,
ZB_ZCL_STRING_CONST_SIZE(PRST_BASIC_MANUF_NAME));
ZB_ZCL_SET_STRING_VAL(
dev_ctx.basic_attr.model_id,
PRST_BASIC_MODEL_ID,
ZB_ZCL_STRING_CONST_SIZE(PRST_BASIC_MODEL_ID));
dev_ctx.identify_attr.identify_time =
ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE;
}
static void toggle_identify_led(zb_bufid_t bufid) {
static int blink_status;
ZB_SCHEDULE_APP_ALARM(toggle_identify_led, bufid, ZB_MILLISECONDS_TO_BEACON_INTERVAL(100));
}
static void identify_cb(zb_bufid_t bufid) {
zb_ret_t zb_err_code;
if (bufid) {
ZB_SCHEDULE_APP_CALLBACK(toggle_identify_led, bufid);
} else {
zb_err_code = ZB_SCHEDULE_APP_ALARM_CANCEL(toggle_identify_led, ZB_ALARM_ANY_PARAM);
ZVUNUSED(zb_err_code);
}
}
static void start_identifying(zb_bufid_t bufid) {
ZVUNUSED(bufid);
if (ZB_JOINED()) {
if (dev_ctx.identify_attr.identify_time ==
ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE) {
zb_ret_t zb_err_code = zb_bdb_finding_binding_target(
PRST_ZIGBEE_ENDPOINT);
if (zb_err_code == RET_OK) {
LOG_INF("Enter identify mode");
} else if (zb_err_code == RET_INVALID_STATE) {
LOG_WRN("RET_INVALID_STATE - Cannot enter identify mode");
} else {
ZB_ERROR_CHECK(zb_err_code);
}
} else {
LOG_INF("Cancel identify mode");
zb_bdb_finding_binding_target_cancel();
}
} else {
LOG_WRN("Device not in a network - cannot enter identify mode");
}
}
static void button_changed(uint32_t button_state, uint32_t has_changed) {
if (IDENTIFY_MODE_BUTTON & has_changed) {
if (IDENTIFY_MODE_BUTTON & button_state) {
} else {
if (was_factory_reset_done()) {
LOG_DBG("After Factory Reset - ignore button release");
} else {
ZB_SCHEDULE_APP_CALLBACK(start_identifying, 0);
}
}
}
check_factory_reset_button(button_state, has_changed);
}
void zboss_signal_handler(zb_bufid_t bufid) {
ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
if (bufid) {
@ -198,36 +113,37 @@ void update_sensors_cb(zb_uint8_t arg) {
return;
}
static zb_uint8_t batt = 10;
batt += 1;
zb_zcl_set_attr_val(PRST_ZIGBEE_ENDPOINT, ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_ID,
(zb_uint8_t*)&batt, ZB_FALSE);
// Battery voltlage in units of 100 mV.
uint8_t batt_voltage = sensors.batt.adc_read.millivolts / 100;
prst_zb_set_attr_value(ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_VOLTAGE_ID,
&batt_voltage);
static zb_uint8_t batt_percentage = 10;
batt_percentage += 1;
zb_zcl_set_attr_val(PRST_ZIGBEE_ENDPOINT, ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_REMAINING_ID,
(zb_uint8_t*)&batt_percentage, ZB_FALSE);
// Battery percentage in units of 0.5%.
zb_uint8_t batt_percentage = 2 * 100 * sensors.batt.percentage;
prst_zb_set_attr_value(ZB_ZCL_CLUSTER_ID_POWER_CONFIG,
ZB_ZCL_ATTR_POWER_CONFIG_BATTERY_PERCENTAGE_REMAINING_ID,
&batt_percentage);
static zb_int16_t temperature_value = 27;
temperature_value += 1;
zb_zcl_set_attr_val(PRST_ZIGBEE_ENDPOINT, ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT,
ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID,
(zb_uint8_t*)&temperature_value, ZB_FALSE);
// Temperature in units of 0.01 degrees Celcius.
zb_int16_t temperature_value = 100 * sensors.shtc3.temp_c;
prst_zb_set_attr_value(ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT,
ZB_ZCL_ATTR_TEMP_MEASUREMENT_VALUE_ID,
&temperature_value);
static zb_int16_t rel_humi = 12;
rel_humi += 1;
zb_zcl_set_attr_val(PRST_ZIGBEE_ENDPOINT, ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT,
ZB_ZCL_CLUSTER_SERVER_ROLE, ZB_ZCL_ATTR_REL_HUMIDITY_MEASUREMENT_VALUE_ID,
(zb_uint8_t*)&rel_humi, ZB_FALSE);
// Relative humidity in units of 0.01%.
zb_int16_t rel_humi = 100 * 100 * sensors.shtc3.rel_humi;
prst_zb_set_attr_value(ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT,
ZB_ZCL_ATTR_REL_HUMIDITY_MEASUREMENT_VALUE_ID,
&rel_humi);
// Soil moisture in units of 0.01%.
zb_int16_t soil_moisture = 100 * 100 * sensors.soil.percentage;
zb_zcl_set_attr_val(PRST_ZIGBEE_ENDPOINT, PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID,
ZB_ZCL_CLUSTER_SERVER_ROLE, PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_VALUE_ID,
(zb_uint8_t*)&soil_moisture, ZB_FALSE);
prst_zb_set_attr_value(PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID,
PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_VALUE_ID,
&soil_moisture);
ZB_SCHEDULE_APP_ALARM(update_sensors_cb, NULL, ZB_TIME_ONE_SECOND * 1);
ZB_SCHEDULE_APP_ALARM(update_sensors_cb, NULL, ZB_TIME_ONE_SECOND * 10);
}
int main(void) {
@ -235,23 +151,27 @@ int main(void) {
RET_IF_ERR(prst_led_init());
RET_IF_ERR(prst_button_init());
// We do this to quickly put the shtc3 to sleep.
prst_sensors_read_all(&sensors);
zigbee_configure_sleepy_behavior(/*enable=*/true);
zb_set_rx_on_when_idle(ZB_FALSE);
register_factory_reset_button(FACTORY_RESET_BUTTON);
app_clusters_attr_init();
prst_zb_attrs_init(&dev_ctx);
ZB_AF_REGISTER_DEVICE_CTX(&app_template_ctx);
ZB_AF_SET_IDENTIFY_NOTIFICATION_HANDLER(PRST_ZIGBEE_ENDPOINT, identify_cb);
update_sensors_cb(/*arg=*/0);
// zigbee_configure_sleepy_behavior(/*enable=*/true);
RET_IF_ERR(prst_led_flash(2));
// One minute.
zb_zdo_pim_set_long_poll_interval(60000);
power_down_unused_ram();
zigbee_enable();
LOG_INF("Zigbee application template started");
zigbee_configure_sleepy_behavior(/*enable=*/true);
return 0;
}

View file

@ -0,0 +1,31 @@
#include "prst_zb_attrs.h"
#include <zboss_api_addons.h>
#include "prst_zb_endpoint_defs.h"
void prst_zb_attrs_init(struct zb_device_ctx *dev_ctx) {
dev_ctx->basic_attr.zcl_version = ZB_ZCL_VERSION;
dev_ctx->basic_attr.power_source = ZB_ZCL_BASIC_POWER_SOURCE_BATTERY;
ZB_ZCL_SET_STRING_VAL(
dev_ctx->basic_attr.mf_name,
PRST_BASIC_MANUF_NAME,
ZB_ZCL_STRING_CONST_SIZE(PRST_BASIC_MANUF_NAME));
ZB_ZCL_SET_STRING_VAL(
dev_ctx->basic_attr.model_id,
PRST_BASIC_MODEL_ID,
ZB_ZCL_STRING_CONST_SIZE(PRST_BASIC_MODEL_ID));
dev_ctx->identify_attr.identify_time =
ZB_ZCL_IDENTIFY_IDENTIFY_TIME_DEFAULT_VALUE;
}
zb_zcl_status_t prst_zb_set_attr_value(zb_uint16_t cluster_id, zb_uint16_t attr_id, void *data) {
return zb_zcl_set_attr_val(PRST_ZIGBEE_ENDPOINT,
cluster_id,
ZB_ZCL_CLUSTER_SERVER_ROLE,
attr_id,
(zb_uint8_t *)data,
ZB_FALSE);
}

View file

@ -1,7 +1,7 @@
#ifndef _PRST_ZB_ATTRS_H_
#define _PRST_ZB_ATTRS_H_
#include <zboss_api.h>
#include <zboss_api_addons.h>
// Relative humimdity cluster - section 4.7.2.2.1.
typedef struct {
@ -38,4 +38,8 @@ struct zb_device_ctx {
prst_soil_moisture_attrs_t soil_moisture_attrs;
};
void prst_zb_attrs_init(struct zb_device_ctx *dev_ctx);
zb_zcl_status_t prst_zb_set_attr_value(zb_uint16_t cluster_id, zb_uint16_t attr_id, void *data);
#endif // _PRST_ZB_ATTRS_H_

View file

@ -14,7 +14,7 @@
#define PRST_ZB_CLUSTER_NUM (PRST_ZB_IN_CLUSTER_NUM + PRST_ZB_OUT_CLUSTER_NUM)
#define PRST_ZB_ATTR_REPORTING_COUNT 4
#define ZB_DECLARE_RANGE_EXTENDER_CLUSTER_LIST( \
#define PRST_ZB_DECLARE_CLUSTER_LIST( \
cluster_list_name, \
basic_attr_list, \
identify_attr_list, \
@ -61,31 +61,31 @@
ZB_ZCL_CLUSTER_SERVER_ROLE, \
ZB_ZCL_MANUF_CODE_INVALID)}
#define ZB_ZCL_DECLARE_RANGE_EXTENDER_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num) \
ZB_DECLARE_SIMPLE_DESC(in_clust_num, out_clust_num); \
ZB_AF_SIMPLE_DESC_TYPE(in_clust_num, out_clust_num) \
simple_desc_##ep_name = \
{ \
ep_id, \
ZB_AF_HA_PROFILE_ID, \
PRST_ZB_DEVICE_ID, \
PRST_ZB_DEVICE_VERSION, \
0, \
in_clust_num, \
out_clust_num, \
{ \
ZB_ZCL_CLUSTER_ID_BASIC, \
ZB_ZCL_CLUSTER_ID_IDENTIFY, \
ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, \
ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT, \
PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID, \
ZB_ZCL_CLUSTER_ID_POWER_CONFIG, \
#define PRST_ZB_DECLARE_SIMPLE_DESC(ep_name, ep_id, in_clust_num, out_clust_num) \
ZB_DECLARE_SIMPLE_DESC(in_clust_num, out_clust_num); \
ZB_AF_SIMPLE_DESC_TYPE(in_clust_num, out_clust_num) \
simple_desc_##ep_name = \
{ \
ep_id, \
ZB_AF_HA_PROFILE_ID, \
PRST_ZB_DEVICE_ID, \
PRST_ZB_DEVICE_VERSION, \
0, \
in_clust_num, \
out_clust_num, \
{ \
ZB_ZCL_CLUSTER_ID_BASIC, \
ZB_ZCL_CLUSTER_ID_IDENTIFY, \
ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, \
ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT, \
PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID, \
ZB_ZCL_CLUSTER_ID_POWER_CONFIG, \
}}
#define ZB_DECLARE_RANGE_EXTENDER_EP(ep_name, ep_id, cluster_list) \
#define PRST_ZB_DECLARE_ENDPOINT(ep_name, ep_id, cluster_list) \
ZBOSS_DEVICE_DECLARE_REPORTING_CTX(reporting_ctx_##ep_name, PRST_ZB_ATTR_REPORTING_COUNT); \
ZB_ZCL_DECLARE_RANGE_EXTENDER_SIMPLE_DESC(ep_name, ep_id, \
PRST_ZB_IN_CLUSTER_NUM, PRST_ZB_OUT_CLUSTER_NUM); \
PRST_ZB_DECLARE_SIMPLE_DESC(ep_name, ep_id, \
PRST_ZB_IN_CLUSTER_NUM, PRST_ZB_OUT_CLUSTER_NUM); \
ZB_AF_DECLARE_ENDPOINT_DESC(ep_name, ep_id, ZB_AF_HA_PROFILE_ID, \
/*reserved_length=*/0, /*reserved_ptr=*/NULL, \
ZB_ZCL_ARRAY_SIZE(cluster_list, zb_zcl_cluster_desc_t), cluster_list, \