dever 4 years ago
parent
commit
101a8a3462

+ 10 - 6
conf/dev.conf

@@ -50,9 +50,12 @@ active_mode			= True
 [robot]
 asr					= tencent
 ai 					= tuling
-tts 				= baidu
+tts 				= tencent
 nlu 				= baidu
 
+[espeak]
+appid				= 1
+
 [tencent]
 ;appid				= 1252854083
 ;secret_id			= AKIDmQvEPYRP1Rh0CGdLF1b3MTsaBfnjQ5cP
@@ -61,7 +64,7 @@ nlu 				= baidu
 appid				= 1253537070
 secret_id			= AKID7C7JK9QomcWJUjcsKbK8iLQjhju8fC3z
 secret_key			= 2vhKRVSn4mXQ9PiT7eOtBqQhR5Z6IvPn
-region				= ap-guangzhou
+region				= ap-beijing
 voiceType 			= 0
 language			= 1
 
@@ -70,13 +73,14 @@ appid				= 9670645
 api_key				= qg4haN8b2bGvFtCbBGqhrmZy
 secret_key			= 585d4eccb50d306c401d7df138bb02e7
 dev_pid				= 1936
-per 				= 1
+voice 				= 2
+per 				= 4
 lan 				= zh
 
 [xunfei]
-appid				= 5c540c39
-api_key				= 859bc21e90c64d97fae77695579eb05e
-voice_name 			= xiaoyun
+appid				= 5d1ec5f9
+api_key				= 2381d523ea042f632b2db844dd9896a1
+voice_name 			= aisxping
 
 [ali]
 appKey				= x04ujKJ7oRvDgt6h

+ 1 - 1
install.py

@@ -1,7 +1,7 @@
 # -*- coding: utf-8 -*-
 from demeter.core import *
 
-Shell.popen('apt-get install python-pyaudio python3-pyaudio sox pulseaudio libsox-fmt-all ffmpeg libatlas-base-dev libsox-fmt-mp3 && pip install pydub pyaudio baidu-aip python-dateutil')
+Shell.popen('apt-get install python-pyaudio python3-pyaudio sox pulseaudio libsox-fmt-all ffmpeg libatlas-base-dev libsox-fmt-mp3 && pip install pydub pyaudio baidu-aip hmac python-dateutil pyttsx3')
 
 #CREATE DATABASE IF NOT EXISTS yourdbname DEFAULT CHARSET utf8 COLLATE utf8_general_ci;
 def manage():

+ 1 - 6
service/__init__.py

@@ -1,6 +1 @@
-# -*- coding: utf-8 -*-
-"""
-    demeter init
-    author:rabin
-    实现具体的业务逻辑,前台、后台、接口都走这一套逻辑,此处可以直接生成接口
-"""
+# -*- coding: utf-8 -*-

+ 1 - 0
service/__load__.py

@@ -11,6 +11,7 @@ import os.path
 import wave
 import tempfile
 import cProfile
+import base64
 from pydub import AudioSegment
 from uuid import getnode as get_mac
 from abc import ABCMeta, abstractmethod

+ 8 - 2
service/api/baidu.py

@@ -29,11 +29,12 @@ class Baidu(Base):
 	def __init__(self):
 		super(self.__class__, self).__init__(True)
 
-	def setting(self, appid, api_key, secret_key, dev_pid=1936, per=1, lan='zh', **args):
+	def setting(self, appid, api_key, secret_key, dev_pid=1936, voice=2, per=1, lan='zh', **args):
 		self.engine = AipSpeech(appid, api_key, secret_key)
 		self.dev_pid = dev_pid
 		self.per = per
 		self.lan = lan
+		self.voice = voice
 
 	def asr(self, fp):
 		pcm = getPcmFromWav(fp)
@@ -48,7 +49,12 @@ class Baidu(Base):
 			return self.arrLog(2, res['err_msg'])
 
 	def tts(self, phrase):
-		result  = self.engine.synthesis(phrase, self.lan, 1, {'per': self.per});
+		result  = self.engine.synthesis(phrase, self.lan, self.voice, {
+                                  'vol': 5,
+                                  'spd': 3,
+                                  'pit': 8,
+                                  'per': self.per
+                              });
 		# 识别正确返回语音二进制 错误则返回dict 参照下面错误码
 		if not isinstance(result, dict):
 			tmpfile = writeTempFile(result, '.mp3')

+ 19 - 0
service/api/espeak.py

@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+"""
+pyttsx3
+"""
+from __future__ import division
+from ..__load__ import *
+from base import Base
+
+class Espeak(Base):
+
+	def __init__(self):
+		super(self.__class__, self).__init__()
+
+	def setting(self, appid, **args):
+		self.appid = appid
+
+	def tts(self, phrase):
+		filename = Demeter.md5(phrase)
+		Popen.shell('espeak -v zh "'+phrase+'" --stdout > '+filename+'.mp3')

+ 1 - 1
service/api/tencent.py

@@ -44,7 +44,7 @@ class Tencent(Base):
 		if 'Response' in result and 'Audio' in result['Response']:
 			audio = result['Response']['Audio']
 			data = base64.b64decode(audio)
