《基于stm32的智慧家居基础项目》

发布于:2025-09-07 ⋅ 阅读:(17) ⋅ 点赞:(0)

智慧家居

开发文档:
OneNet开发文档

系统架构设计

首先我想先通过一个图片,来说明项目整体框架

在这里插入图片描述

硬件部分

1.按键与LED实现开关功能

LED驱动代码

#include "led.h"
uint8_t led_sta=0;
void led_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin=GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitStructure);
	GPIO_SetBits(GPIOC,GPIO_Pin_13);
}
void led_turn(void)
{
	if(GPIO_ReadInputDataBit(GPIOC,GPIO_Pin_13)==1)
	{
		GPIO_ResetBits(GPIOC,GPIO_Pin_13);
		led_sta=1;	
	}
	else
	{
		GPIO_SetBits(GPIOC,GPIO_Pin_13);
		led_sta=0;
	}
}
void led_off(void)
{
	GPIO_SetBits(GPIOC,GPIO_Pin_13);
	led_sta=0;
}

void led_on(void)
{
	GPIO_ResetBits(GPIOC,GPIO_Pin_13);
	led_sta=1;
}

Key驱动代码

疑点解析:

  1. 我这里用的是外部中断实现的
  2. 上拉输入就是默认是高电平1,比如我有个按键,按下就是0,如果是下拉输入,默认是0,按下是1
  3. EXTI9_5_IRQHandler(void)是触发中断要执行的功能
#include "key.h"


void key_init(void)
{
	GPIO_InitTypeDef my_key_init;
	EXTI_InitTypeDef my_exti_init;
	NVIC_InitTypeDef my_nvic_init;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启gpio口时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);//开启外部中断AFIO时钟
	
    //gpio初始化
	my_key_init.GPIO_Mode=GPIO_Mode_IPU;//上拉输入
	my_key_init.GPIO_Speed=GPIO_Speed_50MHz;
	my_key_init.GPIO_Pin=GPIO_Pin_6;
	GPIO_Init(GPIOA,&my_key_init);
	
    //AFIO初始化
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource6);
	
    //EXTI初始化
    my_exti_init.EXTI_Line = EXTI_Line6;             // 选择中断线 6,对应 GPIO 的第 6 号引脚
    my_exti_init.EXTI_LineCmd = ENABLE;              // 使能这条中断线
    my_exti_init.EXTI_Mode = EXTI_Mode_Interrupt;    // 中断模式(不是事件)
    my_exti_init.EXTI_Trigger = EXTI_Trigger_Falling;// 触发方式:下降沿触发
    EXTI_Init(&my_exti_init);                        // 调用库函数初始化
	
    //NVIC初始化
	//设置优先级
	my_nvic_init.NVIC_IRQChannel=EXTI9_5_IRQn;
	my_nvic_init.NVIC_IRQChannelCmd=ENABLE;
	my_nvic_init.NVIC_IRQChannelPreemptionPriority=2;
	my_nvic_init.NVIC_IRQChannelSubPriority=2;
	NVIC_Init(&my_nvic_init);
	
}

void EXTI9_5_IRQHandler(void)
{
	if (EXTI_GetITStatus(EXTI_Line6) != RESET)  // 判断是否真的是 EXTI6 触发的中断
	{
		if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0)
		{
			DelayMs(20);  // 简单消抖
			while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == 0);
			DelayMs(20);
			led_turn();  // 执行你的功能函数
		}

		EXTI_ClearITPendingBit(EXTI_Line6);  // 清除中断标志,避免重复进入中断
	}
}

OLED驱动我采用的是江科大的驱动代码,这里不写出了

2.温度与光照传感器模块

这里主要是adc初始化、adc转化温度光照值

#include "waishe.h"
#include <math.h>

#define R1 10000.0f      // NTC分压电阻阻值 10kΩ
#define B 3950.0f        // NTC B值
#define R0 10000.0f      // NTC 25℃时阻值
#define T0 298.15f       // 25℃开尔文温度
#define R2 10000.0f      // LDR分压电阻阻值 10kΩ
#define ADC_REF 3.3f     // ADC参考电压
#define ADC_MAX 4095.0f  // 12位ADC最大值

