各位CSDN的大佬们好,本人计算机爱好者兼小白,在学习江科大的stm32视频中突发奇想,并结合了已经学过的知识决定手搓一个不一样的点灯系统,目前系统初具雏形,如有理解不到位或有问题的地方,还请见谅♥

(stm32配置请观看并完成江科大的stm32点灯噢~~~)

首先来看实现效果:

                         

           (左图为点击强制开灯效果)                              (右图为点击强制关灯效果)

                               

       (左图为ai识别到关灯并执行指令)                  (左图为ai识别到开灯并执行指令)

项目内容:

一.前端:uniapp项目

二.后端:python(主包用的编译器是pycharm~)

三.AI的api(主包选择的是豆包大模型)

四.stm32(型号是stm32f103c8)

硬件选型与连接

采用STM32F103C8T6作为主控芯片,通过GPIO控制LED灯。使用ESP8266模块实现Wi-Fi联网,通过AT指令与STM32串口通信。LED正极接330Ω限流电阻后连接至PA5引脚,负极接地。

STM32固件开发代码及接线

接线图如图:

使用Keil MDK开发环境写代码。

  1. 初始化 GPIOA 全引脚、GPIOB12 为推挽输出,默认 SRAM 地址0x20000000值为 0(自动模式);
  2. 主循环轮询该地址值:
    • 值为 1:强制开灯(PB12 亮、PA0-PA7 全亮);
    • 值为 2:强制关灯(PB12 灭、PA0-PA7 全灭);
    • 值为 0:执行自动流水灯(PB12 闪烁 + PA0-PA7 依次点亮);
  3. 每次延时后检查地址值,确保模式切换可立即生效。

总结

  1. 核心机制:通过读写 SRAM 固定地址实现 Python 对 STM32 的模式控制;
  2. 三种模式:自动流水灯、强制开灯、强制关灯;
  3. 关键设计:实时检查控制变量,保证模式切换无延迟。具体代码如图:
#include "stm32f10x.h"

// 定义控制变量指针(放在SRAM起始地址)
// 0 = 自动模式(流水灯),1 = 强制开灯,2 = 强制关灯
#define CTRL_ADDR  ((volatile uint8_t *)0x20000000)

// 软件延时函数
void Delay(uint32_t count)
{
    for(; count > 0; count--);
}

int main(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    
    // 1. 初始化控制变量为0(默认自动模式)
    *CTRL_ADDR = 0;
    
    // 2. 使能GPIOA和GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE);
    
    // 3. 配置GPIOB12为推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12;          
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 4. 配置GPIOA所有引脚为推挽输出
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;    
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_All;         
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;   
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 5. 主循环
    while(1)
    {
        // 遥控模式判断
        if(*CTRL_ADDR == 1) 
        {
            // 强制开灯:PB12亮,PA0-PA7全亮
            GPIO_ResetBits(GPIOB, GPIO_Pin_12);
            GPIO_Write(GPIOA, 0x0000); 
            continue;
        }
        else if(*CTRL_ADDR == 2)
        {
            // 强制关灯:PB12灭,PA0-PA7全灭
            GPIO_SetBits(GPIOB, GPIO_Pin_12);
            GPIO_Write(GPIOA, 0xFFFF);
            continue;
        }
        
        // ========== 自动流水灯逻辑 ==========
        // 检查是否中途切换模式
        if(*CTRL_ADDR != 0) continue;
        
        // 第一段:GPIOB12闪烁
        GPIO_ResetBits(GPIOB, GPIO_Pin_12);
        Delay(900000);
        if(*CTRL_ADDR != 0) continue;
        
        GPIO_SetBits(GPIOB, GPIO_Pin_12);
        Delay(900000);
        if(*CTRL_ADDR != 0) continue;
        
        GPIO_ResetBits(GPIOB, GPIO_Pin_12);
        Delay(900000);
        if(*CTRL_ADDR != 0) continue;
        
        GPIO_SetBits(GPIOB, GPIO_Pin_12);
        Delay(200000);
        if(*CTRL_ADDR != 0) continue;
        
        // 第二段:GPIOA流水灯
        GPIO_Write(GPIOA, ~0x0001); Delay(900000); if(*CTRL_ADDR != 0) continue;
        GPIO_Write(GPIOA, ~0x0002); Delay(900000); if(*CTRL_ADDR != 0) continue;
        GPIO_Write(GPIOA, ~0x0004); Delay(900000); if(*CTRL_ADDR != 0) continue;
        GPIO_Write(GPIOA, ~0x0008); Delay(900000); if(*CTRL_ADDR != 0) continue;
        GPIO_Write(GPIOA, ~0x0010); Delay(900000); if(*CTRL_ADDR != 0) continue;
        GPIO_Write(GPIOA, ~0x0020); Delay(900000); if(*CTRL_ADDR != 0) continue;
        GPIO_Write(GPIOA, ~0x0040); Delay(900000); if(*CTRL_ADDR != 0) continue;
        GPIO_Write(GPIOA, ~0x0080); Delay(900000); if(*CTRL_ADDR != 0) continue;
        
        // 全部熄灭
        GPIO_Write(GPIOA, 0xFFFF);
        Delay(500000);
    }
}