-			tmpfile = writeTempFile(data, '.wav')
+			tmpfile = writeTempFile(data, '.mp3')
 			return self.ttsLog(1, tmpfile)
 		else:
 			return self.ttsLog(2, '')

+ 13 - 15
service/api/xunfei.py

@@ -7,8 +7,10 @@ voice_name: https://www.xfyun.cn/services/online_tts
 from __future__ import division
 from ..__load__ import *
 from base import Base
+import requests
+import hashlib
 
-class Baidu(Base):
+class Xunfei(Base):
 
 	url = 'http://api.xfyun.cn/v1/service/v1/'
 	def __init__(self):
@@ -21,13 +23,12 @@ class Baidu(Base):
 
 	def getHeader(self, aue, engineType):
 		curTime = str(int(time.time()))
-		# curTime = '1526542623'
-		if aue = 'raw':
+		if aue == 'raw':
 			param = "{\"aue\":\"" + aue + "\"" + ",\"engine_type\":\"" + engineType + "\"}"
 		else:
 			param = "{\"aue\":\""+aue+"\",\"auf\":\"audio/L16;rate=16000\",\"voice_name\":\"" + self.voice_name + "\",\"engine_type\":\"intp65\"}"
 		Demeter.logger.debug("param:{}".format(param))
-		paramBase64 = str(base64.b64encode(param.encode('utf-8')), 'utf-8')
+		paramBase64 = base64.b64encode(param.encode('utf-8'))
 		Demeter.logger.debug("x_param:{}".format(paramBase64))
 
 		m2 = hashlib.md5()
@@ -42,24 +43,21 @@ class Baidu(Base):
 		}
 		return header
 
-	def getBody(self, filepath):
-		binfile = open(filepath, 'rb')
-		data = {'audio': base64.b64encode(binfile.read())}
-		return data
-
 	def asr(self, fp):
-		URL = self.url + "iat"
-		r = requests.post(URL, headers=self.getHeader('raw', 'sms16k'), data=self.getBody(fp))
+		url = self.url + "iat"
+		binfile = open(fp, 'rb')
+		data = {'audio': base64.b64encode(binfile.read())}
+		r = requests.post(url, headers=self.getHeader('raw', 'sms16k'), data=data)
 		res = json.loads(r.content.decode('utf-8'))
 		Demeter.logger.debug(res)
 		if 'code' in res and res['code'] == '0':
-			return self.log(1, res['data'])
+			return self.asrLog(1, res['data'])
 		else:
-			return self.log(2, '')
+			return self.asrLog(2, '')
 
 	def tts(self, phrase):
-		URL = self.url + "tts"
-		r = requests.post(URL, headers=self.getHeader('lame'), data=self.getBody(phrase))
+		url = self.url + "tts"
+		r = requests.post(url, headers=self.getHeader('lame', ''), data={'text':phrase})
 		contentType = r.headers['Content-Type']
 		if contentType == "audio/mpeg":
 			tmpfile = writeTempFile(r.content, '.mp3')

+ 166 - 0
service/brain.py

