My wife recently added an ornamental water fountain next to the stone patio in our back yard. I thought it would be nice to add an LED light and to only turn on the water pump and light when someone was present on the patio. The water fountain currently uses a low flow AC pump and my original plan was to change to a DC pump and add a DC LED light. Both would be controlled by separate channels of a BTS700x-1EPP High-Side-Switch. Unfortunately, I wasn't able to get an equivalent low flow DC pump in time, so I decided to use a dual socket AC WiFi smart switch in order to use the existing pump and then add an AC LED light. The other upside of using the WiFi switch is that I can also use an existing iOS app to manually control the switch from my iPad.
The WiFi switch has an MQTT interface, so I opted to use an existing RPi4 MQTT server with Node-Red as the control interface. The PSoC6 will process the Radar data and use an MQTT client program to communicate with the WiFi switch via the Node-Red interface.
Hardware ComponentsI am using a CY8CKIT-062S2-43012 as the control board. It features a dual core (Cortex M4 and Cortex M0+) CPU and a Murata 1LV module that supports WiFi 4 and Bluetooth 5.2. I added a CY8CKIT-028-TFT for display and it also includes an light sensor that I can use to control the LED lighting.
To do presence detection, I am using a BGT60LTR11 Shield2Go 60GHz Doppler Radar board.
The boards are shown below. I 3D printed a case to hold the assembly.
Quick initial test of the display and light sensor.
The downside of adding the TFT shield is that there are only a small number of pins left available to interface the Radar shield. Luckily, the shield has an autonomous mode that allows control parameters to be adjusted via 2 switches and 2 potentiometers. In this mode there are only two signal pins, TD (target detected) and PD (phase detected - approaching or departing). The picture below shows the 4 wires required to interface the radar shield (+3.3V, GND, TD, PD). I've attached the board schematics below, but the bottom line is that there are really only 4 available GPIO in the Arduino shield headers (CYBSP_A7, CYBSP_A11, CYBSP_A14, CYBSP_A15). I used CYBSP_A7 and CYBSP_A15 since they are on the outer end of the J2 Header. The wiring is shown below.
The LED light unit attached to a mounting stake.
I used ModusToolbox 3.1 Eclipse IDE for the PSoC6 application development.
Here is the Project Explorer view. I've committed the master branch of the project to github (repository is linked in attachments).
I used the Wi-Fi_MQTT_Client example as a starting point which is a FreeRTOS application that includes the MQTT client, Publisher, and Subscriber tasks. I then added the TFT task for the display and light sensor. The source files are shown below:
The application requires that you configure WiFi and MQTT settings for the WiFi network and MQTT server used (see the READ.me file at the github repo). Below is a list of the configuration files:
MQTT Configuration
Changes to the Wi-Fi_MQTT_Client example were straightforward. Modify the required MQTT settings to match my project in the mqtt_client_config.h file:
Add my WiFi AP address in the wifi_config.h:
And modify setup and callback functions in the publisher_task.c file:
static void publisher_init(void)
{
/* Initialize the GPIO pins for Radar TD and PD */
cyhal_gpio_init(CYBSP_A7, CYHAL_GPIO_DIR_INPUT, CYHAL_GPIO_DRIVE_NONE, false); // TD outside row
cyhal_gpio_init(CYBSP_A15, CYHAL_GPIO_DIR_INPUT, CYHAL_GPIO_DRIVE_NONE, false); // PD inside row
cyhal_gpio_register_callback(CYBSP_A7, &cb_data);
cyhal_gpio_enable_event(CYBSP_A7, CYHAL_GPIO_IRQ_BOTH,
USER_BTN_INTR_PRIORITY, true);
}
static void isr_button_press(void *callback_arg, cyhal_gpio_event_t event)
{
bool tdetectState = 0;
bool pdetectState = 0;
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
publisher_data_t publisher_q_data;
/* To avoid compiler warnings */
(void) callback_arg;
(void) event;
/* Assign the publish command to be sent to the publisher task. */
publisher_q_data.cmd = PUBLISH_MQTT_MSG;
tdetectState = cyhal_gpio_read(CYBSP_A7);
if (tdetectState)
{
publisher_q_data.data = (char *)MQTT_DEVICE_OFF_MESSAGE;
}
else
{
publisher_q_data.data = (char *)MQTT_DEVICE_ON_MESSAGE;
}
/* Send the command and data to publisher task over the queue */
xQueueSendFromISR(publisher_task_q, &publisher_q_data, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
TFT Configuration
I needed to create new tft_task.c and tft_task.h files. The principle function is tft_task() which initializes and updates the TFT display with the sensor information:
/******************************************************************************
*
* File Name: tft_task.c
*
* Description: This file contains task and functions related to the tft-task
* for the Water Feature Controller
*******************************************************************************/
#include "cy_pdl.h"
#include "cyhal.h"
#include "cybsp.h"
#include "GUI.h"
#include "mtb_st7789v.h"
#include "mtb_light_sensor.h"
#include "tft_task.h"
#include "FreeRTOS.h"
#include "task.h"
/* The pins are defined by the st7789v library. If the display is being used
* on different hardware the mappings will be different. */
const mtb_st7789v_pins_t tft_pins =
{
.db08 = CYBSP_J2_2,
.db09 = CYBSP_J2_4,
.db10 = CYBSP_J2_6,
.db11 = CYBSP_J2_10,
.db12 = CYBSP_J2_12,
.db13 = CYBSP_D7,
.db14 = CYBSP_D8,
.db15 = CYBSP_D9,
.nrd = CYBSP_D10,
.nwr = CYBSP_D11,
.dc = CYBSP_D12,
.rst = CYBSP_D13
};
#define LIGHT_SENSOR_PIN (CYBSP_A0)
cyhal_adc_t adc;
mtb_light_sensor_t light_sensor;
void tft_task(void *arg)
{
/* Initialize the display controller */
result = mtb_st7789v_init8(&tft_pins);
CY_ASSERT(result == CY_RSLT_SUCCESS);
/* Initialize the adc and light sensor */
result = cyhal_adc_init(&adc, LIGHT_SENSOR_PIN, NULL);
CY_ASSERT(result == CY_RSLT_SUCCESS);
result = mtb_light_sensor_init(&light_sensor, &adc, LIGHT_SENSOR_PIN);
CY_ASSERT(result == CY_RSLT_SUCCESS);
/* To avoid compiler warning */
(void)result;
bool tdState = 0;
bool pdState = 0;
uint8_t light = mtb_light_sensor_light_level(&light_sensor);
GUI_Init();
GUI_SetBkColor(GUI_BLUE);
GUI_Clear();
GUI_SetTextMode(GUI_TM_NORMAL);
GUI_SetFont(&GUI_Font32B_1);
GUI_DispStringHCenterAt("Water Feature" , 160, 50);
GUI_DispStringHCenterAt("Controller" , 160, 90);
GUI_SetFont(&GUI_Font16B_1);
for(;;)
{
light = mtb_light_sensor_light_level(&light_sensor);
GUI_DispStringAt("Ambient Light: ", 100, 150);
GUI_DispDec(light, 3);
tdState = cyhal_gpio_read(CYBSP_A7);
pdState = cyhal_gpio_read(CYBSP_A15);
cyhal_gpio_write(CYBSP_USER_LED, tdState);
cyhal_gpio_write(CYBSP_USER_LED2, pdState);
if (!tdState)
{
GUI_DispStringAt("Presence Detected", 100, 170);
if (pdState)
{
GUI_DispStringAt("Approaching", 100, 190);
} else
{
GUI_ClearRect(90, 190, 250, 250);
}
} else
{
GUI_ClearRect(90, 170, 250, 250);
}
cyhal_system_delay_ms(1000);
}
}
MQTT Server and Node-Red SetupMQTT Server
I am running Mosquitto version 2.0.11 on an 8GB RPi4.
Node-Red Flow
On that same RPi4 I am running Node-Red version 3.1.0
Below is the Node-Red flow that I am using to receive the MQTT message from the PSoC6 and to forward it to the Tuya Smart Plug.
I needed two additional Node-Red pallettes (libraries) to provide the required functionality (nodes).
Tuya Smart Device node
I am using the tuya-smart-device node to pass the MQTT message payload to the Smart Plug.
The node can be configured to use the Device Virtual ID and Device Key or the Device IP as the device identifier. I am using the Virtual ID and Key which requires a Tuya account to obtain.
The bottom section of the flow is configured as a manual test of the smart plug interface.
I use inject nodes to insert a true (On) or false (Off) condition into the msg.payload.
And then use a function node to add the switch identifier "dps".
The following video shows the operation of the two smart plug sockets in response to manually asserting the inject nodes.
MQTT Subsciber Interface
The upper part of the flow is the MQTT interface to the PSoC6.
The MQTT In node is configured to subcribe to the "presencedetected " topic. It then passes on the msg.payload true (On) or false (Off).
Glitch Filter
The Boolean Logic Ultimate node is used as a "glitch" filter for the MQTT message. I noticed that the radar detector signals would glitch occasionally and would cause power to toggle on and off in under a second. In order to prevent this I added a one-shot node that would only recheck the message state after a delay interval from a state transition (currently set at 3 seconds).
The video below shows the LED lamp which is plugged into the SW1 socket being controlled by the radar "target detected" signal via the MQTT "presencedetected" message. The radar detector has a blue LED that turns on to indicate target detected and a red LED that turns on to indicate movement away (phase).
If you look carefully, you'll see that the blue LED toggles more frequently than the light and the state changes (true/false) of the debug messages (only evaluates every 3 seconds).
I'm debating whether to control the sockets (pump/light) independently or have separate MQTT messages (possibly have the light also gated by ambient light level). To switch them together I would just need to also connect the Gate node output to SW2)
Demo of Water Feature Light and Pump ControlThe water pump is very low flow as required by the water fountain design - the upper water bowl fills slowly and overflows down cascading cups before returning to the reservoir in the base of the fountain. You'll notice that after the light swiches on, it takes 10-15 seconds before the water cascade stabilizes. You'll also notice a switch time delay after presence is detected or not (due to the glitch filter).
Smart Life Mobile AppAn added bonus of using the WiFi smart plug is that there is an existing BLE app to control it manually. The app is pretty simple. It just allows switching the power on each socket independently.
Comments