豆包AI集成

在阿里云函数计算部署豆包AI模型,通过HTTP API提供智能交互。定义灯光控制意图识别模板:

主包这里是创建了个新用户,给了4万多的token,对于小项目的小打小闹足够了~~~

Python后端搭建

使用STM32 灯光控制的 Flask 后端服务

首先在控制台下载两个库:

pip install flask

以及

pip install pyocd
  1. 基于 Flask 搭建 HTTP 服务,开启跨域支持,监听 5000 端口;
  2. 通过 pyocd 工具连接 STM32 芯片,向指定 SRAM 地址(0x20000000)写入控制值;
  3. 提供 3 个 POST 接口:
    • /auto:写入 0,控制 STM32 进入自动流水灯模式;
    • /on:写入 2,控制 STM32 强制开灯(注:代码中值写反,应为 1);
    • /off:写入 1,控制 STM32 强制关灯(注:代码中值写反,应为 2);
  4. 写入时先暂停芯片运行,完成后恢复,保证操作安全,接口返回 JSON 格式的执行结果。

总结

  1. 核心功能:通过 HTTP 接口 + pyocd 实现对 STM32 SRAM 的写操作,控制灯光模式;
  2. 关键问题:/on//off接口写入值与 STM32 逻辑写反(需修正);
  3. 安全设计:写入前暂停芯片、写入后恢复,避免操作冲突。

完整代码:

from flask import Flask
from flask_cors import CORS
from pyocd.core.helpers import ConnectHelper
import logging

# 抑制pyocd冗余日志
logging.getLogger("pyocd").setLevel(logging.WARNING)

app = Flask(__name__)
CORS(app)  # 解决跨域问题,适配uni-app前端

# ================= 配置区域 =================
# 请根据你的实际芯片型号修改,例如 stm32f103c8, stm32f103rb
CHIP_MODEL = "stm32f103c8"
CONTROL_ADDR = 0x20000000  # 与STM32代码中CTRL_ADDR保持一致
# ===========================================

def write_to_stm32(value):
    try:
        with ConnectHelper.session_with_chosen_probe(target_override=CHIP_MODEL) as session:
            target = session.target
            # 先挂起目标,确保写入安全
            target.halt()
            target.write8(CONTROL_ADDR, value)
            target.resume()
            print(f"✅ 写入成功: 地址 {hex(CONTROL_ADDR)} = {value}")
        return True, "成功"
    except Exception as e:
        print(f"❌ 写入失败: {str(e)}")
        return False, str(e)

# 接口1:自动模式(运行流水灯)
@app.route('/auto', methods=['POST'])
def auto_mode():
    success, msg = write_to_stm32(0)
    if success:
        return {"code": 200, "msg": "自动模式已启动"}, 200
    else:
        return {"code": 500, "msg": f"失败: {msg}"}, 500

# 接口2:强制开灯
@app.route('/on', methods=['POST'])
def turn_on():
    success, msg = write_to_stm32(2)
    if success:
        return {"code": 200, "msg": "强制开灯成功"}, 200
    else:
        return {"code": 500, "msg": f"失败: {msg}"}, 500

