You've already forked STARK
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:
@ -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
|
||||
@ -14,5 +17,16 @@ class Callback:
|
||||
def answer(this, string):
|
||||
for pattern in this.patterns:
|
||||
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
|
||||
|
||||
@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
|
||||
|
@ -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
6
Command/Response.py
Normal 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
|
@ -1,2 +1,3 @@
|
||||
from .Command import *
|
||||
from .Callback import *
|
||||
from .Response import *
|
||||
|
@ -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)
|
||||
|
22
QA/QA.py
22
QA/QA.py
@ -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', {}, [])
|
||||
|
@ -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)
|
||||
|
31
Raspi/tv.py
31
Raspi/tv.py
@ -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 = ['* (верни|вернуть|включи*|покажи|показать) [нормальн|стандартн|привычн]* (телевизор|экран|картинк|изображение) *']
|
||||
|
@ -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: ['который час', 'сколько времени'],
|
||||
|
@ -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)
|
||||
|
@ -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 = ['* (включ|выключ)* свет *']
|
||||
|
@ -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 = ['* (закрыть|закрой) (окно|окна) *', '* (опусти|опустить) (шторы|роллеты) *']
|
||||
|
@ -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 = ['* следующ* (предмет|урок|пара)']
|
||||
|
@ -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)
|
||||
|
4
main.py
4
main.py
@ -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"')
|
||||
|
@ -1,6 +1,6 @@
|
||||
import QA
|
||||
import SmallTalk
|
||||
import Media
|
||||
import SmartHome
|
||||
#import SmartHome
|
||||
import Raspi
|
||||
import Zieit
|
||||
|
@ -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)
|
||||
thread['finish_event'].clear()
|
||||
del thread
|
||||
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
|
||||
check_threads()
|
||||
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='')
|
||||
# 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 speech['status'] in ['error', 'void']:
|
||||
return speech
|
||||
text = speech['text']
|
||||
print(text, end='')
|
||||
while True:
|
||||
check_threads()
|
||||
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;
|
||||
if voids >= 3:
|
||||
voids = 0
|
||||
if config.double_clap_activation:
|
||||
print('\nSleep (-_-)zzZZ\n')
|
||||
sleep()
|
||||
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
|
||||
|
Reference in New Issue
Block a user