发帖
7 0 0

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

WildboarG
论坛元老

16

主题

141

回帖

3185

积分

论坛元老

积分
3185
Ai-WB2系列 67 7 3 天前

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


Static Badge Static Badge Static Badge

关于


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

常见的个人推送项目:

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
    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} 替换为支持的任一平台。

    wget https://github.com/gotify/server/releases/download/v{VERSION}/gotify-{PLATFORM}.zip
    

    解压存档

    unzip gotify-{PLATFORM}.zip
    

    使二进制文件可执行

    chmod +x gotify-{PLATFORM}
    

    下载默认配置文件

    wget -O config.yml https://raw.githubusercontent.com/gotify/server/master/config.example.yml
    

    启动服务

    ./gotify-{PLATFORM}
    
  2. 登陆设置
    网页登陆gotify服务端进行配置
    打开localhost
    首次登陆用管理员用户密码皆是admin

截图2025-03-0310-47-33.png
点击USERS,Create一个新用户,然后退出登陆。
用新用户登陆。
点击APPS,创建一组推送用户令牌。

截图2025-03-0310-51-13.png

这里就搭建完成。


  1. 下载手机端

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

  1. 命令行测试是否生效

截图2025-03-0315-10-24.png

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

  • 标题:hello
  • 消息:cli notify

打开网页验证

截图2025-03-0315-11-58.png

Ai-WB2推送


AI-WB2wifi连接

  1. 创建一个新工程叫Notify
## wb2-cli create
Create New Project:Notify
Notify Created Successfully
  1. 添加必要的必要的network库到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更新为自动生成的)
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
/**
 * @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 notify.c notify.h
  1. 编写通知函数

notify.h

#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

#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!");
  }
}

烧录程序测试

截图2025-03-0315-17-00.png

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

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

登陆:

1740986629342.jpg

查看:

1740986629339.jpg

后续


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

──── 0人觉得很赞 ────

使用道具 举报

棒~啥时候把那个spi重新跑一遍
3 天前
爱笑 发表于 2025-3-3 15:48
棒~啥时候把那个spi重新跑一遍

下一篇
这是本地搭建的服务器吗
3 天前
HaydenHu 发表于 2025-3-3 17:49
这是本地搭建的服务器吗

没有nas,可以把推送服务放在树莓派吗?
前天 13:41
本帖最后由 WildboarG 于 2025-3-4 13:43 编辑
bzhou830 发表于 2025-3-4 08:32
没有nas,可以把推送服务放在树莓派吗?

可以的软件很小,用docker部署一下,100来兆,在配合IPV6 ddns 或者内网穿透就可以远程接收了,手机端支持IPV6域名的
厉害
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 28019 个
  • 话题数: 39445 篇