void waishe_init(void)
{
    GPIO_InitTypeDef gpio;
    ADC_InitTypeDef adc;
    
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_ADC1, ENABLE);

    gpio.GPIO_Mode = GPIO_Mode_AIN;
    gpio.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
    GPIO_Init(GPIOA, &gpio);
	
    //adc初始化
    ADC_DeInit(ADC1);

    adc.ADC_Mode = ADC_Mode_Independent;
    adc.ADC_ScanConvMode = DISABLE;
    adc.ADC_ContinuousConvMode = DISABLE;
    adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;
    adc.ADC_DataAlign = ADC_DataAlign_Right;
    adc.ADC_NbrOfChannel = 1;
    ADC_Init(ADC1, &adc);

    ADC_Cmd(ADC1, ENABLE);

    ADC_ResetCalibration(ADC1);
    while (ADC_GetResetCalibrationStatus(ADC1));
    ADC_StartCalibration(ADC1);
    while (ADC_GetCalibrationStatus(ADC1));
}

uint16_t Read_ADC_Channel(uint8_t channel)
{
    ADC_RegularChannelConfig(ADC1, channel, 1, ADC_SampleTime_239Cycles5);
    ADC_SoftwareStartConvCmd(ADC1, ENABLE);

    while (!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC));

    return ADC_GetConversionValue(ADC1);
}

// 计算NTC温度,返回摄氏度
float calculate_ntc_temperature(uint16_t adc_val)
{
    float voltage;
    float resistance;
    float temp_k;

    voltage = adc_val * ADC_REF / ADC_MAX;

    if (voltage == 0) return -273.15f;  // 防止除零错误

    resistance = (voltage * R1) / (ADC_REF - voltage);

    temp_k = 1.0f / ( (1.0f / T0) + (1.0f / B) * log(resistance / R0) );

    return temp_k - 273.15f;  // 转摄氏度
}

float calculate_ldr_light_level(uint16_t adc_val)
{
    float voltage;
    float resistance;
    float light_level;

    voltage = adc_val * ADC_REF / ADC_MAX;

    if (voltage == 0) return 0.0f;  // 防止除零

    resistance = (ADC_REF - voltage) * R2 / voltage;

    light_level = 100000.0f / resistance;

    if (light_level > 100.0f) light_level = 100.0f;
    if (light_level < 0.0f) light_level = 0.0f;

    return light_level;
}

void show_info(float *temperature, float *light)
{
    uint16_t temp_val = Read_ADC_Channel(ADC_Channel_0);   // PA0: NTC
    uint16_t light_val = Read_ADC_Channel(ADC_Channel_1);  // PA1: LDR

    *temperature = calculate_ntc_temperature(temp_val);
    *light = calculate_ldr_light_level(light_val);

    OLED_ShowString(1, 1, "Tem:");
    OLED_ShowNum(1, 5, (int)(*temperature), 2);

    OLED_ShowString(2, 1, "Light:");
    OLED_ShowNum(2, 7, (100-(int)(*light)%100), 2);
    OLED_ShowString(2, 9, "%");

	
	if (GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_13) == 0)
    {
        OLED_ShowString(3, 1, "Led_status:On   "); // 注意后面空格
    }
    else
    {
        OLED_ShowString(3, 1, "Led_status:Off  ");
    }
}

3.usart通讯模块