@@ -0,0 +1,166 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from .__load__ import *
+# 大脑,技能,记忆
+class Brain(object):
+
+	def init(self, robot):
+		# vecan机器人
+		self.robot = robot
+		# 记忆
+		self.memory = []
+		# 询问模式
+		self.hasPardon = False
+		# 插件
+		self.plugins = []
+		#self.plugins = plugin_loader.get_plugins(self.robot)
+		self.handling = False
+		return self
+
+	def isImmersive(self, plugin, text, parsed):
+		return self.robot.getImmersiveMode() == plugin.SLUG and \
+			plugin.isValidImmersive(text, parsed)
+
+	def printPlugins(self):
+		plugin_list = []
+		for plugin in self.plugins:
+			plugin_list.append(plugin.SLUG)
+		self.robot.logger.info(self.log('已激活插件:{}'.format(plugin_list)))
+
+	def query(self, text):
+		"""
+		query 模块
+
+		Arguments:
+		text -- 用户输入
+		"""
+
+		args = {
+			"service_id": "S13442",
+			"api_key": 'w5v7gUV3iPGsGntcM84PtOOM',
+			"secret_key": 'KffXwW6E1alcGplcabcNs63Li6GvvnfL'
+		}
+		parsed = self.robot.doParse(text, **args)
+		print parsed
+		return parsed
+
+		for plugin in self.plugins:
+			if not plugin.isValid(text, parsed) and not self.isImmersive(plugin, text, parsed):
+				continue
+
+			self.robot.logger.info(self.log("'{}' 命中技能 {}".format(text, plugin.SLUG)))
+			self.robot.matchPlugin = plugin.SLUG
+
+			if plugin.IS_IMMERSIVE:
+				self.robot.setImmersiveMode(plugin.SLUG)
+
+			continueHandle = False
+			try:
+				self.handling = True
+				continueHandle = plugin.handle(text, parsed)
+				self.handling = False				
+			except Exception:
+				self.robot.logger.critical('Failed to execute plugin',
+								   exc_info=True)
+				reply = u"抱歉,插件{}出故障了,晚点再试试吧".format(plugin.SLUG)
+				self.robot.say(reply, plugin=plugin.SLUG)
+			else:
+				self.robot.logger.debug(self.log("Handling of phrase '%s' by " +
+								   "plugin '%s' completed", text,
+								   plugin.SLUG))					
+			finally:
+				if not continueHandle:
+					return True
+
+		self.robot.logger.debug(self.log("No plugin was able to handle phrase {} ".format(text)))
+		return False
+
+	def restore(self):
+		""" 恢复某个技能的处理 """
+		if not self.robot.immersiveMode:
+			return
+		for plugin in self.plugins:
+			if plugin.SLUG == self.robot.immersiveMode and plugin.restore:
+				plugin.restore()
+
+	def pause(self):
+		""" 暂停某个技能的处理 """
+		if not self.robot.immersiveMode:
+			return
+		for plugin in self.plugins:
+			if plugin.SLUG == self.robot.immersiveMode and plugin.pause:
+				plugin.pause()
+
+	def understand(self, fp):
+		if self.robot and self.robot.tool['asr']:
+			return self.robot.tool['asr'].asr(fp)
+		return None
+
+	def say(self, msg, cache=False):
+		if self.robot and self.robot.tool['tts']:
+			self.robot.tool['tts'].tts(msg)
+
+	def think(self, query, uid='', onSay=None):
+		# 统计
+		#statistic.report(1)
+		self.robot.stop()
+		self.addMemory(0, query, uid)
+
+		if onSay:
+			self.onSay = onSay
+
+		if query.strip() == '':
+			self.pardon()
+			return
+
+		lastImmersiveMode = self.robot.immersiveMode
+
+		if not self.query(query):
+			# 没命中技能,使用机器人回复
+			msg = self.robot.tool['ai'].ai(query)
+			self.robot.say(msg, True, completed=self.robot.checkRestore)
+		else:
+			if lastImmersiveMode is not None and lastImmersiveMode != self.robot.matchPlugin:
+				time.sleep(1)
+				if self.checkSpeeker():
+					self.robot.logger.debug(self.log('等说完再checkRestore'))
+					self.speaker.appendOnCompleted(lambda: self.checkRestore())
+				else:
+					self.robot.logger.debug(self.log('checkRestore'))
+					self.robot.checkRestore()
+
+	def doubt(self, msg):
+		if Demeter.config['snowboy']['active_mode'] and (msg.endswith('?') or msg.endswith(u'?') or u'告诉我' in msg or u'请回答' in msg):
+			query = self.robot.ear.listen()
+			self.think(query)
+
+	def pardon(self):
+		if not self.hasPardon:
+			self.robot.say('抱歉,刚刚没听清,能再说一遍吗?', completed=lambda: self.response(self.robot.ear.listen()))
+			self.hasPardon = True
+		else:
+			self.robot.say('没听清呢')
+			self.hasPardon = False
+
+	def getMemory(self):
+		return self.memory
+
+	def addMemory(self, t, text, uid=''):
+		if t in (0, 1) and text != '':
+			if text.endswith(',') or text.endswith(','):
+				text = text[:-1]
+			if uid == '' or uid == None or uid == 'null':
+				uid = Demeter.uuid(__name__)
+			# 将图片处理成HTML
+			pattern = r'https?://.+\.(?:png|jpg|jpeg|bmp|gif|JPG|PNG|JPEG|BMP|GIF)'
+			url_pattern = r'^https?://.+'
+			imgs = re.findall(pattern, text)
+			for img in imgs:
+				text = text.replace(img, '<img src={} class="img"/>'.format(img))
+			urls = re.findall(url_pattern, text)
+			for url in urls:
+				text = text.replace(url, '<a href={} target="_blank">{}</a>'.format(url, url))
+			self.memory.append({'type': t, 'text': text, 'time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())), 'uid': uid})
+
+	def log(self, text):
+		return 'Brain:' + text

+ 32 - 26
service/snowboy.py → service/ear.py

@@ -1,22 +1,26 @@
 # -*- coding: utf-8 -*-
 from __future__ import division
 from .__load__ import *
-from snowboy import snowboydecoder
-class Snowboy(object):
+from .snowboy import snowboydecoder
+# 耳朵,聆听者
+class Ear(object):
 
 	def init(self, robot):
+		# vecan机器人
+		self.robot = robot
 		# 勿扰模式控制 勿扰模式关闭
 		self.bother = False
 		# snowboy detector
 		self.detector = None
 		# 中断控制
 		self.interrupted = False
-		# vecan机器人
-		self.robot = robot
-		self.hotword()
+		return self
 
-	# 启动hotword
-	def hotword(self):
+	# 语音唤醒模式
+	def wait(self):
+		print 22
+		return
+		self.robot.logger.debug(self.log('唤醒模式'))
 		if self.detector is not None:
 			self.detector.terminate()
 
@@ -25,29 +29,29 @@ class Snowboy(object):
 		# main loop
 		try:
 			self.detector.start(detected_callback=self.config['callbacks'],
-								audio_recorder_callback=self.robot.talk,
+								audio_recorder_callback=self.robot.mouth.talk,
 								interrupt_check=self.interruptCallback,
 								silent_count_threshold=int(self.config['silent_threshold']),
 								recording_timeout=int(self.config['recording_timeout']) * 4,
 								sleep_time=0.03)
 			self.detector.terminate()
 		except Exception as e:
-			Demeter.logger.critical('离线唤醒机制初始化失败:{}'.format(e))
+			self.robot.logger.critical(self.log('离线唤醒机制初始化失败:{}'.format(e)))
 
-	# active
+	# listen
 	def listen(self, silent=False):
-		Demeter.logger.debug('activeListen')
+		self.robot.logger.debug(self.log('聆听模式'))
 		try:
 			if not silent:
 				time.sleep(1)
-				self.robot.play(self.robot.data + 'beep_hi.wav')
+				self.robot.mouth.speek(self.robot.data + 'beep_hi.wav')
 			listener = snowboydecoder.ActiveListener([self.robot.data + self.config['hotword']], outname=Demeter.config['vecan']['outname'], temp=Demeter.config['vecan']['temp'])
 			voice = listener.listen(
 				silent_count_threshold=int(self.config['silent_threshold']),
 				recording_timeout=int(self.config['recording_timeout']) * 4
 			)
 			if not silent:
-				self.robot.play(self.robot.data + 'beep_lo.wav')
+				self.robot.mouth.speek(self.robot.data + 'beep_lo.wav')
 			if voice:
 				query = self.robot.tool['asr'].asr(voice)
 				Demeter.remove(voice)
@@ -75,40 +79,39 @@ class Snowboy(object):
 			self.config['models'] = self.robot.data + self.config['hotword']
 			self.config['callbacks'] = self.detectedCallback
 
-	# stop
-	def stop(self):
+	# close
+	def close(self):
 		self.interrupted = True
-		clean()
 
 	# callback
 	def detectedCallback(self):
 		if not self.isProperTime():
-			Demeter.logger.warning('勿扰模式开启中')
+			self.robot.logger.warning(self.log('勿扰模式开启中'))
 			return
 		if self.robot.isRecording:
-			Demeter.logger.warning('正在录音中,跳过')
+			self.robot.logger.warning(self.log('正在录音中,跳过'))
 			return
-		self.robot.player.play(self.robot.data + 'beep_hi.wav')
-		Demeter.logger.info('开始录音')
+		self.robot.mouth.speek(self.robot.data + 'beep_hi.wav')
+		self.robot.logger.info(self.log('开始录音'))
 		self.robot.interrupt()
 		self.robot.isRecording = True;
 
 	def onHotwordCallback(self):
 		if self.config['hotword_switch'] == True:
 			self.bother = True
-			self.robot.player.play(self.robot.data + 'off.wav')
-			Demeter.logger.info('勿扰模式打开')
+			self.robot.mouth.speek(self.robot.data + 'off.wav')
+			self.robot.logger.info(self.log('勿扰模式打开'))
 
 	def offHotwordCallback(self):
 		if self.config['hotword_switch'] == True:
 			self.bother = False
-			self.robot.player.play(self.robot.data + 'on.wav')
-			Demeter.logger.info('勿扰模式关闭')
+			self.robot.mouth.speek(self.robot.data + 'on.wav')
+			self.robot.logger.info(self.log('勿扰模式关闭'))
 
 	def interruptCallback(self):
 		return self.interrupted
 
-	def isProperTime():
+	def isProperTime(self):
 		if self.bother == True:
 			return False
 		if 'bother_enable' not in self.config:
@@ -124,4 +127,7 @@ class Snowboy(object):
 			return current not in range(since, till)
 		else:
 			return not (current in range(since, 25) or
-						current in range(-1, till))
+						current in range(-1, till))
+
+	def log(self, text):
+		return 'Ear:' + text

+ 12 - 0
service/eye.py

@@ -0,0 +1,12 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from .__load__ import *
+
+# 眼睛
+class Eye(object):
+
+	def init(self, robot):
+		self.robot = robot
+
+	def close():
+		pass

+ 95 - 0
service/mouth.py

@@ -0,0 +1,95 @@
+# -*- coding: utf-8 -*-
+from __future__ import division
+from .__load__ import *
+# 口 说话
+class Mouth(object):
+	tool = {}
+	def init(self, robot):
+		self.robot = robot
+		# 播放器
+		self.speaker = None
+		# 对话
+		self.onSay = None
+		return self
+
+	def say(self, msg, cache=False, plugin='', completed=None):
+		print msg
+		if self.onSay:
+			self.robot.logger.info(self.log('onSay: {}'.format(msg)))
+			if plugin != '':
+				self.onSay("[{}] {}".format(plugin, msg))
+			else:
+				self.onSay(msg)
+			self.onSay = None
+		if plugin != '':
+			self.robot.brain.addMemory(1, "[{}] {}".format(plugin, msg))
+		else:
+			self.robot.brain.addMemory(1, msg)
+		pattern = r'^https?://.+'
+		if re.match(pattern, msg):
+			self.robot.logger.info(self.log('内容包含URL,无法读取'))
+			return
+		voice = ''
+		voice = self.robot.tool['tts'].tts(msg)
+		if cache:
+			saveCache(voice, msg)
+		'''
+		if getCache(msg):
+			self.robot.logger.info(self.log('命中缓存,播放缓存语音'))
+			voice = getCache(msg)
+		else:
+			try:
+				voice = self.robot.tool['tts'].tts(msg)
+				if cache:
+					saveCache(voice, msg)
+			except Exception as e:
+				self.robot.logger.error(self.log('保存缓存失败:{}'.format(e)))
+		'''
+		if completed is None:
+			completed = lambda: self.robot.brain.doubt(msg)
+		self.speek(voice, not cache, completed=completed)
+
+	def speek(self, voice, delete=False, completed=None):
+		if self.speaker == None:
+			self.speaker = Demeter.service('sox', 'player')
+		self.speaker.play(voice, delete=delete, onCompleted=completed)
+
+	def checkSpeeker(self):
+		return self.speaker is not None and self.speaker.is_playing()
+
+	# 中断、停止
+	def close(self):
+		if self.checkSpeeker():
+			self.speaker.stop()
+			self.speaker = None
+
+	def talk(self, fp, callback=None):
+		self.play(self.robot.data + 'beep_lo.wav')
+		self.robot.logger.info(self.log('结束录音'))
+		self.robot.isRecording = False
+		if self.robot.profiling:
+			self.robot.logger.info(self.log('性能调试已打开'))
+			pr = cProfile.Profile()
+			pr.enable()
+			self.onTalk(fp, callback)
+			pr.disable()
+			s = io.StringIO()
+			sortby = 'cumulative'
+			ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
+			ps.print_stats()
+			print(s.getvalue())
+		else:
+			self.onTalk(fp, callback)
+
+	def onTalk(self, fp, callback=None, onSay=None):
+		try:
+			self.robot.stop()
+			query = self.robot.tool['asr'].asr(fp)
+			File.remove(fp)
+			self.robot.brain.think(query, callback, onSay)
+		except Exception as e:
+			self.robot.logger.critical(self.log(e))
+			clean()
+
+	def log(self, text):
+		return 'Mouth:' + text

+ 2 - 0
service/player/sox.py

@@ -16,6 +16,8 @@ class Sox(Base):
 	def run(self):
 		cmd = ['play', '-v', str(self.volume), str(self.src)]
 		self.logger.debug('Executing %s', ' '.join(cmd))
+
+		#self.proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
 		print cmd
 		return
 		self.proc = subprocess.Popen(cmd)

+ 34 - 175
service/robot.py

@@ -3,15 +3,23 @@ from __future__ import division
 from .__load__ import *
 
 class Robot(object):
-	tool = {}
 	def init(self, profiling=False):
-		self.start()
-		# 历史记录
-		self.history = []
+		# 大脑
+		self.brain = Demeter.service('brain').init(self)
+		# 耳朵
+		self.ear = Demeter.service('ear').init(self)
+		# 嘴
+		self.mouth = Demeter.service('mouth').init(self)
+		# 眼睛
+		self.eye = Demeter.service('eye').init(self)
+
+		# 行为工具
+		self.tool = {}
 		# 数据文件夹
 		self.data = Demeter.config['vecan']['data']
 		# 日志
 		self.logger = Log.init(__name__)
+
 		# 是否正在录音
 		self.isRecording = False
 		# 插件验证--暂时无用
@@ -20,79 +28,13 @@ class Robot(object):
 		self.immersiveMode = None
 		# 优化
 		self.profiling = profiling
-		# 询问模式
-		self.hasPardon = False
-		# 播放器
-		self.player = None
-		# snowboy
-		self.snowboy = None
-		# 对话
-		self.onSay = None
-		# 技能
-		self.skill = None
-
-	def initSnowboy(self, snowboy):
-		self.snowboy = snowboy
-
-	def getSnowBoy(self):
-		if self.snowboy:
-			return self.snowboy
-		else:
-			self.snowboy = Demeter.service('snowboy')
-			self.snowboy.init(self)
-			return self.snowboy
-
-	def getHistory(self):
-		return self.history
-
-	def say(self, msg, cache=False, plugin='', completed=None):
-		print msg
-		if self.onSay:
-			self.logger.info('onSay: {}'.format(msg))
-			if plugin != '':
-				self.onSay("[{}] {}".format(plugin, msg))
-			else:
-				self.onSay(msg)
-			self.onSay = None
-		if plugin != '':
-			self.appendHistory(1, "[{}] {}".format(plugin, msg))
-		else:
-			self.appendHistory(1, msg)
-		pattern = r'^https?://.+'
-		if re.match(pattern, msg):
-			self.logger.info('内容包含URL,无法读取')
-			return
-		voice = ''
-		voice = self.tool['tts'].tts(msg)
-		if cache:
-			saveCache(voice, msg)
-		'''
-		if getCache(msg):
-			self.logger.info('命中缓存,播放缓存语音')
-			voice = getCache(msg)
-		else:
-			try:
-				voice = self.tool['tts'].tts(msg)
-				if cache:
-					saveCache(voice, msg)
-			except Exception as e:
-				self.logger.error('保存缓存失败:{}'.format(e))
-		'''
-		if completed is None:
-			completed = lambda: self.completed(msg)
-		self.play(voice, not cache, completed=completed)
-
-	def play(self, voice, delete=False, completed=None):
-		if self.player == None:
-			self.player = Demeter.service('sox', 'player')
-		self.player.play(voice, delete=delete, onCompleted=completed)
+		
+		# 醒来 启动
+		self.wakeup()
 
-	def start(self):
+	def wakeup(self):
 		# 机器人配置,后续改成从数据库读取
 		self.config = Demeter.config['robot']
-		self.reload()
-
-	def reload(self):
 		try:
 			for item in self.config:
 				setting = self.setting(self.config[item])
@@ -100,18 +42,25 @@ class Robot(object):
 					self.tool[item] = Demeter.service(self.config[item], 'api')
 					self.tool[item].setting(**setting)
 			self.player = None
-			self.skill = Demeter.service('skill')
-			self.skill.init(self)
-			self.skill.printPlugins()
+			self.ear.wait()
+			self.brain.printPlugins()
 		except Exception as e:
 			self.logger.critical('机器人初始化失败:{}'.format(e))
 
+	def sleep(self, signal, frame):
+		clean()
+		self.brain.pause()
+		self.ear.close()
+		self.mouth.close()
+		self.eye.close()
+		Demeter.dog.stop()
+
+	def say(self, msg, cache=False, plugin='', completed=None):
+		self.mouth.say(msg, cache, plugin, completed)
+
 	def checkRestore(self):
 		if self.immersiveMode:
-			self.skill.restore()
-
-	def checkPlayer(self):
-		return self.player is not None and self.player.is_playing()
+			self.brain.restore()
 
 	def setting(self, name):
 		# 后续要改成数据库读取
@@ -119,40 +68,10 @@ class Robot(object):
 		return config
 
 	# 中断、停止
-	def interrupt(self):
-		if self.checkPlayer():
-			self.player.stop()
-			self.player = None
+	def stop(self):
+		self.mouth.close()
 		if self.immersiveMode:
-			self.skill.pause()
-
-	def talk(self, fp, callback=None):
-		self.play(self.data + 'beep_lo.wav')
-		self.logger.info('结束录音')
-		self.isRecording = False
-		if self.profiling:
-			self.logger.info('性能调试已打开')
-			pr = cProfile.Profile()
-			pr.enable()
-			self.onTalk(fp, callback)
-			pr.disable()
-			s = io.StringIO()
-			sortby = 'cumulative'
-			ps = pstats.Stats(pr, stream=s).sort_stats(sortby)
-			ps.print_stats()
-			print(s.getvalue())
-		else:
-			self.onTalk(fp, callback)
-
-	def onTalk(self, fp, callback=None, onSay=None):
-		try:
-			self.interrupt()
-			query = self.tool['asr'].asr(fp)
-			File.remove(fp)
-			self.response(query, callback, onSay)
-		except Exception as e:
-			self.logger.critical(e)
-			clean()
+			self.brain.pause()
 
 	def doParse(self, query, **args):
 		return self.tool['nlu'].nlu(query, **args)
@@ -161,64 +80,4 @@ class Robot(object):
 		self.immersiveMode = slug
 
 	def getImmersiveMode(self):
-		return self.immersiveMode
-
-	def response(self, query, uid='', onSay=None):
-		# 统计
-		#statistic.report(1)
-		self.interrupt()
-		self.appendHistory(0, query, uid)
-
-		if onSay:
-			self.onSay = onSay
-
-		if query.strip() == '':
-			self.pardon()
-			return
-
-		lastImmersiveMode = self.immersiveMode
-
-		if not self.skill.query(query):
-			# 没命中技能,使用机器人回复
-			msg = self.tool['ai'].ai(query)
-			self.say(msg, True, completed=self.checkRestore)
-		else:
-			if lastImmersiveMode is not None and lastImmersiveMode != self.matchPlugin:
-				time.sleep(1)
-				if self.checkPlayer():
-					self.logger.debug('等说完再checkRestore')
-					self.player.appendOnCompleted(lambda: self.checkRestore())
-				else:
-					self.logger.debug('checkRestore')
-					self.checkRestore()
-
-	#将会话历史加进历史记录
-	def appendHistory(self, t, text, uid=''):
-		if t in (0, 1) and text != '':
-			if text.endswith(',') or text.endswith(','):
-				text = text[:-1]
-			if uid == '' or uid == None or uid == 'null':
-				uid = Demeter.uuid(__name__)
-			# 将图片处理成HTML
-			pattern = r'https?://.+\.(?:png|jpg|jpeg|bmp|gif|JPG|PNG|JPEG|BMP|GIF)'
-			url_pattern = r'^https?://.+'
-			imgs = re.findall(pattern, text)
-			for img in imgs:
-				text = text.replace(img, '<img src={} class="img"/>'.format(img))
-			urls = re.findall(url_pattern, text)
-			for url in urls:
-				text = text.replace(url, '<a href={} target="_blank">{}</a>'.format(url, url))
-			self.history.append({'type': t, 'text': text, 'time': time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())), 'uid': uid})
-
-	def completed(self, msg):
-		if Demeter.config['snowboy']['active_mode'] and (msg.endswith('?') or msg.endswith(u'?') or u'告诉我' in msg or u'请回答' in msg):
-			query = self.getSnowBoy().listen()
-			self.response(query)
-
-	def pardon(self):
-		if not self.hasPardon:
-			self.say('抱歉,刚刚没听清,能再说一遍吗?', completed=lambda: self.response(self.getSnowBoy().listen()))
-			self.hasPardon = True
-		else:
-			self.say('没听清呢')
-			self.hasPardon = False
+		return self.immersiveMode

