1
0
mirror of https://github.com/MarkParker5/STARK.git synced 2025-07-02 22:36:54 +02:00

add Response class, recursive voice assistant algorithm

This commit is contained in:
MarkParker5
2021-02-21 21:06:00 +02:00
parent 96bb146822
commit 792bede347
17 changed files with 147 additions and 186 deletions

View File

@ -1,9 +1,12 @@
from .Command import Command from .Command import Command
from .Response import Response
import re import re
class Callback: class Callback:
def __init__(this, patterns): def __init__(this, patterns, quiet = False, once = True):
this.patterns = patterns this.patterns = patterns
this.quiet = quiet
this.once = once
def setStart(this, function): def setStart(this, function):
this.start = function this.start = function
@ -14,5 +17,16 @@ class Callback:
def answer(this, string): def answer(this, string):
for pattern in this.patterns: for pattern in this.patterns:
if match := re.search(re.compile(Command.compilePattern(pattern)), string): if match := re.search(re.compile(Command.compilePattern(pattern)), string):
return this.start({**match.groupdict(), 'string':string}) return this.start({**match.groupdict(), 'string':string})
return None return None
@staticmethod
def background(answer = '', voice = ''):
def decorator(cmd):
def wrapper(text):
finish_event = Event()
thread = RThread(target=cmd, args=(text, finish_event))
thread.start()
return Response(voice = voice, text = answer, thread = {'thread': thread, 'finish_event': finish_event} )
return wrapper
return decorator

View File

@ -252,14 +252,6 @@ class Command(ABC):
finish_event = Event() finish_event = Event()
thread = RThread(target=cmd, args=(text, finish_event)) thread = RThread(target=cmd, args=(text, finish_event))
thread.start() thread.start()
return { return Response(voice = voice, text = answer, thread = {'thread': thread, 'finish_event': finish_event} )
'type': 'background',
'text': answer,
'voice': voice,
'thread': {
'thread': thread,
'finish_event': finish_event,
}
}
return wrapper return wrapper
return decorator return decorator

6
Command/Response.py Normal file
View File

@ -0,0 +1,6 @@
class Response:
def __init__(this, voice, text, callback = None, thread = None):
this.voice: str = voice
this.text: str = text
this.callback: Callback = callback
this.thread = thread

View File

@ -1,2 +1,3 @@
from .Command import * from .Command import *
from .Callback import * from .Callback import *
from .Response import *

View File

@ -2,7 +2,7 @@ from .Media import *
import requests import requests
from bs4 import BeautifulSoup as BS from bs4 import BeautifulSoup as BS
import os import os
from Command import Callback from Command import Callback, Response
################################################################################ ################################################################################
def findPage(name): def findPage(name):
query = name + ' site:kinogo.by' query = name + ' site:kinogo.by'
@ -83,17 +83,8 @@ def film(params):
else: else:
voice = text = 'Какой фильм включить?' voice = text = 'Какой фильм включить?'
callback = kinogo_film_cb callback = kinogo_film_cb
return { return Response(text = text, voice = voice, callback = callback)
'type': 'question', return Response(text = text, voice = voice)
'text': text,
'voice': voice,
'callback': callback,
}
return {
'type': 'simple',
'text': text,
'voice': voice,
}
def start_film(params): def start_film(params):
name = params.get('text') name = params.get('text')
@ -104,12 +95,7 @@ def start_film(params):
if url: if url:
startFilm(url, title) startFilm(url, title)
voice = text = 'Включаю' voice = text = 'Включаю'
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
def serial(params): def serial(params):
name = params.get('text') name = params.get('text')
@ -124,17 +110,8 @@ def serial(params):
else: else:
voice = text = 'Какой сериал включить?' voice = text = 'Какой сериал включить?'
callback = kinogo_serial_cb callback = kinogo_serial_cb
return { return Response(text = text, voice = voice, callback = callback)
'type': 'question', return Response(text = text, voice = voice)
'text': text,
'voice': voice,
'callback': callback,
}
return {
'type': 'simple',
'text': text,
'voice': voice,
}
def start_serial(params): def start_serial(params):
name = params.get('text') name = params.get('text')
@ -145,11 +122,7 @@ def start_serial(params):
if url: if url:
startSerial(url, title) startSerial(url, title)
voice = text = 'Включаю' voice = text = 'Включаю'
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
kinogo_film_cb = Callback(['$text',]) kinogo_film_cb = Callback(['$text',])
kinogo_film_cb.setStart(start_film) kinogo_film_cb.setStart(start_film)

