Merge pull request #81 from rbaron/zigbee-sample

Introduce an experimental/educactional/exploratory Zigbee firmware sample
This commit is contained in:
rbaron 2022-12-24 10:29:38 +01:00 committed by GitHub
commit 54792ce35f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 808 additions and 24 deletions

View file

@ -7,8 +7,15 @@ REVISION=$3
CMAKE_EXTRA=$4
OUTPUT_BIN=$5
TODAY=$(date +'%Y-%m-%d')
# Replaces occurrences of "__TODAY__" with $TODAY in $CMAKE_EXTRA.
CMAKE_EXTRA="${CMAKE_EXTRA/__TODAY__/"$TODAY"}"
cd "${GITHUB_WORKSPACE}/${SAMPLE_DIR}"
echo $CMAKE_EXTRA
west build --build-dir ./build --pristine --board "${BOARD}@${REVISION}" -- $CMAKE_EXTRA
mv build/zephyr/zephyr.hex build/zephyr/"${OUTPUT_BIN}"

View file

@ -17,6 +17,7 @@ jobs:
uses: jidicula/clang-format-action@v4.9.0
with:
check-path: 'code/nrf-connect'
exclude-regex: '\/build\/'
- name: Build blinky
uses: ./.github/actions/build
with:
@ -71,4 +72,11 @@ jobs:
with:
sample-dir: code/nrf-connect/samples/ble
board: bparasite_nrf52833
output-bin: ble_nrf52833_default.hex
output-bin: ble_nrf52833_default.hex
- name: Build zigbee_nrf528340_default.hex
uses: ./.github/actions/build_and_upload
with:
sample-dir: code/nrf-connect/samples/zigbee
board: bparasite_nrf52840
output-bin: zigbee_nrf52840_default.hex
cmake-extra: -DPRST_ZB_BUILD_DATE=__TODAY__

View file