+ 15 - 8
service/sdk/tencent.py

@@ -13,11 +13,13 @@ import requests
 import hmac
 import base64
 import urllib
+from hashlib import sha1
 class Tencent(object):
 	__slots__ = 'SECRET_ID', 'SECRET_KEY', 'SourceType', 'URL', 'VoiceFormat', 'PrimaryLanguage', 'Text', 'VoiceType', 'Region'
 
 	def init(self, SECRET_ID, SECRET_KEY):
 		self.SECRET_KEY, self.SECRET_ID = SECRET_KEY, SECRET_ID
+		return self
 	@property
 	def secret_id(self):
 		return self.SECRET_ID
@@ -109,7 +111,7 @@ class Tencent(object):
 			##注意URL编码的时候分str编码,整段编码会丢data
 			body = ''
 			for a, b in config_dict:
-				body += urllib.parse.quote(a) + '=' + urllib.parse.quote(str(b)) + '&'
+				body += self.quote(a) + '=' + self.quote(str(b)) + '&'
 			return body + 'Signature=' + sign_encode
 		HOST = 'aai.tencentcloudapi.com'
 		config_dict= {
@@ -129,10 +131,10 @@ class Tencent(object):
 		#按key排序
 		config_dict = sorted(config_dict.items())
 		signstr = self.formatSignString(config_dict)
-		sign_encode = urllib.parse.quote(self.encode_sign(signstr, self.SECRET_KEY))
+		sign_encode = self.quote(self.encode_sign(signstr, self.SECRET_KEY))
 		body = make_body(config_dict, sign_encode)
 		#Get URL
-		req_url = "https://aai.tencentcloudapi.com"
+		req_url = "https://aai."+self.region+".tencentcloudapi.com"
 		header = {
 			'Host' : HOST,
 			'Content-Type' : 'application/x-www-form-urlencoded',
@@ -142,6 +144,11 @@ class Tencent(object):
 		#有些音频utf8解码失败,存在编码错误
 		s = request.content.decode("utf8","ignore")
 		return json.loads(s)
+	def quote(self, str):
+		if Demeter.checkPy3():
+			return urllib.parse.quote(str)
+		else:
+			return urllib.quote(str)
 	def ASR(self, URL, voiceformat, sourcetype, region):
 		self.url, self.voiceformat, self.source_type, self.region = URL, voiceformat, sourcetype, region
 		return self.oneSentenceRecognition()
@@ -151,7 +158,7 @@ class Tencent(object):
 			##注意URL编码的时候分str编码,整段编码会丢data
 			body = ''
 			for a, b in config_dict:
-				body += urllib.parse.quote(a) + '=' + urllib.parse.quote(str(b)) + '&'
+				body += self.quote(a) + '=' + self.quote(str(b)) + '&'
 			return body + 'Signature=' + sign_encode
 		HOST = 'aai.tencentcloudapi.com'
 		config_dict= {
@@ -169,7 +176,7 @@ class Tencent(object):
 						'SourceType'	 : self.SourceType
 		}
 		if self.SourceType == '0':
-			config_dict['Url'] = urllib.parse.quote(str(self.url))
+			config_dict['Url'] = self.quote(str(self.url))
 		else:
 			#不能大于1M
 			file_path = self.URL
@@ -182,10 +189,10 @@ class Tencent(object):
 		#按key排序
 		config_dict = sorted(config_dict.items())
 		signstr = self.formatSignString(config_dict)
-		sign_encode = urllib.parse.quote(self.encode_sign(signstr, self.SECRET_KEY))
+		sign_encode = self.quote(self.encode_sign(signstr, self.SECRET_KEY))
 		body = make_body(config_dict, sign_encode)
 		#Get URL
-		req_url = "https://aai.tencentcloudapi.com"
+		req_url = "https://aai."+self.region+".tencentcloudapi.com"
 		header = {
 			'Host' : HOST,
 			'Content-Type' : 'application/x-www-form-urlencoded',
@@ -205,7 +212,7 @@ class Tencent(object):
 		return signstr + config_str
 	#生成签名
 	def encode_sign(self, signstr, SECRET_KEY):
-		myhmac = hmac.new(SECRET_KEY.encode(), signstr.encode(), digestmod = 'sha1')
+		myhmac = hmac.new(SECRET_KEY.encode(), signstr.encode(), sha1)
 		code = myhmac.digest()
 		#hmac() 完一定要decode()和 python 2 hmac不一样
 		signature = base64.b64encode(code).decode()

+ 0 - 96
service/skill.py

@@ -1,96 +0,0 @@
-# -*- coding: utf-8 -*-
-from __future__ import division
-from .__load__ import *
-
-class Skill(object):
-
-	def init(self, robot):
-		# vecan机器人
-		self.robot = robot
-		self.logger = Log.init(__name__)
-		self.plugins = []
-		#self.plugins = plugin_loader.get_plugins(self.robot)
-		self.handling = False
-
-	def isImmersive(self, plugin, text, parsed):
-		return self.robot.getImmersiveMode() == plugin.SLUG and \
-			plugin.isValidImmersive(text, parsed)
-
-	def printPlugins(self):
-		plugin_list = []
-		for plugin in self.plugins:
-			plugin_list.append(plugin.SLUG)
-		self.logger.info('已激活插件:{}'.format(plugin_list))
-
-	def query(self, text):
-		"""
-		query 模块
-
-		Arguments:
-		text -- 用户输入
-		"""
-
-		args = {
-			"service_id": "S13442",
-			"api_key": 'w5v7gUV3iPGsGntcM84PtOOM',
-			"secret_key": 'KffXwW6E1alcGplcabcNs63Li6GvvnfL'
-		}
-		parsed = self.robot.doParse(text, **args)
-		print parsed
-		return parsed
-
-		for plugin in self.plugins:
-			if not plugin.isValid(text, parsed) and not self.isImmersive(plugin, text, parsed):
-				continue
-
-			self.logger.info("'{}' 命中技能 {}".format(text, plugin.SLUG))
-			self.robot.matchPlugin = plugin.SLUG
-
-			if plugin.IS_IMMERSIVE:
-				self.robot.setImmersiveMode(plugin.SLUG)
-
-			continueHandle = False
-			try:
-				self.handling = True
-				continueHandle = plugin.handle(text, parsed)
-				self.handling = False				
-			except Exception:
-				self.logger.critical('Failed to execute plugin',
-								   exc_info=True)
-				reply = u"抱歉,插件{}出故障了,晚点再试试吧".format(plugin.SLUG)
-				self.robot.say(reply, plugin=plugin.SLUG)
-			else:
-				self.logger.debug("Handling of phrase '%s' by " +
-								   "plugin '%s' completed", text,
-								   plugin.SLUG)					
-			finally:
-				if not continueHandle:
-					return True
-
-		self.logger.debug("No plugin was able to handle phrase {} ".format(text))
-		return False
-
-	def restore(self):
-		""" 恢复某个技能的处理 """
-		if not self.robot.immersiveMode:
-			return
-		for plugin in self.plugins:
-			if plugin.SLUG == self.robot.immersiveMode and plugin.restore:
-				plugin.restore()
-
-	def pause(self):
-		""" 暂停某个技能的处理 """
-		if not self.robot.immersiveMode:
-			return
-		for plugin in self.plugins:
-			if plugin.SLUG == self.robot.immersiveMode and plugin.pause:
-				plugin.pause()
-
-	def understand(self, fp):
-		if self.robot and self.robot.tool['asr']:
-			return self.robot.tool['asr'].asr(fp)
-		return None
-
-	def say(self, msg, cache=False):
-		if self.robot and self.robot.tool['tts']:
-			self.robot.tool['tts'].tts(msg)

+ 1 - 0
service/snowboy/__init__.py

@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-

+ 0 - 0
snowboy/_snowboydetect.so → service/snowboy/_snowboydetect.so


+ 0 - 0
snowboy/resources/common.res → service/snowboy/resources/common.res


+ 0 - 0
snowboy/resources/ding.wav → service/snowboy/resources/ding.wav


+ 0 - 0
snowboy/resources/dong.wav → service/snowboy/resources/dong.wav


+ 1 - 1
snowboy/snowboydecoder.py → service/snowboy/snowboydecoder.py

@@ -1,6 +1,6 @@
 # -*- coding: utf-8 -*-
 from __future__ import division
-from .__load__ import *
+from ..__load__ import *
 import collections
 import pyaudio
 from . import snowboydetect

+ 1 - 1
snowboy/snowboydetect.py → service/snowboy/snowboydetect.py

@@ -3,7 +3,7 @@
 #
 # Do not make changes to this file unless you know what you are doing--modify
 # the SWIG interface file instead.
-
+import _snowboydetect
 from sys import version_info as _swig_python_version_info
 if _swig_python_version_info >= (2, 7, 0):
     def swig_import_helper():

+ 0 - 0
snowboy/__init__.py


+ 5 - 14
vecan.py

@@ -20,7 +20,7 @@ class Vecan(object):
 
 		self.robot = Demeter.service('robot')
 		self.robot.init()
-		self.robot.say('你好,我是神算终端机器人vecan,很高兴为你服务')
+		#self.robot.say('你好,我是神算终端机器人vecan,很高兴为你服务')
 
 		Demeter.dog = WatchDog.init()
 
@@ -34,23 +34,14 @@ class Vecan(object):
 		if 'outname' not in Demeter.config['vecan']:
 			Demeter.config['vecan']['outname'] = 'output'
 
-	def stop(self, signal, frame):
-		self.clean()
-		self.snowboy.stop()
-		Demeter.dog.stop()
-
 	def run(self):
-		self.init()
-		signal.signal(signal.SIGINT, self.stop)
-		#Demeter.webInit('front')
-		return
 		try:
-			self.snowboy = Demeter.service('snowboy')
-			self.snowboy.init(self.robot)
-			self.robot.initSnowboy(self.snowboy)
+			self.init()
 		except AttributeError:
-			Demeter.logger.error('初始化离线唤醒功能失败')
+			Demeter.logger.error('初始化机器人功能失败')
 			pass
+		signal.signal(signal.SIGINT, self.robot.sleep)
+		#Demeter.webInit('front')
 
 if __name__ == '__main__':
 	vecan = Vecan()