View File

@ -1,5 +1,5 @@
from bs4 import BeautifulSoup as BS from bs4 import BeautifulSoup as BS
from Command import Command from Command import Command, Response
import wikipedia as wiki import wikipedia as wiki
import requests import requests
import random import random
@ -8,11 +8,11 @@ import re
class QA(Command): class QA(Command):
def googleDictionary(this, word): def googleDictionary(this, word):
responce = requests.get(f'https://api.dictionaryapi.dev/api/v2/entries/ru/{word}') response = requests.get(f'https://api.dictionaryapi.dev/api/v2/entries/ru/{word}')
if responce.status_code == 200: if response.status_code == 200:
responce = json.loads(responce.content) response = json.loads(response.content)
text = '' text = ''
r = responce[0] r = response[0]
definition = r['meanings'][0]['definitions'][0] definition = r['meanings'][0]['definitions'][0]
short = r['word'].lower().capitalize() + ' (' + ( r['meanings'][0]['partOfSpeech'].capitalize() if r['meanings'][0]['partOfSpeech'] != 'undefined' else 'Разговорный' ) + ') — ' + definition['definition'].lower().capitalize() + ( '. Синонимы: ' + ', '.join(definition['synonyms']) if definition['synonyms'] else '') short = r['word'].lower().capitalize() + ' (' + ( r['meanings'][0]['partOfSpeech'].capitalize() if r['meanings'][0]['partOfSpeech'] != 'undefined' else 'Разговорный' ) + ') — ' + definition['definition'].lower().capitalize() + ( '. Синонимы: ' + ', '.join(definition['synonyms']) if definition['synonyms'] else '')
short = short.replace(word[0].lower()+'.', word.lower()) short = short.replace(word[0].lower()+'.', word.lower())
@ -21,7 +21,7 @@ class QA(Command):
short = short.replace('потр.', 'потребляется') short = short.replace('потр.', 'потребляется')
short = short.replace('знач.', 'значении') short = short.replace('знач.', 'значении')
for r in responce: for r in response:
text += '\n' + r['word'].lower().capitalize() + ' (' + (r['meanings'][0]['partOfSpeech'].capitalize() if r['meanings'][0]['partOfSpeech'] != 'undefined' else 'Разговорный') + ')\n' text += '\n' + r['word'].lower().capitalize() + ' (' + (r['meanings'][0]['partOfSpeech'].capitalize() if r['meanings'][0]['partOfSpeech'] != 'undefined' else 'Разговорный') + ')\n'
for definition in r['meanings'][0]['definitions']: for definition in r['meanings'][0]['definitions']:
text += '\t' + definition['definition'].lower().capitalize() text += '\t' + definition['definition'].lower().capitalize()
@ -46,8 +46,8 @@ class QA(Command):
except: return '' except: return ''
def googleSearch(this, word): def googleSearch(this, word):
responce = requests.get(f'https://www.google.ru/search?&q={word}&lr=lang_ru&lang=ru') response = requests.get(f'https://www.google.ru/search?&q={word}&lr=lang_ru&lang=ru')
page = BS(responce.content, 'html.parser') page = BS(response.content, 'html.parser')
info = page.select('div.BNeawe>div>div.BNeawe') info = page.select('div.BNeawe>div>div.BNeawe')
return info[0].get_text() if info else '' return info[0].get_text() if info else ''
@ -67,10 +67,6 @@ class QA(Command):
try: search = this.googleSearch(query) try: search = this.googleSearch(query)
except: search = '' except: search = ''
voice = text = search or random.choice(['Не совсем понимаю, о чём вы.', 'Вот эта последняя фраза мне не ясна.', 'А вот это не совсем понятно.', 'Можете сказать то же самое другими словами?', 'Вот сейчас я совсем вас не понимаю.', 'Попробуйте выразить свою мысль по-другому',]) voice = text = search or random.choice(['Не совсем понимаю, о чём вы.', 'Вот эта последняя фраза мне не ясна.', 'А вот это не совсем понятно.', 'Можете сказать то же самое другими словами?', 'Вот сейчас я совсем вас не понимаю.', 'Попробуйте выразить свою мысль по-другому',])
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
Command.QA = QA('QA', {}, []) Command.QA = QA('QA', {}, [])

View File