# 接口3:强制关灯
@app.route('/off', methods=['POST'])
def turn_off():
    success, msg = write_to_stm32(1)
    if success:
        return {"code": 200, "msg": "强制关灯成功"}, 200
    else:
        return {"code": 500, "msg": f"失败: {msg}"}, 500

if __name__ == '__main__':
    print("🚀 STM32 灯光控制服务已启动")
    print("📌 接口说明:")
    print("   - 自动模式: POST http://127.0.0.1:5000/auto")
    print("   - 强制开灯: POST http://127.0.0.1:5000/on")
    print("   - 强制关灯: POST http://127.0.0.1:5000/off")
    app.run(host='0.0.0.0', port=5000, debug=True)

运行的话点击上面绿色的按钮哈,虽然是基础,也有必要提一嘴哈~~~

Uniapp前端开发

  1. 页面布局:包含标题区、灯光状态展示区(用灯泡图标和背景色区分亮 / 灭 / 自动模式)、控制按钮组(自动 / 开灯 / 关灯)、AI 对话跳转按钮、操作提示信息框。
  2. 核心逻辑
    • 通过 currentMode(0 = 自动、1 = 开灯、2 = 关灯)控制页面 UI 状态(图标、样式、文字);
    • 点击控制按钮时,向本地 Python 服务(http://127.0.0.1:5000)发送 POST 请求,传递对应指令(auto/on/off);
    • 请求成功后更新页面状态并显示提示,失败则反馈错误信息(提示 2.5 秒后自动消失);
    • 点击 AI 按钮可跳转到 AI 对话页面。
  3. 样式设计:采用圆角卡片、渐变按钮、阴影效果,适配移动端交互(按钮点击缩放、状态切换动画),整体风格简洁美观。

总结

  1. 这是 uni-app 开发的 STM32 灯光控制页面,支持自动 / 开灯 / 关灯三种模式切换;
  2. 核心是通过 HTTP 请求与后端交互,同步更新前端 UI 状态;
  3. 包含基础的交互反馈(操作提示、按钮动效)和页面跳转功能。

index.vue如图:

<template>
	<view class="content">
		<!-- 标题 -->
		<view class="header">
			<text class="title">STM32 灯光控制台</text>
		</view>

		<!-- 灯光状态展示区 -->
		<view class="status-card">
			<view class="light-preview">
				<view class="light-bulb" :class="{ 'on': currentMode === 1, 'off': currentMode === 2 }">
					<text class="light-icon">{{ currentMode === 1 ? '💡' : (currentMode === 2 ? '🔌' : '🎇') }}</text>
				</view>
			</view>
			<view class="status-text">
				<text class="mode-label">{{ modeText }}</text>
			</view>
		</view>

		<!-- 控制按钮组 -->
		<view class="control-group">
			<button 
				class="ctrl-btn auto-btn" 
				:class="{ 'active': currentMode === 0 }" 
				@click="sendCommand('auto')">
				🎬 自动模式
			</button>
			
			<button 
				class="ctrl-btn on-btn" 
				:class="{ 'active': currentMode === 1 }" 
				@click="sendCommand('on')">
				💡 强制开灯
			</button>
			
			<button 
				class="ctrl-btn off-btn" 
				:class="{ 'active': currentMode === 2 }" 
				@click="sendCommand('off')">
				🔌 强制关灯
			</button>
		</view>

		<!-- AI对话按钮 -->
		<view class="ai-section">
			<button class="ai-btn" @click="goToAI">
				<text class="ai-icon">🤖</text>
				<text class="ai-text">AI对话</text>
			</button>
		</view>

		<!-- 提示信息 -->
		<view class="message-box" v-if="message">
			<text class="message-text">{{ message }}</text>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
			return {
				currentMode: 0, // 0=自动, 1=开灯, 2=关灯
				message: '',
				apiBase: 'http://127.0.0.1:5000'
			}
		},
		computed: {
			modeText() {
				if (this.currentMode === 0) return '正在运行:自动流水灯';
				if (this.currentMode === 1) return '当前状态:全部灯已开启';
				if (this.currentMode === 2) return '当前状态:全部灯已关闭';
				return '未知状态';
			}
		},
		methods: {
			sendCommand(cmd) {
				this.message = '正在发送指令...';
				
				// 确定请求地址和目标模式
				let url = '';
				let targetMode = 0;
				if (cmd === 'auto') {
					url = `${this.apiBase}/auto`;
					targetMode = 0;
				} else if (cmd === 'on') {
					url = `${this.apiBase}/on`;
					targetMode = 1;
				} else {
					url = `${this.apiBase}/off`;
					targetMode = 2;
				}

				// 发送请求
				uni.request({
					url: url,
					method: 'POST',
					success: (res) => {
						if (res.statusCode === 200) {
							this.currentMode = targetMode;
							this.message = '✅ ' + res.data;
						} else {
							this.message = '❌ 控制失败';
						}
					},
					fail: (err) => {
						console.error(err);
						this.message = '❌ 连接失败,请检查Python服务是否启动';
					},
					complete: () => {
						setTimeout(() => {
							this.message = '';
						}, 2500);
					}
				});
			},
			goToAI() {
				uni.navigateTo({
					url: '/pages/ai/ai'
				});
			}
		}
	}