Usart1用于ch340通讯
/*
************************************************************
*	函数名称:	Usart1_Init
*
*	函数功能:	串口1初始化
*
*	入口参数:	baud:设定的波特率
*
*	返回参数:	无
*
*	说明:		TX-PA9		RX-PA10
************************************************************
*/
void Usart1_Init(unsigned int baud)
{

	GPIO_InitTypeDef gpio_initstruct;
	USART_InitTypeDef usart_initstruct;
	NVIC_InitTypeDef nvic_initstruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	
	//PA9	TXD
	gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
	gpio_initstruct.GPIO_Pin = GPIO_Pin_9;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_initstruct);
	
	//PA10	RXD
	gpio_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	gpio_initstruct.GPIO_Pin = GPIO_Pin_10;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_initstruct);
	
	usart_initstruct.USART_BaudRate = baud;
	usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		//无硬件流控
	usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;						//接收和发送
	usart_initstruct.USART_Parity = USART_Parity_No;									//无校验
	usart_initstruct.USART_StopBits = USART_StopBits_1;								//1位停止位
	usart_initstruct.USART_WordLength = USART_WordLength_8b;							//8位数据位
	USART_Init(USART1, &usart_initstruct);
	
	USART_Cmd(USART1, ENABLE);														//使能串口
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);									//使能接收中断
	
	nvic_initstruct.NVIC_IRQChannel = USART1_IRQn;
	nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;
	nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;
	nvic_initstruct.NVIC_IRQChannelSubPriority = 2;
	NVIC_Init(&nvic_initstruct);

}
Usart2用于esp和服务器通讯
/*
************************************************************
*	函数名称:	Usart2_Init
*
*	函数功能:	串口2初始化
*
*	入口参数:	baud:设定的波特率
*
*	返回参数:	无
*
*	说明:		TX-PA2		RX-PA3
************************************************************
*/
void Usart2_Init(unsigned int baud)
{

	GPIO_InitTypeDef gpio_initstruct;
	USART_InitTypeDef usart_initstruct;
	NVIC_InitTypeDef nvic_initstruct;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
	
	//PA2	TXD
	gpio_initstruct.GPIO_Mode = GPIO_Mode_AF_PP;
	gpio_initstruct.GPIO_Pin = GPIO_Pin_2;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_initstruct);
	
	//PA3	RXD
	gpio_initstruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	gpio_initstruct.GPIO_Pin = GPIO_Pin_3;
	gpio_initstruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA, &gpio_initstruct);
	
	usart_initstruct.USART_BaudRate = baud;
	usart_initstruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;		//无硬件流控
	usart_initstruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;						//接收和发送
	usart_initstruct.USART_Parity = USART_Parity_No;									//无校验
	usart_initstruct.USART_StopBits = USART_StopBits_1;								//1位停止位
	usart_initstruct.USART_WordLength = USART_WordLength_8b;							//8位数据位
	USART_Init(USART2, &usart_initstruct);
	
	USART_Cmd(USART2, ENABLE);														//使能串口
	
	USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);									//使能接收中断
	
	nvic_initstruct.NVIC_IRQChannel = USART2_IRQn;
	nvic_initstruct.NVIC_IRQChannelCmd = ENABLE;
	nvic_initstruct.NVIC_IRQChannelPreemptionPriority = 0;
	nvic_initstruct.NVIC_IRQChannelSubPriority = 0;
	NVIC_Init(&nvic_initstruct);

}


串口函数
/*
************************************************************
*	函数名称:	Usart_SendString
*
*	函数功能:	串口数据发送
*
*	入口参数:	USARTx:串口组
*				str:要发送的数据
*				len:数据长度
*
*	返回参数:	无
*
*	说明:		
************************************************************
*/
void Usart_SendString(USART_TypeDef *USARTx, unsigned char *str, unsigned short len)
{

	unsigned short count = 0;
	
	for(; count < len; count++)
	{
		USART_SendData(USARTx, *str++);									//发送数据
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);		//等待发送完成
	}

}

/*
************************************************************
*	函数名称:	UsartPrintf
*
*	函数功能:	格式化打印
*
*	入口参数:	USARTx:串口组
*				fmt:不定长参
*
*	返回参数:	无
*
*	说明:		
************************************************************
*/
void UsartPrintf(USART_TypeDef *USARTx, char *fmt,...)
{

	unsigned char UsartPrintfBuf[296];
	va_list ap;
	unsigned char *pStr = UsartPrintfBuf;
	
	va_start(ap, fmt);
	vsnprintf((char *)UsartPrintfBuf, sizeof(UsartPrintfBuf), fmt, ap);							//格式化
	va_end(ap);
	
	while(*pStr != 0)
	{
		USART_SendData(USARTx, *pStr++);
		while(USART_GetFlagStatus(USARTx, USART_FLAG_TC) == RESET);
	}

}

/*
************************************************************
*	函数名称:	USART1_IRQHandler
*
*	函数功能:	串口1收发中断
*
*	入口参数:	无
*
*	返回参数:	无
*
*	说明:		
************************************************************
*/
void USART1_IRQHandler(void)
{

	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断
	{
		USART_ClearFlag(USART1, USART_FLAG_RXNE);
	}

}

4.esp8266驱动

为了便于后续了解外设各个功能,我这里拆解一下esp的驱动代码

#define REV_OK		0	//接收完成标志
#define REV_WAIT	1	//接收未完成标志

1.初始化