@ -1,16 +1,12 @@
from .Raspi import * from .Raspi import *
import os import os
from Command import Callback from Command import Callback, Response
import config import config
################################################################################ ################################################################################
def reboot(params): def reboot(params):
if params['bool']: if params['bool']:
os.system('sudo reboot') os.system('sudo reboot')
return { return Response(text = '', voice = '')
'text': 'Хорошо',
'voice': 'Хорошо',
'type': 'simple',
}
reboot_cb = Callback(['$bool',]) reboot_cb = Callback(['$bool',])
reboot_cb.setStart(reboot) reboot_cb.setStart(reboot)
@ -20,19 +16,11 @@ def method(params, finish_event):
os.system('git -C '+config.path+' remote update') os.system('git -C '+config.path+' remote update')
if not 'git pull' in os.popen('git -C '+config.path+' status -uno').read(): if not 'git pull' in os.popen('git -C '+config.path+' status -uno').read():
finish_event.set() finish_event.set()
return { return Response(text = text, voice = voice)
'text': 'Установлена последняя версия',
'voice': 'Установлена последняя версия',
'type': 'simple',
}
os.system('git -C '+config.path+' pull') os.system('git -C '+config.path+' pull')
finish_event.set() finish_event.set()
return { voice = text = 'Обновления скачаны. Перезагрузиться?'
'text': 'Обновления скачаны. Перезагрузиться?', return Response(text = text, voice = voice, callback = reboot_cb)
'voice': 'Обновления скачаны. Перезагрузиться?',
'type': 'question',
'callback': reboot_cb,
}
patterns = ['* обновись *', '* можешь обновиться *', '* обнови себя *', '* скачай обновлени* *', '* провер* обновлени* *'] patterns = ['* обновись *', '* можешь обновиться *', '* обнови себя *', '* скачай обновлени* *', '* провер* обновлени* *']
gitpull = Raspi('git pull archie.git', [], patterns) gitpull = Raspi('git pull archie.git', [], patterns)

View File

@ -1,14 +1,11 @@
from .Raspi import * from .Raspi import *
from Command import Response
################################################################################ ################################################################################
def method(params): def method(params):
Raspi.hdmi_cec('on 0') Raspi.hdmi_cec('on 0')
Raspi.hdmi_cec('as') Raspi.hdmi_cec('as')
voice = text = '' voice = text = ''
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* включи* (телевизор|экран) *'] patterns = ['* включи* (телевизор|экран) *']
@ -18,11 +15,7 @@ tv_on.setStart(method)
def method(params): def method(params):
Raspi.hdmi_cec('standby 0') Raspi.hdmi_cec('standby 0')
voice = text = '' voice = text = ''
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* (выключи|отключи)* (телевизор|экран) *'] patterns = ['* (выключи|отключи)* (телевизор|экран) *']
@ -33,11 +26,7 @@ def method(params):
port = params['num'] + '0' if len(params['num']) == 1 else params['num'] port = params['num'] + '0' if len(params['num']) == 1 else params['num']
Raspi.hdmi_cec(f'tx 4F:82:{port}:00') Raspi.hdmi_cec(f'tx 4F:82:{port}:00')
voice = text = '' voice = text = ''
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* (выведи|вывести|покажи|открой|показать|открыть) * с (|провода|hdmi|кабеля|порта) * $num *'] patterns = ['* (выведи|вывести|покажи|открой|показать|открыть) * с (|провода|hdmi|кабеля|порта) * $num *']
@ -47,11 +36,7 @@ tv_hdmi.setStart(method)
def method(params): def method(params):
Raspi.hdmi_cec('tx 4F:82:20:00') Raspi.hdmi_cec('tx 4F:82:20:00')
voice = text = '' voice = text = ''
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* (выведи|вывести|покажи|открой|показать|открыть) * с (ноута|ноутбука|провода|hdmi)'] patterns = ['* (выведи|вывести|покажи|открой|показать|открыть) * с (ноута|ноутбука|провода|hdmi)']
@ -61,11 +46,7 @@ tv_hdmi.setStart(method)
def method(params): def method(params):
Raspi.hdmi_cec('as') Raspi.hdmi_cec('as')
voice = text = '' voice = text = ''
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* (верни|вернуть|включи*|покажи|показать) [нормальн|стандартн|привычн]* (телевизор|экран|картинк|изображение) *'] patterns = ['* (верни|вернуть|включи*|покажи|показать) [нормальн|стандартн|привычн]* (телевизор|экран|картинк|изображение) *']

