Contributed by WildboarG, organized by Ai-Thinker
Push Messages to Personal Notifications with AI-WB2
AI-WB2 Message Push to Personal Notifications
About
Common personal push projects usually refer to notification or message push systems built by developers or individuals using existing technologies to meet personal needs (such as reminders, message forwarding, status monitoring, etc.) or as projects for learning and demonstrating technical skills. These projects are often lightweight, easy to deploy and open source, and are commonly used for automated notifications, personal productivity or hobby experiments.
Common personal push projects:
Feishu
SMTP Email
WEBHOOK
WeCom bot
All of these personal message push projects I've used are quite good — WeChat official accounts, QQ bots, email and so on. Some applications have free usage limits.
Application Scenarios
In simple low-power scenarios, or scenarios that only need occasional notifications, there is no need to use persistent connections for large-scale pushing, for example, task notifications from Qinglong Panel. Do microcontrollers also have such needs? In some low-power scenarios, I think a one-off HTTP push offers better value for money than MQTT.
MQTT requires deploying a broker, while HTTP only needs a target URL for simple message pushing.
Some WeChat service accounts that require no deployment fully satisfy receiving message notifications, such as pushplus. However, for pushing privacy-sensitive content (such as dynamic passwords), it is still not recommended to push to someone else's server. We can set up a lightweight message push server on our own home NAS or router.
Setting Up a Message Server
Let's build a personal push message server (NOTIFY) from scratch, so that messages pushed by the Ai-wb2 can be received on your phone.
Download the notify server
- Deploy with Docker on a NAS
Click to expand full code
docker run -p 80:80 -v /var/gotify/data:/app/data ghcr.io/gotify/serverDownload and deploy on a local computer
- inux-amd64 (64bit)
- linux-386 (32bit)
- linux-arm-7 (32bit used for Raspberry Pi)
- linux-arm64 (ARMv8)
- windows-386.exe (32bit)
- windows-amd64.exe (64bit)
Download the binary zip package for your platform from the gotify/server releases, using placeholders for the version and platform. You need to replace
{VERSION}with the latest version and{PLATFORM}with any supported platform.
Click to expand full code
wget [https://github.com/gotify/server/releases/download/v](https://github.com/gotify/server/releases/download/v){VERSION}/gotify-{PLATFORM}.zipUnzip the archive
Click to expand full code
unzip gotify-{PLATFORM}.zipMake the binary executable
Click to expand full code
chmod +x gotify-{PLATFORM}Download the default configuration file
Click to expand full code
wget -O config.yml [https://raw.githubusercontent.com/gotify/server/master/config.example.yml](https://raw.githubusercontent.com/gotify/server/master/config.example.yml)Start the service
Click to expand full code
./gotify-{PLATFORM}- Login and settings Log in to the gotify server web page to configure it Open localhost On first login, both the admin username and password are
admin
Click USERS, create a new user, then log out. Log in with the new user. Click APPS and create an app token for pushing.

The setup is complete at this point.
- Download the mobile app
- Android: gotify-android
- IOS: download igotify from the App Store
To connect, enter the IP address plus the port number and the username/password you just created, and you can receive messages on your phone as well.
- Test from the command line whether it works

A JSON-format response means the message push was successful
- Title: hello
- Message: cli notify
Open the web page to verify

Ai-WB2 Push
AI-WB2 wifi connection
- Create a new project called Notify
Click to expand full code
## wb2-cli create
Create New Project:Notify
Notify Created Successfully- Add the required network libraries to the MAKEFILE
Click to expand full code
#
## This is a project Makefile. It is assumed the directory this Makefile resides in is a
## project subdirectory.
PROJECT_NAME := Notify
PROJECT_PATH :=
$(
abspath
.)
PROJECT_BOARD := evb
export
PROJECT_PATH PROJECT_BOARD
#CONFIG_TOOLPREFIX :=
-include
./proj_config.mk
ifeq
(
$(
origin
BL60X_SDK_PATH)
, undefined)
BL60X_SDK_PATH ?= /home/wildboarg/github/Ai-Thinker-WB2
$(info ****** Please SET BL60X_SDK_PATH ******)
$(info ****** Trying SDK PATH [
$(BL60X_SDK_PATH)
])
endif
COMPONENTS_BLSYS := bltime blfdt blmtd bloop loopset looprt loopadc
COMPONENTS_VFS := romfs
COMPONENTS_NETWORK := wifi wifi_manager wpa_supplicant bl_os_adapter wifi_hosal lwip lwip_dhcpd httpc netutils blcrypto_suite dns_server
INCLUDE_COMPONENTS += freertos_riscv_ram
INCLUDE_COMPONENTS += bl602 bl602_std
INCLUDE_COMPONENTS += hosal mbedtls_lts cli vfs yloop utils blog blog_testc newlibc coredump easyflash4
INCLUDE_COMPONENTS += rfparam_adapter_tmp
INCLUDE_COMPONENTS +=
$(COMPONENTS_NETWORK)
INCLUDE_COMPONENTS +=
$(COMPONENTS_BLSYS)
INCLUDE_COMPONENTS +=
$(COMPONENTS_VFS)
INCLUDE_COMPONENTS +=
$(PROJECT_NAME)
include
$(BL60X_SDK_PATH)
/make_scripts_riscv/project.mk- Modify pro_config.mk (still not very user-friendly; I'll update wb2-cli to generate it automatically when I have time)
Click to expand full code
CONFIG_BOARD_FLASH_SIZE :=
2
#firmware config domain
#
#set CONFIG_ENABLE_ACP to 1 to enable ACP, set to 0 or comment this line to disable
#CONFIG_ENABLE_ACP:=1
CONFIG_BL_IOT_FW_AP:=
1
CONFIG_BL_IOT_FW_AMPDU:=
0
CONFIG_BL_IOT_FW_AMSDU:=
0
CONFIG_BL_IOT_FW_P2P:=
0
CONFIG_ENABLE_PSM_RAM:=
1
#CONFIG_ENABLE_CAMERA:=1
#CONFIG_ENABLE_BLSYNC:=1
#CONFIG_ENABLE_VFS_SPI:=1
CONFIG_ENABLE_VFS_ROMFS:=
1
CONFIG_EASYFLASH_ENABLE:=
1
CONFIG_SYS_APP_TASK_STACK_SIZE:=
4096
CONFIG_SYS_APP_TASK_PRIORITY:=
15
CONFIG_FREERTOS_TICKLESS_MODE:=
0
CONFIG_SYS_VFS_ENABLE:=
1
CONFIG_SYS_VFS_UART_ENABLE:=
1
CONFIG_SYS_AOS_CLI_ENABLE:=
1
CONFIG_SYS_AOS_LOOP_ENABLE:=
1
CONFIG_SYS_BLOG_ENABLE:=
1
CONFIG_SYS_DMA_ENABLE:=
0
CONFIG_SYS_USER_VFS_ROMFS_ENABLE:=
0
CONFIG_BL602_USE_ROM_DRIVER:=
1
CONFIG_BT:=
0
CONFIG_BT_CENTRAL:=
1
CONFIG_BT_OBSERVER:=
1
CONFIG_BT_PERIPHERAL:=
1
CONFIG_BT_STACK_CLI:=
1
CONFIG_BT_MESH :=
0
CONF_ENABLE_COREDUMP:=
1
#blog enable components format :=blog_testc cli vfs helper
LOG_ENABLED_COMPONENTS:=blog_testc hosal loopset looprt bloop notify_task- Port the main.c used for networking
Click to expand full code
/**
* @file main.c
* @author your name (you@domain.com)
* @brief
* @version 0.1
* @date 2022-10-13
*
* @copyright Copyright (c) 2022
*
*/
#
include
"notify.h"
#
include
<FreeRTOS.h>
#
include
<aos/kernel.h>
#
include
<aos/yloop.h>
#
include
<blog.h>
#
include
<cli.h>
#
include
<hal_wifi.h>
#
include
<lwip/init.h>
#
include
<lwip/sockets.h>
#
include
<lwip/tcpip.h>
#
include
<stdio.h>
#
include
<string.h>
#
include
<task.h>
#
include
<wifi_mgmr_ext.h>
#
define
ROUTER_SSID
"CU_5ZPu"
#
define
ROUTER_PWD
"HYGS3305"
typedef
struct
{
const
char
*title;
const
char
*message;
const
char
*priority;
} Message;
static
wifi_conf_t
conf = {
.country_code =
"CN"
,
};
/**
* @brief wifi_sta_connect
* wifi station mode connect start
* @param ssid
* @param password
*/
static
void
wifi_sta_connect
(
char
*ssid,
char
*password)
{
wifi_interface_t
wifi_interface;
wifi_interface = wifi_mgmr_sta_enable();
wifi_mgmr_sta_connect(wifi_interface, ssid, password,
NULL
,
NULL
,
0
,
0
);
}
/**
* @brief event_cb_wifi_event
* wifi connet ap event Callback function
* @param event
* @param private_data
*/
static
void
event_cb_wifi_event
(
input_event_t
*event,
void
*private_data)
{
static
char
*ssid;
static
char
*password;
// 构建消息内容结构体
static
const
char
title[] =
"Testtitle"
;
static
const
char
message[] =
"hello,this is test message"
;
static
const
char
priority[] =
"0"
;
static
Message msg = {title, message, priority};
switch
(event->code) {
case
CODE_WIFI_ON_INIT_DONE: {
printf
(
"[APP] [EVT] INIT DONE %lld\r\n"
, aos_now_ms());
wifi_mgmr_start_background(&conf);
}
break
;
case
CODE_WIFI_ON_MGMR_DONE: {
printf
(
"[APP] [EVT] MGMR DONE %lld\r\n"
, aos_now_ms());
//_connect_wifi();
wifi_sta_connect(ROUTER_SSID, ROUTER_PWD);
}
break
;
case
CODE_WIFI_ON_SCAN_DONE: {
printf
(
"[APP] [EVT] SCAN Done %lld\r\n"
, aos_now_ms());
// wifi_mgmr_cli_scanlist();
}
break
;
case
CODE_WIFI_ON_DISCONNECT: {
printf
(
"[APP] [EVT] disconnect %lld\r\n"
, aos_now_ms());
}
break
;
case
CODE_WIFI_ON_CONNECTING: {
printf
(
"[APP] [EVT] Connecting %lld\r\n"
, aos_now_ms());
}
break
;
case
CODE_WIFI_CMD_RECONNECT: {
printf
(
"[APP] [EVT] Reconnect %lld\r\n"
, aos_now_ms());
}
break
;
case
CODE_WIFI_ON_CONNECTED: {
printf
(
"[APP] [EVT] connected %lld\r\n"
, aos_now_ms());
}
break
;
case
CODE_WIFI_ON_PRE_GOT_IP: {
printf
(
"[APP] [EVT] connected %lld\r\n"
, aos_now_ms());
}
break
;
case
CODE_WIFI_ON_GOT_IP: {
printf
(
"[APP] [EVT] GOT IP %lld\r\n"
, aos_now_ms());
printf
(
"[SYS] Memory left is %d Bytes\r\n"
, xPortGetFreeHeapSize());
// wifi connection succeeded, create tcp server task
xTaskCreate(¬ify_task,
"notify_task"
,
1024
*
10
, &msg,
5
,
NULL
);
}
break
;
case
CODE_WIFI_ON_PROV_SSID: {
printf
(
"[APP] [EVT] [PROV] [SSID] %lld: %s\r\n"
, aos_now_ms(),
event->value ? (
const
char
*)event->value :
"UNKNOWN"
);
if
(ssid) {
vPortFree(ssid);
ssid =
NULL
;
}
ssid = (
char
*)event->value;
}
break
;
case
CODE_WIFI_ON_PROV_BSSID: {
printf
(
"[APP] [EVT] [PROV] [BSSID] %lld: %s\r\n"
, aos_now_ms(),
event->value ? (
const
char
*)event->value :
"UNKNOWN"
);
if
(event->value) {
vPortFree((
void
*)event->value);
}
}
break
;
case
CODE_WIFI_ON_PROV_PASSWD: {
printf
(
"[APP] [EVT] [PROV] [PASSWD] %lld: %s\r\n"
, aos_now_ms(),
event->value ? (
const
char
*)event->value :
"UNKNOWN"
);
if
(password) {
vPortFree(password);
password =
NULL
;
}
password = (
char
*)event->value;
}
break
;
case
CODE_WIFI_ON_PROV_CONNECT: {
printf
(
"[APP] [EVT] [PROV] [CONNECT] %lld\r\n"
, aos_now_ms());
printf
(
"connecting to %s:%s...\r\n"
, ssid, password);
wifi_sta_connect(ssid, password);
}
break
;
case
CODE_WIFI_ON_PROV_DISCONNECT: {
printf
(
"[APP] [EVT] [PROV] [DISCONNECT] %lld\r\n"
, aos_now_ms());
}
break
;
default
: {
printf
(
"[APP] [EVT] Unknown code %u, %lld\r\n"
, event->code, aos_now_ms());
/*nothing*/
}
}
static
void
proc_main_entry
(
void
*pvParameters)
{
aos_register_event_filter(EV_WIFI, event_cb_wifi_event,
NULL
);
hal_wifi_start_firmware_task();
aos_post_event(EV_WIFI, CODE_WIFI_ON_INIT_DONE,
0
);
vTaskDelete(
NULL
);
}
void
main
()
{
puts
(
"[OS] Starting TCP/IP Stack...\r\n"
);
tcpip_init(
NULL
,
NULL
);
puts
(
"[OS] proc_main_entry task...\r\n"
);
xTaskCreate(proc_main_entry, (
char
*)
"main_entry"
,
1024
,
NULL
,
15
,
NULL
);
}- Create the notification source file and header file
Click to expand: touch
touch
notify.c notify.h- Write the notification functions
notify.h
Click to expand full code
#
ifndef
__NOTIFY_H__
#
define
__NOTIFY_H__
#
define
WEB_SERVER
"192.168.0.14"
//这里改成你搭建的gotify服务器地址
#
define
WEB_PORT
"8000"
//默认地址是80 我改为了8000
#
define
WEB_PATH_BASE
"/message"
#
define
QUERY_STRING
"?token=A-ahgMFXlr2wekG"
//token替换为你的通道token
void
notify_task
(
void
*pvParameters)
;
#
endifnotify.c
Click to expand full code
#
include
"notify.h"
#
include
<FreeRTOS.h>
#
include
<blog.h>
#
include
<cli.h>
#
include
<http_client.h>
#
include
<lwip/err.h>
#
include
<lwip/netdb.h>
#
include
<lwip/sockets.h>
#
include
<lwip/tcp.h>
#
include
<stdio.h>
#
include
<task.h>
#
define
BOUNDARY
"---"
static
char
REQUEST[
512
];
typedef
struct
{
const
char
*title;
const
char
*message;
const
char
*priority;
} Message;
void
notify_task
(
void
*pvParameters)
{
Message *params = (Message *)pvParameters;
// 接收参数
//
//
//
printf
(
"title=%s \r\nmessage=%s\r\npriority=%s\r\n"
, params->title,
params->message, params->priority);
//
const
struct
addrinfo
hints
=
{
.ai_family = AF_INET,
.ai_socktype = SOCK_STREAM,
};
struct
addrinfo
*
res
;
struct
in_addr
*
addr
;
int
s, r;
char
recv_buf[
4096
];
while
(
1
) {
int
err = getaddrinfo(WEB_SERVER, WEB_PORT, &hints, &res);
if
(err !=
0
|| res ==
NULL
) {
blog_error(
"DNS lookup failed err=%d res=%p"
, err, res);
vTaskDelay(
1000
/ portTICK_PERIOD_MS);
continue
;
}
/* Code to print the resolved IP.
Note: inet_ntoa is non-reentrant, look at ipaddr_ntoa_r for "real" code
*/
addr = &((
struct
sockaddr_in *)res->ai_addr)->sin_addr;
blog_info(
"DNS lookup succeeded. IP=%s"
, inet_ntoa(*addr));
s = socket(res->ai_family, res->ai_socktype,
0
);
if
(s <
0
) {
blog_error(
"... Failed to allocate socket."
);
freeaddrinfo(res);
vTaskDelay(
1000
/ portTICK_PERIOD_MS);
continue
;
}
blog_info(
"... allocated socket"
);
if
(connect(s, res->ai_addr, res->ai_addrlen) !=
0
) {
blog_error(
"... socket connect failed errno=%d"
, errno);
close(s);
freeaddrinfo(res);
vTaskDelay(
4000
/ portTICK_PERIOD_MS);
continue
;
}
blog_info(
"... connected"
);
freeaddrinfo(res);
// 构造请求体
char
body[
1024
];
snprintf
(body,
sizeof
(body),
"--%s\r\n"
"Content-Disposition: form-data; name=\"title\"\r\n\r\n"
"%s\r\n"
"--%s\r\n"
"Content-Disposition: form-data; name=\"message\"\r\n\r\n"
"%s\r\n"
"--%s\r\n"
"Content-Disposition: form-data; name=\"priority\"\r\n\r\n"
"%s\r\n"
"--%s--\r\n"
,
BOUNDARY, params->title, BOUNDARY, params->message, BOUNDARY,
params->priority, BOUNDARY);
snprintf
(REQUEST,
sizeof
(REQUEST),
"POST %s%s HTTP/1.0\r\n"
"Host: %s:%s\r\n"
"User-Agent: aithinker wb2\r\n"
"Content-Type: multipart/form-data; boundary=%s\r\n"
"Content-Length: %d\r\n"
"\r\n"
"%s"
,
WEB_PATH_BASE, QUERY_STRING, WEB_SERVER, WEB_PORT, BOUNDARY,
strlen
(body), body);
if
(write(s, REQUEST,
strlen
(REQUEST)) <
0
) {
blog_error(
"... socket send failed"
);
close(s);
vTaskDelay(
4000
/ portTICK_PERIOD_MS);
continue
;
}
blog_info(
"... socket send success"
);
struct
timeval
receiving_timeout
;
receiving_timeout.tv_sec =
5
;
receiving_timeout.tv_usec =
0
;
if
(setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &receiving_timeout,
sizeof
(receiving_timeout)) <
0
) {
blog_error(
"... failed to set socket receiving timeout"
);
close(s);
vTaskDelay(
4000
/ portTICK_PERIOD_MS);
continue
;
}
blog_info(
"... set socket receiving timeout success"
);
// FIXME fix putchar
extern
int
bl_putchar
(
int
c)
;
/* Read HTTP response */
do
{
bzero(recv_buf,
sizeof
(recv_buf));
r = read(s, recv_buf,
sizeof
(recv_buf) -
1
);
for
(
int
i =
0
; i < r; i++) {
bl_putchar(recv_buf[i]);
}
}
while
(r >
0
);
blog_info(
"... done reading from socket. Last read return=%d "
"errno=%d\r\n"
,
r, errno);
close(s);
for
(
int
countdown =
10
; countdown >=
0
; countdown--) {
blog_info(
"%d... "
, countdown);
vTaskDelay(
1000
/ portTICK_PERIOD_MS);
}
blog_info(
"Starting again!"
);
}
}Flash the program and test

You can see from the background web page or the terminal logs that the message has been pushed to the server
Mobile: (the mobile app will pop up a notification in the status bar!)
Login:

View:

What's Next
Next, I plan to pair it with the dust-collecting RD04 radar module to make a human detection push notification. It's not safe for a girl renting alone — placed at home, it's a proper little burglar detector.

