发帖
8 1 0

[AiPi-PalchatV1]定制自己的小安AI内置工具

WildboarG
论坛元老

34

主题

236

回帖

6591

积分

论坛元老

积分
6591
小安AI 332 8 2025-11-9 23:21:30
[i=s] 本帖最后由 WildboarG 于 2025-11-10 08:46 编辑 [/i]



Static Badge Static Badge Static Badge

准备工作


  • 小安AI开发板一枚(AI-wv01-32s)
  • 集成开发环境SDK 确保配置无误
  • 一双懒惰的手

what


  1. 首先明确一下想将什么工具集成到内置环境
  2. 编写对应的函数
  3. 编译验证

过程


我本地nas有一个alist服务挂载我的硬盘,由于接入了公网环境可以从外部访问,为了安全起见,开启了2FA二次验证(自行百度),就是输入了账号密码还需要验证码,避免我在公共环境下登陆而保存了密码,2FA验证码每30S更新一次,我绑定在手机的密码本里,但是手机进入密码需要经过设置,设置进行了加密,我要拿到验证码需要打开手机(刷脸一次)进入设置(刷脸第二次),查看验证码(刷脸第三次)。在家我想方便快捷的访问我的alist硬盘可就有点冗余了,就想着将验证码写入小安内置,让小安帮我读出来。

然后就看打开SDK,参考内置工具写,小安AI内置了2个工具,调整音量开灯.

参考一下先写个查询 CPU温度的小工具验证一下,之前写过检测CPU温度直接就拿来。

按照手册的要求定义回复格式

extern hosal_adc_dev_t adc0;

static void checkTEMPRequestHander(void *value){
  if(value == NULL){
    return;
  }
  returnValues_t *returnValues = (returnValues_t *)value;
  uint8_t temperature=0;
  for(uint8_t i=0;i<3;i++){
    temperature = hosal_adc_tsen_value_get(&adc0);
    vTaskDelay(pdMS_TO_TICKS(5));
  }
  vTaskDelay(pdMS_TO_TICKS(10));
  sprintf(returnValues->value, "%d", temperature);
    printf("[%s()-%d]get temperature:%s\r\n", __func__, __LINE__, returnValues->value);

#ifdef UART_MCP_ENABLE
    printf_at("{\"role\":\"AI board\",\"temperature\":%s,\"msgType\":\"status\",\"status\":\"%s\"}\r\n\r\n", returnValues->value, "OK");
#endif
}

然后烧录验证:

验证通过,唤醒小安可以问它当前CPU的温度是多少。现在是冬天,测试了一下室内环境小安开一天温度还是能压到45度

然后来分析一下我的2FA验证,用python脚本先验证一下2FA需要到底是是什么本地时间还是UTC时间

import hmac
import struct
import hashlib
import base64

SECRET_BASE32 = "32位BASE32KEY"

def hotp(key: bytes, counter: int) -> int:
    """标准的 HOTP 函数(RFC 4226)"""
    msg = struct.pack(">Q", counter & 0xFFFFFFFFFFFFFFFF)
    digest = hmac.new(key, msg, hashlib.sha1).digest()
    offset = digest[-1] & 0x0F
    binary = struct.unpack(">I", digest[offset:offset+4])[0] & 0x7FFFFFFF
    return binary % 1_000_000

def totp():
    key = base64.b32decode(SECRET_BASE32, casefold=True)
    utc_now = 0  # 1970-01-01 00:00:00 UTC
    current_step = utc_now // 30  # 0 // 30 = 0
    return hotp(key, current_step)

if __name__ == "__main__":
    otp = totp()
    print(f"{otp:06d}")

先用当前时间的时戳计算一个固定的base32数据的验证码,从在线网站也生成一个进行对比验证加密算法,确认我的alist2FA验证的格式,然后用一块WB2的板子写出对应的C代码,然后将脚本中的时戳改成 1970-01-01 00:00:00 UTC原点时戳(因为板子先不进行NTP联网时钟同步),运行脚本和wb2的输出比对,直到确定C算法没有问题。

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include "mbedtls/sha1.h"

