Skip to content

由 WildboarG 贡献,Ai-Thinker 进行整理

用AI-WB2消息推送到个人通知

AI-WB2消息推送到个人通知


关于


常见的个人推送项目通常是指开发者或个人利用现有技术搭建的通知或消息推送系统,用于满足个人需求(如提醒、消息转发、状态监控等)或作为学习和展示技术能力的项目。这些项目往往具有轻量级、易部署、开源等特点,常用于自动化通知、个人效率提升或兴趣实验。

常见的个人推送项目:

go-cqhttp

gotify

server 酱

pushplus

qmsg 酱

wxpusher

巴法云

飞书

SMTP邮件

WEBHOOK

企业微信机器人

这些笔者用过的个人消息推送项目都不错,有微信公众号,qq机器人,邮件等等。部分应用有免费额度限制。

应用场景

在一下简单的低功耗场景下或只需要偶尔发送通知的场景中,没有必要使用持久化连接大规模推送,比如,一些青龙面板的任务通知。那么在单片机的是否也有这种需求,一些低功耗的场景下,我觉得http的一次性推送比mqtt更有性价比。

MQTT 需要部署broker,而http 进行简单的消息推送只需要目标url。

一些不需要部署的微信服务号完全满足接受消息的通知,比如pushplus。但在一些隐私(比如动态密码)内容的推送还是不建议推送到别人的服务器上。我们可以自己搭建一个轻量级的消息推送服务器在家里的NAS或路由器。

搭建消息服务器


带大家从头搭建一个个人推送消息服务器(NOTIFY)比用Ai-wb2进行消息推送,这样手机上就可以接收到单片机推送的消息了。

  1. 下载notify服务端

    • docker部署到NAS
📜 点击展开完整代码
html
docker run -p 80:80 -v /var/gotify/data:/app/data ghcr.io/gotify/server
  • 本地电脑下载部署

    • 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)

    下载适用于您平台的二进制文件 zip 包,来自 gotify/server 发行版,用占位符表示版本和平台。您需要将 {VERSION} 替换为最新版本,将 {PLATFORM} 替换为支持的任一平台。

📜 点击展开完整代码
html
wget [https://github.com/gotify/server/releases/download/v](https://github.com/gotify/server/releases/download/v){VERSION}/gotify-{PLATFORM}.zip

解压存档

📜 点击展开完整代码
html
unzip gotify-{PLATFORM}.zip

使二进制文件可执行

📜 点击展开完整代码
html
chmod +x gotify-{PLATFORM}

下载默认配置文件

📜 点击展开完整代码
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)

启动服务

📜 点击展开完整代码
html
./gotify-{PLATFORM}
  1. 登陆设置 网页登陆gotify服务端进行配置 打开localhost 首次登陆用管理员用户密码皆是admin

点击USERS,Create一个新用户,然后退出登陆。 用新用户登陆。 点击APPS,创建一组推送用户令牌。

这里就搭建完成。


  1. 下载手机端

连接就输入ip地址加端口号,输入刚才创建的用户名密码,这样就能从手机上也能接收消息了。

  1. 命令行测试是否生效

返回json格式消息证明消息推送成功了

  • 标题:hello
  • 消息:cli notify

打开网页验证

Ai-WB2推送


AI-WB2wifi连接

  1. 创建一个新工程叫Notify
📜 点击展开完整代码
c
## wb2-cli create
Create New Project:Notify
Notify Created Successfully
  1. 添加必要的必要的network库到MAKEFILE
📜 点击展开完整代码
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. 修改pro_config.mk(还不太好用,等有空把wb2-cli更新为自动生成的)
📜 点击展开完整代码
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. 移植联网用的的main.c
📜 点击展开完整代码
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. 新建通知文件以及头文件
📜 点击展开:touch
sh
touch
 notify.c notify.h
  1. 编写通知函数

notify.h

📜 点击展开完整代码
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

📜 点击展开完整代码
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!"
);
  }
}

烧录程序测试

可以从背景网页或终端日志中发现消息已经推送到服务器了

手机端:(手机端会在状态栏弹窗提示的!)

登陆:

查看:

后续


后续打算用吃灰的RD04雷达模块配合做一个人体检测推送,女孩子一个人租房不安全,放家里不妥妥的小毛贼检测器。

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