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 .Response import Response
import re
class Callback:
def __init__(this, patterns):
def __init__(this, patterns, quiet = False, once = True):
this.patterns = patterns
this.quiet = quiet
this.once = once
def setStart(this, function):
this.start = function
@ -16,3 +19,14 @@ class Callback:
if match := re.search(re.compile(Command.compilePattern(pattern)), string):
return this.start({**match.groupdict(), 'string':string})
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()
thread = RThread(target=cmd, args=(text, finish_event))
thread.start()
return {
'type': 'background',
'text': answer,
'voice': voice,
'thread': {
'thread': thread,
'finish_event': finish_event,
}
}
return Response(voice = voice, text = answer, thread = {'thread': thread, 'finish_event': finish_event} )
return wrapper
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 .Callback import *
from .Response import *

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,7 @@ def index(request):
text = request.GET.get("text")
if text == None: return HttpResponse("")
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)
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"')
except:
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 SmallTalk
import Media
import SmartHome
#import SmartHome
import Raspi
import Zieit

View File

@ -9,11 +9,10 @@ import os
listener = SpeechRecognition.SpeechToText()
voice = Text2Speech.Engine()
threads = []
reports = []
memory = []
voids = 0
listener.listen_noise()
if config.double_clap_activation:
# check double clap from arduino microphone module
def checkClap(channel):
@ -45,65 +44,95 @@ if config.double_clap_activation:
GPIO.setup(12, GPIO.IN)
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():
for thread in threads:
if thread['finish_event'].is_set():
responce = thread['thread'].join()
reply(responce)
if not thread['finish_event'].is_set(): continue
response = thread['thread'].join()
reply(response)
if response.callback:
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')
while True: # main loop
def report():
global reports
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='')
speech = listener.listen()
if speech['status'] in ['error', 'void']:
return speech
text = speech['text']
print(text, end='')
while True:
check_threads()
print('\nYou: ', end='')
# input
speech = listener.listen()
text = speech['text']
if speech['status'] == 'ok':
print(text)
voids = 0
# repeat last answer
if Command.isRepeat(text):
reply(memory[0]['responce']);
continue
# trying to recognize command with context
if not callback: break
try:
cmd, params = memory[0]['cmd'].checkContext(text).values()
if memory[0].get('params'): params = {**memory[0].get('params'), **params}
if response := callback.answer(text):
reply(response)
except:
cmd, params = Command.reg_find(text).values()
# 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
break
memory.insert(0, {
'text': text,
'cmd': cmd,
'responce': responce,
'response': response,
})
else:
if speech['status'] == 'error': print('Отсутсвует подключение к интернету');
elif speech['status'] == 'void': voids += 1;
speech = recognize(response.callback, params)
if callback.once: break
return speech
listener.listen_noise()
os.system('clear')
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