@ -21,6 +21,7 @@ This repository also hosts a few different firmware samples for b-parasite.
|Sample|Description|Extra Documentation|
|---|---|---|
|[samples/ble](./code/nrf-connect/samples/ble)|This is the most battle-tested and useful firmware. It periodically reads all sensors and broadcast them via Bluetooth Low Energy (BLE). It works with [Home Assistant](https://www.home-assistant.io/) out of the box. |[Docs](./code/nrf-connect/samples/ble/README.md)|
|[samples/zigbee](./code/nrf-connect/samples/zigbee)| An experimental/educational/exploratory basic Zigbee sample built on [nRF Connect + ZBOSS](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/ug_zigbee.html). It integrates with [Home Assistant](https://www.home-assistant.io/) via [ZHA](https://www.home-assistant.io/integrations/zha) or [Zigbee2MQTT](https://www.zigbee2mqtt.io/). |[Docs](./code/nrf-connect/samples/zigbee/README.md)|
|[samples/blinky](./code/nrf-connect/samples/blinky)| The classic "Hello, world" |-|
|[samples/soil_read_loop](./code/nrf-connect/samples/soil_read_loop)| Read the soil moisture sensor on a loop. Useful for experimenting and calibrating the sensor. |-|

View file

@ -0,0 +1,3 @@
module = PRSTLIB
module-str = prstlib
source "subsys/logging/Kconfig.template.log_config"

View file

@ -6,4 +6,4 @@ config BOARD_ENABLE_DCDC
select SOC_DCDC_NRF52X
default y
endif # BOARD_BPARASITE_NRF52833
endif # BOARD_BPARASITE_NRF52833

View file

@ -11,4 +11,4 @@ config BOARD_ENABLE_DCDC_HV
select SOC_DCDC_NRF52X_HV
default y
endif # BOARD_BPARASITE_NRF52840
endif # BOARD_BPARASITE_NRF52840

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

@ -9,7 +9,7 @@
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(adc, LOG_LEVEL_WRN);
LOG_MODULE_REGISTER(adc, CONFIG_PRSTLIB_LOG_LEVEL);
// PWM spec for square wave. Input to the soil sensing circuit.
static const struct pwm_dt_spec soil_pwm_dt =
@ -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

@ -6,7 +6,7 @@
#include "prstlib/led.h"
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(button, LOG_LEVEL_WRN);
LOG_MODULE_REGISTER(button, CONFIG_PRSTLIB_LOG_LEVEL);
static struct gpio_dt_spec button =
GPIO_DT_SPEC_GET(DT_NODELABEL(button0), gpios);

View file

@ -4,7 +4,7 @@
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(led, LOG_LEVEL_WRN);
LOG_MODULE_REGISTER(led, CONFIG_PRSTLIB_LOG_LEVEL);
struct gpio_dt_spec led = GPIO_DT_SPEC_GET(DT_NODELABEL(led0), gpios);

View file

@ -6,21 +6,22 @@
#include "prstlib/led.h"
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(sensors, LOG_LEVEL_WRN);
LOG_MODULE_REGISTER(sensors, CONFIG_PRSTLIB_LOG_LEVEL);
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("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("Batt: %d mV (%.2f%%)", sensors->batt.adc_read.millivolts,
100 * sensors->batt.percentage);
LOG_DBG("Soil: %.0f %% (%d mV)", 100 * sensors->soil.percentage,
sensors->soil.adc_read.millivolts);
LOG_DBG("Photo: %u lx (%d mV)", sensors->photo.brightness,
sensors->soil.adc_read.millivolts);
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

@ -6,7 +6,7 @@
#include "prstlib/macros.h"
LOG_MODULE_REGISTER(shtc3, LOG_LEVEL_WRN);
LOG_MODULE_REGISTER(shtc3, CONFIG_PRSTLIB_LOG_LEVEL);
static const struct i2c_dt_spec shtc3 = I2C_DT_SPEC_GET(DT_NODELABEL(shtc3));

View file

@ -1,5 +1,6 @@
source "Kconfig.zephyr"
rsource "../../prstlib/Kconfig"
config PRST_SLEEP_DURATION_SEC
int "Sleep duration in seconds"

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

@ -0,0 +1,2 @@
source "Kconfig.zephyr"
rsource "../../prstlib/Kconfig"

View file

@ -0,0 +1,2 @@
build
build_*

View file

@ -0,0 +1,21 @@
cmake_minimum_required(VERSION 3.20.0)
# Pull in the dts/ and boards/ from prstlib.
set(DTS_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../prstlib)
set(BOARD_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../prstlib)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project("b-parasite Zigbee sample")
include_directories(src)
target_sources(app PRIVATE
src/main.c
src/prst_zb_attrs.c
src/prst_zb_soil_moisture_defs.c
)
add_subdirectory(../../prstlib prstlib)
target_include_directories(app PRIVATE ../../prstlib/include)
target_link_libraries(app PUBLIC prstlib)

View file

@ -0,0 +1,22 @@
source "Kconfig.zephyr"
rsource "../../prstlib/Kconfig"
config PRST_ZB_SLEEP_DURATION_SEC
int "Sleep duration between waking up and reading sensors in seconds."
default 600
config PRST_ZB_PARENT_POLL_INTERVAL_SEC
int "Interval for when b-parasite polls its parent for data in seconds."
default 60
config PRST_ZB_BUILD_DATE
string "Zigbee basic cluster build date attribute. Max 16 bytes."
default ""
config PRST_ZB_MODEL_ID
string "Zigbee basic cluster model id attribute. Max 32 bytes."
default "b-parasite"
config PRST_ZB_HARDWARE_VERSION
int "Zigbee basic cluster hardware version attribute. 1 byte."
default 2

View file

@ -0,0 +1,46 @@
# Zigbee firmware sample
This sample is adapted from the [zigbee_template](https://developer.nordicsemi.com/nRF_Connect_SDK/doc/latest/nrf/samples/zigbee/template/README.html) from the nRF Connect SDK. It's a basic experimental/educational/exploratory firmware sample for b-parasite.
## Clusters
These [clusters](https://en.wikipedia.org/wiki/Zigbee#Cluster_library) are defined in the sample:
|Cluster ID|Name|
|--------|---|
|0x0001|Power Configuration|
|0x0400|Illuminance Measurement|
|0x0402|Temperature Measurement|
|0x0405|Relative Humidity Measurement|
|0x0408|Soil Moisture Measurement|
## Pairing Mode
The sample will first boot and start looking for a Zigbee coordinator - in pairing mode. The onboard LED will be flashing once a second while in this mode. Once a suitable network is found, the LED will briefly flash 3 times and remain off.
### Factory Reset
Most Zigbee devices provide a physical button to "factory reset" it - causing it to forget its joined network and look for a new one.
b-parasite has no physical buttons, and the implemented work around is to distinguish between two *reset modes*:
#### Power up mode
The device enters this mode when it is powered. For example, swapping an old battery or connecting to eternal power. This is the "usual" reset mode, and joined networks will be remembered.
#### Reset pin mode
If the device's RESET pin is briefly grounded, the device will effectively be **factory reset**. The device will leave its previous network and start looking for a new one.
## Configs
Available options in `Kconfig`. Notable options:
* `CONFIG_PRST_ZB_SLEEP_DURATION_SEC`: amount of time (in seconds) the device sleeps between reading all sensors and updating its clusters
* `CONFIG_PRST_ZB_PARENT_POLL_INTERVAL_SEC`: amount of time (in seconds) the device waits between polling its parent for data
## Home Assistant Integration
This firmware sample has only been tested with Home Assistant, using one of the following integrations.
### Zigbee Home Automation (ZHA)
With the [ZHA](https://www.home-assistant.io/integrations/zha) Home Assistant integration, b-parasite should work out of the box.
### Zigbee2MQTT & Home Assistant
With [Zigbee2MQTT](https://zigbee2mqtt.io/), a custom converter is required. The [b-parasite.js](b-parasite.js) file contains such a converter. See [Support new devices](https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html) for instructions.
## Battery Life
While sleeping, the device consumes around 2 uA:
![sleeping current](./media/power-profile/sleeping.png)
In the active cycle, it averages around 125 uA for 1 second:
![active current](media/power-profile/active.png)

View file

@ -0,0 +1,47 @@
// This is a zigbee2mqtt zigbee2mqtt.io converter for this sample.
// See https://www.zigbee2mqtt.io/advanced/support-new-devices/01_support_new_devices.html
// on how to use it.
const fz = require('zigbee-herdsman-converters/converters/fromZigbee');
const tz = require('zigbee-herdsman-converters/converters/toZigbee');
const exposes = require('zigbee-herdsman-converters/lib/exposes');
const reporting = require('zigbee-herdsman-converters/lib/reporting');
const extend = require('zigbee-herdsman-converters/lib/extend');
const e = exposes.presets;
const ea = exposes.access;
const definition = {
zigbeeModel: ['b-parasite'],
model: 'b-parasite',
vendor: 'b-parasite',
description: 'IoT development board - Zigbee sample',
fromZigbee: [fz.temperature, fz.humidity, fz.battery, fz.soil_moisture, fz.illuminance],
toZigbee: [],
exposes: [
e.temperature(),
e.humidity(),
e.battery(),
e.soil_moisture(),
e.illuminance_lux()],
configure: async (device, coordinatorEndpoint, logger) => {
const endpoint = device.getEndpoint(10);
await reporting.bind(
endpoint,
coordinatorEndpoint, [
'genPowerCfg',
'msTemperatureMeasurement',
'msRelativeHumidity',
'msSoilMoisture',
'msIlluminanceMeasurement',
]);
await reporting.batteryPercentageRemaining(endpoint);
// Not reportable :(
// await reporting.batteryVoltage(endpoint);
await reporting.temperature(endpoint);
await reporting.humidity(endpoint);
await reporting.soil_moisture(endpoint);
await reporting.illuminance(endpoint);
}
};
module.exports = definition;

Binary file not shown.

After

Width:  |  Height:  |  Size: 86 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 84 KiB

View file

@ -0,0 +1,43 @@
CONFIG_LOG=y
CONFIG_PWM=y
CONFIG_I2C=y
CONFIG_ADC=y
CONFIG_GPIO=y
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_SERIAL=n
CONFIG_HEAP_MEM_POOL_SIZE=2048
CONFIG_MAIN_THREAD_PRIORITY=7
CONFIG_ZIGBEE=y
CONFIG_ZIGBEE_APP_UTILS=y
CONFIG_ZIGBEE_ROLE_END_DEVICE=y
# This example requires more workqueue stack
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
# Enable nRF ECB driver
CONFIG_CRYPTO=y
CONFIG_CRYPTO_NRF_ECB=y
CONFIG_CRYPTO_INIT_PRIORITY=80
# Networking
CONFIG_NET_IPV6_MLD=n
CONFIG_NET_IPV6_NBR_CACHE=n
CONFIG_NET_IPV6_RA_RDNSS=n
CONFIG_NET_IP_ADDR_CHECK=n
CONFIG_NET_UDP=n
# Get Zigbee to scan every channel.
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

@ -0,0 +1,49 @@
CONFIG_LOG=y
CONFIG_PWM=y
CONFIG_I2C=y
CONFIG_ADC=y
CONFIG_GPIO=y
CONFIG_PM=y
CONFIG_PM_DEVICE=y
CONFIG_NEWLIB_LIBC=y
CONFIG_NEWLIB_LIBC_FLOAT_PRINTF=y
CONFIG_SERIAL=n
CONFIG_HEAP_MEM_POOL_SIZE=2048
CONFIG_MAIN_THREAD_PRIORITY=7
CONFIG_ZIGBEE=y
CONFIG_ZIGBEE_APP_UTILS=y
CONFIG_ZIGBEE_ROLE_END_DEVICE=y
# This example requires more workqueue stack
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
# Enable nRF ECB driver
CONFIG_CRYPTO=y
CONFIG_CRYPTO_NRF_ECB=y
CONFIG_CRYPTO_INIT_PRIORITY=80
# Networking
CONFIG_NET_IPV6_MLD=n
CONFIG_NET_IPV6_NBR_CACHE=n
CONFIG_NET_IPV6_RA_RDNSS=n
CONFIG_NET_IP_ADDR_CHECK=n
CONFIG_NET_UDP=n
# Get Zigbee to scan every channel.
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
# Config options in Kconfig.
CONFIG_PRST_ZB_SLEEP_DURATION_SEC=10
CONFIG_LOG_DEFAULT_LEVEL=4
CONFIG_PRSTLIB_LOG_LEVEL_DBG=y

View file

@ -0,0 +1,232 @@
#include <dk_buttons_and_leds.h>
#include <hal/nrf_power.h>
#include <math.h>
#include <prstlib/adc.h>
#include <prstlib/button.h>
#include <prstlib/led.h>
#include <prstlib/macros.h>
#include <prstlib/sensors.h>
#include <prstlib/shtc3.h>
#include <ram_pwrdn.h>
#include <zb_nrf_platform.h>
#include <zboss_api.h>
#include <zboss_api_addons.h>
#include <zcl/zb_zcl_power_config.h>
#include <zephyr/kernel.h>
#include <zephyr/logging/log.h>
#include <zigbee/zigbee_app_utils.h>
#include <zigbee/zigbee_error_handler.h>
#include "prst_zb_attrs.h"
#include "prst_zb_endpoint_defs.h"
#include "prst_zb_soil_moisture_defs.h"
#define FACTORY_RESET_BUTTON DK_BTN4_MSK
LOG_MODULE_REGISTER(app, CONFIG_LOG_DEFAULT_LEVEL);
static struct zb_device_ctx dev_ctx;
static prst_sensors_t sensors;
static void maybe_erase_pairing_info(struct k_timer *timer) {
uint32_t reset_reason = nrf_power_resetreas_get(NRF_POWER);
// If we're resetting via the RESET pin (e.g.: reset pin shorting, firmware flashing).
if (reset_reason & 0x1) {
LOG_WRN("Manual reset / re-flashing detected - erasing pairing info");
// TODO: consider zb_bdb_reset_via_local_action(/*param=*/0);
zigbee_erase_persistent_storage(/*erase=*/true);
} else { // It's a power-on cycle (e.g.: swapping battery, first boot).
LOG_INF("Power-on cycle - keeping pairing info");
}
}
static void led_flashing_cb(struct k_timer *timer) {
prst_led_toggle();
}
K_TIMER_DEFINE(led_flashing_timer, led_flashing_cb, /*stop_fn=*/NULL);
ZB_ZCL_DECLARE_IDENTIFY_ATTRIB_LIST(
identify_attr_list,
&dev_ctx.identify_attr.identify_time);
ZB_ZCL_DECLARE_BASIC_ATTRIB_LIST_EXT(
basic_attr_list,
&dev_ctx.basic_attr.zcl_version,
&dev_ctx.basic_attr.app_version,
&dev_ctx.basic_attr.stack_version,
&dev_ctx.basic_attr.hw_version,
&dev_ctx.basic_attr.mf_name,
&dev_ctx.basic_attr.model_id,
&dev_ctx.basic_attr.date_code,
&dev_ctx.basic_attr.power_source,
&dev_ctx.basic_attr.location_id,
&dev_ctx.basic_attr.ph_env,
&dev_ctx.basic_attr.sw_ver);
ZB_ZCL_DECLARE_TEMP_MEASUREMENT_ATTRIB_LIST(temp_measurement_attr_list,
&dev_ctx.temp_measure_attrs.measure_value,
&dev_ctx.temp_measure_attrs.min_measure_value,
&dev_ctx.temp_measure_attrs.max_measure_value,
&dev_ctx.temp_measure_attrs.tolerance);
ZB_ZCL_DECLARE_REL_HUMIDITY_MEASUREMENT_ATTRIB_LIST(
rel_humi_attr_list,
&dev_ctx.rel_humidity_attrs.rel_humidity,
&dev_ctx.rel_humidity_attrs.min_val,
&dev_ctx.rel_humidity_attrs.max_val);
// https://devzone.nordicsemi.com/f/nordic-q-a/85315/zboss-declare-power-config-attribute-list-for-battery-bat_num
#define bat_num
ZB_ZCL_DECLARE_POWER_CONFIG_BATTERY_ATTRIB_LIST_EXT(
batt_attr_list,
&dev_ctx.batt_attrs.voltage,
/*battery_size=*/ZB_ZCL_POWER_CONFIG_BATTERY_SIZE_OTHER,
/*battery_quantity=*/1,
/*battery_rated_voltage=*/NULL,
/*battery_alarm_mask=*/NULL,
/*battery_voltage_min_threshold=*/NULL,
/*battery_percentage_remaining=*/&dev_ctx.batt_attrs.percentage,
/*battery_voltage_threshold1=*/NULL,
/*battery_voltage_threshold2=*/NULL,
/*battery_voltage_threshold3=*/NULL,
/*battery_percentage_min_threshold=*/NULL,
/*battery_percentage_threshold1=*/NULL,
/*battery_percentage_threshold2=*/NULL,
/*battery_percentage_threshold3=*/NULL,
/*battery_alarm_state=*/NULL);
PRST_ZB_ZCL_DECLARE_SOIL_MOISTURE_ATTRIB_LIST(
soil_moisture_attr_list,
&dev_ctx.soil_moisture_attrs.percentage);
ZB_ZCL_DECLARE_ILLUMINANCE_MEASUREMENT_ATTRIB_LIST(
illuminance_attr_list,
/*value=*/&dev_ctx.illuminance_attrs.log_lux,
/*min_value=*/NULL,
/*max_value*/ NULL);
PRST_ZB_DECLARE_CLUSTER_LIST(
app_template_clusters,
basic_attr_list,
identify_attr_list,
temp_measurement_attr_list,
rel_humi_attr_list,
batt_attr_list,
soil_moisture_attr_list,
illuminance_attr_list);
PRST_ZB_DECLARE_ENDPOINT(
app_template_ep,
PRST_ZIGBEE_ENDPOINT,
app_template_clusters);
ZBOSS_DECLARE_DEVICE_CTX_1_EP(
app_template_ctx,
app_template_ep);
void zboss_signal_handler(zb_bufid_t bufid) {
// See zigbee_default_signal_handler() for all available signals.
zb_zdo_app_signal_type_t sig = zb_get_app_signal(bufid, /*sg_p=*/NULL);
zb_ret_t status = ZB_GET_APP_SIGNAL_STATUS(bufid);
switch (sig) {
case ZB_BDB_SIGNAL_STEERING: // New network.
case ZB_BDB_SIGNAL_DEVICE_REBOOT: { // Previously joined network.
LOG_DBG("Steering complete. Status: %d", status);
prst_led_flash(/*times=*/3);
if (status == RET_OK) {
k_timer_stop(&led_flashing_timer);
prst_led_off();
}
break;
}
case ZB_ZDO_SIGNAL_SKIP_STARTUP:
case ZB_ZDO_SIGNAL_LEAVE: {
LOG_DBG("Will restart flashing");
k_timer_start(&led_flashing_timer, K_NO_WAIT, K_SECONDS(1));
break;
}
}
ZB_ERROR_CHECK(zigbee_default_signal_handler(bufid));
if (bufid) {
zb_buf_free(bufid);
}
}
void update_sensors_cb(zb_uint8_t arg) {
LOG_INF("Updating sensors");
if (prst_sensors_read_all(&sensors)) {
LOG_ERR("Unable to read sensors");
return;
}
// Battery voltage 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);
// 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);
// 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);
// 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;
prst_zb_set_attr_value(PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID,
PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_VALUE_ID,
&soil_moisture);
// Illuminance in 10000 * log_10(lux) + 1.
zb_int16_t log_lux = 10000 * log10((float)sensors.photo.brightness) + 1;
prst_zb_set_attr_value(ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT,
ZB_ZCL_ATTR_ILLUMINANCE_MEASUREMENT_MEASURED_VALUE_ID,
&log_lux);
ZB_SCHEDULE_APP_ALARM(update_sensors_cb,
/*param=*/0,
ZB_TIME_ONE_SECOND * CONFIG_PRST_ZB_SLEEP_DURATION_SEC);
}
int main(void) {
RET_IF_ERR(prst_adc_init());
RET_IF_ERR(prst_led_init());
RET_IF_ERR(prst_button_init());
maybe_erase_pairing_info(NULL);
register_factory_reset_button(FACTORY_RESET_BUTTON);
prst_zb_attrs_init(&dev_ctx);
ZB_AF_REGISTER_DEVICE_CTX(&app_template_ctx);
update_sensors_cb(/*arg=*/0);
zb_zdo_pim_set_long_poll_interval(
ZB_TIME_ONE_SECOND * CONFIG_PRST_ZB_PARENT_POLL_INTERVAL_SEC);
power_down_unused_ram();
RET_IF_ERR(prst_led_flash(2));
zigbee_enable();
zigbee_configure_sleepy_behavior(/*enable=*/true);
return 0;
}

View file

@ -0,0 +1,38 @@
#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,
CONFIG_PRST_ZB_MODEL_ID,
ZB_ZCL_STRING_CONST_SIZE(CONFIG_PRST_ZB_MODEL_ID));
ZB_ZCL_SET_STRING_VAL(
dev_ctx->basic_attr.date_code,
CONFIG_PRST_ZB_BUILD_DATE,
ZB_ZCL_STRING_CONST_SIZE(CONFIG_PRST_ZB_BUILD_DATE));
dev_ctx->basic_attr.hw_version = CONFIG_PRST_ZB_HARDWARE_VERSION;
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

@ -0,0 +1,50 @@
#ifndef _PRST_ZB_ATTRS_H_
#define _PRST_ZB_ATTRS_H_
#include <zboss_api_addons.h>
// Relative humimdity cluster - section 4.7.2.2.1.
typedef struct {
// 100x relative humidity (mandatory).
zb_uint16_t rel_humidity;
// 0x0000 0x270f (mandatory).
zb_uint16_t min_val;
// 0x0001 0x2710 (mandatory).
zb_uint16_t max_val;
} prst_rel_humidity_attrs_t;
// Power configuration cluster - section 3.3.2.2.3.
typedef struct {
// Units of 100 mV. 0x00 - 0xff (optional, not reportable :()).
zb_uint8_t voltage;
// Units of 0.5%. 0x00 (0%) - 0xc8 (100%) (optional, reportable).
zb_uint8_t percentage;
} prst_batt_attrs_t;
// Soil moisture cluster.
typedef struct {
// 0-100, units of 0.01?
zb_uint16_t percentage;
} prst_soil_moisture_attrs_t;
// Illuminance cluster - section 4.2.2.2.
typedef struct {
// 10000 * log_10(lux) + 1.
zb_uint16_t log_lux;
} prst_illuminancce_attrs_t;
struct zb_device_ctx {
zb_zcl_basic_attrs_ext_t basic_attr;
zb_zcl_identify_attrs_t identify_attr;
zb_zcl_temp_measurement_attrs_t temp_measure_attrs;
prst_rel_humidity_attrs_t rel_humidity_attrs;
prst_batt_attrs_t batt_attrs;
prst_soil_moisture_attrs_t soil_moisture_attrs;
prst_illuminancce_attrs_t illuminance_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

@ -0,0 +1,103 @@
#ifndef _PRST_ZB_H_
#define _PRST_ZB_H_
#include "prst_zb_soil_moisture_defs.h"
#define PRST_ZIGBEE_ENDPOINT 10
#define PRST_BASIC_MANUF_NAME "b-parasite"
#define PRST_ZB_DEVICE_ID 0x0008
#define PRST_ZB_DEVICE_VERSION 0
#define PRST_ZB_IN_CLUSTER_NUM 7
#define PRST_ZB_OUT_CLUSTER_NUM 0
#define PRST_ZB_CLUSTER_NUM (PRST_ZB_IN_CLUSTER_NUM + PRST_ZB_OUT_CLUSTER_NUM)
#define PRST_ZB_ATTR_REPORTING_COUNT 5
#define PRST_ZB_DECLARE_CLUSTER_LIST( \
cluster_list_name, \
basic_attr_list, \
identify_attr_list, \
temp_measurement_attr_list, \
rel_humidity_attr_list, \
batt_att_list, \
soil_moisture_attr_list, \
illuminance_attr_list) \
zb_zcl_cluster_desc_t cluster_list_name[] = \
{ \
ZB_ZCL_CLUSTER_DESC( \
ZB_ZCL_CLUSTER_ID_IDENTIFY, \
ZB_ZCL_ARRAY_SIZE(identify_attr_list, zb_zcl_attr_t), \
(identify_attr_list), \
ZB_ZCL_CLUSTER_SERVER_ROLE, \
ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC( \
ZB_ZCL_CLUSTER_ID_BASIC, \
ZB_ZCL_ARRAY_SIZE(basic_attr_list, zb_zcl_attr_t), \
(basic_attr_list), \
ZB_ZCL_CLUSTER_SERVER_ROLE, \
ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC( \
ZB_ZCL_CLUSTER_ID_TEMP_MEASUREMENT, \
ZB_ZCL_ARRAY_SIZE(temp_measurement_attr_list, zb_zcl_attr_t), \
(temp_measurement_attr_list), \
ZB_ZCL_CLUSTER_SERVER_ROLE, \
ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC( \
ZB_ZCL_CLUSTER_ID_REL_HUMIDITY_MEASUREMENT, \
ZB_ZCL_ARRAY_SIZE(rel_humidity_attr_list, zb_zcl_attr_t), \
(rel_humi_attr_list), \
ZB_ZCL_CLUSTER_SERVER_ROLE, \
ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC( \
PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID, \
ZB_ZCL_ARRAY_SIZE(soil_moisture_attr_list, zb_zcl_attr_t), \
(soil_moisture_attr_list), \
ZB_ZCL_CLUSTER_SERVER_ROLE, \
ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC( \
ZB_ZCL_CLUSTER_ID_ILLUMINANCE_MEASUREMENT, \
ZB_ZCL_ARRAY_SIZE(illuminance_attr_list, zb_zcl_attr_t), \
(illuminance_attr_list), \
ZB_ZCL_CLUSTER_SERVER_ROLE, \
ZB_ZCL_MANUF_CODE_INVALID), \
ZB_ZCL_CLUSTER_DESC( \
ZB_ZCL_CLUSTER_ID_POWER_CONFIG, \
ZB_ZCL_ARRAY_SIZE(batt_attr_list, zb_zcl_attr_t), \
(batt_attr_list), \
ZB_ZCL_CLUSTER_SERVER_ROLE, \
ZB_ZCL_MANUF_CODE_INVALID)}
#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_ILLUMINANCE_MEASUREMENT, \
ZB_ZCL_CLUSTER_ID_POWER_CONFIG, \
}}
#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); \
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, \
(zb_af_simple_desc_1_1_t *)&simple_desc_##ep_name, \
PRST_ZB_ATTR_REPORTING_COUNT, reporting_ctx_##ep_name, \
/*lev_ctrl_count=*/0, /*lev_ctrl_ctx=*/NULL)
#endif // _PRST_ZB_H_

View file

@ -0,0 +1,15 @@
#include "prst_zb_soil_moisture_defs.h"
#include <zboss_api.h>
void prst_zcl_soil_moisture_init_server(void) {
zb_zcl_add_cluster_handlers(PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID,
ZB_ZCL_CLUSTER_SERVER_ROLE,
/*cluster_check_value=*/NULL,
/*cluster_write_attr_hook=*/NULL,
/*cluster_handler=*/NULL);
}
void prst_zcl_soil_moisture_init_client(void) {
// Nothing.
}

View file

@ -0,0 +1,40 @@
#ifndef _PRST_ZB_SOIL_MOISTURE_DEFS_
#define _PRST_ZB_SOIL_MOISTURE_DEFS_
#include <zboss_api.h>
#include <zcl/zb_zcl_common.h>
// Most defines in this file are updated from the ZB_ZCL_DECLARE_TEMP_MEASUREMENT_ATTRIB_LIST,
// adapting attributes and IDs to match the mSoilMoisture cluster spec.
// Values from https://github.com/Koenkk/zigbee-herdsman/blob/master/src/zcl/definition/cluster.ts#L2570
// (msSoilMoisture).
// Cluster attributes definitions in https://www.st.com/resource/en/user_manual/um2977-stm32wb-series-zigbee-cluster-library-api-stmicroelectronics.pdf.
#define PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID 1032
// Soil moisture value represented as an uint16.
#define PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_VALUE_ID 0x00
// Required callbacks. ZBOSS will call these.
void prst_zcl_soil_moisture_init_server(void);
void prst_zcl_soil_moisture_init_client(void);
#define PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID_SERVER_ROLE_INIT prst_zcl_soil_moisture_init_server
#define PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_CLUSTER_ID_CLIENT_ROLE_INIT prst_zcl_soil_moisture_init_client
#define PRST_ZB_ZCL_SOIL_MOISTURE_CLUSTER_REVISION_DEFAULT ((zb_uint16_t)0x42)
#define ZB_SET_ATTR_DESCR_WITH_PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_VALUE_ID(data_ptr) \
{ \
PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_VALUE_ID, \
ZB_ZCL_ATTR_TYPE_U16, \
ZB_ZCL_ATTR_ACCESS_READ_ONLY | ZB_ZCL_ATTR_ACCESS_REPORTING, \
(void*)data_ptr \
}
#define PRST_ZB_ZCL_DECLARE_SOIL_MOISTURE_ATTRIB_LIST(attr_list, value) \
ZB_ZCL_START_DECLARE_ATTRIB_LIST_CLUSTER_REVISION(attr_list, PRST_ZB_ZCL_SOIL_MOISTURE) \
ZB_ZCL_SET_ATTR_DESC(PRST_ZB_ZCL_ATTR_SOIL_MOISTURE_VALUE_ID, (value)) \
ZB_ZCL_FINISH_DECLARE_ATTRIB_LIST
#endif // _PRST_ZB_SOIL_MOISTURE_DEFS_

View file

@ -0,0 +1,16 @@
{
"folders": [
{
"path": "."
},
{
"path": "../../prstlib"
}
],
"settings": {
"C_Cpp.autoAddFileAssociations": false,
"nrf-connect.applications": [
"${workspaceFolder}"
]
}
}