├── .gitignore ├── Chack_Commands_Module.py ├── Config.ini ├── Print_With_Style_Module.py ├── README.md ├── Speech_Command_Module.py ├── Time_Sign_Module.py ├── WOL_Module.py ├── baidu_voice_Module.py ├── images └── topo.png ├── main.py ├── resource ├── 32位专用aplay ├── 64位专用aplay └── 树莓派专用的改进的aplay ├── tuling_robot_Module.py ├── xiaoice_robot_Module.py ├── 下位机-ESP8266端程序.py └── 说明.txt /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | -------------------------------------------------------------------------------- /Chack_Commands_Module.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | 本模块用于对命令进行处理(包括开关命令,提醒命令,以及对相应命令返回回复语), 5 | 它还兼顾与下位机通讯的工作,工作原理主要是检查command_temp.txt文件中的命令, 6 | 并执行 7 | 包含两个函数,1)Chack_Commands(),它需要在线程内不断执行以及时响应 8 | 2)Write_Commands_Back_To_File(),用在finally中,遇到错误时把命令写回文本中 9 | """ 10 | """ 11 | #DEBUG装饰器 12 | def debug(func) : 13 | 14 | def wrapper(*args, **kwargs) : 15 | print '\033[94m' , 16 | print '[DEBUG]: enter {}()'.format(func.__name__) , 17 | print "\033[0m" 18 | with open('./command_temp.txt',"r") as f : 19 | print '调用前:' , 20 | print f.read() 21 | print f_commands 22 | return func(*args, **kwargs) 23 | return wrapper 24 | """ 25 | 26 | #树莓派现在通过socket与下位机通讯,下位机为MicriPython的ESP8266 27 | 28 | #回复模板随机选择 29 | from random import choice 30 | #时间库 31 | from datetime import datetime 32 | import time 33 | #socket通信库 34 | import socket 35 | #WOL打开电脑的模块 36 | from WOL_Module import * 37 | #引入有颜色的输出的模块 38 | from Print_With_Style_Module import * 39 | #读取配置文件的模块 40 | from configobj import ConfigObj 41 | 42 | 43 | ### 44 | #通信部分(与下位机通信) 45 | HOST = '192.168.1.142' #树莓派(主机)的地址 46 | PORT = 21567 #主机端口 47 | BUFSIZ = 1024 #缓冲区大小 48 | ADDR = (HOST, PORT) 49 | 50 | tcpServerSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 51 | tcpServerSock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #允许地址重用 52 | tcpServerSock.bind(ADDR) 53 | 54 | tcpServerSock.listen(1) #可挂起的最大连接数 55 | tcpServerSock.settimeout(10) #设置连接的超时时间 56 | 57 | #等待连接 58 | try : 59 | print "等待下位机连接..." 60 | ESP8266Sock, ESP8266addr = tcpServerSock.accept() #接受客户端连接 61 | print "下位机已连接,地址为:" , ESP8266addr 62 | 63 | except socket.timeout : 64 | Print_With_Style("下位机连接超时", 'r+B') 65 | tcpServerSock.close() 66 | 67 | exit(0) 68 | #没有错误的话,就会生成一个名为ESP8266Sock的socket连接对象 69 | ### 70 | 71 | 72 | #配置文件对象 73 | Config = ConfigObj('./Config.ini', encoding= 'utf-8') 74 | 75 | 76 | 77 | #保存命令的临时文件的路径 78 | command_file_path = Config["Config"]["Command"]["command_file_path"] 79 | 80 | #回复语模板 81 | reply_mods_ON = Config["Config"]["Command_Reply"]["reply_mods_ON"] 82 | reply_mods_OFF = Config["Config"]["Command_Reply"]["reply_mods_OFF"] 83 | reply_mods_ON_and_OFF = Config["Config"]["Command_Reply"]["reply_mods_ON_and_OFF"] 84 | reply_mods_Tixing = Config["Config"]["Command_Reply"]["reply_mods_Tixing"] 85 | reply_mods_Tixing_With_Time = Config["Config"]["Command_Reply"]["reply_mods_Tixing_With_Time"] 86 | reply_mods_ONOFF_With_Time = Config["Config"]["Command_Reply"]["reply_mods_ONOFF_With_Time"] 87 | 88 | 89 | #预先加载-----从文件中加载上次未完成的命令 90 | f_commands = set([]) #从文件加载的命令包,这个包也是全局的命令包,后面的函数主要针对它处理 91 | with open(command_file_path, 'r') as f : 92 | y_cm_lines = f.readlines() 93 | y_new_lines = '' 94 | for y_cm in y_cm_lines : 95 | if not y_cm.split('_')[1] == '0' : #预先加载-移除过期的命令 96 | if not (datetime.strptime(y_cm.split('_')[1], '%Y-%m-%d|%H:%M:%S') - datetime.now()).total_seconds() < 0 : 97 | y_new_lines += y_cm 98 | f_commands.add(y_cm) 99 | 100 | with open(command_file_path, 'w') as f : 101 | f.write(y_new_lines) 102 | #--------------------------------------预先加载结束 103 | 104 | #这个函数用于命令刚生成时返回一个回复 105 | #获取对'Remind_2016-11-19|07:30:00_起床','cnON_2016-11-19|07:30:00_'这种命令的回复语,即: 106 | #好的,将在'2016年11月19日21点18分18秒'叫你[起床]' 107 | #传入类型只有cnON,cnOFF,Remind三种命令 108 | #@debug 109 | def Get_Reply_Of_Command_With_TimeSign(cn_command) : 110 | global reply_mods_Tixing_With_Time #提醒事项回复语模板 111 | global reply_mods_ONOFF_With_Time #开/关事项回复语模板 112 | 113 | cn_cm_list = cn_command.split('_') 114 | #先检查时间标记代表的时间是否已经过去,过去的话就回复"貌似这个时间已经过了耶,请重新设定个时间哦" 115 | if (datetime.strptime(cn_command.split('_')[1], '%Y-%m-%d|%H:%M:%S') - datetime.now()).total_seconds() < 0 : 116 | reply = "貌似这个时间已经过了耶,请重新设定个时间哦" 117 | else : 118 | #获取回复模板 119 | if cn_cm_list[0] == u'Remind' : 120 | if cn_cm_list[-1] != "闹钟\n" : 121 | reply_mod = choice(reply_mods_Tixing_With_Time) 122 | action = cn_cm_list[-1] 123 | else : #是闹钟命令的话 124 | reply_mod = u"好的,已为您设置闹钟" 125 | action = "" #闹钟不需要回复"动作" 126 | else : 127 | reply_mod = choice(reply_mods_ONOFF_With_Time) 128 | if cn_cm_list[0] == u'cnON' : 129 | action_v = "打开" 130 | else : 131 | action_v = "关闭" 132 | action = action_v + cn_cm_list[-1] 133 | 134 | #合成回复 135 | cn_time_sign = datetime.strptime(cn_cm_list[1], '%Y-%m-%d|%H:%M:%S') 136 | cntime_str = cn_time_sign.strftime('%Y年%m月%d日 %H点%M分%S秒') 137 | cntime_str = unicode(cntime_str, 'utf-8') 138 | action = unicode(action, 'utf-8') 139 | action = action.replace('\n', '') #去除换行符 140 | reply_mod = reply_mod.replace('t',cntime_str) 141 | reply_mod = reply_mod.replace('v', action) 142 | 143 | reply = reply_mod 144 | #print '回复语:' , 145 | #print reply 146 | 147 | return reply 148 | 149 | 150 | #刷新命令包,即从文件中重新加载命令包,它将同时处理新生成的命令,例: 151 | #'十分钟后叫我起床'-----返回'好的,将在'2016年11月19日21点18分18秒'叫你[起床]' 152 | #一次刷新只会处理/添加一条命令到f_commands命令表 153 | #@debug 154 | def Fresh_Commands() : 155 | global command_file_path 156 | global f_commands 157 | 158 | with open(command_file_path, 'r') as f : 159 | cm_lines = f.readlines() 160 | for cm_line in cm_lines : 161 | if not cm_line in f_commands : 162 | f_commands.add(cm_line) 163 | if not cm_line.split('_')[1] == '0' : 164 | if any([cm_line.startswith("cn"), cm_line.startswith("Remind")]) : #只对中文命令回复 165 | #有新的'一段时间后的命令',返回回复语,必须是中文命令 166 | 167 | return Get_Reply_Of_Command_With_TimeSign(cm_line) 168 | #如果是ON_OFF的英文命令,则只把它加入命令包而不回复 169 | return None 170 | 171 | 172 | #时间到了就把时间标记设为0,需要同时更新f_commands和文件中的命令,对于时间已过的命令需要把它移除 173 | #检查f_commands内是否有命令到时间了,到时间了就把它的时间标记部分设为0 174 | #@debug 175 | def Chack_Commands_Time() : 176 | global command_file_path 177 | global f_commands 178 | 179 | new_f_commands = set([]) 180 | now_time = datetime.now() #当前时间 181 | for f_cm in f_commands : 182 | if not f_cm.split('_')[1] == '0' : 183 | cm_time = datetime.strptime(f_cm.split('_')[1], '%Y-%m-%d|%H:%M:%S') 184 | if 2> (now_time - cm_time).total_seconds() > -2 : #命令时间点前后各给予一秒的余量 185 | #时间到了,把时间标记设为0 186 | f_cm = f_cm.replace(f_cm.split('_')[1], '0') 187 | elif (now_time - cm_time).total_seconds() > 0: #时间已经过去的话 188 | continue #时间已经过去,就不要把它放入命令表中了 189 | 190 | new_f_commands.add(f_cm) 191 | 192 | f_commands = new_f_commands #更新f_commands中的命令 193 | 194 | with open(command_file_path,'w') as f : #更新文件中的命令 195 | for new_cm in new_f_commands : 196 | f.write(new_cm) 197 | 198 | 199 | 200 | #命令执行完后,调用这个函数把失效的命令从f_commands中清除(执行完后就是无效的命令了) 201 | #@debug 202 | def Clear_Disabled_Commands() : 203 | global command_file_path 204 | global f_commands 205 | 206 | new_f_commands = set([]) 207 | for cm in f_commands : 208 | if not cm.split('_')[1] == '0' : 209 | new_f_commands.add(cm) 210 | 211 | f_commands = new_f_commands 212 | 213 | with open(command_file_path, 'w') as f : #文件中的也要清除,不然又会被Flash_Commands()刷新进去f_commands 214 | for new_cm in new_f_commands : 215 | f.write(new_cm) 216 | 217 | 218 | #命令时间到了时的回复 219 | #获取对'Remind_0_吃饭','cnON_0_风扇'这种命令的回复,也就是执行命令的时间到了时的回复语, 220 | #'cnON_0_风扇' ---'正在为你打开风扇' 221 | #它只对cnON,cnOFF,Remind三种命令进行处理,这次处理是以命令包的形式 222 | #@debug 223 | def Get_Reply_Of_Command_On_Time(r_commands) : 224 | #回复语模板 225 | global reply_mods_ON 226 | global reply_mods_OFF 227 | global reply_mods_ON_and_OFF 228 | global reply_mods_Tixing 229 | 230 | #选择回复模板 231 | if len(r_commands) == 1 : 232 | if r_commands[0].startswith("cnON") : 233 | reply_mod = choice(reply_mods_ON) 234 | elif r_commands[0].startswith("cnOFF") : 235 | reply_mod = choice(reply_mods_OFF) 236 | elif r_commands[0].startswith("Remind") : 237 | if r_commands[0].endswith("闹钟\n") : 238 | reply_mod = u"闹钟设定的时间到了哦" 239 | else : 240 | reply_mod = choice(reply_mods_Tixing) 241 | else : 242 | reply_mod = choice(reply_mods_ON_and_OFF) 243 | 244 | #合成回复 245 | if r_commands[0].startswith("Remind") : 246 | action = r_commands[0].split('_')[-1] 247 | action = unicode(action, 'utf-8') 248 | #action = action.replace('\n', '') #去除换行符 249 | reply_mod = reply_mod.replace('v',action) 250 | 251 | else : 252 | for one_cm in r_commands : 253 | one_cm_split = one_cm.split('_') 254 | reply_n = [] #把要回复的名词放这 255 | for i in one_cm_split[2:] : 256 | reply_n.append(i) 257 | if not i == one_cm_split[-1] : 258 | reply_n.append(",") #放入逗号 259 | #如果有一个以上名词就把最后一个逗号替换为”和“ 260 | if len(reply_n) > 2 : 261 | reply_n[-2] = '和' 262 | reply_n_string = "".join(reply_n) 263 | reply_n_string = unicode(reply_n_string, 'utf-8') 264 | #合成最终回复 265 | if one_cm.startswith("cnON") : 266 | reply_mod = reply_mod.replace('x', reply_n_string) 267 | if one_cm.startswith("cnOFF") : 268 | reply_mod = reply_mod.replace('y', reply_n_string) 269 | 270 | reply = reply_mod 271 | reply = reply.replace('\n', '') #去除换行符 272 | Clear_Disabled_Commands() #执行完后把命令从f_commands中清除 273 | #print '回复语:' , 274 | #print reply 275 | 276 | return reply 277 | 278 | 279 | #执行时间标记为零的命令并返回回复语 280 | #@debug 281 | def Do_Commands() : 282 | global f_commands 283 | 284 | commands_cn = [] 285 | commands_en = [] 286 | commands_tixing = [] 287 | for f_command in f_commands : 288 | if f_command.split('_')[1] == '0' : #只取时间标记为0的命令 289 | if f_command.startswith("cn") : 290 | commands_cn.append(f_command) 291 | elif f_command.startswith("O") : 292 | commands_en.append(f_command) 293 | elif f_command.startswith("Remind") : 294 | commands_tixing.append(f_command) 295 | #得到时间标记为0的英文包,中文包,提醒包 296 | 297 | commands_cn.extend(commands_tixing) #把提醒包放入中文包然后一起给获取回复语的函数处理 298 | #获取回复语 299 | reply = None #初始值 300 | if not len(commands_cn) == 0 : 301 | reply = Get_Reply_Of_Command_On_Time(commands_cn) 302 | #下面是执行 303 | if not len(commands_en) == 0 : 304 | d_commands = commands_en #do_commands用英文包 305 | for one_command in d_commands : 306 | #关于电脑的指令 307 | if u'_computer' in one_command : 308 | if one_command[0:2] == u'ON' : #如果是'ON'指令的话 309 | Wake_Up_Computer() #唤醒电脑 310 | else : #如果是'OFF'指令的话 311 | ESP8266Sock.send("OFF_0_computer") 312 | #Arduino.write(("OFF_0_computer"+"\n").encode('utf-8')) #关闭电脑(交给Arduino处理) 313 | time.sleep(0.05) 314 | #执行完后把关于电脑的指令移除 315 | one_command = one_command.replace('_computer','') 316 | #指令格式'ON_0_jdq1_jdq2' 317 | """这里放把剩余指令发送到串口的函数(添加换行符后发送)""" 318 | if len(one_command.split("_")) > 2 : #执行完打开/关闭电脑的操作后如果还有其它命令的话就发给Arduino处理 319 | ESP8266Sock.send(one_command) 320 | #Arduino.write((one_command+"\n").encode('utf-8')) 321 | time.sleep(0.05) #延时一下防止发送速度太快Arduino接收不到 322 | return reply 323 | 324 | 325 | #这个函数写在try...finally...语句中的finally语句中,功能是在发生错误时把 326 | #f_commands中的命令写回文本中 327 | #@debug 328 | def Write_Commands_Back_To_File() : 329 | global command_file_path 330 | global f_commands 331 | 332 | with open(command_file_path, 'w') as f : 333 | for b_cm in f_commands : 334 | f.write(b_cm) 335 | 336 | 337 | 338 | 339 | #本模块的主要函数 340 | #检查命令的函数,这个函数需要在线程内不断运行以及时响应命令 341 | def Chack_Commands() : 342 | #给CPU留点时间 343 | time.sleep(0.1) 344 | global f_commands 345 | 346 | 347 | wt_reply = Fresh_Commands() #命令刚生成时回复 348 | if not wt_reply == None : 349 | return wt_reply 350 | Chack_Commands_Time() #检查f_commands中是否有命令到期,到期则把时间标记设为0,以便下面的程序处理并回复 351 | 352 | ot_reply = Do_Commands() #命令时间到了时的回复与执行 353 | if not ot_reply == None : 354 | return ot_reply 355 | 356 | 357 | 358 | if __name__ == "__main__" : 359 | try : 360 | while True : 361 | ret = Chack_Commands() 362 | if not ret == None : 363 | print "回复语" , 364 | print ret 365 | finally : 366 | #Arduino.close() 367 | Write_Commands_Back_To_File() 368 | 369 | 370 | -------------------------------------------------------------------------------- /Config.ini: -------------------------------------------------------------------------------- 1 | #此文件是语语音机器人项目的配置文件 2 | 3 | #主配置 4 | [Config] 5 | robot = Xiaoice #这里填你想要使用的机器人(Xiaoice/Tuling) 6 | 7 | 8 | #WOL部分 9 | [[WOL]] 10 | Computer_MAC = '00:00:00:00:00:00' #要唤醒主机的MAC 11 | 12 | #Command部分 13 | [[Command]] 14 | command_file_path = './command_temp.txt' #命令保存的临时文件的路径 15 | keyword_TiXing = '叫', '提醒' #命令"提醒"的关键词,需为动词(例句:十分钟后叫我起床) 16 | keyword_ON = '开', '打开', '开启' #命令"打开"的关键词,需为动词(可自己添加) 17 | keyword_OFF = '关', '关闭', '关掉' #命令"关闭"的关键词,需为动词(可自己添加) 18 | keywords_N = '电脑', '风扇', '电灯' #所有命令名词,只有在这里面的名词才会被识别为命令名词 19 | JDQ_1 = '风扇' #绑定继电器与命令名词,需与keywords_N配合使用 20 | JDQ_2 = '电灯' 21 | JDQ_3 = 22 | JDQ_4 = 23 | 24 | 25 | #语音命令回复语模板部分 26 | [[Command_Reply]] 27 | reply_mods_ON = '正在为你打开x', '好的,已为你打开x', '小冰不情愿地为你打开了x' #回复语“打开”的模板 28 | reply_mods_OFF = '已为你关闭y', '正在关闭y', '尊敬的发哥,小冰已为你关闭y' #回复语“打开”的模板 29 | reply_mods_ON_and_OFF = '好的,已为你打开x,关闭y', '好的,已执行操作' 30 | reply_mods_Tixing = '到v的时间啦,快点v', 'v时间到了哦' 31 | reply_mods_Tixing_With_Time = '好的,将在t提醒你v,放心吧', '好的,记住了' #提醒命令的回复语的模板,带时间,例:t=2016年11月19日22时16分28秒,v=吃饭 32 | reply_mods_ONOFF_With_Time = '欧拉!不就是在tv吗?记住了', '恩恩,到t我会v,放心吧' #打开/关闭命令的回复语的模板,带时间,例:t=2016年11月19日22时16分28秒,v=打开电灯 33 | 34 | 35 | 36 | #关于引脚的配置(使用BOARD编号系统) 37 | [Pin] 38 | Lu_Yin_Pin = 3 #引发中断进行录音的引脚,下降沿触发 39 | Lu_Yin_State_Pin = 5 #显示录音状态的引脚 40 | 41 | #关于百度的配置 42 | [Baidu] 43 | 44 | [[API]] 45 | #参数 46 | grant_type = "client_credentials" 47 | client_id = "#######" #API KEY填在这里 48 | client_secret = "#########" #Secret KEY填在这里 49 | 50 | 51 | #关于图灵的配置 52 | [Tuling] 53 | 54 | [[API]] 55 | #参数 56 | tuling_api_key = "########" #图灵的API KEY填这里 57 | 58 | 59 | #关于小冰的配置 60 | [Xiaoice] 61 | 62 | [[API]] 63 | #参数 64 | mobile = "#########" # 微博测试号的账号 65 | password = "#######" # 微博测试号的密码 66 | 67 | -------------------------------------------------------------------------------- /Print_With_Style_Module.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | 本模块定义了一个为输出添加格式的函数(颜色,加粗等) 5 | Print_With_Style(p_str, p_style) ----分别输入要打印的字符 6 | 串和需要的格式,str型 7 | """ 8 | 9 | 10 | #输入想要打印的字符串和打印格式,即可在终端打印出 11 | #带颜色/格式的文本,其格式为: 12 | #小写字母代表颜色:b(蓝色),g(绿色),r(红色) 13 | #大写字母代表格式:B(加粗),U(下划线) 14 | #例:Print_With_Style(''Hello!'', 'g+B') ----绿色加粗打印"Hello" 15 | def Print_With_Style(p_str, p_style) : 16 | if 'b' in p_style : #蓝色 17 | print '\033[94m' , 18 | if 'g' in p_style : #绿色 19 | print '\033[92m' , 20 | if 'r' in p_style : #红色 21 | print '\033[31m' , 22 | if 'B' in p_style : #加粗 23 | print '\033[1m' , 24 | if 'U' in p_style : #下划线 25 | print '\033[4m' , 26 | #打印正文 27 | print p_str , 28 | 29 | print '\033[0m' #END -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 基于树莓派的语音机器人项目 2 | > 题外话:本项目是对原 "Arduino语音控制项目" 的扩展(基于Arduino、LD3320和其它一些模块),由于功能有限,原项目已在2015年年末被废弃,取而代之的是这个基于 Python + 树莓派 的语音机器人项目。此项目开始于2016年末,最初计划是制作一个类似家庭语音助手的东西,在原来基础上添加一些语音对话,家电控制,日程提醒等功能。但由于项目初期个人能力有限,版本控制方面做的并不好,所以这里会使用新的版本库来提交。 3 | 4 | > 由于学业方面的原因,此项目实际上已经有半年多没有更新了。所以一些接口可能已经失效,或者某个问题已经有更简单的解决方法,关于这点我在下文中会尽量说明。 5 | 6 | ## 目录 7 | - [系统拓扑](#系统拓扑) 8 | - [功能](#功能) 9 | - [项目说明](#项目说明) 10 | - [依赖](#依赖) 11 | - [脚本说明](#脚本说明) 12 | - [使用方法](#使用方法) 13 | - [交互示例](#交互示例) 14 | - [参考资料](#参考资料) 15 | 16 | ## 系统拓扑 17 | ![系统拓扑图](https://github.com/enify/raspi-bot/blob/master/images/topo.png) 18 | 19 | ## 功能 20 | 21 | - [x] 语音对话 22 | - [x] 语音控制 23 | - [x] 闹钟 24 | - [x] 日程提醒 25 | - [x] 电器控制 26 | - [x] 电器定时开闭 27 | - [x] WOL唤醒 28 | - [x] 天气查询 29 | - [ ] 音乐播放 30 | - [ ] 配合LCD4Linux用作显示 31 | - [ ] 手势唤醒 32 | - [ ] 红外控制 33 | - [ ] 微信接入 34 | - [ ] 更多功能... 35 | 36 | 37 | ## 项目说明 38 | 39 | - 本项目基于Python2.7,尚未进行Python3化。 40 | - 语音输入功能依赖于树莓派的硬件中断,而输入完毕时录音可以自动停止。这个功能很大程度上受益于改造过的arecord命令(改造方法见:[在linux上如何做一个简单的vad功能,即录音时说话停止即录音停止。](http://blog.csdn.net/lijin6249/article/details/51955206),你也可以直接点击[这里](https://github.com/enify/raspi-bot/tree/master/resource)下载编译后的版本。) 41 | - 语音识别和语音合成功能由[百度语音](http://yuyin.baidu.com/)提供,请先开通应用,并把API KEY和Secret KEY填入 `Config.ini` 中。 42 | - 语音对话功能有两个对话源可选:图灵机器人或微软小冰。通过设置 `Config.ini` 中的`"robot"`值来选择。其中: 43 | - 使用图灵机器人先要到其[官网](http://www.tuling123.com)申请开通应用,并将机器人的APIkey填入 `Config.ini` 中。 44 | - 使用微软小冰需要先在微博上关注小冰的账号(weiruanxiaobing),然后将测试用的微博账号密码写入 `Config.ini` 中。(聊天功能是通过给小冰发私信来实现的)。 45 | > 注意:由于微博页面改版,故将小冰作为对话源的功能暂不可用。当前更好的办法是:使用 [ItChat](https://github.com/littlecodersh/itchat) + 小冰公众号 实现原有的 `"Get_Weibo_Xiaoice_Result(info)"` 方法以获取回复语。这个模块将在以后的版本中重写。 46 | - 要使用WOL唤醒功能,请先将被唤醒主机的MAC填入 `Config.ini` 中。 47 | - 当前版本对下位机的处理采用了一种并不理智的方法(连接不上的话就直接退出),并不符合平稳退化的原则。这将在以后的版本中得到改善。 48 | - 要对部分命令及命令回复语进行定制,请关注 `Config.ini` 中的 `[[Command_Reply]]` 部分。 49 | 50 | ## 依赖 51 | - Python外部库:`configobj`、`requests`、`lxml`、`RPi.GPIO`、`jieba` 。 52 | - Linux软件包:`mpg123`、`(改造过的)alsa-utils` 。 53 | - 硬件要求: 54 | - Raspberry Pi 全系列; 55 | - USB声卡; 56 | - 音箱; 57 | - 麦克风; 58 | - 已刷MicroPython的ESP-8266模块; 59 | - 按钮及杜邦线等; 60 | 61 | ## 脚本说明 62 | 本项目在设计时遵循了模块化的原则,各功能大都各司其职,在启动脚本中得到整合: 63 | - `main.py` :启动脚本。 64 | - `baidu_voice_Module.py` :百度语音合成与识别模块。 65 | - `tuling_robot_Module.py` :图灵机器人对话模块。 66 | - `xiaoice_robot_Module.py` :小冰机器人对话模块。*(待重写)* 67 | - `Speech_Command_Module.py` :语音命令提取模块。 68 | - `Chack_Commands_Module.py` :语音命令执行模块,兼具与下位机通信的工作。 69 | - `Time_Sign_Module.py` :时间处理相关,用于从语句中分析获取时间标记。 70 | - `Print_With_Style_Module.py` :带颜色的输出(方便调试用)。 71 | - `WOL_Module.py` :WOL网络唤醒模块。 72 | - `Config.ini` :配置文件。 73 | - `下位机-ESP8266端程序.py` :下位机程序。 74 | 75 | 76 | ## 使用方法 77 | - 克隆此版本库到树莓派任意路径: 78 | ``` 79 | git clone https://github.com/enify/raspi-bot.git 80 | ``` 81 | - 将arecord链接到版本库路径下: 82 | ``` 83 | ln ./resource/树莓派专用的改进的aplay arecord 84 | ``` 85 | - 连接触发按钮(下降沿触发),并在配置文件中填入触发按钮的中断引脚编号及状态显示引脚编号。 86 | - 在配置文件中填入在[百度语音官网]((http://yuyin.baidu.com/))申请的API KEY和Secret KEY 。 87 | - 在配置文件中填入在[图灵机器人官网]((http://www.tuling123.com))申请的API KEY 。 88 | - (可选)在配置文件中填入微博测试号的账号密码,以使小冰机器人可用。*(待重写)* 89 | - 在配置文件中配置被控电器的名称到命令名词字段`keywords_N` ,并将其绑定到 `JDQ_x` 字段上。 90 | - 在路由器后台配置静态地址绑定,分别设置树莓派和ESP-8266的地址为静态IP,其中ESP-8266的地址配置到 `Chack_Commands_Module.py` 中,树莓派的地址配置到 `下位机-ESP8266端程序.py` 中。*(待重写)* 91 | - 继电器引脚请通过 `下位机-ESP8266端程序.py` 这个脚本来配置,并将其上传到ESP-8266下位机中。*(待重写)* 92 | - 若要使用WOL唤醒功能的话,请将被唤醒主机的MAC填入配置文件中。 93 | - 连接USB声卡、麦克风、音响。 94 | - 运行: 95 | ``` 96 | python main.py 97 | ``` 98 | > 注意:脚本尚未做成后台形式,建议在 tmux 或 screen 中运行。 99 | 100 | ## 交互示例 101 | 102 | - 语音对话 103 | - 用户:吃饭了没? 104 | - 回复:吃了,吃了两人分,撑死我啦! 105 | - 用户:好开心啊 106 | - 回复:嘻嘻嘻我也开心 107 | - 用户:认识王羲之不? 108 | - 回复:王羲之的兰亭集序真的是书法一绝。 109 | - 语音控制 110 | - 用户:打开电灯 111 | - 回复:好的,已为你打开电灯 112 | - 用户:打开风扇并关闭电灯 113 | - 回复:好的,已为你打开风扇,关闭电灯 114 | - 用户:一小时三十八秒后打开电灯 115 | - 回复:欧拉!不就是在2016年12月20日22时16分28秒打开电灯吗?记住了 116 | - 闹钟、提醒事项 117 | - 用户:十分钟后叫我起床 118 | - 回复:好的,将在2016年11月19日22时16分28秒提醒你起床,放心吧 119 | - 回复:(到时间后)起床时间到了哦 120 | - 用户:设一个明天早上七点十分的闹钟 121 | - 回复:好的,记住了 122 | - 回复:(到时间后)铃声响起.. 123 | - 天气查询 124 | - 用户:明天天气如何? 125 | - 回复:请问你想查询哪个城市? 126 | - 用户:深圳 127 | - 回复:深圳:周五,多云 无持续风向微风,最低气温23度,最高气温27度 128 | - 用户:北京天气 129 | - 回复:北京:周六 11月19日,晴转阴 南风微风,最低气温1度,最高气温8度 130 | - WOL唤醒 131 | - 用户:帮我开一下电脑 132 | - 回复:正在为你打开电脑(发送幻数据包) 133 | - 用户:四分钟后打开电脑 134 | - 回复:恩恩,到2016年11月19日22时16分28秒我会打开电脑,放心吧 135 | 136 | 137 | ## 参考资料 138 | 139 | - [在linux上如何做一个简单的vad功能,即录音时说话停止即录音停止。](http://blog.csdn.net/lijin6249/article/details/51955206) 140 | - [使用命令行操作树莓派3上的USB声卡](http://blog.sina.com.cn/s/blog_55c3a5ae0102wqku.html) 141 | - [【MicroPython】ESP8266教程和资源 ](http://bbs.eeworld.com.cn/thread-497226-1-1.html) 142 | - [jieba:结巴中文分词 ](https://github.com/fxsjy/jieba) 143 | - [ICTCLAS 汉语词性标注集](http://fhqllt.iteye.com/blog/947917) 144 | -------------------------------------------------------------------------------- /Speech_Command_Module.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | 本模块为处理语音命令的模块,用于将语音文本内的部分命令放入到command_temp.txt文本中 5 | 再由Chack_Commands模块提供的函数检查这个文本中的命令并执行 6 | 包含一个函数Append_Speech_Command(speech_text) 7 | """ 8 | #结巴分词模块 9 | import jieba 10 | import jieba.posseg as pseg 11 | 12 | #读取配置文件的模块 13 | from configobj import ConfigObj 14 | #获取时间标记的模块(同时为本模块提供了jieba_Posseg_Cut()类) 15 | from Time_Sign_Module import * 16 | 17 | 18 | 19 | jieba.initialize() # 手动初始化jieba(可选) 20 | #配置文件对象 21 | Config = ConfigObj('./Config.ini', encoding= 'utf-8') 22 | 23 | #保存命令的临时文件的路径 24 | command_file_path = Config["Config"]["Command"]["command_file_path"] 25 | #提醒命令的命令名词(list) 26 | keyword_TiXing = Config["Config"]["Command"]["keyword_TiXing"] 27 | #打开/关闭的命令名词(list) 28 | keyword_ON = Config["Config"]["Command"]["keyword_ON"] 29 | keyword_OFF = Config["Config"]["Command"]["keyword_OFF"] 30 | #命令动词 31 | keywords_N = Config["Config"]["Command"]["keywords_N"] 32 | #继电器绑定 33 | JDQ_1 = Config["Config"]["Command"]["JDQ_1"] 34 | JDQ_2 = Config["Config"]["Command"]["JDQ_2"] 35 | JDQ_3 = Config["Config"]["Command"]["JDQ_3"] 36 | JDQ_4 = Config["Config"]["Command"]["JDQ_4"] 37 | 38 | #所有命令动词 39 | keywords_ON_OFF = [] 40 | keywords_ON_OFF.extend(keyword_ON) 41 | keywords_ON_OFF.extend(keyword_OFF) 42 | 43 | 44 | """ 45 | jieba带词性分词的类,返回各词性的词组成的二维list, 46 | #每项由['词', '词性',词在原文中的顺序]组成,属性有: 47 | self.cut_or_not : 有没有执行分词(用于判断None),返回值为True/False 48 | self.ci_xing_m : 词性为“数词”的词组成的列表 49 | self.ci_xing_f : 词性为“方位词”的词组成的列表 50 | self.ci_xing_v : 词性为“动词”的词组成的列表 51 | self.ci_xing_n : 词性为“名词”的词组成的列表 52 | self.ci_xing_r : 词性为“代词”的词组成的列表 53 | self.ci_xing_nr : 词性为“人名”的词组成的列表 54 | self.ci_xing_c : 词性为“连词”的词组成的列表 55 | self.ci_xing_ q : 词性为“量词”的词组成的列表 56 | """ 57 | 58 | """ 59 | #!!!!下面这个类现在由Time_Sign_Module提供!!!! 60 | #一个分词的类,包含的属性有:分词状态,每种词性的词的列表(每项由['词', '词性',词在原文中的顺序]组成,可包含多个项目) 61 | class jieba_Posseg_Cut(object) : 62 | 63 | def __init__(self, cut_text) : 64 | self.cut_text = cut_text 65 | 66 | if self.cut_text == None : 67 | 68 | #分词了吗? 69 | self.cut_or_not =False 70 | else : 71 | p = pseg.cut(self.cut_text) 72 | self.text_cut_list = [] #-----新增的属性----- 73 | ci_number = 0 #词在原文中的顺序 74 | #转换成list 75 | for ci, ci_xing in p : 76 | self.text_cut_list.append([ci, ci_xing, ci_number]) 77 | ci_number += 1 78 | #text_cut_list是一个二维list,每项由['词', '词性',词在原文中的顺序]组成 79 | #下面将它分类以便分析(只取我需要的词性,其它词性不处理) 80 | self.ci_xing_m = [] #数词,例:"十分钟,七点" 81 | self.ci_xing_f = [] #方位词,例:"后" 82 | self.ci_xing_v = [] #动词,例:"关闭","叫","起床","帮","提醒" 83 | self.ci_xing_n = [] #名词,例:"电脑","风扇" 84 | self.ci_xing_r = [] #代词,例:"我","他" 85 | self.ci_xing_nr = [] #人名,例:"小冰" 86 | #self.ci_xing_x = [] #非语素字,即标点,例:"," 87 | self.ci_xing_c = [] #连词,例:"和" 88 | self.ci_xing_q = [] #量词,例:"分钟" 89 | 90 | for i in self.text_cut_list : 91 | if i[1] == 'm' : 92 | self.ci_xing_m.append(i) 93 | elif i[1] == 'f' : 94 | self.ci_xing_f.append(i) 95 | elif i[1] == 'v' : 96 | self.ci_xing_v.append(i) 97 | elif i[1] == 'n' : 98 | self.ci_xing_n.append(i) 99 | elif i[1] == 'r' : 100 | self.ci_xing_r.append(i) 101 | elif i[1] == 'nr' : 102 | self.ci_xing_nr.append(i) 103 | elif i[1] == 'c' : 104 | self.ci_xing_c.append(i) 105 | elif i[1] == 'q' : 106 | self.ci_xing_q.append(i) 107 | 108 | #分词了吗? 109 | self.cut_or_not = True 110 | """ 111 | 112 | #把命令语句写入临时文件的函数 113 | def Write_Command_To_File(command_str) : 114 | global command_file_path 115 | global append_or_not 116 | 117 | append_or_not = True #这个函数被调用了,说明有命令被写入 118 | command_str = command_str.encode('utf-8') 119 | with open(command_file_path, 'r') as f : 120 | f_commands = f.readlines() 121 | with open(command_file_path, 'w') as f : 122 | for o_command in f_commands : 123 | f.write(o_command) 124 | if not command_str in f_commands : #防止有重复的命令进入 125 | f.write(command_str) 126 | f.write('\n') 127 | 128 | 129 | #有两种命令:提醒/闹钟,开/关 130 | #通过分析语音文字生成指令表,包含一个英文命令包和一个中文命令包 131 | def Text_To_Commands(speech_text) : 132 | global keyword_TiXing 133 | global keyword_ON 134 | global keyword_OFF 135 | global keywords_ON_OFF 136 | global keywords_N 137 | global JDQ_1, JDQ_2, JDQ_3, JDQ_4 138 | 139 | text_cut = jieba_Posseg_Cut(speech_text) 140 | if not text_cut.cut_or_not == False : 141 | #先看是不是提醒/闹钟命令 142 | tixing_action = '' 143 | for tixing_word in keyword_TiXing : 144 | 145 | if tixing_word in speech_text : #是提醒命令的话("半个小时后叫我起床") 146 | if not len(text_cut.ci_xing_r) == 0 : 147 | for one_ci in text_cut.text_cut_list : 148 | if one_ci[2] > text_cut.ci_xing_r[0][2] : 149 | tixing_action += one_ci[0] #代词后面的词是提醒的动作 150 | tixing_time_sign = Get_Time_Sign(speech_text) 151 | tixing_time_str = '0' 152 | if not tixing_time_sign == None : 153 | tixing_time_str = tixing_time_sign.strftime('%Y-%m-%d|%H:%M:%S') 154 | Write_Command_To_File('Remind_%s_%s' %(tixing_time_str,tixing_action)) #命令写入文件 155 | text_cut.ci_xing_v = [] #因为证明了这条命令是提醒命令,所以把text_cut.ci_xing_v置零以让它跳过对开关指令的分析 156 | break 157 | 158 | for one_ci in text_cut.ci_xing_n : 159 | if u"闹钟" in one_ci : #是设闹钟命令的话("设一个明天早上七点半的闹钟") 160 | tixing_time_sign = Get_Time_Sign(speech_text) 161 | tixing_time_str = '0' 162 | if not tixing_time_sign == None : 163 | tixing_time_str = tixing_time_sign.strftime('%Y-%m-%d|%H:%M:%S') 164 | Write_Command_To_File('Remind_%s_%s' %(tixing_time_str,u'闹钟')) 165 | text_cut.ci_xing_v = [] #因为证明了这条命令是提醒命令,所以把text_cut.ci_xing_v置零以让它跳过对开关指令的分析 166 | break 167 | 168 | 169 | 170 | #由动词开始分析句子 171 | if not len(text_cut.ci_xing_v) == 0 : 172 | command_v = [] #命令动词 173 | command_n_1 = [] #动词1对谁执行(命令名词) 174 | command_n_2 = [] #动词2对谁执行(命令名词) 175 | command_n_3 = [] #动词3对谁执行(命令名词) 176 | command_1 = [] #第1条命令 177 | command_2 = [] #第2条命令 178 | command_3 = [] #第3条命令 179 | 180 | for v_ci in text_cut.ci_xing_v : 181 | if v_ci[0] in keywords_ON_OFF : 182 | #把符合要求的动词放入command_v中(命令动词) 183 | command_v.append(v_ci) 184 | 185 | #开始分析名词--------开关量名词分析开始 186 | if len(command_v) == 1 and not len(text_cut.ci_xing_n) == 0 : 187 | print "一类" 188 | #只有一个规定动词的话 189 | for n_ci in text_cut.ci_xing_n : 190 | if n_ci[0] in keywords_N : 191 | #把符合要求的名词放入command_n中(命令名词) 192 | command_n_1.append(n_ci) 193 | #将动词和名词组合成一条命令放入command_1中 194 | if not len(command_n_1) == 0 : 195 | command_1.append(command_v[0]) 196 | command_1.extend(command_n_1) 197 | 198 | elif len(command_v) == 2 and not len(text_cut.ci_xing_n) == 0 : 199 | print "二类" 200 | #有两个规定动词的话 201 | for n_ci in text_cut.ci_xing_n : 202 | if n_ci[0] in keywords_N : 203 | if n_ci[2] < command_v[1][2] : 204 | #标号小于第二个动词的标号的话,属于command_1 205 | command_n_1.append(n_ci) 206 | else : 207 | #标号大于第二个动词的标号的话,属于command_2 208 | command_n_2.append(n_ci) 209 | #将动词和名词组合成命令后分别放入command_1和command_2中 210 | if not len(command_n_1) == 0 : 211 | command_1.append(command_v[0]) 212 | command_1.extend(command_n_1) 213 | if not len(command_n_2) == 0 : 214 | command_2.append(command_v[1]) 215 | command_2.extend(command_n_2) 216 | 217 | elif len(command_v) > 2 and not len(text_cut.ci_xing_n) == 0 : 218 | print "三类" 219 | #有三个规定动词的话(本程序规定最多只能有三个动词,多于三个则把名词交给第三个动词处理) 220 | for n_ci in text_cut.ci_xing_n : 221 | if n_ci[0] in keywords_N : 222 | if n_ci[2] < command_v[1][2] : 223 | #标号小于第二个动词的标号的话,属于command_1 224 | command_n_1.append(n_ci) 225 | 226 | elif n_ci[2] >command_v[1][2] and n_ci[2] < command_v[2][2]: 227 | #标号大于第二个动词的标号小于第三个动词的标号的话,属于command_2 228 | command_n_2.append(n_ci) 229 | else : 230 | #标号大于第三个动词的标号的话,属于command_3 231 | command_n_3.append(n_ci) 232 | #将动词和名词组合成命令后分别放入command_1,command_2和command_3中 233 | if not len(command_n_1) == 0 : 234 | command_1.append(command_v[0]) 235 | command_1.extend(command_n_1) 236 | if not len(command_n_2) == 0 : 237 | command_2.append(command_v[1]) 238 | command_2.extend(command_n_2) 239 | if not len(command_n_3) == 0 : 240 | command_3.append(command_v[2]) 241 | command_3.extend(command_n_3) 242 | else : 243 | print "四类" 244 | #print "没命令动词或没名词" 245 | if len(command_v) == 0 : 246 | print "三级过滤,有动词,没有命令动词" 247 | elif len(text_cut.ci_xing_n) == 0 : 248 | print "三级过滤,有命令动词,没有名词" 249 | else : 250 | print "其它" 251 | 252 | #没命令动词或没(普通)名词 253 | return None 254 | #-----------开关量名词分析结束 255 | 256 | #command_x 形式: [['动词', '词性', '标号'], ['名词', '词性', '标号'], ['名词', '词性', '标号']] 257 | #commands 形式: ['打开_0_名词_名词', '关闭_0_名词_名词'] 258 | #合并命令结果 259 | command_ON = '' #打开命令放这里(string形式) 260 | command_OFF = '' #关闭命令放这里(string形式) 261 | for cm in [command_1, command_2, command_3] : 262 | if not len(cm) == 0 : 263 | if cm[0][0] in keyword_ON : 264 | #放入一个'ON_0',其中0为备用位,以后用来放时间 265 | if not u'ON_0' in command_ON : 266 | command_ON += u'ON_0' 267 | #合并名词结果 268 | for cm_n in cm[1:] : 269 | if not cm_n[0] in command_ON : 270 | command_ON += '_' 271 | command_ON += cm_n[0] 272 | elif cm[0][0] in keyword_OFF : 273 | #放入一个'OFF_0',其中0为备用位,以后用来放时间 274 | if not u'OFF_0' in command_OFF : 275 | command_OFF += u'OFF_0' 276 | #合并名词结果 277 | for cm_n in cm[1:] : 278 | if not cm_n[0] in command_OFF : 279 | command_OFF += '_' 280 | command_OFF += cm_n[0] 281 | 282 | #命令包 283 | commands_en = [] #英文命令包,格式:['ON_0_jdq1_jdq2', 'OFF_0_jdq3_jdq4'] 284 | commands_cn = [] #中文命令包,格式:['ON_0_名词_名词', '关闭_0_名词_名词'] 285 | for cm in [command_ON, command_OFF] : 286 | if not len(cm) == 0 : 287 | #放入中文命令包 288 | commands_cn.append(cm) 289 | #替换中文指令为英文 290 | #替换前:u'ON_0_电脑_风扇_台灯' 291 | #替换后:u'ON_0_computer_jdq1_jdq2' 292 | cm = cm.replace(u'电脑', 'computer') 293 | if not len(JDQ_1) == 0 : 294 | cm = cm.replace(JDQ_1, 'jdq1') 295 | if not len(JDQ_2) == 0 : 296 | cm = cm.replace(JDQ_2, 'jdq2') 297 | if not len(JDQ_3) == 0 : 298 | cm = cm.replace(JDQ_3, 'jdq3') 299 | if not len(JDQ_4) == 0 : 300 | cm = cm.replace(JDQ_4, 'jdq4') 301 | #放入英文命令包 302 | commands_en.append(cm) 303 | if len(commands_en) == 0 : 304 | #有命令动词没命令名词 305 | print "四级过滤,有命令动词,没命令名词" 306 | return None 307 | 308 | else : 309 | print "分析成功!有命令动词,有命令名词" 310 | #把两个命令包都返回,中文包用于回复信息,英文包用于执行命令 311 | return commands_en, commands_cn 312 | 313 | else : 314 | #没有动词 315 | print "二级过滤,没有动词" 316 | return None 317 | else : 318 | #输入为None 319 | print "一级过滤,输入为None" 320 | return None 321 | 322 | #主要函数,从输入文本中获取命令并写入临时文件 323 | def Append_Speech_Command(speech_text) : 324 | global append_or_not 325 | append_or_not = False #放入命令了吗? 326 | 327 | commands = Text_To_Commands(speech_text) 328 | if not commands == None : 329 | en_commands = commands[0] #英文包 330 | cn_commands = commands[1] #中文包 331 | #将时间标记文本写入命令中 332 | onoff_time_sign = Get_Time_Sign(speech_text) 333 | onoff_time_str = '0' 334 | if not onoff_time_sign == None : 335 | onoff_time_str = onoff_time_sign.strftime('%Y-%m-%d|%H:%M:%S') #时间标记文本 336 | 337 | #写入文件中 338 | for en_cm in en_commands : 339 | en_cm = en_cm.replace('0', onoff_time_str) 340 | Write_Command_To_File(en_cm) 341 | for cn_cm in cn_commands : 342 | cn_cm = cn_cm.replace('0', onoff_time_str) 343 | cn_cm = cn_cm.replace(u'ON',u'cnON') #中文包现在由cnON作为前缀,方便分析 344 | cn_cm = cn_cm.replace(u'OFF',u'cnOFF') 345 | Write_Command_To_File(cn_cm) 346 | 347 | return append_or_not 348 | 349 | 350 | 351 | 352 | 353 | #测试Do_Commands() 354 | if __name__ == "__main__" : 355 | try : 356 | while True : 357 | text = raw_input("请输入你要说的话>>>") 358 | text = unicode(text,'utf-8') 359 | Append_Speech_Command(text) 360 | finally : 361 | #Arduino.close() 362 | pass 363 | 364 | 365 | """ 366 | #测试Text_To_Commands() 367 | if __name__ == "__main__" : 368 | while True : 369 | text = raw_input("请输入要分词的句子>>") 370 | cms = Text_To_Commands(text) 371 | if not cms == None : 372 | for cm in cms : 373 | print cm 374 | """ 375 | 376 | 377 | 378 | #测试jieba_Posseg_Cut() 379 | """ 380 | if __name__ == "__main__" : 381 | while True : 382 | text = raw_input("请输入要分词的句子>>") 383 | j_cut = jieba_Posseg_Cut(text) 384 | 385 | if not j_cut.cut_or_not == False : 386 | print j_cut.ci_xing_m 387 | print j_cut.ci_xing_f 388 | print j_cut.ci_xing_v 389 | print j_cut.ci_xing_n 390 | print j_cut.ci_xing_r 391 | print j_cut.ci_xing_nr 392 | print j_cut.ci_xing_c 393 | print j_cut.ci_xing_q 394 | 395 | """ -------------------------------------------------------------------------------- /Time_Sign_Module.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | 本模块被Speech_Command_Module模块调用 5 | 6 | 本模块用于在语句中获取获取时间标记,包含一个函数: 7 | Get_Time_Sign(speech_text),它返回一个datetime对象或者None 8 | """ 9 | 10 | import jieba 11 | import jieba.posseg as pseg 12 | 13 | from datetime import datetime, timedelta #时间 14 | 15 | 16 | jieba.initialize() # 手动初始化jieba(可选) 17 | 18 | 19 | #一个分词的类,包含的属性有:分词状态,每种词性的词的列表(每项由['词', '词性',词在原文中的顺序]组成,可包含多个项目) 20 | class jieba_Posseg_Cut(object) : 21 | 22 | def __init__(self, cut_text) : 23 | self.cut_text = cut_text 24 | 25 | if self.cut_text == None : 26 | 27 | #分词了吗? 28 | self.cut_or_not =False 29 | else : 30 | p = pseg.cut(self.cut_text) 31 | self.text_cut_list = [] #-----新增的属性----- 32 | ci_number = 0 #词在原文中的顺序 33 | #转换成list 34 | for ci, ci_xing in p : 35 | self.text_cut_list.append([ci, ci_xing, ci_number]) 36 | ci_number += 1 37 | #text_cut_list是一个二维list,每项由['词', '词性',词在原文中的顺序]组成 38 | #下面将它分类以便分析(只取我需要的词性,其它词性不处理) 39 | self.ci_xing_m = [] #数词,例:"十分钟,七点" 40 | self.ci_xing_f = [] #方位词,例:"后" 41 | self.ci_xing_v = [] #动词,例:"关闭","叫","起床","帮","提醒" 42 | self.ci_xing_n = [] #名词,例:"电脑","风扇" 43 | self.ci_xing_r = [] #代词,例:"我","他" 44 | self.ci_xing_nr = [] #人名,例:"小冰" 45 | #self.ci_xing_x = [] #非语素字,即标点,例:"," 46 | self.ci_xing_c = [] #连词,例:"和" 47 | self.ci_xing_q = [] #量词,例:"分钟" 48 | self.ci_xing_i = [] #习用语,例:"大后天" 49 | 50 | for i in self.text_cut_list : 51 | if i[1] == 'm' : 52 | self.ci_xing_m.append(i) 53 | elif i[1] == 'f' : 54 | self.ci_xing_f.append(i) 55 | elif i[1] == 'v' : 56 | self.ci_xing_v.append(i) 57 | elif i[1] == 'n' : 58 | self.ci_xing_n.append(i) 59 | elif i[1] == 'r' : 60 | self.ci_xing_r.append(i) 61 | elif i[1] == 'nr' : 62 | self.ci_xing_nr.append(i) 63 | elif i[1] == 'c' : 64 | self.ci_xing_c.append(i) 65 | elif i[1] == 'q' : 66 | self.ci_xing_q.append(i) 67 | elif i[1] == 'i' : 68 | if i[0] == u"大后天" : #习用语只接收"大后天" 69 | self.ci_xing_q.append(i) 70 | 71 | #分词了吗? 72 | self.cut_or_not = True 73 | 74 | 75 | 76 | #分析单个时间数,它可能是时/分/秒中的一个 77 | def Time_Str_To_Value(time_str) : 78 | time_value = 0 79 | 80 | ge_str = time_str 81 | #分析十位数 82 | ten_place = time_str.find(u'十') #找到'十'的位置,看时间是一位数还是两位数 83 | if not ten_place == -1 : 84 | #是两位数时间数 85 | ten_str = time_str[0:ten_place] 86 | if len(ten_str) == 0 : #十几 87 | time_value += 10 88 | else : #二十几/三十几... 89 | if u'二' in ten_str : 90 | time_value += 20 91 | elif u'三' in ten_str : 92 | time_value += 30 93 | elif u'四' in ten_str : 94 | time_value += 40 95 | elif u'五' in ten_str : 96 | time_value += 50 97 | elif u'六' in ten_str : 98 | time_value += 60 99 | elif u'七' in ten_str : 100 | time_value += 70 101 | elif u'八' in ten_str : 102 | time_value += 80 103 | elif u'九' in ten_str : 104 | time_value += 90 105 | ge_str = time_str[ten_place:] #移除十位数剩下个位数 106 | #分析个位数 107 | if u'一' in ge_str : 108 | time_value += 1 109 | elif u'两' in ge_str : 110 | time_value += 2 111 | elif u'二' in ge_str : 112 | time_value += 2 113 | elif u'三' in ge_str : 114 | time_value += 3 115 | elif u'四' in ge_str : 116 | time_value += 4 117 | elif u'五' in ge_str : 118 | time_value += 5 119 | elif u'六' in ge_str : 120 | time_value += 6 121 | elif u'七' in ge_str : 122 | time_value += 7 123 | elif u'八' in ge_str : 124 | time_value += 8 125 | elif u'九' in ge_str : 126 | time_value += 9 127 | 128 | if u'半' in ge_str : #"半"可以与上面的数同时出现,所以分开处理 129 | time_value += 0.5 130 | 131 | return time_value 132 | 133 | 134 | 135 | #获取时间标记---一段时间后(推荐格式:一小时三十分二十八秒后) 136 | def Time_After_To_Time_Sign(t_text) : 137 | 138 | if not u'分钟' in t_text : 139 | t_text = t_text.replace(u'分',u'分钟') #把'分'换成'分钟' 140 | 141 | H_place = t_text.find(u'小时') 142 | M_place = t_text.find(u'分钟') 143 | S_place = t_text.find(u'秒') 144 | H_value = 0 #初始值 145 | M_value = 0 146 | S_value = 0 147 | #分析'时' 148 | if not H_place == -1 : 149 | H_str = t_text[0:H_place] 150 | #print H_str 151 | H_value = Time_Str_To_Value(H_str) 152 | M_start_place = H_place + 2 #切割'分'时间数的开始位置 153 | else : 154 | M_start_place = 0 #切割'分'时间数的开始位置 155 | #分析'分' 156 | if not M_place == -1 : 157 | M_str = t_text[M_start_place:M_place] 158 | #print M_str 159 | M_value = Time_Str_To_Value(M_str) 160 | S_start_place = M_place + 2 #切割'秒'时间数的开始位置 161 | else : 162 | S_start_place = 0 163 | #分析'秒' 164 | if not S_place == -1 : 165 | if not H_place == -1 : 166 | S_str = t_text[M_start_place:S_place] #有'时'无'分'有'秒'的情况 167 | else : 168 | S_str = t_text[S_start_place:S_place] 169 | #print S_str 170 | S_value = Time_Str_To_Value(S_str) 171 | #分析结束 172 | if all([H_value==0, M_value==0, S_value==0]) : 173 | return None #无时间标记,返回None 174 | else : 175 | now_time = datetime.now() #当前时间 176 | target_time = now_time + timedelta(hours=H_value, minutes=M_value, seconds=S_value) 177 | 178 | #print u'小时:%s;分钟:%d;秒:%d后' %(H_value, M_value, S_value), 179 | #print str(target_time) 180 | 181 | return target_time #返回时间标记 182 | 183 | 184 | 185 | #获取时间标记----时间点(推荐输入格式:明天早上七点四十五分) 186 | def Time_Point_To_Time_Sign(tp_text) : 187 | D_value = 0 #初始值,注意这里的各种value和Time_After_To_Time_Sign()函数中的value含义是不一样的 188 | H_value = 0 189 | M_value = 0 190 | 191 | #分析'天' 192 | if u'明天' in tp_text : 193 | D_value += 1 194 | tp_text = tp_text.replace(u'明天','') 195 | elif u'大后天' in tp_text : 196 | D_value += 3 197 | tp_text = tp_text.replace(u'大后天','') 198 | elif u'后天' in tp_text : 199 | D_value += 2 200 | tp_text = tp_text.replace(u'后天','') 201 | #分析'时辰',本程序中把时间换算成24小时制来算 202 | tp_text = tp_text.replace(u'今天','') 203 | tp_text = tp_text.replace(u'早上','') 204 | tp_text = tp_text.replace(u'上午','') 205 | tp_text = tp_text.replace(u'中午','') 206 | if u'晚上' in tp_text : 207 | H_value += 12 208 | tp_text = tp_text.replace(u'晚上','') 209 | elif u'下午' in tp_text : 210 | H_value += 12 211 | tp_text = tp_text.replace(u'下午','') 212 | #分析'时',它以'点'为标志 213 | H_place = tp_text.find(u'点') 214 | M_place = tp_text.find(u'分') 215 | if not H_place == -1 : 216 | H_str = tp_text[0:H_place] 217 | #print H_str 218 | H_value += Time_Str_To_Value(H_str) 219 | M_start_place = H_place + 1 220 | #分析'分',必须'时'存在 221 | if not M_place == -1 : 222 | M_str = tp_text[M_start_place:M_place] 223 | else : 224 | M_str = tp_text[M_start_place:] 225 | if u'半' in M_str : #'点'作为标志时'半'必须单独分析,而不应该交给'分'分析 226 | M_value += 30 227 | M_str = M_str.replace(u'半','') 228 | #print M_str 229 | M_value += Time_Str_To_Value(M_str) 230 | #分析结束 231 | if all([D_value==0, H_value==0, M_value==0]) : 232 | 233 | return None #没有时间标记的话 234 | 235 | now_time = datetime.now() #当前时间 236 | if all([D_value!=0, H_value==0, M_value==0]) : 237 | target_time = now_time + timedelta(days=D_value) #为了应对'明天/后天的这个时候叫我起床'这种情况 238 | else : 239 | target_time = datetime(now_time.year, now_time.month, now_time.day, H_value, M_value) + timedelta(days=D_value) #目标时间 240 | 241 | #print u'%s天后%s点%s分' %(D_value,H_value,M_value), 242 | #print str(target_time) 243 | 244 | return target_time #返回时间标记 245 | 246 | 247 | 248 | 249 | 250 | #获取时间标记-------主要函数 251 | def Get_Time_Sign(speech_text) : 252 | if u'闹钟' in speech_text : 253 | speech_text = speech_text.replace(u'一个','') #为了应对"设一个七点三十八的闹钟"这种情况 254 | 255 | text_cut = jieba_Posseg_Cut(speech_text) 256 | 257 | if text_cut.cut_or_not == True : 258 | #由方位词开始分析 259 | time_str_start = 0 #初始值 260 | time_str_end = 0 261 | if not len(text_cut.ci_xing_f) == 0 : 262 | if text_cut.ci_xing_f[0][0] == u"后" : 263 | #有方位词"后",表明是一段时间 264 | if len(text_cut.ci_xing_m) > 0 : 265 | time_str_start = text_cut.ci_xing_m[0][2] #时间语开始的位置 266 | time_str_end = text_cut.ci_xing_f[0][2] #时间语结束的位置 267 | time_str = '' #时间语----一段时间后 268 | for t_ci in text_cut.text_cut_list[time_str_start:time_str_end] : 269 | time_str += t_ci[0] 270 | 271 | print time_str 272 | 273 | time_sign = Time_After_To_Time_Sign(time_str) 274 | if not time_sign == None : 275 | print "时间后,结果:" , 276 | print str(time_sign) 277 | #代表一个时间点或无时间标记 278 | else : 279 | for t_ci in text_cut.text_cut_list : 280 | if t_ci[1] == 't' or t_ci[1] == 'm' or t_ci[1] == 'i': 281 | time_str_start = t_ci[2] #时间语开始的位置 282 | break 283 | 284 | for t_ci in reversed(text_cut.text_cut_list) : 285 | if t_ci[1] == 'm' : 286 | time_str_end = t_ci[2] #时间语结束的位置 287 | break 288 | 289 | time_str = '' #时间语----特点时间点 290 | for t_ci in text_cut.text_cut_list[time_str_start:time_str_end + 1] : 291 | time_str += t_ci[0] 292 | 293 | print time_str 294 | 295 | time_sign = Time_Point_To_Time_Sign(time_str) 296 | if not time_sign == None : 297 | print "时间点,结果:" , 298 | print str(time_sign) 299 | 300 | return time_sign 301 | 302 | 303 | 304 | 305 | if __name__ == "__main__" : 306 | while True : 307 | text = raw_input("请输入要测试命令>>>") 308 | text = unicode(text, 'utf-8') 309 | Get_Time_Sign(text) -------------------------------------------------------------------------------- /WOL_Module.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | 本模块被Speech_Command_Module模块调用 5 | 6 | WOL(网络唤醒)的模块,用到socket通信 7 | 包含一个函数:Wake_Up_Computer() -------无需参数唤醒的机器由Config.ini决定 8 | 9 | 说明:局域网远程唤醒(WakeupOnLAN)--发送一个MagicPacket到某个MAC地址 10 | MagicPacket:UDP广播包,端口不限,数据是FF-FF-FF-FF-FF-FF加16个MAC 11 | """ 12 | 13 | import struct #用于处理二进制 14 | import socket 15 | from socket import AF_INET, SOCK_DGRAM, SOL_SOCKET, SO_BROADCAST 16 | 17 | #读取配置文件的模块 18 | from configobj import ConfigObj 19 | #配置文件对象 20 | Config = ConfigObj('./Config.ini', encoding= 'utf-8') 21 | 22 | 23 | #要唤醒的电脑的MAC 24 | computer_mac = Config['Config']['WOL']['Computer_MAC'] 25 | 26 | 27 | def Wake_Up_Computer() : 28 | 29 | wake_data = ''.join(['FFFFFFFFFFFF', computer_mac.replace(':', '')*16]) 30 | #幻数据包(编码成二进制) 31 | MagicPacket = b'' 32 | for i in range(0, len(wake_data), 2) : 33 | byte_dat = struct.pack('B', int(wake_data[i: i + 2], 16)) 34 | MagicPacket = MagicPacket + byte_dat 35 | 36 | #建立UDP发送 37 | wake_sock = socket.socket(AF_INET, SOCK_DGRAM) 38 | wake_sock.setsockopt(SOL_SOCKET, SO_BROADCAST, 1) 39 | wake_sock.sendto(MagicPacket, ('255.255.255.255', 7)) 40 | wake_sock.close() 41 | print "唤醒成功!MAC为:" , 42 | print computer_mac 43 | 44 | 45 | 46 | if __name__ == "__main__" : 47 | Wake_Up_Computer() -------------------------------------------------------------------------------- /baidu_voice_Module.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | 百度语音识别和语音合成的模块 5 | 6 | 主要有两个函数: 7 | Speech_To_Text(speech_path) -----输入speech文件的路径,返回得识别结果(列表形式) 8 | Text_To_Speech(target_text) -----输入文本,返回合成的speech文件路径 9 | #返回TTS合成的MP3文件的路径 10 | """ 11 | import os 12 | import requests 13 | import uuid 14 | import base64 15 | import json 16 | 17 | #读取配置文件的模块 18 | from configobj import ConfigObj 19 | #配置文件对象 20 | Config = ConfigObj('./Config.ini', encoding= 'utf-8') 21 | 22 | 23 | #语音识别的API地址 24 | api_url = "http://vop.baidu.com/server_api" 25 | #语音合成的API地址 26 | baidu_tts_api_url = "http://tsn.baidu.com/text2audio" 27 | 28 | #获取Access token 的部分 29 | access_url = "https://openapi.baidu.com/oauth/2.0/token" #Access token的获取地址 30 | my_mac = uuid.UUID(int = uuid.getnode()).hex[-12:] #获取本机MAC 31 | 32 | #参数 33 | grant_type = Config['Baidu']['API']['grant_type'] 34 | client_id = Config['Baidu']['API']['client_id'] #百度的API KEY 35 | client_secret = Config['Baidu']['API']['client_secret'] #百度的Secret KEY 36 | 37 | #构建表单 38 | access_form = { 39 | 'grant_type' : grant_type , 40 | 'client_id' : client_id , 41 | 'client_secret' : client_secret 42 | } 43 | 44 | #此函数用于获取Accesstoken 45 | def Get_Access_token() : 46 | try : 47 | access_page = requests.post(access_url, data =access_form) 48 | access_dict = access_page.json() 49 | access_token = access_dict["access_token"] 50 | 51 | return access_token 52 | except : 53 | print "获取Access token异常!请检查网络设置!" 54 | 55 | exit(0) 56 | 57 | #取得百度的Access_token 58 | access_token = Get_Access_token() 59 | 60 | ################################ 61 | 62 | #输入speech文件的路径到此函数,即可取得识别结果(列表形式) 63 | def Speech_To_Text(speech_path) : 64 | with open(speech_path, "rb") as speech_file : 65 | speech_data = speech_file.read() 66 | 67 | speech_data_length = len(speech_data) #原始语音数据长度 68 | speech_b64_data = base64.b64encode(speech_data).decode('utf-8') 69 | dict_data = { 70 | 'format' : 'wav' , 71 | 'rate' : 8000 , #采样率 72 | 'channel' : 1 , 73 | 'cuid' : my_mac , 74 | 'token' : access_token , 75 | 'lan' : 'zh' , #语种 76 | 'speech' : speech_b64_data , 77 | 'len' : speech_data_length 78 | } 79 | 80 | json_data = json.dumps(dict_data).encode('utf-8') 81 | json_data_length = len(json_data) 82 | 83 | #构建请求头 84 | post_headers ={ 85 | 'Content-Type' : 'application/json' , 86 | 'Content-length' : json_data_length 87 | } 88 | 89 | print "正在发送录音数据到网络" 90 | try : 91 | result_page = requests.post(api_url, headers=post_headers, data=json_data,timeout=3) 92 | 93 | except requests.exceptions.Timeout : 94 | print "数据POST超时,是不是网络不好?" 95 | return None 96 | 97 | print "网络数据发送成功" 98 | dict_result = result_page.json() 99 | 100 | err_no = dict_result['err_no'] 101 | if err_no == 0 : 102 | text_result_list = dict_result['result'] 103 | 104 | text_result = text_result_list[0] #取候选词中的第一个 105 | if text_result.endswith(u",") : 106 | text_result = text_result[:-1] 107 | 108 | return text_result 109 | else : 110 | print "识别结果错误!错误码为:%d" %err_no 111 | 112 | return None 113 | 114 | ############################### 115 | 116 | 117 | ################################# 118 | 119 | #输入文本到此函数,返回百度合成的语音文件(合成的语音文件将被保存成tts_cache.mp3文件) 120 | #返回TTS合成的MP3文件的路径 121 | def Text_To_Speech(target_text) : 122 | 123 | if not target_text == None : 124 | #构建表单 125 | tts_form = { 126 | 'tex' : target_text , 127 | 'lan' : 'zh' , #语言选择 --选填(可以不填) 128 | 'tok' : access_token, 129 | 'ctp' : 1 , #客户端类型选择 130 | 'cuid' : my_mac , 131 | #以下为选填的项目 132 | 'spq' : 5 , #语速 133 | 'pit' : 5 , #语调 134 | 'vol' : 5 , #音量 135 | 'per' : 0 #发音人选择,0为女声,1为男声 136 | } 137 | 138 | #提交数据给baidu_tts 139 | try : 140 | tts_result_page = requests.post(baidu_tts_api_url, data=tts_form, timeout=2) 141 | 142 | except requests.exceptions.Timeout : 143 | print "baidu_tts段数据POST超时" 144 | 145 | return None 146 | #获取响应头 147 | tts_result_headers = tts_result_page.headers 148 | 149 | #合成错误的话 150 | if tts_result_headers['Content-Type'] == 'application/json' : 151 | tts_result_error_dict =tts_result_page.json() 152 | print "合成错误!错误码为:%d" %tts_result_error_dict['err_no'] 153 | 154 | return None 155 | 156 | elif tts_result_headers['Content-Type'] == 'audio/mp3' : 157 | #获取合成文件的二进制数据 158 | tts_mp3_data = tts_result_page.content 159 | with open('./tts_cache.mp3', 'wb') as tts_mp3_file : 160 | tts_mp3_file.write(tts_mp3_data) 161 | 162 | #如合成成功,则返回合成文件的路径,反之则返回None 163 | return './tts_cache.mp3' 164 | 165 | else : 166 | return None 167 | ################################# 168 | 169 | 170 | if __name__ == "__main__": 171 | print access_token 172 | print Speech_To_Text("./test.wav") 173 | print Text_To_Speech("你叫什么名字?") 174 | -------------------------------------------------------------------------------- /images/topo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enify/raspi-bot/f73925f468ad4c22294ba12499b6db4ef3399b11/images/topo.png -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | 使用本程序前应先安装mpg123,安装方法:sudo apt-get install mpg123 5 | 6 | 本程序是语音机器人项目的主代码,其功能为语音识别--获取机器人对话-- 7 | 语音合成 8 | """ 9 | 10 | import os 11 | import time 12 | import threading 13 | import requests 14 | #读取配置文件的模块 15 | from configobj import ConfigObj 16 | #树莓派独有模块 17 | import RPi.GPIO as GPIO 18 | 19 | #引入有颜色的输出的模块 20 | from Print_With_Style_Module import * 21 | 22 | 23 | ##检测配置文件是否存在 24 | if os.path.exists('./Config.ini') == False : 25 | Print_With_Style("配置文件Config.ini不存在!", 'r+B') 26 | exit(0) 27 | 28 | ##检测arecord录音程序是否存在 29 | if os.path.exists("./arecord") == False : 30 | Print_With_Style("arecord程序不存在!请将程序放到此脚本路径下", 'r+B') 31 | exit(0) 32 | 33 | ##检测是否有网 34 | net_test = requests.get('http://www.baidu.com') 35 | if net_test.encoding == None : 36 | Print_With_Style("网络异常!是不是没有连接上互联网?", 'r+B') 37 | exit(0) 38 | 39 | ##检测下位机是否连接(连接了的话会生成一个用于通信socket对象) 40 | #这一部分由Chack_Commands_Module模块来做 41 | #××××××××××××× 42 | 43 | 44 | #外部的模块 45 | from baidu_voice_Module import * 46 | from tuling_robot_Module import * 47 | from xiaoice_robot_Module import * 48 | from Speech_Command_Module import * 49 | from Chack_Commands_Module import * 50 | 51 | 52 | #配置类对象,所有的配置都保存在此对象中 53 | Config = ConfigObj('./Config.ini', encoding= 'utf-8') 54 | 55 | 56 | 57 | Lu_Yin_Pin = int(Config['Pin']['Lu_Yin_Pin']) #引发中断的按键引脚 58 | Lu_Yin_State_Pin = int(Config['Pin']['Lu_Yin_State_Pin']) #显示录音状态的引脚 59 | Lu_Yin_Flag = False #正在录音吗 60 | New_speech_Flag = False #这个录音文件是不是新的录音文件(未识别过的) 61 | previous_time = 0.0 #和current_time配对,目的是让录音事件在一定时间内只能触发一次 62 | 63 | 64 | #设置录音指示灯的状态的函数 65 | def Set_The_State_Light(lu_yin_flag) : 66 | #print "我是设置指示灯的函数,当前lu_yin_flag为%s" %lu_yin_flag 67 | pin_state = GPIO.input(Lu_Yin_State_Pin) #检测输出引脚现在的状态 68 | if pin_state == lu_yin_flag : 69 | pass 70 | else : 71 | GPIO.output(Lu_Yin_State_Pin, lu_yin_flag) 72 | 73 | 74 | 75 | ## 76 | #第二线程上的函数(录音) 77 | def Lu_Yin() : 78 | global Lu_Yin_Flag 79 | global New_speech_Flag 80 | #print "我是录音函数,初始Lu_Yin_Flag为%s,New_speech_Flag为%s" %(Lu_Yin_Flag, New_speech_Flag) 81 | 82 | Lu_Yin_Flag = True 83 | print "开始录音..." 84 | os.system("./arecord -Dplughw:CARD=Device -fcd -c1 -twav -r8000 -Vmono ./speech_cache.wav") 85 | Lu_Yin_Flag = False 86 | New_speech_Flag = True #新建了一个录音文件 87 | print "(自动)结束录音" 88 | #print "我是录音函数,处理后Lu_Yin_Flag为%s,New_speech_Flag为%s" %(Lu_Yin_Flag, New_speech_Flag) 89 | 90 | 91 | #建立中断的回调函数(会调用第二线程录音) 92 | def Hui_Diao(Lu_Yin_Pin) : 93 | global Lu_Yin_Flag 94 | global New_speech_Flag 95 | global previous_time 96 | #print "我是回调函数,初始Lu_Yin_Flag为%s,New_speech_Flag为%s" %(Lu_Yin_Flag, New_speech_Flag) 97 | 98 | 99 | current_time = time.time() 100 | #print "我是回调函数,初始previous_time为%f,current_time为%f" %(previous_time,current_time) 101 | if current_time - previous_time > 1 : #1为阀值,保证下面的程序在1秒内只能触发一次 102 | previous_time = current_time 103 | 104 | if Lu_Yin_Flag == True : #检测现在是否在录音,如果在录音的话就终止它(用于在嘈杂的环境下手动停止) 105 | os.system("pkill arecord") 106 | Lu_Yin_Flag = False 107 | New_speech_Flag = True #新建了一个录音文件 108 | print "(手动)结束录音" 109 | else : 110 | t1=threading.Thread(target=Lu_Yin, args=()) 111 | t1.setDaemon(False) #关闭守护线程,也就是说录音结束后主线程才会结束 112 | t1.start() #开启第二线程录音 113 | ## 114 | 115 | ### 116 | #第三线程上的函数(播放语音) 117 | def Play_Mp3() : 118 | #不知道为什么直接播放mp3会出现问题,所以将mp3转换成wav再播放 119 | os.system('mpg123 -q -w ./tts_cache.wav ./tts_cache.mp3') 120 | os.system('aplay -Dplughw:CARD=Device ./tts_cache.wav') 121 | print "播放语音数据完毕" 122 | 123 | 124 | #第三线程建立--播放语音的函数(建立播放线程) 125 | def Play_Speech(tts_path) : 126 | if not tts_path == None : 127 | t2=threading.Thread(target=Play_Mp3, args=()) 128 | t2.setDaemon(False) #关闭守护线程 129 | t2.start() #开启第三线程播放语音 130 | 131 | ### 132 | 133 | 134 | #### 135 | #第四线程上的函数(检查并执行命令文本中的命令语句) 136 | def Chack_And_Do_Commands() : 137 | while True : 138 | reply = Chack_Commands() 139 | if not reply == None : 140 | Print_With_Style(reply, 'g+B') 141 | tts_result_path = Text_To_Speech(reply) 142 | print "开始播放语音" 143 | Play_Speech(tts_result_path) #播放语音 144 | #第四线程建立 145 | t3 = threading.Thread(target=Chack_And_Do_Commands, args=() ) 146 | t3.setDaemon(True) #打开守护线程,当主线程结束时会带着这个线程结束 147 | t3.start() 148 | 149 | #### 150 | 151 | 152 | 153 | #与机器人对话的函数,输入一个文本,合成并播放回复语 154 | def Robot_Chat(speech_text_result) : 155 | global Config 156 | 157 | if Config['Config']['robot'] == 'Xiaoice' : #选择小冰与你聊天 158 | chat_text_result = Get_Weibo_Xiaoice_Result(speech_text_result) 159 | else : #选择图灵与你聊天 160 | chat_text_result = Get_Tuling_Result(speech_text_result) 161 | #语音合成 162 | tts_result_path = Text_To_Speech(chat_text_result) 163 | print "开始播放语音" 164 | Play_Speech(tts_result_path) #播放语音 165 | 166 | return chat_text_result 167 | 168 | 169 | #设置GPIO 170 | GPIO.setmode(GPIO.BOARD) 171 | GPIO.setup(Lu_Yin_State_Pin, GPIO.OUT) 172 | GPIO.setup(Lu_Yin_Pin, GPIO.IN, pull_up_down=GPIO.PUD_UP) 173 | #添加回调事件 174 | GPIO.add_event_detect(Lu_Yin_Pin, GPIO.FALLING, callback=Hui_Diao, bouncetime=20) 175 | 176 | 177 | #主线上的程序 178 | def Main_Code() : 179 | global New_speech_Flag 180 | 181 | 182 | while True : 183 | #设置指示灯的状态 184 | Set_The_State_Light(Lu_Yin_Flag) 185 | #如果新录了一个音的话 186 | if New_speech_Flag == True : 187 | #语音识别 188 | speech_text = Speech_To_Text("./speech_cache.wav") 189 | Print_With_Style(speech_text, 'g+B') 190 | #分析语音命令并把它写入文本 191 | append_command = Append_Speech_Command(speech_text) 192 | #是否要调用机器人来进行语音对话 193 | if append_command == True : #有命令就由Chack_Commands()函数来回复并播放 194 | pass 195 | else : 196 | chat_text = Robot_Chat(speech_text) #没命令就由机器人来回复并播放 197 | Print_With_Style(chat_text, 'g+B') 198 | ''' 199 | #语音合成 200 | tts_result_path = Text_To_Speech(chat_text) 201 | print "开始播放语音" 202 | Play_Speech(tts_result_path) #播放语音 203 | ''' 204 | New_speech_Flag = False #录音识别完成,不再是新文件了 205 | time.sleep(0.05) 206 | 207 | 208 | 209 | 210 | 211 | 212 | try : 213 | #主线程序 214 | Main_Code() 215 | 216 | finally : 217 | #将命令语句写回文本,这样语句的时间未到就不会被清除 218 | Write_Commands_Back_To_File() 219 | #删除缓存文件 220 | for cache_path in ['speech_cache.wav', 'tts_cache.wav', 'tts_cache.mp3'] : 221 | if os.path.exists(cache_path) : 222 | os.remove(cache_path) 223 | #释放Pin 224 | GPIO.cleanup(Lu_Yin_Pin) 225 | GPIO.cleanup(Lu_Yin_State_Pin) 226 | #释放socket(这两个对象在Chack_Commands_Module里) 227 | ESP8266Sock.close() 228 | tcpServerSock.close() 229 | 230 | -------------------------------------------------------------------------------- /resource/32位专用aplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enify/raspi-bot/f73925f468ad4c22294ba12499b6db4ef3399b11/resource/32位专用aplay -------------------------------------------------------------------------------- /resource/64位专用aplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enify/raspi-bot/f73925f468ad4c22294ba12499b6db4ef3399b11/resource/64位专用aplay -------------------------------------------------------------------------------- /resource/树莓派专用的改进的aplay: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/enify/raspi-bot/f73925f468ad4c22294ba12499b6db4ef3399b11/resource/树莓派专用的改进的aplay -------------------------------------------------------------------------------- /tuling_robot_Module.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | """ 4 | 图灵机器人聊天的模块 5 | 6 | 包含一个函数: 7 | Get_Tuling_Result(info) -----输入文本到此函数,返回图灵回应的文本 8 | """ 9 | import requests 10 | import json 11 | import uuid 12 | 13 | #读取配置文件的模块 14 | from configobj import ConfigObj 15 | #配置文件对象 16 | Config = ConfigObj('./Config.ini', encoding= 'utf-8') 17 | 18 | 19 | 20 | #图灵的API 地址 21 | tuling_api_url ="http://www.tuling123.com/openapi/api" 22 | #本机MAC,用作用户标识 23 | my_mac = uuid.UUID(int = uuid.getnode()).hex[-12:] 24 | 25 | #输入文本,获取图灵机器人对你的回复 26 | def Get_Tuling_Result(info) : 27 | global Config 28 | 29 | if not info == None : 30 | #构建json数据 31 | tuling_dict_data = { 32 | 'key' : Config['Tuling']['API']['tuling_api_key'] , 33 | 'info' : info , #请求的内容 34 | 'loc' : '' , #位置信息 35 | 'userid' : my_mac 36 | } 37 | 38 | tuling_json_data = json.dumps(tuling_dict_data).encode('utf-8') 39 | 40 | #发送数据 41 | try : 42 | tuling_result_page =requests.post(tuling_api_url, data=tuling_json_data, timeout=2) 43 | 44 | except requests.exceptions.Timeout : 45 | print "图灵POST超时" 46 | 47 | return None 48 | #JSON转换为dict 49 | tuling_dict_result = tuling_result_page.json() 50 | #标识码 51 | tuling_result_code = tuling_dict_result['code'] 52 | if tuling_result_code in [40001, 40002, 40004, 40007] : 53 | print "图灵数据已POST,但是出现异常,异常码为:%d" %tuling_result_code 54 | 55 | return None 56 | elif tuling_result_code in [200000, 302000, 308000] : 57 | print "图灵返回结果为链接类/新闻类/菜谱类,本程序将不会处理和返回结果" 58 | 59 | return None 60 | elif tuling_result_code == 100000 : 61 | tuling_text_result = tuling_dict_result['text'].encode('utf-8') 62 | 63 | return tuling_text_result 64 | else : 65 | return None 66 | 67 | 68 | ####################### 69 | if __name__ == "__main__": 70 | print Get_Tuling_Result("你好") -------------------------------------------------------------------------------- /xiaoice_robot_Module.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | 3 | """ 4 | 小冰机器人聊天的模块 5 | 包含一个函数: 6 | Get_Weibo_Xiaoice_Result(info) ---输入文本到此函数,返回小冰回应的文本 7 | """ 8 | 9 | import requests 10 | import requests.utils 11 | from lxml import etree 12 | 13 | from os import system as os_system 14 | import os.path 15 | import json 16 | from time import sleep 17 | 18 | #以下模块可以依情况而调用(当cookies过期无法直接登录时) 19 | from PIL import Image 20 | from StringIO import StringIO 21 | 22 | 23 | #读取配置文件的模块 24 | from configobj import ConfigObj 25 | #配置文件对象 26 | Config = ConfigObj('./Config.ini', encoding= 'utf-8') 27 | 28 | ########################################### 29 | #检查本地保存的cookies可不可用,不可用则返回False, 30 | #可用则返回已装载cookies的会话实例 31 | def Check_Weibo_Cookies(weibo_cookies_path) : 32 | 33 | if os.path.exists(weibo_cookies_path) == False : 34 | print "当前cookies文件不存在" 35 | 36 | return False 37 | 38 | else : 39 | #cookies文件存在,下面检测它是否还有用 40 | with open(weibo_cookies_path, 'r') as cookies_file : 41 | #装载成dict 42 | cookies_dict = json.load(cookies_file) 43 | #用于检测的会话对象 44 | check_session = requests.Session() 45 | #加载cookies到会话 46 | check_session.cookies = requests.utils.cookiejar_from_dict(cookies_dict) 47 | #开始测试cookies 48 | check_page = check_session.get('http://weibo.cn') 49 | #没有登录的话会跳转到微博广场(http://weibo.cn/pub/),登录了的话则不会跳转 50 | if check_page.url == 'http://weibo.cn/pub/' : 51 | print "cookies已失效!" 52 | 53 | return False 54 | 55 | elif check_page.url == 'http://weibo.cn/' : 56 | #print "cookies测试有效!" 57 | 58 | #返回实例 59 | return check_session 60 | 61 | 62 | ########################################### 63 | #本程序返回一个可用的会话实例(它会判断是否需要登录) 64 | def Get_Weibo_Session() : 65 | global Config 66 | 67 | weibo_session = Check_Weibo_Cookies('./weibo_xiao_ice_cookie.txt') 68 | 69 | #如果cookies已失效,则再次登录以获取一个新的可用的会话 70 | if weibo_session == False : 71 | #尝试6次,若六次都没获取到则退出程序,提示用户检查 72 | for i in range(6) : 73 | weibo_session = requests.Session() 74 | #微博登陆页的URL,从这里可以获得数据POST的一些信息 75 | weibo_login_page_url = 'http://login.weibo.cn/login/' 76 | 77 | #获取登录页 78 | weibo_login_page = weibo_session.get(weibo_login_page_url) 79 | 80 | 81 | #直接解析会报错,故用二进制数据生成etree 82 | weibo_login_page_etree = etree.HTML(weibo_login_page.content) 83 | 84 | #开始获取登录信息 85 | 86 | #POST网址--将登录信息POST到这个网址 87 | weibo_post_url = weibo_login_page_url + weibo_login_page_etree.xpath('/html/body/div[2]/form/@action')[0] 88 | #password名称 89 | weibo_post_password_name = weibo_login_page_etree.xpath('/html/body/div[2]/form/div/input[2]/@name')[0] 90 | #验证码地址 91 | weibo_yzm_url = weibo_login_page_etree.xpath('/html/body/div[2]/form/div/img[1]/@src')[0] 92 | #vk值 93 | weibo_post_vk_value = weibo_login_page_etree.xpath('/html/body/div[2]/form/div/input[8]/@value')[0] 94 | #capId值 95 | weibo_post_capId_value = weibo_login_page_etree.xpath('/html/body/div[2]/form/div/input[9]/@value')[0] 96 | 97 | #获取验证码 98 | weibo_yzm_page = weibo_session.get(weibo_yzm_url) 99 | 100 | weibo_yzm_img = Image.open(StringIO(weibo_yzm_page.content)) 101 | #显示验证码 102 | weibo_yzm_img.show() 103 | 104 | #>>>提示用户输入验证码 105 | yzm_code = raw_input("\033[92m请输入你看到的验证码(注意切换窗口焦点)\033[1m>>>\033[0m") 106 | #关闭显示验证码的窗口(即名为'display'的进程) 107 | os.system('killall display') 108 | 109 | ###########以下开始POST数据 110 | 111 | #构建请求头 112 | login_post_headers = { 113 | 'User-Agent' : 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:49.0) Gecko/20100101 Firefox/49.0' 114 | } 115 | 116 | #构建表单数据 117 | login_post_form = { 118 | 'mobile' : Config['Xiaoice']['API']['mobile'] , #这里填手机号 119 | weibo_post_password_name : Config['Xiaoice']['API']['password'] , #这里填密码 120 | 'code' : yzm_code , #这里填验证码 121 | 'remember' : 'on' , 122 | 'backURL' : 'http%3A%2F%2Fweibo.cn' , 123 | 'backTitle' : u'手机新浪网' , 124 | 'tryCount' : ' ', 125 | 'vk' : weibo_post_vk_value , 126 | 'capId' : weibo_post_capId_value , 127 | 'submit' : u'登录' 128 | } 129 | 130 | #POST后的页面 131 | weibo_after_post_page = weibo_session.post(weibo_post_url, headers= login_post_headers, data= login_post_form) 132 | 133 | 134 | if weibo_after_post_page.url[7:12] == 'weibo' : 135 | print "Weibo登录成功!" 136 | 137 | """ 138 | 保存cookie为json数据 139 | """ 140 | #保存cookie为json数据 141 | with open('./weibo_xiao_ice_cookie.txt' , 'w') as weibo_xiao_ice_cookie : 142 | json.dump(requests.utils.dict_from_cookiejar(weibo_session.cookies), weibo_xiao_ice_cookie) 143 | 144 | return weibo_session 145 | 146 | 147 | elif weibo_after_post_page.url[7:12] == 'login' : 148 | print "Weibo登录失败!" , 149 | weibo_after_post_page_etree = etree.HTML(weibo_after_post_page.content) 150 | 151 | #登录失败的原因 152 | weibo_login_error_code = weibo_after_post_page_etree.xpath('/html/body/div[2]')[0].text 153 | print '\033[31m' + weibo_login_error_code + '\033[0m' 154 | 155 | print "正在尝试重新登录。。。" 156 | print "第六次尝试登录后失败,请检查代码!" 157 | exit(0) 158 | else : 159 | return weibo_session 160 | 161 | 162 | #本程序是从微博上获取小冰回复的主程序,输入为一个文本 163 | def Get_Weibo_Xiaoice_Result(info) : 164 | 165 | if not info == None : 166 | 167 | #获取一个已经是登录状态的会话 168 | xiao_ice_session = Get_Weibo_Session() 169 | #注:与小冰聊天的界面网址为http://weibo.cn/im/chat?uid=5175429989&rl=1(固定值,不分帐号) 170 | xiaoice_chat_page_url = 'http://weibo.cn/im/chat?uid=5175429989&rl=1' 171 | 172 | xiaoice_chat_page = xiao_ice_session.get(xiaoice_chat_page_url) 173 | #下面获取聊天数据的POST地址 174 | xiaoice_chat_page_etree = etree.HTML(xiaoice_chat_page.content) 175 | 176 | xiaoice_chat_data_post_url = 'http://weibo.cn' + xiaoice_chat_page_etree.xpath('/html/body/form/@action')[0] 177 | 178 | 179 | #print xiaoice_chat_data_post_url 180 | 181 | 182 | #构建发送聊天数据的表单 183 | xiaoice_chat_form = { 184 | 'content' : info , 185 | 'rl' : '2' , 186 | 'uid' : '5175429989' , 187 | 'send' : u'发送' 188 | } 189 | #构建发送聊天数据的请求头 190 | xiaoice_chat_headers = { 191 | 'User-Agent' : 'Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:49.0) Gecko/20100101 Firefox/49.0' 192 | } 193 | 194 | #发送聊天数据 195 | xiao_ice_session.post(xiaoice_chat_data_post_url, headers=xiaoice_chat_headers, data= xiaoice_chat_form) 196 | #一直try,直到微博返回包含信息的页面(尝试10次) 197 | for i in range(10) : 198 | #等待0.2秒 199 | sleep(0.2) 200 | try : 201 | #获取包含回复的页面(刷新) 202 | xiaoice_chat_page = xiao_ice_session.get(xiaoice_chat_page_url) 203 | 204 | xiaoice_chat_page_etree = etree.HTML(xiaoice_chat_page.content) 205 | 206 | 207 | #抓取聊天的回复[1:]用于去掉引号 208 | xiaoice_chat_text_result = xiaoice_chat_page_etree.xpath('/html/body/div[3]/img[2]')[0].tail[1:] 209 | #回复为空的话 210 | if len(xiaoice_chat_text_result) == 0 : 211 | continue 212 | except IndexError : #获取不到回复的话 213 | #跳过此次循环的后半部分 214 | continue 215 | 216 | #print "我是len" 217 | #print len(xiaoice_chat_text_result) 218 | #如果没有错误则会返回 219 | if xiaoice_chat_text_result == u'分享语音[' : 220 | xiaoice_chat_text_result = u'小冰给你分享了一段语音,暂时无法查看' 221 | elif xiaoice_chat_text_result == u'分享图片[' : 222 | xiaoice_chat_text_result = u'小冰给你分享了一张图片,暂时无法查看' 223 | 224 | return xiaoice_chat_text_result 225 | 226 | print "远程服务器错误,获取不到回复" 227 | return None 228 | else : 229 | return None 230 | 231 | 232 | 233 | if __name__ == "__main__": 234 | print Get_Weibo_Xiaoice_Result("你好") -------------------------------------------------------------------------------- /下位机-ESP8266端程序.py: -------------------------------------------------------------------------------- 1 | """ 2 | 本程序为树莓派语音机器人项目中下位机(ESP8266-MicroPython)的代码 3 | Starttime :2016-12-5 4 | """ 5 | #注意代码要以Python3.x的风格写 6 | import time 7 | import socket 8 | import network 9 | from machine import Pin 10 | from machine import reset as machine_reset 11 | 12 | #定义引脚(依据GPIO) 13 | JDQ1_Pin = 4 14 | JDQ2_Pin = 0 15 | JDQ3_Pin = 2 16 | JDQ4_Pin = 15 17 | 18 | #将引脚设置为输出 19 | jdq1 = Pin(JDQ1_Pin, Pin.OUT) 20 | jdq2 = Pin(JDQ2_Pin, Pin.OUT) 21 | jdq3 = Pin(JDQ3_Pin, Pin.OUT) 22 | jdq4 = Pin(JDQ4_Pin, Pin.OUT) 23 | #设置初始值为关闭 24 | jdq1.high() 25 | jdq2.high() 26 | jdq3.high() 27 | jdq4.high() 28 | 29 | 30 | #通信部分 31 | HOST = '192.168.1.100' #树莓派(主机)的地址 32 | PORT = 21567 #主机端口 33 | BUFSIZ = 1024 #缓冲区大小 34 | ADDR = (HOST, PORT) 35 | 36 | tcpSock = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 37 | tcpSock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR, 1) 38 | 39 | wlan = network.WLAN(network.STA_IF) 40 | 41 | 42 | try : 43 | while not wlan.isconnected() : 44 | time.sleep(2) #等待wifi连接 45 | 46 | tcpSock.connect(ADDR) #连接主机,阻塞 47 | print("已连接上位机") 48 | while True : 49 | recv_data = tcpSock.recv(BUFSIZ) #接收数据,阻塞 50 | if not len(recv_data) == 0 : 51 | recv_data = recv_data.decode() #bytes=>string 52 | ################## 53 | #下面对从上位机接收到的数据进行处理 54 | ON_OFF_Flag = -1 55 | recv_data = recv_data.strip() #移除字符串末尾的换行符 56 | print(recv_data) 57 | 58 | command_list = recv_data.split("_") 59 | if command_list[0] == "ON" : 60 | ON_OFF_Flag = 0 #继电器为低电平触发 61 | elif command_list[0] == "OFF" : 62 | ON_OFF_Flag = 1 63 | 64 | if "jdq1" in command_list : 65 | jdq1.value(ON_OFF_Flag) 66 | if "jdq2" in command_list : 67 | jdq2.value(ON_OFF_Flag) 68 | if "jdq3" in command_list : 69 | jdq3.value(ON_OFF_Flag) 70 | if "jdq4" in command_list : 71 | jdq4.value(ON_OFF_Flag) 72 | 73 | except Exception as e: 74 | tcpSock.close() 75 | print(e) 76 | time.sleep(3) 77 | machine_reset() #连接不上就重启(以重新连接) 78 | -------------------------------------------------------------------------------- /说明.txt: -------------------------------------------------------------------------------- 1 | Main.py为主程序,包括语音识别,语音命令,机器人对话,语音合成的功能,依赖于下面的功能模块: 2 | 3 | 1.baidu_voice_Module ----百度语音识别/语音合成 4 | ---1)Speech_To_Text(speech_path) -----输入speech文件的路径,返回得识别结果(列表形式) 5 | ----2)Text_To_Speech(target_text) -----输入文本,返回合成的speech文件的路径 6 | 7 | 2.tuling_robot_Module ----图灵机器人聊天的模块 8 | ----1)Get_Tuling_Result(info) -----输入文本到此函数,返回图灵回应的文本 9 | 10 | 3.xiaoice_robot_Module ----小冰机器人聊天的模块 11 | ----1)Get_Weibo_Xiaoice_Result(info) ---输入文本到此函数,返回小冰回应的文本 12 | 13 | 4.Print_With_Style_Module ----带格式的打印 14 | ----1)Print_With_Style(p_str, p_style) ----输入要打印的字符与格式即可按格式打印字符串 15 | 16 | 5.WOL_Module ----WOL(网络唤醒)的模块 17 | ----1)Wake_Up_Computer() -------唤醒计算机,唤醒的机器由Config.ini决定 18 | 19 | 6.Speech_Command_Module ------本模块为处理语音命令的模块,用于将语音文本内的部分命令放入到command_temp.txt文本中,再由Chack_Commands模块检查并执行 20 | ----1)Append_Speech_Command(speech_text) -----输入语音文本,生成命令语句并放到command_temp.txt文本中 21 | 7.Time_Sign_Module ------从语音识别文本中获取时间标记的模块,主要被Speech_Command_Module模块调用,用于生成命令语句的时间部分 22 | ----1)Get_Time_Sign(speech_text) ------获取时间标记--它是一个目标时间datetime对象,无时间标记时返回None 23 | 8.Chack_Commands_Module ------检查并执行command_temp.txt文本中的命令语句,并且返回回复语 24 | ----1)Chack_Commands() -------检查并执行命令语句的函数,它需要在线程内不断执行以及时响应 25 | ----2)Write_Commands_Back_To_File(),用在finally中,遇到错误时把命令写回文本中 26 | 9. 27 | 28 | 29 | 30 | 支持的语音命令: 31 | 例:一个小时后叫我起床 32 | 十九点三十八叫我吃饭 ("时间点"可以不包含单位"分") 33 | 晚上七点三十五分叫我吃饭 34 | 明天的这个时候叫我起床 35 | 一小时三十八秒后打开电灯 ("一段时间后"需要包含时间单位,如"小时","分/分钟","秒") 36 | 设一个明天早上七点十分的闹钟 37 | 设一个下午七点半的闹钟 ("设"不是必要的) 38 | 39 | --------------------------------------------------------------------------------