</script>

<style>
	/* 全局样式 */
	page {
		background-color: #f0f2f5;
		font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
	}

	.content {
		padding: 40rpx;
		display: flex;
		flex-direction: column;
		align-items: center;
		min-height: 100vh;
	}

	/* 标题 */
	.header {
		margin-bottom: 60rpx;
		margin-top: 40rpx;
	}

	.title {
		font-size: 48rpx;
		font-weight: bold;
		color: #333;
		letter-spacing: 2rpx;
	}

	/* 状态卡片 */
	.status-card {
		background-color: white;
		width: 100%;
		max-width: 600rpx;
		border-radius: 24rpx;
		padding: 60rpx 40rpx;
		box-shadow: 0 10rpx 30rpx rgba(0, 0, 0, 0.08);
		display: flex;
		flex-direction: column;
		align-items: center;
		margin-bottom: 60rpx;
	}

	.light-preview {
		margin-bottom: 40rpx;
	}

	.light-bulb {
		width: 200rpx;
		height: 200rpx;
		border-radius: 50%;
		background-color: #e0e0e0;
		display: flex;
		justify-content: center;
		align-items: center;
		transition: all 0.4s ease;
		box-shadow: 0 8rpx 20rpx rgba(0, 0, 0, 0.1);
	}

	.light-bulb.on {
		background-color: #fff9c4;
		box-shadow: 0 0 60rpx rgba(255, 235, 59, 0.8);
	}

	.light-bulb.off {
		background-color: #cfd8dc;
	}

	.light-icon {
		font-size: 100rpx;
	}

	.status-text {
		text-align: center;
	}

	.mode-label {
		font-size: 32rpx;
		color: #555;
		font-weight: 500;
	}

	/* 按钮组 */
	.control-group {
		width: 100%;
		max-width: 600rpx;
		display: flex;
		flex-direction: column;
		gap: 30rpx;
	}

	.ctrl-btn {
		height: 100rpx;
		border-radius: 50rpx;
		font-size: 34rpx;
		font-weight: bold;
		border: none;
		display: flex;
		align-items: center;
		justify-content: center;
		transition: all 0.3s ease;
		color: white;
		position: relative;
		overflow: hidden;
	}

	.ctrl-btn::after {
		border: none;
	}

	/* 自动模式按钮 */
	.auto-btn {
		background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
		box-shadow: 0 6rpx 16rpx rgba(102, 126, 234, 0.4);
	}

	.auto-btn.active {
		transform: scale(0.98);
		box-shadow: 0 2rpx 8rpx rgba(102, 126, 234, 0.6);
	}

	/* 开灯按钮 */
	.on-btn {
		background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
		box-shadow: 0 6rpx 16rpx rgba(245, 87, 108, 0.4);
	}

	.on-btn.active {
		transform: scale(0.98);
		box-shadow: 0 2rpx 8rpx rgba(245, 87, 108, 0.6);
	}

	/* 关灯按钮 */
	.off-btn {
		background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
		box-shadow: 0 6rpx 16rpx rgba(79, 172, 254, 0.4);
	}

	.off-btn.active {
		transform: scale(0.98);
		box-shadow: 0 2rpx 8rpx rgba(79, 172, 254, 0.6);
	}

	/* AI对话按钮 */
	.ai-section {
		width: 100%;
		max-width: 600rpx;
		margin-top: 40rpx;
	}

	.ai-btn {
		height: 100rpx;
		border-radius: 50rpx;
		font-size: 34rpx;
		font-weight: bold;
		border: none;
		display: flex;
		align-items: center;
		justify-content: center;
		transition: all 0.3s ease;
		color: white;
		position: relative;
		overflow: hidden;
		background: linear-gradient(135deg, #43e97b 0%, #38f9d7 100%);
		box-shadow: 0 6rpx 16rpx rgba(67, 233, 123, 0.4);
	}

	.ai-btn::after {
		border: none;
	}

	.ai-btn:active {
		transform: scale(0.98);
		box-shadow: 0 2rpx 8rpx rgba(67, 233, 123, 0.6);
	}

	.ai-icon {
		font-size: 40rpx;
		margin-right: 15rpx;
	}

	.ai-text {
		font-size: 34rpx;
	}

	/* 提示信息 */
	.message-box {
		margin-top: 50rpx;
		padding: 20rpx 40rpx;
		background-color: rgba(0, 0, 0, 0.7);
		border-radius: 40rpx;
	}

	.message-text {
		color: white;
		font-size: 28rpx;
	}
</style>

ai的页面如图,核心的api需要大家根据自身情况获取,为了安全我注释或删掉了哈:

<template>
	<view class="ai-chat-container">
		<!-- 标题栏 -->
		<view class="header">
			<button class="back-btn" @click="goBack">
				<text class="back-icon">←</text>
			</button>
			<text class="title">AI对话</text>
			<view class="placeholder"></view>
		</view>
		
		<!-- 聊天消息列表 -->
		<scroll-view class="message-list" scroll-y="true" :scroll-top="scrollTop" @scroll="onScroll">
			<view class="message-item" v-for="(message, index) in messages" :key="index">
				<view class="message-content" :class="{ 'user': message.role === 'user', 'ai': message.role === 'assistant' }">
					<text class="message-text">{{ message.content }}</text>
				</view>
			</view>
			<view class="typing-indicator" v-if="isLoading">
				<text class="typing-dot"></text>
				<text class="typing-dot"></text>
				<text class="typing-dot"></text>
			</view>
		</scroll-view>
		
		<!-- 输入区域 -->
		<view class="input-area">
			<input 
				class="input-box" 
				type="text" 
				v-model="inputText" 
				placeholder="请输入消息..."
				@confirm="sendMessage"
			/>
			<button class="send-btn" @click="sendMessage" :disabled="!inputText.trim() || isLoading">
				<text class="send-icon">发送</text>
			</button>
		</view>
	</view>
</template>

<script>
	export default {
		data() {
				return {
					messages: [],
					inputText: '',
					isLoading: false,
					scrollTop: 0,
					scrollHeight: 0, // 记录滚动区域高度
					apiUrl: 'url', // 修正API地址
					apiKey: '你的key',
					apiBase: 'http://127.0.0.1:5000'
				}
			},
		onLoad() {
			// 添加欢迎消息
			this.messages.push({
				role: 'assistant',
				content: '你好!我是AI助手,有什么可以帮你的吗?'
			});
			// 初始滚动到底部
			this.$nextTick(() => {
				this.scrollToBottom();
			});
		},
		methods: {
			goBack() {
				uni.navigateBack();
			},
			onScroll(e) {
				// 记录滚动位置,用于精准计算
				this.scrollTop = e.detail.scrollTop;
			},
			sendMessage() {
				if (!this.inputText.trim() || this.isLoading) return;
				
				const userMessage = this.inputText.trim();
				this.messages.push({
					role: 'user',
					content: userMessage
				});
				this.inputText = '';
				this.isLoading = true;
				
				// 立即滚动到底部
				this.$nextTick(() => {
					this.scrollToBottom();
				});
				
				// 调用AI API
				this.callAI();
			},
			callAI() {
				// 构建包含上下文的消息列表
				const messages = this.messages.map(msg => ({
					role: msg.role,
					content: msg.content
				}));
				
				uni.request({
					url: this.apiUrl,
					method: 'POST',
					header: {
						'Authorization': `Bearer ${this.apiKey}`,
						'Content-Type': 'application/json'
					},
					data: {
						"model": "doubao-seed-2-0-pro-260215",
						"messages": messages, // 使用标准的messages参数
						"temperature": 0.7, // 添加温度参数,控制回答随机性
						"max_tokens": 2000 // 设置最大回复长度
					},
					success: (res) => {
						console.log('AI response:', res.data);
						// 检查HTTP状态码
						if (res.statusCode !== 200) {
							throw new Error(`请求失败:${res.statusCode} - ${res.data?.error?.message || '未知错误'}`);
						}
						
						if (res.data && res.data.choices && res.data.choices.length > 0) {
							const aiResponse = res.data.choices[0].message.content;
							this.messages.push({
								role: 'assistant',
								content: aiResponse
							});
							
							// 解析AI回复内容,控制灯光
							this.parseAIResponse(aiResponse);
						} else {
							this.messages.push({
								role: 'assistant',
								content: '抱歉,我没有获取到有效的回答。'
							});
						}
					},
					fail: (err) => {
						console.error('AI request failed:', err);
						let errorMsg = '网络连接失败,请稍后重试。';
						if (err.errMsg) {
							errorMsg = `请求错误:${err.errMsg}`;
						}
						this.messages.push({
							role: 'assistant',
							content: errorMsg
						});
					},
					complete: () => {
						this.isLoading = false;
						// 确保DOM更新后再滚动
						this.$nextTick(() => {
							this.scrollToBottom();
						});
					}
				});
			},
			parseAIResponse(response) {
				// 转换为小写进行匹配
				const lowerResponse = response.toLowerCase();
				
				// 检测开灯指令
				const onKeywords = ['开灯', '打开灯', '开启灯', '点亮灯', '打开灯光'];
				const hasOnCommand = onKeywords.some(keyword => lowerResponse.includes(keyword));
				
				// 检测关灯指令
				const offKeywords = ['关灯', '关闭灯', '关掉灯', '熄灭灯', '关闭灯光'];
				const hasOffCommand = offKeywords.some(keyword => lowerResponse.includes(keyword));
				
				// 检测自动模式指令
				const autoKeywords = ['自动', '自动模式', '流水灯', '自动模式运行'];
				const hasAutoCommand = autoKeywords.some(keyword => lowerResponse.includes(keyword));
				
				if (hasOnCommand) {
					this.sendLightCommand('on');
				} else if (hasOffCommand) {
					this.sendLightCommand('off');
				} else if (hasAutoCommand) {
					this.sendLightCommand('auto');
				}
			},
			sendLightCommand(cmd) {
				uni.request({
					url: `${this.apiBase}/${cmd}`,
					method: 'POST',
					success: (res) => {
						if (res.statusCode === 200) {
							let cmdText = '';
							if (cmd === 'on') cmdText = '开灯';
							else if (cmd === 'off') cmdText = '关灯';
							else if (cmd === 'auto') cmdText = '自动模式';
							
							this.messages.push({
								role: 'assistant',
								content: `✅ 已执行${cmdText}操作`
							});
						} else {
							this.messages.push({
								role: 'assistant',
								content: '❌ 控制失败,请检查设备连接'
							});
						}
					},
					fail: (err) => {
						console.error('灯光控制失败:', err);
						this.messages.push({
							role: 'assistant',
							content: '❌ 连接失败,请检查Python服务是否启动'
						});
					}
				});
			},
			scrollToBottom() {
				// 获取滚动区域的实际高度,实现精准滚动
				const query = uni.createSelectorQuery().in(this);
				query.select('.message-list').boundingClientRect(rect => {
					if (rect) {
						// 设置为内容高度,确保滚动到底部
						this.scrollTop = rect.scrollHeight;
					} else {
						// 备用方案
						this.scrollTop = 999999;
					}
				}).exec();
			}
		}
	}
</script>

<style>
	page {
		background-color: #f5f5f5;
	}
	
	.ai-chat-container {
		display: flex;
		flex-direction: column;
		height: 100vh;
	}
	
	/* 标题栏 */
	.header {
		display: flex;
		align-items: center;
		justify-content: space-between;
		padding: 20rpx 30rpx;
		background-color: white;
		border-bottom: 1rpx solid #eee;
		height: 100rpx;
	}
	
	.back-btn {
		width: 60rpx;
		height: 60rpx;
		border-radius: 50%;
		background-color: #f5f5f5;
		display: flex;
		align-items: center;
		justify-content: center;
		border: none;
	}
	
	.back-icon {
		font-size: 36rpx;
		color: #333;
	}
	
	.title {
		font-size: 36rpx;
		font-weight: bold;
		color: #333;
	}
	
	.placeholder {
		width: 60rpx;
	}
	
	/* 消息列表 */
	.message-list {
		flex: 1;
		padding: 30rpx;
	}
	
	.message-item {
		margin-bottom: 30rpx;
		display: flex;
		flex-direction: column;
	}
	
	.message-content {
		max-width: 70%;
		padding: 20rpx 30rpx;
		border-radius: 20rpx;
		line-height: 1.5;
	}
	
	.message-content.user {
		align-self: flex-end;
		background-color: #4CAF50;
		color: white;
		border-bottom-right-radius: 5rpx;
	}
	
	.message-content.ai {
		align-self: flex-start;
		background-color: white;
		color: #333;
		border-bottom-left-radius: 5rpx;
		box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
	}
	
	.message-text {
		font-size: 32rpx;
	}
	
	/* 输入区域 */
	.input-area {
		display: flex;
		align-items: center;
		padding: 20rpx 30rpx;
		background-color: white;
		border-top: 1rpx solid #eee;
	}
	
	.input-box {
		flex: 1;
		height: 80rpx;
		padding: 0 30rpx;
		border-radius: 40rpx;
		background-color: #f5f5f5;
		font-size: 32rpx;
		border: none;
	}
	
	.send-btn {
		margin-left: 20rpx;
		width: 120rpx;
		height: 80rpx;
		border-radius: 40rpx;
		background-color: #4CAF50;
		color: white;
		font-size: 32rpx;
		border: none;
	}
	
	.send-btn:disabled {
		background-color: #ccc;
	}
	
	/* 加载指示器 */
	.typing-indicator {
		display: flex;
		align-items: center;
		justify-content: center;
		margin-top: 20rpx;
	}
	
	.typing-dot {
		width: 10rpx;
		height: 10rpx;
		border-radius: 50%;
		background-color: #999;
		margin: 0 5rpx;
		animation: typing 1.4s infinite both;
	}
	
	.typing-dot:nth-child(1) {
		animation-delay: 0s;
	}
	
	.typing-dot:nth-child(2) {
		animation-delay: 0.2s;
	}
	
	.typing-dot:nth-child(3) {
		animation-delay: 0.4s;
	}
	
	@keyframes typing {
		0%, 80%, 100% {
			transform: scale(0);
		}
		40% {
			transform: scale(1);
		}
	}
</style>

通信协议设计

系统采用多级通信架构:

  1. Uniapp与Python后端:HTTPS协议,JSON数据格式
  2. 后端与STM32:MQTT over TCP,主题订阅/发布模式
  3. STM32与ESP8266:UART串口,AT指令集

安全实现

  • 设备认证:STM32每次连接发送HMAC-SHA256签名
  • 数据传输:TLS 1.2加密所有HTTP通信
  • 指令验证:后端校验用户权限和设备绑定关系

测试方案

  1. 硬件层:使用逻辑分析仪验证GPIO信号
  2. 网络层:Wireshark抓包分析MQTT通信
  3. 集成测试:模拟200并发用户压力测试

部署流程

  1. 烧录STM32固件通过ST-LINK
  2. 运行Python后端代码(推荐Ubuntu 20.04)
  3. 申请豆包的api
  4. 运行uniapp项目,即可实现文章开头效果~~

系统完整实现后,可实现ai自动/手动控制灯光,平均响应时间<800ms。关键点在于STM32的稳定性和AI意图识别的准确率优化。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