完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
前言
大二忙里偷闲,花了一个月左右自己利用了Python+ESP8266 DIY 了一个智能聊天机器人,调用的是图灵机器人的体验API,现在把DIY过程记录下来,希望能分享给别的对这方面有兴趣的人。 DIY前的准备 1.STM32F429IG作为主控芯片 2.ESP8266,用来与自己电脑上服务器通信 3.VS1053,用来保存和播放音乐 硬件方面很简单,当然也可以自己兴趣拓展,比如自己加一块显示屏什么的,都是可以的。 电脑端服务器Python 思路是,电脑利用Python开服务器,等待ESP8266的连接,连接上后,STM32会发送给服务端刚刚录下的音乐,然后调用百度语音识别api,就可以将刚刚的录下的音乐发送给百度语音识别,百度语音会返回识别完成的字符串,再调用图灵机器人的api,把识别后的字符串发送出去,就会得到聊天的回复语句,最后一步,将回复语句发送给 百度语音合成,生成的回复语句的mp3,发送给stm32,stm32再通过VS1053播放,以上就实现了聊天的功能。 流程就是 vs1053>录音下的语句(stm32) >百度语音识别 >图灵机器人 >百度语音生成 >stm32>vs1053 流程很简单,那么直接上代码 #coding=utf-8 from socket import * import sys import json import base64 from urllib.request import urlopen from urllib.request import Request from urllib.error import URLError from urllib.parse import urlencode import string import requests class DemoError(Exception): pass """ 获取TOKEN""" def fetch_token(): TOKEN_URL = 'http://openapi.baidu.com/oauth/2.0/token' SCOPE = 'audio_voice_assistant_get' # 有此scope表示有asr能力,没有请在网页里勾选 API_KEY = '你的api_key' SECRET_KEY = '你的api_secret' params = {'grant_type': 'client_credentials', 'client_id': API_KEY, 'client_secret': SECRET_KEY} post_data = urlencode(params) post_data = post_data.encode( 'utf-8') req = Request(TOKEN_URL, post_data) try: f = urlopen(req) result_str = f.read() except URLError as err: result_str = err.read() result_str = result_str.decode() result = json.loads(result_str) if ('access_token' in result.keys() and 'scope' in result.keys()): if not SCOPE in result['scope'].split(' '): raise DemoError('scope is not correct') return result['access_token'] else: raise DemoError('MAYBE API_KEY or SECRET_KEY not correct: access_token or scope not found in token response') """ 语音识别""" ASR_URL = 'http://vop.baidu.com/server_api' def voice_judge(): token = fetch_token() # 需要识别的文件 AUDIO_FILE = '8k.wav' #只支持 pcm/wav/amr # 文件格式 FORMAT = AUDIO_FILE[-3:]; # 文件后缀 pcm/wav/amr # 根据文档填写PID,选择语言及识别模型 DEV_PID = 1537; # 1537 表示识别普通话,使用输入法模型。1536表示识别普通话,使用搜索模型 CUID = '123456PYTHON'; # 采样率 RATE = 8000; # 固定值 speech_data = [] with open(AUDIO_FILE, 'rb') as speech_file: speech_data = speech_file.read() length = len(speech_data) if length == 0: raise DemoError('file %s length read 0 bytes' % AUDIO_FILE) speech = base64.b64encode(speech_data) speech = str(speech, 'utf-8') params = {'dev_pid': DEV_PID, 'format': FORMAT, 'rate': RATE, 'token': token, 'cuid': CUID, 'channel': 1, 'speech': speech, 'len': length } post_data = json.dumps(params, sort_keys=False) req = Request(ASR_URL, post_data.encode('utf-8')) req.add_header('Content-Type', 'application/json') try: f = urlopen(req) result_str = f.read() except URLError as err: result_str = err.read() result_str = str(result_str, 'utf-8') return (result_str) """ 聊天回复""" def get_response(msg): api = 'http://openapi.tuling123.com/openapi/api/v2' dat = { "perception": { "inputText": { "text": msg }, "inputImage": { "url": "imageUrl" }, "selfInfo": { "location": { "city": "厦门", } } }, "userInfo": { "apiKey": '你的api_key', "userId": "随意的用户id,用来判断是否为同一人,因为图灵机器人会根据上文回复" } } dat = json.dumps(dat) r = requests.post(api, data=dat).json() mesage = r['results'][0]['values']['text'] return mesage """ 语音生成""" def voice_make(msg): token = fetch_token() # 发音人选择, 0为普通女声,1为普通男生,3为情感合成-度逍遥,4为情感合成-度丫丫,默认为普通女声 PER = 4 # 语速,取值0-15,默认为5中语速 SPD = 2 # 音调,取值0-15,默认为5中语调 PIT = 5 # 音量,取值0-9,默认为5中音量 VOL = 5 # 下载的文件格式, 3:mp3(default) 4: pcm-16k 5: pcm-8k 6. wav AUE = 3 FORMATS = {3: "mp3", 4: "pcm", 5: "pcm", 6: "wav"} FORMAT = FORMATS[AUE] CUID = "123456PYTHON" TTS_URL = 'http://tsn.baidu.com/text2audio' params = {'tok': token, 'tex': msg, 'per': PER, 'spd': SPD, 'pit': PIT, 'vol': VOL, 'aue': AUE, 'cuid': CUID, 'lan': 'zh', 'ctp': 1} # lan ctp 固定参数 data = urlencode(params) req = Request(TTS_URL, data.encode('utf-8')) has_error = False try: f = urlopen(req) result_str = f.read() has_error = ('Content-Type' not in f.headers.keys() or f.headers['Content-Type'].find('audio/') < 0) except URLError as err: result_str = err.read() has_error = True save_file = "error.txt" if has_error else 'result.' + FORMAT with open(save_file, 'wb') as of: of.write(result_str) if has_error: result_str = str(result_str, 'utf-8') #服务器,主程序 HOST = '你当前电脑的ip地址' PORT = 80 BUFSIZ = 0x500000 ADDR=(HOST,PORT) AUDIO_FILE = '8k.wav' #只支持 pcm/wav/amr s = socket(AF_INET, SOCK_STREAM) s.bind(ADDR) s.listen(5) while True: print('waiting for connecting...') print('') c, addr = s.accept() print('..connected from:', addr) speech_file= open(AUDIO_FILE, 'r+') speech_file.seek(0) speech_file.truncate() #清空文件 speech_file.close( ) while True: data = c.recv(BUFSIZ) if not data: break speech_file= open(AUDIO_FILE, 'ab+') speech_file.write(data) speech_file.flush() speech_file.close( ) c.close() mystr=voice_judge() result = json.loads(mystr) if(result['err_no']==0): mystr = "".join(result['result']) else: mystr="无效" print(mystr) mybyte = bytes(mystr, encoding = "gbk") reply=get_response(mystr) voice_make(reply) print(reply) c, addr = s.accept() speech_file= open('result.mp3', 'rb') data=speech_file.read() speech_file.close( ) c.sendall(data) time.sleep(1) c.close() s.close() stm32f429的流程也很简单,就是按下按键,开始录音,再按一下结束录音,然后等待回传回来的音频文件并且播放。 至于VS1053的代码,我之前有篇博客说了,如果不懂可以看看https://blog.csdn.net/qq_41495871/article/details/83686514 另外一个模块就是ESP8266,ESP8266的代码也是很简单的,我使用的是模组,所以很简单的调用api就好了,如果使用的是正统的esp8266,除了传输速度慢了一些,别的应该都一样。至于esp8266的配置,这边就不详细说明了,网络一大堆这个东西,我之前也用过NodeMcu实现过,Arduino调库调起来也是容易实现的。 void User_BSP_Init() { delay_init(168); // 初始化系统时钟,主频为168M SDRAM_Init(); //SDRAM初始化 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 配置NVIC为优先级组2 LED_GPIO_Config(); //配置板载LED USART_Config(); //配置串口 USART_IT_ENABLE(); //打开串口接收中断 EXTI_Key_Config(); //打开Key的外部中断 Fatfs_Flash_Format(); //初始化Fatfs_SPI_Flash M8266_Module_User_Init(); //初始化M8266,并打印相关信息 VS_Init(); //初始化VS1053 f_mount(&fs,"1:",1); //挂载SPI_Flash 为盘符 1: } 这是BSP的初始化 void User_main() { OS_ERR err; OSSchedRoundRobinCfg(DEF_ENABLED,0,&err); //开启时间片转轮调度 10*系统节拍 即10ms OSMemCreate(&uC_mem,"uC/Data",uC_Data,4,16,&err); //开启内存管理系统 ,128个内存块,每个4个字节 OSTaskCreate(&USART1_Get_TCB,"串口接收",USART1_Get,0,USART1_Get_PRIO,USART1_Get_STK,USART1_Get_STK_SIZE/10,USART1_Get_STK_SIZE,0,0,0,(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),&err); OSTaskCreate(&Key_TCB,"按键中断",Key_On,0,Key_PRIO,Key_Stk,Key_Stk_Size/10,Key_Stk_Size,0,0,0,(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),&err); OSTaskCreate(&USART1_OK_TCB,"串口接收完成",USART1_OK,0,USART1_OK_PRIO,USART1_OK_STK,USART1_OK_STK_SIZE/10,USART1_OK_STK_SIZE,0,0,0,(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),&err); OSTaskCreate(&M8266_Get_TCB,"M8266接收",M8266_Get,0,M8266_Get_PRIO,M8266_Gett_Stk,M8266_Get_Stk_Size/10,M8266_Get_Stk_Size,0,0,0,(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),&err); M8266_Module_Join_AP((u8*)WiFi_SSID,(u8*)WiFi_PAWD,Hostname); } 上面是几个主要任务,并且esp8266连接上你的热点 static void Key_On (void *p_arg) { OS_ERR err; unsigned long file_size ; CPU_SR_ALLOC(); LED_TOGGLE; OSTimeDly(300,OS_OPT_TIME_DLY,&err); LED_TOGGLE; OSTimeDly(300,OS_OPT_TIME_DLY,&err); LED_TOGGLE; OSTimeDly(300,OS_OPT_TIME_DLY,&err); LED_TOGGLE; //闪灯表示准备完成 while(1) { OSTaskSemPend (0,OS_OPT_PEND_BLOCKING,NULL,&err); OS_CRITICAL_ENTER(); M8266_Module_Set_Connect(Goal_Ip,Remote_Port,LinkNum,10); //连接 OS_CRITICAL_EXIT(); f_unlink("1:录音文件.wav"); f_unlink("1:音乐文件.mp3"); vs1053_record_start(); //开始录音 OSTaskCreate(&Record_TCB,"录音",Record,0,Record_PRIO,Record_Stk,Record_Stk_Size/10,Record_Stk_Size,2,0,0,(OS_OPT_TASK_STK_CHK | OS_OPT_TASK_STK_CLR),&err); OSTaskSemPend (0,OS_OPT_PEND_BLOCKING,NULL,&err); OSTaskDel(&Record_TCB,&err); vs1053_record_stop("1:录音文件.wav"); //停止录音,并且保存在外部Flash中 OSTimeDly(100,OS_OPT_TIME_DLY,&err); M8266_Module_SendFile((uint8_t*)"1:录音文件.wav",LinkNum); //发送录音文件 printf("音乐文件大小是 %ld Byte,%.2f KBrn",file_size,(double)file_size/1024); M8266WIFI_SPI_Delete_Connection(LinkNum,NULL); //断开连接 OSTimeDly(1500,OS_OPT_TIME_DLY,&err); M8266_Module_Set_Connect(Goal_Ip,Remote_Port,LinkNum,10); //开启连接,等待服务端发送处理好的回复音频文件 OSTimeDly(1000,OS_OPT_TIME_DLY,&err); M8266WIFI_SPI_Delete_Connection(LinkNum,NULL); LED_TOGGLE; } } static void Record (void *p_arg) { OS_ERR err; CPU_SR_ALLOC(); OSTimeDly(500,OS_OPT_TIME_DLY,&err); LED_TOGGLE; while(1) { OS_CRITICAL_ENTER(); vs1053_record_run(); OS_CRITICAL_EXIT(); OSTimeDly(33,OS_OPT_TIME_DLY,&err); //经过测试大概33ms收集一次,音质最佳 } } 这是按键任务,应该是最主要的任务。还有录音时创建的任务。 static void M8266_Get (void *p_arg) { OS_ERR err; u16 recv_data_num; u16 status; u16 wifi_get_flag; unsigned long file_size ; while(1) { status=M8266_Module_GetData(NULL,&recv_data_num); if(status!=0x0001) { wifi_get_flag=0; while(status) { memcpy(&VS1053_Mem[wifi_get_flag],test_get,recv_data_num); memset(test_get,0,recv_data_num); wifi_get_flag+= recv_data_num; status=M8266_Module_GetData(NULL,&recv_data_num); } memcpy(VS1053_Mem,test_get,recv_data_num); memset(test_get,0,recv_data_num); wifi_get_flag+=recv_data_num; //将接收到的音乐文件保存到 VS1053_Mem SDRAM中 printf("接收到 %d Bytern",wifi_get_flag); vs1053_write_misic_file("1:音乐文件.mp3",wifi_get_flag); //写入Flash中 OSTimeDly(100,OS_OPT_TIME_DLY,&err); vs1053_player_song((uint8_t*)"1:音乐文件.mp3",&file_size); //播放刚刚保存的文集 printf("音乐文件大小是 %ld Bytern",file_size); } OSTimeDly(50,OS_OPT_TIME_DLY,&err); } } 然后是ESP8266接收到音频数据后,播放音频的任务 其余部分就是一些串口部分的任务了 static void USART1_OK(void *p_arg) //串口接收完成任务 { OS_ERR err; uint32_t M8266_flag; uint32_t Debug_flag; u16 status; while(1) { OSTaskSemPend (0,OS_OPT_PEND_BLOCKING,NULL,&err); M8266_flag=0; Debug_flag=0; printf("接收到 %d 串口数据rn",Write_Usart_flag); while(M8266_flag if(Write_Usart_flag-M8266_flag<=1024) { Debug_flag+=M8266WIFI_SPI_Send_Data(&Usart_Mem[M8266_flag],Write_Usart_flag-M8266_flag,LinkNum,&status); M8266_flag+=Write_Usart_flag-M8266_flag; } else { Debug_flag+=M8266WIFI_SPI_Send_Data(&Usart_Mem[M8266_flag],1024,LinkNum,&status); M8266_flag+=1024; } } printf("成功发送 %d Bytern",Debug_flag); memset(Usart_Mem,0,Write_Usart_flag); Write_Usart_flag=0; } } static void USART1_Get (void *p_arg) { OS_ERR err; OS_MSG_SIZE msg_size; char * pMsg; while (DEF_TRUE) { pMsg = OSTaskQPend(0,OS_OPT_PEND_BLOCKING,&msg_size,NULL,&err); //无限期限堵塞等待 Usart_Mem[Write_Usart_flag]=*pMsg; Write_Usart_flag++; OSMemPut(&uC_mem,pMsg,&err); // 退还内存块 } } 以上就是几个主要部分了,很简单。 |
|
|
|
只有小组成员才能发言,加入小组>>
3310 浏览 9 评论
2991 浏览 16 评论
3492 浏览 1 评论
9057 浏览 16 评论
4086 浏览 18 评论
1176浏览 3评论
604浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
597浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2334浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1895浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-22 16:30 , Processed in 1.171161 second(s), Total 80, Slave 61 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号