Skip to content

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:

go-cqhttp

gotify

ServerChan

pushplus

Qmsg

wxpusher

Bemfa

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.

  1. Download the notify server

    • Deploy with Docker on a NAS
Click to expand full code
html
docker run -p 80:80 -v /var/gotify/data:/app/data ghcr.io/gotify/server
  • Download 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
html
wget [https://github.com/gotify/server/releases/download/v](https://github.com/gotify/server/releases/download/v){VERSION}/gotify-{PLATFORM}.zip

Unzip the archive

Click to expand full code
html
unzip gotify-{PLATFORM}.zip

Make the binary executable

Click to expand full code
html
chmod +x gotify-{PLATFORM}

Download the default configuration file

Click to expand full code
html
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
html
./gotify-{PLATFORM}
  1. 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.


  1. Download the mobile app

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.

  1. 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

  1. Create a new project called Notify
Click to expand full code
c
## wb2-cli create
Create New Project:Notify
Notify Created Successfully
  1. Add the required network libraries to the MAKEFILE
Click to expand full code
Makefile
#

## 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
  1. 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
cmake
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
  1. Port the main.c used for networking
Click to expand full code
C
/**
 * @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(&notify_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
);
}
  1. Create the notification source file and header file
Click to expand: touch
sh
touch
 notify.c notify.h
  1. Write the notification functions

notify.h

Click to expand full code
c
#
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)
;

#
endif

notify.c

Click to expand full code
c
#
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.

Released under the MIT License. Build Time 2026-09-11 14:52:23