From 7a2cfaa59b0aa9251de6cd1f2d7302f2f3f3ec1f Mon Sep 17 00:00:00 2001 From: MarkParker5 <34688010+MarkParker5@users.noreply.github.com> Date: Sun, 24 Oct 2021 22:26:09 +0300 Subject: [PATCH] Partially new flow. Remove Callback, create CommandsManager, create Core --- .gitignore | 2 + ArchieCore/Command/Command.py | 34 +++ ArchieCore/Command/CommandsManager.py | 60 +++++ {Features => ArchieCore}/Command/RThread.py | 0 ArchieCore/Command/Response.py | 15 ++ ArchieCore/Command/ThreadData.py | 9 + ArchieCore/Command/__init__.py | 3 + {Features => ArchieCore}/Command/synonyms.py | 0 ArchieCore/Pattern/ACObjects/ACObject.py | 16 ++ ArchieCore/Pattern/ACObjects/__init__.py | 1 + ArchieCore/Pattern/Pattern.py | 36 +++ ArchieCore/Pattern/__init__.py | 2 + ArchieCore/Pattern/expressions.py | 13 + ArchieCore/__init__.py | 2 + Controls/RemoteControl/RemoteControl.py | 4 +- Controls/TelegramBot/TelegramBot.py | 4 +- Controls/VoiceAssistant/VoiceAssistant.py | 29 ++- Features/Command/Callback.py | 32 --- Features/Command/Command.py | 245 ------------------- Features/Command/Response.py | 6 - Features/Command/__init__.py | 3 - Features/Media/Media.py | 2 +- Features/Media/kinogo.py | 6 +- Features/QA/QA.py | 4 +- Features/Raspi/Raspi.py | 2 +- Features/Raspi/gitpull.py | 6 +- Features/Raspi/tv.py | 17 +- Features/SmallTalk/SmallTalk.py | 2 +- Features/SmallTalk/ctime.py | 16 +- Features/SmallTalk/hello.py | 2 +- Features/SmartHome/SmartHome.py | 2 +- Features/SmartHome/alarmclock.py | 5 +- Features/SmartHome/light.py | 14 +- Features/SmartHome/main_light.py | 5 +- Features/SmartHome/window.py | 6 +- Features/Zieit/Zieit.py | 2 +- Features/Zieit/myshedule.py | 13 +- Features/__init__.py | 5 +- General/Text2Speech/TTS.py | 1 + README.md | 27 +- libs.txt => dependences.txt | 3 +- startup[deprecated].py | 21 -- 42 files changed, 287 insertions(+), 390 deletions(-) create mode 100644 ArchieCore/Command/Command.py create mode 100644 ArchieCore/Command/CommandsManager.py rename {Features => ArchieCore}/Command/RThread.py (100%) create mode 100644 ArchieCore/Command/Response.py create mode 100644 ArchieCore/Command/ThreadData.py create mode 100644 ArchieCore/Command/__init__.py rename {Features => ArchieCore}/Command/synonyms.py (100%) create mode 100644 ArchieCore/Pattern/ACObjects/ACObject.py create mode 100644 ArchieCore/Pattern/ACObjects/__init__.py create mode 100644 ArchieCore/Pattern/Pattern.py create mode 100644 ArchieCore/Pattern/__init__.py create mode 100644 ArchieCore/Pattern/expressions.py create mode 100644 ArchieCore/__init__.py delete mode 100644 Features/Command/Callback.py delete mode 100644 Features/Command/Command.py delete mode 100644 Features/Command/Response.py delete mode 100644 Features/Command/__init__.py rename libs.txt => dependences.txt (84%) delete mode 100644 startup[deprecated].py diff --git a/.gitignore b/.gitignore index 72eefc1..b0a8159 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,5 @@ __pycache__ /logs.txt audio/ + +.idea diff --git a/ArchieCore/Command/Command.py b/ArchieCore/Command/Command.py new file mode 100644 index 0000000..a1f5b02 --- /dev/null +++ b/ArchieCore/Command/Command.py @@ -0,0 +1,34 @@ +from typing import Callable +from abc import ABC +from .RThread import RThread, Event +from .CommandsManager import CommandsManager +from .. import Pattern + +class Command(ABC): + name: str + patterns: list[Pattern] + start: Callable + + def __init__(self, name, keywords = {}, patterns = []): # initialisation of new command + self._name = name #TODO: change name to path + self._patterns = patterns + CommandsManager().append(self) + + def setStart(self, function): # define start (required) + self.start = function + + @property + def name(self): + return self._name + + @property + def patterns(self): + return self._patterns + + @classmethod + def new(cls, *args, **kwargs): + def creator(func): + cmd: Command = cls(*args, **kwargs) + cmd.setStart(func) + return func + return creator diff --git a/ArchieCore/Command/CommandsManager.py b/ArchieCore/Command/CommandsManager.py new file mode 100644 index 0000000..4103e62 --- /dev/null +++ b/ArchieCore/Command/CommandsManager.py @@ -0,0 +1,60 @@ +from typing import Type +from . import Command +from ..Pattern import ACObject + +class SearchResult: + command: Command + parameters: dict[str, ACObject] + + def __init__(self, command: Command, parameters: dict[str, ACObject] = {}): + self.command = command + self.parameters = parameters + +class CommandsManager: + allCommands: list[Command] = [] + + def __new__(cls): # Singleton + if not hasattr(cls, 'instance'): + cls.instance = super().__new__(cls) + return cls.instance + + def search(self, string) -> list[SearchResult]: # find command by pattern + string = string.lower() + results: list[SearchResult] = [] + acstring = ACString(string) + + # find command obj by pattern + for command in self.allCommands: + for pattern in command.patterns: + if groupdict := pattern.match(string): + parameters: dict[str: ACObject] = {'string': acstring,} + for key, value in groupdict.items(): + name, typeName = key.split(':') + ACType: Type[ACObject] = CommandsManager.classFromString(typeName) + parameters[name] = ACType(value) + results.append(SearchResult(command, parameters)) + + if results: return results + else: return [SearchResult(Command.QA, {'string': acstring,}),] + + def append(self, obj): # add new command to list + self.allCommands.append(obj) + + def getCommand(name): # TODO: quick search + for command in self.allCommands: + if command.name == name: return command + + @staticmethod + def classFromString(className: str) -> ACObject: + return getattr(sys.modules[__name__], className) + + @staticmethod + def background(answer = '', voice = ''): # make background cmd + def decorator(func): + def wrapper(text): + finishEvent = Event() + thread = RThread(target=func, args=(text, finish_event)) + thread.start() + return Response(voice = voice, text = answer, thread = ThreadData(thread, finishEvent) ) + return wrapper + return decorator diff --git a/Features/Command/RThread.py b/ArchieCore/Command/RThread.py similarity index 100% rename from Features/Command/RThread.py rename to ArchieCore/Command/RThread.py diff --git a/ArchieCore/Command/Response.py b/ArchieCore/Command/Response.py new file mode 100644 index 0000000..24583af --- /dev/null +++ b/ArchieCore/Command/Response.py @@ -0,0 +1,15 @@ +from typing import Optional +from .Command import Command +from .ThreadData import ThreadData + +class Response: + voice: str + text: str + callback: Optional[Command] + thread: Optional[ThreadData] + + def __init__(self, voice, text, callback = None, thread = None): + self.voice = voice + self.text = text + self.callback = callback + self.thread = thread diff --git a/ArchieCore/Command/ThreadData.py b/ArchieCore/Command/ThreadData.py new file mode 100644 index 0000000..d2f239c --- /dev/null +++ b/ArchieCore/Command/ThreadData.py @@ -0,0 +1,9 @@ +from .RThread import RThread, Event + +class ThreadData: + thread: RThread + finishEvent: Event + + def __init__(thread: RThread, finishEvent: Event): + self.thread = thread + self.finishEvent = finishEvent diff --git a/ArchieCore/Command/__init__.py b/ArchieCore/Command/__init__.py new file mode 100644 index 0000000..c5e52fa --- /dev/null +++ b/ArchieCore/Command/__init__.py @@ -0,0 +1,3 @@ +from .Command import * +from .Response import * +from .CommandsManager import CommandsManager, SearchResult diff --git a/Features/Command/synonyms.py b/ArchieCore/Command/synonyms.py similarity index 100% rename from Features/Command/synonyms.py rename to ArchieCore/Command/synonyms.py diff --git a/ArchieCore/Pattern/ACObjects/ACObject.py b/ArchieCore/Pattern/ACObjects/ACObject.py new file mode 100644 index 0000000..0f108ad --- /dev/null +++ b/ArchieCore/Pattern/ACObjects/ACObject.py @@ -0,0 +1,16 @@ +from abc import ABC, abstractmethod +from typing import Any +from .. import Pattern + +class classproperty(property): + def __get__(self, cls, owner): + return classmethod(self.fget).__get__(None, owner)() + +class ACObject(ABC): + pattern: Pattern # static getonly + value: Any + + @classproperty + @abstractmethod + def pattern() -> Pattern: + return Pattern('') diff --git a/ArchieCore/Pattern/ACObjects/__init__.py b/ArchieCore/Pattern/ACObjects/__init__.py new file mode 100644 index 0000000..31b500f --- /dev/null +++ b/ArchieCore/Pattern/ACObjects/__init__.py @@ -0,0 +1 @@ +from .ACObject import ACObject diff --git a/ArchieCore/Pattern/Pattern.py b/ArchieCore/Pattern/Pattern.py new file mode 100644 index 0000000..685bf72 --- /dev/null +++ b/ArchieCore/Pattern/Pattern.py @@ -0,0 +1,36 @@ +from typing import Type, Optional +import re + +from .expressions import expressions +from .ACObjects import * + +class Pattern: + origin: str + compiled: str #getonly + + def __init__(self, origin: str): + self.origin = origin + + @property + def compiled(self) -> str: # transform pattern to classic regex with named groups + pattern: str = self.origin + + # transform patterns to regexp + for ptrn, regex in expressions.items(): + pattern = re.sub(re.compile(ptrn), regex, pattern) + + # find and transform arguments like $name:Type + argumentRegex = re.compile(r'\$[:word:]:[:word:]') + while match := re.search(argumentRegex, pattern)[0]: + arg: str = match[1:] + argName, argTypeName = arg.split(':') + argType: Type[ACObject] = classFromString(argTypeName) + pattern = re.sub('\\'+link[0], f'(?P<{arg}>{argType.pattern.compiled})', pattern) + + return re.compile(pattern) + + + def match(self, string: str) -> Optional[dict[str, str]]: + if match := re.search(self.compiled, string): + return match.groupdict() + return None diff --git a/ArchieCore/Pattern/__init__.py b/ArchieCore/Pattern/__init__.py new file mode 100644 index 0000000..17f0022 --- /dev/null +++ b/ArchieCore/Pattern/__init__.py @@ -0,0 +1,2 @@ +from .Pattern import Pattern +from .ACObjects import * diff --git a/ArchieCore/Pattern/expressions.py b/ArchieCore/Pattern/expressions.py new file mode 100644 index 0000000..ea299db --- /dev/null +++ b/ArchieCore/Pattern/expressions.py @@ -0,0 +1,13 @@ +expressions = { + # stars * + r'([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)\*([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)': r'\\b\1.*\2\\b', # 'te*xt' + r'\*([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)': r'\\b.*\1', # '*text' + r'([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)\*': r'\1.*\\b', # 'text*' + r'(^|\s)\*($|\s)': r'.*', # '*' ' * ' + # one of the list (a|b|c) + r'\(((?:.*\|)*.*)\)': r'(?:\1)', + # 0 or 1 the of list [abc] + r'\[((?:.*\|?)*?.*?)\]': r'(?:\1)??', + # one or more of the list, without order {a|b|c} + r'\{((?:.*\|?)*?.*?)\}': r'(?:\1)+?', +} diff --git a/ArchieCore/__init__.py b/ArchieCore/__init__.py new file mode 100644 index 0000000..61a7d69 --- /dev/null +++ b/ArchieCore/__init__.py @@ -0,0 +1,2 @@ +from .Pattern import * +from .Command import * diff --git a/Controls/RemoteControl/RemoteControl.py b/Controls/RemoteControl/RemoteControl.py index 774929d..891d4ba 100644 --- a/Controls/RemoteControl/RemoteControl.py +++ b/Controls/RemoteControl/RemoteControl.py @@ -2,8 +2,8 @@ import time import requests import json as JSON -from Controls.Control import Control -from Features.Command import Command +from ..Control import Control +from ArchieCore import Command from Features import * class RemoteControl(Control): diff --git a/Controls/TelegramBot/TelegramBot.py b/Controls/TelegramBot/TelegramBot.py index 4e0afe1..c9cc921 100644 --- a/Controls/TelegramBot/TelegramBot.py +++ b/Controls/TelegramBot/TelegramBot.py @@ -3,9 +3,9 @@ import time import os import config -from Features import Command +from ArchieCore import Command from General import Text2Speech -from Controls.Control import Control +from ..Control import Control from .MyTeleBot import MyTeleBot class TelegramBot(Control): diff --git a/Controls/VoiceAssistant/VoiceAssistant.py b/Controls/VoiceAssistant/VoiceAssistant.py index 6075490..7cbb453 100644 --- a/Controls/VoiceAssistant/VoiceAssistant.py +++ b/Controls/VoiceAssistant/VoiceAssistant.py @@ -1,8 +1,8 @@ #!/usr/local/bin/python3.8 import os -from Controls.Control import Control +from ..Control import Control from General import SpeechRecognition, Text2Speech -from Features.Command import Command +from ArchieCore import CommandsManager import config class VoiceAssistant(Control): @@ -29,6 +29,7 @@ class VoiceAssistant(Control): print('\nYou: ', end='') speech = self.listener.listen() print(speech.get('text') or '', end='') + while True: if speech['status'] == 'error': break @@ -36,16 +37,19 @@ class VoiceAssistant(Control): self.voids += 1 break text = speech['text'] - cmd, params = Command.reg_find(text).values() - try: response = cmd.start(params) - except: break - self.reply(response) - self.check_threads() - self.report() - if response.callback: - speech = recognize(response.callback, {}) - else: - break + + for result in CommandsManager().search(text): + try: response = result.command.start(result.parameters) + except: break + + self.reply(response) + self.check_threads() + self.report() + + if response.callback: + speech = recognize(response.callback, {}) + else: + break def recognize(self, callback, params): print('\nYou: ', end='') @@ -54,6 +58,7 @@ class VoiceAssistant(Control): return speech text = speech['text'] print(text, end='') + while True: self.check_threads() if not callback: break diff --git a/Features/Command/Callback.py b/Features/Command/Callback.py deleted file mode 100644 index 72520f4..0000000 --- a/Features/Command/Callback.py +++ /dev/null @@ -1,32 +0,0 @@ -from .Command import Command -from .Response import Response -import re - -class Callback: - def __init__(self, patterns, quiet = False, once = True): - self.patterns = patterns - self.quiet = quiet - self.once = once - - def setStart(self, function): - self.start = function - - def start(self, params): - pass - - def answer(self, string): - for pattern in self.patterns: - if match := re.search(re.compile(Command.compilePattern(pattern)), string): - return self.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 diff --git a/Features/Command/Command.py b/Features/Command/Command.py deleted file mode 100644 index eef935e..0000000 --- a/Features/Command/Command.py +++ /dev/null @@ -1,245 +0,0 @@ -# Abstract class Command -# -# Command - parent of all command classes -# command - object (class instance) -# Command.list - list of all commands -# Command.find() - recognize command from a string by fuzzywuzzy, return command object -# must return dict like {'cmd': cmd, 'params': params} -# Command.reg_find() - recognize command from a string with regexe patterns, return command object -# must return dict like {'cmd': cmd, 'params': params} -# self - object (class instance) pointer (self) -# abstract self.start() - required method for all commands -# self.keywords - dictionary of arrays keywords -# like { -# (int)weight : ['word1', 'word2', 'word3'], -# (int)weight1 : ['word3', 'word4'], -# (int)weight2 : ['word5', 'word6', 'word7', 'word8', 'word9'], -# } -# self.patterns - list of command patterns -# self.subpatterns - list of subpaterns (context patterns) -# like ['* который * час *', '* скольк* * (врем|час)* *'] -# Command._entities - linked patterns $Pattern -# Command.regex - regex patterns dict for better syntax -# -# -# - - - -from abc import ABC, abstractmethod -import re -from .RThread import RThread, Event -from .synonyms import synonyms - -class Command(ABC): - _list = [] # list of all commands - _entities = { - 'word': lambda: r'\b[A-Za-zА-ЯЁа-яё0-9\-]+\b', - 'text': lambda: r'[A-Za-zА-ЯЁа-яё0-9\- ]+', - 'num': lambda: r'[0-9]+', - 'quest' : lambda: Command.compilePattern('(кто|что|как|какой|какая|какое|где|зачем|почему|сколько|чей|куда|когда)'), - 'repeat': lambda: Command.compilePattern('* ((повтор*)|(еще раз)|(еще*раз)*) *'), - 'true': lambda: Command.compilePattern('('+'|'.join(synonyms.get('да'))+')'), - 'false': lambda: Command.compilePattern('('+'|'.join(synonyms.get('нет'))+')'), - 'bool': lambda: Command.compilePattern('($true|$false)') - } - _regex = { - # stars * - '([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)\*([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)': r'\\b\1.*\2\\b', # 'te*xt' - '\*([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)': r'\\b.*\1', # '*text' - '([A-Za-zА-ЯЁа-яё0-9\(\)\[\]\{\}]+)\*': r'\1.*\\b', # 'text*' - '(^|\s)\*($|\s)': r'.*', # '*' ' * ' - # one of the list (a|b|c) - '\(((?:.*\|)*.*)\)': r'(?:\1)', - # 0 or 1 the of list [abc] - '\[((?:.*\|?)*?.*?)\]': r'(?:\1)??', - # one or more of the list, without order {a|b|c} - '\{((?:.*\|?)*?.*?)\}': r'(?:\1)+?', - } - def __init__(self, name, keywords = {}, patterns = [], subPatterns = []): # initialisation of new command - self._name = name - self._keywords = keywords - self._patterns = patterns - self._subPatterns = subPatterns - Command.append(self) - - def __str__(self): - str = f'{self.__class__.__name__}.{self.getName()}:\n' - for key, value in self._keywords.items(): - str += f'\t{key}:\t{value}\n' - return str - -###################################################################################### -# CONTROL KEYWORDS # -###################################################################################### - def getKeyword(self, string): # return position of the keyword - for weight, array in self._keywords.items(): - index = 0 - for word in array: - if string == word: - return (weight, index) - index += 1 - return None # if not found - - def removeKeyword(self, string): - position = self.getKeyword(string) - if(position): del self._keywords[ position[0] ][ position[1] ] - - def addKeyword(self, weight, string): # add new keywords to end of the list - if self.getKeyword(string): return - if( self._keywords.get(weight) ): self._keywords[weight].append(string) - else: self._keywords[weight] = [string] - - def changeKeyword(self, weight, name): # set new weight to keyword (find by name) - self.removeKeyword(name) - self.addKeyword(weight, name) - - def checkContext(self, string): # return cmd if the string matches the cmd context - for pattern in self.getSubPatterns(): - if match := re.search(re.compile(Command.compilePattern(pattern)), string): - return { - 'cmd': self, - 'params': {**match.groupdict(), 'string':string}, # return parans+initial text - } - raise Exception("Not Found") # raise exception if context not found - -###################################################################################### -# SETTERS # -###################################################################################### - def setStart(self, function): # define start (required) - self.start = function - -###################################################################################### -# GETTERS # -###################################################################################### - def getName(self): - return self._name - - def getKeywords(self): - return self._keywords - - def getPatterns(self): - return self._patterns - - def getSubPatterns(self): - return self._subPatterns - -###################################################################################### -# STATIC METHODS # -###################################################################################### - @staticmethod - def getList(): - return Command._list # all commands - - @staticmethod - def getRegexDict(): - return Command._regex # all standart patterns - - @staticmethod - def getEntity(key): - entity = Command._entities.get(key) # all linked $Pattern s - return entity() - - @staticmethod - def append(obj): # add new command to list - Command._list.append(obj) - - @staticmethod - def getCommand(name): # get command by name - for obj in Command.getList(): - if obj.getName() == name: return obj - - @staticmethod - def isRepeat(string): # return True if command is repeat-cmd - if re.search(re.compile(Command.getEntity('repeat')), string): return True - return False - - @staticmethod - def ratio(string, word): # get average distance of string and pattern - return ( fuzz.WRatio(string, word) + fuzz.ratio(string, word) ) / 2 - - @staticmethod - def compilePattern(pattern): # transform my syntax to standart regexp - # transform patterns to regexp - for ptrn, regex in Command.getRegexDict().items(): - pattern = re.sub(re.compile(ptrn), regex, pattern) - # find and replace links like $Pattern - while link := re.search(re.compile('\$[a-z]+'), pattern): - pattern = re.sub('\\'+link[0], f'(?P<{link[0][1:]}>{Command.getEntity( link[0][1:] )})', pattern) - # return compiled regexp - return pattern - - ''' - @staticmethod - def find(string): # find command by fuzzywuzzy - string = string.lower() - chances = {} - list = Command.getList() - # calculate chances of every command - for i, obj in enumerate( list ): - chances[i] = 0 - k = 1 / ( sum( [int(w)*len(kw) for w, kw in obj.getKeywords().items()] ) or 1 ) - for weight, kws in obj.getKeywords().items(): - for kw in kws: - chances[i] += Command.ratio(string, kw) * weight * k - # find command with the biggest chance - if( sum( chances.values() ) ): - top = max( chances.values() ) / sum( chances.values() ) * 100 - else: # if all chances is 0 - return { - 'cmd': Command.QA, - 'params': {'string':string,}, - } - #if( max( chances.values() ) < 800 or top < 50): return list[0] - # find top command obj - for i, chance in chances.items(): - if chance == max( chances.values() ): - return { - 'cmd': Command.QA, #dialog mode - 'params': {'string':string,}, - } - ''' - - @staticmethod - def reg_find(string): # find comman by pattern - string = string.lower() - list = Command.getList() - if not string: return { - 'cmd': Command.getCommand('Hello'), - 'params': {'string':string,}, - } - # find command obj by pattern - for obj in list: - for pattern in obj.getPatterns(): - if match := re.search(re.compile(Command.compilePattern(pattern)), string): - if params := match.groupdict(): - if params.get('true'): params['bool'] = True - if params.get('false'): params['bool'] = False - return { - 'cmd': obj, - 'params': {**params, 'string':string,}, - } - # return Question-Answer system if command not found - return { - 'cmd': Command.QA, - 'params': {'string':string,}, - } - - @staticmethod #TODO: pass Response instead of answer and voice; create ThreadData struct - def background(answer = '', voice = ''): # make background cmd - def decorator(func): #wrapper of wrapper (decorator of decorator) - def wrapper(text): - finish_event = Event() - thread = RThread(target=func, args=(text, finish_event)) - thread.start() - return Response(voice = voice, text = answer, thread = {'thread': thread, 'finish_event': finish_event} ) - return wrapper - return decorator - - @classmethod - def new(cls, *args, **kwargs): - def creator(func): - cmd: Command = cls(*args, **kwargs) - cmd.setStart(func) - return func - return creator diff --git a/Features/Command/Response.py b/Features/Command/Response.py deleted file mode 100644 index cec427b..0000000 --- a/Features/Command/Response.py +++ /dev/null @@ -1,6 +0,0 @@ -class Response: - def __init__(self, voice, text, callback = None, thread = None): - self.voice: str = voice - self.text: str = text - self.callback: Callback = callback - self.thread = thread diff --git a/Features/Command/__init__.py b/Features/Command/__init__.py deleted file mode 100644 index 2e91fba..0000000 --- a/Features/Command/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .Command import * -from .Callback import * -from .Response import * diff --git a/Features/Media/Media.py b/Features/Media/Media.py index e4875bd..c49dec9 100644 --- a/Features/Media/Media.py +++ b/Features/Media/Media.py @@ -1,4 +1,4 @@ -from ..Command import Command +from ArchieCore import Command class Media(Command): def start(self, string): # main method diff --git a/Features/Media/kinogo.py b/Features/Media/kinogo.py index 4d09fbe..7e57487 100644 --- a/Features/Media/kinogo.py +++ b/Features/Media/kinogo.py @@ -2,7 +2,7 @@ from .Media import * import requests from bs4 import BeautifulSoup as BS import os -from ..Command import Callback, Response +from ArchieCore import Response ################################################################################ def findPage(name): query = name + ' site:kinogo.by' @@ -128,7 +128,7 @@ kinogo_film_cb = Callback(['$text',]) kinogo_film_cb.setStart(start_film) patterns = ['* включ* фильм $text', '* включ* фильм*'] -kinogo_film = Media('KinogoFilm', {}, patterns) +kinogo_film = Media('KinogoFilm', patterns) kinogo_film.setStart(film) @@ -136,5 +136,5 @@ kinogo_serial_cb = Callback(['$text',]) kinogo_serial_cb.setStart(start_serial) patterns = ['* включ* сериал $text', '* включ* сериал*'] -kinogo_serial = Media('KinogoSerial', {}, patterns) +kinogo_serial = Media('KinogoSerial', patterns) kinogo_serial.setStart(serial) diff --git a/Features/QA/QA.py b/Features/QA/QA.py index 434d6f8..e3646e7 100644 --- a/Features/QA/QA.py +++ b/Features/QA/QA.py @@ -1,5 +1,5 @@ from bs4 import BeautifulSoup as BS -from ..Command import Command, Response +from ArchieCore import Command, Response import wikipedia as wiki import requests import random @@ -71,4 +71,4 @@ class QA(Command): voice = text = search or random.choice(['Не совсем понимаю, о чём вы.', 'Вот эта последняя фраза мне не ясна.', 'А вот это не совсем понятно.', 'Можете сказать то же самое другими словами?', 'Вот сейчас я совсем вас не понимаю.', 'Попробуйте выразить свою мысль по-другому',]) return Response(text = text, voice = voice) -Command.QA = QA('QA', {}, []) +Command.QA = QA('QA', []) diff --git a/Features/Raspi/Raspi.py b/Features/Raspi/Raspi.py index 5d13c3c..e30e541 100644 --- a/Features/Raspi/Raspi.py +++ b/Features/Raspi/Raspi.py @@ -1,4 +1,4 @@ -from ..Command import Command # import parent class +from ArchieCore import Command # import parent class import os class Raspi(Command): diff --git a/Features/Raspi/gitpull.py b/Features/Raspi/gitpull.py index 171968e..6edfafc 100644 --- a/Features/Raspi/gitpull.py +++ b/Features/Raspi/gitpull.py @@ -1,6 +1,6 @@ from .Raspi import * import os -from ..Command import Callback, Response +from ArchieCore import CommandsManager, Callback, Response import config ################################################################################ def reboot(params): @@ -11,7 +11,7 @@ def reboot(params): reboot_cb = Callback(['$bool',]) reboot_cb.setStart(reboot) -@Raspi.background(answer = 'Проверяю обновления...', voice = 'Проверяю обновления') +@CommandsManager.background(answer = 'Проверяю обновления...', voice = 'Проверяю обновления') 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(): @@ -24,5 +24,5 @@ def method(params, finish_event): return Response(text = text, voice = voice, callback = reboot_cb) patterns = ['* обновись *', '* можешь обновиться *', '* обнови себя *', '* скачай обновлени* *', '* провер* обновлени* *'] -gitpull = Raspi('git pull archie.git', [], patterns) +gitpull = Raspi('git pull archie.git', patterns) gitpull.setStart(method) diff --git a/Features/Raspi/tv.py b/Features/Raspi/tv.py index 19ffc69..81aaf4e 100644 --- a/Features/Raspi/tv.py +++ b/Features/Raspi/tv.py @@ -1,5 +1,5 @@ from .Raspi import * -from ..Command import Response +from ArchieCore import Response ################################################################################ def method(params): Raspi.hdmi_cec('on 0') @@ -7,9 +7,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* включи* (телевизор|экран) *'] -tv_on = Raspi('tv on', keywords, patterns) +tv_on = Raspi('tv on', patterns) tv_on.setStart(method) ################################################################################ def method(params): @@ -17,9 +16,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* (выключи|отключи)* (телевизор|экран) *'] -tv_off = Raspi('tv off', keywords, patterns) +tv_off = Raspi('tv off', patterns) tv_off.setStart(method) ################################################################################ def method(params): @@ -28,9 +26,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* (выведи|вывести|покажи|открой|показать|открыть) * с (провода|hdmi|кабеля|порта) * $num *'] -tv_hdmi = Raspi('tv hdmi source', keywords, patterns) +tv_hdmi = Raspi('tv hdmi source', patterns) tv_hdmi.setStart(method) ################################################################################ def method(params): @@ -38,9 +35,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* (выведи|вывести|покажи|открой|показать|открыть) * с (ноута|ноутбука|планшета|провода|hdmi)'] -tv_hdmi = Raspi('tv hdmi source', keywords, patterns) +tv_hdmi = Raspi('tv hdmi source', patterns) tv_hdmi.setStart(method) ################################################################################ def method(params): @@ -48,7 +44,6 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* (верни|вернуть|включи*|покажи|показать) [основн|нормальн|стандартн|привычн]* (телевизор|экран|картинк|изображение) *'] -tv_rpi = Raspi('tv rpi source', keywords, patterns) +tv_rpi = Raspi('tv rpi source', patterns) tv_rpi.setStart(method) diff --git a/Features/SmallTalk/SmallTalk.py b/Features/SmallTalk/SmallTalk.py index 3c5d1fb..672c38e 100644 --- a/Features/SmallTalk/SmallTalk.py +++ b/Features/SmallTalk/SmallTalk.py @@ -3,7 +3,7 @@ # Module for speaking with voice assistent # See class Command -from ..Command import Command # import parent class +from ArchieCore import Command # import parent class class SmallTalk(Command): def start(self, string): # main method diff --git a/Features/SmallTalk/ctime.py b/Features/SmallTalk/ctime.py index b36b9cf..e5b87b0 100644 --- a/Features/SmallTalk/ctime.py +++ b/Features/SmallTalk/ctime.py @@ -3,7 +3,7 @@ import requests from bs4 import BeautifulSoup as BS import math from .SmallTalk import SmallTalk -from ..Command import Response +from ArchieCore import Response ################################################################################ def method(params): if city := params.get('text'): @@ -88,12 +88,12 @@ def method(params): return Response(text = text, voice = voice) -keywords = { - 10: ['который час', 'сколько времени'], - 5: ['текущее', 'сейчас', 'время'], - 1: ['сколько'] -} +# keywords = { +# 10: ['который час', 'сколько времени'], +# 5: ['текущее', 'сейчас', 'время'], +# 1: ['сколько'] +# } patterns = ['* который * час в $text', '* скольк* * (врем|час)* * в $text', '* время в $text', '* который * час *', '* скольк* * (врем|час)* *'] -subpatterns = ['а (сейчас|в $text)'] -ctime = SmallTalk('Current Time', keywords, patterns, subpatterns) +# subpatterns = ['а (сейчас|в $text)'] +ctime = SmallTalk('Current Time', patterns) ctime.setStart(method) diff --git a/Features/SmallTalk/hello.py b/Features/SmallTalk/hello.py index d2d0dba..ca70648 100644 --- a/Features/SmallTalk/hello.py +++ b/Features/SmallTalk/hello.py @@ -1,5 +1,5 @@ from .SmallTalk import * -from ..Command import Response +from ArchieCore import Response ################################################################################ @SmallTalk.new('Hello', patterns = ['* привет* *',]) diff --git a/Features/SmartHome/SmartHome.py b/Features/SmartHome/SmartHome.py index 8fcb7bb..1447d34 100644 --- a/Features/SmartHome/SmartHome.py +++ b/Features/SmartHome/SmartHome.py @@ -5,7 +5,7 @@ import spidev import time import json as JSON from threading import Thread -from ..Command import Command +from ArchieCore import Command GPIO.setmode(GPIO.BCM) diff --git a/Features/SmartHome/alarmclock.py b/Features/SmartHome/alarmclock.py index bd4fbd1..1cfa424 100644 --- a/Features/SmartHome/alarmclock.py +++ b/Features/SmartHome/alarmclock.py @@ -1,5 +1,5 @@ from .SmartHome import * -from ..Command import Response, Command +from ArchieCore import Response, Command import Text2Speech import os ################################################################################ @@ -19,7 +19,6 @@ def method(params): Text2Speech.Engine().generate(voice).speak() return Response(text = text, voice = voice) -keywords = {} patterns = [] -alarmclock = SmartHome('alarmclock', keywords, patterns) +alarmclock = SmartHome('alarmclock', patterns) alarmclock.setStart(method) diff --git a/Features/SmartHome/light.py b/Features/SmartHome/light.py index 7eaa2bf..1bc3d96 100644 --- a/Features/SmartHome/light.py +++ b/Features/SmartHome/light.py @@ -1,5 +1,5 @@ from .SmartHome import * -from ..Command import Response +from ArchieCore import Response ################################################################################ def method(params): @@ -10,9 +10,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* (включ|выключ)* свет *'] -main_light = SmartHome('main_light', keywords, patterns) +main_light = SmartHome('main_light', patterns) main_light.setStart(method) ################################################################################ @@ -26,9 +25,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* включи* подсветку *'] -light_on = SmartHome('led_on', keywords, patterns) +light_on = SmartHome('led_on', patterns) light_on.setStart(method) ################################################################################ @@ -41,9 +39,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* выключи* подсветку *'] -led_off = SmartHome('led_off', keywords, patterns) +led_off = SmartHome('led_off', patterns) led_off.setStart(method) ################################################################################ @@ -56,9 +53,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = [] -led_hello = SmartHome('led_hello', keywords, patterns) +led_hello = SmartHome('led_hello', patterns) led_hello.setStart(method) ################################################################################ diff --git a/Features/SmartHome/main_light.py b/Features/SmartHome/main_light.py index f3825e0..2bccc08 100644 --- a/Features/SmartHome/main_light.py +++ b/Features/SmartHome/main_light.py @@ -1,5 +1,5 @@ from .SmartHome import * -from ..Command import Response +from ArchieCore import Response ################################################################################ def method(params): @@ -10,7 +10,6 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* (включ|выключ)* свет *'] -main_light = SmartHome('main_light', keywords, patterns) +main_light = SmartHome('main_light', patterns) main_light.setStart(method) diff --git a/Features/SmartHome/window.py b/Features/SmartHome/window.py index f081642..b93766c 100644 --- a/Features/SmartHome/window.py +++ b/Features/SmartHome/window.py @@ -10,9 +10,8 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* (открыть|открой) (окно|окна) *', '* (подними|поднять) (шторы|роллеты) *'] -window_open = SmartHome('window_open', keywords, patterns) +window_open = SmartHome('window_open', patterns) window_open.setStart(method) ################################################################################ @@ -25,7 +24,6 @@ def method(params): voice = text = '' return Response(text = text, voice = voice) -keywords = {} patterns = ['* (закрыть|закрой) (окно|окна) *', '* (опусти|опустить) (шторы|роллеты) *'] -window_close = SmartHome('window_close', keywords, patterns) +window_close = SmartHome('window_close', patterns) window_close.setStart(method) diff --git a/Features/Zieit/Zieit.py b/Features/Zieit/Zieit.py index 4dbf96b..c4a6ddd 100644 --- a/Features/Zieit/Zieit.py +++ b/Features/Zieit/Zieit.py @@ -1,4 +1,4 @@ -from ..Command import Command, Response # import parent class +from ArchieCore import Command, Response # import parent class import urllib.request import xlrd import xlwt diff --git a/Features/Zieit/myshedule.py b/Features/Zieit/myshedule.py index 09d1cef..91529ba 100644 --- a/Features/Zieit/myshedule.py +++ b/Features/Zieit/myshedule.py @@ -33,9 +33,8 @@ def formatDay(lessons): def nextLessonFunc(params): return formatLesson(Zieit.getNextLesson(Zieit.lessonsStartTime)) -keywords = {} patterns = ['* следующ* (предмет|урок|пара)'] -nextLesson = Zieit('Next Lesson', keywords, patterns) +nextLesson = Zieit('Next Lesson', patterns) nextLesson.setStart(nextLessonFunc) ################################################################################ @@ -43,9 +42,8 @@ nextLesson.setStart(nextLessonFunc) def currentLessonFunc(params): return formatLesson(Zieit.getNextLesson(Zieit.lessonsEndTime)) -keywords = {} patterns = ['* (текущ*|сейчас) (предмет|урок|пара)'] -currentLesson = Zieit('Current Lesson', keywords, patterns) +currentLesson = Zieit('Current Lesson', patterns) currentLesson.setStart(currentLessonFunc) ################################################################################ @@ -57,10 +55,8 @@ def todaysSheduleFunc(params): text = voice = 'Сегодня пар нет' return Response(text = text, voice = voice) - -keywords = {} patterns = ['* сегодня (предметы|уроки|пары|расписание)', '* (предметы|уроки|пары|расписание) * сегодня *'] -todaysShedule = Zieit('Todays Shedule', keywords, patterns) +todaysShedule = Zieit('Todays Shedule', patterns) todaysShedule.setStart(todaysSheduleFunc) ################################################################################ @@ -72,7 +68,6 @@ def tomorrowsSheduleFunc(params): text = voice = 'Завтра пар нет' return Response(text = text, voice = voice) -keywords = {} patterns = ['* завтра (предметы|уроки|пары|расписание)', '* (предметы|уроки|пары|расписание) * завтра *'] -tomorrowsShedule = Zieit('Todays Shedule', keywords, patterns) +tomorrowsShedule = Zieit('Todays Shedule', patterns) tomorrowsShedule.setStart(tomorrowsSheduleFunc) diff --git a/Features/__init__.py b/Features/__init__.py index 428617e..c381d95 100644 --- a/Features/__init__.py +++ b/Features/__init__.py @@ -1,6 +1,3 @@ -from .Command import Command -from .Command import Response - from .Media import Media from .QA.QA import QA from .SmallTalk import SmallTalk @@ -8,4 +5,4 @@ from .Raspi import Raspi from .Zieit import Zieit try: from .SmartHome import SmartHome -except: print('cannot import module named "SmartHome" from Features/Smarthome\n') +except: pass diff --git a/General/Text2Speech/TTS.py b/General/Text2Speech/TTS.py index d460b5b..9e05f07 100644 --- a/General/Text2Speech/TTS.py +++ b/General/Text2Speech/TTS.py @@ -1,5 +1,6 @@ from google.cloud import texttospeech import os +os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = 'hide' from pygame import mixer from time import sleep import mmap diff --git a/README.md b/README.md index b896196..b69609c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,27 @@ # A.R.C.H.I.E. -Voice assistant, smart home hub, media center and smart tv +##Voice assistant, smart home hub, media center and smart tv + +###Project structure: + - ####ArchieCore - Core, base classes + - Command + - CommandsManager + - SearchResult + - Response + - ThreadData + - Pattern + - ACObject and subclasses + - ####Controls - Responsible for user interaction + - Control(ABC) + - VoiceAssistant + - TelegramBot + - RemoteControl + - Django + - ####Features - Possibilities, set of functions + - ####General - For helper classes + - ####Raspberry - Control system and hardware + +###Root files: + - **start.py** - entry point + - **config.example.py** - file for settings. Copy as config.py and type own paraneters + - **helper.py** - use in terminal for creating new modules, commands, features, controls, etc. + - **dependences.txt** - list of all dependences (Python version and libraries) diff --git a/libs.txt b/dependences.txt similarity index 84% rename from libs.txt rename to dependences.txt index e9043fd..8c00d9a 100644 --- a/libs.txt +++ b/dependences.txt @@ -1,3 +1,4 @@ +Python 3.10 pip install speech_recognition pip install google-cloud-texttospeech pip install pygame @@ -7,4 +8,4 @@ pip install xlrd pip install xlwt pip install xlutils pip install pyaudio # if instalation fails, try install from .whl (https://www.lfd.uci.edu/~gohlke/pythonlibs/#pyaudio) -pip install pip install PyTelegramBotApi \ No newline at end of file +pip install pip install PyTelegramBotApi diff --git a/startup[deprecated].py b/startup[deprecated].py deleted file mode 100644 index 6496291..0000000 --- a/startup[deprecated].py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/local/bin/python3.8 -import os -import config - -os.system('git pull') - -modules = { - 'Voice Assistant': 'voice_assistant', - 'Telegram bot': 'telegram_bot', -} - -for name, module in modules.items(): - try: - print(f'launching the {name}') - os.system(f'lxterminal --command="python3.8 {config.path}/{module}.py & read"') - except: - print(f'[error]\t{name} launch failed') - -print('Running server...') -os.system(f'lxterminal --command="python3.8 {config.path}/Controls/Django/manage.py runserver 192.168.0.129:8000 & read"') -os.system(f'lxterminal --command="vlc"')