void ESP8266_Init(void)
{
	ESP8266_Clear();
	
	UsartPrintf(USART_DEBUG, "1. AT\r\n");          //u1负责打印到串口助手
	while(ESP8266_SendCmd("AT\r\n", "OK"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "2. CWMODE\r\n");
	while(ESP8266_SendCmd("AT+CWMODE=1\r\n", "OK"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "3. AT+CWDHCP\r\n");
	while(ESP8266_SendCmd("AT+CWDHCP=1,1\r\n", "OK"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "4. CWJAP\r\n");
	while(ESP8266_SendCmd(ESP8266_WIFI_INFO, "GOT IP"))
		DelayXms(500);
	
	UsartPrintf(USART_DEBUG, "5. ESP8266 Init OK\r\n");

}

2.发送esp执行命令

Bool ESP8266_SendCmd(char *cmd, char *res)
{
	unsigned char timeOut = 200;
	Usart_SendString(USART2, (unsigned char *)cmd, strlen((const char *)cmd));
	while(timeOut--)
	{
		if(ESP8266_WaitRecive() == REV_OK)							//如果收到数据
		{
			if(strstr((const char *)esp8266_buf, res) != NULL)		//如果检索到关键词
			{
				ESP8266_Clear();									//清空缓存
				return 0;
			}
		}	
		DelayXms(10);
	}
	return 1;
}

3.清空缓存

void ESP8266_Clear(void)
{

	memset(esp8266_buf, 0, sizeof(esp8266_buf));
	esp8266_cnt = 0;
}

4.等待响应

Bool ESP8266_WaitRecive(void)
{
	if(esp8266_cnt == 0) 							//如果接收计数为0 则说明没有处于接收数据中,所以直接跳出,结束函数
		return REV_WAIT;
	if(esp8266_cnt == esp8266_cntPre)				//如果上一次的值和这次相同,则说明接收完毕
	{
		esp8266_cnt = 0;							//清0接收计数
		return REV_OK;								//返回接收完成标志
	}
	esp8266_cntPre = esp8266_cnt;					//置为相同
	return REV_WAIT;								//返回接收未完成标志

}

5.esp发送数据给服务器

AT+CIPSEND=%d\r\n这是发送数据给服务器的指令,所以下面串口发送的数据不是给esp而是服务器

void ESP8266_SendData(unsigned char *data, unsigned short len)
{

	char cmdBuf[32];
	
	ESP8266_Clear();								//清空接收缓存
	sprintf(cmdBuf, "AT+CIPSEND=%d\r\n", len);		//发送命令
	if(!ESP8266_SendCmd(cmdBuf, ">"))				//收到‘>’时可以发送数据
	{
		Usart_SendString(USART2, data, len);		//发送设备连接请求数据
	}
}

6.获取平台返回数据

unsigned char *ESP8266_GetIPD(unsigned short timeOut)
{

	char *ptrIPD = NULL;
	do
	{
		if(ESP8266_WaitRecive() == REV_OK)								//如果接收完成
		{
			ptrIPD = strstr((char *)esp8266_buf, "IPD,");				//搜索“IPD”头
			if(ptrIPD == NULL)											//如果没找到,可能是IPD头的延迟,还是需要等待一会,但不会超过设定的时间
			{
				//UsartPrintf(USART_DEBUG, "\"IPD\" not found\r\n");
			}
			else
			{
				ptrIPD = strchr(ptrIPD, ':');							//找到':'
				if(ptrIPD != NULL)
				{
					ptrIPD++;
					return (unsigned char *)(ptrIPD);
				}
				else
					return NULL;	
			}
		}
		DelayXms(5);													//延时等待
	} while(timeOut--);	
	return NULL;														//超时还未找到,返回空指针
}

通信部分

1.mqtt

​ 这里我们要用到MQTT.fx这一个客户端去进行测试,客户端就是在模拟我们设备(这个项目),去和服务器通讯

你作为客户端程序

  • 想要连服务器 → 调 MQTT_PacketConnect() 组一个 CONNECT 报文,通过 TCP 发送。
  • 想要订阅主题 → 调 MQTT_PacketSubscribe() 组一个 SUBSCRIBE 报文,发给服务器。
  • 想要发消息 → 调 MQTT_PacketPublish() 组一个 PUBLISH 报文,发给服务器。

服务器返回的数据(二进制 MQTT 报文):

  • 收到后先用 MQTT_UnPacketRecv() 或者具体的解包函数,比如 MQTT_UnPacketConnectAck()MQTT_UnPacketPublish() 来解析。
  • 解析出来的结果就是你能在代码里直接用的内容(比如连接是否成功、主题是什么、消息内容是什么)。

小程序部分

先介绍一下

JavaScriptJSON 是两个不同的东西:


JavaScript

  • 是一种编程语言,用来写逻辑、操作网页、调用 API、控制流程等。

  • 例如:

    let name = "Tom";
    function sayHello() {
        console.log("Hello, " + name);
    }
    sayHello();
    

    👉 这是 JavaScript 代码,能执行。


JSON (JavaScript Object Notation)

  • 是一种 数据格式,长得很像 JavaScript 的对象语法,但只能用来存数据,不能执行逻辑

  • 常用在前后端通信、配置文件里。

  • 例如:

    {
      "name": "Tom",
      "age": 18,
      "isStudent": true
    }
    

    👉 这是 JSON,只能存数据,不能像 JavaScript 那样写函数或逻辑。


关系

  • JSON 的语法最早就是从 JavaScript 对象语法演变出来的。

  • 但 JSON 独立于语言,Python、Java、C、Go 等都能读写 JSON。

  • JavaScript 可以很方便地处理 JSON:

    let obj = JSON.parse('{"name":"Tom"}');  // JSON → JS 对象
    console.log(obj.name);
    
    let str = JSON.stringify({name: "Tom"}); // JS 对象 → JSON
    console.log(str);
    

👉 所以:

  • JavaScript = 一门编程语言
  • JSON = 一种数据交换格式

Vue 组件的 <script>,功能是:

👉 定时从 OneNET 平台获取设备数据(温度、光照、LED 状态),并能控制 LED 开关。


1. 导入部分

const { createCommonToken } = require('@/key.js')
  • key.js 文件里引入了 createCommonToken 函数。
  • 这个函数用来生成 鉴权 token,请求 OneNET 平台 API 时要带上。

2. data 数据

data() {
  return {
    temp: '',   // 温度
    light: '',  // 光照
    led: true,  // led状态(布尔值)
    token: '',  // 鉴权 token
  }
}

这些变量绑定到页面 <template> 里,用来显示和交互。


3. 生命周期钩子

onLoad() {
  const params = {
    author_key: 'xxx',
    version: '2022-05-01',
    user_id: '460751',
  }
  this.token = createCommonToken(params);
},
  • 页面加载时执行。
  • 传入密钥、版本号、用户 ID,生成 token,保存到 this.token
onShow() {
  this.fetchDevData();
  setInterval(() => {
    this.fetchDevData();
  }, 3000)
},
  • 页面显示时执行。
  • 先调用一次 fetchDevData() 获取设备数据。
  • 然后每隔 3 秒刷新一次数据,实现 实时监控

4. methods 方法

(1) 获取设备数据
fetchDevData() {
  uni.request({
    url: 'https://iot-api.heclouds.com/thingmodel/query-device-property',
    method: 'GET',
    data: {
      product_id: 'ECHECArR1s',
      device_name: 't1'
    },
    header: {
      'authorization': this.token // 自定义请求头信息
    },
    success: (res) => {
      console.log(res.data);
      this.led = res.data.data[0].value === 'true';
      this.light = res.data.data[1].value;
      this.temp = res.data.data[2].value;
    }
  })
}
  • 调用 OneNET 设备属性查询接口,获取 t1 设备的数据。
  • 带上 authorization 头部(token)。
  • 成功后解析返回数据,更新页面的 LED、光照、温度
    • this.led = res.data.data[0].value === 'true'; → 解析出 LED 状态并转成布尔值。
    • this.light = res.data.data[1].value; → 光照数值。
    • this.temp = res.data.data[2].value; → 温度数值。

(2) 控制 LED 开关
on_led(event) {
  let value = event.detail.value;
  uni.request({
    url: 'https://iot-api.heclouds.com/thingmodel/set-device-property',
    method: 'POST',
    data: {
      product_id: 'ECHECArR1s',
      device_name: 't1',
      params: {
        "led": value
      }
    },
    header: {
      'authorization': this.token
    },
    success: () => {
      console.log();
    }
  })
}
  • 当用户在页面点击 <switch> 控件时触发。
  • 获取开关状态 event.detail.value (true / false)。
  • 调用 OneNET 设置设备属性接口,修改 LED 的状态。
  • 请求体里的 params: {"led": value} 告诉服务器要开还是关。

5. 总体流程

  1. 生成 token → 用于鉴权。
  2. 页面加载 → 保存 token。
  3. 页面显示 → 每 3 秒调用一次 OneNET 查询接口,获取设备属性(温度、光照、LED)。
  4. 用户切换开关 → 调用 OneNET 设置接口,控制设备的 LED 灯。


内容还需更新改进,感谢Onenet平台提供的开发文档以及demo,后续可以在此基础添加新的功能


网站公告

今日签到

点亮在社区的每一天
去签到