// Base32 字符表
static const char BASE32_ALPHABET[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";

// Base32 解码
size_t base32_decode(const char *str, uint8_t *out, size_t out_len) {
    size_t len = strlen(str);
    size_t out_idx = 0;
    uint32_t buffer = 0;
    int bits = 0;
    int i;

    for (i = 0; i < len; i++) {
        char c = toupper(str[i]);
        if (c == '\0' || c == '=') break;
        const char *p = strchr(BASE32_ALPHABET, c);
        if (!p) continue;
        int val = p - BASE32_ALPHABET;

        buffer = (buffer << 5) | val;
        bits += 5;
        if (bits >= 8) {
            if (out_idx >= out_len) return 0;
            out[out_idx++] = (buffer >> (bits - 8)) & 0xFF;
            bits -= 8;
        }
    }
    return out_idx;
}

// HMAC-SHA1(原代码保持不变)
static void hmac_sha1(const uint8_t *key, size_t key_len,
                      const uint8_t *data, size_t data_len,
                      uint8_t *result) {
    mbedtls_sha1_context ctx;
    uint8_t k_pad[64] = {0};
    uint8_t tk[20];
    size_t i;

    if (key_len > 64) {
        mbedtls_sha1_ret(key, key_len, tk);
        key = tk;
        key_len = 20;
    }
    memcpy(k_pad, key, key_len);
    for (i = 0; i < key_len; ++i) k_pad[i] ^= 0x36;
    for (i = key_len; i < 64; ++i) k_pad[i] = 0x36;
    mbedtls_sha1_init(&ctx);
    mbedtls_sha1_starts_ret(&ctx);
    mbedtls_sha1_update_ret(&ctx, k_pad, 64);
    mbedtls_sha1_update_ret(&ctx, data, data_len);
    mbedtls_sha1_finish_ret(&ctx, result);
    mbedtls_sha1_free(&ctx);
    for (i = 0; i < key_len; ++i) k_pad[i] ^= 0x36 ^ 0x5C;
    for (i = key_len; i < 64; ++i) k_pad[i] = 0x5C;
    mbedtls_sha1_init(&ctx);
    mbedtls_sha1_starts_ret(&ctx);
    mbedtls_sha1_update_ret(&ctx, k_pad, 64);
    mbedtls_sha1_update_ret(&ctx, result, 20);
    mbedtls_sha1_finish_ret(&ctx, result);
    mbedtls_sha1_free(&ctx);
}

// 动态截取(原代码保持不变)
uint32_t dynamic_truncation(const uint8_t *hmac) {
    uint8_t offset = hmac[19] & 0x0F;
    return ((hmac[offset] & 0x7F) << 24) |
           ((hmac[offset + 1] & 0xFF) << 16) |
           ((hmac[offset + 2] & 0xFF) << 8) |
           (hmac[offset + 3] & 0xFF);
}

// 原 TOTP 函数(保持不变)
uint32_t totp(const uint8_t *key, uint32_t key_len,
              uint64_t time_step, uint8_t digits) {
    uint8_t msg[8];
    for (int i = 8; i--; time_step >>= 8) {
        msg[i] = time_step & 0xFF;
    }
    uint8_t hmac[20];
    hmac_sha1(key, key_len, msg, 8, hmac);
    uint32_t code = dynamic_truncation(hmac);
    uint32_t modulus = 1;
    for (uint8_t i = 0; i < digits; ++i) modulus *= 10;
    return code % modulus;
}

// 新增支持 Base32 的 TOTP 函数
uint32_t totp_base32(const char *base32_key, uint64_t time_step, uint8_t digits) {
    size_t base32_len = strlen(base32_key);
    size_t max_out_len = (base32_len * 5) / 8 + 1;
    uint8_t *key = malloc(max_out_len);
    if (!key) return 0;

    size_t key_len = base32_decode(base32_key, key, max_out_len);
    if (key_len == 0) {
        free(key);
        return 0;
    }

    uint32_t result = totp(key, key_len, time_step, digits);
    free(key);
    return result;
}
#ifndef __TOTP_H__
#define __TOTP_H__

#include <stdint.h>
#include <stddef.h>

// 注意:下面两行必须和 totp.c 里完全一模一样!!
uint32_t dynamic_truncation(const uint8_t *hmac);

uint32_t totp(const uint8_t *key, uint32_t key_len,
              uint64_t time_step, uint8_t digits);

uint32_t totp_base32(const char *base32_key, uint64_t time_step, uint8_t digits);
#endif

进行移植到AI目录下

/~/aipi-palchatv1/mcp_server

编写对应AI的获取 OTP的工具

static void checkOTPRequestHander(void *value){
  if(value == NULL){
    return;
  }
  const char *SECRET_BASE32 = "NOO6NRJHXNP4MUIY4RCB5O7EFQSWBTMF";

  returnValues_t *returnValues = (returnValues_t *)value;
  int utc_now=0,weishu=0;
  sntp_get_time(&utc_now,&weishu); //获取当前时间戳
  uint64_t time_step = utc_now/30;
  uint32_t otp = totp_base32(SECRET_BASE32, time_step, 6);
 // int otp = 1234;
  sprintf(returnValues->value, "%lu", otp);
    printf("[%s()-%d]get otp:%s\r\n", __func__, __LINE__, returnValues->value);

#ifdef UART_MCP_ENABLE
    printf_at("{\"role\":\"AI board\",\"OTP\":%s,\"msgType\":\"status\",\"status\":\"%s\"}\r\n\r\n", returnValues->value, "OK");
#endif
}

当前密钥只有一个固定在代码里

KEY = NOO6NRJHXNP4MUIY4RCB5O7EFQSWBTMF

内置工具注册

//添加一个2FA验证码获取的工具
  mcp_server_tool_t otp = {
    .name = "onetipa",
    .description = "获取基于时间的一次性验证码",
    .setRequestHandler =  NULL,
    .checkRequestHandler = checkOTPRequestHander,
    .inputSchema = {
      .properties = {
      {"OTP","无设置功能,(查询是值为:null)",MCP_SERVER_TOOL_TYPE_NUMBER},
      },
    },
  };
  ret  = mcp_server_add_tool_to_toolList(json_toolsList,&otp);
  if(ret != 0){
    return ret;
  }

烧录到小安AI进行使用测试,一直报超时,就是不起作用。先说结果,在mcp_server.h文件里定义了工具的个数是3个,内置是两个,我加了一个测量CPU温度的没有问题,又加了一个OTP验证码,此时工具是4个,第四个注册就不起作用,我一开始以为是注册了就可以用了,每注意到这里有限制,多余的工具即便存在调用也超时无法使用。把工具数量改大就可以再次编译测试。

结果如图:

20251109_23h04m42s_grim.png

固件


固件:upload 附件:wildboarg.tar

密码:NOO6NRJHXNP4MUIY4RCB5O7EFQSWBTMF

在线OTP密码生成器: 验证网站

密钥格式:BASE32 算法:SHA1

演示✨



──── 1人觉得很赞 ────

使用道具 举报

那个动画是怎么搞出来的,好高级
2025-11-10 09:36:29
bzhou830 发表于 2025-11-10 08:20
那个动画是怎么搞出来的,好高级

那个是访客记录,每看到一次就会+1,忘记在哪抄的,直接从我主页复制过来的
赞👍
AiPi-PalChatV1:求求啦,我的内存就这一点了
2025-11-10 19:29:40
起个名字好难啊 发表于 2025-11-10 17:35
AiPi-PalChatV1:求求啦,我的内存就这一点了

榨干!!!  话说莫哥要不在用mdns搞个局域网内设备发现自动注册成工具让小安去调用
2025-11-11 00:27:21
哇,好高级的开头!
哇,这个开头怎么弄的啊
2025-11-11 16:22:19
小安(助理版) 发表于 2025-11-11 14:41
哇,这个开头怎么弄的啊

你求求我
您需要登录后才可以回帖 立即登录
高级模式
返回
统计信息
  • 会员数: 30224 个
  • 话题数: 44437 篇