View File

@ -3,6 +3,7 @@ import datetime, time
import requests import requests
from bs4 import BeautifulSoup as BS from bs4 import BeautifulSoup as BS
import math import math
from Command import Response
################################################################################ ################################################################################
def method(params): def method(params):
if city := params.get('text'): if city := params.get('text'):
@ -85,11 +86,7 @@ def method(params):
if city: text = f'Текущее время в {city}: {hours}:{minutes}' if city: text = f'Текущее время в {city}: {hours}:{minutes}'
else: text = f'Текущее время: {hours}:{minutes}' else: text = f'Текущее время: {hours}:{minutes}'
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = { keywords = {
10: ['который час', 'сколько времени'], 10: ['который час', 'сколько времени'],

View File

@ -1,12 +1,9 @@
from .SmallTalk import * from .SmallTalk import *
from Command import Response
################################################################################ ################################################################################
def method(params): def method(params):
voice = text = 'Привет' voice = text = 'Привет'
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
patterns = ['* привет* *',] patterns = ['* привет* *',]
hello = SmallTalk('Hello', {}, patterns) hello = SmallTalk('Hello', {}, patterns)

View File

@ -7,11 +7,7 @@ def method(params):
'cmd': 'light_on', 'cmd': 'light_on',
}) })
voice = text = '' voice = text = ''
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* (включ|выключ)* свет *'] patterns = ['* (включ|выключ)* свет *']

View File

@ -8,11 +8,7 @@ def method(params):
'cmd': 'window_open', 'cmd': 'window_open',
}) })
voice = text = 'Поднимаю роллеты' voice = text = 'Поднимаю роллеты'
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* (открыть|открой) (окно|окна) *', '* (подними|поднять) (шторы|роллеты) *'] patterns = ['* (открыть|открой) (окно|окна) *', '* (подними|поднять) (шторы|роллеты) *']
@ -27,11 +23,7 @@ def method(params):
'cmd': 'window_close', 'cmd': 'window_close',
}) })
voice = text = 'Опускаю роллеты' voice = text = 'Опускаю роллеты'
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* (закрыть|закрой) (окно|окна) *', '* (опусти|опустить) (шторы|роллеты) *'] patterns = ['* (закрыть|закрой) (окно|окна) *', '* (опусти|опустить) (шторы|роллеты) *']

View File

@ -10,11 +10,7 @@ def nextLessonMethod(params):
type = lesson['type'] type = lesson['type']
voice = f'{type} по предмету {subject} в аудитории {auditory}' voice = f'{type} по предмету {subject} в аудитории {auditory}'
text = f'{subject}\n{teacher}\n{auditory}\n{type}' text = f'{subject}\n{teacher}\n{auditory}\n{type}'
return { return Response(text = text, voice = voice)
'type': 'simple',
'text': text,
'voice': voice,
}
keywords = {} keywords = {}
patterns = ['* следующ* (предмет|урок|пара)'] patterns = ['* следующ* (предмет|урок|пара)']

View File

@ -9,6 +9,7 @@ def index(request):
text = request.GET.get("text") text = request.GET.get("text")
if text == None: return HttpResponse("") if text == None: return HttpResponse("")
cmd, params = Command.reg_find(text).values() cmd, params = Command.reg_find(text).values()
responce = cmd.start(params) try: responce = cmd.start(params)
except: {'text': f'Ошибка в модуле {cmd.getName()}', 'voice': 'Произошла ошибка'}
json_string = json.dumps(responce) json_string = json.dumps(responce)
return HttpResponse(json_string) return HttpResponse(json_string)

View File

@ -13,4 +13,6 @@ for name, module in modules.items():
os.system(f'lxterminal --command="python3.8 {config.path}/{module}.py"') os.system(f'lxterminal --command="python3.8 {config.path}/{module}.py"')
except: except:
print(f'[error]\t{name} launch failed') print(f'[error]\t{name} launch failed')
os.system(f'lxterminal --command="python3.8 {config.path}/manage.py runserver"')
os.system(f'lxterminal --command="python3.8 {config.path}/manage.py runserver 192.168.0.129:8000"')
os.system(f'lxterminal --command="vlc"')

View File

@ -1,6 +1,6 @@
import QA import QA
import SmallTalk import SmallTalk
import Media import Media
import SmartHome #import SmartHome
import Raspi import Raspi
import Zieit import Zieit

