I love the Zephyr
Project philosophy and the best way I've found to start learning is to use the nRF5 Connect SDK.
Furthermore, I am passionate about home automation and Matter
is already revolutionizing the very concept of home automation.
Anyone can build a device ready to be used in anyone's home! WOW!So I can't miss the chance to have three things in one:
- Some totally free Nordic DKs
- An incentive to start learning about Zephyr Project
- The side effect of taking the first steps on Matter
I started thinking about what I could build and obviously what I would need at home... and after a while my eyes focused on "Cacio", my turtle!
Cacio requires a controlled environment with a hot spot of approximately 30°C made by a lamp, a UVB spot made by another special lamp, the minimum water temperature of 20°C and maximum of 25°C, a filter for water and sometimes he needs food too!
My hope was to realize a controller that can be immediately used by people through commercial home automation controllers, like Google Nest.
TRYING OTBR AND MATTER-OVER-THREAD...While waiting to find out if my idea would be chosen for the assignment of the nRF7002-DK, my heart as a curious engineer immediately led me to want to experiment with MATTER over THREAD.
I already had two nRF52840-DK, a Raspberry Pi 3B+ and a Google Nest, so I spent a lot of time trying to create the OTBR with Rasperry Pi 3B+ and using one of the nRF52840-DK as RCP-Thread; I would have used the other one as a demo app to simulate a matter over thread light bulb.
Ubuntu is installed on my desktop PC and from there I downloaded the entire chip-tools repo and compiled it.
My PC and the OTBR
was connected to the same WiFi network, the router I use is a FRITZ!box 7590 in which I have deactivated the IPv6 Router Advertisement Guard.
I followed this guide step by step: "Matter over Thread: Configuring Border Router and Linux/macOS controller on separate devices — nRF Connect SDK 2.4.2 documentation (nordicsemi.com)"
Everything seemed to work fine, but the pairing of the bulb over the OTB, started by CHIP-TOOL
, Everything seemed to work fine, but the bulb pairing on the OTB, started by CHIP-TOOL, kept failing because something timed out.
I am selected to have the free nRF7002-DK
!!!
As soon as I received it I abandoned the OTBR and started to work in MATTER-OVER-WIFI manner.
I will try again with OTBR (now using the nRF82540-dongle) after the end of the contest, but actually I need to go ahead and do something...
My first attempt is to compile and flash a sample to the nRF7002-DK and pair it with my Google Nest
:
Then I use CHIP-TOOL to interact with the MATTER demo. After my experience with the explained before, I decide to use the prebuilt tool from here: "Release v2.4.1 · nrfconnect/sdk-connectedhomeip (github.com)".
Well, it's time to go in dept of my project and I use the "Adding clusters to Matter application — nRF Connect SDK 2.4.2 documentation (nordicsemi.com)" guide as my start point.
SKETCH OF THE PROJECTFor my MatTerraquarium I need to:
- [
RELAY
1] Control a light bulb - [RELAY 2] Control a special UVB light bulb
- [RELAY 3] Control the water heater
- [RELAY 4] Control the water filter pump
- [
DHT22
] Measure temperature and humidity of the "hot" zone (under the hot spot) - [
DHT11
] Measure temperature and humidity of the "cold" zone (far from the hot spot) - [
DS18B20
] Measure the water temperature - [
PWM
-SERVO
] Control the servo of the automatic feeder - [
TXB0108
] Interface the relays which works at 5V to the 1, 8V level of the nRF7002-DK
Here the repository with all Fritzing
parts and the prototype sketch: "MatTerraquarium / Fritzing Sketch · GitLab"
N.B.: the sensors and the pwm-servo interfaced via TXB0108 doesn't work (?!) so I tried to attach them directly to the DK and they work (!!).
3D PRINTED AUTOMATIC FEEDERThanks to my friend and colleague Simone Torquati - Hackster.io who has designed the 3D parts for the automatic feeder and printed them.
Here the repository with all the parts in STL format: "MatTerraquarium / Automatic Feeder · GitLab"
This is the first time I use Nordic a microcontroller so the first step I need to complete is to get the hardware up and running.
To do that I use the "Hello World — Zephyr Project documentation (nRF Connect SDK) (nordicsemi.com)" sample as my start point and then I extended it to configure and interact with my own hardware pieces.
Here is the repository of the hardware test: "MatTerraquarium / Test Hardware · GitLab"
To drive the automatic feeder servo I defined the "dts/bindings/pwm-servo.yaml" like the "Servomotor — Zephyr Project documentation (nRF Connect SDK) (nordicsemi.com)" sample:
description: PWM-driven servo motor.
compatible: "pwm-servo"
include: base.yaml
properties:
pwms:
required: true
type: phandle-array
description: PWM specifier driving the servo motor.
min-pulse:
required: true
type: int
description: Minimum pulse width (nanoseconds).
max-pulse:
required: true
type: int
description: Maximum pulse width (nanoseconds).
This define the "pwm-servo" node type as a pwm with min and max pulse attributes.
In the overlay file I instanciate a servo node
/ {
aliases {
servo = &servo0;
};
servo0: servo_0 {
compatible = "pwm-servo";
pwms = <&pwm1 0 PWM_MSEC(20) PWM_POLARITY_NORMAL>;
min-pulse = <PWM_USEC(1000)>;
max-pulse = <PWM_USEC(1390)>;
};
};
&pwm1 {
status = "okay";
pinctrl-0 = <&pwm1_default_alt>;
pinctrl-1 = <&pwm1_sleep_alt>;
pinctrl-names = "default", "sleep";
};
&pinctrl {
pwm1_default_alt: pwm1_default_alt {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 27)>;
};
};
pwm1_sleep_alt: pwm1_sleep_alt {
group1 {
psels = <NRF_PSEL(PWM_OUT0, 0, 27)>;
low-power-enable;
};
};
};
So my servo use the channel 0 of the pwm1 hw module and out the signal from P0.27.
To get temperature and humidity from the DHT11 (and DHT22) I use the "DHT: Aosong DHT Digital-output Humidity and Temperature Sensor" as the reference.
So the corresponding part of the overaly is
/ {
aliases {
dht11 = &dht110;
dht22 = &dht220;
};
dht110: dht11_0 {
compatible = "aosong,dht";
status = "okay";
dio-gpios = <&gpio0 26 (GPIO_OPEN_DRAIN | GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
};
dht220: dht22_0 {
compatible = "aosong,dht";
status = "okay";
dio-gpios = <&gpio0 25 (GPIO_OPEN_DRAIN | GPIO_PULL_UP | GPIO_ACTIVE_LOW)>;
dht22;
};
};
that means the data pin of the DHT11 sensor is connected to P0.26; the DHT22 sensor is connected to P0.25 and both are configured as active low and with a pull-up resistor.
To get temperature from the DS18B200 I use the "DS18B20 1-Wire Temperature Sensor" as the reference.
So the corresponding part of the overaly is
/ {
aliases {
ds18b20 = &ds18b200;
};
};
&uart3 {
status = "okay";
current-speed = <115200>;
pinctrl-0 = <&uart3_default_alt>;
pinctrl-1 = <&uart3_sleep_alt>;
pinctrl-names = "default", "sleep";
w1_0: w1-zephyr-serial-0 {
compatible = "zephyr,w1-serial";
status = "okay";
ds18b200: ds18b20_0 {
compatible = "maxim,ds18b20";
family-code = <0x28>;
resolution = <12>;
status = "okay";
};
};
};
&pinctrl {
uart3_default_alt: uart3_default_alt {
group1 {
psels = <NRF_PSEL(UART_RX, 0, 6)>;
bias-pull-up;
};
group2 {
psels = <NRF_PSEL(UART_TX, 0, 7)>;
nordic,drive-mode = <NRF_DRIVE_H0D1>;
};
};
uart3_sleep_alt: uart3_sleep_alt {
group1 {
psels = <NRF_PSEL(UART_RX, 0, 6)>;
low-power-enable;
};
group2 {
psels = <NRF_PSEL(UART_TX, 0, 7)>;
low-power-enable;
};
};
};
that means the data pin of the DS18B200 sensor is connected to P0.6 in pull-up and P0.7 in open-drain which are controlled using the UART3 in 1-WIRE mode.
To control the four relays, I config them like on/off leds.The output of the nRF7002-DK is at 1, 8V so I also need a level shifter to get the relay input signals at 5V.
So the corresponding part of the overaly is
/ {
aliases {
level-shifter1 = &level_shifter1;
relay1 = &relay_1;
relay2 = &relay_2;
relay3 = &relay_3;
relay4 = &relay_4;
};
level_shifters {
compatible = "gpio-leds";
level_shifter0: level_shifter_0 {
status = "okay";
gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>;
label = "Level Shifter - Output Enable";
};
};
relays {
compatible = "gpio-leds";
relay_1: relay_04 {
status = "okay";
gpios = <&gpio1 11 GPIO_ACTIVE_HIGH>;
label = "Ralay 4";
};
relay_2: relay_03 {
status = "okay";
gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
label = "Ralay 3";
};
relay_3: relay_02 {
status = "okay";
gpios = <&gpio1 13 GPIO_ACTIVE_HIGH>;
label = "Ralay 2";
};
relay_4: relay_01 {
status = "okay";
gpios = <&gpio1 14 GPIO_ACTIVE_HIGH>;
label = "Ralay 1";
};
};
};
This overlay map the output-enable pin of the shifter to the P1.15 of the DK, and the four relay to the P1.11 P1.12 P1.13 and P1.14 pins.
In the prj.conf file I enabled the features that I need
# nothing here
CONFIG_PRINTK=y
CONFIG_GPIO=y
CONFIG_PWM=y
CONFIG_SENSOR=y
CONFIG_W1=y
and that's all! I can start play with my first hardware prototype!
/* */
#include <zephyr/kernel.h>
#include <zephyr/sys/printk.h>
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/drivers/pwm.h>
#include <zephyr/drivers/sensor.h>
#include <stdio.h>
static const struct gpio_dt_spec level_shifter1 =
GPIO_DT_SPEC_GET(DT_ALIAS(level_shifter1), gpios);
static const struct gpio_dt_spec relay1 =
GPIO_DT_SPEC_GET(DT_ALIAS(relay1), gpios);
static const struct gpio_dt_spec relay2 =
GPIO_DT_SPEC_GET(DT_ALIAS(relay2), gpios);
static const struct gpio_dt_spec relay3 =
GPIO_DT_SPEC_GET(DT_ALIAS(relay3), gpios);
static const struct gpio_dt_spec relay4 =
GPIO_DT_SPEC_GET(DT_ALIAS(relay4), gpios);
static const struct device *const dht11 = DEVICE_DT_GET(DT_ALIAS(dht11));
static const struct device *const dht22 = DEVICE_DT_GET(DT_ALIAS(dht22));
static const struct device *const ds18b20 = DEVICE_DT_GET(DT_ALIAS(ds18b20));
static const struct pwm_dt_spec servo = PWM_DT_SPEC_GET(DT_ALIAS(servo));
static const uint32_t min_pulse = DT_PROP(DT_ALIAS(servo), min_pulse);
static const uint32_t max_pulse = DT_PROP(DT_ALIAS(servo), max_pulse);
volatile uint32_t pulse_width;
int main(void)
{
int ret;
struct sensor_value dht11_temperature;
struct sensor_value dht11_humidity;
struct sensor_value dht22_temperature;
struct sensor_value dht22_humidity;
struct sensor_value ds18b20_temperature;
ulse_width = (uint32_t)((max_pulse + min_pulse) / 2);
printk("Pulse Width: %d\n", pulse_width);
///////////////////////////////////////////////////////////////////////////
// VERIFY IF THE GPIO ATTACHED TO THE OE OF THE LEVEL SHIFTER IS READY
ret = gpio_is_ready_dt(&level_shifter1);
if (ret == 0) {
printk("Error: level_shifter1 is not ready\n");
return 0;
}
// CONFIGURE THE GPIO ATTACHED TO THE OE OF THE LEVEL SHIFTER AS A GPIO OUT
// AND SET IT TO ACTIVE STATE
ret = gpio_pin_configure_dt(&level_shifter1, GPIO_OUTPUT_ACTIVE);
if (ret != 0) {
printk("Error %d: failed to configure level shifter 1\n", ret);
return 0;
}
// SET THE GPIO ATTACHED TO THE OE OF THE LEVEL SHIFTER TO LOGIC LEVEL '1'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&level_shifter1, 1);
if (ret != 0) {
printk("Error %d: failed to set level shifter 1
at logic level 1\n", ret);
return 0;
}
///////////////////////////////////////////////////////////////////////////
// VERIFY IF THE GPIO ATTACHED TO THE RELAY IS READY
ret = gpio_is_ready_dt(&relay1);
if (ret == 0) {
printk("Error: relay1 is not ready\n");
return 0;
}
// CONFIGURE THE GPIO ATTACHED TO THE OE OF THE RELAY AS A GPIO OUT
// AND SET IT TO ACTIVE STATE
ret = gpio_pin_configure_dt(&relay1, GPIO_OUTPUT_HIGH);
if (ret != 0) {
printk("Error %d: failed to configure relay 1\n", ret);
return 0;
}
// VERIFY IF THE GPIO ATTACHED TO THE RELAY IS READY
ret = gpio_is_ready_dt(&relay2);
if (ret == 0) {
printk("Error: relay2 is not ready\n");
return 0;
}
// CONFIGURE THE GPIO ATTACHED TO THE OE OF THE RELAY AS A GPIO OUT
// AND SET IT TO ACTIVE STATE
ret = gpio_pin_configure_dt(&relay2, GPIO_OUTPUT_HIGH);
if (ret != 0) {
printk("Error %d: failed to configure relay 2\n", ret);
return 0;
}
// VERIFY IF THE GPIO ATTACHED TO THE RELAY IS READY
ret = gpio_is_ready_dt(&relay3);
if (ret == 0) {
printk("Error: relay3 is not ready\n");
return 0;
}
// CONFIGURE THE GPIO ATTACHED TO THE OE OF THE RELAY AS A GPIO OUT
// AND SET IT TO ACTIVE STATE
ret = gpio_pin_configure_dt(&relay3, GPIO_OUTPUT_HIGH);
if (ret != 0) {
printk("Error %d: failed to configure relay 3\n", ret);
return 0;
}
// VERIFY IF THE GPIO ATTACHED TO THE RELAY IS READY
ret = gpio_is_ready_dt(&relay4);
if (ret == 0) {
printk("Error: relay4 is not ready\n");
return 0;
}
// CONFIGURE THE GPIO ATTACHED TO THE OE OF THE RELAY AS A GPIO OUT
// AND SET IT TO ACTIVE STATE
ret = gpio_pin_configure_dt(&relay4, GPIO_OUTPUT_HIGH);
if (ret != 0) {
printk("Error %d: failed to configure relay 4\n", ret);
return 0;
}
// SET THE GPIO ATTACHED TO THE OE OF THE RELAY TO LOGIC LEVEL '1'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&relay1, 0); // ON
if (ret != 0) {
printk("Error %d: failed to set relay 1 at logic level 1\n", ret);
return 0;
}
k_sleep(K_MSEC(500));
// SET THE GPIO ATTACHED TO THE OE OF THE RELAY TO LOGIC LEVEL '0'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&relay1, 1); // OFF
if (ret != 0) {
printk("Error %d: failed to set relay 1 at logic level 0\n", ret);
return 0;
}
// SET THE GPIO ATTACHED TO THE OE OF THE RELAY TO LOGIC LEVEL '1'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&relay2, 0); // ON
if (ret != 0) {
printk("Error %d: failed to set relay 2 at logic level 1\n", ret);
return 0;
}
k_sleep(K_MSEC(500));
// SET THE GPIO ATTACHED TO THE OE OF THE RELAY TO LOGIC LEVEL '0'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&relay2, 1); // OFF
if (ret != 0) {
printk("Error %d: failed to set relay 2 at logic level 0\n", ret);
return 0;
}
// SET THE GPIO ATTACHED TO THE OE OF THE RELAY TO LOGIC LEVEL '1'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&relay3, 0); // ON
if (ret != 0) {
printk("Error %d: failed to set relay 3 at logic level 1\n", ret);
return 0;
}
k_sleep(K_MSEC(500));
// SET THE GPIO ATTACHED TO THE OE OF THE RELAY TO LOGIC LEVEL '0'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&relay3, 1); // OFF
if (ret != 0) {
printk("Error %d: failed to set relay 3 at logic level 0\n", ret);
return 0;
}
// SET THE GPIO ATTACHED TO THE OE OF THE RELAY TO LOGIC LEVEL '1'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&relay4, 0); // ON
if (ret != 0) {
printk("Error %d: failed to set relay 4 at logic level 1\n", ret);
return 0;
}
k_sleep(K_MSEC(500));
// SET THE GPIO ATTACHED TO THE OE OF THE RELAY TO LOGIC LEVEL '0'
// (FOR DEBUG PURPOSES)
ret = gpio_pin_set_dt(&relay4, 1); // OFF
if (ret != 0) {
printk("Error %d: failed to set relay 4 at logic level 0\n", ret);
return 0;
}
///////////////////////////////////////////////////////////////////////////
// VERIFY IF THE DHT11 SENSOR DEVICE IS READY
ret = device_is_ready(dht11);
if (ret == 0) {
printk("Device %s is not ready\n", dht11->name);
return 0;
}
k_sleep(K_MSEC(2000));
// DO A FETCH OF THE DHT11 SENSOR DEVICE
ret = sensor_sample_fetch(dht11);
if (ret != 0) {
printk("Sensor %s fetch failed: %d\n", dht11->name, ret);
//return 0;
} else {
// GET THE TEMP AND HUMIDITY VALUES OBTAINED FROM THE FETCH
ret = sensor_channel_get(dht11, SENSOR_CHAN_AMBIENT_TEMP,
&dht11_temperature);
if (ret == 0) {
ret = sensor_channel_get(dht11, SENSOR_CHAN_HUMIDITY,
&dht11_humidity);
}
if (ret != 0) {
printk("get failed: %d\n", ret);
return 0;
}
// PRINT THE READ TEMP AND HUMIDITY VALUES
printk("temp: %d\n%d\n", dht11_temperature.val1, dht11_temperature.val2);
printk("hum: %d\n%d\n", dht11_humidity.val1, dht11_humidity.val2);
}
// VERIFY IF THE DHT22 SENSOR DEVICE IS READY
ret = device_is_ready(dht22);
if (ret == 0) {
printk("Device %s is not ready\n", dht22->name);
return 0;
}
k_sleep(K_MSEC(2000));
// DO A FETCH OF THE DHT22 SENSOR DEVICE
ret = sensor_sample_fetch(dht22);
if (ret != 0) {
printk("Sensor %s fetch failed: %d\n",dht22->name, ret);
//return 0;
} else {
// GET THE TEMP AND HUMIDITY VALUES OBTAINED FROM THE FETCH
ret = sensor_channel_get(dht22, SENSOR_CHAN_AMBIENT_TEMP,
&dht22_temperature);
if (ret == 0) {
ret = sensor_channel_get(dht22, SENSOR_CHAN_HUMIDITY, &dht22_humidity);
}
if (ret != 0) {
printk("get failed: %d\n", ret);
return 0;
}
// PRINT THE READ TEMP AND HUMIDITY VALUES
printk("temp: %d\n%d\n", dht22_temperature.val1, dht22_temperature.val2);
printk("hum: %d\n%d\n", dht22_humidity.val1, dht22_humidity.val2);
}
// VERIFY IF THE DS18B20 SENSOR DEVICE IS READY
ret = device_is_ready(ds18b20);
if (ret == 0) {
printk("Device %s is not ready\n", ds18b20->name);
return 0;
}
k_sleep(K_MSEC(2000));
// DO A FETCH OF THE DS18B20 SENSOR DEVICE
ret = sensor_sample_fetch(ds18b20);
if (ret != 0) {
printk("Sensor %s fetch failed: %d\n",ds18b20->name, ret);
//return 0;
} else {
// GET THE TEMP VALUE OBTAINED FROM THE FETCH
ret = sensor_channel_get(ds18b20, SENSOR_CHAN_AMBIENT_TEMP,
&ds18b20_temperature);
if (ret != 0) {
printk("get failed: %d\n", ret);
return 0;
}
// PRINT THE READ TEMP VALUES
printk("temp: %d\n%d\n", ds18b20_temperature.val1,
ds18b20_temperature.val2);
}
k_sleep(K_MSEC(1500));
///////////////////////////////////////////////////////////////////////////
// VERIFY IF THE SERVO DEVICE IS READY
ret = device_is_ready(servo.dev);
if (ret == 0) {
printk("Error: PWM device %s is not ready\n", servo.dev->name);
return 0;
}
// SET THE SERVO PULSE PARAMETER ACCORDINGLY TO THE OVERLAY DEFINITION
ret = pwm_set_pulse_dt(&servo, pulse_width);
if (ret != 0) {
printk("Error %d: failed to set pulse width\n", ret);
return 0;
}
k_sleep(K_SECONDS(3));
// SET THE SERVO PULSE PARAMETER ACCORDINGLY TO THE OVERLAY DEFINITION
ret = pwm_set_pulse_dt(&servo, 0);
if (ret != 0) {
printk("Error %d: failed to set pulse width\n", ret);
return 0;
}
while (1) {
k_sleep(K_SECONDS(1));
}
return 0;
}
And this is a part (DHT11 and SERVO-PWM) of the result! It seems that I am on the good way!
To create my matter "composite" idea, and guarantee the compatibility with Google, I decided to distribute the functions through basic endpoints:
- ENDPOINT 1: "Dimmable Light" [ONBOARD LED] to control the led on the DK
- ENDPOINT 2: "On/Off Light" [RELAY 1] to control the hot-spot light
- ENDPOINT 3: "On/Off Light" [RELAY 2] to control the UVB light
- ENDPOINT 4: "On/Off Plug-in Unit" [RELAY 3] to control the water heater
- ENDPOINT 5: "On/Off Plug-in Unit" [RELAY 4] to control the water filter pump
- ENDPOINT 6: "On/Off Plug-in Unit" [SERVO-PWM] to control the automatic feeder
- ENDPOINT 7: "Temperature Sensor" [DHT22] to get the hot-spot temperature
- ENDPOINT 8: "Humidity Sensor" [DHT22] to get the hot-spot humidity
- ENDPOINT 9: "Temperature Sensor" [DHT11] to get the cold zone temperature
- ENDPOINT 10: "Humidity Sensor" [DHT11] to get the cold zone humidity
- ENDPOINT 11: "Temperature Sensor" [DS18B20] to get the water temperature
As I mentioned above, I started using the "Adding clusters to Matter application — nRF Connect SDK 2.4.2 documentation (nordicsemi.com)" guide.
So I have configurate the endpoints using the ZAP tool:
and then I have generated the files which must be integrated to the project:
The application task do the following:
- SENSORS: use a periodic software timer with which every 2 seconds the app fetch all the sensors data and update the corresponding endpoints with the new read values
- LAMPS: accept matter commands to turn On/Off the Hot Lamp and the UVB Lamp; the app simply set the corrisponding GPIO-OUT to the requested state.
- HEATER: accept matter commands to turn On/Off the water heater; the app simply set the corrisponding GPIO-OUT to the requested state
- FILTER: accept matter commands to turn On/Off the water filter pump; the app simply set the corrisponding GPIO-OUT to the requested state
- FEEDER: accept matter commands to turn On/Off the automatic feeder; at the Off command, the app simply set the PWM-SERVO in a way that the MOTOR is stopped; at the On command the app set the PWM-SERVO to START the MOTOR and start a software timer, when the software timer reach the timeout, in it's callback the app change the PWM-SERVO in a way that the MOTOR is stopped again, so, only the right ration of food will be delivered.
THE SENSORS MANAGEMENT :
app_task.cpp
/* ****************************************************************************
*
* SENSORS MANAGEMENT
*
* sSensorTimer: 2 second periodic software timer that trigger the events
* hot_ev, cold_ev and water_ev, which do the fetch of
* corresponding sensor
*
* hot_ev: app event that trigger the HotSensorMeasureHandler
* cold_ev: app event that trigger the ColdSensorMeasureHandler
* water_ev: app event that trigger the WaterTempSensorMeasureHandler
*
* HotSensorMeasureHandler: fetch the Hot-Spot sensor and update the endpoints
* with temperature and humidity values
*
* ColdSensorMeasureHandler: fetch the Cold-Zone sensor and update the relative
* endpoints with temperature and humidity values
*
* WaterTempSensorMeasureHandler: fetch the Water sensor and update the relative
* endpoint with temperature value
*
* ***************************************************************************/
/* Software timer to periodically fetch the sensors */
k_timer sSensorTimer;
/* Global data of last measures */
struct sensor_value last_temperature_1;
struct sensor_value last_humidity_1;
struct sensor_value last_temperature_2;
struct sensor_value last_humidity_2;
struct sensor_value last_temperature_3;
CHIP_ERROR AppTask::Init()
{
/* ... */
/* Init the Sensors Timer to periodically call the
* SensorTimerHandler every 5 seconds */
k_timer_init(&sSensorTimer, &SensorTimerHandler, nullptr);
k_timer_user_data_set(&sSensorTimer, this);
k_timer_start(&sSensorTimer, K_MSEC(5000), K_MSEC(5000));
/* ... */
return CHIP_NO_ERROR;
}
/* The SensorTimerHandler callback is called periodically
* every 5 seconds.
* In this callback will be sent 3 events to the main app
* handler, ones per sensor. */
void SensorTimerHandler(k_timer *timer)
{
AppEvent hot_ev, cold_ev, water_ev;
hot_ev.Type = AppEventType::HotSensorMeasure;
hot_ev.Handler = AppTask::HotSensorMeasureHandler;
AppTask::Instance().PostEvent(hot_ev);
cold_ev.Type = AppEventType::ColdSensorMeasure;
cold_ev.Handler = AppTask::ColdSensorMeasureHandler;
AppTask::Instance().PostEvent(cold_ev);
water_ev.Type = AppEventType::WaterTempSensorMeasure;
water_ev.Handler = AppTask::WaterTempSensorMeasureHandler;
AppTask::Instance().PostEvent(water_ev);
}
/* This execute a fetch to the Hot-Spot sensor and update
* the endpoints EP6 with the read temperature and EP7 with
* the read relative humidity */
void AppTask::HotSensorMeasureHandler(const AppEvent &)
{
int rc = sensor_sample_fetch(dht22);
if (rc != 0) {
LOG_ERR("Sensor fetch failed: %d\n", rc);
if ((last_temperature_1.val1 != 0) && (last_humidity_1.val1 != 0)) {
LOG_INF("Sensor DHT22 temp: %d, %d", last_temperature_1.val1,
last_temperature_1.val2);
LOG_INF("Sensor DHT22 hum: %d, %d", last_humidity_1.val1,
last_humidity_1.val2);
}
} else {
struct sensor_value temperature;
struct sensor_value humidity;
rc = sensor_channel_get(dht22, SENSOR_CHAN_AMBIENT_TEMP, &temperature);
if (rc == 0) {
rc = sensor_channel_get(dht22, SENSOR_CHAN_HUMIDITY, &humidity);
}
if (rc != 0) {
LOG_ERR("get failed: %d\n", rc);
} else {
last_temperature_1 = temperature;
last_humidity_1 = humidity;
LOG_INF("Sensor DHT22 temp: %d, %d", temperature.val1, temperature.val2);
LOG_INF("Sensor DHT22 hum: %d, %d", humidity.val1, humidity.val2);
}
}
chip::app::Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Set(
/* endpoint ID */ 7,
/* temperature in 0.01*C */ int16_t(sensor_value_to_double(
&last_temperature_1)));
chip::app::Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Set(
/* endpoint ID */ 8,
/* humidity */ int16_t(sensor_value_to_double(&last_humidity_1)));
}
/* This execute a fetch to the Cold Zone sensor and update
* the endpoints EP9 with the read temperature and EP10 with
* the read relative humidity */
void AppTask::ColdSensorMeasureHandler(const AppEvent &)
{
int rc = sensor_sample_fetch(dht11);
if (rc != 0) {
LOG_ERR("Sensor fetch failed: %d\n", rc);
if ((last_temperature_2.val1 != 0) && (last_humidity_2.val1 != 0)) {
LOG_INF("Sensor DHT11 temp: %d, %d", last_temperature_2.val1,
last_temperature_2.val2);
LOG_INF("Sensor DHT11 hum: %d, %d", last_humidity_2.val1,
last_humidity_2.val2);
}
} else {
struct sensor_value temperature;
struct sensor_value humidity;
rc = sensor_channel_get(dht11, SENSOR_CHAN_AMBIENT_TEMP, &temperature);
if (rc == 0) {
rc = sensor_channel_get(dht11, SENSOR_CHAN_HUMIDITY, &humidity);
}
if (rc != 0) {
LOG_ERR("get failed: %d\n", rc);
} else {
last_temperature_2 = temperature;
last_humidity_2 = humidity;
LOG_INF("Sensor DHT11 temp: %d, %d", temperature.val1, temperature.val2);
LOG_INF("Sensor DHT11 hum: %d, %d", humidity.val1, humidity.val2);
}
}
chip::app::Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Set(
/* endpoint ID */ 9,
/* temperature in 0.01*C */ int16_t(sensor_value_to_double(
&last_temperature_2)));
chip::app::Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Set(
/* endpoint ID */ 10,
/* humidity */ int16_t(sensor_value_to_double(&last_humidity_2)));
}
/* This execute a fetch to the Water sensor and update
* the endpoint EP11 with the read temperature */
void AppTask::WaterTempSensorMeasureHandler(const AppEvent &)
{
int rc = sensor_sample_fetch(ds18b20);
if (rc != 0) {
LOG_ERR("Sensor fetch failed: %d\n", rc);
} else {
struct sensor_value temperature;
rc = sensor_channel_get(ds18b20, SENSOR_CHAN_AMBIENT_TEMP, &temperature);
if (rc != 0) {
LOG_ERR("get failed: %d\n", rc);
} else {
last_temperature_3 = temperature;
LOG_INF("Sensor DS18B20 temp: %d, %d", temperature.val1, temperature.val2);
}
}
chip::app::Clusters::TemperatureMeasurement::Attributes::MeasuredValue::Set(
/* endpoint ID */ 11,
/* temperature in 0.01*C */ int16_t(sensor_value_to_double(
&last_temperature_3)));
}
THE ACTUATORS MANAGEMENT:
zcl_callbacks.cpp
/* ***************************************************************************
*
* MATTER COMMANDS LISTNER
*
* MatterPostAttributeChangeCallback: callback of matter command is received
*
* ***************************************************************************/
/* MATTER COMMANDS LISTENER */
void MatterPostAttributeChangeCallback(
const chip::app::ConcreteAttributePath & attributePath,
uint8_t type,
uint16_t size, uint8_t * value)
{
AppEvent event;
/* LED */
if (attributePath.mEndpointId == 1) {
return;
}
/* HOT-LAMP */
/* Verify if the command receiver is for the endpoint 2 */
if (attributePath.mEndpointId == 2) {
/* Verify if the command receiver is managed [On/Off] */
if (attributePath.mClusterId != OnOff::Id ||
attributePath.mAttributeId != OnOff::Attributes::OnOff::Id)
return;
/* In case of On command turn on the hot lamp */
if (*value) {
event.Type = AppEventType::HotLampActivate;
event.Handler = AppTask::HotLampActivateHandler;
}
/* In case of Off command turn off the hot lamp */
else {
event.Type = AppEventType::HotLampDeactivate;
event.Handler = AppTask::HotLampDeactivateHandler;
}
AppTask::Instance().PostEvent(event);
}
/* UVB LAMP */
/* Verify if the command receiver is for the endpoint 3 */
if (attributePath.mEndpointId == 3) {
/* Verify if the command receiver is managed [On/Off] */
if (attributePath.mClusterId != OnOff::Id ||
attributePath.mAttributeId != OnOff::Attributes::OnOff::Id)
return;
/* In case of On command turn on the uvb lamp */
if (*value) {
event.Type = AppEventType::UvbLampActivate;
event.Handler = AppTask::UvbLampActivateHandler;
}
/* In case of Off command turn off the uvb lamp */
else {
event.Type = AppEventType::UvbLampDeactivate;
event.Handler = AppTask::UvbLampDeactivateHandler;
}
AppTask::Instance().PostEvent(event);
}
/* HEATER */
/* Verify if the command receiver is for the endpoint 4 */
if (attributePath.mEndpointId == 4) {
/* Verify if the command receiver is managed [On/Off] */
if (attributePath.mClusterId != OnOff::Id ||
attributePath.mAttributeId != OnOff::Attributes::OnOff::Id)
return;
/* In case of On command turn on the water heater */
if (*value) {
event.Type = AppEventType::HeaterActivate;
event.Handler = AppTask::HeaterActivateHandler;
}
/* In case of Off command turn off the water heater */
else {
event.Type = AppEventType::HeaterDeactivate;
event.Handler = AppTask::HeaterDeactivateHandler;
}
AppTask::Instance().PostEvent(event);
}
/* FILTER */
/* Verify if the command receiver is for the endpoint 5 */
if (attributePath.mEndpointId == 5) {
/* Verify if the command receiver is managed [On/Off] */
if (attributePath.mClusterId != OnOff::Id ||
attributePath.mAttributeId != OnOff::Attributes::OnOff::Id)
return;
/* In case of On command turn on the water filter pump */
if (*value) {
event.Type = AppEventType::FilterActivate;
event.Handler = AppTask::FilterActivateHandler;
}
/* In case of Off command turn off the water filter pump */
else {
event.Type = AppEventType::FilterDeactivate;
event.Handler = AppTask::FilterDeactivateHandler;
}
AppTask::Instance().PostEvent(event);
}
/* FEEDER */
/* Verify if the command receiver is for the endpoint 5 */
if (attributePath.mEndpointId == 6) {
/* Verify if the command receiver is managed [On/Off] */
if (attributePath.mClusterId != OnOff::Id ||
attributePath.mAttributeId != OnOff::Attributes::OnOff::Id)
return;
/* In case of On command turn on the automatic feeder */
if (*value) {
event.Type = AppEventType::FeederActivate;
event.Handler = AppTask::FeederActivateHandler;
}
/* In case of Off command turn off the automatic feeder */
else {
event.Type = AppEventType::FeederDeactivate;
event.Handler = AppTask::FeederDeactivateHandler;
}
AppTask::Instance().PostEvent(event);
}
}
app_task.cpp
/* ****************************************************************************
*
* ACTUATORS MANAGEMENT
*
* sFeederMonoTimer: software timer for monostable monostable actuation
* of the feeder
*
* feeder_ev: event to internally trigger the FeederDeactivateHandler
*
* FeederMonoTimerHandler: launch the feeder_ev event
*
* HotLampActivateHandler: turn the hot amp On
* HotLampDeactivateHandler: turn the hot lamp Off
*
* UvbLampActivateHandler: turn the uvb lamp On
* UvbLampDeactivateHandler: turn the uvb lamp Off
*
* HeaterActivateHandler: turn the water heater On
* HeaterDeactivateHandler: turn the water heater Off
*
* FilterActivateHandler: turn the filter pump On
* FilterDeactivateHandler: turn the filter pump Off
*
* FeederActivateHandler: turn the feeder pwm-servo On
* FeederDeactivateHandler: turn the feeder pwm-servo Off
*
* ***************************************************************************/
/* Software timer for monostable actuation of the feeder */
k_timer sFeederMonoTimer;
/* At activation timeout the feeder change it's state to Off */
void FeederMonoTimerHandler(k_timer *timer)
{
chip::app::Clusters::OnOff::Attributes::OnOff::Set(
/* endpoint ID */ 6,
/* On/Off state */ false);
}
CHIP_ERROR AppTask::Init()
{
/* ... */
/* Init the Feeder Timer */
k_timer_init(&sFeederMonoTimer, &FeederMonoTimerHandler, nullptr);
k_timer_user_data_set(&sFeederMonoTimer, this);
/* ... */
return CHIP_NO_ERROR;
}
/* Turn on the hot lamp */
void AppTask::HotLampActivateHandler(const AppEvent &)
{
gpio_pin_set_dt(&rel1, 1);
}
/* Turn off the hot lamp */
void AppTask::HotLampDeactivateHandler(const AppEvent &)
{
gpio_pin_set_dt(&rel1, 0);
}
/* Turn on the uvb lamp */
void AppTask::UvbLampActivateHandler(const AppEvent &)
{
gpio_pin_set_dt(&rel2, 1);
}
/* Turn off the uvb lamp */
void AppTask::UvbLampDeactivateHandler(const AppEvent &)
{
gpio_pin_set_dt(&rel2, 0);
}
/* Turn on the water heater */
void AppTask::HeaterActivateHandler(const AppEvent &)
{
gpio_pin_set_dt(&rel3, 1);
}
/* Turn off the water heater */
void AppTask::HeaterDeactivateHandler(const AppEvent &)
{
gpio_pin_set_dt(&rel3, 0);
}
/* Turn on the filter pump */
void AppTask::FilterActivateHandler(const AppEvent &)
{
gpio_pin_set_dt(&rel4, 1);
}
/* Turn off the filter pump */
void AppTask::FilterDeactivateHandler(const AppEvent &)
{
gpio_pin_set_dt(&rel4, 0);
}
/* Start the pwm-servo and start the sFeederMonoTimer software timer */
void AppTask::FeederActivateHandler(const AppEvent &)
{
int ret;
ret = pwm_set_pulse_dt(&servo, (uint32_t)((min_pulse + max_pulse) / 2));
k_timer_start(&sFeederMonoTimer, K_MSEC(1000), K_MSEC(0));
}
/* Stop the pwm-servo and stop the sFeederMonoTimer software timer */
void AppTask::FeederDeactivateHandler(const AppEvent &)
{
int ret;
ret = pwm_set_pulse_dt(&servo, 0);
k_timer_stop(&sFeederMonoTimer);
}
DEMO USING CHIP TOOLAfter built and flash the device, I have test it in my safetly demo-environment:
- Four Relay module controlled at 5VDC with connected
- Two leds, instead of the lamps
- One 5V heater
- One 5V water pump
- One DHT22 sensor (it give me Temp 0 and Humidity 1, I think it's broken...)
- One DHT11 sensor
- One DS18B20 sensor
- One SERVO MOTOR
In the real application the parts connected to the relays are tipically powered from mains, but there are no other differences then the power supply.
The list of command I use in the demo:
- chip-tool pairing ble-wifi 1 "WiFi-SSID" "WiFi-PASS" 20202021 3840
- chip-tool onoff on 1 2
- chip-tool onoff off 1 2
- chip-tool onoff on 1 3
- chip-tool onoff off 1 3
- chip-tool onoff on 1 4
- chip-tool onoff off 1 4
- chip-tool onoff on 1 5
- chip-tool onoff off 1 5
- chip-tool onoff on 1 6
- chip-tool onoff off 1 6
- chip-tool temperaturemeasurement read measured-value 1 7
- chip-tool relativehumiditymeasurement read measured-value 1 8
- chip-tool temperaturemeasurement read measured-value 1 9
- chip-tool relativehumiditymeasurement read measured-value 1 10
- chip-tool temperaturemeasurement read measured-value 1 11
The next step of this "matter" project will be the implementation of programmable automations to give the device the ability to function in total autonomy, based on the temperature and humidity values detected, the thresholds set and the programmed time.
My idea is to have a Smartphone App for programming the various parameters via Bluetooth and structure the firmware as a multi thread machine which will communicate through a zBus implementation, but if someone have better ideas, they are strongly invited to collaborate!
Comments