View File

@ -9,11 +9,10 @@ import os
listener = SpeechRecognition.SpeechToText() listener = SpeechRecognition.SpeechToText()
voice = Text2Speech.Engine() voice = Text2Speech.Engine()
threads = [] threads = []
reports = []
memory = [] memory = []
voids = 0 voids = 0
listener.listen_noise()
if config.double_clap_activation: if config.double_clap_activation:
# check double clap from arduino microphone module # check double clap from arduino microphone module
def checkClap(channel): def checkClap(channel):
@ -45,65 +44,95 @@ if config.double_clap_activation:
GPIO.setup(12, GPIO.IN) GPIO.setup(12, GPIO.IN)
GPIO.add_event_detect(12, GPIO.RISING, callback=checkClap) GPIO.add_event_detect(12, GPIO.RISING, callback=checkClap)
def reply(responce):
if responce['text']: # print answer
print('Archie: '+responce['text'])
if responce['voice']: # say answer
voice.generate(responce['voice']).speak()
if responce['type'] == 'background': # add background thread to list
threads.append(responce['thread'])
def check_threads(): def check_threads():
for thread in threads: for thread in threads:
if thread['finish_event'].is_set(): if not thread['finish_event'].is_set(): continue
responce = thread['thread'].join() response = thread['thread'].join()
reply(responce) reply(response)
thread['finish_event'].clear() if response.callback:
del thread if response.callback.quiet:
response.callback.start()
else:
for _ in range(3):
print('\nYou: ', end='')
speech = listener.listen()
if speech['status'] == 'ok':
print(speech['text'], '\n')
new_response = response.callback.answer(speech['text'])
reply(new_response)
break
else:
reports.append(response)
thread['finish_event'].clear()
del thread
os.system('clear') def report():
while True: # main loop global reports
check_threads() for response in reports:
if response.voice:
voice.generate(response.voice).speak()
time.sleep(2)
reports = []
def reply(response):
if response.text: # print answer
print('\nArchie: '+response.text)
if response.voice: # say answer
voice.generate(response.voice).speak()
if response.thread: # add background thread to stack
threads.append(response.thread)
def recognize(callback, params):
print('\nYou: ', end='') print('\nYou: ', end='')
# input
speech = listener.listen() speech = listener.listen()
text = speech['text'] if speech['status'] in ['error', 'void']:
if speech['status'] == 'ok': return speech
print(text) text = speech['text']
voids = 0 print(text, end='')
# repeat last answer while True:
if Command.isRepeat(text): check_threads()
reply(memory[0]['responce']); if not callback: break
continue
# trying to recognize command with context
try: try:
cmd, params = memory[0]['cmd'].checkContext(text).values() if response := callback.answer(text):
if memory[0].get('params'): params = {**memory[0].get('params'), **params} reply(response)
except: except:
cmd, params = Command.reg_find(text).values() break
# execute command
responce = cmd.start(params)
# say answer
reply(responce)
# waiting answer on question
if responce['type'] == 'question':
print('\nYou: ', end='')
speech = listener.listen()
if speech['status'] == 'ok':
text = speech['text']
print(text)
if responce := responce['callback'].answer(text): reply(responce)
# remember the command
memory.insert(0, { memory.insert(0, {
'text': text, 'text': text,
'cmd': cmd, 'cmd': cmd,
'responce': responce, 'response': response,
}) })
else: speech = recognize(response.callback, params)
if speech['status'] == 'error': print('Отсутсвует подключение к интернету'); if callback.once: break
elif speech['status'] == 'void': voids += 1; return speech
if voids >= 3:
voids = 0 listener.listen_noise()
if config.double_clap_activation: os.system('clear')
print('\nSleep (-_-)zzZZ\n')
sleep() while True:
if voids >= 3:
voids = 0
if config.double_clap_activation:
print('\nSleep (-_-)zzZZ\n')
sleep()
print('\nYou: ', end='')
speech = listener.listen()
print(speech.get('text') or '', end='')
voids = 0
while True:
if speech['status'] == 'error':
break
if speech['status'] == 'void':
voids += 1
break
text = speech['text']
cmd, params = Command.reg_find(text).values()
try: response = cmd.start(params)
except: break
reply(response)
check_threads()
report()
if response.callback:
speech = recognize(response.callback, {})
else:
break