From e3e7e32fe6fc9675011b991623d9229fbe648b57 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 10 Oct 2020 13:23:53 +0200 Subject: [PATCH 001/126] :sparkles: this is beta 4 --- core/commons/constants.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/commons/constants.py b/core/commons/constants.py index c70c99acb..059379a69 100644 --- a/core/commons/constants.py +++ b/core/commons/constants.py @@ -1,4 +1,4 @@ -VERSION = '1.0.0-b3' +VERSION = '1.0.0-b4' DEFAULT = 'default' UNKNOWN_WORD = 'unknownword' From bc843da3a6e45debc2e7eb67d02351f6b27ea98a Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 10 Oct 2020 14:10:13 +0200 Subject: [PATCH 002/126] :construction: Reworked pre and post conf update to allow reference to a manager to declutter ConfigManager. Cleaned up AdminView --- config-schema.json | 4 +-- configTemplate.json | 8 +++++ core/base/ConfigManager.py | 53 +++++++++++++++++++++++++++---- core/interface/views/AdminView.py | 18 ++++------- 4 files changed, 63 insertions(+), 20 deletions(-) diff --git a/config-schema.json b/config-schema.json index 8678c05d6..59652dbf3 100644 --- a/config-schema.json +++ b/config-schema.json @@ -66,11 +66,11 @@ }, "beforeUpdate": { "type" : "string", - "pattern": "^[a-z].*$" + "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$" }, "onUpdate" : { "type" : "string", - "pattern": "^[a-z].*$" + "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$" }, "parent" : { "type" : "object", diff --git a/configTemplate.json b/configTemplate.json index 30a698fe4..c5213f983 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -649,6 +649,14 @@ "description" : "Change the web interface port to be used", "category" : "system" }, + "scenariosActive" : { + "defaultValue": false, + "dataType" : "boolean", + "isSensitive" : false, + "description" : "Activates the scenarios support on the webinterface, using node-red.", + "category" : "system", + "onUpdate" : "NodeRedManager.toggle" + }, "devMode" : { "defaultValue": false, "dataType" : "boolean", diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index 8ce65615f..bc46a62ac 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -131,13 +131,14 @@ def loadJsonFromFile(jsonFile: Path) -> dict: raise - def updateAliceConfiguration(self, key: str, value: typing.Any): + def updateAliceConfiguration(self, key: str, value: typing.Any, dump: bool = True): """ Updating a core config is sensitive, if the request comes from a skill. First check if the request came from a skill at anytime and if so ask permission to the user :param key: str :param value: str + :param dump: bool If set to False, the configs won't be dumped to the json file :return: None """ @@ -167,8 +168,18 @@ def updateAliceConfiguration(self, key: str, value: typing.Any): self.logWarning(f'Was asked to update **{key}** but key doesn\'t exist') raise ConfigurationUpdateFailed() + pre = self.getAliceConfUpdatePreProcessing(key) + if pre and not self.ConfigManager.doConfigUpdatePreProcessing(pre, value): + return + self._aliceConfigurations[key] = value - self.writeToAliceConfigurationFile() + + if dump: + self.writeToAliceConfigurationFile() + + pp = self.ConfigManager.getAliceConfUpdatePostProcessing(key) + if pp: + self.ConfigManager.doConfigUpdatePostProcessing(pp) def bulkUpdateAliceConfigurations(self): @@ -179,7 +190,7 @@ def bulkUpdateAliceConfigurations(self): if key not in self._aliceConfigurations: self.logWarning(f'Was asked to update **{key}** but key doesn\'t exist') continue - self._aliceConfigurations[key] = value + self.updateAliceConfiguration(key, value, False) self.writeToAliceConfigurationFile() self.deletePendingAliceConfigurationUpdates() @@ -501,7 +512,18 @@ def getAliceConfUpdatePostProcessing(self, confName: str) -> typing.Optional[str def doConfigUpdatePreProcessing(self, function: str, value: typing.Any) -> bool: # Call alice config pre processing functions. try: - func = getattr(self, function) + if '.' in function: + manager, function = function.split('.') + + try: + mngr = getattr(self, manager) + except AttributeError: + self.logWarning(f'Config pre processing manager **{manager}** does not exist') + return False + else: + mngr = self + + func = getattr(mngr, function) except AttributeError: self.logWarning(f'Configuration pre processing method **{function}** does not exist') return False @@ -513,12 +535,27 @@ def doConfigUpdatePreProcessing(self, function: str, value: typing.Any) -> bool: return False - def doConfigUpdatePostProcessing(self, functions: set): + def doConfigUpdatePostProcessing(self, functions: typing.Union[str, set]): # Call alice config post processing functions. This will call methods that are needed after a certain setting was # updated while Project Alice was running + + if isinstance(functions, str): + functions = {functions} + for function in functions: try: - func = getattr(self, function) + if '.' in function: + manager, function = function.split('.') + + try: + mngr = getattr(self, manager) + except AttributeError: + self.logWarning(f'Config post processing manager **{manager}** does not exist') + return False + else: + mngr = self + + func = getattr(mngr, function) except AttributeError: self.logWarning(f'Configuration post processing method **{function}** does not exist') continue @@ -611,6 +648,10 @@ def toggleDebugLogs(self): logging.getLogger('ProjectAlice').setLevel(logging.WARN) + def toggleNodeRed(self): + self.NodeRedManager.toggle() + + def getGithubAuth(self) -> tuple: username = self.getAliceConfigByName('githubUsername') token = self.getAliceConfigByName('githubToken') diff --git a/core/interface/views/AdminView.py b/core/interface/views/AdminView.py index f95a27aa5..75c89e006 100644 --- a/core/interface/views/AdminView.py +++ b/core/interface/views/AdminView.py @@ -3,6 +3,7 @@ from flask import jsonify, render_template, request from flask_login import login_required +from core.ProjectAliceExceptions import ConfigurationUpdateFailed from core.interface.model.View import View @@ -49,23 +50,16 @@ def saveAliceSettings(self): # or float because HTTP data is type less. confs = {key: self.retrieveValue(value) for key, value in request.form.items()} - postProcessing = set() for conf, value in confs.items(): if value == self.ConfigManager.getAliceConfigByName(conf): continue - pre = self.ConfigManager.getAliceConfUpdatePreProcessing(conf) - if pre and not self.ConfigManager.doConfigUpdatePreProcessing(pre, value): - continue - - pp = self.ConfigManager.getAliceConfUpdatePostProcessing(conf) - if pp: - postProcessing.add(pp) - - confs['supportedLanguages'] = self.ConfigManager.getAliceConfigByName('supportedLanguages') + try: + self.ConfigManager.updateAliceConfiguration(conf, value, False) + except ConfigurationUpdateFailed as e: + self.logError(f'Updating config failed for **{conf}**: {e}') - self.ConfigManager.writeToAliceConfigurationFile(confs=confs) - self.ConfigManager.doConfigUpdatePostProcessing(postProcessing) + self.ConfigManager.writeToAliceConfigurationFile() return self.index() except Exception as e: self.logError(f'Failed saving Alice config: {e}') From ec5b20b01a493eeea69c3641ade7b1156c47b7d4 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 10 Oct 2020 14:19:29 +0200 Subject: [PATCH 003/126] :construction: Work on NodeRedManager started, time to go home --- core/base/ConfigManager.py | 4 -- core/base/model/AliceSkill.py | 14 +++---- core/base/model/Manager.py | 2 + core/base/model/ProjectAliceObject.py | 5 +++ core/interface/NodeRedManager.py | 53 +++++++++++++++++++++------ 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index bc46a62ac..0d9cd198b 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -648,10 +648,6 @@ def toggleDebugLogs(self): logging.getLogger('ProjectAlice').setLevel(logging.WARN) - def toggleNodeRed(self): - self.NodeRedManager.toggle() - - def getGithubAuth(self) -> tuple: username = self.getAliceConfigByName('githubUsername') token = self.getAliceConfigByName('githubToken') diff --git a/core/base/model/AliceSkill.py b/core/base/model/AliceSkill.py index dde89e250..8a659a2c2 100644 --- a/core/base/model/AliceSkill.py +++ b/core/base/model/AliceSkill.py @@ -59,8 +59,8 @@ def __init__(self, supportedIntents: Iterable = None, databaseSchema: dict = Non self._widgets = dict() self._deviceTypes = dict() self._intentsDefinitions = dict() - self._scenarioNodeName = '' - self._scenarioNodeVersion = Version(mainVersion=0, updateVersion=0, hotfix=0) + self._scenarioPackageName = '' + self._scenarioPackageVersion = Version(mainVersion=0, updateVersion=0, hotfix=0) self._supportedIntents: Dict[str, Intent] = self.buildIntentList(supportedIntents) self.loadIntentsDefinition() @@ -103,8 +103,8 @@ def loadScenarioNodes(self): try: with path.open('r') as fp: data = json.load(fp) - self._scenarioNodeName = data['name'] - self._scenarioNodeVersion = Version.fromString(data['version']) + self._scenarioPackageName = data['name'] + self._scenarioPackageVersion = Version.fromString(data['version']) except Exception as e: self.logWarning(f'Failed to load scenario nodes: {e}') @@ -402,12 +402,12 @@ def delayed(self, value: bool): @property def scenarioNodeName(self) -> str: - return self._scenarioNodeName + return self._scenarioPackageName @property def scenarioNodeVersion(self) -> Version: - return self._scenarioNodeVersion + return self._scenarioPackageVersion @property @@ -431,7 +431,7 @@ def instructions(self) -> str: def hasScenarioNodes(self) -> bool: - return self._scenarioNodeName != '' + return self._scenarioPackageName != '' def subscribeIntents(self): diff --git a/core/base/model/Manager.py b/core/base/model/Manager.py index f2bc47f8f..567476c34 100644 --- a/core/base/model/Manager.py +++ b/core/base/model/Manager.py @@ -41,11 +41,13 @@ def getFunctionCaller(self) -> Optional[str]: def onStart(self): self.logInfo(f'Starting') + self._isActive = True return self._initDB() def onStop(self): self.logInfo(f'Stopping') + self._isActive = False def _initDB(self): diff --git a/core/base/model/ProjectAliceObject.py b/core/base/model/ProjectAliceObject.py index ac7882c3b..94bd93a44 100644 --- a/core/base/model/ProjectAliceObject.py +++ b/core/base/model/ProjectAliceObject.py @@ -750,3 +750,8 @@ def LocationManager(self): #NOSONAR @property def WakewordManager(self): #NOSONAR return SM.SuperManager.getInstance().wakewordManager + + + @property + def NodeRedManager(self): #NOSONAR + return SM.SuperManager.getInstance().nodeRedManager diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 9e411fa6c..2dfe57ddf 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -7,29 +7,52 @@ class NodeRedManager(Manager): + PACKAGE_PATH = Path('../.node-red/package.json') + def __init__(self): - super().__init__() + if not self.ConfigManager.getAliceConfigByName('scenariosActive'): + self.isActive = False + else: + super().__init__() def onStart(self): + if not self.isActive: + return + super().onStart() + + if not self.PACKAGE_PATH.exists(): + self.logInfo('Node red not found, installing, this might take a while...') + self.injectSkillNodes() self.Commons.runRootSystemCommand(['systemctl', 'start', 'nodered']) def onStop(self): + if not self.isActive: + return + super().onStop() self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) + def toggle(self): + if self.isActive: + self.onStop() + else: + self.onStart() + + def reloadServer(self): self.Commons.runRootSystemCommand(['systemctl', 'restart', 'nodered']) def injectSkillNodes(self): - package = Path('../.node-red/package.json') - if not package.exists(): - #self.logWarning('Package json file for Node Red is missing. Is Node Red even installed?') + restart = False + + if not self.PACKAGE_PATH.exists(): + self.logWarning('Package json file for Node Red is missing. Is Node Red even installed?') return for skillName, tup in self.SkillManager.allScenarioNodes().items(): @@ -40,15 +63,21 @@ def injectSkillNodes(self): install = self.Commons.runSystemCommand(f'cd ~/.node-red && npm install {scenarioNodePath}', shell=True) if install.returncode == 1: self.logWarning(f'Something went wrong installing new node: {install.stderr}') + else: + restart = True continue - with path.open('r') as fp: - data = json.load(fp) - version = Version.fromString(data['version']) + data = json.loads(path.read_text()) + version = Version.fromString(data['version']) + + if version < scenarioNodeVersion: + self.logInfo('New scenario node update found') + install = self.Commons.runSystemCommand(f'cd ~/.node-red && npm install {scenarioNodePath}', shell=True) + if install.returncode == 1: + self.logWarning(f'Something went wrong updating node: {install.stderr}') + else: + restart = True - if version < scenarioNodeVersion: - self.logInfo('New scenario node update found') - install = self.Commons.runSystemCommand(f'cd ~/.node-red && npm install {scenarioNodePath}', shell=True) - if install.returncode == 1: - self.logWarning(f'Something went wrong updating node: {install.stderr}') + if restart: + self.reloadServer() From 7178b0e1eed8fc579b70ec47fe0eda0f45bde1ce Mon Sep 17 00:00:00 2001 From: Psycho Date: Sat, 10 Oct 2020 14:27:14 +0200 Subject: [PATCH 004/126] Update pytest.yml --- .github/workflows/pytest.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 9c642da9d..86d008ca7 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -5,7 +5,6 @@ name: Pytest and Sonarcloud analysis on: push: - pull_request: jobs: test: From 31e22e234f961a85e4f76fa3db0391b5f077d092 Mon Sep 17 00:00:00 2001 From: Psycho Date: Sat, 10 Oct 2020 16:03:39 +0200 Subject: [PATCH 005/126] :sparkles: Finding, installing and updating nodes implemented #321 --- core/base/SkillManager.py | 15 ++++++++++++++- core/interface/NodeRedManager.py | 31 +++++++++++++++++++------------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index fbd3bf897..117e8a468 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -34,7 +34,8 @@ class SkillManager(Manager): DATABASE = { 'skills' : [ 'skillName TEXT NOT NULL UNIQUE', - 'active INTEGER NOT NULL DEFAULT 1' + 'active INTEGER NOT NULL DEFAULT 1', + 'scenarioVersion TEXT NOT NULL DEFAULT "0.0.0"', ], 'widgets': [ 'parent TEXT NOT NULL UNIQUE', @@ -913,6 +914,18 @@ def allScenarioNodes(self) -> Dict[str, tuple]: return ret + def getSkillScenarioVersion(self, skillName: str) -> Version: + if skillName not in self._skillList: + return Version.fromString('0.0.0') + else: + query = 'SELECT * FROM :__table__ WHERE skillName = :skillName' + data = self.DatabaseManager.fetch(tableName='skills', query=query, values={'skillName': skillName}, callerName=self.name) + if not data: + return Version.fromString('0.0.0') + + return Version.fromString(data['scenarioVersion']) + + def wipeSkills(self, addDefaults: bool = True): shutil.rmtree(Path(self.Commons.rootDir(), 'skills')) Path(self.Commons.rootDir(), 'skills').mkdir() diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 2dfe57ddf..5a965b8d5 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -10,13 +10,12 @@ class NodeRedManager(Manager): PACKAGE_PATH = Path('../.node-red/package.json') def __init__(self): - if not self.ConfigManager.getAliceConfigByName('scenariosActive'): - self.isActive = False - else: - super().__init__() + super().__init__() def onStart(self): + self.isActive = self.ConfigManager.getAliceConfigByName('scenariosActive') + if not self.isActive: return @@ -59,25 +58,33 @@ def injectSkillNodes(self): scenarioNodeName, scenarioNodeVersion, scenarioNodePath = tup path = Path('../.node-red/node_modules', scenarioNodeName, 'package.json') if not path.exists(): - self.logInfo('New scenario node found') + self.logInfo(f'New scenario node found for skill **{skillName}**: {scenarioNodeName}') install = self.Commons.runSystemCommand(f'cd ~/.node-red && npm install {scenarioNodePath}', shell=True) if install.returncode == 1: - self.logWarning(f'Something went wrong installing new node: {install.stderr}') + self.logWarning(f'Something went wrong installing new node **{scenarioNodeName}**: {install.stderr}') else: restart = True continue - data = json.loads(path.read_text()) - version = Version.fromString(data['version']) + version = self.SkillManager.getSkillScenarioVersion(skillName) if version < scenarioNodeVersion: - self.logInfo('New scenario node update found') + self.logInfo(f'New scenario update found for node **{scenarioNodeName}**') install = self.Commons.runSystemCommand(f'cd ~/.node-red && npm install {scenarioNodePath}', shell=True) if install.returncode == 1: - self.logWarning(f'Something went wrong updating node: {install.stderr}') - else: - restart = True + self.logWarning(f'Something went wrong updating node **{scenarioNodeName}**: {install.stderr}') + continue + + self.DatabaseManager.update( + tableName='skills', + callerName=self.SkillManager.name, + values={ + 'scenarioVersion': str(scenarioNodeVersion) + }, + row=('skillName', skillName) + ) + restart = True if restart: self.reloadServer() From 73a5abe6385a8f4653196afaa5a9105442e7a4d3 Mon Sep 17 00:00:00 2001 From: Psycho Date: Sat, 10 Oct 2020 17:07:15 +0200 Subject: [PATCH 006/126] :sparkles: auto install node red if needed --- core/interface/NodeRedManager.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 5a965b8d5..b94d36f67 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -1,8 +1,7 @@ -import json from pathlib import Path +from subprocess import PIPE, Popen from core.base.model.Manager import Manager -from core.base.model.Version import Version class NodeRedManager(Manager): @@ -23,11 +22,33 @@ def onStart(self): if not self.PACKAGE_PATH.exists(): self.logInfo('Node red not found, installing, this might take a while...') + self.ThreadManager.newThread(name='installNodered', target=self.install) + return self.injectSkillNodes() self.Commons.runRootSystemCommand(['systemctl', 'start', 'nodered']) + def install(self): + self.Commons.downloadFile( + url='https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered', + dest='var/cache/node-red.sh' + ) + self.Commons.runRootSystemCommand('chmod +x var/cache/node-red.sh'.split()) + proc = Popen('./var/cache/node-red.sh'.split(), stdin=PIPE, stdout=PIPE) + proc.stdin.write(b'y\n') + proc.stdin.write(b'n\n') + proc.stdin.close() + for line in iter(proc.stdout.readline, ''): + self.logInfo(line) + returnCode = proc.wait() + + if returnCode: + self.logError('Failed installing Node-red') + self.onStop() + self.onStart() + + def onStop(self): if not self.isActive: return @@ -52,6 +73,7 @@ def injectSkillNodes(self): if not self.PACKAGE_PATH.exists(): self.logWarning('Package json file for Node Red is missing. Is Node Red even installed?') + self.onStop() return for skillName, tup in self.SkillManager.allScenarioNodes().items(): From 901d7d5b0e84ebf2a67e512f93a81c0d70fac601 Mon Sep 17 00:00:00 2001 From: Psycho Date: Sat, 10 Oct 2020 17:08:03 +0200 Subject: [PATCH 007/126] :bug: decode --- core/interface/NodeRedManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index b94d36f67..e66730ccf 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -40,7 +40,7 @@ def install(self): proc.stdin.write(b'n\n') proc.stdin.close() for line in iter(proc.stdout.readline, ''): - self.logInfo(line) + self.logInfo(line.decode()) returnCode = proc.wait() if returnCode: From 073b3183f95cab427dcf765d186d5b1bcd9e7470 Mon Sep 17 00:00:00 2001 From: Psycho Date: Sat, 10 Oct 2020 17:11:09 +0200 Subject: [PATCH 008/126] :bug: surely works better that way --- core/interface/NodeRedManager.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index e66730ccf..615edf668 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -21,7 +21,6 @@ def onStart(self): super().onStart() if not self.PACKAGE_PATH.exists(): - self.logInfo('Node red not found, installing, this might take a while...') self.ThreadManager.newThread(name='installNodered', target=self.install) return @@ -30,6 +29,7 @@ def onStart(self): def install(self): + self.logInfo('Node red not found, installing, this might take a while...') self.Commons.downloadFile( url='https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered', dest='var/cache/node-red.sh' @@ -38,15 +38,15 @@ def install(self): proc = Popen('./var/cache/node-red.sh'.split(), stdin=PIPE, stdout=PIPE) proc.stdin.write(b'y\n') proc.stdin.write(b'n\n') - proc.stdin.close() - for line in iter(proc.stdout.readline, ''): - self.logInfo(line.decode()) returnCode = proc.wait() + proc.stdin.close() if returnCode: self.logError('Failed installing Node-red') self.onStop() - self.onStart() + else: + self.logInfo('Succesfully installed Node-red') + self.onStart() def onStop(self): From 0f66d05d0742cfedaa39f68389edb2ebea36a3ae Mon Sep 17 00:00:00 2001 From: Psycho Date: Sat, 10 Oct 2020 19:11:03 +0200 Subject: [PATCH 009/126] :bug: this correctly installs node-red and starts the manager that loads new nodes and writes them to db. Still no feedback if installing or not --- core/interface/NodeRedManager.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 615edf668..725156d1e 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -35,11 +35,18 @@ def install(self): dest='var/cache/node-red.sh' ) self.Commons.runRootSystemCommand('chmod +x var/cache/node-red.sh'.split()) - proc = Popen('./var/cache/node-red.sh'.split(), stdin=PIPE, stdout=PIPE) - proc.stdin.write(b'y\n') - proc.stdin.write(b'n\n') - returnCode = proc.wait() - proc.stdin.close() + + process = Popen('./var/cache/node-red.sh'.split(), stdin=PIPE, stdout=PIPE) + try: + process.stdin.write(b'y\n') + process.stdin.write(b'n\n') + except IOError: + self.logError('Failed installing Node-red') + self.onStop() + return + + process.stdin.close() + returnCode = process.wait(timeout=120) if returnCode: self.logError('Failed installing Node-red') From 7327d9820cbd55fcbb3bbb79156a5a6c55bbc988 Mon Sep 17 00:00:00 2001 From: Psycho Date: Sat, 10 Oct 2020 19:29:47 +0200 Subject: [PATCH 010/126] :sparkles: disabling non alice nodes after install --- core/interface/NodeRedManager.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 725156d1e..22fcda976 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -1,3 +1,5 @@ +import json +import time from pathlib import Path from subprocess import PIPE, Popen @@ -5,7 +7,6 @@ class NodeRedManager(Manager): - PACKAGE_PATH = Path('../.node-red/package.json') def __init__(self): @@ -21,6 +22,7 @@ def onStart(self): super().onStart() if not self.PACKAGE_PATH.exists(): + self.isActive = False self.ThreadManager.newThread(name='installNodered', target=self.install) return @@ -29,7 +31,7 @@ def onStart(self): def install(self): - self.logInfo('Node red not found, installing, this might take a while...') + self.logInfo('Node-RED not found, installing, this might take a while...') self.Commons.downloadFile( url='https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered', dest='var/cache/node-red.sh' @@ -41,7 +43,7 @@ def install(self): process.stdin.write(b'y\n') process.stdin.write(b'n\n') except IOError: - self.logError('Failed installing Node-red') + self.logError('Failed installing Node-RED') self.onStop() return @@ -53,9 +55,27 @@ def install(self): self.onStop() else: self.logInfo('Succesfully installed Node-red') + self.configureNewNodeRed() self.onStart() + def configureNewNodeRed(self): + self.logInfo('Configuring Node-RED') + # Start to generate base configs and stop it after + self.Commons.runRootSystemCommand(['systemctl', 'start', 'nodered']) + time.sleep(5) + self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) + time.sleep(5) + + config = Path(self.PACKAGE_PATH.parent, '.config.json') + data = json.loads(config.read_text()) + for package in data['nodes'].values(): + for node in package['nodes'].values(): + node['enabled'] = False + + config.write_text(json.dumps(data)) + + def onStop(self): if not self.isActive: return @@ -79,7 +99,7 @@ def injectSkillNodes(self): restart = False if not self.PACKAGE_PATH.exists(): - self.logWarning('Package json file for Node Red is missing. Is Node Red even installed?') + self.logWarning('Package json file for Node-RED is missing. Is Node Red even installed?') self.onStop() return @@ -93,7 +113,6 @@ def injectSkillNodes(self): self.logWarning(f'Something went wrong installing new node **{scenarioNodeName}**: {install.stderr}') else: restart = True - continue version = self.SkillManager.getSkillScenarioVersion(skillName) From be99a7ad06bbb21f11b65d19db00c627f97d47ed Mon Sep 17 00:00:00 2001 From: Psycho Date: Sun, 11 Oct 2020 05:45:40 +0200 Subject: [PATCH 011/126] :art: only display scenarios if scenarios are actually enabled --- core/interface/templates/base.html | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/interface/templates/base.html b/core/interface/templates/base.html index 973e30332..86fa628ba 100644 --- a/core/interface/templates/base.html +++ b/core/interface/templates/base.html @@ -60,7 +60,9 @@ - + {% if aliceSettings['scenariosActive'] is sameas true %} + + {% endif %} From ee2f5241055c28fed2185285b7cabe3f6e28b13b Mon Sep 17 00:00:00 2001 From: Psycho Date: Sun, 11 Oct 2020 06:36:17 +0200 Subject: [PATCH 012/126] :sparkles: configure node-red and install dark theme --- core/interface/NodeRedManager.py | 5 + core/interface/templates/scenarios.html | 2 +- system/node-red/settings.js | 309 ++++++++++++++++++++++++ 3 files changed, 315 insertions(+), 1 deletion(-) create mode 100644 system/node-red/settings.js diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 22fcda976..404baad84 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -1,4 +1,6 @@ import json +import os +import shutil import time from pathlib import Path from subprocess import PIPE, Popen @@ -75,6 +77,9 @@ def configureNewNodeRed(self): config.write_text(json.dumps(data)) + self.Commons.runSystemCommand('npm install --prefix ~/.node-red @node-red-contrib-themes/midnight-red'.split()) + shutil.copy(Path('system/node-red/settings.js'), Path(os.path.expanduser('~/.node-red'), 'settings.js')) + def onStop(self): if not self.isActive: diff --git a/core/interface/templates/scenarios.html b/core/interface/templates/scenarios.html index 032f30292..099069a0c 100644 --- a/core/interface/templates/scenarios.html +++ b/core/interface/templates/scenarios.html @@ -18,5 +18,5 @@ {% endblock %} {% block content %} - + {% endblock %} diff --git a/system/node-red/settings.js b/system/node-red/settings.js new file mode 100644 index 000000000..7f4ef83c5 --- /dev/null +++ b/system/node-red/settings.js @@ -0,0 +1,309 @@ +/** + * Copyright JS Foundation and other contributors, http://js.foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + **/ + +module.exports = { + // the tcp port that the Node-RED web server is listening on + uiPort: process.env.PORT || 1880, + + // By default, the Node-RED UI accepts connections on all IPv4 interfaces. + // To listen on all IPv6 addresses, set uiHost to "::", + // The following property can be used to listen on a specific interface. For + // example, the following would only allow connections from the local machine. + //uiHost: "127.0.0.1", + + // Retry time in milliseconds for MQTT connections + mqttReconnectTime: 15000, + + // Retry time in milliseconds for Serial port connections + serialReconnectTime: 15000, + + // Retry time in milliseconds for TCP socket connections + //socketReconnectTime: 10000, + + // Timeout in milliseconds for TCP server socket connections + // defaults to no timeout + //socketTimeout: 120000, + + // Maximum number of messages to wait in queue while attempting to connect to TCP socket + // defaults to 1000 + //tcpMsgQueueSize: 2000, + + // Timeout in milliseconds for HTTP request connections + // defaults to 120 seconds + //httpRequestTimeout: 120000, + + // The maximum length, in characters, of any message sent to the debug sidebar tab + debugMaxLength: 1000, + + // The maximum number of messages nodes will buffer internally as part of their + // operation. This applies across a range of nodes that operate on message sequences. + // defaults to no limit. A value of 0 also means no limit is applied. + //nodeMessageBufferMaxLength: 0, + + // To disable the option for using local files for storing keys and certificates in the TLS configuration + // node, set this to true + //tlsConfigDisableLocalFiles: true, + + // Colourise the console output of the debug node + //debugUseColors: true, + + // The file containing the flows. If not set, it defaults to flows_.json + //flowFile: 'flows.json', + + // To enabled pretty-printing of the flow within the flow file, set the following + // property to true: + //flowFilePretty: true, + + // By default, credentials are encrypted in storage using a generated key. To + // specify your own secret, set the following property. + // If you want to disable encryption of credentials, set this property to false. + // Note: once you set this property, do not change it - doing so will prevent + // node-red from being able to decrypt your existing credentials and they will be + // lost. + //credentialSecret: "a-secret-key", + + // By default, all user data is stored in a directory called `.node-red` under + // the user's home directory. To use a different location, the following + // property can be used + //userDir: '/home/nol/.node-red/', + + // Node-RED scans the `nodes` directory in the userDir to find local node files. + // The following property can be used to specify an additional directory to scan. + //nodesDir: '/home/nol/.node-red/nodes', + + // By default, the Node-RED UI is available at http://localhost:1880/ + // The following property can be used to specify a different root path. + // If set to false, this is disabled. + //httpAdminRoot: '/admin', + + // Some nodes, such as HTTP In, can be used to listen for incoming http requests. + // By default, these are served relative to '/'. The following property + // can be used to specifiy a different root path. If set to false, this is + // disabled. + //httpNodeRoot: '/red-nodes', + + // The following property can be used in place of 'httpAdminRoot' and 'httpNodeRoot', + // to apply the same root to both parts. + //httpRoot: '/red', + + // When httpAdminRoot is used to move the UI to a different root path, the + // following property can be used to identify a directory of static content + // that should be served at http://localhost:1880/. + //httpStatic: '/home/nol/node-red-static/', + + // The maximum size of HTTP request that will be accepted by the runtime api. + // Default: 5mb + //apiMaxLength: '5mb', + + // If you installed the optional node-red-dashboard you can set it's path + // relative to httpRoot + //ui: { path: "ui" }, + + // Securing Node-RED + // ----------------- + // To password protect the Node-RED editor and admin API, the following + // property can be used. See http://nodered.org/docs/security.html for details. + //adminAuth: { + // type: "credentials", + // users: [{ + // username: "admin", + // password: "$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN.", + // permissions: "*" + // }] + //}, + + // To password protect the node-defined HTTP endpoints (httpNodeRoot), or + // the static content (httpStatic), the following properties can be used. + // The pass field is a bcrypt hash of the password. + // See http://nodered.org/docs/security.html#generating-the-password-hash + //httpNodeAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + //httpStaticAuth: {user:"user",pass:"$2a$08$zZWtXTja0fB1pzD4sHCMyOCMYz2Z6dNbM6tl8sJogENOMcxWV9DN."}, + + // The following property can be used to enable HTTPS + // See http://nodejs.org/api/https.html#https_https_createserver_options_requestlistener + // for details on its contents. + // This property can be either an object, containing both a (private) key and a (public) certificate, + // or a function that returns such an object: + //// https object: + //https: { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + //}, + ////https function: + // https: function() { + // // This function should return the options object, or a Promise + // // that resolves to the options object + // return { + // key: require("fs").readFileSync('privkey.pem'), + // cert: require("fs").readFileSync('cert.pem') + // } + // }, + + // The following property can be used to refresh the https settings at a + // regular time interval in hours. + // This requires: + // - the `https` setting to be a function that can be called to get + // the refreshed settings. + // - Node.js 11 or later. + //httpsRefreshInterval : 12, + + // The following property can be used to cause insecure HTTP connections to + // be redirected to HTTPS. + //requireHttps: true, + + // The following property can be used to disable the editor. The admin API + // is not affected by this option. To disable both the editor and the admin + // API, use either the httpRoot or httpAdminRoot properties + //disableEditor: false, + + // The following property can be used to configure cross-origin resource sharing + // in the HTTP nodes. + // See https://github.com/troygoode/node-cors#configuration-options for + // details on its contents. The following is a basic permissive set of options: + //httpNodeCors: { + // origin: "*", + // methods: "GET,PUT,POST,DELETE" + //}, + + // If you need to set an http proxy please set an environment variable + // called http_proxy (or HTTP_PROXY) outside of Node-RED in the operating system. + // For example - http_proxy=http://myproxy.com:8080 + // (Setting it here will have no effect) + // You may also specify no_proxy (or NO_PROXY) to supply a comma separated + // list of domains to not proxy, eg - no_proxy=.acme.co,.acme.co.uk + + // The following property can be used to add a custom middleware function + // in front of all http in nodes. This allows custom authentication to be + // applied to all http in nodes, or any other sort of common request processing. + //httpNodeMiddleware: function(req,res,next) { + // // Handle/reject the request, or pass it on to the http in node by calling next(); + // // Optionally skip our rawBodyParser by setting this to true; + // //req.skipRawBodyParser = true; + // next(); + //}, + + + // The following property can be used to add a custom middleware function + // in front of all admin http routes. For example, to set custom http + // headers + // httpAdminMiddleware: function(req,res,next) { + // // Set the X-Frame-Options header to limit where the editor + // // can be embedded + // //res.set('X-Frame-Options', 'sameorigin'); + // next(); + // }, + + // The following property can be used to pass custom options to the Express.js + // server used by Node-RED. For a full list of available options, refer + // to http://expressjs.com/en/api.html#app.settings.table + //httpServerOptions: { }, + + // The following property can be used to verify websocket connection attempts. + // This allows, for example, the HTTP request headers to be checked to ensure + // they include valid authentication information. + //webSocketNodeVerifyClient: function(info) { + // // 'info' has three properties: + // // - origin : the value in the Origin header + // // - req : the HTTP request + // // - secure : true if req.connection.authorized or req.connection.encrypted is set + // // + // // The function should return true if the connection should be accepted, false otherwise. + // // + // // Alternatively, if this function is defined to accept a second argument, callback, + // // it can be used to verify the client asynchronously. + // // The callback takes three arguments: + // // - result : boolean, whether to accept the connection or not + // // - code : if result is false, the HTTP error status to return + // // - reason: if result is false, the HTTP reason string to return + //}, + + // The following property can be used to seed Global Context with predefined + // values. This allows extra node modules to be made available with the + // Function node. + // For example, + // functionGlobalContext: { os:require('os') } + // can be accessed in a function block as: + // global.get("os") + functionGlobalContext : { + // os:require('os'), + // jfive:require("johnny-five"), + // j5board:require("johnny-five").Board({repl:false}) + }, + // `global.keys()` returns a list of all properties set in global context. + // This allows them to be displayed in the Context Sidebar within the editor. + // In some circumstances it is not desirable to expose them to the editor. The + // following property can be used to hide any property set in `functionGlobalContext` + // from being list by `global.keys()`. + // By default, the property is set to false to avoid accidental exposure of + // their values. Setting this to true will cause the keys to be listed. + exportGlobalContextKeys: false, + + + // Context Storage + // The following property can be used to enable context storage. The configuration + // provided here will enable file-based context that flushes to disk every 30 seconds. + // Refer to the documentation for further options: https://nodered.org/docs/api/context/ + // + //contextStorage: { + // default: { + // module:"localfilesystem" + // }, + //}, + + // The following property can be used to order the categories in the editor + // palette. If a node's category is not in the list, the category will get + // added to the end of the palette. + // If not set, the following default order is used: + //paletteCategories: ['subflows', 'common', 'function', 'network', 'sequence', 'parser', 'storage'], + + // Configure the logging output + logging: { + // Only console logging is currently supported + console: { + // Level of logging to be recorded. Options are: + // fatal - only those errors which make the application unusable should be recorded + // error - record errors which are deemed fatal for a particular request + fatal errors + // warn - record problems which are non fatal + errors + fatal errors + // info - record information about the general running of the application + warn + error + fatal errors + // debug - record information which is more verbose than info + info + warn + error + fatal errors + // trace - record very detailed logging + debug + info + warn + error + fatal errors + // off - turn off all logging (doesn't affect metrics or audit) + level : 'info', + // Whether or not to include metric events in the log output + metrics: false, + // Whether or not to include audit events in the log output + audit : false + } + }, + + // Customising the editor + editorTheme: { + page : { + title : 'Project Alice Node-RED', + favicon: '~/ProjectAlice/core/interface/static/favicon.ico', + css : [ + '/home/pi/.node-red/node_modules/@node-red-contrib-themes/midnight-red/theme.css', + '/home/pi/.node-red/node_modules/@node-red-contrib-themes/midnight-red/scrollbars.css' + ] + }, + menu : { + 'menu-item-keyboard-shortcuts': false + }, + projects: { + enabled: false + } + } +}; From bdf42637f239efcb5ddd3b670c63e7d97a518ca0 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sun, 11 Oct 2020 11:41:54 +0200 Subject: [PATCH 013/126] :construction: Few corrections --- core/interface/NodeRedManager.py | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 404baad84..29d6b37b2 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -10,6 +10,14 @@ class NodeRedManager(Manager): PACKAGE_PATH = Path('../.node-red/package.json') + DEFAULT_NODES_ACTIVE = { + 'node-red': [ + 'debug', + 'JSON', + 'split', + 'sort' + ] + } def __init__(self): super().__init__() @@ -62,12 +70,12 @@ def install(self): def configureNewNodeRed(self): - self.logInfo('Configuring Node-RED') + self.logInfo('Configuring') # Start to generate base configs and stop it after self.Commons.runRootSystemCommand(['systemctl', 'start', 'nodered']) - time.sleep(5) + time.sleep(4) self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) - time.sleep(5) + time.sleep(3) config = Path(self.PACKAGE_PATH.parent, '.config.json') data = json.loads(config.read_text()) @@ -76,15 +84,15 @@ def configureNewNodeRed(self): node['enabled'] = False config.write_text(json.dumps(data)) + self.logInfo('Nodes configured') + self.logInfo('Applying Project Alice settings') self.Commons.runSystemCommand('npm install --prefix ~/.node-red @node-red-contrib-themes/midnight-red'.split()) shutil.copy(Path('system/node-red/settings.js'), Path(os.path.expanduser('~/.node-red'), 'settings.js')) + self.logInfo("All done, let's start all this") def onStop(self): - if not self.isActive: - return - super().onStop() self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) @@ -104,7 +112,7 @@ def injectSkillNodes(self): restart = False if not self.PACKAGE_PATH.exists(): - self.logWarning('Package json file for Node-RED is missing. Is Node Red even installed?') + self.logWarning('Package json file for Node-RED is missing. Is Node-RED even installed?') self.onStop() return From 67b8288b06b65e89df2c6241a07b628f1513da75 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Sun, 11 Oct 2020 15:53:28 +0200 Subject: [PATCH 014/126] :sparkles: SK can now upload to Github also --- core/base/SkillManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index 117e8a468..edb671c36 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -1130,7 +1130,7 @@ def uploadSkillToGithub(self, skillName: str, skillDesc: str) -> bool: self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'remote', 'add', 'origin', remote]) self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'add', '--all']) - self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'commit', '-m', '"Initial upload"']) + self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'commit', '-m', '"Initial upload by Project Alice Skill Kit"']) self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'push', '--set-upstream', 'origin', 'master']) url = f'https://github.com/{self.ConfigManager.getAliceConfigByName("githubUsername")}/skill_{skillName}.git' From 7ece153446ae83ce7c5133498e82606c4f465c8f Mon Sep 17 00:00:00 2001 From: Psycho Date: Sun, 11 Oct 2020 16:30:07 +0200 Subject: [PATCH 015/126] :art: cleanup --- system/node-red/settings.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/system/node-red/settings.js b/system/node-red/settings.js index 7f4ef83c5..091767c76 100644 --- a/system/node-red/settings.js +++ b/system/node-red/settings.js @@ -299,9 +299,6 @@ module.exports = { '/home/pi/.node-red/node_modules/@node-red-contrib-themes/midnight-red/scrollbars.css' ] }, - menu : { - 'menu-item-keyboard-shortcuts': false - }, projects: { enabled: false } From 5f3349c6e97153eeab68296ff7723c800b577de4 Mon Sep 17 00:00:00 2001 From: Psycho Date: Sun, 11 Oct 2020 16:39:42 +0200 Subject: [PATCH 016/126] :sparkles: bridging events over mqtt on correct topic --- core/base/model/ProjectAliceObject.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/base/model/ProjectAliceObject.py b/core/base/model/ProjectAliceObject.py index 94bd93a44..42980e77e 100644 --- a/core/base/model/ProjectAliceObject.py +++ b/core/base/model/ProjectAliceObject.py @@ -98,7 +98,7 @@ def broadcast(self, method: str, exceptions: list = None, manager = None, propag pass self.MqttManager.publish( - topic=method, + topic=f'projectalice/events/{method}', payload=payload ) From 7f818b686c6956889276191c895078ed743d1f88 Mon Sep 17 00:00:00 2001 From: Psycho Date: Sun, 11 Oct 2020 17:35:12 +0200 Subject: [PATCH 017/126] :bug: fixing a couple of things --- core/base/model/ProjectAliceObject.py | 2 ++ core/interface/NodeRedManager.py | 4 +++- core/voice/TTSManager.py | 5 +++++ 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/core/base/model/ProjectAliceObject.py b/core/base/model/ProjectAliceObject.py index 42980e77e..b2cc42a8e 100644 --- a/core/base/model/ProjectAliceObject.py +++ b/core/base/model/ProjectAliceObject.py @@ -87,6 +87,8 @@ def broadcast(self, method: str, exceptions: list = None, manager = None, propag for name in deadManagers: del SM.SuperManager.getInstance().managers[name] + if method == 'onAudioFrame': + return # Now send the event over mqtt payload = dict() diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 29d6b37b2..b1cef2941 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -15,7 +15,9 @@ class NodeRedManager(Manager): 'debug', 'JSON', 'split', - 'sort' + 'sort', + 'function', + 'change' ] } diff --git a/core/voice/TTSManager.py b/core/voice/TTSManager.py index ad807afa4..bfb1ba99c 100644 --- a/core/voice/TTSManager.py +++ b/core/voice/TTSManager.py @@ -118,6 +118,11 @@ def onSay(self, session: DialogSession): if session.textOnly: return + if 'text' not in session.payload: + self.logWarning('Was asked to say something but no text provided') + self.MqttManager.endSession(sessionId=session.sessionId) + return + if session and session.user != constants.UNKNOWN_USER: user: User = self.UserManager.getUser(session.user) if user and user.tts: From 17d2a07ef89dcade9b14fe24464ad6b9a5311cfb Mon Sep 17 00:00:00 2001 From: Psycho Date: Mon, 12 Oct 2020 06:30:35 +0200 Subject: [PATCH 018/126] :sparkles: Commons.isUuid, with tests --- core/commons/CommonsManager.py | 10 +++++++++ tests/commons/test_CommonsManager.py | 33 ++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/core/commons/CommonsManager.py b/core/commons/CommonsManager.py index 1f9f438a9..f22a94d4c 100644 --- a/core/commons/CommonsManager.py +++ b/core/commons/CommonsManager.py @@ -13,6 +13,7 @@ from datetime import datetime from pathlib import Path from typing import Any, Union +from uuid import UUID import requests from googletrans import Translator @@ -335,6 +336,15 @@ def randomNumber(self, length: int) -> int: return int(number) if not number.startswith('0') else self.randomNumber(length) + @staticmethod + def isUuid(uuid: str) -> bool: + try: + _ = UUID(uuid) + return True + except ValueError: + return False + + # noinspection PyUnusedLocal def py_error_handler(filename, line, function, err, fmt): #NOSONAR # Errors are handled by our loggers diff --git a/tests/commons/test_CommonsManager.py b/tests/commons/test_CommonsManager.py index ca452dbc0..1069b43d3 100644 --- a/tests/commons/test_CommonsManager.py +++ b/tests/commons/test_CommonsManager.py @@ -1,6 +1,7 @@ import unittest from unittest import mock from unittest.mock import MagicMock +from uuid import UUID from core.commons.CommonsManager import CommonsManager @@ -296,5 +297,37 @@ def test_indexOf(self): self.assertEqual(CommonsManager.indexOf('nn', string3), 1) + def test_isUuid(self): + validStrings = [ + '6e9bb2f8-bedb-4ade-9db7-f455e4c03051', + '{6e9bb2f8-bedb-4ade-9db7-f455e4c03051}', + '6e9bb2f8-becb-4ade-9db7-f455e4c03051', + '6e9bb2f8bedb4ade9db7f455e4c03051', + 'urn:uuid:6e9bb2f8bedb4ade9db7f455e4c03051' + ] + invalidStrings = [ + '{6e9bb2f8-begb-4ade-9db7-f455e4c03051}', + '6e9bb2f8-bedb-4ade-9db7-f455e4c030', + 'unittests' + ] + + for string in validStrings: + try: + val = UUID(string) + except: + val = None + + self.assertIsNotNone(val) + + + for string in invalidStrings: + try: + val = UUID(string) + except: + val = None + + self.assertIsNone(val) + + if __name__ == '__main__': unittest.main() From c06967b9589d5f97134caa3b163c190a453f2d3f Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Mon, 12 Oct 2020 20:27:36 +0200 Subject: [PATCH 019/126] :sparkles: option to not stop Node-RED on Alice shutdown --- configTemplate.json | 18 +++++++++++++++--- core/interface/NodeRedManager.py | 3 ++- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index c5213f983..2ee9913c8 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -649,14 +649,26 @@ "description" : "Change the web interface port to be used", "category" : "system" }, - "scenariosActive" : { + "scenariosActive" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, - "description" : "Activates the scenarios support on the webinterface, using node-red.", - "category" : "system", + "description" : "Activates the scenarios support on the webinterface, using Node-RED.", + "category" : "scenarios", "onUpdate" : "NodeRedManager.toggle" }, + "dontStopNodeRed" : { + "defaultValue": false, + "dataType" : "boolean", + "isSensitive" : false, + "description" : "If activated, Node-RED won't be stopped when Alice is shut down.", + "category" : "scenarios", + "parent" : { + "config" : "scenariosActive", + "condition": "is", + "value" : true + } + }, "devMode" : { "defaultValue": false, "dataType" : "boolean", diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index b1cef2941..f896b711a 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -96,7 +96,8 @@ def configureNewNodeRed(self): def onStop(self): super().onStop() - self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) + if not self.ConfigManager.getAliceConfigByName('dontStopNodeRed'): + self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) def toggle(self): From 65fe34bcae1fbb7f351ba409737917d43b8224c2 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Mon, 12 Oct 2020 20:28:22 +0200 Subject: [PATCH 020/126] :sparkles: option to not stop Node-RED on Alice shutdown --- configTemplate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configTemplate.json b/configTemplate.json index 2ee9913c8..080acbfa1 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -674,6 +674,6 @@ "dataType" : "boolean", "isSensitive" : false, "description" : "Activates the developer part of the interface, for skill development", - "category" : "system" + "category" : "scenarios" } } From 7141c034a37fb33aee18497335d3356c88ec59d1 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Mon, 12 Oct 2020 20:28:41 +0200 Subject: [PATCH 021/126] :sparkles: option to not stop Node-RED on Alice shutdown --- configTemplate.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/configTemplate.json b/configTemplate.json index 080acbfa1..2ee9913c8 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -674,6 +674,6 @@ "dataType" : "boolean", "isSensitive" : false, "description" : "Activates the developer part of the interface, for skill development", - "category" : "scenarios" + "category" : "system" } } From 1ce020442bcef6bfb2bd39f3fdbbd29472c01847 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Tue, 13 Oct 2020 06:17:13 +0200 Subject: [PATCH 022/126] :bug: raise a github not found if the skill wasn't yet deployed --- core/base/SkillStoreManager.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/base/SkillStoreManager.py b/core/base/SkillStoreManager.py index 4ca26005c..bb798d2db 100644 --- a/core/base/SkillStoreManager.py +++ b/core/base/SkillStoreManager.py @@ -42,6 +42,9 @@ def refreshStoreData(self): def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]: versionMapping = self._skillStoreData.get(skillName, dict()).get('versionMapping', dict()) + if not versionMapping: + raise GithubNotFound + userUpdatePref = self.ConfigManager.getAliceConfigByName('skillsUpdateChannel') skillUpdateVersion = (Version(), '') From bda7a5275dce648e45ada21fbb3982649f3ab1a2 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Tue, 13 Oct 2020 18:15:48 +0200 Subject: [PATCH 023/126] :bug: Fix for mqttuser, password and tls file config wrongly saved --- core/base/ConfigManager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index 0d9cd198b..ba95a243a 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -569,9 +569,9 @@ def doConfigUpdatePostProcessing(self, functions: typing.Union[str, set]): def updateMqttSettings(self): self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt', f'{self.getAliceConfigByName("mqttHost")}:{self.getAliceConfigByName("mqttPort"):}', False, True) - self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_username', self.getAliceConfigByName('mqttHost'), False, True) - self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_password', self.getAliceConfigByName('mqttHost'), False, True) - self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_tls_cafile', self.getAliceConfigByName('mqttHost'), True, True) + self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_username', self.getAliceConfigByName('mqttUser'), False, True) + self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_password', self.getAliceConfigByName('mqttPassword'), False, True) + self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_tls_cafile', self.getAliceConfigByName('mqttTLSFile'), True, True) self.reconnectMqtt() From 6e2afde1d4cd6fb37d9df1ccda53a143b3c1934b Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Wed, 14 Oct 2020 12:14:36 +0200 Subject: [PATCH 024/126] :fire: removed unused scenario test stuff from year ago --- core/scenario/__init__.py | 0 core/scenario/model/ScenarioTile.py | 18 ------------------ core/scenario/model/ScenarioTileType.py | 14 -------------- core/scenario/model/__init__.py | 0 4 files changed, 32 deletions(-) delete mode 100644 core/scenario/__init__.py delete mode 100644 core/scenario/model/ScenarioTile.py delete mode 100644 core/scenario/model/ScenarioTileType.py delete mode 100644 core/scenario/model/__init__.py diff --git a/core/scenario/__init__.py b/core/scenario/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/core/scenario/model/ScenarioTile.py b/core/scenario/model/ScenarioTile.py deleted file mode 100644 index f9c78e972..000000000 --- a/core/scenario/model/ScenarioTile.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Any - -from core.base.model.ProjectAliceObject import ProjectAliceObject -from core.scenario.model.ScenarioTileType import ScenarioTileType - - -class ScenarioTile(ProjectAliceObject): - - def __init__(self): - super().__init__() - self.tileType: ScenarioTileType = ScenarioTileType.ACTION - self.name: str = '' - self.description: str = '' - self.value: Any = '' - - - def interfaceName(self) -> str: - return self.name diff --git a/core/scenario/model/ScenarioTileType.py b/core/scenario/model/ScenarioTileType.py deleted file mode 100644 index cc4f50b00..000000000 --- a/core/scenario/model/ScenarioTileType.py +++ /dev/null @@ -1,14 +0,0 @@ -from enum import Enum - - -class ScenarioTileType(Enum): - CONDITION_BLOCK = 10 - LOOP_BLOCK = 20 - VARIABLE = 30 - INTEGER = 40 - STRING = 50 - FLOAT = 60 - BOOLEAN = 70 - ARRAY = 80 - ACTION = 150 - EVENT = 200 diff --git a/core/scenario/model/__init__.py b/core/scenario/model/__init__.py deleted file mode 100644 index e69de29bb..000000000 From 0633f016e52b1a1d8607564f9c1cd6977c65c4f0 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Wed, 14 Oct 2020 12:36:26 +0200 Subject: [PATCH 025/126] :children_crossing: easier to read --- core/interface/static/css/projectalice.css | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index 21d66942b..a072cc502 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -174,9 +174,11 @@ button.button{ font-size: 1.6em; height: 2em; } .skillInstructions { width: 100%; margin: .2em; text-align: right; } /* Configuration Screens */ -.configBox { width: 100%; display: table; font-family: var(--long); background-color: var(--secondary); border-spacing: 0 10px; } +.configBox { width: 100%; display: table; font-family: var(--readable); background-color: var(--secondary); border-spacing: 0 10px; } + .configCategory { width: 100%; display: table; margin-bottom: 30px; font-family: var(--long); background-color: var(--secondary); border-spacing: 0 10px; } -.configLine { width: 100%; display: table-row; } + +.configLine { width: 100%; display: table-row; font-family: var(--readable); } .configLine input, select, textarea { width: 500px; } From 58cfea0bb053636f87387481ee9de955c51c4f70 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Wed, 14 Oct 2020 14:00:44 +0200 Subject: [PATCH 026/126] :bug: Increased node red install timeout to 15 minutes --- core/interface/NodeRedManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index f896b711a..2bf76d4e1 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -60,7 +60,7 @@ def install(self): return process.stdin.close() - returnCode = process.wait(timeout=120) + returnCode = process.wait(timeout=900) if returnCode: self.logError('Failed installing Node-red') From e8c841eedd402f958bae6b1eb4d4ecf3cf94cea8 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Wed, 14 Oct 2020 17:29:09 +0200 Subject: [PATCH 027/126] :sparkles: The interface now uses ProjectAlice-sk for skill creation --- core/base/SkillManager.py | 124 ++++---------------------- core/interface/languages/de.json | 5 +- core/interface/languages/en.json | 5 +- core/interface/languages/fr.json | 5 +- core/interface/languages/it.json | 5 +- core/interface/templates/devmode.html | 39 +++++++- core/interface/views/DevModeView.py | 18 ++-- requirements.txt | 1 + 8 files changed, 81 insertions(+), 121 deletions(-) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index edb671c36..35af16351 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -918,6 +918,7 @@ def getSkillScenarioVersion(self, skillName: str) -> Version: if skillName not in self._skillList: return Version.fromString('0.0.0') else: + # noinspection SqlResolve query = 'SELECT * FROM :__table__ WHERE skillName = :skillName' data = self.DatabaseManager.fetch(tableName='skills', query=query, values={'skillName': skillName}, callerName=self.name) if not data: @@ -951,21 +952,7 @@ def createNewSkill(self, skillDefinition: dict) -> bool: try: self.logInfo(f'Creating new skill "{skillDefinition["name"]}"') - rootDir = Path(self.Commons.rootDir()) / 'skills' - skillTemplateDir = rootDir / 'skill_DefaultTemplate' - - if skillTemplateDir.exists(): - shutil.rmtree(skillTemplateDir) - - self.Commons.runSystemCommand(['git', '-C', str(rootDir), 'clone', f'{constants.GITHUB_URL}/skill_DefaultTemplate.git']) - skillName = skillDefinition['name'][0].upper() + skillDefinition['name'][1:] - skillDir = rootDir / skillName - - skillTemplateDir.rename(skillDir) - - installFile = skillDir / f'{skillDefinition["name"]}.install' - Path(skillDir, 'DefaultTemplate.install').rename(installFile) supportedLanguages = [ 'en' ] @@ -995,103 +982,26 @@ def createNewSkill(self, skillDefinition: dict) -> bool: if skillDefinition['conditionActiveManager']: conditions['activeManager'] = [manager.strip() for manager in skillDefinition['conditionActiveManager'].split(',')] - installContent = { - 'name' : skillName, - 'version' : '0.0.1', - 'icon' : 'fab fa-battle-net', - 'category' : 'undefined', - 'author' : self.ConfigManager.getAliceConfigByName('githubUsername'), - 'maintainers' : [], - 'desc' : skillDefinition['description'].capitalize(), - 'aliceMinVersion' : constants.VERSION, - 'systemRequirements': [req.strip() for req in skillDefinition['sysreq'].split(',')], - 'pipRequirements' : [req.strip() for req in skillDefinition['pipreq'].split(',')], + data = { + 'username' : self.ConfigManager.getAliceConfigByName('githubUsername'), + 'skillName' : skillName, + 'description' : skillDefinition['description'].capitalize(), + 'category' : skillDefinition['category'], + 'langs' : supportedLanguages, + 'createInstructions': skillDefinition['instructions'], + 'pipreq' : [req.strip() for req in skillDefinition['pipreq'].split(',')], + 'sysreq' : [req.strip() for req in skillDefinition['sysreq'].split(',')], + 'widgets' : [self.Commons.toPascalCase(widget) for widget in skillDefinition['widgets'].split(',')], + 'scenarioNodes' : [self.Commons.toPascalCase(node) for node in skillDefinition['nodes'].split(',')], + 'outputDestination' : str(Path(self.Commons.rootDir()) / 'skills' / skillName), 'conditions' : conditions } - # Install file - with installFile.open('w') as fp: - fp.write(json.dumps(installContent, indent=4)) - - # Dialog templates and talks - dialogTemplateTemplate = skillDir / 'dialogTemplate/default.json' - with dialogTemplateTemplate.open() as fp: - dialogTemplate = json.load(fp) - dialogTemplate['skill'] = skillName - dialogTemplate['description'] = skillDefinition['description'].capitalize() - - for lang in supportedLanguages: - with Path(skillDir, f'dialogTemplate/{lang}.json').open('w+') as fp: - fp.write(json.dumps(dialogTemplate, indent=4)) - - with Path(skillDir, f'talks/{lang}.json').open('w+') as fp: - fp.write(json.dumps(dict())) - - dialogTemplateTemplate.unlink() - - # Widgets - if skillDefinition['widgets']: - widgetRootDir = skillDir / 'widgets' - css = widgetRootDir / 'css/widget.css' - js = widgetRootDir / 'js/widget.js' - lang = widgetRootDir / 'lang/widget.lang.json' - html = widgetRootDir / 'templates/widget.html' - python = widgetRootDir / 'widget.py' - - for widget in skillDefinition['widgets'].split(','): - widgetName = widget.strip() - widgetName = widgetName[0].upper() + widgetName[1:] - - content = css.read_text().replace('%widgetname%', widgetName) - with Path(widgetRootDir, f'css/{widgetName}.css').open('w+') as fp: - fp.write(content) - - shutil.copy(str(js), str(js).replace('widget.js', f'{widgetName}.js')) - shutil.copy(str(lang), str(lang).replace('widget.lang.json', f'{widgetName}.lang.json')) - - content = html.read_text().replace('%widgetname%', widgetName) - with Path(widgetRootDir, f'templates/{widgetName}.html').open('w+') as fp: - fp.write(content) - - content = python.read_text().replace('Template(Widget)', f'{widgetName}(Widget)') - with Path(widgetRootDir, f'{widgetName}.py').open('w+') as fp: - fp.write(content) - - css.unlink() - js.unlink() - lang.unlink() - html.unlink() - python.unlink() - - else: - shutil.rmtree(str(Path(skillDir, 'widgets'))) - - languages = '' - for lang in supportedLanguages: - languages += f' {lang}\n' - - # Readme file - content = Path(skillDir, 'README.md').read_text().replace('%skillname%', skillName) \ - .replace('%author%', self.ConfigManager.getAliceConfigByName('githubUsername')) \ - .replace('%minVersion%', constants.VERSION) \ - .replace('%description%', skillDefinition['description'].capitalize()) \ - .replace('%languages%', languages) - - with Path(skillDir, 'README.md').open('w') as fp: - fp.write(content) - - # Main class - classFile = skillDir / f'{skillDefinition["name"]}.py' - Path(skillDir, 'DefaultTemplate.py').rename(classFile) - - content = classFile.read_text().replace('%skillname%', skillName) \ - .replace('%author%', self.ConfigManager.getAliceConfigByName('githubUsername')) \ - .replace('%description%', skillDefinition['description'].capitalize()) - - with classFile.open('w') as fp: - fp.write(content) + dump = Path(f'/tmp/{skillName}.json') + dump.write_text(json.dumps(data, ensure_ascii=False)) - self.logInfo(f'Created "{skillDefinition["name"]}" skill') + self.Commons.runSystemCommand(['./venv/bin/projectalice-sk', 'create', '--file', f'{str(dump)}']) + self.logInfo(f'Created **skillName** skill') return True except Exception as e: diff --git a/core/interface/languages/de.json b/core/interface/languages/de.json index 1b19a0bbe..81e34a9d1 100644 --- a/core/interface/languages/de.json +++ b/core/interface/languages/de.json @@ -68,5 +68,8 @@ "renameLocation" : "Wie lautet der neue Name der Lokation?", "warningSkillChangeAliceconf": "Änderung der Systemeinstellungen durch Skills erkannt", "accept" : "akzeptieren", - "refuse" : "ablehnen" + "refuse" : "ablehnen", + "leaveEmptyIfNotNeeded" : "Leave empty is not needed", + "scenarioNodes" : "Scenario nodes", + "category" : "Category" } diff --git a/core/interface/languages/en.json b/core/interface/languages/en.json index 480dc6b1c..cc183bbe1 100644 --- a/core/interface/languages/en.json +++ b/core/interface/languages/en.json @@ -68,5 +68,8 @@ "renameLocation" : "What is the locations new name?", "warningSkillChangeAliceconf": "Core configuration change attempt detected!", "accept" : "accept", - "refuse" : "refuse" + "refuse" : "refuse", + "leaveEmptyIfNotNeeded" : "Leave empty is not needed", + "scenarioNodes" : "Scenario nodes", + "category" : "Category" } diff --git a/core/interface/languages/fr.json b/core/interface/languages/fr.json index 5f98f7962..25d9b00ab 100644 --- a/core/interface/languages/fr.json +++ b/core/interface/languages/fr.json @@ -68,5 +68,8 @@ "renameLocation" : "Quel est le nouveau nom de l'emplacement?", "warningSkillChangeAliceconf": "Modification de configuration de base demandée par un ou des skill(s)", "accept" : "accepter", - "refuse" : "refuser" + "refuse" : "refuser", + "leaveEmptyIfNotNeeded" : "Leave empty is not needed", + "scenarioNodes" : "Scenario nodes", + "category" : "Category" } diff --git a/core/interface/languages/it.json b/core/interface/languages/it.json index f2c172fb2..487775341 100644 --- a/core/interface/languages/it.json +++ b/core/interface/languages/it.json @@ -68,5 +68,8 @@ "renameLocation" : "Qual è il nuovo nome per la posizione?", "warningSkillChangeAliceconf": "Some skill(s) want to change some core configurations", "accept" : "accettare", - "refuse" : "refiutare" + "refuse" : "refiutare", + "leaveEmptyIfNotNeeded" : "Leave empty is not needed", + "scenarioNodes" : "Scenario nodes", + "category" : "Category" } diff --git a/core/interface/templates/devmode.html b/core/interface/templates/devmode.html index ca6163657..09fe9e212 100644 --- a/core/interface/templates/devmode.html +++ b/core/interface/templates/devmode.html @@ -37,6 +37,27 @@ +
+ +
+ +
+
@@ -50,6 +71,12 @@
+
+ +
+ +
+
@@ -84,8 +111,13 @@
- + +
+
+
+ +
+
@@ -103,7 +135,8 @@ {% for skillName, skill in skills.items() %}
-
{{ skillName }}
+
{{ skillName }}
+
{% endfor %} diff --git a/core/interface/views/DevModeView.py b/core/interface/views/DevModeView.py index 9e728477f..77f2a4bc9 100644 --- a/core/interface/views/DevModeView.py +++ b/core/interface/views/DevModeView.py @@ -45,20 +45,24 @@ def put(self, skillName: str): newSkill = { 'name' : skillName, 'description' : request.form.get('description', 'Missing description'), + 'category' : request.form.get('skillCategory', 'undefined'), 'fr' : request.form.get('fr', False), 'de' : request.form.get('de', False), 'it' : request.form.get('it', False), - 'pipreq' : request.form.get('pipreq', list()), - 'sysreq' : request.form.get('sysreq', list()), + 'instructions' : request.form.get('createInstructions', False), + 'pipreq' : request.form.get('pipreq', ''), + 'sysreq' : request.form.get('sysreq', ''), 'conditionOnline' : request.form.get('conditionOnline', False), 'conditionASRArbitrary' : request.form.get('conditionASRArbitrary', False), - 'conditionSkill' : request.form.get('conditionSkill', list()), - 'conditionNotSkill' : request.form.get('conditionNotSkill', list()), - 'conditionActiveManager': request.form.get('conditionActiveManager', list()), - 'widgets' : request.form.get('widgets', list()) + 'conditionSkill' : request.form.get('conditionSkill', ''), + 'conditionNotSkill' : request.form.get('conditionNotSkill', ''), + 'conditionActiveManager': request.form.get('conditionActiveManager', ''), + 'widgets' : request.form.get('widgets', ''), + 'nodes' : request.form.get('nodes', '') } + if not self.SkillManager.createNewSkill(newSkill): - raise Exception('Unhandled skill creation exception') + raise Exception return jsonify(success=True) diff --git a/requirements.txt b/requirements.txt index 1fe6d888b..02d064f38 100644 --- a/requirements.txt +++ b/requirements.txt @@ -24,4 +24,5 @@ PyAudio==0.2.11 pyjwt==1.7.1 toml==0.10.1 markdown==3.2.2 +ProjectAlice-sk #pyopenssl==19.1.0 From 1c82868040e324aeac27abb646bb34e7e35855bb Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 15 Oct 2020 10:51:35 +0200 Subject: [PATCH 028/126] :art: making widget use readable font --- core/interface/static/css/projectalice.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index a072cc502..cf70369f1 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -127,7 +127,8 @@ button.button{ font-size: 1.6em; height: 2em; } .addWidgetCheck { float: right; color: var(--text); margin-right: .4em; } /* To use with custom Widgets! */ -.widget { display: block; box-shadow: 0 0 20px 5px #141414; background-color: var(--windowBG); cursor: default; overflow: hidden; } +.widget { display: block; box-shadow: 0 0 20px 5px #141414; background-color: var(--windowBG); cursor: default; overflow: hidden; font-family: var(--readable) } + .widgetIcons { font-size: 1.5em; display: flex; align-items: flex-start; } .widgetIconsHidden .widgetIcons { display: none; } .widgetIcon { display: flex; flex-grow: 1; } From f61e09d5e074a71a68d2cef6c0acd51943da2ba2 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 15 Oct 2020 17:59:20 +0200 Subject: [PATCH 029/126] :sparkles: Fix #317 --- configTemplate.json | 89 +++++++++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 11 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index 2ee9913c8..abf7557cf 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -72,21 +72,36 @@ "dataType" : "string", "isSensitive" : true, "description" : "API key for IBM Cloud Watson Tts and ASR", - "category" : "credentials" + "category" : "credentials", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "ibmCloudAPIURL" : { "defaultValue": "", "dataType" : "string", "isSensitive" : false, "description" : "API url for IBM Cloud Watson Tts and ASR", - "category" : "credentials" + "category" : "credentials", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "autoReportSkillErrors" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, "description" : "If true, an error thrown by a skill will automatically post a github issue and ping the author", - "category" : "system" + "category" : "system", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "disableSoundAndMic" : { "defaultValue": false, @@ -408,14 +423,24 @@ "dataType" : "string", "isSensitive" : true, "description" : "Your Amazon services access key", - "category" : "credentials" + "category" : "credentials", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "awsSecretKey" : { "defaultValue": "", "dataType" : "string", "isSensitive" : true, "description" : "Your Amazon services secret key", - "category" : "credentials" + "category" : "credentials", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "useHLC" : { "defaultValue": false, @@ -450,42 +475,72 @@ "dataType" : "string", "isSensitive" : false, "description" : "If you want to use a non natively supported language, set it here.", - "category" : "system" + "category" : "system", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "nonNativeSupportCountry" : { "defaultValue": "", "dataType" : "string", "isSensitive" : false, "description" : "If you want to use a non natively supported country, set it here.", - "category" : "system" + "category" : "system", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "aliceAutoUpdate" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, "description" : "Whether Alice should auto update, checked every hour", - "category" : "system" + "category" : "system", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "skillAutoUpdate" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, "description" : "Whether skills should auto update, checked every 15 minutes", - "category" : "system" + "category" : "system", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "githubUsername" : { "defaultValue": "", "dataType" : "string", "isSensitive" : false, "description" : "Not mendatory, your github username and token allows you to use Github API much more, such as checking for skills, updating them etc etc", - "category" : "credentials" + "category" : "credentials", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "githubToken" : { "defaultValue": "", "dataType" : "string", "isSensitive" : true, "description" : "Not mendatory, your github username and token allows you to use Github API much more, such as checking for skills, updating them etc etc", - "category" : "credentials" + "category" : "credentials", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } }, "aliceUpdateChannel" : { "defaultValue": "master", @@ -675,5 +730,17 @@ "isSensitive" : false, "description" : "Activates the developer part of the interface, for skill development", "category" : "system" + }, + "suggestSkillsToInstall" : { + "defaultValue": false, + "dataType" : "boolean", + "isSensitive" : false, + "description" : "If enabled, whenever something you say is not recognized, Alice will try to propose a skill that can do it.", + "category" : "system", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } } } From 61de39a2c210e99dff57acf810d4e3f049ba3277 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 16 Oct 2020 06:02:02 +0200 Subject: [PATCH 030/126] :construction: Part of shop implementation #316 --- core/base/SkillStoreManager.py | 8 ++++++++ core/util/InternetManager.py | 3 +++ 2 files changed, 11 insertions(+) diff --git a/core/base/SkillStoreManager.py b/core/base/SkillStoreManager.py index bb798d2db..657bc38be 100644 --- a/core/base/SkillStoreManager.py +++ b/core/base/SkillStoreManager.py @@ -14,6 +14,7 @@ class SkillStoreManager(Manager): def __init__(self): super().__init__() self._skillStoreData = dict() + self._skillSamplesData = dict() @property @@ -30,6 +31,7 @@ def onQuarterHour(self): self.refreshStoreData() + @Online(catchOnly=True) def refreshStoreData(self): updateChannel = self.ConfigManager.getAliceConfigByName('skillsUpdateChannel') req = requests.get(url=f'https://skills.projectalice.io/assets/store/{updateChannel}.json') @@ -38,6 +40,12 @@ def refreshStoreData(self): self._skillStoreData = req.json() + req = requests.get(url=f'https://skills.projectalice.io/assets/store/{updateChannel}.samples') + if req.status_code not in {200, 304}: + return + + self._skillSamplesData = req.json() + def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]: versionMapping = self._skillStoreData.get(skillName, dict()).get('versionMapping', dict()) diff --git a/core/util/InternetManager.py b/core/util/InternetManager.py index 40de5ce14..78a4bf983 100644 --- a/core/util/InternetManager.py +++ b/core/util/InternetManager.py @@ -35,6 +35,9 @@ def onFullMinute(self): def checkOnlineState(self, addr: str = 'https://clients3.google.com/generate_204', silent: bool = False) -> bool: + if self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'): + return False + try: online = requests.get(addr).status_code == 204 except RequestException: From 1184a3515fb0670f0bc3e74cbc4e36b226a4d1f1 Mon Sep 17 00:00:00 2001 From: ChrisB85 Date: Sat, 17 Oct 2020 12:26:06 +0200 Subject: [PATCH 031/126] polish translations added --- system/manager/LanguageManager/strings.json | 40 +++++++++ system/manager/TalkManager/talks/pl.json | 93 +++++++++++++++++++++ 2 files changed, 133 insertions(+) create mode 100644 system/manager/TalkManager/talks/pl.json diff --git a/system/manager/LanguageManager/strings.json b/system/manager/LanguageManager/strings.json index 720acae16..dceb0a28d 100644 --- a/system/manager/LanguageManager/strings.json +++ b/system/manager/LanguageManager/strings.json @@ -11,6 +11,10 @@ ], "it": [ "fuori" + ], + "pl": [ + "na zewnątrz", + "na dworze" ] }, "intentSeparator": { @@ -25,6 +29,9 @@ ], "it": [ " ed anche " + ], + "pl": [ + " a także " ] }, "inThe": { @@ -53,6 +60,11 @@ "nei ", "dentro ", "in " + ], + "pl": [ + "w pokoju ", + "w ", + "w pomieszczeniu " ] }, "everywhere": { @@ -68,6 +80,9 @@ "it": [ "dappertutto", "ovunque" + ], + "pl": [ + "wszędzie" ] }, "+": { @@ -82,6 +97,9 @@ ], "it": [ "più" + ], + "pl": [ + "plus" ] }, "-": { @@ -96,6 +114,9 @@ ], "it": [ "meno" + ], + "pl": [ + "minus" ] }, "*": { @@ -110,6 +131,9 @@ ], "it": [ "per" + ], + "pl": [ + "razy" ] }, "/": { @@ -124,6 +148,9 @@ ], "it": [ "diviso" + ], + "pl": [ + "dzielone przez" ] }, "%": { @@ -138,6 +165,9 @@ ], "it": [ "modulo" + ], + "en": [ + "modulo" ] }, "cancelIntent": { @@ -165,6 +195,16 @@ "annulla", "sta 'zitto", "silenzio" + ], + "pl": [ + "anuluj", + "stop", + "zamknij się", + "cisza", + "cicho bądź", + "bądź cicho", + "morda w kubeł", + "ucisz się" ] } } diff --git a/system/manager/TalkManager/talks/pl.json b/system/manager/TalkManager/talks/pl.json new file mode 100644 index 000000000..874143a25 --- /dev/null +++ b/system/manager/TalkManager/talks/pl.json @@ -0,0 +1,93 @@ + { + "noAccess": { + "default": [ + "Przepraszam, nie mogę. Nie masz wystarczających uprawnień", + "Niemożliwe. Twoje uprawnienia są za niskie" + ], + "short":[ + "Brak dostępu" + ] + }, + "unknowUser": { + "default": [ + "Przepraszam ale nie wiem kim jesteś", + "Przepraszam, nie mogę. Nie znam cię", + "Musisz zwracać się do mnie po imieniu abym wiedział kim jesteś" + ], + "short": [ + "Nie znam cię" + ] + }, + "notUnderstood": { + "default": [ + "Przepraszam ale nie jestem pewien czy dobrze rozumiem czego chcesz", + "Nie zrozumiełem czego chcesz", + "Przepraszam, czy możesz powtórzyć?", + "Co??" + ], + "short": [ + "Nie zrozumiałem" + ] + }, + "notUnderstoodEnd": { + "default": [ + "Przepraszam ale Cię nie rozumiem", + "Przepraszam ale nie rozumiem czego chcesz", + "Koniec tematu. Nie rozumiem Twojego polecenia", + "Stop. Nic z tego nie rozumiem" + ], + "short": [ + "Nie rozumiem" + ] + }, + "youreWelcome": { + "default": [ + "Proszę bardzo!", + "Cała przyjemność po mojej stronie!", + "Nie ma sprawy!", + "W porządku!" + ] + }, + "error": { + "default": [ + "Coś poszło nie tak, sprawdź logi" + ] + }, + "newDeviceAdditionFailed": { + "default": [ + "Przepraszam, pojawił się problem z dodaniem nowego urządzenia" + ] + }, + "newDeviceAdditionSuccess": { + "default": [ + "Nowe urządzenie zostało znalezione i pomyślnie dodane!" + ] + }, + "maxOneAlicePerRoom": { + "default": [ + "Przepraszam ale nie możesz mieć więcej niż jeden odbiornik w pomieszczeniu", + "Przepraszam ale w jednym pomieszczeniu może być tylko jeden odbiornik" + ] + }, + "wakewordCaptured": [ + "Zarejestrowałem twoje wyrażenie wywołujące, sprawdźmy je", + "OK, zarejestrowane. Odtwarzam do weryfikacji", + "Zarejestrowane. Posłuchaj czy wszystko się zgadza" + ], + "isWakewordSampleOk": [ + "Sprawdź czy tak jest dobrze" + ], + "offline": [ + "Przepraszam ale nie mogę tego zrobić będąc w trybie offline" + ], + "notification": [ + "Tak?", + "Słucham?", + "Zamieniam się w słuch!", + "Do usług!", + "Czekam na polecenie", + "Czekam na rozkaz", + "No co tam?", + "Mów o co chodzi" + ] +} From d5cdae0b3f2ff65205876d02ed062aeb6793b5c6 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 17 Oct 2020 18:06:33 +0200 Subject: [PATCH 032/126] Implemented #316, skill suggestion, skill store part --- core/base/SkillStoreManager.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/core/base/SkillStoreManager.py b/core/base/SkillStoreManager.py index 657bc38be..10d620a8b 100644 --- a/core/base/SkillStoreManager.py +++ b/core/base/SkillStoreManager.py @@ -1,3 +1,4 @@ +import difflib from typing import Optional import requests @@ -6,11 +7,14 @@ from core.base.model.Manager import Manager from core.base.model.Version import Version from core.commons import constants +from core.dialog.model.DialogSession import DialogSession from core.util.Decorators import Online class SkillStoreManager(Manager): + SUGGESTIONS_DIFF_LIMIT = 0.75 + def __init__(self): super().__init__() self._skillStoreData = dict() @@ -26,7 +30,6 @@ def onStart(self): self.refreshStoreData() - @Online(catchOnly=True) def onQuarterHour(self): self.refreshStoreData() @@ -40,11 +43,20 @@ def refreshStoreData(self): self._skillStoreData = req.json() + if not self.ConfigManager.getAliceConfigByName('suggestSkillsToInstall'): + return + req = requests.get(url=f'https://skills.projectalice.io/assets/store/{updateChannel}.samples') if req.status_code not in {200, 304}: return - self._skillSamplesData = req.json() + data = req.json() + + for skillName, skill in data.items(): + for intent, samples in skill.get(self.LanguageManager.activeLanguage, dict()).items(): + for sample in samples: + self._skillSamplesData.setdefault(skillName, list()) + self._skillSamplesData[skillName].append(sample) def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]: @@ -79,6 +91,22 @@ def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]: return skillUpdateVersion + def findSkillSuggestion(self, session: DialogSession, string: str = None) -> set: + suggestions = set() + if not self._skillSamplesData or not self.InternetManager.online(): + return suggestions + + userInput = session.previousInput if not string else string + for skillName, samples in self._skillSamplesData.items(): + for sample in samples: + diff = difflib.SequenceMatcher(None, userInput, sample).ratio() + if diff >= self.SUGGESTIONS_DIFF_LIMIT: + suggestions.add(skillName) + break + + return suggestions + + def getSkillUpdateTag(self, skillName: str) -> str: try: return self._getSkillUpdateVersion(skillName)[1] From 4f2d0de0cccfda70353a7e5cf954d3d1fd005266 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sun, 18 Oct 2020 05:52:00 +0200 Subject: [PATCH 033/126] Politness forms as system strings --- system/manager/LanguageManager/strings.json | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/system/manager/LanguageManager/strings.json b/system/manager/LanguageManager/strings.json index 720acae16..c7075e326 100644 --- a/system/manager/LanguageManager/strings.json +++ b/system/manager/LanguageManager/strings.json @@ -166,6 +166,26 @@ "sta 'zitto", "silenzio" ] + }, + "politness": { + "en": [ + "please", + "could you", + "would you be so nice" + ], + "fr": [ + "s'il te plaît", + "pourrais-tu" + ], + "de": [ + "bitte", + "danke", + "kannst du", + "wärst du so nett", + "würdest du", + "könntest du" + ], + "it": [] } } From 7e6401fab536d46267c9f73525e476c787c3d285 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sun, 18 Oct 2020 05:59:17 +0200 Subject: [PATCH 034/126] :construction_worker: These tests need more work --- tests/util/test_InternetManager.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/util/test_InternetManager.py b/tests/util/test_InternetManager.py index 93dd54b1d..e83ea4ec0 100644 --- a/tests/util/test_InternetManager.py +++ b/tests/util/test_InternetManager.py @@ -12,11 +12,17 @@ class TestInternetManager(unittest.TestCase): @mock.patch('core.util.InternetManager.Manager.broadcast') @mock.patch('core.util.InternetManager.requests') @mock.patch('core.util.InternetManager.InternetManager.Commons', new_callable=PropertyMock) - def test_checkOnlineState(self, mock_commons, mock_requests, mock_broadcast): + @mock.patch('core.base.SuperManager.SuperManager') + def test_checkOnlineState(self, mock_commons, mock_requests, mock_broadcast, mock_superManager): common_mock = MagicMock() common_mock.getFunctionCaller.return_value = 'InternetManager' mock_commons.return_value = common_mock + # mock SuperManager + mock_instance = MagicMock() + mock_superManager.getInstance.return_value = mock_instance + mock_instance.configManager.getAliceConfigByName.return_value = 'uuid' + internetManager = InternetManager() # request returns status code 204 From b56db73e57aa30f5797ea14ca6735efd4f4491f0 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sun, 18 Oct 2020 06:33:20 +0200 Subject: [PATCH 035/126] WIP --- core/base/SkillStoreManager.py | 31 +++++++++++++++++++-- system/manager/LanguageManager/strings.json | 4 ++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/core/base/SkillStoreManager.py b/core/base/SkillStoreManager.py index 10d620a8b..2b556ec14 100644 --- a/core/base/SkillStoreManager.py +++ b/core/base/SkillStoreManager.py @@ -50,12 +50,39 @@ def refreshStoreData(self): if req.status_code not in {200, 304}: return - data = req.json() + self.prepareSamplesData(req.json()) + + strings = [ + 'time', + 'give me the date', + 'what time is it', + 'give me money', + 'give me time', + 'shopping list', + 'add something to my shopping list', + 'tell me time', + 'what is blue' + ] + + for string in strings: + sug = self.findSkillSuggestion(session=None, string=string) + self.logDebug(f'Found {len(sug)} potential skill for **{string}**: {sug}', plural='skill') + + + def prepareSamplesData(self, data: dict): + if not data: + return + junks = self.LanguageManager.getStrings(key='politness', skill='system') for skillName, skill in data.items(): for intent, samples in skill.get(self.LanguageManager.activeLanguage, dict()).items(): for sample in samples: self._skillSamplesData.setdefault(skillName, list()) + + for junk in junks: + if junk in sample: + sample = sample.replace(junk, '') + self._skillSamplesData[skillName].append(sample) @@ -93,7 +120,7 @@ def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]: def findSkillSuggestion(self, session: DialogSession, string: str = None) -> set: suggestions = set() - if not self._skillSamplesData or not self.InternetManager.online(): + if not self._skillSamplesData or not self.InternetManager.online: return suggestions userInput = session.previousInput if not string else string diff --git a/system/manager/LanguageManager/strings.json b/system/manager/LanguageManager/strings.json index c7075e326..a8f31a98f 100644 --- a/system/manager/LanguageManager/strings.json +++ b/system/manager/LanguageManager/strings.json @@ -185,7 +185,9 @@ "würdest du", "könntest du" ], - "it": [] + "it": [ + "per favore" + ] } } From 81acacbd3ea37cc4576fe4e9e95285847ac9f799 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Sun, 18 Oct 2020 12:59:44 +0200 Subject: [PATCH 036/126] :art: fix internet manager tests --- tests/util/test_InternetManager.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/tests/util/test_InternetManager.py b/tests/util/test_InternetManager.py index e83ea4ec0..7c2ff6270 100644 --- a/tests/util/test_InternetManager.py +++ b/tests/util/test_InternetManager.py @@ -13,7 +13,7 @@ class TestInternetManager(unittest.TestCase): @mock.patch('core.util.InternetManager.requests') @mock.patch('core.util.InternetManager.InternetManager.Commons', new_callable=PropertyMock) @mock.patch('core.base.SuperManager.SuperManager') - def test_checkOnlineState(self, mock_commons, mock_requests, mock_broadcast, mock_superManager): + def test_checkOnlineState(self, mock_superManager, mock_commons, mock_requests, mock_broadcast): common_mock = MagicMock() common_mock.getFunctionCaller.return_value = 'InternetManager' mock_commons.return_value = common_mock @@ -21,7 +21,7 @@ def test_checkOnlineState(self, mock_commons, mock_requests, mock_broadcast, moc # mock SuperManager mock_instance = MagicMock() mock_superManager.getInstance.return_value = mock_instance - mock_instance.configManager.getAliceConfigByName.return_value = 'uuid' + mock_instance.configManager.getAliceConfigByName.return_value = False internetManager = InternetManager() @@ -31,7 +31,14 @@ def test_checkOnlineState(self, mock_commons, mock_requests, mock_broadcast, moc type(mock_requestsResult).status_code = mock_statusCode mock_requests.get.return_value = mock_requestsResult + # Not called if stay completly offline + mock_instance.configManager.getAliceConfigByName.return_value = True internetManager.checkOnlineState() + mock_requests.get.asset_not_called() + + mock_instance.configManager.getAliceConfigByName.return_value = False + internetManager.checkOnlineState() + mock_requests.get.assert_called_once_with('https://clients3.google.com/generate_204') mock_broadcast.assert_called_once_with(method='internetConnected', exceptions=['InternetManager'], propagateToSkills=True) self.assertEqual(internetManager.online, True) From 311ea6fd95706ab8a06f4cd2af74cd1a2b5dca4a Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sun, 18 Oct 2020 18:35:39 +0200 Subject: [PATCH 037/126] :sparkles: Implemented skill suggestion! Close #313 --- core/base/SkillStoreManager.py | 52 ++++++++++++++++++++++------------ core/server/MqttManager.py | 12 ++++---- core/voice/model/Tts.py | 2 ++ 3 files changed, 43 insertions(+), 23 deletions(-) diff --git a/core/base/SkillStoreManager.py b/core/base/SkillStoreManager.py index 2b556ec14..203699ff8 100644 --- a/core/base/SkillStoreManager.py +++ b/core/base/SkillStoreManager.py @@ -1,4 +1,6 @@ import difflib +from random import shuffle + from typing import Optional import requests @@ -52,22 +54,6 @@ def refreshStoreData(self): self.prepareSamplesData(req.json()) - strings = [ - 'time', - 'give me the date', - 'what time is it', - 'give me money', - 'give me time', - 'shopping list', - 'add something to my shopping list', - 'tell me time', - 'what is blue' - ] - - for string in strings: - sug = self.findSkillSuggestion(session=None, string=string) - self.logDebug(f'Found {len(sug)} potential skill for **{string}**: {sug}', plural='skill') - def prepareSamplesData(self, data: dict): if not data: @@ -123,7 +109,10 @@ def findSkillSuggestion(self, session: DialogSession, string: str = None) -> set if not self._skillSamplesData or not self.InternetManager.online: return suggestions - userInput = session.previousInput if not string else string + userInput = session.input if not string else string + if not userInput: + return suggestions + for skillName, samples in self._skillSamplesData.items(): for sample in samples: diff = difflib.SequenceMatcher(None, userInput, sample).ratio() @@ -131,7 +120,34 @@ def findSkillSuggestion(self, session: DialogSession, string: str = None) -> set suggestions.add(skillName) break - return suggestions + userInputs = list() + userInput = userInput.split() + + if len(userInput) == 1: + userInputs.append(userInput.copy()) + + for _ in range(max(len(userInput), 8)): + shuffle(userInput) + userInputs.append(userInput.copy()) + + for skillName, samples in self._skillSamplesData.items(): + for sample in samples: + for userInput in userInputs: + diff = difflib.SequenceMatcher(None, userInput, sample).ratio() + if diff >= self.SUGGESTIONS_DIFF_LIMIT: + suggestions.add(skillName) + break + + ret = set() + for suggestedSkillName in suggestions: + speakableName = self._skillStoreData.get(suggestedSkillName, dict()).get('speakableName', '') + + if not speakableName: + continue + + ret.add((suggestedSkillName, speakableName)) + + return ret def getSkillUpdateTag(self, skillName: str) -> str: diff --git a/core/server/MqttManager.py b/core/server/MqttManager.py index de021482e..b87b82a82 100644 --- a/core/server/MqttManager.py +++ b/core/server/MqttManager.py @@ -488,11 +488,13 @@ def intentNotRecognized(self, _client, _data, msg: mqtt.MQTTMessage): if session.notUnderstood <= self.ConfigManager.getAliceConfigByName('notUnderstoodRetries'): session.notUnderstood = session.notUnderstood + 1 - self.continueDialog( - sessionId=sessionId, - text=self.TalkManager.randomTalk('notUnderstood', skill='system'), - intentFilter=session.intentFilter - ) + + if not self.ConfigManager.getAliceConfigByName('suggestSkillsToInstall'): + self.continueDialog( + sessionId=sessionId, + text=self.TalkManager.randomTalk('notUnderstood', skill='system'), + intentFilter=session.intentFilter + ) else: session.notUnderstood = 0 self.endDialog(sessionId=sessionId, text=self.TalkManager.randomTalk('notUnderstoodEnd', skill='system')) diff --git a/core/voice/model/Tts.py b/core/voice/model/Tts.py index c70094c68..7b29ee3a0 100644 --- a/core/voice/model/Tts.py +++ b/core/voice/model/Tts.py @@ -160,6 +160,8 @@ def _speak(self, file: Path, session: DialogSession): duration = round(len(AudioSegment.from_file(file)) / 1000, 2) except CouldntDecodeError: self.logError('Error decoding TTS file') + file.unlink() + self.onSay(session) else: self.DialogManager.increaseSessionTimeout(session=session, interval=duration + 0.2) self.ThreadManager.doLater(interval=duration + 0.1, func=self._sayFinished, args=[session, uid]) From c7a223750e6343f3d3d43e149f355e102b7c4477 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 19 Oct 2020 06:31:29 +0200 Subject: [PATCH 038/126] :sparkles: Re-enable skill download counter --- core/base/SkillManager.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index 35af16351..8b2254bed 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -1060,6 +1060,8 @@ def downloadInstallTicket(self, skillName: str) -> bool: ): raise Exception + requests.get(f'https://skills.projectalice.ch/{skillName}') + shutil.move(tmpFile.with_suffix('.tmp'), tmpFile) return True except Exception as e: From 48e57f65244bcfe8a0ea431992e510908e733796 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 19 Oct 2020 17:09:33 +0200 Subject: [PATCH 039/126] Fixing web console bug about togglers and blockly --- .gitprefix | 28 +++++++++++++++++++++++++ core/interface/static/js/common.js | 5 ++++- core/interface/templates/scenarios.html | 4 ---- 3 files changed, 32 insertions(+), 5 deletions(-) create mode 100644 .gitprefix diff --git a/.gitprefix b/.gitprefix new file mode 100644 index 000000000..756dfefb8 --- /dev/null +++ b/.gitprefix @@ -0,0 +1,28 @@ +:art: cleanup +:rocket: deploy +:pencil2: typo +:construction: WIP +:heavy_plus_sign: add dependency +:heavy_minus_sign: remove dependency +:speaker: add logs +:mute: remove logs +:bug: fix +:globe_with_meridians: i18n +:poop: crap +:boom: breaking +:beers: drunk coding +:lipstick: cosmetic +:lock: fix security issue +:heavy_exclamation_mark: woot +:white_check_mark: add test +:green_heart: fix ci +:recycle: refactor +:children_crossing: improve user experience +:wastebasket: deprecated +:see_no_evil: gitignore +:alien: api change +:sparkles: new feature +:wheelchair: improve accessibility +:zap: improve performance +:fire: remove code or file +:ambulance: hotfix diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 304f14dd4..2818a7283 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -287,5 +287,8 @@ $(function () { setInterval(checkCoreStatus, 2000); - $(":checkbox").checkToggler(); + let $checkboxes = $(':checkbox'); + if ($checkboxes.length) { + $checkboxes.checkToggler(); + } }); diff --git a/core/interface/templates/scenarios.html b/core/interface/templates/scenarios.html index 099069a0c..9563027fb 100644 --- a/core/interface/templates/scenarios.html +++ b/core/interface/templates/scenarios.html @@ -7,10 +7,6 @@ {% block loaders %} - - - - {% endblock %} {% block pageTitle %} From 350b71ac141cf8c20449a652a296697415bcf210 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 19 Oct 2020 17:28:27 +0200 Subject: [PATCH 040/126] :pencil2: typo in NR manager for configs after install --- core/interface/NodeRedManager.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index 2bf76d4e1..c4d7404db 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -75,11 +75,11 @@ def configureNewNodeRed(self): self.logInfo('Configuring') # Start to generate base configs and stop it after self.Commons.runRootSystemCommand(['systemctl', 'start', 'nodered']) - time.sleep(4) + time.sleep(10) self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) - time.sleep(3) + time.sleep(5) - config = Path(self.PACKAGE_PATH.parent, '.config.json') + config = Path(self.PACKAGE_PATH.parent, '.config.nodes.json') data = json.loads(config.read_text()) for package in data['nodes'].values(): for node in package['nodes'].values(): From 9f28a261d39485fff9590aad66682118b11beb55 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 19 Oct 2020 17:29:11 +0200 Subject: [PATCH 041/126] :art: Shorter time --- core/interface/NodeRedManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index c4d7404db..a42f627b7 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -75,9 +75,9 @@ def configureNewNodeRed(self): self.logInfo('Configuring') # Start to generate base configs and stop it after self.Commons.runRootSystemCommand(['systemctl', 'start', 'nodered']) - time.sleep(10) - self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) time.sleep(5) + self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered']) + time.sleep(3) config = Path(self.PACKAGE_PATH.parent, '.config.nodes.json') data = json.loads(config.read_text()) From 70133a99d17bcd0507a8d06c16d82bd326f5100f Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 19 Oct 2020 17:32:19 +0200 Subject: [PATCH 042/126] :poop: This is going to be very very shitty if NR changes data structure every now and then... --- core/interface/NodeRedManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index a42f627b7..f127815c1 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -81,7 +81,7 @@ def configureNewNodeRed(self): config = Path(self.PACKAGE_PATH.parent, '.config.nodes.json') data = json.loads(config.read_text()) - for package in data['nodes'].values(): + for package in data.values(): for node in package['nodes'].values(): node['enabled'] = False From 46f13965a99201bb62954287b52ece5495dd0773 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 19 Oct 2020 17:50:05 +0200 Subject: [PATCH 043/126] :sparkles: interface on 0.0.0.0 --- core/interface/NodeRedManager.py | 3 +++ core/interface/WebInterfaceManager.py | 2 +- core/interface/static/css/projectalice.css | 19 +++++++++++++------ 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py index f127815c1..2fec477ae 100644 --- a/core/interface/NodeRedManager.py +++ b/core/interface/NodeRedManager.py @@ -82,7 +82,10 @@ def configureNewNodeRed(self): config = Path(self.PACKAGE_PATH.parent, '.config.nodes.json') data = json.loads(config.read_text()) for package in data.values(): + keeper = self.DEFAULT_NODES_ACTIVE.get(package['name'], list()) for node in package['nodes'].values(): + if node['name'] in keeper: + continue node['enabled'] = False config.write_text(json.dumps(data)) diff --git a/core/interface/WebInterfaceManager.py b/core/interface/WebInterfaceManager.py index dfd37122f..c66b82fba 100644 --- a/core/interface/WebInterfaceManager.py +++ b/core/interface/WebInterfaceManager.py @@ -119,7 +119,7 @@ def onStart(self): # 'ssl_context' : 'adhoc', 'debug' : self.ConfigManager.getAliceConfigByName('debug'), 'port' : int(self.ConfigManager.getAliceConfigByName('webInterfacePort')), - 'host' : self.Commons.getLocalIp(), + 'host' : '0.0.0.0', 'use_reloader': False } ) diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index cf70369f1..1563b4b2a 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -148,26 +148,33 @@ button.button{ font-size: 1.6em; height: 2em; } .skillStoreSkillLeft { flex: 1 } .skillStoreSkillRight { display: flex; margin-right: 5px; align-items: center; justify-content: flex-end; font-size: 3.5em; } -.skillStoreSkillAuthor,.skillStoreSkillVersion,.skillStoreSkillCategory,.skillVersion, -.spaced{ margin: .2em; } +.skillStoreSkillAuthor, .skillStoreSkillVersion, .skillStoreSkillCategory, .skillVersion, +.spaced { margin: .2em; font-size: 0.8em; } /* bigger icon for FA */ .spaced i { width: 1.2em; } /* Tile structure */ .tileContainer { margin: .2em; overflow-y: auto; overflow-x: hidden; display: flex; flex-wrap: wrap; align-content: flex-start; flex-direction: row; } + .tile { background-color: var(--windowBG); margin: .6em; display: flex; flex-wrap: wrap; align-content: flex-start; position: relative; box-shadow: 0 0 20px 5px #141414; } -.skillStoreSkillTile { width: 18.75em; height: 18.75em; } -.skillTile { width: 18.75em; height: 13em; } -.skillStoreSkillDescription { background-color: var(--secondary); font-family: var(--long); font-size: 0.75em; width: 100%; height: 10em; margin: .5em; padding: .5em; box-sizing: border-box; overflow-x: hidden; overflow-y: auto; } +.skillStoreSkillTile { width: 18.75em; height: 18.75em; font-family: var(--readable); } + +.skillTile { width: 18.75em; height: 13em; font-family: var(--readable); font-size: 1em; } + +.skillStoreSkillDescription { background-color: var(--secondary); font-family: var(--long); font-size: 0.75em; width: 100%; height: 10em; margin: .5em; padding: .5em; box-sizing: border-box; overflow-x: hidden; overflow-y: auto; } .skillContainer { position: relative; flex-wrap: wrap; align-content: flex-start; height: 11.3em; margin: .2em; } + .skillDefaultView { display: flex; } + .skillIntentsView { width: 100%; overflow-x: hidden; overflow-y: auto; } -.skillTitle { background-color: var(--secondary); height: 1.5em; width: 100%; padding: 0 .4em; display: flex; align-items: center; justify-content: space-between; overflow: hidden; } +.skillTitle { background-color: var(--secondary); height: 1.5em; width: 100%; padding: 0 .4em; display: flex; align-items: center; justify-content: space-between; overflow: hidden; font-family: var(--short)} + .skillStatus { width: 100%; margin-top: 10px; margin-left: 4px; } + .skillName { overflow: hidden; font-size: 1.25em; } .skillViewIntents, From 694d06e4a4f9cdb8a650d1c896c1565850bd723c Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 19 Oct 2020 20:10:58 +0200 Subject: [PATCH 044/126] :sparkles: simplified skill samples --- core/base/SkillStoreManager.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/core/base/SkillStoreManager.py b/core/base/SkillStoreManager.py index 203699ff8..8d5c32105 100644 --- a/core/base/SkillStoreManager.py +++ b/core/base/SkillStoreManager.py @@ -1,6 +1,5 @@ import difflib from random import shuffle - from typing import Optional import requests @@ -59,17 +58,8 @@ def prepareSamplesData(self, data: dict): if not data: return - junks = self.LanguageManager.getStrings(key='politness', skill='system') for skillName, skill in data.items(): - for intent, samples in skill.get(self.LanguageManager.activeLanguage, dict()).items(): - for sample in samples: - self._skillSamplesData.setdefault(skillName, list()) - - for junk in junks: - if junk in sample: - sample = sample.replace(junk, '') - - self._skillSamplesData[skillName].append(sample) + self._skillSamplesData.setdefault(skillName, skill.get(self.LanguageManager.activeLanguage, list())) def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]: From 16ebc2e0bfe03168f81f4c2d695d843b5a581586 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Tue, 20 Oct 2020 12:20:23 +0200 Subject: [PATCH 045/126] :art: json dumps with tabs --- core/Initializer.py | 17 ++++++++--------- core/base/AssistantManager.py | 2 +- core/base/ConfigManager.py | 6 +++--- core/base/model/AliceSkill.py | 2 +- core/dialog/DialogTemplateManager.py | 4 ++-- core/nlu/model/SnipsNlu.py | 7 +++---- core/voice/WakewordRecorder.py | 2 +- 7 files changed, 19 insertions(+), 21 deletions(-) diff --git a/core/Initializer.py b/core/Initializer.py index 9f7dc5f9d..6447aef4b 100644 --- a/core/Initializer.py +++ b/core/Initializer.py @@ -26,7 +26,9 @@ def __init__(self, default: dict): def __getitem__(self, item): try: - return super().__getitem__(item) or '' + item = super().__getitem__(item) + if not item: + raise Exception except: print(f'Missing key **{item}** in provided yaml file.') return '' @@ -83,9 +85,10 @@ def start(self): if not self.confsFile.exists() and self.oldConfFile.exists(): self._logger.logFatal('Found old conf file, trying to migrate...') try: + # noinspection PyPackageRequirements,PyUnresolvedReferences import config.py - self.confsFile.write_text(json.dumps(config.settings, indent=4, ensure_ascii=False, sort_keys=True)) + self.confsFile.write_text(json.dumps(config.settings, indent='\t', ensure_ascii=False, sort_keys=True)) except: self._logger.logFatal('Something went wrong migrating the old configs, aborting') return False @@ -137,13 +140,9 @@ def loadConfig(self) -> dict: try: # noinspection PyUnboundLocalVariable load = yaml.safe_load(f) - if not load: - raise yaml.YAMLError - initConfs = InitDict(load) except yaml.YAMLError as e: self._logger.logFatal(f'Failed loading init configurations: {e}') - return dict() # Check that we are running using the latest yaml if float(initConfs['version']) < VERSION: @@ -359,7 +358,7 @@ def initProjectAlice(self) -> bool: # NOSONAR elif not self._confsFile.exists() and self._confsSample.exists(): self._logger.logWarning('No config file found, creating it from sample file') - self._confsFile.write_text(json.dumps({configName: configData['defaultValue'] for configName, configData in json.loads(self._confsSample.read_text()).items()}, indent=4, ensure_ascii=False)) + self._confsFile.write_text(json.dumps({configName: configData['defaultValue'] for configName, configData in json.loads(self._confsSample.read_text()).items()}, indent='\t', ensure_ascii=False)) elif self._confsFile.exists() and initConfs['forceRewrite']: self._logger.logWarning('Config file found and force rewrite specified, let\'s restart all this!') @@ -367,7 +366,7 @@ def initProjectAlice(self) -> bool: # NOSONAR self._logger.logFatal('Unfortunately it won\'t be possible, config sample is not existing') return False - self._confsFile.write_text(json.dumps(self._confsSample.read_text(), indent=4)) + self._confsFile.write_text(json.dumps(self._confsSample.read_text(), indent='\t')) try: @@ -656,7 +655,7 @@ def initProjectAlice(self) -> bool: # NOSONAR sort = dict(sorted(confs.items())) try: - self._confsFile.write_text(json.dumps(sort, indent=4)) + self._confsFile.write_text(json.dumps(sort, indent='\t')) except Exception as e: self._logger.logFatal(f'An error occured while writting final configuration file: {e}') diff --git a/core/base/AssistantManager.py b/core/base/AssistantManager.py index 10a200264..7344ab2cf 100644 --- a/core/base/AssistantManager.py +++ b/core/base/AssistantManager.py @@ -196,7 +196,7 @@ def train(self): assistant['intents'] = [intent for intent in intents.values()] - self._assistantPath.write_text(json.dumps(assistant, ensure_ascii=False, indent=4, sort_keys=True)) + self._assistantPath.write_text(json.dumps(assistant, ensure_ascii=False, indent='\t', sort_keys=True)) self.linkAssistant() self.broadcast(method='snipsAssistantInstalled', exceptions=[self.name], propagateToSkills=True) diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index ba95a243a..155ae5edd 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -57,7 +57,7 @@ def _loadCheckAndUpdateAliceConfigFile(self): if not aliceConfigs: self.logInfo('Creating config file from config template') aliceConfigs = {configName: configData['defaultValue'] if 'defaultValue' in configData else configData for configName, configData in self._aliceTemplateConfigurations.items()} - self.CONFIG_FILE.write_text(json.dumps(aliceConfigs, indent=4, ensure_ascii=False)) + self.CONFIG_FILE.write_text(json.dumps(aliceConfigs, indent='\t', ensure_ascii=False)) changes = False @@ -287,7 +287,7 @@ def writeToAliceConfigurationFile(self, confs: dict = None): self._aliceConfigurations = sort try: - self.CONFIG_FILE.write_text(json.dumps(sort, indent=4, sort_keys=True)) + self.CONFIG_FILE.write_text(json.dumps(sort, indent='\t', sort_keys=True)) except Exception: raise ConfigurationUpdateFailed() @@ -304,7 +304,7 @@ def _writeToSkillConfigurationFile(self, skillName: str, confs: dict): confsCleaned = {key: value for key, value in confs.items() if key not in misterProper} skillConfigFile = Path(self.Commons.rootDir(), 'skills', skillName, 'config.json') - skillConfigFile.write_text(json.dumps(confsCleaned, indent=4, ensure_ascii=False, sort_keys=True)) + skillConfigFile.write_text(json.dumps(confsCleaned, indent='\t', ensure_ascii=False, sort_keys=True)) def loadSnipsConfigurations(self) -> dict: diff --git a/core/base/model/AliceSkill.py b/core/base/model/AliceSkill.py index 8a659a2c2..717b4f76e 100644 --- a/core/base/model/AliceSkill.py +++ b/core/base/model/AliceSkill.py @@ -89,7 +89,7 @@ def addUtterance(self, text: str, intent: str) -> bool: if not text in utterances: utterances.append(text) data['intents'][i]['utterances'] = utterances - file.write_text(json.dumps(data, ensure_ascii=False, indent=4)) + file.write_text(json.dumps(data, ensure_ascii=False, indent='\t')) return True return False diff --git a/core/dialog/DialogTemplateManager.py b/core/dialog/DialogTemplateManager.py index efab84e8b..56588aff6 100644 --- a/core/dialog/DialogTemplateManager.py +++ b/core/dialog/DialogTemplateManager.py @@ -165,7 +165,7 @@ def buildCache(self): for file in pathToResources.glob(f'*{constants.JSON_EXT}'): cached[skillName][file.stem] = self.Commons.fileChecksum(file) - self._pathToChecksums.write_text(json.dumps(cached, indent=4, sort_keys=True)) + self._pathToChecksums.write_text(json.dumps(cached, indent='\t', sort_keys=True)) def cleanCache(self, skillName: str): @@ -176,7 +176,7 @@ def cleanCache(self, skillName: str): checksums = json.load(self._pathToChecksums) checksums.pop(skillName, None) - self._pathToChecksums.write_text(json.dumps(checksums, indent=4, sort_keys=True)) + self._pathToChecksums.write_text(json.dumps(checksums, indent='\t', sort_keys=True)) def clearCache(self, rebuild: bool = True): diff --git a/core/nlu/model/SnipsNlu.py b/core/nlu/model/SnipsNlu.py index a57591a72..24ae3138b 100644 --- a/core/nlu/model/SnipsNlu.py +++ b/core/nlu/model/SnipsNlu.py @@ -1,9 +1,8 @@ import json -from pathlib import Path -from subprocess import CompletedProcess - import re import shutil +from pathlib import Path +from subprocess import CompletedProcess from core.commons import constants from core.nlu.model.NluEngine import NluEngine @@ -113,7 +112,7 @@ def train(self): datasetFile = Path('/tmp/snipsNluDataset.json') with datasetFile.open('w') as fp: - json.dump(dataset, fp, ensure_ascii=False, indent=4) + json.dump(dataset, fp, ensure_ascii=False, indent='\t') self.logInfo('Generated dataset for training') diff --git a/core/voice/WakewordRecorder.py b/core/voice/WakewordRecorder.py index 16adcdefe..be2ded6c4 100644 --- a/core/voice/WakewordRecorder.py +++ b/core/voice/WakewordRecorder.py @@ -233,7 +233,7 @@ def finalizeWakeword(self): path.mkdir() - (path/'config.json').write_text(json.dumps(config, indent=4)) + (path/'config.json').write_text(json.dumps(config, indent='\t')) for i in range(1, 4): shutil.move(Path(tempfile.gettempdir(), f'{i}.wav'), path/f'{i}.wav') From cc501cde3f928fb8e0b657072c3c46e2fcc53bbe Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Tue, 20 Oct 2020 19:20:01 +0200 Subject: [PATCH 046/126] :sparkles: Support for speakable name on dev interface --- core/base/SkillManager.py | 1 + core/interface/languages/de.json | 3 ++- core/interface/languages/en.json | 3 ++- core/interface/languages/fr.json | 3 ++- core/interface/languages/it.json | 3 ++- core/interface/static/css/projectalice.css | 5 +++-- core/interface/static/js/devmode.js | 16 +++++++++++++++- core/interface/templates/devmode.html | 8 ++++++++ core/interface/views/DevModeView.py | 1 + 9 files changed, 36 insertions(+), 7 deletions(-) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index 8b2254bed..12e0f1014 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -987,6 +987,7 @@ def createNewSkill(self, skillDefinition: dict) -> bool: 'skillName' : skillName, 'description' : skillDefinition['description'].capitalize(), 'category' : skillDefinition['category'], + 'speakableName' : skillDefinition['speakableName'], 'langs' : supportedLanguages, 'createInstructions': skillDefinition['instructions'], 'pipreq' : [req.strip() for req in skillDefinition['pipreq'].split(',')], diff --git a/core/interface/languages/de.json b/core/interface/languages/de.json index 81e34a9d1..dfa260c79 100644 --- a/core/interface/languages/de.json +++ b/core/interface/languages/de.json @@ -71,5 +71,6 @@ "refuse" : "ablehnen", "leaveEmptyIfNotNeeded" : "Leave empty is not needed", "scenarioNodes" : "Scenario nodes", - "category" : "Category" + "category" : "Category", + "speakableName" : "Speakable name" } diff --git a/core/interface/languages/en.json b/core/interface/languages/en.json index cc183bbe1..834fe6f45 100644 --- a/core/interface/languages/en.json +++ b/core/interface/languages/en.json @@ -71,5 +71,6 @@ "refuse" : "refuse", "leaveEmptyIfNotNeeded" : "Leave empty is not needed", "scenarioNodes" : "Scenario nodes", - "category" : "Category" + "category" : "Category", + "speakableName" : "Speakable name" } diff --git a/core/interface/languages/fr.json b/core/interface/languages/fr.json index 25d9b00ab..b881c84a8 100644 --- a/core/interface/languages/fr.json +++ b/core/interface/languages/fr.json @@ -71,5 +71,6 @@ "refuse" : "refuser", "leaveEmptyIfNotNeeded" : "Leave empty is not needed", "scenarioNodes" : "Scenario nodes", - "category" : "Category" + "category" : "Category", + "speakableName" : "Nom prononçable" } diff --git a/core/interface/languages/it.json b/core/interface/languages/it.json index 487775341..cffd6effa 100644 --- a/core/interface/languages/it.json +++ b/core/interface/languages/it.json @@ -71,5 +71,6 @@ "refuse" : "refiutare", "leaveEmptyIfNotNeeded" : "Leave empty is not needed", "scenarioNodes" : "Scenario nodes", - "category" : "Category" + "category" : "Category", + "speakableName" : "Speakable name" } diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index 1563b4b2a..bf01c1403 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -141,8 +141,9 @@ button.button{ font-size: 1.6em; height: 2em; } .tabsContainer { padding-bottom: .1em; border: 2px solid var(--secondary); } .tabsContainer ul { display: flex; background-color: var(--secondary); margin-top: 0; padding-left: .4em; } .tabsContainer li { background-color: var(--windowBG); font-size: 1.25em; padding: .2em .7em; margin: .3em .3em 0 0; display: inline-flex; align-items: center; box-sizing: border-box; border-radius: .3em .3em 0 0; border: 2px solid var(--accent); border-bottom: 0; } -.tabsContainer .activeTab { background-color: var(--mainBG); } -.tabsContent { display: flex; overflow-y: auto; overflow-x: hidden; padding-left: .7em; } +.tabsContainer .activeTab { background-color: var(--mainBG); } + +.tabsContent { display: flex; overflow-y: auto; overflow-x: hidden; padding-left: .7em; padding-right: .7em; padding-bottom: .7em; } .skillStoreSkillInfoContainer { margin-top: .4em; display: flex; width: 100%; flex-flow: row; } .skillStoreSkillLeft { flex: 1 } diff --git a/core/interface/static/js/devmode.js b/core/interface/static/js/devmode.js index 3decec6c3..16dba8fe7 100644 --- a/core/interface/static/js/devmode.js +++ b/core/interface/static/js/devmode.js @@ -8,7 +8,7 @@ $(function () { let $goGithubButton = $('#goGithubButton'); function toggleCreateButton() { - if ($('#skillNameOk').is(':visible') && $('#skillDescOk').is(':visible')) { + if ($('#skillNameOk').is(':visible') && $('#skillDescOk').is(':visible') && $('#speakableNameNameOk').is(':visible')) { $createSkillButton.show(); } else { $createSkillButton.hide(); @@ -22,6 +22,8 @@ $(function () { $('#skillNameKo').show(); $('#skillDescOk').hide(); $('#skillDescKo').show(); + $('#speakableNameDescOk').hide(); + $('#speakableNameDescKo').show(); $uploadSkillButton.hide(); $goGithubButton.hide(); } @@ -80,6 +82,17 @@ $(function () { }); }); + $('#speakableName').on('input', function () { + if ($(this).val().length < 5) { + $('#speakableNameNameOk').hide(); + $('#speakableNameNameKo').show(); + } else { + $('#speakableNameNameOk').show(); + $('#speakableNameNameKo').hide(); + } + toggleCreateButton(); + }); + $('#skillDesc').on('input', function () { if ($(this).val().length > 20) { $('#skillDescKo').hide(); @@ -96,6 +109,7 @@ $(function () { url: '/devmode/' + $skillName.val() + '/', type: 'PUT', data: { + 'speakableName' : $('#speakableName').val(), 'description' : $('#skillDesc').val(), 'fr' : ($('#fr').is(':checked')) ? 'yes' : 'no', 'de' : ($('#de').is(':checked')) ? 'yes' : 'no', diff --git a/core/interface/templates/devmode.html b/core/interface/templates/devmode.html index 09fe9e212..0113f0d87 100644 --- a/core/interface/templates/devmode.html +++ b/core/interface/templates/devmode.html @@ -29,6 +29,14 @@
+
+ +
+ + + +
+
diff --git a/core/interface/views/DevModeView.py b/core/interface/views/DevModeView.py index 77f2a4bc9..c4e77e44c 100644 --- a/core/interface/views/DevModeView.py +++ b/core/interface/views/DevModeView.py @@ -44,6 +44,7 @@ def put(self, skillName: str): try: newSkill = { 'name' : skillName, + 'speakableName' : request.form.get('speakableName', ''), 'description' : request.form.get('description', 'Missing description'), 'category' : request.form.get('skillCategory', 'undefined'), 'fr' : request.form.get('fr', False), From a5f3351fe540dc30aafadc0efc15b2a2230df2db Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Tue, 20 Oct 2020 19:25:18 +0200 Subject: [PATCH 047/126] :bug: fix for missing scrollbar? --- core/interface/static/css/projectalice.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index bf01c1403..4616eab39 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -183,7 +183,7 @@ button.button{ font-size: 1.6em; height: 2em; } .skillInstructions { width: 100%; margin: .2em; text-align: right; } /* Configuration Screens */ -.configBox { width: 100%; display: table; font-family: var(--readable); background-color: var(--secondary); border-spacing: 0 10px; } +.configBox { width: 100%; font-family: var(--readable); background-color: var(--secondary); border-spacing: 0 10px; } .configCategory { width: 100%; display: table; margin-bottom: 30px; font-family: var(--long); background-color: var(--secondary); border-spacing: 0 10px; } From 415cfec71815fb60b74931c0bb15bd876b8c080f Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Tue, 20 Oct 2020 19:26:51 +0200 Subject: [PATCH 048/126] :art: make sure SK is uptodate prior to create skill --- core/base/SkillManager.py | 1 + 1 file changed, 1 insertion(+) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index 12e0f1014..2e88e73ba 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -1001,6 +1001,7 @@ def createNewSkill(self, skillDefinition: dict) -> bool: dump = Path(f'/tmp/{skillName}.json') dump.write_text(json.dumps(data, ensure_ascii=False)) + self.Commons.runSystemCommand(['./venv/bin/pip', '--upgrade', 'projectalice-sk']) self.Commons.runSystemCommand(['./venv/bin/projectalice-sk', 'create', '--file', f'{str(dump)}']) self.logInfo(f'Created **skillName** skill') From f97b4f9fe5c3c4848ec5c8654023e50ce868b460 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Wed, 21 Oct 2020 11:16:35 +0200 Subject: [PATCH 049/126] :sparkles: enable polish core side --- configTemplate.json | 17 +++++- core/base/SkillManager.py | 2 + core/interface/languages/de.json | 79 ++++++++++++++------------- core/interface/languages/en.json | 79 ++++++++++++++------------- core/interface/languages/fr.json | 79 ++++++++++++++------------- core/interface/languages/it.json | 79 ++++++++++++++------------- core/interface/templates/devmode.html | 2 + core/interface/views/DevModeView.py | 1 + core/voice/model/AmazonTts.py | 48 +++++++++++++++- core/voice/model/GoogleTts.py | 42 +++++++++++++- 10 files changed, 268 insertions(+), 160 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index abf7557cf..b45273789 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -458,7 +458,8 @@ "fr", "de", "it", - "pt" + "pt", + "pl" ], "description" : "Project Alice active language", "category" : "system" @@ -603,6 +604,13 @@ "IT", "CH" ] + }, + "pl": { + "default" : false, + "defaultCountryCode": "PL", + "countryCodes" : [ + "PL" + ] } }, "dataType" : "list", @@ -640,6 +648,13 @@ "IT", "CH" ] + }, + "pl": { + "default" : false, + "defaultCountryCode": "PL", + "countryCodes" : [ + "PL" + ] } }, "display" : "hidden", diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index 2e88e73ba..3d3a25b6a 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -962,6 +962,8 @@ def createNewSkill(self, skillDefinition: dict) -> bool: supportedLanguages.append('de') if skillDefinition['it'] == 'yes': supportedLanguages.append('it') + if skillDefinition['pl'] == 'yes': + supportedLanguages.append('pl') conditions = { 'lang': supportedLanguages diff --git a/core/interface/languages/de.json b/core/interface/languages/de.json index dfa260c79..7390806c7 100644 --- a/core/interface/languages/de.json +++ b/core/interface/languages/de.json @@ -15,46 +15,47 @@ "updatee": "aktualisieren", "addWidgetTitle": "Verfügbare Widgets", "viewIntents": "Intents ansehen", - "skillSettings": "Skilleinstellungen", - "save": "Speichern", - "followLogs": "Autoscroll", - "adminTabTitleSetting": "Alice Einstellungen", - "adminTabTitleUtilities": "Utilities", - "restartAlice": "Alice neustarten", - "reboot": "Reboot", - "loading": "Laden...", - "trainAssistant": "Assistant trainieren", - "wipeAll": "Alles löschen", - "devmodeNewSkill": "Neuer Skill", - "devmodeEditSkill": "Skill editieren", - "reload": "Neu laden", - "skillName": "Skill Name", - "skillDesc": "Skill Beschreibung", - "skillLanguage": "Skill Sprache", - "english": "Englisch", - "french": "Französisch", - "german": "Deutsch", - "italian": "Italienisch", - "pipreq": "PIP Anforderungen", - "sysreq": "System Anforderungen", - "conditions": "Bedingungen", - "conditionOnline": "Benötigt eine bestehende Internetverbindung", - "conditionSkill": "Benötigt weitere Skills", - "conditionNotSkill": "Skills die nicht parallel installiert sein dürfen", - "conditionASRArbitrary": "Dieser Skill benötigt Wörter, auf welche die Spracherkennung nicht trainiert wurde", - "conditionActiveManager": "Die gelisteten Manager werden benötigt ", - "widgets": "Widgets", + "skillSettings" : "Skilleinstellungen", + "save" : "Speichern", + "followLogs" : "Autoscroll", + "adminTabTitleSetting" : "Alice Einstellungen", + "adminTabTitleUtilities" : "Utilities", + "restartAlice" : "Alice neustarten", + "reboot" : "Reboot", + "loading" : "Laden...", + "trainAssistant" : "Assistant trainieren", + "wipeAll" : "Alles löschen", + "devmodeNewSkill" : "Neuer Skill", + "devmodeEditSkill" : "Skill editieren", + "reload" : "Neu laden", + "skillName" : "Skill Name", + "skillDesc" : "Skill Beschreibung", + "skillLanguage" : "Skill Sprache", + "english" : "Englisch", + "french" : "Französisch", + "german" : "Deutsch", + "italian" : "Italienisch", + "polish" : "Polish", + "pipreq" : "PIP Anforderungen", + "sysreq" : "System Anforderungen", + "conditions" : "Bedingungen", + "conditionOnline" : "Benötigt eine bestehende Internetverbindung", + "conditionSkill" : "Benötigt weitere Skills", + "conditionNotSkill" : "Skills die nicht parallel installiert sein dürfen", + "conditionASRArbitrary" : "Dieser Skill benötigt Wörter, auf welche die Spracherkennung nicht trainiert wurde", + "conditionActiveManager" : "Die gelisteten Manager werden benötigt ", + "widgets" : "Widgets", "commaSeparatedExplanation": "Einzelne Elemente mit einem Komma trennen, zum Beispiel 'foo, bar, baz'", - "createSkill" : "Erstelle den Skill", - "uploadSkillToGithub" : "Den Skill auf Github hochladen", - "resetSkill" : "Reset", - "actions" : "Actions", - "addUser" : "Neuer Benutzer", - "addWakeword" : "Neues Wakeword", - "tuneWakeword" : "Tune wakeword", - "widgetCustStyleTitle" : "Anzeige Einstellungen", - "widgetConfigTitle" : "Widget Einstellungen", - "noConfs" : "Keine Einstellungen", + "createSkill" : "Erstelle den Skill", + "uploadSkillToGithub" : "Den Skill auf Github hochladen", + "resetSkill" : "Reset", + "actions" : "Actions", + "addUser" : "Neuer Benutzer", + "addWakeword" : "Neues Wakeword", + "tuneWakeword" : "Tune wakeword", + "widgetCustStyleTitle" : "Anzeige Einstellungen", + "widgetConfigTitle" : "Widget Einstellungen", + "noConfs" : "Keine Einstellungen", "saving" : "Speichern", "saved" : "Gespeichert", "saveFailed" : "Speichern fehlgeschlagen", diff --git a/core/interface/languages/en.json b/core/interface/languages/en.json index 834fe6f45..5a8c65a93 100644 --- a/core/interface/languages/en.json +++ b/core/interface/languages/en.json @@ -15,46 +15,47 @@ "updatee": "Update", "addWidgetTitle": "Available widgets", "viewIntents": "View intents", - "skillSettings": "Skill settings", - "save": "Save", - "followLogs": "Autoscroll", - "adminTabTitleSetting": "Alice Settings", - "adminTabTitleUtilities": "Utilities", - "restartAlice": "Restart Alice", - "reboot": "Reboot", - "trainAssistant": "Train assistant", - "wipeAll": "Wipe all", - "devmodeNewSkill": "Skill creation", - "devmodeEditSkill": "Skill edition", - "reload": "Reload", - "loading": "Loading...", - "skillName": "Skill name", - "skillDesc": "Skill description", - "skillLanguage": "Skill language", - "english": "English", - "french": "French", - "german": "German", - "italian": "Italian", - "pipreq": "PIP requirements", - "sysreq": "System requirements", - "conditions": "Conditions", - "conditionOnline": "Requires to be connected to the internet", - "conditionSkill": "Required skills", - "conditionNotSkill": "Skills that can't be installed at the same time", - "conditionASRArbitrary": "This skill requires the ASR to understand words that were not trained", - "conditionActiveManager": "Managers listed are required to be running", - "widgets": "Widgets", + "skillSettings" : "Skill settings", + "save" : "Save", + "followLogs" : "Autoscroll", + "adminTabTitleSetting" : "Alice Settings", + "adminTabTitleUtilities" : "Utilities", + "restartAlice" : "Restart Alice", + "reboot" : "Reboot", + "trainAssistant" : "Train assistant", + "wipeAll" : "Wipe all", + "devmodeNewSkill" : "Skill creation", + "devmodeEditSkill" : "Skill edition", + "reload" : "Reload", + "loading" : "Loading...", + "skillName" : "Skill name", + "skillDesc" : "Skill description", + "skillLanguage" : "Skill language", + "english" : "English", + "french" : "French", + "german" : "German", + "italian" : "Italian", + "polish" : "Polish", + "pipreq" : "PIP requirements", + "sysreq" : "System requirements", + "conditions" : "Conditions", + "conditionOnline" : "Requires to be connected to the internet", + "conditionSkill" : "Required skills", + "conditionNotSkill" : "Skills that can't be installed at the same time", + "conditionASRArbitrary" : "This skill requires the ASR to understand words that were not trained", + "conditionActiveManager" : "Managers listed are required to be running", + "widgets" : "Widgets", "commaSeparatedExplanation": "Enter a list separated by comma, example 'foo, bar, baz'", - "createSkill" : "Create the skill", - "uploadSkillToGithub" : "Upload the skill to Github", - "resetSkill" : "Reset", - "actions" : "Actions", - "addUser" : "Add user", - "addWakeword" : "Add wakeword", - "tuneWakeword" : "Tune wakeword", - "widgetCustStyleTitle" : "Display Options", - "widgetConfigTitle" : "Widget Options", - "noConfs" : "No config for this widget!", + "createSkill" : "Create the skill", + "uploadSkillToGithub" : "Upload the skill to Github", + "resetSkill" : "Reset", + "actions" : "Actions", + "addUser" : "Add user", + "addWakeword" : "Add wakeword", + "tuneWakeword" : "Tune wakeword", + "widgetCustStyleTitle" : "Display Options", + "widgetConfigTitle" : "Widget Options", + "noConfs" : "No config for this widget!", "saving" : "Saving", "saved" : "Saved", "saveFailed" : "Save failed", diff --git a/core/interface/languages/fr.json b/core/interface/languages/fr.json index b881c84a8..3722d4340 100644 --- a/core/interface/languages/fr.json +++ b/core/interface/languages/fr.json @@ -15,46 +15,47 @@ "updatee": "mise à jour", "addWidgetTitle": "Widgets disponibles", "viewIntents": "Voir intents", - "skillSettings": "Configuration", - "save": "Sauvegarder", - "followLogs": "Autoscroll", - "adminTabTitleSetting": "Parametres Alice", - "adminTabTitleUtilities": "Utilitaires", - "restartAlice": "Relancer Alice", - "reboot": "Reboot", - "trainAssistant": "Entrainer assistant", - "wipeAll": "Tout supprimer", - "devmodeNewSkill": "Nouveau skill", - "devmodeEditSkill": "Edition de skill", - "reload": "Recharger", - "loading": "Chargement...", - "skillName": "Nom du skill", - "skillDesc": "Description du skill", - "skillLanguage": "Langue du skill", - "english": "Anglais", - "french": "Français", - "german": "Allemand", - "italian": "Italien", - "pipreq": "Dépendances PIP", - "sysreq": "Dépendance systeme", - "conditions": "Conditions", - "conditionOnline": "Connexion à internet requise", - "conditionSkill": "Skills requis", - "conditionNotSkill": "Skills qui ne doivent pas être installé en même temps", - "conditionASRArbitrary": "Ce skill a besoin d'un ASR qui comprend des mots pour lesquels il n'a pas été entrainé", - "conditionActiveManager": "Managers qui doivent être actifs pour que ce skill fonctionne", - "widgets": "Widgets", + "skillSettings" : "Configuration", + "save" : "Sauvegarder", + "followLogs" : "Autoscroll", + "adminTabTitleSetting" : "Parametres Alice", + "adminTabTitleUtilities" : "Utilitaires", + "restartAlice" : "Relancer Alice", + "reboot" : "Reboot", + "trainAssistant" : "Entrainer assistant", + "wipeAll" : "Tout supprimer", + "devmodeNewSkill" : "Nouveau skill", + "devmodeEditSkill" : "Edition de skill", + "reload" : "Recharger", + "loading" : "Chargement...", + "skillName" : "Nom du skill", + "skillDesc" : "Description du skill", + "skillLanguage" : "Langue du skill", + "english" : "Anglais", + "french" : "Français", + "german" : "Allemand", + "italian" : "Italien", + "polish" : "Polonais", + "pipreq" : "Dépendances PIP", + "sysreq" : "Dépendance systeme", + "conditions" : "Conditions", + "conditionOnline" : "Connexion à internet requise", + "conditionSkill" : "Skills requis", + "conditionNotSkill" : "Skills qui ne doivent pas être installé en même temps", + "conditionASRArbitrary" : "Ce skill a besoin d'un ASR qui comprend des mots pour lesquels il n'a pas été entrainé", + "conditionActiveManager" : "Managers qui doivent être actifs pour que ce skill fonctionne", + "widgets" : "Widgets", "commaSeparatedExplanation": "Une liste séparée par des virgules, exemple 'foo, bar, baz'", - "createSkill" : "Créer le skill", - "uploadSkillToGithub" : "Uploader le skill sur Github", - "resetSkill" : "Reset", - "actions" : "Actions", - "addUser" : "Ajouter utilisateur", - "addWakeword" : "Ajouter wakeword", - "tuneWakeword" : "Tuner wakeword", - "widgetCustStyleTitle" : "Options d'affichage", - "widgetConfigTitle" : "Options de widget", - "noConfs" : "Pas de config pour ce widget!", + "createSkill" : "Créer le skill", + "uploadSkillToGithub" : "Uploader le skill sur Github", + "resetSkill" : "Reset", + "actions" : "Actions", + "addUser" : "Ajouter utilisateur", + "addWakeword" : "Ajouter wakeword", + "tuneWakeword" : "Tuner wakeword", + "widgetCustStyleTitle" : "Options d'affichage", + "widgetConfigTitle" : "Options de widget", + "noConfs" : "Pas de config pour ce widget!", "saving" : "Sauvegarde", "saved" : "Sauvegardé", "saveFailed" : "Erreur de sauvegarde", diff --git a/core/interface/languages/it.json b/core/interface/languages/it.json index cffd6effa..230ff3fb0 100644 --- a/core/interface/languages/it.json +++ b/core/interface/languages/it.json @@ -15,46 +15,47 @@ "updatee": "aggiorna", "addWidgetTitle": "Widgets disponibili", "viewIntents": "Visualizza intents", - "skillSettings": "Impostazioni skills", - "save": "Save", - "followLogs": "Autoscroll", - "adminTabTitleSetting": "Impostazioni Alice", - "adminTabTitleUtilities": "Utilities", - "restartAlice": "Riavvia Alice", - "reboot": "Riavvia il Sistema", - "trainAndDlAssistant": "Scarica Assistente", - "wipeAll": "Cancella tutto", - "devmodeNewSkill": "Crea skill", - "devmodeEditSkill": "Modifica skill", - "reload": "Ricarica", - "loading": "Caricamento...", - "skillName": "nome Skill", - "skillDesc": "descrizione skill", - "skillLanguage": "Lingua skill", - "english": "Inglese", - "french": "Francese", - "german": "Tedesco", - "italian": "Italiano", - "pipreq": "Prerequisiti di PIP", - "sysreq": "Prerequisiti di sistema", - "conditions": "Condizioni", - "conditionOnline": "Richiede una connessione ad internet", - "conditionSkill": "Skills richieste", - "conditionNotSkill": "Skills che non possono essere installate contemporaneamente", - "conditionASRArbitrary": "Questa skill contiene parole che l'ASR non è addestrato a riconoscere", - "conditionActiveManager": "Devono essere avviati i seguenti gestori", - "widgets": "Widgets", + "skillSettings" : "Impostazioni skills", + "save" : "Save", + "followLogs" : "Autoscroll", + "adminTabTitleSetting" : "Impostazioni Alice", + "adminTabTitleUtilities" : "Utilities", + "restartAlice" : "Riavvia Alice", + "reboot" : "Riavvia il Sistema", + "trainAndDlAssistant" : "Scarica Assistente", + "wipeAll" : "Cancella tutto", + "devmodeNewSkill" : "Crea skill", + "devmodeEditSkill" : "Modifica skill", + "reload" : "Ricarica", + "loading" : "Caricamento...", + "skillName" : "nome Skill", + "skillDesc" : "descrizione skill", + "skillLanguage" : "Lingua skill", + "english" : "Inglese", + "french" : "Francese", + "german" : "Tedesco", + "italian" : "Italiano", + "polish" : "Polish", + "pipreq" : "Prerequisiti di PIP", + "sysreq" : "Prerequisiti di sistema", + "conditions" : "Condizioni", + "conditionOnline" : "Richiede una connessione ad internet", + "conditionSkill" : "Skills richieste", + "conditionNotSkill" : "Skills che non possono essere installate contemporaneamente", + "conditionASRArbitrary" : "Questa skill contiene parole che l'ASR non è addestrato a riconoscere", + "conditionActiveManager" : "Devono essere avviati i seguenti gestori", + "widgets" : "Widgets", "commaSeparatedExplanation": "Una lista separata da virgole, per esempio 'foo, bar, baz'", - "createSkill" : "Crea la skill", - "uploadSkillToGithub" : "Carica la skill su Github", - "resetSkill" : "Resetta", - "actions" : "Azioni", - "addUser" : "Nuovo utente", - "addWakeword" : "Aggiungi wakeword", - "tuneWakeword" : "Regola wakeword", - "widgetCustStyleTitle" : "Opzioni dello schermo", - "widgetConfigTitle" : "Opzioni dei widget", - "noConfs" : "Nessuna configurazione per questo widget!", + "createSkill" : "Crea la skill", + "uploadSkillToGithub" : "Carica la skill su Github", + "resetSkill" : "Resetta", + "actions" : "Azioni", + "addUser" : "Nuovo utente", + "addWakeword" : "Aggiungi wakeword", + "tuneWakeword" : "Regola wakeword", + "widgetCustStyleTitle" : "Opzioni dello schermo", + "widgetConfigTitle" : "Opzioni dei widget", + "noConfs" : "Nessuna configurazione per questo widget!", "saving" : "Salvataggio in corso", "saved" : "Salvato", "saveFailed" : "Salvataggio fallito", diff --git a/core/interface/templates/devmode.html b/core/interface/templates/devmode.html index 0113f0d87..ee4367d9f 100644 --- a/core/interface/templates/devmode.html +++ b/core/interface/templates/devmode.html @@ -77,6 +77,8 @@
+ +
diff --git a/core/interface/views/DevModeView.py b/core/interface/views/DevModeView.py index c4e77e44c..9fb9984ff 100644 --- a/core/interface/views/DevModeView.py +++ b/core/interface/views/DevModeView.py @@ -50,6 +50,7 @@ def put(self, skillName: str): 'fr' : request.form.get('fr', False), 'de' : request.form.get('de', False), 'it' : request.form.get('it', False), + 'pl' : request.form.get('pl', False), 'instructions' : request.form.get('createInstructions', False), 'pipreq' : request.form.get('pipreq', ''), 'sysreq' : request.form.get('sysreq', ''), diff --git a/core/voice/model/AmazonTts.py b/core/voice/model/AmazonTts.py index 9bedd0c64..bc5ffef9e 100644 --- a/core/voice/model/AmazonTts.py +++ b/core/voice/model/AmazonTts.py @@ -6,6 +6,7 @@ from core.voice.model.Tts import Tts try: + # noinspection PyUnresolvedReferences import boto3 except ModuleNotFoundError: pass # Auto installeed @@ -186,7 +187,52 @@ def __init__(self, user: User = None): 'Bianca': { 'neural': False }, - 'Carla' : { + 'Carla' : { + 'neural': False + } + } + }, + 'pl-PL': { + 'male' : { + 'Jacek': { + 'neural': False + }, + 'Jan' : { + 'neural': False + } + }, + 'female': { + 'Ewa' : { + 'neural': False + }, + 'Maja': { + 'neural': False + } + } + }, + 'pt-BR': { + 'male' : { + 'Ricardo': { + 'neural': False + } + }, + 'female': { + 'Camila' : { + 'neural': False + }, + 'Vitoria': { + 'neural': False + } + } + }, + 'pt-PT': { + 'male' : { + 'Cristiano': { + 'neural': False + } + }, + 'female': { + 'Ines': { 'neural': False } } diff --git a/core/voice/model/GoogleTts.py b/core/voice/model/GoogleTts.py index b3e244425..72267c88c 100644 --- a/core/voice/model/GoogleTts.py +++ b/core/voice/model/GoogleTts.py @@ -7,7 +7,9 @@ from core.voice.model.Tts import Tts try: + # noinspection PyUnresolvedReferences,PyPackageRequirements from google.oauth2.service_account import Credentials + # noinspection PyUnresolvedReferences,PyPackageRequirements from google.cloud import texttospeech except: pass # Auto installed @@ -142,13 +144,49 @@ def __init__(self, user: User = None): 'it-IT-Standard-A': { 'neural': False }, - 'it-IT-Standard-B' : { + 'it-IT-Standard-B': { 'neural': False }, 'it-IT-Wavenet-A' : { 'neural': True }, - 'it-IT-Wavenet-B': { + 'it-IT-Wavenet-B' : { + 'neural': True + } + } + }, + 'pl-PL': { + 'male' : { + 'pl-PL-Standard-B': { + 'neural': False + }, + 'pl-PL-Standard-C': { + 'neural': False + }, + 'pl-PL-Wavenet-B' : { + 'neural': True + }, + 'pl-PL-Wavenet-C' : { + 'neural': True + } + }, + 'female': { + 'pl-PL-Standard-A': { + 'neural': False + }, + 'pl-PL-Standard-D': { + 'neural': False + }, + 'pl-PL-Standard-E': { + 'neural': True + }, + 'pl-PL-Wavenet-A' : { + 'neural': True + }, + 'pl-PL-Wavenet-D' : { + 'neural': True + }, + 'pl-PL-Wavenet-E' : { 'neural': True } } From a1c7c8acb7a3486e2d1c7ccd3ee0de1e86850270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20B=C5=82achowicz?= Date: Wed, 21 Oct 2020 11:51:51 +0200 Subject: [PATCH 050/126] Interface polish translation --- core/interface/languages/pl.json | 73 ++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 core/interface/languages/pl.json diff --git a/core/interface/languages/pl.json b/core/interface/languages/pl.json new file mode 100644 index 000000000..3f3052f3f --- /dev/null +++ b/core/interface/languages/pl.json @@ -0,0 +1,73 @@ +{ + "home": "start", + "skills": "umiejętności", + "myHome": "mój dom", + "scenarios": "scenariusze", + "syslog": "log systemowy", + "alicewatch": "alicewatch", + "admin": "admin", + "devMode": "tryb dev", + "by": "przez", + "status": "status", + "enable": "włącz", + "disable": "wyłącz", + "delete": "usuń", + "updatee": "Aktualizuj", + "addWidgetTitle": "Dostępne widżety", + "viewIntents": "Zobacz intencje", + "skillSettings": "Skill settings", + "save": "Zapisz", + "followLogs": "Auto-przewijanie", + "adminTabTitleSetting": "Ustawienia", + "adminTabTitleUtilities": "Narzędzia", + "restartAlice": "Uruchom ponownie Alice", + "reboot": "Uruchom ponownie", + "trainAssistant": "Trenuj asystenta", + "wipeAll": "Usuń wszystko", + "devmodeNewSkill": "Utwórz umiejętność", + "devmodeEditSkill": "Edycja umijejętności", + "reload": "Przeładuj", + "loading": "Ładowanie...", + "skillName": "Nazwa umiejętności", + "skillDesc": "Opis umiejętności", + "skillLanguage": "Język umiejętności", + "english": "Angielski", + "french": "Francuski", + "german": "Niemiecki", + "italian": "Włoski", + "polish": "Polski", + "pipreq": "Wymagania PIP", + "sysreq": "Wymagania systempwe", + "conditions": "Warunki", + "conditionOnline": "Wymaga połączenia z internetem", + "conditionSkill": "Wymagane umiejętności", + "conditionNotSkill": "Umiejętności, które nie moga być instalowane w tym samym czasie", + "conditionASRArbitrary": "Ta umiejętność wymaga od modułu ASR zrozumienia słów, które nie były trenowane", + "conditionActiveManager": "Menedżerowie z listy poniżej muszą być uruchomieni", + "widgets": "Widżety", + "commaSeparatedExplanation": "Wprowadź listę rozdzieloną przecinkami, np. 'jeden, dwa, trzy'", + "createSkill" : "Utwórz umiejętność", + "uploadSkillToGithub" : "Wyślij umiejętność na Github", + "resetSkill" : "Reset", + "actions" : "Akcje", + "addUser" : "Dodaj użytkownika", + "addWakeword" : "Dodaj wyrażenie wywołujące", + "tuneWakeword" : "Popraw wyrażenie wywołujące", + "widgetCustStyleTitle" : "Wyświetl opcje", + "widgetConfigTitle" : "Opcje widżetu", + "noConfs" : "Brak konfiguracji widżetu!", + "saving" : "Zapisywanie", + "saved" : "Zapisano", + "saveFailed" : "Błąd zapisu", + "confMoveDevice" : "Czy chcesz umieścić to urządzenie w nowej lokalizacji: ", + "confDeleteDevice" : "Czy na pewno chcesz usunąć to urządzenie?", + "confDeleteZone" : "Czy na pewno chcesz usunąć tę lokalizację?", + "nameNewZone" : "Podaj nazwę lokalizacji", + "unreachable" : "Projekt Alice jest obecnie niedostępny,\nstrona spróbuje się wkrótce przeładować", + "instructions" : "Instrukcja", + "renameDevice" : "Podaj nową nazwę urządzenia?", + "renameLocation" : "Podaj nową nazwę lokalizacji?", + "warningSkillChangeAliceconf": "Wykryto próbę zmiany podstawowej konfiguracji!", + "accept" : "zaakceptuj", + "refuse" : "odmów" +} From b216784dac2e5cf7783a4299ba68e1e0462297ac Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Wed, 21 Oct 2020 14:06:23 +0200 Subject: [PATCH 051/126] :sparkles: don't switch asr if the fallback is the same --- core/asr/ASRManager.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/asr/ASRManager.py b/core/asr/ASRManager.py index 4d8d7638e..2d0236032 100644 --- a/core/asr/ASRManager.py +++ b/core/asr/ASRManager.py @@ -23,6 +23,7 @@ def __init__(self): self._asr = None self._streams: Dict[str, Recorder] = dict() self._translator = Translator() + self._usingFallback = False def onStart(self): @@ -98,7 +99,8 @@ def asr(self) -> Asr: def onInternetConnected(self): - if self.ConfigManager.getAliceConfigByName('stayCompletlyOffline') or self.ConfigManager.getAliceConfigByName('keepASROffline'): + if self.ConfigManager.getAliceConfigByName('stayCompletlyOffline') or self.ConfigManager.getAliceConfigByName('keepASROffline') or \ + self.ConfigManager.getAliceConfigByName('asrFallback') == self.ConfigManager.getAliceConfigByName('asr'): return if not self._asr.isOnlineASR: From ee0df171e2cfe1bd596bc0d06d8f431b08e11095 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Wed, 21 Oct 2020 20:22:54 +0200 Subject: [PATCH 052/126] :sparkles: disable audio and mic if no audio hardware selected in yaml --- core/Initializer.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/Initializer.py b/core/Initializer.py index 6447aef4b..d4bd07f24 100644 --- a/core/Initializer.py +++ b/core/Initializer.py @@ -536,6 +536,11 @@ def initProjectAlice(self) -> bool: # NOSONAR audioHardware = hardware break + if not audioHardware: + confs['disableSoundAndMic'] = True + else: + confs['disableSoundAndMic'] = False + hlcServiceFilePath = Path('/etc/systemd/system/hermesledcontrol.service') if initConfs['useHLC']: From 524e06c74d530621b77b5384a2461f9d254dda23 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Wed, 21 Oct 2020 20:35:56 +0200 Subject: [PATCH 053/126] :boom: :construction: Started skill store versioning rework --- core/base/SkillStoreManager.py | 14 +++++++++++--- core/commons/constants.py | 2 ++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/base/SkillStoreManager.py b/core/base/SkillStoreManager.py index 8d5c32105..790101343 100644 --- a/core/base/SkillStoreManager.py +++ b/core/base/SkillStoreManager.py @@ -37,8 +37,7 @@ def onQuarterHour(self): @Online(catchOnly=True) def refreshStoreData(self): - updateChannel = self.ConfigManager.getAliceConfigByName('skillsUpdateChannel') - req = requests.get(url=f'https://skills.projectalice.io/assets/store/{updateChannel}.json') + req = requests.get(url=constants.SKILLS_STORE_ASSETS) if req.status_code not in {200, 304}: return @@ -47,7 +46,7 @@ def refreshStoreData(self): if not self.ConfigManager.getAliceConfigByName('suggestSkillsToInstall'): return - req = requests.get(url=f'https://skills.projectalice.io/assets/store/{updateChannel}.samples') + req = requests.get(url=constants.SKILLS_SAMPLES_STORE_ASSETS) if req.status_code not in {200, 304}: return @@ -63,6 +62,15 @@ def prepareSamplesData(self, data: dict): def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]: + """ + Get the highest skill version number a user can install. + This is based on the user preferences, dependending on the current Alice version + and the user's selected update channel for skills + In case nothing is found, DO NOT FALLBACK TO MASTER + + :param skillName: The skill to look for + :return: tuple + """ versionMapping = self._skillStoreData.get(skillName, dict()).get('versionMapping', dict()) if not versionMapping: diff --git a/core/commons/constants.py b/core/commons/constants.py index 059379a69..cb3bc1c02 100644 --- a/core/commons/constants.py +++ b/core/commons/constants.py @@ -15,6 +15,8 @@ GITHUB_RAW_URL = 'https://raw.githubusercontent.com/project-alice-assistant' GITHUB_API_URL = 'https://api.github.com/repos/project-alice-assistant' SKILL_REDIRECT_URL = 'https://skills.projectalice.ch' +SKILLS_STORE_ASSETS = 'https://skills.projectalice.io/assets/store/skills.json' +SKILLS_SAMPLES_STORE_ASSETS = 'https://skills.projectalice.io/assets/store/skills.samples' GITHUB_REPOSITORY_ID = 193512918 JSON_EXT = '.json' From 53e8da7d02061abc698e37f8d373c7506de0d99d Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 22 Oct 2020 13:06:34 +0200 Subject: [PATCH 054/126] :bug: This fixes quadruple skill logs and update failure --- core/base/model/GithubCloner.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/base/model/GithubCloner.py b/core/base/model/GithubCloner.py index 4c4221b06..57ea027be 100644 --- a/core/base/model/GithubCloner.py +++ b/core/base/model/GithubCloner.py @@ -1,6 +1,5 @@ -from pathlib import Path - import shutil +from pathlib import Path from core.base.SuperManager import SuperManager from core.base.model.ProjectAliceObject import ProjectAliceObject @@ -43,8 +42,10 @@ def _doClone(self, skillName: str) -> bool: if not Path(self._dest / '.git').exists(): self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'init']) self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'remote', 'add', 'origin', self._baseUrl]) + self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull', 'origin', 'master']) + else: + self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull']) - self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull', 'origin', 'master']) self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'checkout', self.SkillStoreManager.getSkillUpdateTag(skillName)]) return True From d92a93578d7d3e87b06ef5176a5133248bbf2690 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 22 Oct 2020 13:31:43 +0200 Subject: [PATCH 055/126] :bug: his should avoid version leakage --- core/base/model/GithubCloner.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/base/model/GithubCloner.py b/core/base/model/GithubCloner.py index 57ea027be..405d850ca 100644 --- a/core/base/model/GithubCloner.py +++ b/core/base/model/GithubCloner.py @@ -39,14 +39,14 @@ def clone(self, skillName: str) -> bool: def _doClone(self, skillName: str) -> bool: try: + updateTag = self.SkillStoreManager.getSkillUpdateTag(skillName) if not Path(self._dest / '.git').exists(): self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'init']) self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'remote', 'add', 'origin', self._baseUrl]) - self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull', 'origin', 'master']) - else: self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull']) - self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'checkout', self.SkillStoreManager.getSkillUpdateTag(skillName)]) + self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'checkout', updateTag]) + self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull', 'origin', updateTag]) return True except Exception as e: From 4e4bf99e55449395cdacc563a566ee6977567a01 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 22 Oct 2020 13:48:29 +0200 Subject: [PATCH 056/126] :art: don't check cache more than once --- core/base/AssistantManager.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/base/AssistantManager.py b/core/base/AssistantManager.py index 7344ab2cf..b6e37d4d0 100644 --- a/core/base/AssistantManager.py +++ b/core/base/AssistantManager.py @@ -31,7 +31,14 @@ def clearAssistant(self): def checkAssistant(self, forceRetrain: bool = False): self.logInfo('Checking assistant') - if not self.checkConsistency() or forceRetrain: + + if forceRetrain: + self.logInfo('Forced assistant training') + self.train() + elif not self._assistantPath.exists(): + self.logInfo('Assistant not found') + self.train() + elif not self.checkConsistency(): self.logInfo('Assistant is not consistent, it needs training') self.train() @@ -43,9 +50,6 @@ def checkAssistant(self, forceRetrain: bool = False): def checkConsistency(self) -> bool: - if not self._assistantPath.exists() or not self.DialogTemplateManager.checkData(): - return False - existingIntents: Dict[str, dict] = dict() existingSlots: Dict[str, set] = dict() From 832144fea440b958b924af22e64f680308ccddbc Mon Sep 17 00:00:00 2001 From: LazzaAU Date: Fri, 23 Oct 2020 09:33:06 +1000 Subject: [PATCH 057/126] added Samples and instructions --- .../Validate skills.run.xml | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/jetbrainsDebuggers.run/Validate skills.run.xml b/jetbrainsDebuggers.run/Validate skills.run.xml index 8c9598274..66008f130 100644 --- a/jetbrainsDebuggers.run/Validate skills.run.xml +++ b/jetbrainsDebuggers.run/Validate skills.run.xml @@ -1,26 +1,24 @@ - - - - + + + \ No newline at end of file From e9b137e80cd8b2e059df640bf73f2d49c3fafd50 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 23 Oct 2020 16:53:18 +0200 Subject: [PATCH 058/126] :art: everything needs to be tried --- core/Initializer.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/Initializer.py b/core/Initializer.py index d4bd07f24..ce96a31d4 100644 --- a/core/Initializer.py +++ b/core/Initializer.py @@ -141,13 +141,14 @@ def loadConfig(self) -> dict: # noinspection PyUnboundLocalVariable load = yaml.safe_load(f) initConfs = InitDict(load) + + # Check that we are running using the latest yaml + if float(initConfs['version']) < VERSION: + self._logger.logFatal('The yaml file you are using is deprecated. Please update it before trying again') + except yaml.YAMLError as e: self._logger.logFatal(f'Failed loading init configurations: {e}') - # Check that we are running using the latest yaml - if float(initConfs['version']) < VERSION: - self._logger.logFatal('The yaml file you are using is deprecated. Please update it before trying again') - return initConfs From 0f58ad30d30cc21e66d117f62daa4f4f1ac2205f Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 24 Oct 2020 05:59:57 +0200 Subject: [PATCH 059/126] Internet check down to 2 seconds --- core/asr/model/GoogleAsr.py | 17 +++++++++++++++++ core/util/InternetManager.py | 13 ++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/core/asr/model/GoogleAsr.py b/core/asr/model/GoogleAsr.py index 69b3ab527..4a9abc9d3 100644 --- a/core/asr/model/GoogleAsr.py +++ b/core/asr/model/GoogleAsr.py @@ -1,5 +1,6 @@ import os from pathlib import Path +from threading import Event from typing import Generator, Optional from core.asr.model.ASRResult import ASRResult @@ -34,6 +35,8 @@ def __init__(self): self._client: Optional[SpeechClient] = None self._streamingConfig: Optional[types.StreamingRecognitionConfig] = None + self._internetLostFlag = Event() + self._previousCapture = '' @@ -68,6 +71,7 @@ def decodeStream(self, session: DialogSession) -> Optional[ASRResult]: responses = self._client.streaming_recognize(self._streamingConfig, requests) result = self._checkResponses(session, responses) except: + self._internetLostFlag.clear() self.logWarning('Failed ASR request') self.end() @@ -80,11 +84,24 @@ def decodeStream(self, session: DialogSession) -> Optional[ASRResult]: ) if result else None + def onInternetLost(self): + self._internetLostFlag.set() + + def _checkResponses(self, session: DialogSession, responses: Generator) -> Optional[tuple]: if responses is None: return None for response in responses: + if self._internetLostFlag.is_set(): + self.logDebug('Internet connectivity lost during ASR decoding') + + if not response.results: + raise Exception('Internet connectivity lost during decoding') + + result = response.results[0] + return result.alternatives[0].transcript, result.alternatives[0].confidence + if not response.results: continue diff --git a/core/util/InternetManager.py b/core/util/InternetManager.py index 78a4bf983..f100d0637 100644 --- a/core/util/InternetManager.py +++ b/core/util/InternetManager.py @@ -10,12 +10,14 @@ class InternetManager(Manager): def __init__(self): super().__init__() self._online = False + self._checkThread = None def onStart(self): super().onStart() if not self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'): self.checkOnlineState(silent=True) + self._checkThread = self.ThreadManager.newThread(name='internetCheckThread', target=self.checkInternet) else: self.logInfo('Configurations set to stay completly offline') @@ -29,9 +31,14 @@ def onBooted(self): self.checkOnlineState() - def onFullMinute(self): - if not self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'): - self.checkOnlineState() + # def onFullMinute(self): + # if not self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'): + # self.checkOnlineState() + + + def checkInternet(self): + self.checkOnlineState() + self.ThreadManager.doLater(interval=2, func=self.checkInternet) def checkOnlineState(self, addr: str = 'https://clients3.google.com/generate_204', silent: bool = False) -> bool: From a38b4798ac11a2dfce2cb002c61e79df325b1f99 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Sat, 24 Oct 2020 07:35:27 +0200 Subject: [PATCH 060/126] :sparkles: setting for internet quality --- configTemplate.json | 15 +++++++++++++++ core/util/Decorators.py | 9 ++++++--- core/util/InternetManager.py | 17 ++++++++--------- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index b45273789..20ad0bdb3 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -757,5 +757,20 @@ "condition": "is", "value" : false } + }, + "internetQuality" : { + "defaultValue": 10, + "dataType" : "range", + "min" : 1, + "max" : 10, + "step" : 1, + "isSensitive" : false, + "description" : "How would you rate your internet connection QUALITY? 0 = drops all the time, 10 = Very stable", + "category" : "system", + "parent" : { + "config" : "stayCompletlyOffline", + "condition": "is", + "value" : false + } } } diff --git a/core/util/Decorators.py b/core/util/Decorators.py index 6a6120b0c..6832cf313 100644 --- a/core/util/Decorators.py +++ b/core/util/Decorators.py @@ -2,7 +2,7 @@ import functools import warnings -from typing import Any, Callable, Tuple, Union +from typing import Any, Callable, Optional, Tuple, Union from flask import jsonify, request @@ -172,7 +172,7 @@ def wrapper(*args, **kwargs): return wrapper -def IfSetting(func: Callable = None, settingName: str = None, settingValue: Any = None, inverted: bool = False, skillName: str = None): #NOSONAR +def IfSetting(func: Callable = None, settingName: str = None, settingValue: Any = None, inverted: bool = False, skillName: str = None, returnValue: Optional[Any] = None): #NOSONAR """ Checks wheter a setting is equal to the given value before executing the wrapped method If the setting is not equal to the given value, the wrapped method is not called @@ -183,6 +183,7 @@ def IfSetting(func: Callable = None, settingName: str = None, settingValue: Any :param settingValue: :param inverted: :param skillName: + :param returnValue: The value to return if the setting check fails :return: """ @@ -199,11 +200,13 @@ def settingDecorator(*args, **kwargs): value = configManager.getSkillConfigByName(skillName, settingName) if skillName else configManager.getAliceConfigByName(settingName) if value is None: - return None + return returnValue if (not inverted and value == settingValue) or \ (inverted and value != settingValue): return func(*args, **kwargs) + else: + return returnValue return settingDecorator diff --git a/core/util/InternetManager.py b/core/util/InternetManager.py index f100d0637..a79fe2ea6 100644 --- a/core/util/InternetManager.py +++ b/core/util/InternetManager.py @@ -3,6 +3,7 @@ from core.base.model.Manager import Manager from core.commons import constants +from core.util.Decorators import IfSetting class InternetManager(Manager): @@ -11,12 +12,17 @@ def __init__(self): super().__init__() self._online = False self._checkThread = None + self._checkFrequency = 2 def onStart(self): super().onStart() if not self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'): self.checkOnlineState(silent=True) + # 20 seconds is the max, 2 seconds the minimum + # We have 10 positions in the config (from 1 to 10) So the frequency = max / 10 * setting = 2 * setting + internetQuality = self.ConfigManager.getAliceConfigByName('internetQuality') or 1 + self._checkFrequency = internetQuality * 2 self._checkThread = self.ThreadManager.newThread(name='internetCheckThread', target=self.checkInternet) else: self.logInfo('Configurations set to stay completly offline') @@ -31,20 +37,13 @@ def onBooted(self): self.checkOnlineState() - # def onFullMinute(self): - # if not self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'): - # self.checkOnlineState() - - def checkInternet(self): self.checkOnlineState() - self.ThreadManager.doLater(interval=2, func=self.checkInternet) + self.ThreadManager.doLater(interval=self._checkFrequency, func=self.checkInternet) + @IfSetting(settingName='stayCompletlyOffline', settingValue=False, returnValue=False) def checkOnlineState(self, addr: str = 'https://clients3.google.com/generate_204', silent: bool = False) -> bool: - if self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'): - return False - try: online = requests.get(addr).status_code == 204 except RequestException: From a64de6bb103dd77e0622785ae8d59f155bfff1c1 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Sat, 24 Oct 2020 12:06:17 +0200 Subject: [PATCH 061/126] :construction: Cut google ASR recording if intermediary result stays the same for over 3 seconds --- core/asr/model/GoogleAsr.py | 20 ++++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/core/asr/model/GoogleAsr.py b/core/asr/model/GoogleAsr.py index 4a9abc9d3..57126beb8 100644 --- a/core/asr/model/GoogleAsr.py +++ b/core/asr/model/GoogleAsr.py @@ -1,6 +1,7 @@ import os from pathlib import Path from threading import Event +from time import time from typing import Generator, Optional from core.asr.model.ASRResult import ASRResult @@ -10,6 +11,7 @@ from core.util.Stopwatch import Stopwatch try: + # noinspection PyUnresolvedReferences,PyPackageRequirements from google.cloud.speech import SpeechClient, enums, types except: pass # Auto installed @@ -35,9 +37,10 @@ def __init__(self): self._client: Optional[SpeechClient] = None self._streamingConfig: Optional[types.StreamingRecognitionConfig] = None - self._internetLostFlag = Event() + self._internetLostFlag = Event() # Set if internet goes down, cut the decoding + self._lastResultCheck = 0 # The time the intermediate results were last checked. If actual time is greater than this value + 3, stop processing, internet issues - self._previousCapture = '' + self._previousCapture = '' # The text that was last captured in the iteration def onStart(self): @@ -114,5 +117,18 @@ def _checkResponses(self, session: DialogSession, responses: Generator) -> Optio elif result.alternatives[0].transcript != self._previousCapture: self.partialTextCaptured(session=session, text=result.alternatives[0].transcript, likelihood=result.alternatives[0].confidence, seconds=0) self._previousCapture = result.alternatives[0].transcript + elif result.alternatives[0].transcript == self._previousCapture: + now = int(time()) + + if self._lastResultCheck == 0: + self._lastResultCheck = 0 + continue + + if now > self._lastResultCheck + 3: + self.logDebug(f'Stopping process as there seems to be connectivity issues') + return result.alternatives[0].transcript, result.alternatives[0].confidence + + self._lastResultCheck = now + continue return None From fdfb7e07eec0a8f9039360c65a8b32eae7fceb30 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Sat, 24 Oct 2020 12:12:29 +0200 Subject: [PATCH 062/126] :construction: Fix for tests --- core/util/InternetManager.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/core/util/InternetManager.py b/core/util/InternetManager.py index a79fe2ea6..4dfeac28a 100644 --- a/core/util/InternetManager.py +++ b/core/util/InternetManager.py @@ -3,7 +3,6 @@ from core.base.model.Manager import Manager from core.commons import constants -from core.util.Decorators import IfSetting class InternetManager(Manager): @@ -42,8 +41,10 @@ def checkInternet(self): self.ThreadManager.doLater(interval=self._checkFrequency, func=self.checkInternet) - @IfSetting(settingName='stayCompletlyOffline', settingValue=False, returnValue=False) def checkOnlineState(self, addr: str = 'https://clients3.google.com/generate_204', silent: bool = False) -> bool: + if self.ConfigManager.getAliceConfigByName('stayCompletlyOffline'): + return False + try: online = requests.get(addr).status_code == 204 except RequestException: From 03c5d6f582c5096061e3377136544e9bbb3ad9e4 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sun, 25 Oct 2020 06:21:26 +0100 Subject: [PATCH 063/126] :bug: fix dev pannel visual --- core/base/SkillManager.py | 4 ++-- core/interface/templates/devmode.html | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index 3d3a25b6a..686b7580c 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -994,8 +994,8 @@ def createNewSkill(self, skillDefinition: dict) -> bool: 'createInstructions': skillDefinition['instructions'], 'pipreq' : [req.strip() for req in skillDefinition['pipreq'].split(',')], 'sysreq' : [req.strip() for req in skillDefinition['sysreq'].split(',')], - 'widgets' : [self.Commons.toPascalCase(widget) for widget in skillDefinition['widgets'].split(',')], - 'scenarioNodes' : [self.Commons.toPascalCase(node) for node in skillDefinition['nodes'].split(',')], + 'widgets' : [self.Commons.toPascalCase(widget).strip() for widget in skillDefinition['widgets'].split(',')], + 'scenarioNodes' : [self.Commons.toPascalCase(node).strip() for node in skillDefinition['nodes'].split(',')], 'outputDestination' : str(Path(self.Commons.rootDir()) / 'skills' / skillName), 'conditions' : conditions } diff --git a/core/interface/templates/devmode.html b/core/interface/templates/devmode.html index ee4367d9f..ec36d2bc8 100644 --- a/core/interface/templates/devmode.html +++ b/core/interface/templates/devmode.html @@ -76,7 +76,7 @@
- +
From db513b887e4a1954daa1cd4e77ee6d832f8c035f Mon Sep 17 00:00:00 2001 From: Philipp <41761223+philipp2310@users.noreply.github.com> Date: Sun, 25 Oct 2020 20:49:49 +0100 Subject: [PATCH 064/126] Delete Links on Location Delete --- core/device/LocationManager.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/device/LocationManager.py b/core/device/LocationManager.py index 843923806..fb157952b 100644 --- a/core/device/LocationManager.py +++ b/core/device/LocationManager.py @@ -63,6 +63,10 @@ def deleteLocation(self, locId: int) -> bool: callerName=self.name, values={'id': locId}) self._locations.pop(locId, None) + + self.DatabaseManager.delete(tableName=self.DeviceManager.DB_LINKS, + callerName=self.DeviceManager.name, + values={'locationID': locId}) return True From e702e289a5f1a1ae270b25756eb6047bbfb1c04a Mon Sep 17 00:00:00 2001 From: Philipp <41761223+philipp2310@users.noreply.github.com> Date: Sun, 25 Oct 2020 21:53:06 +0100 Subject: [PATCH 065/126] maybe return the value when using get ;) --- core/Initializer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/Initializer.py b/core/Initializer.py index ce96a31d4..1cb0c9d77 100644 --- a/core/Initializer.py +++ b/core/Initializer.py @@ -29,6 +29,7 @@ def __getitem__(self, item): item = super().__getitem__(item) if not item: raise Exception + return item except: print(f'Missing key **{item}** in provided yaml file.') return '' @@ -141,7 +142,6 @@ def loadConfig(self) -> dict: # noinspection PyUnboundLocalVariable load = yaml.safe_load(f) initConfs = InitDict(load) - # Check that we are running using the latest yaml if float(initConfs['version']) < VERSION: self._logger.logFatal('The yaml file you are using is deprecated. Please update it before trying again') From e67dbe16d3d15b318d229414e7acd5d40d799e96 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 26 Oct 2020 06:34:19 +0100 Subject: [PATCH 066/126] :construction: Reworking broken z-indexing --- core/interface/static/js/admin.js | 18 +-- core/interface/static/js/adminAuth.js | 10 +- core/interface/static/js/alicewatch.js | 8 +- core/interface/static/js/common.js | 22 ++- core/interface/static/js/devmode.js | 18 +-- core/interface/static/js/myHome.js | 198 ++++++++++++------------- core/interface/static/js/skills.js | 52 +++---- core/interface/static/js/syslog.js | 6 +- core/interface/static/js/widgets.js | 32 ++-- core/interface/templates/home.html | 43 +++--- 10 files changed, 203 insertions(+), 204 deletions(-) diff --git a/core/interface/static/js/admin.js b/core/interface/static/js/admin.js index 21019bc88..be43d7810 100644 --- a/core/interface/static/js/admin.js +++ b/core/interface/static/js/admin.js @@ -37,7 +37,7 @@ $(function () { let $icon = $div.children('.utilityIcon').children('i'); $icon.addClass('fa-spin red'); $.ajax({ - url: '/admin/' + endpoint + '/', + url : '/admin/' + endpoint + '/', type: 'POST' }); setTimeout(function () { @@ -45,42 +45,42 @@ $(function () { }, timeout); } - $('#restart').on('click touchstart', function () { + $('#restart').on('click touch', function () { handleUtilityClick($(this), 'restart', 5000); return false; }); - $('#reboot').on('click touchstart', function () { + $('#reboot').on('click touch', function () { handleUtilityClick($(this), 'reboot', 10000); return false; }); - $('#trainAssistant').on('click touchstart', function () { + $('#trainAssistant').on('click touch', function () { handleUtilityClick($(this), 'trainAssistant', 5000); return false; }); - $('#wipeAll').on('click touchstart', function () { + $('#wipeAll').on('click touch', function () { handleUtilityClick($(this), 'wipeAll', 5000); return false; }); - $('#update').on('click touchstart', function () { + $('#update').on('click touch', function () { handleUtilityClick($(this), 'updatee', 5000); return false; }); - $('#addUser').on('click touchstart', function () { + $('#addUser').on('click touch', function () { handleUtilityClick($(this), 'addUser', 1000); return false; }); - $('#addWakeword').on('click touchstart', function () { + $('#addWakeword').on('click touch', function () { handleUtilityClick($(this), 'addWakeword', 1000); return false; }); - $('#tuneWakeword').on('click touchstart', function () { + $('#tuneWakeword').on('click touch', function () { handleUtilityClick($(this), 'tuneWakeword', 1000); return false; }); diff --git a/core/interface/static/js/adminAuth.js b/core/interface/static/js/adminAuth.js index 9429c19bb..7d6951ad2 100644 --- a/core/interface/static/js/adminAuth.js +++ b/core/interface/static/js/adminAuth.js @@ -23,7 +23,7 @@ $(function () { $('#username').on('keyup', function () { $.ajax({ - url: '/adminAuth/login/', + url : '/adminAuth/login/', data: { 'username': $(this).val() }, @@ -31,7 +31,7 @@ $(function () { }); }); - $('.adminAuthKeyboardKey').not('.erase').not('.backspace').on('click touchstart', function () { + $('.adminAuthKeyboardKey').not('.erase').not('.backspace').on('click touch', function () { if (!keyboardAuthNotified) { $.post('/adminAuth/keyboardAuth/'); keyboardAuthNotified = true; @@ -64,16 +64,16 @@ $(function () { return true; }); - $('.erase').on('click touchstart', function () { + $('.erase').on('click touch', function () { code = ''; $('#codeContainer').children('.adminAuthDisplayDigit').each(function () { $(this).removeClass('adminAuthDigitFilled'); }); }); - $('.backspace').on('click touchstart', function () { + $('.backspace').on('click touch', function () { code = code.slice(0, -1); - $('#codeContainer').children('.adminAuthDigitFilled').last().removeClass('adminAuthDigitFilled') + $('#codeContainer').children('.adminAuthDigitFilled').last().removeClass('adminAuthDigitFilled'); }); $('#adminAuthKeyboardContainer').hide(); diff --git a/core/interface/static/js/alicewatch.js b/core/interface/static/js/alicewatch.js index dd505c388..78c4b33c3 100644 --- a/core/interface/static/js/alicewatch.js +++ b/core/interface/static/js/alicewatch.js @@ -55,23 +55,23 @@ $(function () { '[' + time + '] [AliceWatch]Watching on ' + MQTT_HOST + ':' + MQTT_PORT + ' (MQTT)' ); - MQTT.subscribe('projectalice/logging/alicewatch') + MQTT.subscribe('projectalice/logging/alicewatch'); } - $stopScroll.on('click touchstart', function () { + $stopScroll.on('click touch', function () { $(this).hide(); $startScroll.show(); return false; }); - $startScroll.on('click touchstart', function () { + $startScroll.on('click touch', function () { $(this).hide(); $stopScroll.show(); return false; }); let $thermometers = $('[class^="fas fa-thermometer"]'); - $thermometers.on('click touchstart', function () { + $thermometers.on('click touch', function () { $('[class^="fas fa-thermometer"]').removeClass('active'); $(this).addClass('active'); let level = $(this).data('verbosity'); diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 2818a7283..c1f45fdf6 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -36,7 +36,7 @@ function mqttRegisterSelf(target, method) { function initIndexers($element) { let indexer = $element.children('.zindexer'); - indexer.children('.zindexer-up').on('click touchscreen', function () { + indexer.children('.zindexer-up').on('click touch', function () { let $parent = $(this).parent().parent(); let actualIndex = $element.css('z-index'); if (actualIndex == null || actualIndex == 'auto') { @@ -46,7 +46,7 @@ function initIndexers($element) { } let baseClass = $parent.attr('class').split(/\s+/)[0]; - $('.' + baseClass).each(function() { + $('.' + baseClass).each(function () { let thisIndex = $(this).css('z-index'); if (thisIndex != null && thisIndex != 'auto' && parseInt(thisIndex) == actualIndex + 1) { $(this).css('z-index', actualIndex); @@ -56,7 +56,7 @@ function initIndexers($element) { }); }); - indexer.children('.zindexer-down').on('click touchscreen', function () { + indexer.children('.zindexer-down').on('click touch', function () { let $parent = $(this).parent().parent(); let actualIndex = $element.css('z-index'); if (actualIndex == null || actualIndex == 'auto' || parseInt(actualIndex) <= 0) { @@ -66,7 +66,7 @@ function initIndexers($element) { } let baseClass = $parent.attr('class').split(/\s+/)[0]; - $('.' + baseClass).each(function() { + $('.' + baseClass).each(function () { let thisIndex = $(this).css('z-index'); if (thisIndex != null && thisIndex != 'auto' && parseInt(thisIndex) == actualIndex -1) { $(this).css('z-index', actualIndex); @@ -237,13 +237,12 @@ $(function () { $('.tabsContent').children().each(function () { if ($(this).attr('id') == $defaultTab.data('for')) { $(this).show(); - } - else { + } else { $(this).hide(); } }); - $('.tab').on('click touchstart', function () { + $('.tab').on('click touch', function () { let target = $(this).data('for'); $(this).addClass('activeTab'); @@ -256,25 +255,24 @@ $(function () { $('.tabsContent').children().each(function () { if ($(this).attr('id') == target) { $(this).show(); - } - else { + } else { $(this).hide(); } }); return false; }); - $('.overlayInfoClose').on('click touchstart', function () { + $('.overlayInfoClose').on('click touch', function () { $(this).parent().hide(); }); - $('#refuseAliceConfUpdate').on('click touchstart', function() { + $('#refuseAliceConfUpdate').on('click touch', function () { $.post('/admin/refuseAliceConfigUpdate/').done(function () { $('#coreConfigUpdateAlert').hide(); }); }); - $('#acceptAliceConfUpdate').on('click touchstart', function() { + $('#acceptAliceConfUpdate').on('click touch', function () { $.post('/admin/acceptAliceConfigUpdate/').done(function () { $('#coreConfigUpdateAlert').hide(); }); diff --git a/core/interface/static/js/devmode.js b/core/interface/static/js/devmode.js index 16dba8fe7..ffcd30daf 100644 --- a/core/interface/static/js/devmode.js +++ b/core/interface/static/js/devmode.js @@ -37,7 +37,7 @@ $(function () { } }); - $('.tab').on('click touchstart', function () { + $('.tab').on('click touch', function () { let target = $(this).data('for'); $(this).addClass('activeTab'); @@ -104,9 +104,9 @@ $(function () { toggleCreateButton(); }); - $createSkillButton.on('click touchstart', function () { + $createSkillButton.on('click touch', function () { $.ajax({ - url: '/devmode/' + $skillName.val() + '/', + url : '/devmode/' + $skillName.val() + '/', type: 'PUT', data: { 'speakableName' : $('#speakableName').val(), @@ -121,7 +121,7 @@ $(function () { 'conditionSkill' : $('#conditionSkill').val(), 'conditionNotSkill' : $('#conditionNotSkill').val(), 'conditionActiveManager': $('#conditionActiveManager').val(), - 'widgets': $('#widgets').val() + 'widgets' : $('#widgets').val() } }).done(function () { $('#newSkillForm :input').prop('disabled', true); @@ -131,9 +131,9 @@ $(function () { }); }); - $uploadSkillButton.on('click touchstart', function () { + $uploadSkillButton.on('click touch', function () { $.ajax({ - url: '/devmode/uploadToGithub/', + url : '/devmode/uploadToGithub/', type: 'POST', data: { 'skillName': $('#skillName').val(), @@ -147,11 +147,11 @@ $(function () { }); }); - $('#resetSkillButton').on('click touchstart', function () { + $('#resetSkillButton').on('click touch', function () { resetSkillPage(); }); - $goGithubButton.on('click touchstart', function () { + $goGithubButton.on('click touch', function () { window.open($(this).text()); }); @@ -161,7 +161,7 @@ $(function () { } }); - $('[id*=editSkill_]').on('click touchstart', function () { + $('[id*=editSkill_]').on('click touch', function () { window.location.href = '/devmode/editskill/' + $(this).data('skill'); }); }); diff --git a/core/interface/static/js/myHome.js b/core/interface/static/js/myHome.js index 7e55f8cfe..fda6f1605 100644 --- a/core/interface/static/js/myHome.js +++ b/core/interface/static/js/myHome.js @@ -191,7 +191,7 @@ $(function () { return result; } - $('#backupMyHome').on('click touchstart', function () { + $('#backupMyHome').on('click touch', function () { let data = saveHouse(); let file = new Blob([data], {type: 'application/json'}); let url = window.URL.createObjectURL(file); @@ -201,7 +201,7 @@ $(function () { document.getElementById('downloadBackup').click(); window.URL.revokeObjectURL(url); $link.remove(); - }) + }); // Basic functionality for build area function matrixToAngle(matrix) { @@ -377,23 +377,23 @@ $(function () { 'style="left: ' + data['display']['x'] + 'px; top: ' + data['display']['y'] + 'px; width: ' + data['display']['width'] + 'px; height: ' + data['display']['height'] + 'px; position: absolute; transform: rotate(' + data['display']['rotation'] + 'deg); z-index: ' + data['display']['z-index'] + '">' + '
' + data['name'] + '
' + '
' + - '
' + - '
' + + '
' + + '
' + '
' + '
'); initIndexers($newZone); - $newZone.on('click touchstart', function () { + $newZone.on('click touch', function () { if (buildingMode) { if (selectedConstruction == null || selectedConstruction == '') { let wallData = { - 'x' : 50, - 'y' : 50, - 'width' : 25, - 'height': 75, + 'x' : 50, + 'y' : 50, + 'width' : 25, + 'height' : 75, 'rotation': 0 - } + }; let wall = newWall($newZone, wallData); makeResizableRotatableAndDraggable(wall); } @@ -478,23 +478,23 @@ $(function () { content += data['name']; content += ""; content += "
"; - content += "
"; - content += "
"; - content += "
Synonyms:
"; - content += "
"; + content += '
'; + content += '
'; + content += '
Synonyms:
'; + content += '
'; // content += "
Devices:
"; // content += "
Linked Devices:
"; - content += "
"; + content += '
'; $sideBar.html(content); - $('#renameLocation').on('click touchstart', function () { - let targetName = $(this).parent().children('#content') + $('#renameLocation').on('click touch', function () { + let targetName = $(this).parent().children('#content'); let newLocName = prompt($('#langRenameLocation').text(), data['name']); - if(newLocName === null){ + if (newLocName === null) { return; } - $.post('/myhome/Location/'+data['id']+'/saveCoreSettings', {name : newLocName}).done(function(ret) { + $.post('/myhome/Location/' + data['id'] + '/saveCoreSettings', {name: newLocName}).done(function (ret) { if (handleError(ret)) { return; } @@ -509,25 +509,25 @@ $(function () { $sideBar.sidebar({side: 'right'}).trigger('sidebar:open'); // reroute enter to click event - $('.configInput').on('keydown',function (e) { - if (e.key == 'Enter') { - $(this).parent().children('.configListAdd').trigger('click'); - return false; - } + $('.configInput').on('keydown', function (e) { + if (e.key == 'Enter') { + $(this).parent().children('.configListAdd').trigger('click'); + return false; + } }); // add new entry to conf. List - $('.configListAdd').on('click touchstart',function() { + $('.configListAdd').on('click touch', function () { let $parent = $(this).parent(); let $inp = $parent.children('.configInput'); $parent = $parent.parent(); if ($inp.val() != '') { - $.post( '/myhome/'+$parent[0].id, - { value: $inp.val() } ) - .done(function( result ) { - if(handleError(result)){ - return; - } + $.post('/myhome/' + $parent[0].id, + {value: $inp.val()}) + .done(function (result) { + if (handleError(result)) { + return; + } newConfigListVal($parent,$inp.val(),$parent.data('dellink') ); $inp.val(''); }); @@ -568,13 +568,13 @@ $(function () { } function newWall($element, data) { - data = snapPosition(data) + data = snapPosition(data); data = snapAngle(data); let $newWall = $('
' + '
'); - $newWall.on('click touchstart', function () { + $newWall.on('click touch', function () { return false; }); @@ -590,7 +590,7 @@ $(function () { } function newConstruction($element, data) { - data = snapPosition(data) + data = snapPosition(data); data = snapAngle(data); // noinspection CssUnknownTarget let $newConstruction = $('
' + '
'); - $newConstruction.on('click touchstart', function () { + $newConstruction.on('click touch', function () { return false; }); @@ -614,7 +614,7 @@ $(function () { } function newDeco($element, data) { - data = snapPosition(data) + data = snapPosition(data); data = snapAngle(data); // noinspection CssUnknownTarget let $newDeco = $('
' + '
'); - $newDeco.on('click touchstart', function () { + $newDeco.on('click touch', function () { return false; }); @@ -638,35 +638,35 @@ $(function () { } function newDevice($element, data) { - data = snapPosition(data) + data = snapPosition(data); data = snapAngle(data); // noinspection CssUnknownTarget - let $newDevice = $('
' + + let $newDevice = $('
' + '
'); - $newDevice.on('click touchstart', function () { - if(deviceSettingsMode) { - let content = "
"; + $newDevice.on('click touch', function () { + if (deviceSettingsMode) { + let content = '
'; content += data['name']; - content += "
"; - content += "

" + data['deviceType'] + "

"; + content += '
'; + content += '

' + data['deviceType'] + '

'; if (data['uid'] == 'undefined' || data['uid'] == null) { - content += "NO DEVICE PAIRED!
Search Device
" + content += 'NO DEVICE PAIRED!
Search Device
'; } else { - content += "
" + data['uid'] + "
"; + content += '
' + data['uid'] + '
'; } $sideBar.html(content); - $('#startPair').on('click touchstart', function () { - $(this).addClass('waiting') + $('#startPair').on('click touch', function () { + $(this).addClass('waiting'); $.post('Device/' + data['id'] + '/pair').done(function (dataa) { if (handleError(dataa)) { return; } - let sp = $('#startPair') + let sp = $('#startPair'); sp.removeClass('waiting'); sp.hide(); }); @@ -696,9 +696,9 @@ $(function () { $sideBar.append(content); saveEnter(); - $('#SetForm').on('change', function() { + $('#SetForm').on('change', function () { makeDirty(); - }) + }); } selectedLinks = null; @@ -706,19 +706,19 @@ $(function () { }); - $('#renameDevice').on('click touchstart', function () { - let targetName = $(this).parent().children('#content') + $('#renameDevice').on('click touch', function () { + let targetName = $(this).parent().children('#content'); let newDevName = prompt($('#langRenameDevice').text(), data['name']); - if(newDevName === null){ + if (newDevName === null) { return; } - $.post('/myhome/Device/'+data['id']+'/saveCoreSettings', {name : newDevName}).done(function(ret) { + $.post('/myhome/Device/' + data['id'] + '/saveCoreSettings', {name: newDevName}).done(function (ret) { if (handleError(ret)) { return; } data['name'] = newDevName; targetName.html(newDevName); - let chTitle = $('#device_'+data['id']); + let chTitle = $('#device_' + data['id']); chTitle.prop('title', newDevName); }); }); @@ -726,7 +726,7 @@ $(function () { // add new synonym entry to conf. List //TODO add to DB // check if is existing - /* $('.configListAdd').on('click touchstart', function () { + /* $('.configListAdd').on('click touch', function () { let $parent = $(this).parent(); let $inp = $parent.children('.configInput'); if ($inp.val() != '') { @@ -736,7 +736,7 @@ $(function () { $parent.children('.configListCurrent').append("
  • " + $inp.val() + "
  • "); $inp.val(''); - $('.configListRemove').on('click touchstart', function () { + $('.configListRemove').on('click touch', function () { $(this).parent().remove(); }); }); @@ -814,18 +814,18 @@ $(function () { } function newConfigListVal($parent, val, deletionLink) { - $parent.find('.configListCurrent').append("
  • " + val + "
  • "); - $('.configListRemove').on('click touchstart', function () { + $parent.find('.configListCurrent').append('
  • ' + val + '
  • '); + $('.configListRemove').on('click touch', function () { $(this).parent().remove(); - $.post(deletionLink, { 'value': val }) + $.post(deletionLink, {'value': val}); }); } // handle toolbar // save, hide toolbars, restore live view //unused - $('#saveToolbarAction').on('click touchstart', function () { - if($(sZone).hasClass('blueprint')) { + $('#saveToolbarAction').on('click touch', function () { + if ($(sZone).hasClass('blueprint')) { setBPMode(false); saveHouse(); setBPMode(true); @@ -834,7 +834,7 @@ $(function () { } }); - $('#finishToolbarAction').on('click touchstart', function () { + $('#finishToolbarAction').on('click touch', function () { setBPMode(false); saveHouse(); initEditable(); @@ -849,14 +849,14 @@ $(function () { }); // enter edit mode - $('#toolbarToggleShow').on('click touchstart', function () { + $('#toolbarToggleShow').on('click touch', function () { $('#toolbarOverview').show(); $('#toolbarToggle').hide(); initEditable(); }); // enter construction/location mode - $('#toolbarConstructionShow').on('click touchstart', function () { + $('#toolbarConstructionShow').on('click touch', function () { initEditable(); setBPMode(false); locationEditMode = true; @@ -865,7 +865,7 @@ $(function () { }); // enter device editing mode - $('#toolbarTechnicShow').on('click touchstart', function () { + $('#toolbarTechnicShow').on('click touch', function () { initEditable(); setBPMode(true); deviceEditMode = true; @@ -873,13 +873,13 @@ $(function () { $('#toolbarTechnic').show(); }); - $('#toolbarOverviewShow').on('click touchstart', function () { + $('#toolbarOverviewShow').on('click touch', function () { $('#toolbarOverview').show(); $('#toolbarToggle').hide(); initEditable(); }); - $floorPlan.on('click touchstart', function (e) { + $floorPlan.on('click touch', function (e) { if (!zoneMode) { return; } @@ -888,8 +888,8 @@ $(function () { let x = $(this).offset().left; let y = $(this).offset().top; - $.post('/myhome/Location/0/add', {name : zoneName}).done(function(data){ - if( handleError(data) ) { + $.post('/myhome/Location/0/add', {name: zoneName}).done(function (data) { + if (handleError(data)) { return; } let zoneId = data['id']; @@ -961,12 +961,12 @@ $(function () { } // construction tools - $('#addZone').on('click touchstart', function () { - if(!zoneMode) { + $('#addZone').on('click touch', function () { + if (!zoneMode) { markSelectedTool($(this)); zoneMode = true; $floorPlan.addClass(classAddZone); - removeResizableRotatableAndDraggable($(sZone+", "+sDeco+", "+sWall+", "+sConstr)); + removeResizableRotatableAndDraggable($(sZone + ', ' + sDeco + ', ' + sWall + ', ' + sConstr)); } else { zoneMode = false; markSelectedTool(null); @@ -974,7 +974,7 @@ $(function () { } }); - $('#builder').on('click touchstart', function () { + $('#builder').on('click touch', function () { if (!buildingMode) { markSelectedTool($(this)); @@ -983,7 +983,7 @@ $(function () { $('#constructionTiles').css('display', 'flex'); makeDroppable($(sZone), true); makeResizableRotatableAndDraggable($(sWallConstr)); - removeResizableRotatableAndDraggable($(sZone+", "+sDeco)); + removeResizableRotatableAndDraggable($(sZone + ', ' + sDeco)); } else { removeResizableRotatableAndDraggable($(sWallConstr)); $(sZone).droppable('destroy'); @@ -993,14 +993,14 @@ $(function () { } }); - $('#painter').on('click touchstart', function () { + $('#painter').on('click touch', function () { if (!paintingMode) { markSelectedTool($(this)); paintingMode = true; $('#painterTiles').css('display', 'flex'); $floorPlan.removeClass(classAddZone); - removeResizableRotatableAndDraggable($(sZone+", "+sDeco+", "+sWall+", "+sConstr)); + removeResizableRotatableAndDraggable($(sZone + ', ' + sDeco + ', ' + sWall + ', ' + sConstr)); } else { paintingMode = false; markSelectedTool(null); @@ -1008,14 +1008,14 @@ $(function () { } }); - $('#locationMover').on('click touchstart', function () { + $('#locationMover').on('click touch', function () { if (!locationMoveMode) { markSelectedTool($(this)); locationMoveMode = true; $('.zindexer').show(); makeResizableRotatableAndDraggable($(sZone)); - removeResizableRotatableAndDraggable($(sDeco+", "+sWallConstr)); + removeResizableRotatableAndDraggable($(sDeco + ', ' + sWallConstr)); makeDroppable($floorPlan, false); @@ -1026,7 +1026,7 @@ $(function () { } }); - $('#decorator').on('click touchstart', function () { + $('#decorator').on('click touch', function () { if (!decoratorMode) { markSelectedTool($(this)); @@ -1037,7 +1037,7 @@ $(function () { makeDroppable($(sZone), true); makeResizableRotatableAndDraggable($(sDeco)); - removeResizableRotatableAndDraggable($(sZone+", "+sWallConstr)); + removeResizableRotatableAndDraggable($(sZone + ', ' + sWallConstr)); } else { $(sZone).droppable('destroy'); removeResizableRotatableAndDraggable($(sDeco)); @@ -1045,13 +1045,13 @@ $(function () { } }); - $('#locationSettings').on('click touchstart', function () { + $('#locationSettings').on('click touch', function () { if (!locationSettingsMode) { markSelectedTool($(this)); locationSettingsMode = true; $(sDeco).css('pointer-events', 'none'); - removeResizableRotatableAndDraggable($(sZone+", "+sDeco+", "+sWall+", "+sConstr)); + removeResizableRotatableAndDraggable($(sZone + ', ' + sDeco + ', ' + sWall + ', ' + sConstr)); } else { locationSettingsMode = false; markSelectedTool(null); @@ -1059,7 +1059,7 @@ $(function () { }); // technic tools - $('#deviceInstaller').on('click touchstart', function () { + $('#deviceInstaller').on('click touch', function () { if (!deviceInstallerMode) { markSelectedTool($(this)); @@ -1074,13 +1074,13 @@ $(function () { } }); - $('#deviceLinker').on('click touchstart', function () { + $('#deviceLinker').on('click touch', function () { - if(!deviceLinkerMode){ + if (!deviceLinkerMode) { markSelectedTool($(this)); deviceLinkerMode = true; removeResizableRotatableAndDraggable($(sDevice)); - }else{ + } else { deviceLinkerMode = false; markSelectedTool(null); setSelectedDevice(false); @@ -1088,7 +1088,7 @@ $(function () { }); - $('#deviceMover').on('click touchstart', function () { + $('#deviceMover').on('click touch', function () { if (!deviceMoveMode) { markSelectedTool($(this)); @@ -1097,8 +1097,8 @@ $(function () { makeResizableRotatableAndDraggable($(sDevice)); $(sZone).droppable({ - drop: function( event, ui ) { - let userConf = false; + drop: function (event, ui) { + let userConf = false; let roomChange = false; let errorOccured = false; // check if room didn't change @@ -1141,7 +1141,7 @@ $(function () { } setTimeout( function() { ui.draggable.draggable( 'option', 'revert', true ); }, 1000 ); } - } + } }); } else { @@ -1151,7 +1151,7 @@ $(function () { } }); - $('#deviceSettings').on('click touchstart', function () { + $('#deviceSettings').on('click touch', function () { if (!deviceSettingsMode) { markSelectedTool($(this)); @@ -1177,7 +1177,7 @@ $(function () { for (let i = 1; i <= 11; i++) { // noinspection CssUnknownTarget let $tile = $('
    '); - $tile.on('click touchstart', function () { + $tile.on('click touch', function () { if (!$(this).hasClass('selected')) { $(sTile).removeClass('selected'); $(this).addClass('selected'); @@ -1193,7 +1193,7 @@ $(function () { // load floor tiles for (let i = 1; i <= 80; i++) { let $tile = $('
    '); - $tile.on('click touchstart', function () { + $tile.on('click touch', function () { if (!$(this).hasClass('selected')) { $(sTile).removeClass('selected'); $(this).addClass('selected'); @@ -1211,7 +1211,7 @@ $(function () { for (let i = 1; i <= 167; i++) { // noinspection CssUnknownTarget let $tile = $('
    '); - $tile.on('click touchstart', function () { + $tile.on('click touch', function () { if (!$(this).hasClass('selected')) { $(sTile).removeClass('selected'); $(this).addClass('selected'); @@ -1228,7 +1228,7 @@ $(function () { $.each(dats, function(k, dat) { // noinspection CssUnknownTarget let $tile = $('
    '); - $tile.on('click touchstart', function () { + $tile.on('click touch', function () { if (!$(this).hasClass('selected')) { $(sTile).removeClass('selected'); $(this).addClass('selected'); diff --git a/core/interface/static/js/skills.js b/core/interface/static/js/skills.js index 8505f9fdc..ff727b0a4 100644 --- a/core/interface/static/js/skills.js +++ b/core/interface/static/js/skills.js @@ -71,7 +71,7 @@ $(function () { let $actions = $('
    '); let $button = $('
    '); - $button.on('click touchstart', function () { + $button.on('click touch', function () { $(this).hide(); $(this).parent().children('.skillStoreSkillDownloadButton').css('display', 'flex'); for (let i = 0; i < selectedSkillsToDownload.length; i++) { @@ -88,7 +88,7 @@ $(function () { $actions.append($button); $button = $('
    '); - $button.on('click touchstart', function () { + $button.on('click touch', function () { $(this).hide(); $(this).parent().children('.skillStoreSkillSelected').css('display', 'flex'); selectedSkillsToDownload.push({'skill': installer['name'], 'author': installer['author']}); @@ -111,17 +111,17 @@ $(function () { function loadStoreData() { $.ajax({ - url: '/skills/loadStoreData/', + url : '/skills/loadStoreData/', type: 'POST' - }).done(function (answer){ + }).done(function (answer) { $('#skillStoreWait').hide(); - $.each(answer, function(skillName, installer){ + $.each(answer, function (skillName, installer) { addToStore(installer); }); }); } - $applySkillStore.on('click touchstart', function () { + $applySkillStore.on('click touch', function () { $('.skillStoreSkillSelected').hide(); $(this).hide(); $.each(selectedSkillsToDownload, function (index, skill) { @@ -129,8 +129,8 @@ $(function () { }); $.ajax({ - url: '/skills/installSkills/', - data: JSON.stringify(selectedSkillsToDownload), + url : '/skills/installSkills/', + data : JSON.stringify(selectedSkillsToDownload), contentType: 'application/json', dataType: 'json', type: 'POST' @@ -145,9 +145,9 @@ $(function () { return false; }); - $('[id^=toggle_]').on('click touchstart', function () { + $('[id^=toggle_]').on('click touch', function () { $.ajax({ - url: '/skills/toggleSkill/', + url : '/skills/toggleSkill/', data: { id: $(this).attr('id') }, @@ -168,18 +168,18 @@ $(function () { }); $('[id^=instructions_for_]').dialog({ - autoOpen: false, + autoOpen : false, draggable: false, - width: '60%', - height: 600, - modal: true, + width : '60%', + height : 600, + modal : true, resizable: false }); - $('[id^=update_]').on('click touchstart', function () { + $('[id^=update_]').on('click touch', function () { let $self = $(this); $.ajax({ - url: '/skills/updateSkill/', + url : '/skills/updateSkill/', data: { id: $(this).attr('id') }, @@ -194,31 +194,31 @@ $(function () { return false; }); - $('.skillSettings').on('click touchstart', function () { + $('.skillSettings').on('click touch', function () { $('#config_for_' + $(this).attr('data-forSkill')).dialog('open'); return false; }); - $('.skillViewIntents').on('click touchstart', function () { + $('.skillViewIntents').on('click touch', function () { $(this).parent('.skillDefaultView').css('display', 'none'); $(this).parent().parent().children('.skillIntentsView').css('display', 'flex'); return false; }); - $('.skillIntentsViewCloseButton').on('click touchstart', function () { + $('.skillIntentsViewCloseButton').on('click touch', function () { $(this).parent().parent().children('.skillDefaultView').css('display', 'flex'); $(this).parent('.skillIntentsView').css('display', 'none'); return false; }); - $('.skillInstructions').on('click touchstart', function () { + $('.skillInstructions').on('click touch', function () { $('#instructions_for_' + $(this).attr('data-forSkill')).dialog('open'); return false; }); - $('[id^=delete_]').on('click touchstart', function () { + $('[id^=delete_]').on('click touch', function () { $.ajax({ - url: '/skills/deleteSkill/', + url : '/skills/deleteSkill/', data: { id: $(this).attr('id') }, @@ -229,9 +229,9 @@ $(function () { return false; }); - $('[id^=reload_]').on('click touchstart', function () { + $('[id^=reload_]').on('click touch', function () { $.ajax({ - url: '/skills/reloadSkill/', + url : '/skills/reloadSkill/', data: { id: $(this).attr('id') }, @@ -242,7 +242,7 @@ $(function () { return false; }); - $('#openSkillStore').on('click touchstart', function () { + $('#openSkillStore').on('click touch', function () { loadStoreData(); $('#skillsPane').hide(); $('#skillStore').css('display', 'flex'); @@ -251,7 +251,7 @@ $(function () { return false; }); - $('#closeSkillStore').on('click touchstart', function () { + $('#closeSkillStore').on('click touch', function () { location.reload(); return false; }); diff --git a/core/interface/static/js/syslog.js b/core/interface/static/js/syslog.js index 9b0abb84c..a8feb0ee9 100644 --- a/core/interface/static/js/syslog.js +++ b/core/interface/static/js/syslog.js @@ -23,13 +23,13 @@ $(function () { } } - $stopScroll.on('click touchstart', function () { + $stopScroll.on('click touch', function () { $(this).hide(); $startScroll.show(); return false; }); - $startScroll.on('click touchstart', function () { + $startScroll.on('click touch', function () { $(this).hide(); $stopScroll.show(); return false; @@ -39,7 +39,7 @@ $(function () { MQTT.subscribe('projectalice/logging/syslog'); } - for(let i = 0; i < logHistory.length; i++) { + for (let i = 0; i < logHistory.length; i++) { addToLogs(logHistory[i]); } diff --git a/core/interface/static/js/widgets.js b/core/interface/static/js/widgets.js index 380040b48..5b2cab997 100644 --- a/core/interface/static/js/widgets.js +++ b/core/interface/static/js/widgets.js @@ -18,17 +18,17 @@ $(function () { }); /* Toolbar Functions */ - $('#toolbarToggleShow').on('click touchstart', function () { + $('#toolbarToggleShow').on('click touch', function () { $('#toolbar_full').show(); $('#toolbar_toggle').hide(); let widget = $('.widget'); - widget.css('outline', "2px var(--accent) dotted"); + widget.css('outline', '2px var(--accent) dotted'); widget.draggable('enable'); widget.resizable('enable'); $('.zindexer').show(); }); - $('#toolbarToggleHide').on('click touchstart', function () { + $('#toolbarToggleHide').on('click touch', function () { $('#toolbar_full').hide(); $('#toolbar_toggle').show(); let widget = $('.widget'); @@ -46,7 +46,7 @@ $(function () { w : $(this).outerWidth(), h : $(this).outerHeight(), zindex: $(this).css('z-index') - } + }; }); $.ajax({ @@ -85,26 +85,26 @@ $(function () { }); - $('#removeWidget').on('click touchstart', function () { + $('#removeWidget').on('click touch', function () { $('.widgetDelete').show(); $('#toolbar_checkmark').show(); $('#toolbar_full').hide(); return false; }); - $('#addWidget').on('click touchstart', function () { + $('#addWidget').on('click touch', function () { $('#addWidgetDialog').dialog('open'); return false; }); - $('#configToggle').on('click touchstart', function () { + $('#configToggle').on('click touch', function () { $('.widgetConfig').show(); $('#toolbar_checkmark').show(); $('#toolbar_full').hide(); return false; }); - $('#cinemaToggle').on('click touchstart', function () { + $('#cinemaToggle').on('click touch', function () { $('nav').toggle(); $('#toolbar_full').hide(); $('header').toggle(); @@ -115,7 +115,7 @@ $(function () { $('.zindexer').hide(); }); - $('#widgetCheck').on('click touchstart', function () { + $('#widgetCheck').on('click touch', function () { $('#toolbar_checkmark').hide(); $('.widgetDelete').hide(); $('.widgetConfig').hide(); @@ -125,7 +125,7 @@ $(function () { /*=================== Functions for the single widgets ======================*/ /* Remove the selected widget */ - $('.widgetDelete').on('click touchstart', function () { + $('.widgetDelete').on('click touch', function () { if ($(this).parents('.widget').length > 0) { $.post('/home/removeWidget/', {id: $(this).parent().attr('id')}); $(this).parent().remove(); @@ -145,8 +145,8 @@ $(function () { } // build configuration - let newForm = "
    "; - newForm += ""; + let newForm = ''; + newForm += ''; jQuery.each(data, function (i, val) { let input = '
    '; if (i == 'background') { @@ -170,9 +170,9 @@ $(function () { input += '
    '; } - newForm += "
    " + input; + newForm += '
    ' + input; }); - newForm += "
    "; + newForm += '
    '; dialogContainer.find('#' + tab).html(newForm); // perform submit/save of the form without switching page @@ -208,7 +208,7 @@ $(function () { } /* Opening of widget specific settings */ - $('.widgetConfig').on('click touchstart', function () { + $('.widgetConfig').on('click touch', function () { if ($(this).parents('.widget').length > 0) { let parent = $(this).parent(); prepareConfigTab(parent, 'WidgetConfig'); @@ -217,7 +217,7 @@ $(function () { return false; }); - $('.addWidgetCheck').on('click touchstart', function () { + $('.addWidgetCheck').on('click touch', function () { if ($(this).parents('.addWidgetLine').length > 0) { $.post('/home/addWidget/', {id: $(this).parent().attr('id')}); $(this).parent().remove(); diff --git a/core/interface/templates/home.html b/core/interface/templates/home.html index 822204f53..1ee8cf345 100644 --- a/core/interface/templates/home.html +++ b/core/interface/templates/home.html @@ -62,7 +62,8 @@ {% for widgetName, widget in widgetList.items() %}
    {{ widgetName }}
    - +
    {% endfor %}
    @@ -77,29 +78,29 @@ {% if widget.custStyle.get('titlebar') == "False" %} widgetIconsHidden {% endif %} - " id="{{ widget.parent }}_{{ widget.name }}" + z-indexed" id="{{ widget.parent }}_{{ widget.name }}" style="position: relative; top: {{ widget.y }}px; left: {{ widget.x }}px; - {% if widget.width > 0 %}width: {{widget.width}}px;{% endif %} - {% if widget.height > 0 %} height:{{widget.height}}px;{% endif %} - z-index: {{ widget.zindex }}; + {% if widget.width > 0 %}width: {{ widget.width }}px;{% endif %} + {% if widget.height > 0 %} height:{{ widget.height }}px;{% endif %} + z-index: {{ widget.zindex }}; - {% if widget.custStyle.get('background-opactiy')|float < 1.0 %} - {% if widget.custStyle.get('background') != "" %} - background: rgba({{ widget.backgroundRGBA }}); - {% else %} - background: var(--windowBG); - {% endif %} - box-shadow: none; - {% else %} - {% if widget.custStyle.get('background') != "" %} - background: {{ widget.custStyle.get('background') }}; - {% else %} - background: var(--windowBG); - {% endif %} - {% endif %} + {% if widget.custStyle.get('background-opactiy')|float < 1.0 %} + {% if widget.custStyle.get('background') != "" %} + background: rgba({{ widget.backgroundRGBA }}); + {% else %} + background: var(--windowBG); + {% endif %} + box-shadow: none; + {% else %} + {% if widget.custStyle.get('background') != "" %} + background: {{ widget.custStyle.get('background') }}; + {% else %} + background: var(--windowBG); + {% endif %} + {% endif %} - {% if widget.custStyle.get('color') != "" %}color: {{ widget.custStyle.get('color') }};{% endif %} - {% if widget.custStyle.get('font-size') != "" %}font-size: {{ widget.custStyle.get('font-size') }}em;{% endif %} + {% if widget.custStyle.get('color') != "" %}color: {{ widget.custStyle.get('color') }};{% endif %} + {% if widget.custStyle.get('font-size') != "" %}font-size: {{ widget.custStyle.get('font-size') }}em;{% endif %} "> {{ widget.html()|safe }}
    From 44a7e0eed71d857c316a6844b72b222a774ddb42 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Mon, 26 Oct 2020 13:50:29 +0100 Subject: [PATCH 067/126] :construction: Untested, reworked indexers on widget page --- core/interface/static/js/common.js | 95 ++++++++++++++++-------------- core/interface/templates/home.html | 12 ++-- 2 files changed, 55 insertions(+), 52 deletions(-) diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index c1f45fdf6..334990965 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -33,50 +33,6 @@ function mqttRegisterSelf(target, method) { mqttSubscribers[method].push(target); } -function initIndexers($element) { - let indexer = $element.children('.zindexer'); - - indexer.children('.zindexer-up').on('click touch', function () { - let $parent = $(this).parent().parent(); - let actualIndex = $element.css('z-index'); - if (actualIndex == null || actualIndex == 'auto') { - actualIndex = 0; - } else { - actualIndex = parseInt(actualIndex); - } - - let baseClass = $parent.attr('class').split(/\s+/)[0]; - $('.' + baseClass).each(function () { - let thisIndex = $(this).css('z-index'); - if (thisIndex != null && thisIndex != 'auto' && parseInt(thisIndex) == actualIndex + 1) { - $(this).css('z-index', actualIndex); - $parent.css('z-index', actualIndex + 1); - return false; - } - }); - }); - - indexer.children('.zindexer-down').on('click touch', function () { - let $parent = $(this).parent().parent(); - let actualIndex = $element.css('z-index'); - if (actualIndex == null || actualIndex == 'auto' || parseInt(actualIndex) <= 0) { - actualIndex = 0; - } else { - actualIndex = parseInt(actualIndex); - } - - let baseClass = $parent.attr('class').split(/\s+/)[0]; - $('.' + baseClass).each(function () { - let thisIndex = $(this).css('z-index'); - if (thisIndex != null && thisIndex != 'auto' && parseInt(thisIndex) == actualIndex -1) { - $(this).css('z-index', actualIndex); - $parent.css('z-index', actualIndex - 1); - return false; - } - }); - }); -} - $(document).tooltip(); $(function () { @@ -233,6 +189,35 @@ $(function () { } } + function reorder($widget, direction) { + let $container = $widget.parent(); + let $family = $container.children('.z-indexed'); + + let actualIndex = $widget.css('z-index'); + try { + actualIndex = parseInt(actualIndex); + } catch { + actualIndex = 0 + } + + let toIndex = actualIndex + 1; + if (direction === 'down') { + toIndex = Math.max(0, actualIndex - 1); + } + + $family.each(function(){ + try { + if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { + $(this).css('z-index', actualIndex); + return false; + } + } catch { + return true; + } + }) + $widget.css('z-index', toIndex); + } + let $defaultTab = $('.tabsContainer ul li:first'); $('.tabsContent').children().each(function () { if ($(this).attr('id') == $defaultTab.data('for')) { @@ -289,4 +274,26 @@ $(function () { if ($checkboxes.length) { $checkboxes.checkToggler(); } + + // Z-indexers + let $zindexed = $('.z-indexed'); + if ($zindexed.length) { + let $indexUp = $('
    ') + let $indexDown = $('
    ') + + $indexUp.on('click touch', function() { + reorder($(this), 'up'); + }); + $indexUp.on('click touch', function() { + reorder($(this), 'down'); + }); + + let $zindexer = $('
    '); + $zindexer.append($indexUp) + $zindexer.append($indexDown) + + $zindexed.each(function() { + $zindexed.append($zindexer); + }); + } }); diff --git a/core/interface/templates/home.html b/core/interface/templates/home.html index 1ee8cf345..c31db0266 100644 --- a/core/interface/templates/home.html +++ b/core/interface/templates/home.html @@ -74,25 +74,25 @@ {% for parentName, widgetList in widgets.items() %} {% for widgetName, widget in widgetList.items() %} {% if (widget.state|int) == 1 %} -
    -
    -
    -
    -
    {% endif %} {% endfor %} From 10f146314d5a9741e292b824c93f780a19c7c6e7 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Mon, 26 Oct 2020 14:01:59 +0100 Subject: [PATCH 068/126] :construction: Don't allow z-index to raise infinitly --- core/interface/static/js/common.js | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 334990965..5406de5e7 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -200,21 +200,35 @@ $(function () { actualIndex = 0 } - let toIndex = actualIndex + 1; + let toIndex; if (direction === 'down') { toIndex = Math.max(0, actualIndex - 1); + } else { + let highest = 0; + $family.each(function(){ + try { + if (parseInt($(this).css('z-index')) > highest) { + highest = parseInt($(this).css('z-index')); + } + } catch { + return true; + } + }); + toIndex = actualIndex + 1; + if (toIndex > highest) { + return; + } } $family.each(function(){ try { if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { $(this).css('z-index', actualIndex); - return false; } } catch { return true; } - }) + }); $widget.css('z-index', toIndex); } From e831150daef3a596681ae11d902f4d0561f7470a Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 26 Oct 2020 16:50:57 +0100 Subject: [PATCH 069/126] :sparkles: working z-indexer --- core/interface/static/js/common.js | 23 ++++++++++++----------- core/interface/static/js/widgets.js | 4 ---- 2 files changed, 12 insertions(+), 15 deletions(-) diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 5406de5e7..50a15f599 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -189,7 +189,9 @@ $(function () { } } - function reorder($widget, direction) { + function reorder($arrow, direction) { + console.log(direction); + let $widget = $arrow.parent().parent(); let $container = $widget.parent(); let $family = $container.children('.z-indexed'); @@ -197,7 +199,7 @@ $(function () { try { actualIndex = parseInt(actualIndex); } catch { - actualIndex = 0 + actualIndex = 0; } let toIndex; @@ -223,6 +225,7 @@ $(function () { $family.each(function(){ try { if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { + console.log($(this).attr('id')); $(this).css('z-index', actualIndex); } } catch { @@ -292,22 +295,20 @@ $(function () { // Z-indexers let $zindexed = $('.z-indexed'); if ($zindexed.length) { - let $indexUp = $('
    ') - let $indexDown = $('
    ') + let $indexUp = $('
    '); + let $indexDown = $('
    '); - $indexUp.on('click touch', function() { + $indexUp.on('click touch', function () { reorder($(this), 'up'); }); - $indexUp.on('click touch', function() { + $indexDown.on('click touch', function () { reorder($(this), 'down'); }); let $zindexer = $('
    '); - $zindexer.append($indexUp) - $zindexer.append($indexDown) + $zindexer.append($indexUp); + $zindexer.append($indexDown); - $zindexed.each(function() { - $zindexed.append($zindexer); - }); + $zindexed.append($zindexer); } }); diff --git a/core/interface/static/js/widgets.js b/core/interface/static/js/widgets.js index 5b2cab997..c76110d5d 100644 --- a/core/interface/static/js/widgets.js +++ b/core/interface/static/js/widgets.js @@ -13,10 +13,6 @@ $(function () { disabled: true }); - $widgets.each(function () { - initIndexers($(this)); - }); - /* Toolbar Functions */ $('#toolbarToggleShow').on('click touch', function () { $('#toolbar_full').show(); From 27fef85f07caa3fb74736011d6f292c8b6906673 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 26 Oct 2020 16:51:47 +0100 Subject: [PATCH 070/126] :lipstick: Cleanup --- core/interface/static/js/common.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 50a15f599..73bdfc887 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -190,7 +190,6 @@ $(function () { } function reorder($arrow, direction) { - console.log(direction); let $widget = $arrow.parent().parent(); let $container = $widget.parent(); let $family = $container.children('.z-indexed'); @@ -225,7 +224,6 @@ $(function () { $family.each(function(){ try { if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { - console.log($(this).attr('id')); $(this).css('z-index', actualIndex); } } catch { From c5432386ae5db949d7763524163e3845bb72a927 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 26 Oct 2020 17:14:11 +0100 Subject: [PATCH 071/126] :lipstick: :art: Cosmetic --- core/interface/static/css/projectalice.css | 19 ++++++++++++++++--- core/interface/static/js/common.js | 4 ++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index 4616eab39..a8351af7c 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -94,26 +94,39 @@ button.button{ font-size: 1.6em; height: 2em; } .pageTitle { font-size: 2em; margin-left: 1em; align-items: center; display: flex; width: 7.4em;} .aliceStatus { flex-grow: 1; } + .updateChannelMarker { font-style: italic; margin: 0 .9em; align-items: center; display: flex; } .resourceUsage { margin: 0 .9em; align-items: center; display: flex; } -.page { flex: 1 100%; flex-flow: row nowrap; display: flex; } +.page { flex: 1 100%; flex-flow: row nowrap; display: flex; } .toolbar { background: var(--mainBG); display: flex; float: right; clear: both; padding-top: .2em; padding-left: 1em; border: solid 2px; border-bottom-left-radius: 1.5em; border-top: none; border-color: var(--accent); } + .toolbarButton { font-size: 2em; padding-right: .5em; min-width: .9em; text-align: center; } -.zindexer { position: absolute; top: 15px; right: 15px; color: var(--accent); font-weight: bold; font-size: 1.3em; z-index: 999; cursor: pointer; } -[class^='zindexer-']:hover { color: var(--hover); } +.zindexer { position: absolute; top: 5px; left: 5px; background-color: var(--mainBG); width: 50px; border-radius: 25px; text-align: center; } + +.zindexer .clickable:first-child { margin-bottom: 5px; } + +.clickable { cursor: pointer; } + +.clickable:hover { color: var(--hover); } + +/*.zindexer { position: absolute; top: -15px; right: -15px; color: var(--accent); font-weight: bold; font-size: 1.3em; z-index: 999; cursor: pointer; } +[class^='zindexer-']:hover { color: var(--hover); }*/ /* Utility screen for administration */ .utility { width: 12em; height: 12em; flex-flow: column; align-items: center; } + .utilityIcon { text-align: center; font-size: 5em; margin-top: .4em; width: 100%; } + .utilityText { font-size: 1.25em; margin: .3em; text-align: center; width: 90%; } /* console and logging */ /* using color markings as defined above */ .console { position: absolute; top: 20px; bottom: 40px; left: 20px; right: 20px; padding: 1em; background-color: black; color: var(--text); overflow-y: scroll; overflow-x: hidden; font-family: var(--readable); -moz-user-select: text; -webkit-touch-callout: default; -webkit-user-select: text; -ms-user-select: text; user-select: text; -khtml-user-select: text; } + .log { display: inline-block; } .logLine { display: flex; font-family: var(--readable); font-size: 0.9em; margin: 2px 0 2px 0; align-items: flex-start; } diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 73bdfc887..1db3ad657 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -293,8 +293,8 @@ $(function () { // Z-indexers let $zindexed = $('.z-indexed'); if ($zindexed.length) { - let $indexUp = $('
    '); - let $indexDown = $('
    '); + let $indexUp = $('
    '); + let $indexDown = $('
    '); $indexUp.on('click touch', function () { reorder($(this), 'up'); From 3641f8b8cd6b0bbdb0a58e85bb66df403253282f Mon Sep 17 00:00:00 2001 From: Philipp <41761223+philipp2310@users.noreply.github.com> Date: Tue, 27 Oct 2020 01:23:24 +0100 Subject: [PATCH 072/126] removed old zindexer --- core/interface/static/js/myHome.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/core/interface/static/js/myHome.js b/core/interface/static/js/myHome.js index fda6f1605..87b59e10c 100644 --- a/core/interface/static/js/myHome.js +++ b/core/interface/static/js/myHome.js @@ -369,20 +369,16 @@ $(function () { data['display']['z-index'] = maxZindex; maxZindex++; } - let $newZone = $('
    ' + '
    ' + data['name'] + '
    ' + - '
    ' + - '
    ' + - '
    ' + '
    ' + '
    '); - initIndexers($newZone); $newZone.on('click touch', function () { if (buildingMode) { From cd33871a8931f647d996b86c6174d79c9e35ac94 Mon Sep 17 00:00:00 2001 From: Philipp <41761223+philipp2310@users.noreply.github.com> Date: Tue, 27 Oct 2020 01:33:40 +0100 Subject: [PATCH 073/126] reset color of zindex arrows to --text, z-Index in current draggable to 999 (always on top for this div) --- core/interface/static/css/projectalice.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index a8351af7c..0310577a4 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -105,7 +105,7 @@ button.button{ font-size: 1.6em; height: 2em; } .toolbarButton { font-size: 2em; padding-right: .5em; min-width: .9em; text-align: center; } -.zindexer { position: absolute; top: 5px; left: 5px; background-color: var(--mainBG); width: 50px; border-radius: 25px; text-align: center; } +.zindexer { position: absolute; top: 5px; left: 5px; background-color: var(--mainBG); width: 50px; border-radius: 25px; text-align: center; color: var(--text); z-index:99; } .zindexer .clickable:first-child { margin-bottom: 5px; } From f5fe7f5815f7c6505aae52c91b5dbd49648cacc9 Mon Sep 17 00:00:00 2001 From: Philipp <41761223+philipp2310@users.noreply.github.com> Date: Tue, 27 Oct 2020 01:34:25 +0100 Subject: [PATCH 074/126] resized/reset icons for z-sorting in myHome --- core/interface/static/css/myHome.css | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/interface/static/css/myHome.css b/core/interface/static/css/myHome.css index dea613d47..366a03a9b 100644 --- a/core/interface/static/css/myHome.css +++ b/core/interface/static/css/myHome.css @@ -1,4 +1,5 @@ -.floorPlan-Zone { overflow: visible; background-color: var(--mainBG); color: var(--windowBG); position: absolute; display: flex; justify-content: center; align-items: center; font-size: 1.5em; } +.floorPlan-Zone { overflow: visible; background-color: var(--mainBG); color: var(--windowBG); position: absolute; display: flex; justify-content: center; align-items: center;} +.floorPlan-Zone > .inputOrText {font-size: 1.5em;} .floorPlan-Wall { background-color: var(--accent); } From da1566dfddc0b08af769daeee5e4faf9eaaff226 Mon Sep 17 00:00:00 2001 From: Philipp <41761223+philipp2310@users.noreply.github.com> Date: Tue, 27 Oct 2020 01:36:17 +0100 Subject: [PATCH 075/126] refactored zIndexMe as function in global scope --> myHome creates zindexed objects in runtime, needs to create the zindexer in runtime as well. callback reorder needs global scope as well. --- core/interface/static/js/common.js | 97 ++++++++++++++++-------------- core/interface/static/js/myHome.js | 1 + 2 files changed, 52 insertions(+), 46 deletions(-) diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 1db3ad657..19271e97a 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -189,50 +189,6 @@ $(function () { } } - function reorder($arrow, direction) { - let $widget = $arrow.parent().parent(); - let $container = $widget.parent(); - let $family = $container.children('.z-indexed'); - - let actualIndex = $widget.css('z-index'); - try { - actualIndex = parseInt(actualIndex); - } catch { - actualIndex = 0; - } - - let toIndex; - if (direction === 'down') { - toIndex = Math.max(0, actualIndex - 1); - } else { - let highest = 0; - $family.each(function(){ - try { - if (parseInt($(this).css('z-index')) > highest) { - highest = parseInt($(this).css('z-index')); - } - } catch { - return true; - } - }); - toIndex = actualIndex + 1; - if (toIndex > highest) { - return; - } - } - - $family.each(function(){ - try { - if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { - $(this).css('z-index', actualIndex); - } - } catch { - return true; - } - }); - $widget.css('z-index', toIndex); - } - let $defaultTab = $('.tabsContainer ul li:first'); $('.tabsContent').children().each(function () { if ($(this).attr('id') == $defaultTab.data('for')) { @@ -293,6 +249,56 @@ $(function () { // Z-indexers let $zindexed = $('.z-indexed'); if ($zindexed.length) { + zIndexMe($zindexed) + } +}); + + function reorder($arrow, direction) { + let $widget = $arrow.parent().parent(); + let $container = $widget.parent(); + let $family = $container.children('.z-indexed'); + + let actualIndex = $widget.css('z-index'); + try { + actualIndex = parseInt(actualIndex); + } catch { + actualIndex = 0; + } + + let toIndex; + if (direction === 'down') { + toIndex = Math.max(0, actualIndex - 1); + } else { + let highest = 0; + $family.each(function(){ + try { + if (parseInt($(this).css('z-index')) > highest) { + highest = parseInt($(this).css('z-index')); + } + } catch { + return true; + } + }); + toIndex = actualIndex + 1; + if (toIndex > highest) { + return; + } + } + + $family.each(function(){ + try { + if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { + $(this).css('z-index', actualIndex); + } + } catch { + return true; + } + }); + $widget.css('z-index', toIndex); + } + + +function zIndexMe($zindexed){ let $indexUp = $('
    '); let $indexDown = $('
    '); @@ -308,5 +314,4 @@ $(function () { $zindexer.append($indexDown); $zindexed.append($zindexer); - } -}); +} diff --git a/core/interface/static/js/myHome.js b/core/interface/static/js/myHome.js index 87b59e10c..eee922620 100644 --- a/core/interface/static/js/myHome.js +++ b/core/interface/static/js/myHome.js @@ -379,6 +379,7 @@ $(function () { '
    ' + ''); + zIndexMe($newZone); $newZone.on('click touch', function () { if (buildingMode) { From 505c052779b26fae792cfa70308d421e73e28104 Mon Sep 17 00:00:00 2001 From: Philipp <41761223+philipp2310@users.noreply.github.com> Date: Tue, 27 Oct 2020 22:59:18 +0100 Subject: [PATCH 076/126] should return Ids if asked for Ids --- core/device/DeviceManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/device/DeviceManager.py b/core/device/DeviceManager.py index a8cf57016..16de50973 100644 --- a/core/device/DeviceManager.py +++ b/core/device/DeviceManager.py @@ -555,7 +555,7 @@ def getAliceTypeDevices(self, connectedOnly: bool = False, includeMain: bool = F def getAliceTypeDeviceTypeIds(self): - return [self.getMainDevice().deviceTypeID, self.getDeviceTypeByName(self.SAT_TYPE)] + return [self.getMainDevice().deviceTypeID, self.getDeviceTypeByName(self.SAT_TYPE).id] def siteIdToDeviceName(self, siteId: str) -> str: From 72f70017b80113eb4b09589c4485a55f2f5db13b Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 29 Oct 2020 13:23:03 +0100 Subject: [PATCH 077/126] :construction: experimenting with new audio package --- core/server/AudioServer.py | 1 - requirements.txt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 7b17a6680..e6de0c101 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -4,7 +4,6 @@ from pathlib import Path from typing import Dict, Optional -import pyaudio from webrtcvad import Vad from core.ProjectAliceExceptions import PlayBytesStopped diff --git a/requirements.txt b/requirements.txt index 02d064f38..f312451e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,3 +26,4 @@ toml==0.10.1 markdown==3.2.2 ProjectAlice-sk #pyopenssl==19.1.0 +sounddevice==0.4.1 From 31b2137514e3e8717ea93370b43b28b264cf72ac Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 29 Oct 2020 13:55:22 +0100 Subject: [PATCH 078/126] :construction: publishing works --- core/server/AudioServer.py | 28 ++++++++++++---------------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index e6de0c101..2a6ffcc5c 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -4,6 +4,7 @@ from pathlib import Path from typing import Dict, Optional +import sounddevice as sd from webrtcvad import Vad from core.ProjectAliceExceptions import PlayBytesStopped @@ -21,25 +22,21 @@ class AudioManager(Manager): LAST_USER_SPEECH = 'var/cache/lastUserpeech_{}_{}.wav' SECOND_LAST_USER_SPEECH = 'var/cache/secondLastUserSpeech_{}_{}.wav' - # Inspired by https://github.com/koenvervloesem/hermes-audio-server - def __init__(self): super().__init__() self._stopPlayingFlag: Optional[AliceEvent] = None self._playing = False self._waves: Dict[str, wave.Wave_write] = dict() + self._audioStream = None if self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): return - with self.Commons.shutUpAlsaFFS(): - self._audio = pyaudio.PyAudio() - self._vad = Vad(2) try: - self._audioOutput = self._audio.get_default_output_device_info() + self._audioOutput = sd.query_devices()[0] except: self.logFatal('Audio output not found, cannot continue') return @@ -47,7 +44,7 @@ def __init__(self): self.logInfo(f'Using **{self._audioOutput["name"]}** for audio output') try: - self._audioInput = self._audio.get_default_input_device_info() + self._audioInput = sd.query_devices()[0] except: self.logFatal('Audio input not found, cannot continue') else: @@ -65,11 +62,10 @@ def onStart(self): def onStop(self): super().onStop() + self._audioStream.stop(ignore_errors=True) + self._audioStream.close(ignore_errors=True) self.MqttManager.mqttClient.unsubscribe(constants.TOPIC_AUDIO_FRAME.format(self.ConfigManager.getAliceConfigByName('uuid'))) - if not self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): - self._audio.terminate() - def onStartListening(self, session: DialogSession): if not self.ConfigManager.getAliceConfigByName('recordAudioAfterWakeword'): @@ -103,13 +99,13 @@ def recordFrame(self, siteId: str, frame: bytes): def publishAudio(self): self.logInfo('Starting audio publisher') - audioStream = self._audio.open( - format=pyaudio.paInt16, + self._audioStream = sd.RawInputStream( + dtype='int16', channels=1, - rate=self.SAMPLERATE, - frames_per_buffer=self.FRAMES_PER_BUFFER, - input=True + samplerate=self.SAMPLERATE, + blocksize=self.FRAMES_PER_BUFFER, ) + self._audioStream.start() speech = False silence = self.SAMPLERATE / self.FRAMES_PER_BUFFER @@ -121,7 +117,7 @@ def publishAudio(self): break try: - frames = audioStream.read(num_frames=self.FRAMES_PER_BUFFER, exception_on_overflow=False) + frames = self._audioStream.read(frames=self.FRAMES_PER_BUFFER)[0] if self._vad.is_speech(frames, self.SAMPLERATE): if not speech and speechFrames < minSpeechFrames: From 69f148e7543e92a7acbc6b2ecc02de5ce9967f3b Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 29 Oct 2020 14:28:16 +0100 Subject: [PATCH 079/126] :construction: not sure how to play streams with sounddevice, will check later --- core/server/AudioServer.py | 46 +++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 2a6ffcc5c..020143062 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -1,4 +1,5 @@ import io +import queue import time import wave from pathlib import Path @@ -28,12 +29,15 @@ def __init__(self): self._stopPlayingFlag: Optional[AliceEvent] = None self._playing = False self._waves: Dict[str, wave.Wave_write] = dict() - self._audioStream = None + self._audioInputStream = None + self._audioOutputStream = None + self._playBuffer = queue.Queue() if self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): return self._vad = Vad(2) + print(sd.query_devices()) try: self._audioOutput = sd.query_devices()[0] @@ -62,8 +66,11 @@ def onStart(self): def onStop(self): super().onStop() - self._audioStream.stop(ignore_errors=True) - self._audioStream.close(ignore_errors=True) + self._playBuffer.empty() + self._audioOutputStream.stop(ignore_errors=True) + self._audioOutputStream.close(ignore_errors=True) + self._audioInputStream.stop(ignore_errors=True) + self._audioInputStream.close(ignore_errors=True) self.MqttManager.mqttClient.unsubscribe(constants.TOPIC_AUDIO_FRAME.format(self.ConfigManager.getAliceConfigByName('uuid'))) @@ -99,13 +106,13 @@ def recordFrame(self, siteId: str, frame: bytes): def publishAudio(self): self.logInfo('Starting audio publisher') - self._audioStream = sd.RawInputStream( + self._audioInputStream = sd.RawInputStream( dtype='int16', channels=1, samplerate=self.SAMPLERATE, blocksize=self.FRAMES_PER_BUFFER, ) - self._audioStream.start() + self._audioInputStream.start() speech = False silence = self.SAMPLERATE / self.FRAMES_PER_BUFFER @@ -117,7 +124,7 @@ def publishAudio(self): break try: - frames = self._audioStream.read(frames=self.FRAMES_PER_BUFFER)[0] + frames = self._audioInputStream.read(frames=self.FRAMES_PER_BUFFER)[0] if self._vad.is_speech(frames, self.SAMPLERATE): if not speech and speechFrames < minSpeechFrames: @@ -170,29 +177,28 @@ def onPlayBytes(self, requestId: str, payload: bytearray, siteId: str, sessionId with io.BytesIO(payload) as buffer: try: with wave.open(buffer, 'rb') as wav: - sampleWidth = wav.getsampwidth() - nFormat = self._audio.get_format_from_width(sampleWidth) channels = wav.getnchannels() framerate = wav.getframerate() + def streamCallback(_inData, frameCount, _timeInfo, _status) -> tuple: data = wav.readframes(frameCount) - return data, pyaudio.paContinue + return data + - audioStream = self._audio.open( - format=nFormat, + audioStream = sd.RawOutputStream( + dtype='int16', channels=channels, - rate=framerate, - output=True, - output_device_index=self._audioOutput['index'], - stream_callback=streamCallback + samplerate=framerate, + device=8, + callback=streamCallback ) - self.logDebug(f'Playing wav stream using **{self._audioOutput["name"]}** audio output from site id **{self.DeviceManager.siteIdToDeviceName(siteId)}** (Format: {nFormat}, channels: {channels}, rate: {framerate})') - audioStream.start_stream() - while audioStream.is_active(): + self.logDebug(f'Playing wav stream using **{self._audioOutput["name"]}** audio output from site id **{self.DeviceManager.siteIdToDeviceName(siteId)}** (channels: {channels}, rate: {framerate})') + audioStream.start() + while audioStream.active: if self._stopPlayingFlag.is_set(): - audioStream.stop_stream() + audioStream.stop() audioStream.close() if sessionId: @@ -209,7 +215,7 @@ def streamCallback(_inData, frameCount, _timeInfo, _status) -> tuple: raise PlayBytesStopped time.sleep(0.1) - audioStream.stop_stream() + audioStream.stop() audioStream.close() except PlayBytesStopped: self.logDebug('Playing bytes stopped') From 922c085c484f2d38e615b5f0fd013fe03aeb7492 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 29 Oct 2020 17:43:23 +0100 Subject: [PATCH 080/126] :construction: for me it's working here on respeaker 2 --- core/server/AudioServer.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 020143062..557cbaecd 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -30,14 +30,12 @@ def __init__(self): self._playing = False self._waves: Dict[str, wave.Wave_write] = dict() self._audioInputStream = None - self._audioOutputStream = None - self._playBuffer = queue.Queue() + self._audioQueue = queue.Queue() if self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): return self._vad = Vad(2) - print(sd.query_devices()) try: self._audioOutput = sd.query_devices()[0] @@ -66,9 +64,6 @@ def onStart(self): def onStop(self): super().onStop() - self._playBuffer.empty() - self._audioOutputStream.stop(ignore_errors=True) - self._audioOutputStream.close(ignore_errors=True) self._audioInputStream.stop(ignore_errors=True) self._audioInputStream.close(ignore_errors=True) self.MqttManager.mqttClient.unsubscribe(constants.TOPIC_AUDIO_FRAME.format(self.ConfigManager.getAliceConfigByName('uuid'))) @@ -181,25 +176,28 @@ def onPlayBytes(self, requestId: str, payload: bytearray, siteId: str, sessionId framerate = wav.getframerate() - def streamCallback(_inData, frameCount, _timeInfo, _status) -> tuple: - data = wav.readframes(frameCount) - return data + def streamCallback(outdata, frameCount, _timeInfo, _status): + try: + data = wav.readframes(frameCount) + outdata[:] = data + except: + raise PlayBytesStopped - audioStream = sd.RawOutputStream( + stream = sd.RawOutputStream( dtype='int16', channels=channels, samplerate=framerate, - device=8, + device=0, callback=streamCallback ) self.logDebug(f'Playing wav stream using **{self._audioOutput["name"]}** audio output from site id **{self.DeviceManager.siteIdToDeviceName(siteId)}** (channels: {channels}, rate: {framerate})') - audioStream.start() - while audioStream.active: + stream.start() + while stream.active: if self._stopPlayingFlag.is_set(): - audioStream.stop() - audioStream.close() + stream.stop() + stream.close() if sessionId: self.MqttManager.publish( @@ -215,8 +213,8 @@ def streamCallback(_inData, frameCount, _timeInfo, _status) -> tuple: raise PlayBytesStopped time.sleep(0.1) - audioStream.stop() - audioStream.close() + stream.stop() + stream.close() except PlayBytesStopped: self.logDebug('Playing bytes stopped') except Exception as e: From 5e482bc0a3d2e7db716a89b2fe8ac1b4a9d5577c Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 05:51:13 +0100 Subject: [PATCH 081/126] :construction: force using system default device --- core/server/AudioServer.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 557cbaecd..a700054a0 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -1,5 +1,4 @@ import io -import queue import time import wave from pathlib import Path @@ -30,13 +29,19 @@ def __init__(self): self._playing = False self._waves: Dict[str, wave.Wave_write] = dict() self._audioInputStream = None - self._audioQueue = queue.Queue() if self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): return self._vad = Vad(2) + devices = sd.query_devices() + + for device in devices: + print(device) + + print(devices) + try: self._audioOutput = sd.query_devices()[0] except: @@ -175,7 +180,6 @@ def onPlayBytes(self, requestId: str, payload: bytearray, siteId: str, sessionId channels = wav.getnchannels() framerate = wav.getframerate() - def streamCallback(outdata, frameCount, _timeInfo, _status): try: data = wav.readframes(frameCount) @@ -183,12 +187,11 @@ def streamCallback(outdata, frameCount, _timeInfo, _status): except: raise PlayBytesStopped - stream = sd.RawOutputStream( dtype='int16', channels=channels, samplerate=framerate, - device=0, + device=None, callback=streamCallback ) From 921d90dedc05f664d60ebdd2bbf28b899f091fbb Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 05:59:18 +0100 Subject: [PATCH 082/126] :construction: New configs for audio devices, WIP --- configTemplate.json | 24 +++++++++++++++++++++--- core/server/AudioServer.py | 7 ------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index 20ad0bdb3..826a9ad1a 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -35,14 +35,32 @@ "isSensitive" : false, "description" : "Your asound settings", "beforeUpdate": "injectAsound", - "category" : "system" + "category" : "audio" }, "recordAudioAfterWakeword": { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, "description" : "Allow audio record after a wakeword is detected to keep the last user speech. Can be usefull for recording skills", - "category" : "system" + "category" : "audio" + }, + "outputDevice" : { + "defaultValue": "", + "dataType" : "list", + "isSensitive" : false, + "values" : [], + "description" : "The device to use to play sounds", + "category" : "audio", + "onInit" : "AudioServer.populateOutputDevices" + }, + "intputDevice" : { + "defaultValue": "", + "dataType" : "list", + "isSensitive" : false, + "values" : [], + "description" : "The device to use to record sounds", + "category" : "audio", + "onInit" : "AudioServer.populateInputDevices" }, "deviceName" : { "defaultValue": "default", @@ -109,7 +127,7 @@ "isSensitive" : false, "description" : "If this device is a server without sound and mic, turn this to true", "onUpdate" : "enableDisableSound", - "category" : "device" + "category" : "audio" }, "notUnderstoodRetries": { "defaultValue": 3, diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index a700054a0..0df859f38 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -35,13 +35,6 @@ def __init__(self): self._vad = Vad(2) - devices = sd.query_devices() - - for device in devices: - print(device) - - print(devices) - try: self._audioOutput = sd.query_devices()[0] except: From 39e37b6ab56572b0c34eed9f6a1066c6ba711018 Mon Sep 17 00:00:00 2001 From: "githubbot@projectalice.io" Date: Fri, 30 Oct 2020 07:27:25 +0100 Subject: [PATCH 083/126] :bug: Add utterance should use secondLast intent, as the last intent would always be "yes" --- core/dialog/DialogTemplateManager.py | 2 +- core/dialog/model/DialogSession.py | 17 ++++++++++++++--- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/core/dialog/DialogTemplateManager.py b/core/dialog/DialogTemplateManager.py index 56588aff6..4b7afab20 100644 --- a/core/dialog/DialogTemplateManager.py +++ b/core/dialog/DialogTemplateManager.py @@ -193,7 +193,7 @@ def addUtterance(self, session: DialogSession): if not text: return - intent = session.previousIntent + intent = session.secondLastIntent if not intent: return diff --git a/core/dialog/model/DialogSession.py b/core/dialog/model/DialogSession.py index 9cb7e3487..a59da28c2 100644 --- a/core/dialog/model/DialogSession.py +++ b/core/dialog/model/DialogSession.py @@ -1,7 +1,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Any +from typing import Any, Optional from paho.mqtt.client import MQTTMessage @@ -101,5 +101,16 @@ def addToHistory(self, intent: Intent): @property - def previousIntent(self) -> str: - return str(self.intentHistory[-1]) + def previousIntent(self) -> Optional[str]: + try: + return str(self.intentHistory[-1]) + except: + return None + + + @property + def secondLastIntent(self) -> Optional[str]: + try: + return str(self.intentHistory[-2]) + except: + return None From 0c697cde9f3912fce9da5bed12a87bc844bc314e Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 15:14:26 +0100 Subject: [PATCH 084/126] :construction: Settings for audio devices and upgraded audio server --- config-schema.json | 12 ++++++- configTemplate.json | 21 ++++++++++-- core/base/ConfigManager.py | 55 +++++++++++++++++++++++++++++++- core/server/AudioServer.py | 65 ++++++++++++++++++++++++++++---------- 4 files changed, 132 insertions(+), 21 deletions(-) diff --git a/config-schema.json b/config-schema.json index 59652dbf3..fe8be1335 100644 --- a/config-schema.json +++ b/config-schema.json @@ -44,7 +44,9 @@ "mqtt", "tts", "wakeword", - "advanced debug" + "advanced debug", + "audio", + "scenarios" ] }, "isSensitive" : { @@ -72,6 +74,14 @@ "type" : "string", "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$" }, + "onStart" : { + "type" : "string", + "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$" + }, + "onInit" : { + "type" : "string", + "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$" + }, "parent" : { "type" : "object", "required" : [ diff --git a/configTemplate.json b/configTemplate.json index 826a9ad1a..b2c241755 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -35,7 +35,12 @@ "isSensitive" : false, "description" : "Your asound settings", "beforeUpdate": "injectAsound", - "category" : "audio" + "category" : "audio", + "parent": { + "config": "disableSoundAndMic", + "condition": "isnot", + "value": false + } }, "recordAudioAfterWakeword": { "defaultValue": false, @@ -51,7 +56,12 @@ "values" : [], "description" : "The device to use to play sounds", "category" : "audio", - "onInit" : "AudioServer.populateOutputDevices" + "onStart" : "AudioServer.populateOutputDevices", + "parent": { + "config": "disableSoundAndMic", + "condition": "isnot", + "value": false + } }, "intputDevice" : { "defaultValue": "", @@ -60,7 +70,12 @@ "values" : [], "description" : "The device to use to record sounds", "category" : "audio", - "onInit" : "AudioServer.populateInputDevices" + "onStart" : "AudioServer.populateInputDevices", + "parent": { + "config": "disableSoundAndMic", + "condition": "isnot", + "value": false + } }, "deviceName" : { "defaultValue": "default", diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index 155ae5edd..2196c72ba 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -44,6 +44,28 @@ def onStart(self): if conf not in self._aliceConfigurations or self._aliceConfigurations[conf] == '': raise VitalConfigMissing(conf) + for definition in {**self._aliceTemplateConfigurations, **self._skillsTemplateConfigurations}.values(): + if definition['onInit']: + function = definition['onInit'] + try: + if '.' in function: + manager, function = function.split('.') + + try: + mngr = getattr(self, manager) + except AttributeError: + self.logWarning(f'Config on init manager **{manager}** does not exist') + return False + else: + mngr = self + + func = getattr(mngr, function) + func() + except AttributeError: + self.logWarning(f'Configuration on init method **{function}** does not exist') + except Exception as e: + self.logError(f'Configuration on init method **{function}** failed: {e}') + def _loadCheckAndUpdateAliceConfigFile(self): self.logInfo('Checking Alice configuration file') @@ -96,11 +118,26 @@ def _loadCheckAndUpdateAliceConfigFile(self): elif definition['dataType'] == 'list': values = definition['values'].values() if isinstance(definition['values'], dict) else definition['values'] - if aliceConfigs[setting] not in values: + if aliceConfigs[setting] and aliceConfigs[setting] not in values: changes = True self.logWarning(f'Selected value **{aliceConfigs[setting]}** for setting **{setting}** doesn\'t exist, reverted to default value --{definition["defaultValue"]}--') aliceConfigs[setting] = definition['defaultValue'] + if definition['onInit']: + function = definition['onInit'] + try: + if '.' in function: + self.logWarning(f'Use of manager for configuration **onInit** for config "{setting}" is not allowed') + function = function.split('.')[-1] + + func = getattr(self, function) + func() + except AttributeError: + self.logWarning(f'Configuration on init method **{function}** does not exist') + except Exception as e: + self.logError(f'Configuration on init method **{function}** failed: {e}') + + # Setting logger level immediately if aliceConfigs['advancedDebug'] and not aliceConfigs['debug']: aliceConfigs['debug'] = True @@ -122,6 +159,14 @@ def _loadCheckAndUpdateAliceConfigFile(self): self._aliceConfigurations = aliceConfigs + def updateAliceConfigDefinitionValues(self, setting: str, value: typing.Any): + if setting not in self._aliceTemplateConfigurations: + self.logWarning(f'Was asked to update **{setting}** from config templates, but setting doesn\'t exist') + return + + self._aliceTemplateConfigurations[setting] = value + + @staticmethod def loadJsonFromFile(jsonFile: Path) -> dict: try: @@ -377,6 +422,14 @@ def getAliceConfigByName(self, configName: str) -> typing.Any: return '' + def getAliceConfigTemplateByName(self, configName: str) -> typing.Any: + if configName in self._aliceTemplateConfigurations: + return self._aliceTemplateConfigurations[configName] + else: + self.logDebug(f'Trying to get config template **{configName}** but it does not exist') + return '' + + def getSkillConfigByName(self, skillName: str, configName: str) -> typing.Any: return self._skillsConfigurations.get(skillName, dict()).get(configName, None) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 0df859f38..fdffa2f26 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -5,6 +5,7 @@ from typing import Dict, Optional import sounddevice as sd +# noinspection PyUnresolvedReferences from webrtcvad import Vad from core.ProjectAliceExceptions import PlayBytesStopped @@ -34,25 +35,34 @@ def __init__(self): return self._vad = Vad(2) - - try: - self._audioOutput = sd.query_devices()[0] - except: - self.logFatal('Audio output not found, cannot continue') - return - else: - self.logInfo(f'Using **{self._audioOutput["name"]}** for audio output') - - try: - self._audioInput = sd.query_devices()[0] - except: - self.logFatal('Audio input not found, cannot continue') - else: - self.logInfo(f'Using **{self._audioInput["name"]}** for audio input') + self._audioInput = None + self._audioOutput = None def onStart(self): super().onStart() + + if not self.ConfigManager.getAliceConfigByName('inputDevice'): + self.logWarning('Input device not set in config, trying to find default device') + try: + self._audioInput = self.ConfigManager.getAliceConfigTemplateByName('inputDevice')['values'][0] + except: + self.logFatal('Audio input not found, cannot continue') + return + else: + self.logInfo(f'Using **{self._audioInput}** for audio input') + + if not self.ConfigManager.getAliceConfigByName('outputDevice'): + self.logWarning('Output device not set in config, trying to find default device') + try: + self._audioInput = self.ConfigManager.getAliceConfigTemplateByName('outputDevice')['values'][0] + except: + self.logFatal('Audio output not found, cannot continue') + return + else: + self.logInfo(f'Using **{self._audioOutput}** for audio output') + + self._stopPlayingFlag = self.ThreadManager.newEvent('stopPlaying') self.MqttManager.mqttClient.subscribe(constants.TOPIC_AUDIO_FRAME.format(self.ConfigManager.getAliceConfigByName('uuid'))) @@ -102,6 +112,7 @@ def publishAudio(self): self._audioInputStream = sd.RawInputStream( dtype='int16', channels=1, + device=self._audioInput, samplerate=self.SAMPLERATE, blocksize=self.FRAMES_PER_BUFFER, ) @@ -184,7 +195,7 @@ def streamCallback(outdata, frameCount, _timeInfo, _status): dtype='int16', channels=channels, samplerate=framerate, - device=None, + device=self._audioOutput, callback=streamCallback ) @@ -233,6 +244,28 @@ def stopPlaying(self): self._stopPlayingFlag.set() + def populateInputDevicesConfig(self): + devices = list() + try: + devices = [device['name'] for device in sd.query_devices(kind='input')] + except: + if not self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): + self.logWarning('No input device found') + + self.ConfigManager.updateAliceConfigDefinitionValues(setting='intputDevice', value=devices) + + + def populateOutputDevicesConfig(self): + devices = list() + try: + devices = [device['name'] for device in sd.query_devices(kind='output')] + except: + if not self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): + self.logWarning('No output device found') + + self.ConfigManager.updateAliceConfigDefinitionValues(setting='outputDevice', value=devices) + + @property def isPlaying(self) -> bool: return self._playing From 81a53c4880049b6225850cf7316ee761706ee5ab Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 16:27:45 +0100 Subject: [PATCH 085/126] :construction: don't revert inited configs --- core/base/ConfigManager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index 2196c72ba..8a6958476 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -104,7 +104,7 @@ def _loadCheckAndUpdateAliceConfigFile(self): if setting == 'supportedLanguages': continue - if definition['dataType'] != 'list' and definition['dataType'] != 'longstring': + if definition['dataType'] != 'list' and definition['dataType'] != 'longstring' and not definition['onInit']: if not isinstance(aliceConfigs[setting], type(definition['defaultValue'])): changes = True try: @@ -115,7 +115,7 @@ def _loadCheckAndUpdateAliceConfigFile(self): # If casting failed let's fall back to the new default value self.logWarning(f'Existing configuration type missmatch: **{setting}**, replaced with template configuration') aliceConfigs[setting] = definition['defaultValue'] - elif definition['dataType'] == 'list': + elif definition['dataType'] == 'list' and not definition['onInit']: values = definition['values'].values() if isinstance(definition['values'], dict) else definition['values'] if aliceConfigs[setting] and aliceConfigs[setting] not in values: From c21b82db8250395599f0770c3c0cd494f5178ccd Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 16:29:38 +0100 Subject: [PATCH 086/126] :bug: fix --- core/base/ConfigManager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index 8a6958476..8e478e75b 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -104,7 +104,7 @@ def _loadCheckAndUpdateAliceConfigFile(self): if setting == 'supportedLanguages': continue - if definition['dataType'] != 'list' and definition['dataType'] != 'longstring' and not definition['onInit']: + if definition['dataType'] != 'list' and definition['dataType'] != 'longstring' and 'onInit' not in definition: if not isinstance(aliceConfigs[setting], type(definition['defaultValue'])): changes = True try: @@ -115,7 +115,7 @@ def _loadCheckAndUpdateAliceConfigFile(self): # If casting failed let's fall back to the new default value self.logWarning(f'Existing configuration type missmatch: **{setting}**, replaced with template configuration') aliceConfigs[setting] = definition['defaultValue'] - elif definition['dataType'] == 'list' and not definition['onInit']: + elif definition['dataType'] == 'list' and 'onInit' not in definition: values = definition['values'].values() if isinstance(definition['values'], dict) else definition['values'] if aliceConfigs[setting] and aliceConfigs[setting] not in values: @@ -123,8 +123,8 @@ def _loadCheckAndUpdateAliceConfigFile(self): self.logWarning(f'Selected value **{aliceConfigs[setting]}** for setting **{setting}** doesn\'t exist, reverted to default value --{definition["defaultValue"]}--') aliceConfigs[setting] = definition['defaultValue'] - if definition['onInit']: - function = definition['onInit'] + function = definition.get('onInit', None) + if function: try: if '.' in function: self.logWarning(f'Use of manager for configuration **onInit** for config "{setting}" is not allowed') From af7f9828435ab4f35c4eb1e63c87af1b2af79847 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:01:42 +0100 Subject: [PATCH 087/126] :bug: fix default audio hardware selection --- configTemplate.json | 18 ++--- core/base/ConfigManager.py | 41 ++++++----- core/base/SuperManager.py | 143 +++++++++++++++++++------------------ core/server/AudioServer.py | 26 +------ 4 files changed, 109 insertions(+), 119 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index b2c241755..ec6b31f5e 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -56,25 +56,25 @@ "values" : [], "description" : "The device to use to play sounds", "category" : "audio", - "onStart" : "AudioServer.populateOutputDevices", - "parent": { - "config": "disableSoundAndMic", + "onInit" : "populateAudioDevicesConfig", + "parent" : { + "config" : "disableSoundAndMic", "condition": "isnot", - "value": false + "value" : false } }, - "intputDevice" : { + "inputDevice" : { "defaultValue": "", "dataType" : "list", "isSensitive" : false, "values" : [], "description" : "The device to use to record sounds", "category" : "audio", - "onStart" : "AudioServer.populateInputDevices", - "parent": { - "config": "disableSoundAndMic", + "onInit" : "populateAudioDevicesConfig", + "parent" : { + "config" : "disableSoundAndMic", "condition": "isnot", - "value": false + "value" : false } }, "deviceName" : { diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index 8e478e75b..1b2d673b0 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -4,6 +4,7 @@ import typing from pathlib import Path +import sounddevice as sd import toml from core.ProjectAliceExceptions import ConfigurationUpdateFailed, VitalConfigMissing @@ -44,27 +45,20 @@ def onStart(self): if conf not in self._aliceConfigurations or self._aliceConfigurations[conf] == '': raise VitalConfigMissing(conf) - for definition in {**self._aliceTemplateConfigurations, **self._skillsTemplateConfigurations}.values(): - if definition['onInit']: - function = definition['onInit'] + for setting, definition in {**self._aliceTemplateConfigurations, **self._skillsTemplateConfigurations}.items(): + function = definition.get('onStart', None) + if function: try: if '.' in function: - manager, function = function.split('.') + self.logWarning(f'Use of manager for configuration **onStart** for config "{setting}" is not allowed') + function = function.split('.')[-1] - try: - mngr = getattr(self, manager) - except AttributeError: - self.logWarning(f'Config on init manager **{manager}** does not exist') - return False - else: - mngr = self - - func = getattr(mngr, function) + func = getattr(self, function) func() except AttributeError: - self.logWarning(f'Configuration on init method **{function}** does not exist') + self.logWarning(f'Configuration onStart method **{function}** does not exist') except Exception as e: - self.logError(f'Configuration on init method **{function}** failed: {e}') + self.logError(f'Configuration onStart method **{function}** failed: {e}') def _loadCheckAndUpdateAliceConfigFile(self): @@ -133,9 +127,9 @@ def _loadCheckAndUpdateAliceConfigFile(self): func = getattr(self, function) func() except AttributeError: - self.logWarning(f'Configuration on init method **{function}** does not exist') + self.logWarning(f'Configuration onInit method **{function}** does not exist') except Exception as e: - self.logError(f'Configuration on init method **{function}** failed: {e}') + self.logError(f'Configuration onInit method **{function}** failed: {e}') # Setting logger level immediately @@ -707,6 +701,19 @@ def getGithubAuth(self) -> tuple: return (username, token) if (username and token) else None + def populateAudioDevicesConfig(self): + devices = list() + try: + devices = [device['name'] for device in sd.query_devices()] + if not devices: + raise Exception + except: + if not self.getAliceConfigByName('disableSoundAndMic'): + self.logWarning('No audio device found') + + self.updateAliceConfigDefinitionValues(setting='intputDevice', value=devices) + + @property def snipsConfigurations(self) -> dict: return self._snipsConfigurations diff --git a/core/base/SuperManager.py b/core/base/SuperManager.py index c1c990447..d58540731 100644 --- a/core/base/SuperManager.py +++ b/core/base/SuperManager.py @@ -54,77 +54,83 @@ def __init__(self, mainClass): def onStart(self): - commons = self._managers.pop('CommonsManager') - commons.onStart() - - configManager = self._managers.pop('ConfigManager') - configManager.onStart() - - languageManager = self._managers.pop('LanguageManager') - languageManager.onStart() - - locationManager = self._managers.pop('LocationManager') - locationManager.onStart() - - deviceManager = self._managers.pop('DeviceManager') - deviceManager.onStart() - - audioServer = self._managers.pop('AudioManager') - audioServer.onStart() - - internetManager = self._managers.pop('InternetManager') - internetManager.onStart() - - databaseManager = self._managers.pop('DatabaseManager') - databaseManager.onStart() - - userManager = self._managers.pop('UserManager') - userManager.onStart() - - mqttManager = self._managers.pop('MqttManager') - mqttManager.onStart() - - talkManager = self._managers.pop('TalkManager') - skillManager = self._managers.pop('SkillManager') - assistantManager = self._managers.pop('AssistantManager') - dialogTemplateManager = self._managers.pop('DialogTemplateManager') - nluManager = self._managers.pop('NluManager') - nodeRedManager = self._managers.pop('NodeRedManager') - - for manager in self._managers.values(): - if manager: - manager.onStart() - - talkManager.onStart() - nluManager.onStart() - skillManager.onStart() - dialogTemplateManager.onStart() - assistantManager.onStart() - nodeRedManager.onStart() - - self._managers[configManager.name] = configManager - self._managers[audioServer.name] = audioServer - self._managers[languageManager.name] = languageManager - self._managers[locationManager.name] = locationManager - self._managers[deviceManager.name] = deviceManager - self._managers[talkManager.name] = talkManager - self._managers[databaseManager.name] = databaseManager - self._managers[userManager.name] = userManager - self._managers[mqttManager.name] = mqttManager - self._managers[skillManager.name] = skillManager - self._managers[dialogTemplateManager.name] = dialogTemplateManager - self._managers[assistantManager.name] = assistantManager - self._managers[nluManager.name] = nluManager - self._managers[internetManager.name] = internetManager - self._managers[nodeRedManager.name] = nodeRedManager + try: + commons = self._managers.pop('CommonsManager') + commons.onStart() + + configManager = self._managers.pop('ConfigManager') + configManager.onStart() + + languageManager = self._managers.pop('LanguageManager') + languageManager.onStart() + + locationManager = self._managers.pop('LocationManager') + locationManager.onStart() + + deviceManager = self._managers.pop('DeviceManager') + deviceManager.onStart() + + audioServer = self._managers.pop('AudioManager') + audioServer.onStart() + + internetManager = self._managers.pop('InternetManager') + internetManager.onStart() + + databaseManager = self._managers.pop('DatabaseManager') + databaseManager.onStart() + + userManager = self._managers.pop('UserManager') + userManager.onStart() + + mqttManager = self._managers.pop('MqttManager') + mqttManager.onStart() + + talkManager = self._managers.pop('TalkManager') + skillManager = self._managers.pop('SkillManager') + assistantManager = self._managers.pop('AssistantManager') + dialogTemplateManager = self._managers.pop('DialogTemplateManager') + nluManager = self._managers.pop('NluManager') + nodeRedManager = self._managers.pop('NodeRedManager') + + for manager in self._managers.values(): + if manager: + manager.onStart() + + talkManager.onStart() + nluManager.onStart() + skillManager.onStart() + dialogTemplateManager.onStart() + assistantManager.onStart() + nodeRedManager.onStart() + + self._managers[configManager.name] = configManager + self._managers[audioServer.name] = audioServer + self._managers[languageManager.name] = languageManager + self._managers[locationManager.name] = locationManager + self._managers[deviceManager.name] = deviceManager + self._managers[talkManager.name] = talkManager + self._managers[databaseManager.name] = databaseManager + self._managers[userManager.name] = userManager + self._managers[mqttManager.name] = mqttManager + self._managers[skillManager.name] = skillManager + self._managers[dialogTemplateManager.name] = dialogTemplateManager + self._managers[assistantManager.name] = assistantManager + self._managers[nluManager.name] = nluManager + self._managers[internetManager.name] = internetManager + self._managers[nodeRedManager.name] = nodeRedManager + except: + pass # This happens when onStop is called while booting def onBooted(self): self.mqttManager.playSound(soundFilename='boot') - for manager in self._managers.values(): - if manager: - manager.onBooted() + try: + for manager in self._managers.values(): + if manager: + manager.onBooted() + except: + pass # Do nothing if manager is not ready @staticmethod @@ -197,8 +203,9 @@ def initManagers(self): def onStop(self): managerName = constants.UNKNOWN_MANAGER - mqttManager = self._managers.pop('MqttManager') try: + mqttManager = self._managers.pop('MqttManager') + for managerName, manager in self._managers.items(): manager.onStop() @@ -206,8 +213,6 @@ def onStop(self): mqttManager.onStop() except Exception as e: Logger().logError(f'Error while shutting down manager **{managerName}**: {e}') - import traceback - traceback.print_exc() def getManager(self, managerName: str): diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index fdffa2f26..61a9783c2 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -45,7 +45,7 @@ def onStart(self): if not self.ConfigManager.getAliceConfigByName('inputDevice'): self.logWarning('Input device not set in config, trying to find default device') try: - self._audioInput = self.ConfigManager.getAliceConfigTemplateByName('inputDevice')['values'][0] + self._audioInput = sd.query_devices(kind='input')['name'] except: self.logFatal('Audio input not found, cannot continue') return @@ -55,7 +55,7 @@ def onStart(self): if not self.ConfigManager.getAliceConfigByName('outputDevice'): self.logWarning('Output device not set in config, trying to find default device') try: - self._audioInput = self.ConfigManager.getAliceConfigTemplateByName('outputDevice')['values'][0] + self._audioOutput = sd.query_devices(kind='output')['name'] except: self.logFatal('Audio output not found, cannot continue') return @@ -244,28 +244,6 @@ def stopPlaying(self): self._stopPlayingFlag.set() - def populateInputDevicesConfig(self): - devices = list() - try: - devices = [device['name'] for device in sd.query_devices(kind='input')] - except: - if not self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): - self.logWarning('No input device found') - - self.ConfigManager.updateAliceConfigDefinitionValues(setting='intputDevice', value=devices) - - - def populateOutputDevicesConfig(self): - devices = list() - try: - devices = [device['name'] for device in sd.query_devices(kind='output')] - except: - if not self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): - self.logWarning('No output device found') - - self.ConfigManager.updateAliceConfigDefinitionValues(setting='outputDevice', value=devices) - - @property def isPlaying(self) -> bool: return self._playing From db9ae6be6fef43f73c3e8a62d9f2b3dab1887230 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:04:00 +0100 Subject: [PATCH 088/126] :bug: fix logic --- configTemplate.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index ec6b31f5e..2f941a4d3 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -60,7 +60,7 @@ "parent" : { "config" : "disableSoundAndMic", "condition": "isnot", - "value" : false + "value" : true } }, "inputDevice" : { @@ -74,7 +74,7 @@ "parent" : { "config" : "disableSoundAndMic", "condition": "isnot", - "value" : false + "value" : true } }, "deviceName" : { From c4c34ecf428487c782eabc1a076f70340f197979 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:16:54 +0100 Subject: [PATCH 089/126] :bug: fixes --- configTemplate.json | 4 ++-- core/base/ConfigManager.py | 29 +++++++++++++++++++++++------ core/base/SuperManager.py | 12 ++++++++---- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index 2f941a4d3..571fbed5f 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -56,7 +56,7 @@ "values" : [], "description" : "The device to use to play sounds", "category" : "audio", - "onInit" : "populateAudioDevicesConfig", + "onInit" : "populateAudioOutputConfig", "parent" : { "config" : "disableSoundAndMic", "condition": "isnot", @@ -70,7 +70,7 @@ "values" : [], "description" : "The device to use to record sounds", "category" : "audio", - "onInit" : "populateAudioDevicesConfig", + "onInit" : "populateAudioInputConfig", "parent" : { "config" : "disableSoundAndMic", "condition": "isnot", diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index 1b2d673b0..f132e6914 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -158,7 +158,7 @@ def updateAliceConfigDefinitionValues(self, setting: str, value: typing.Any): self.logWarning(f'Was asked to update **{setting}** from config templates, but setting doesn\'t exist') return - self._aliceTemplateConfigurations[setting] = value + self._aliceTemplateConfigurations[setting]['values'] = value @staticmethod @@ -701,17 +701,34 @@ def getGithubAuth(self) -> tuple: return (username, token) if (username and token) else None - def populateAudioDevicesConfig(self): - devices = list() + def populateAudioInputConfig(self): + try: + devices = self._listAudioDevices() + self.updateAliceConfigDefinitionValues(setting='inputDevice', value=devices) + except: + if not self.getAliceConfigByName('disableSoundAndMic'): + self.logWarning('No audio input device found') + + + def populateAudioOutputConfig(self): + try: + devices = self._listAudioDevices() + self.updateAliceConfigDefinitionValues(setting='outputDevice', value=devices) + except: + if not self.getAliceConfigByName('disableSoundAndMic'): + self.logWarning('No audio output device found') + + + @staticmethod + def _listAudioDevices() -> list: try: devices = [device['name'] for device in sd.query_devices()] if not devices: raise Exception except: - if not self.getAliceConfigByName('disableSoundAndMic'): - self.logWarning('No audio device found') + raise - self.updateAliceConfigDefinitionValues(setting='intputDevice', value=devices) + return devices @property diff --git a/core/base/SuperManager.py b/core/base/SuperManager.py index d58540731..712747913 100644 --- a/core/base/SuperManager.py +++ b/core/base/SuperManager.py @@ -118,19 +118,23 @@ def onStart(self): self._managers[nluManager.name] = nluManager self._managers[internetManager.name] = internetManager self._managers[nodeRedManager.name] = nodeRedManager - except: - pass # This happens when onStop is called while booting + except Exception as e: + import traceback + + traceback.print_exc() + Logger().logFatal(f'Error while starting managers: {e}') def onBooted(self): self.mqttManager.playSound(soundFilename='boot') + manager = None try: for manager in self._managers.values(): if manager: manager.onBooted() - except: - pass # Do nothing if manager is not ready + except Exception as e: + Logger().logError(f'Error while sending onBooted to manager **{manager.name}**: {e}') @staticmethod From fa39885bd16a5736ded607c24badf6ca67eaa22f Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:19:31 +0100 Subject: [PATCH 090/126] :bug: maybe setting the device?? --- core/server/AudioServer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 61a9783c2..71fd51604 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -51,6 +51,8 @@ def onStart(self): return else: self.logInfo(f'Using **{self._audioInput}** for audio input') + else: + self._audioInput = self.ConfigManager.getAliceConfigByName('inputDevice') if not self.ConfigManager.getAliceConfigByName('outputDevice'): self.logWarning('Output device not set in config, trying to find default device') @@ -61,6 +63,8 @@ def onStart(self): return else: self.logInfo(f'Using **{self._audioOutput}** for audio output') + else: + self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') self._stopPlayingFlag = self.ThreadManager.newEvent('stopPlaying') From 6b751d00ed6994598312b49f6bd8868cd147f78a Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:33:38 +0100 Subject: [PATCH 091/126] :fix: working solution, still 2 errors but nothing bad --- core/server/AudioServer.py | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 71fd51604..4cd6e8b09 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -49,8 +49,6 @@ def onStart(self): except: self.logFatal('Audio input not found, cannot continue') return - else: - self.logInfo(f'Using **{self._audioInput}** for audio input') else: self._audioInput = self.ConfigManager.getAliceConfigByName('inputDevice') @@ -61,11 +59,13 @@ def onStart(self): except: self.logFatal('Audio output not found, cannot continue') return - else: - self.logInfo(f'Using **{self._audioOutput}** for audio output') else: self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') + self.logInfo(f'Using **{self._audioInput}** for audio input') + self.logInfo(f'Using **{self._audioOutput}** for audio output') + + sd.default.device = self._audioOutput, self._audioInput self._stopPlayingFlag = self.ThreadManager.newEvent('stopPlaying') self.MqttManager.mqttClient.subscribe(constants.TOPIC_AUDIO_FRAME.format(self.ConfigManager.getAliceConfigByName('uuid'))) @@ -116,7 +116,6 @@ def publishAudio(self): self._audioInputStream = sd.RawInputStream( dtype='int16', channels=1, - device=self._audioInput, samplerate=self.SAMPLERATE, blocksize=self.FRAMES_PER_BUFFER, ) @@ -195,15 +194,15 @@ def streamCallback(outdata, frameCount, _timeInfo, _status): except: raise PlayBytesStopped + stream = sd.RawOutputStream( dtype='int16', channels=channels, samplerate=framerate, - device=self._audioOutput, callback=streamCallback ) - self.logDebug(f'Playing wav stream using **{self._audioOutput["name"]}** audio output from site id **{self.DeviceManager.siteIdToDeviceName(siteId)}** (channels: {channels}, rate: {framerate})') + self.logDebug(f'Playing wav stream using **{self._audioOutput}** audio output from site id **{self.DeviceManager.siteIdToDeviceName(siteId)}** (channels: {channels}, rate: {framerate})') stream.start() while stream.active: if self._stopPlayingFlag.is_set(): From 3b011595337c9a0fcdff2f9ce16632404d928397 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:41:27 +0100 Subject: [PATCH 092/126] :fix: fix audio segment length --- core/server/AudioServer.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 4cd6e8b09..4fc80fdab 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -188,12 +188,13 @@ def onPlayBytes(self, requestId: str, payload: bytearray, siteId: str, sessionId framerate = wav.getframerate() def streamCallback(outdata, frameCount, _timeInfo, _status): - try: - data = wav.readframes(frameCount) + data = wav.readframes(frameCount) + if len(data) < len(outdata): + outdata[:len(data)] = data + outdata[len(data):] = b'\x00' * (len(outdata) - len(data)) + raise sd.CallbackStop + else: outdata[:] = data - except: - raise PlayBytesStopped - stream = sd.RawOutputStream( dtype='int16', From 4975f1c81a5a28223673eff2d6c41ab5a36d9d68 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:48:02 +0100 Subject: [PATCH 093/126] :sparkles: this allows on the fly audio device change --- configTemplate.json | 2 ++ core/server/AudioServer.py | 20 +++++++++++++++----- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index 571fbed5f..4978e7389 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -57,6 +57,7 @@ "description" : "The device to use to play sounds", "category" : "audio", "onInit" : "populateAudioOutputConfig", + "onUpdate" : "AudioServer.updateAudioDevices", "parent" : { "config" : "disableSoundAndMic", "condition": "isnot", @@ -71,6 +72,7 @@ "description" : "The device to use to record sounds", "category" : "audio", "onInit" : "populateAudioInputConfig", + "onUpdate" : "AudioServer.updateAudioDevices", "parent" : { "config" : "disableSoundAndMic", "condition": "isnot", diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 4fc80fdab..926e55453 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -62,10 +62,7 @@ def onStart(self): else: self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') - self.logInfo(f'Using **{self._audioInput}** for audio input') - self.logInfo(f'Using **{self._audioOutput}** for audio output') - - sd.default.device = self._audioOutput, self._audioInput + self.setDefaults() self._stopPlayingFlag = self.ThreadManager.newEvent('stopPlaying') self.MqttManager.mqttClient.subscribe(constants.TOPIC_AUDIO_FRAME.format(self.ConfigManager.getAliceConfigByName('uuid'))) @@ -74,6 +71,13 @@ def onStart(self): self.ThreadManager.newThread(name='audioPublisher', target=self.publishAudio) + def setDefaults(self): + self.logInfo(f'Using **{self._audioInput}** for audio input') + self.logInfo(f'Using **{self._audioOutput}** for audio output') + + sd.default.device = self._audioOutput, self._audioInput + + def onStop(self): super().onStop() self._audioInputStream.stop(ignore_errors=True) @@ -238,7 +242,7 @@ def streamCallback(outdata, frameCount, _timeInfo, _status): self.MqttManager.publish( topic=constants.TOPIC_PLAY_BYTES_FINISHED.format(siteId), payload={ - 'id': requestId, + 'id' : requestId, 'sessionId': sessionId } ) @@ -248,6 +252,12 @@ def stopPlaying(self): self._stopPlayingFlag.set() + def updateAudioDevices(self): + self._audioInput = self.ConfigManager.getAliceConfigByName('inputDevice') + self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') + self.setDefaults() + + @property def isPlaying(self) -> bool: return self._playing From 6cbb04cce349cff024ba4bf0b5743f12ece0f170 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:50:28 +0100 Subject: [PATCH 094/126] :sparkles: Once we found default devices, set them as used --- core/server/AudioServer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 926e55453..165556b58 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -49,6 +49,7 @@ def onStart(self): except: self.logFatal('Audio input not found, cannot continue') return + self.ConfigManager.updateAliceConfig(key='inputDevice', value=self._audioInput) else: self._audioInput = self.ConfigManager.getAliceConfigByName('inputDevice') @@ -59,6 +60,7 @@ def onStart(self): except: self.logFatal('Audio output not found, cannot continue') return + self.ConfigManager.updateAliceConfig(key='outputDevice', value=self._audioOutput) else: self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') From 00619baed31faae17a8d9ad3dc033c421f449eb9 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:51:59 +0100 Subject: [PATCH 095/126] :fix: oops --- core/server/AudioServer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 165556b58..1ec9a70d7 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -51,7 +51,7 @@ def onStart(self): return self.ConfigManager.updateAliceConfig(key='inputDevice', value=self._audioInput) else: - self._audioInput = self.ConfigManager.getAliceConfigByName('inputDevice') + self._audioInput = self.ConfigManager.updateAliceConfiguration('inputDevice') if not self.ConfigManager.getAliceConfigByName('outputDevice'): self.logWarning('Output device not set in config, trying to find default device') @@ -62,7 +62,7 @@ def onStart(self): return self.ConfigManager.updateAliceConfig(key='outputDevice', value=self._audioOutput) else: - self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') + self._audioOutput = self.ConfigManager.updateAliceConfiguration('outputDevice') self.setDefaults() From b4a08cddd18a15991ca704b89ac0c27f30c0186b Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:54:14 +0100 Subject: [PATCH 096/126] :sparkles: all working for me --- core/server/AudioServer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 1ec9a70d7..d0a63dbad 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -49,9 +49,9 @@ def onStart(self): except: self.logFatal('Audio input not found, cannot continue') return - self.ConfigManager.updateAliceConfig(key='inputDevice', value=self._audioInput) + self.ConfigManager.updateAliceConfiguration(key='inputDevice', value=self._audioInput) else: - self._audioInput = self.ConfigManager.updateAliceConfiguration('inputDevice') + self._audioInput = self.ConfigManager.getAliceConfigByName('inputDevice') if not self.ConfigManager.getAliceConfigByName('outputDevice'): self.logWarning('Output device not set in config, trying to find default device') @@ -60,9 +60,9 @@ def onStart(self): except: self.logFatal('Audio output not found, cannot continue') return - self.ConfigManager.updateAliceConfig(key='outputDevice', value=self._audioOutput) + self.ConfigManager.updateAliceConfiguration(key='outputDevice', value=self._audioOutput) else: - self._audioOutput = self.ConfigManager.updateAliceConfiguration('outputDevice') + self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') self.setDefaults() From 916848b1a825970ccf350080e3456d3c41646eb9 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 17:57:28 +0100 Subject: [PATCH 097/126] :bug: fix CI --- requirements_test.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements_test.txt b/requirements_test.txt index 79392e84a..e9cba619e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -27,3 +27,4 @@ webrtcvad~=2.0.10 pyjwt~=1.7.1 toml~=0.10.1 markdown~=3.2.2 +sounddevice==0.4.1 From 21f3990a963389369f73c9a8df86926ae3e41c6e Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 18:07:26 +0100 Subject: [PATCH 098/126] :construction_worker: CI needs portaudio --- .github/workflows/pytest.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 86d008ca7..02409f693 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -21,9 +21,11 @@ jobs: with: python-version: 3.7 - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements_test.txt + uses: py-actions/py-dependency-install@v2 + with: + path: requirements_test.txt + - name: Install Portaudio + run: sudo apt-get install libportaudio2 - name: Tests run: | pytest tests/ --cov=core/ --cov-report=xml From f6346c229229770dab805a7633181398e27a66d5 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 18:10:14 +0100 Subject: [PATCH 099/126] :construction_worker: In your face Github! --- .github/workflows/pytest.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 02409f693..2540e0f81 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -27,11 +27,9 @@ jobs: - name: Install Portaudio run: sudo apt-get install libportaudio2 - name: Tests - run: | - pytest tests/ --cov=core/ --cov-report=xml + run: pytest tests/ --cov=core/ --cov-report=xml - name: Fix paths - run: | - sed -i 's/\/home\/runner\/work\/ProjectAlice\/ProjectAlice\//\/github\/workspace\//g' coverage.xml + run: sed -i 's/\/home\/runner\/work\/ProjectAlice\/ProjectAlice\//\/github\/workspace\//g' coverage.xml - name: Sonarcloud scan uses: sonarsource/sonarcloud-github-action@master env: From 1eb5223641c93a2193dee3643ba7de9e27300188 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 18:14:31 +0100 Subject: [PATCH 100/126] :art: added badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f07337e43..fe1363ccb 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@

    License Discord
    + Tests Coverage Status Maintainability Code Smells From 745ed9a3df0c71eb4c2f6cc6e03d6a838c686e1c Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 18:15:22 +0100 Subject: [PATCH 101/126] :lipstick: looks better --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fe1363ccb..80403e224 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

    License Discord
    - Tests + Tests
    Coverage Status Maintainability Code Smells From dd624d867244262cacf92ee23f11ffa173c2a9d1 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 18:16:58 +0100 Subject: [PATCH 102/126] :lipstick: sounds better --- .github/workflows/pytest.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 2540e0f81..b980817df 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies and run tests with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Pytest and Sonarcloud analysis +name: Unittest & Quality on: push: From d16a547aa9b81c7fefb9c0d37b730dbdb92df8b9 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 18:18:01 +0100 Subject: [PATCH 103/126] :bug: readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80403e224..46762fd80 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

    License Discord
    - Tests
    + Tests
    Coverage Status Maintainability Code Smells From f37d22edc6915d5bc67cc5aadb1bd63d134326e1 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 30 Oct 2020 18:19:25 +0100 Subject: [PATCH 104/126] :art: link on badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 46762fd80..02e3be7ef 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@

    License Discord
    - Tests
    + Tests
    Coverage Status Maintainability Code Smells From ac0b563e3b073910641b810bec48f5040d7c471c Mon Sep 17 00:00:00 2001 From: LazzaAU Date: Sat, 31 Oct 2020 15:14:39 +1000 Subject: [PATCH 105/126] Start on unit tests --- .../test_OvenTemperatureConversion.py | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 tests/OvenTemperatureConversion/test_OvenTemperatureConversion.py diff --git a/tests/OvenTemperatureConversion/test_OvenTemperatureConversion.py b/tests/OvenTemperatureConversion/test_OvenTemperatureConversion.py new file mode 100644 index 000000000..6643b9024 --- /dev/null +++ b/tests/OvenTemperatureConversion/test_OvenTemperatureConversion.py @@ -0,0 +1,42 @@ +import unittest +from unittest import TestCase +from unittest import mock +from unittest.mock import MagicMock +from skills.OvenTemperatureConversion.OvenTemperatureConversion import OvenTemperatureConversion + + +class TestOvenTemperatureConversion(TestCase): + + def test_convertingToCelciusIntent(self): + pass # To be implemented or nothing to test() + + + def test_convertingToFahrenheitIntent(self): + pass # To be implemented or nothing to test() + + + def test_readyToConvert(self): + pass # To be implemented or nothing to test() + + + def test_askToRepeatWithNumber(self): + pass # To be implemented or nothing to test() + + + # todo Psycho please attempt this one so i can see how to do methods with dialog session etc + def test_gasMarkIntent(self): + pass # To be implemented or nothing to test() + + + def test_convertToFahrenheit(self): + result = OvenTemperatureConversion.convertToFahrenheit(temperature=0) + self.assertEquals(first=result, second=32) + + + def test_convertToCelsius(self): + result = OvenTemperatureConversion.convertToCelsius(temperature=32) + self.assertEquals(first=result, second=0) + + +if __name__ == '__main__': + unittest.main() From 8e5f96f9e936f1e9c0f1604a3cd3193319d1fb02 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 31 Oct 2020 19:52:40 +0100 Subject: [PATCH 106/126] :art: always fail on internet check, whatever the exception is --- core/util/InternetManager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/core/util/InternetManager.py b/core/util/InternetManager.py index 4dfeac28a..31201596d 100644 --- a/core/util/InternetManager.py +++ b/core/util/InternetManager.py @@ -1,5 +1,4 @@ import requests -from requests.exceptions import RequestException from core.base.model.Manager import Manager from core.commons import constants @@ -47,7 +46,7 @@ def checkOnlineState(self, addr: str = 'https://clients3.google.com/generate_204 try: online = requests.get(addr).status_code == 204 - except RequestException: + except: online = False if silent: From aaca1a0a9dd3ef324b14df680bfe5afafe12d489 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sun, 1 Nov 2020 05:47:20 +0100 Subject: [PATCH 107/126] :bug: fixed mixed input output --- core/server/AudioServer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index d0a63dbad..1f56d5b5a 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -77,7 +77,7 @@ def setDefaults(self): self.logInfo(f'Using **{self._audioInput}** for audio input') self.logInfo(f'Using **{self._audioOutput}** for audio output') - sd.default.device = self._audioOutput, self._audioInput + sd.default.device = self._audioInput, self._audioOutput def onStop(self): From 35a96f2fc775e58fe869480dd73781ab8f1ba859 Mon Sep 17 00:00:00 2001 From: LazzaAU Date: Mon, 2 Nov 2020 07:54:45 +1000 Subject: [PATCH 108/126] UnitTest for QvenTemperature --- .../test_OvenTemperatureConversion.py | 42 ------------------- 1 file changed, 42 deletions(-) delete mode 100644 tests/OvenTemperatureConversion/test_OvenTemperatureConversion.py diff --git a/tests/OvenTemperatureConversion/test_OvenTemperatureConversion.py b/tests/OvenTemperatureConversion/test_OvenTemperatureConversion.py deleted file mode 100644 index 6643b9024..000000000 --- a/tests/OvenTemperatureConversion/test_OvenTemperatureConversion.py +++ /dev/null @@ -1,42 +0,0 @@ -import unittest -from unittest import TestCase -from unittest import mock -from unittest.mock import MagicMock -from skills.OvenTemperatureConversion.OvenTemperatureConversion import OvenTemperatureConversion - - -class TestOvenTemperatureConversion(TestCase): - - def test_convertingToCelciusIntent(self): - pass # To be implemented or nothing to test() - - - def test_convertingToFahrenheitIntent(self): - pass # To be implemented or nothing to test() - - - def test_readyToConvert(self): - pass # To be implemented or nothing to test() - - - def test_askToRepeatWithNumber(self): - pass # To be implemented or nothing to test() - - - # todo Psycho please attempt this one so i can see how to do methods with dialog session etc - def test_gasMarkIntent(self): - pass # To be implemented or nothing to test() - - - def test_convertToFahrenheit(self): - result = OvenTemperatureConversion.convertToFahrenheit(temperature=0) - self.assertEquals(first=result, second=32) - - - def test_convertToCelsius(self): - result = OvenTemperatureConversion.convertToCelsius(temperature=32) - self.assertEquals(first=result, second=0) - - -if __name__ == '__main__': - unittest.main() From c35aa009a37209f97f4aac0adc7fd3a225be6c31 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 2 Nov 2020 18:48:00 +0100 Subject: [PATCH 109/126] :art: zindexer size smaller, reformatted common.js f'ed up indentation --- core/interface/static/css/projectalice.css | 2 +- core/interface/static/js/common.js | 135 +++++++++++---------- 2 files changed, 70 insertions(+), 67 deletions(-) diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index 0310577a4..5c2da7b96 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -105,7 +105,7 @@ button.button{ font-size: 1.6em; height: 2em; } .toolbarButton { font-size: 2em; padding-right: .5em; min-width: .9em; text-align: center; } -.zindexer { position: absolute; top: 5px; left: 5px; background-color: var(--mainBG); width: 50px; border-radius: 25px; text-align: center; color: var(--text); z-index:99; } +.zindexer { position: absolute; top: 5px; left: 5px; background-color: var(--mainBG); width: 36px; border-radius: 18px; text-align: center; color: var(--text); z-index: 99; } .zindexer .clickable:first-child { margin-bottom: 5px; } diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 19271e97a..545143219 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -3,13 +3,13 @@ let mqttSubscribers = {}; let MQTT_HOST; let MQTT_PORT; let LAST_CORE_HEARTBEAT = 0; -let MAIN_GOING_DOWN = false +let MAIN_GOING_DOWN = false; function getCookie(cname) { let name = cname + '='; let decodedCookie = decodeURIComponent(document.cookie); let ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i++) { + for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); @@ -39,7 +39,9 @@ $(function () { function onFailure(_msg) { console.log('Mqtt connection failed, retry in 5 seconds'); - setTimeout(function() { connectMqtt(); }, 5000); + setTimeout(function () { + connectMqtt(); + }, 5000); } function onConnect(msg) { @@ -52,7 +54,7 @@ $(function () { } function onConnectionLost(resObj) { - console.log('Mqtt disconnected, automatic reconnect is enabled Error code: ' + resObj.errorCode +' - '+ resObj.errorMessage ); + console.log('Mqtt disconnected, automatic reconnect is enabled Error code: ' + resObj.errorCode + ' - ' + resObj.errorMessage); connectMqtt(); } @@ -79,7 +81,7 @@ $(function () { MQTT_HOST = window.location.hostname; } let randomNum = Math.floor((Math.random() * 10000000) + 1); - MQTT = new Paho.MQTT.Client(MQTT_HOST, MQTT_PORT, 'ProjectAliceInterface'+randomNum); + MQTT = new Paho.MQTT.Client(MQTT_HOST, MQTT_PORT, 'ProjectAliceInterface' + randomNum); MQTT.onMessageArrived = onMessage; MQTT.onConnectionLost = onConnectionLost; MQTT.connect({ @@ -88,12 +90,16 @@ $(function () { timeout : 5 }); } else { - console.log('Failed fetching MQTT settings') - setTimeout(function() { connectMqtt(); }, 5000); + console.log('Failed fetching MQTT settings'); + setTimeout(function () { + connectMqtt(); + }, 5000); } }).fail(function () { - console.log("Coulnd't get MQTT information") - setTimeout(function() { connectMqtt(); }, 5000); + console.log('Coulnd\'t get MQTT information'); + setTimeout(function () { + connectMqtt(); + }, 5000); }); } @@ -127,17 +133,14 @@ $(function () { } else if (payload.status == 'done') { $container.text('Nlu training done!'); } - } - else if (msg.topic == 'projectalice/skills/instructions') { + } else if (msg.topic == 'projectalice/skills/instructions') { let payload = JSON.parse(msg.payloadString); $('#skillInstructions').show(); let $content = $('#skillInstructionsContent'); $content.html($content.html() + payload['instructions']); - } - else if (msg.topic == 'projectalice/devices/coreHeartbeat') { + } else if (msg.topic == 'projectalice/devices/coreHeartbeat') { LAST_CORE_HEARTBEAT = Date.now(); - } - else if (msg.topic == 'projectalice/skills/coreConfigUpdateWarning') { + } else if (msg.topic == 'projectalice/skills/coreConfigUpdateWarning') { $('#serverUnavailable').hide(); let $nodal = $('#coreConfigUpdateAlert'); $nodal.show(); @@ -146,12 +149,12 @@ $(function () { let $container = $('#overlaySkillContent'); if ($container.children().length <= 0) { - $container.append('

    ' + payload['skill'] + '
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); + $container.append('
    ' + payload['skill'] + '
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); } else { - let $skillWarning = $('#confWarning_' + payload["skill"]); + let $skillWarning = $('#confWarning_' + payload['skill']); if ($skillWarning.length == 0) { - $container.append('
    ' + payload['skill'] + '
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); + $container.append('
    ' + payload['skill'] + '
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); } else { $skillWarning.append('
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); } @@ -249,69 +252,69 @@ $(function () { // Z-indexers let $zindexed = $('.z-indexed'); if ($zindexed.length) { - zIndexMe($zindexed) + zIndexMe($zindexed); } }); - function reorder($arrow, direction) { - let $widget = $arrow.parent().parent(); - let $container = $widget.parent(); - let $family = $container.children('.z-indexed'); - - let actualIndex = $widget.css('z-index'); - try { - actualIndex = parseInt(actualIndex); - } catch { - actualIndex = 0; - } +function reorder($arrow, direction) { + let $widget = $arrow.parent().parent(); + let $container = $widget.parent(); + let $family = $container.children('.z-indexed'); - let toIndex; - if (direction === 'down') { - toIndex = Math.max(0, actualIndex - 1); - } else { - let highest = 0; - $family.each(function(){ - try { - if (parseInt($(this).css('z-index')) > highest) { - highest = parseInt($(this).css('z-index')); - } - } catch { - return true; - } - }); - toIndex = actualIndex + 1; - if (toIndex > highest) { - return; - } - } + let actualIndex = $widget.css('z-index'); + try { + actualIndex = parseInt(actualIndex); + } catch { + actualIndex = 0; + } - $family.each(function(){ + let toIndex; + if (direction === 'down') { + toIndex = Math.max(0, actualIndex - 1); + } else { + let highest = 0; + $family.each(function () { try { - if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { - $(this).css('z-index', actualIndex); + if (parseInt($(this).css('z-index')) > highest) { + highest = parseInt($(this).css('z-index')); } } catch { return true; } }); - $widget.css('z-index', toIndex); + toIndex = actualIndex + 1; + if (toIndex > highest) { + return; + } } + $family.each(function () { + try { + if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { + $(this).css('z-index', actualIndex); + } + } catch { + return true; + } + }); + $widget.css('z-index', toIndex); +} -function zIndexMe($zindexed){ - let $indexUp = $('
    '); - let $indexDown = $('
    '); - $indexUp.on('click touch', function () { - reorder($(this), 'up'); - }); - $indexDown.on('click touch', function () { - reorder($(this), 'down'); - }); +function zIndexMe($zindexed) { + let $indexUp = $('
    '); + let $indexDown = $('
    '); + + $indexUp.on('click touch', function () { + reorder($(this), 'up'); + }); + $indexDown.on('click touch', function () { + reorder($(this), 'down'); + }); - let $zindexer = $('
    '); - $zindexer.append($indexUp); - $zindexer.append($indexDown); + let $zindexer = $('
    '); + $zindexer.append($indexUp); + $zindexer.append($indexDown); - $zindexed.append($zindexer); + $zindexed.append($zindexer); } From 74aaf054529dfe70987c232d0ae36dc8eba04572 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 2 Nov 2020 18:49:39 +0100 Subject: [PATCH 110/126] :bug: fixed wrong condition for asound file --- configTemplate.json | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/configTemplate.json b/configTemplate.json index 4978e7389..476215036 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -36,10 +36,10 @@ "description" : "Your asound settings", "beforeUpdate": "injectAsound", "category" : "audio", - "parent": { - "config": "disableSoundAndMic", + "parent" : { + "config" : "disableSoundAndMic", "condition": "isnot", - "value": false + "value" : true } }, "recordAudioAfterWakeword": { @@ -138,7 +138,7 @@ "value" : false } }, - "disableSoundAndMic" : { + "disableSoundAndMic" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, @@ -146,21 +146,21 @@ "onUpdate" : "enableDisableSound", "category" : "audio" }, - "notUnderstoodRetries": { + "notUnderstoodRetries" : { "defaultValue": 3, "dataType" : "integer", "isSensitive" : false, "description" : "Defines how many times Alice will ask to repeat if not understood before she gives up", "category" : "system" }, - "ssid" : { + "ssid" : { "defaultValue": "", "dataType" : "string", "isSensitive" : false, "description" : "Your Wifi name", "category" : "device" }, - "debug" : { + "debug" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, @@ -168,14 +168,14 @@ "onUpdate" : "toggleDebugLogs", "category" : "system" }, - "advancedDebug" : { + "advancedDebug" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, "description" : "If true advanced debugging will be enabled. This activates extra information and tools for debugging.", "category" : "advanced debug" }, - "memoryProfiling" : { + "memoryProfiling" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, @@ -187,7 +187,7 @@ "value" : true } }, - "databaseProfiling" : { + "databaseProfiling" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, @@ -199,14 +199,14 @@ "value" : true } }, - "wifipassword" : { + "wifipassword" : { "defaultValue": "", "dataType" : "string", "isSensitive" : true, "description" : "Your Wifi password", "category" : "device" }, - "mqttHost" : { + "mqttHost" : { "defaultValue": "localhost", "dataType" : "string", "isSensitive" : false, @@ -214,7 +214,7 @@ "onUpdate" : "updateMqttSettings", "category" : "mqtt" }, - "mqttPort" : { + "mqttPort" : { "defaultValue": 1883, "dataType" : "integer", "isSensitive" : false, @@ -793,7 +793,7 @@ "value" : false } }, - "internetQuality" : { + "internetQuality" : { "defaultValue": 10, "dataType" : "range", "min" : 1, From 20b53a7903eb4e6e3498ddaed8bac2522b5297e5 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 2 Nov 2020 18:54:31 +0100 Subject: [PATCH 111/126] :bug: zindexers should not display in settings mode --- core/interface/static/js/widgets.js | 1 + 1 file changed, 1 insertion(+) diff --git a/core/interface/static/js/widgets.js b/core/interface/static/js/widgets.js index c76110d5d..66de41384 100644 --- a/core/interface/static/js/widgets.js +++ b/core/interface/static/js/widgets.js @@ -95,6 +95,7 @@ $(function () { $('#configToggle').on('click touch', function () { $('.widgetConfig').show(); + $('.zindexer').hide(); $('#toolbar_checkmark').show(); $('#toolbar_full').hide(); return false; From 909daa842822ff882cd5b6724e56ebe10934a466 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 2 Nov 2020 18:59:17 +0100 Subject: [PATCH 112/126] :bug: widgets should be saved when one clicks check mark from widget settings --- core/interface/static/js/widgets.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/interface/static/js/widgets.js b/core/interface/static/js/widgets.js index 66de41384..f9eb7204b 100644 --- a/core/interface/static/js/widgets.js +++ b/core/interface/static/js/widgets.js @@ -32,7 +32,11 @@ $(function () { widget.draggable('disable'); widget.resizable('disable'); $('.zindexer').hide(); + saveWidgets(); + }); + function saveWidgets() { + let widget = $('.widget'); let data = {}; widget.each(function () { data[$(this).attr('id')] = { @@ -52,7 +56,7 @@ $(function () { dataType : 'json', type : 'POST' }); - }); + } $('.widgetOptions').dialog({ autoOpen : false, @@ -116,6 +120,9 @@ $(function () { $('#toolbar_checkmark').hide(); $('.widgetDelete').hide(); $('.widgetConfig').hide(); + + saveWidgets(); + location.reload(); return false; }); From 9616ab39b5f1b25b31e2cfa9ab897bd1df876e70 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 2 Nov 2020 19:22:05 +0100 Subject: [PATCH 113/126] :art: adding zenhub badge --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 02e3be7ef..5d492ba86 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,8 @@

    License - Discord
    + Discord + ZenHub logo
    Tests
    Coverage Status Maintainability From 796abb0abd69e3d3b8f5424cba32b94e6871b473 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 2 Nov 2020 19:23:12 +0100 Subject: [PATCH 114/126] :art: :lipstick: nicer? --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5d492ba86..2d212e38d 100644 --- a/README.md +++ b/README.md @@ -5,9 +5,9 @@

    License - Discord + Discord
    + Tests ZenHub logo
    - Tests
    Coverage Status Maintainability Code Smells From b2806a65d347fb634ffc0a64dbfaeb5887d3c009 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 2 Nov 2020 19:24:51 +0100 Subject: [PATCH 115/126] :lipstick: :art: css --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 2d212e38d..a01c11401 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -

    +

    Project Alice Logo
    @ProjectAlice/docs

    -

    +

    License Discord
    Tests From 41c5ab7f466c4c6258861aa04bcb7c92b1e1a9a9 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Mon, 2 Nov 2020 19:25:40 +0100 Subject: [PATCH 116/126] :bug: not working --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a01c11401..2d212e38d 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -

    +

    Project Alice Logo
    @ProjectAlice/docs

    -

    +

    License Discord
    Tests From e5da5108d562c9a4d8b5b1af48888458203bd216 Mon Sep 17 00:00:00 2001 From: LazzaAU Date: Wed, 4 Nov 2020 09:37:32 +1000 Subject: [PATCH 117/126] Change typo of eu-us to en-us --- core/asr/model/PocketSphinxAsr.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/asr/model/PocketSphinxAsr.py b/core/asr/model/PocketSphinxAsr.py index 87b7cc41f..35dfd8252 100644 --- a/core/asr/model/PocketSphinxAsr.py +++ b/core/asr/model/PocketSphinxAsr.py @@ -89,7 +89,7 @@ def downloadLanguage(self, forceLang: str = '') -> bool: return False else: # TODO be universal - self.downloadLanguage(forceLang='eu-US') + self.downloadLanguage(forceLang='en-US') else: if download.suffix == '.tar': dest = Path(venv, 'model', lang.lower()) From a2f0a1c6d4aba8a94d019f3ee83a31be40a9ec97 Mon Sep 17 00:00:00 2001 From: lazza <31381872+LazzaAU@users.noreply.github.com> Date: Wed, 4 Nov 2020 09:41:21 +1000 Subject: [PATCH 118/126] 1.0.0 b4 (#2) * :construction: experimenting with new audio package * :construction: publishing works * :construction: not sure how to play streams with sounddevice, will check later * :construction: for me it's working here on respeaker 2 * :construction: force using system default device * :construction: New configs for audio devices, WIP * :construction: Settings for audio devices and upgraded audio server * :construction: don't revert inited configs * :bug: fix * :bug: fix default audio hardware selection * :bug: fix logic * :bug: fixes * :bug: maybe setting the device?? * :fix: working solution, still 2 errors but nothing bad * :fix: fix audio segment length * :sparkles: this allows on the fly audio device change * :sparkles: Once we found default devices, set them as used * :fix: oops * :sparkles: all working for me * :bug: fix CI * :construction_worker: CI needs portaudio * :construction_worker: In your face Github! * :art: added badge * :lipstick: looks better * :lipstick: sounds better * :bug: readme * :art: link on badge * :art: always fail on internet check, whatever the exception is * :bug: fixed mixed input output * :art: zindexer size smaller, reformatted common.js f'ed up indentation * :bug: fixed wrong condition for asound file * :bug: zindexers should not display in settings mode * :bug: widgets should be saved when one clicks check mark from widget settings * :art: adding zenhub badge * :art: :lipstick: nicer? * :lipstick: :art: css * :bug: not working Co-authored-by: Psychokiller1888 --- .github/workflows/pytest.yml | 16 +-- README.md | 2 + config-schema.json | 12 +- configTemplate.json | 63 +++++++-- core/base/ConfigManager.py | 83 +++++++++++- core/base/SuperManager.py | 147 +++++++++++---------- core/interface/static/css/projectalice.css | 2 +- core/interface/static/js/common.js | 135 ++++++++++--------- core/interface/static/js/widgets.js | 10 +- core/server/AudioServer.py | 114 +++++++++------- core/util/InternetManager.py | 3 +- requirements.txt | 1 + requirements_test.txt | 1 + 13 files changed, 378 insertions(+), 211 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 86d008ca7..b980817df 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,7 +1,7 @@ # This workflow will install Python dependencies and run tests with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Pytest and Sonarcloud analysis +name: Unittest & Quality on: push: @@ -21,15 +21,15 @@ jobs: with: python-version: 3.7 - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements_test.txt + uses: py-actions/py-dependency-install@v2 + with: + path: requirements_test.txt + - name: Install Portaudio + run: sudo apt-get install libportaudio2 - name: Tests - run: | - pytest tests/ --cov=core/ --cov-report=xml + run: pytest tests/ --cov=core/ --cov-report=xml - name: Fix paths - run: | - sed -i 's/\/home\/runner\/work\/ProjectAlice\/ProjectAlice\//\/github\/workspace\//g' coverage.xml + run: sed -i 's/\/home\/runner\/work\/ProjectAlice\/ProjectAlice\//\/github\/workspace\//g' coverage.xml - name: Sonarcloud scan uses: sonarsource/sonarcloud-github-action@master env: diff --git a/README.md b/README.md index f07337e43..2d212e38d 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@

    License Discord
    + Tests + ZenHub logo
    Coverage Status Maintainability Code Smells diff --git a/config-schema.json b/config-schema.json index 59652dbf3..fe8be1335 100644 --- a/config-schema.json +++ b/config-schema.json @@ -44,7 +44,9 @@ "mqtt", "tts", "wakeword", - "advanced debug" + "advanced debug", + "audio", + "scenarios" ] }, "isSensitive" : { @@ -72,6 +74,14 @@ "type" : "string", "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$" }, + "onStart" : { + "type" : "string", + "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$" + }, + "onInit" : { + "type" : "string", + "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$" + }, "parent" : { "type" : "object", "required" : [ diff --git a/configTemplate.json b/configTemplate.json index 20ad0bdb3..476215036 100644 --- a/configTemplate.json +++ b/configTemplate.json @@ -35,14 +35,49 @@ "isSensitive" : false, "description" : "Your asound settings", "beforeUpdate": "injectAsound", - "category" : "system" + "category" : "audio", + "parent" : { + "config" : "disableSoundAndMic", + "condition": "isnot", + "value" : true + } }, "recordAudioAfterWakeword": { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, "description" : "Allow audio record after a wakeword is detected to keep the last user speech. Can be usefull for recording skills", - "category" : "system" + "category" : "audio" + }, + "outputDevice" : { + "defaultValue": "", + "dataType" : "list", + "isSensitive" : false, + "values" : [], + "description" : "The device to use to play sounds", + "category" : "audio", + "onInit" : "populateAudioOutputConfig", + "onUpdate" : "AudioServer.updateAudioDevices", + "parent" : { + "config" : "disableSoundAndMic", + "condition": "isnot", + "value" : true + } + }, + "inputDevice" : { + "defaultValue": "", + "dataType" : "list", + "isSensitive" : false, + "values" : [], + "description" : "The device to use to record sounds", + "category" : "audio", + "onInit" : "populateAudioInputConfig", + "onUpdate" : "AudioServer.updateAudioDevices", + "parent" : { + "config" : "disableSoundAndMic", + "condition": "isnot", + "value" : true + } }, "deviceName" : { "defaultValue": "default", @@ -103,29 +138,29 @@ "value" : false } }, - "disableSoundAndMic" : { + "disableSoundAndMic" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, "description" : "If this device is a server without sound and mic, turn this to true", "onUpdate" : "enableDisableSound", - "category" : "device" + "category" : "audio" }, - "notUnderstoodRetries": { + "notUnderstoodRetries" : { "defaultValue": 3, "dataType" : "integer", "isSensitive" : false, "description" : "Defines how many times Alice will ask to repeat if not understood before she gives up", "category" : "system" }, - "ssid" : { + "ssid" : { "defaultValue": "", "dataType" : "string", "isSensitive" : false, "description" : "Your Wifi name", "category" : "device" }, - "debug" : { + "debug" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, @@ -133,14 +168,14 @@ "onUpdate" : "toggleDebugLogs", "category" : "system" }, - "advancedDebug" : { + "advancedDebug" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, "description" : "If true advanced debugging will be enabled. This activates extra information and tools for debugging.", "category" : "advanced debug" }, - "memoryProfiling" : { + "memoryProfiling" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, @@ -152,7 +187,7 @@ "value" : true } }, - "databaseProfiling" : { + "databaseProfiling" : { "defaultValue": false, "dataType" : "boolean", "isSensitive" : false, @@ -164,14 +199,14 @@ "value" : true } }, - "wifipassword" : { + "wifipassword" : { "defaultValue": "", "dataType" : "string", "isSensitive" : true, "description" : "Your Wifi password", "category" : "device" }, - "mqttHost" : { + "mqttHost" : { "defaultValue": "localhost", "dataType" : "string", "isSensitive" : false, @@ -179,7 +214,7 @@ "onUpdate" : "updateMqttSettings", "category" : "mqtt" }, - "mqttPort" : { + "mqttPort" : { "defaultValue": 1883, "dataType" : "integer", "isSensitive" : false, @@ -758,7 +793,7 @@ "value" : false } }, - "internetQuality" : { + "internetQuality" : { "defaultValue": 10, "dataType" : "range", "min" : 1, diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py index 155ae5edd..f132e6914 100644 --- a/core/base/ConfigManager.py +++ b/core/base/ConfigManager.py @@ -4,6 +4,7 @@ import typing from pathlib import Path +import sounddevice as sd import toml from core.ProjectAliceExceptions import ConfigurationUpdateFailed, VitalConfigMissing @@ -44,6 +45,21 @@ def onStart(self): if conf not in self._aliceConfigurations or self._aliceConfigurations[conf] == '': raise VitalConfigMissing(conf) + for setting, definition in {**self._aliceTemplateConfigurations, **self._skillsTemplateConfigurations}.items(): + function = definition.get('onStart', None) + if function: + try: + if '.' in function: + self.logWarning(f'Use of manager for configuration **onStart** for config "{setting}" is not allowed') + function = function.split('.')[-1] + + func = getattr(self, function) + func() + except AttributeError: + self.logWarning(f'Configuration onStart method **{function}** does not exist') + except Exception as e: + self.logError(f'Configuration onStart method **{function}** failed: {e}') + def _loadCheckAndUpdateAliceConfigFile(self): self.logInfo('Checking Alice configuration file') @@ -82,7 +98,7 @@ def _loadCheckAndUpdateAliceConfigFile(self): if setting == 'supportedLanguages': continue - if definition['dataType'] != 'list' and definition['dataType'] != 'longstring': + if definition['dataType'] != 'list' and definition['dataType'] != 'longstring' and 'onInit' not in definition: if not isinstance(aliceConfigs[setting], type(definition['defaultValue'])): changes = True try: @@ -93,14 +109,29 @@ def _loadCheckAndUpdateAliceConfigFile(self): # If casting failed let's fall back to the new default value self.logWarning(f'Existing configuration type missmatch: **{setting}**, replaced with template configuration') aliceConfigs[setting] = definition['defaultValue'] - elif definition['dataType'] == 'list': + elif definition['dataType'] == 'list' and 'onInit' not in definition: values = definition['values'].values() if isinstance(definition['values'], dict) else definition['values'] - if aliceConfigs[setting] not in values: + if aliceConfigs[setting] and aliceConfigs[setting] not in values: changes = True self.logWarning(f'Selected value **{aliceConfigs[setting]}** for setting **{setting}** doesn\'t exist, reverted to default value --{definition["defaultValue"]}--') aliceConfigs[setting] = definition['defaultValue'] + function = definition.get('onInit', None) + if function: + try: + if '.' in function: + self.logWarning(f'Use of manager for configuration **onInit** for config "{setting}" is not allowed') + function = function.split('.')[-1] + + func = getattr(self, function) + func() + except AttributeError: + self.logWarning(f'Configuration onInit method **{function}** does not exist') + except Exception as e: + self.logError(f'Configuration onInit method **{function}** failed: {e}') + + # Setting logger level immediately if aliceConfigs['advancedDebug'] and not aliceConfigs['debug']: aliceConfigs['debug'] = True @@ -122,6 +153,14 @@ def _loadCheckAndUpdateAliceConfigFile(self): self._aliceConfigurations = aliceConfigs + def updateAliceConfigDefinitionValues(self, setting: str, value: typing.Any): + if setting not in self._aliceTemplateConfigurations: + self.logWarning(f'Was asked to update **{setting}** from config templates, but setting doesn\'t exist') + return + + self._aliceTemplateConfigurations[setting]['values'] = value + + @staticmethod def loadJsonFromFile(jsonFile: Path) -> dict: try: @@ -377,6 +416,14 @@ def getAliceConfigByName(self, configName: str) -> typing.Any: return '' + def getAliceConfigTemplateByName(self, configName: str) -> typing.Any: + if configName in self._aliceTemplateConfigurations: + return self._aliceTemplateConfigurations[configName] + else: + self.logDebug(f'Trying to get config template **{configName}** but it does not exist') + return '' + + def getSkillConfigByName(self, skillName: str, configName: str) -> typing.Any: return self._skillsConfigurations.get(skillName, dict()).get(configName, None) @@ -654,6 +701,36 @@ def getGithubAuth(self) -> tuple: return (username, token) if (username and token) else None + def populateAudioInputConfig(self): + try: + devices = self._listAudioDevices() + self.updateAliceConfigDefinitionValues(setting='inputDevice', value=devices) + except: + if not self.getAliceConfigByName('disableSoundAndMic'): + self.logWarning('No audio input device found') + + + def populateAudioOutputConfig(self): + try: + devices = self._listAudioDevices() + self.updateAliceConfigDefinitionValues(setting='outputDevice', value=devices) + except: + if not self.getAliceConfigByName('disableSoundAndMic'): + self.logWarning('No audio output device found') + + + @staticmethod + def _listAudioDevices() -> list: + try: + devices = [device['name'] for device in sd.query_devices()] + if not devices: + raise Exception + except: + raise + + return devices + + @property def snipsConfigurations(self) -> dict: return self._snipsConfigurations diff --git a/core/base/SuperManager.py b/core/base/SuperManager.py index c1c990447..712747913 100644 --- a/core/base/SuperManager.py +++ b/core/base/SuperManager.py @@ -54,77 +54,87 @@ def __init__(self, mainClass): def onStart(self): - commons = self._managers.pop('CommonsManager') - commons.onStart() - - configManager = self._managers.pop('ConfigManager') - configManager.onStart() - - languageManager = self._managers.pop('LanguageManager') - languageManager.onStart() - - locationManager = self._managers.pop('LocationManager') - locationManager.onStart() - - deviceManager = self._managers.pop('DeviceManager') - deviceManager.onStart() - - audioServer = self._managers.pop('AudioManager') - audioServer.onStart() - - internetManager = self._managers.pop('InternetManager') - internetManager.onStart() - - databaseManager = self._managers.pop('DatabaseManager') - databaseManager.onStart() - - userManager = self._managers.pop('UserManager') - userManager.onStart() - - mqttManager = self._managers.pop('MqttManager') - mqttManager.onStart() - - talkManager = self._managers.pop('TalkManager') - skillManager = self._managers.pop('SkillManager') - assistantManager = self._managers.pop('AssistantManager') - dialogTemplateManager = self._managers.pop('DialogTemplateManager') - nluManager = self._managers.pop('NluManager') - nodeRedManager = self._managers.pop('NodeRedManager') - - for manager in self._managers.values(): - if manager: - manager.onStart() - - talkManager.onStart() - nluManager.onStart() - skillManager.onStart() - dialogTemplateManager.onStart() - assistantManager.onStart() - nodeRedManager.onStart() - - self._managers[configManager.name] = configManager - self._managers[audioServer.name] = audioServer - self._managers[languageManager.name] = languageManager - self._managers[locationManager.name] = locationManager - self._managers[deviceManager.name] = deviceManager - self._managers[talkManager.name] = talkManager - self._managers[databaseManager.name] = databaseManager - self._managers[userManager.name] = userManager - self._managers[mqttManager.name] = mqttManager - self._managers[skillManager.name] = skillManager - self._managers[dialogTemplateManager.name] = dialogTemplateManager - self._managers[assistantManager.name] = assistantManager - self._managers[nluManager.name] = nluManager - self._managers[internetManager.name] = internetManager - self._managers[nodeRedManager.name] = nodeRedManager + try: + commons = self._managers.pop('CommonsManager') + commons.onStart() + + configManager = self._managers.pop('ConfigManager') + configManager.onStart() + + languageManager = self._managers.pop('LanguageManager') + languageManager.onStart() + + locationManager = self._managers.pop('LocationManager') + locationManager.onStart() + + deviceManager = self._managers.pop('DeviceManager') + deviceManager.onStart() + + audioServer = self._managers.pop('AudioManager') + audioServer.onStart() + + internetManager = self._managers.pop('InternetManager') + internetManager.onStart() + + databaseManager = self._managers.pop('DatabaseManager') + databaseManager.onStart() + + userManager = self._managers.pop('UserManager') + userManager.onStart() + + mqttManager = self._managers.pop('MqttManager') + mqttManager.onStart() + + talkManager = self._managers.pop('TalkManager') + skillManager = self._managers.pop('SkillManager') + assistantManager = self._managers.pop('AssistantManager') + dialogTemplateManager = self._managers.pop('DialogTemplateManager') + nluManager = self._managers.pop('NluManager') + nodeRedManager = self._managers.pop('NodeRedManager') + + for manager in self._managers.values(): + if manager: + manager.onStart() + + talkManager.onStart() + nluManager.onStart() + skillManager.onStart() + dialogTemplateManager.onStart() + assistantManager.onStart() + nodeRedManager.onStart() + + self._managers[configManager.name] = configManager + self._managers[audioServer.name] = audioServer + self._managers[languageManager.name] = languageManager + self._managers[locationManager.name] = locationManager + self._managers[deviceManager.name] = deviceManager + self._managers[talkManager.name] = talkManager + self._managers[databaseManager.name] = databaseManager + self._managers[userManager.name] = userManager + self._managers[mqttManager.name] = mqttManager + self._managers[skillManager.name] = skillManager + self._managers[dialogTemplateManager.name] = dialogTemplateManager + self._managers[assistantManager.name] = assistantManager + self._managers[nluManager.name] = nluManager + self._managers[internetManager.name] = internetManager + self._managers[nodeRedManager.name] = nodeRedManager + except Exception as e: + import traceback + + traceback.print_exc() + Logger().logFatal(f'Error while starting managers: {e}') def onBooted(self): self.mqttManager.playSound(soundFilename='boot') - for manager in self._managers.values(): - if manager: - manager.onBooted() + manager = None + try: + for manager in self._managers.values(): + if manager: + manager.onBooted() + except Exception as e: + Logger().logError(f'Error while sending onBooted to manager **{manager.name}**: {e}') @staticmethod @@ -197,8 +207,9 @@ def initManagers(self): def onStop(self): managerName = constants.UNKNOWN_MANAGER - mqttManager = self._managers.pop('MqttManager') try: + mqttManager = self._managers.pop('MqttManager') + for managerName, manager in self._managers.items(): manager.onStop() @@ -206,8 +217,6 @@ def onStop(self): mqttManager.onStop() except Exception as e: Logger().logError(f'Error while shutting down manager **{managerName}**: {e}') - import traceback - traceback.print_exc() def getManager(self, managerName: str): diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index 0310577a4..5c2da7b96 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -105,7 +105,7 @@ button.button{ font-size: 1.6em; height: 2em; } .toolbarButton { font-size: 2em; padding-right: .5em; min-width: .9em; text-align: center; } -.zindexer { position: absolute; top: 5px; left: 5px; background-color: var(--mainBG); width: 50px; border-radius: 25px; text-align: center; color: var(--text); z-index:99; } +.zindexer { position: absolute; top: 5px; left: 5px; background-color: var(--mainBG); width: 36px; border-radius: 18px; text-align: center; color: var(--text); z-index: 99; } .zindexer .clickable:first-child { margin-bottom: 5px; } diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js index 19271e97a..545143219 100644 --- a/core/interface/static/js/common.js +++ b/core/interface/static/js/common.js @@ -3,13 +3,13 @@ let mqttSubscribers = {}; let MQTT_HOST; let MQTT_PORT; let LAST_CORE_HEARTBEAT = 0; -let MAIN_GOING_DOWN = false +let MAIN_GOING_DOWN = false; function getCookie(cname) { let name = cname + '='; let decodedCookie = decodeURIComponent(document.cookie); let ca = decodedCookie.split(';'); - for(let i = 0; i < ca.length; i++) { + for (let i = 0; i < ca.length; i++) { let c = ca[i]; while (c.charAt(0) == ' ') { c = c.substring(1); @@ -39,7 +39,9 @@ $(function () { function onFailure(_msg) { console.log('Mqtt connection failed, retry in 5 seconds'); - setTimeout(function() { connectMqtt(); }, 5000); + setTimeout(function () { + connectMqtt(); + }, 5000); } function onConnect(msg) { @@ -52,7 +54,7 @@ $(function () { } function onConnectionLost(resObj) { - console.log('Mqtt disconnected, automatic reconnect is enabled Error code: ' + resObj.errorCode +' - '+ resObj.errorMessage ); + console.log('Mqtt disconnected, automatic reconnect is enabled Error code: ' + resObj.errorCode + ' - ' + resObj.errorMessage); connectMqtt(); } @@ -79,7 +81,7 @@ $(function () { MQTT_HOST = window.location.hostname; } let randomNum = Math.floor((Math.random() * 10000000) + 1); - MQTT = new Paho.MQTT.Client(MQTT_HOST, MQTT_PORT, 'ProjectAliceInterface'+randomNum); + MQTT = new Paho.MQTT.Client(MQTT_HOST, MQTT_PORT, 'ProjectAliceInterface' + randomNum); MQTT.onMessageArrived = onMessage; MQTT.onConnectionLost = onConnectionLost; MQTT.connect({ @@ -88,12 +90,16 @@ $(function () { timeout : 5 }); } else { - console.log('Failed fetching MQTT settings') - setTimeout(function() { connectMqtt(); }, 5000); + console.log('Failed fetching MQTT settings'); + setTimeout(function () { + connectMqtt(); + }, 5000); } }).fail(function () { - console.log("Coulnd't get MQTT information") - setTimeout(function() { connectMqtt(); }, 5000); + console.log('Coulnd\'t get MQTT information'); + setTimeout(function () { + connectMqtt(); + }, 5000); }); } @@ -127,17 +133,14 @@ $(function () { } else if (payload.status == 'done') { $container.text('Nlu training done!'); } - } - else if (msg.topic == 'projectalice/skills/instructions') { + } else if (msg.topic == 'projectalice/skills/instructions') { let payload = JSON.parse(msg.payloadString); $('#skillInstructions').show(); let $content = $('#skillInstructionsContent'); $content.html($content.html() + payload['instructions']); - } - else if (msg.topic == 'projectalice/devices/coreHeartbeat') { + } else if (msg.topic == 'projectalice/devices/coreHeartbeat') { LAST_CORE_HEARTBEAT = Date.now(); - } - else if (msg.topic == 'projectalice/skills/coreConfigUpdateWarning') { + } else if (msg.topic == 'projectalice/skills/coreConfigUpdateWarning') { $('#serverUnavailable').hide(); let $nodal = $('#coreConfigUpdateAlert'); $nodal.show(); @@ -146,12 +149,12 @@ $(function () { let $container = $('#overlaySkillContent'); if ($container.children().length <= 0) { - $container.append('

    ' + payload['skill'] + '
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); + $container.append('
    ' + payload['skill'] + '
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); } else { - let $skillWarning = $('#confWarning_' + payload["skill"]); + let $skillWarning = $('#confWarning_' + payload['skill']); if ($skillWarning.length == 0) { - $container.append('
    ' + payload['skill'] + '
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); + $container.append('
    ' + payload['skill'] + '
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); } else { $skillWarning.append('
    ' + payload['key'] + ' => ' + payload['value'] + '
    '); } @@ -249,69 +252,69 @@ $(function () { // Z-indexers let $zindexed = $('.z-indexed'); if ($zindexed.length) { - zIndexMe($zindexed) + zIndexMe($zindexed); } }); - function reorder($arrow, direction) { - let $widget = $arrow.parent().parent(); - let $container = $widget.parent(); - let $family = $container.children('.z-indexed'); - - let actualIndex = $widget.css('z-index'); - try { - actualIndex = parseInt(actualIndex); - } catch { - actualIndex = 0; - } +function reorder($arrow, direction) { + let $widget = $arrow.parent().parent(); + let $container = $widget.parent(); + let $family = $container.children('.z-indexed'); - let toIndex; - if (direction === 'down') { - toIndex = Math.max(0, actualIndex - 1); - } else { - let highest = 0; - $family.each(function(){ - try { - if (parseInt($(this).css('z-index')) > highest) { - highest = parseInt($(this).css('z-index')); - } - } catch { - return true; - } - }); - toIndex = actualIndex + 1; - if (toIndex > highest) { - return; - } - } + let actualIndex = $widget.css('z-index'); + try { + actualIndex = parseInt(actualIndex); + } catch { + actualIndex = 0; + } - $family.each(function(){ + let toIndex; + if (direction === 'down') { + toIndex = Math.max(0, actualIndex - 1); + } else { + let highest = 0; + $family.each(function () { try { - if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { - $(this).css('z-index', actualIndex); + if (parseInt($(this).css('z-index')) > highest) { + highest = parseInt($(this).css('z-index')); } } catch { return true; } }); - $widget.css('z-index', toIndex); + toIndex = actualIndex + 1; + if (toIndex > highest) { + return; + } } + $family.each(function () { + try { + if ($(this) != $widget && parseInt($(this).css('z-index')) == toIndex) { + $(this).css('z-index', actualIndex); + } + } catch { + return true; + } + }); + $widget.css('z-index', toIndex); +} -function zIndexMe($zindexed){ - let $indexUp = $('
    '); - let $indexDown = $('
    '); - $indexUp.on('click touch', function () { - reorder($(this), 'up'); - }); - $indexDown.on('click touch', function () { - reorder($(this), 'down'); - }); +function zIndexMe($zindexed) { + let $indexUp = $('
    '); + let $indexDown = $('
    '); + + $indexUp.on('click touch', function () { + reorder($(this), 'up'); + }); + $indexDown.on('click touch', function () { + reorder($(this), 'down'); + }); - let $zindexer = $('
    '); - $zindexer.append($indexUp); - $zindexer.append($indexDown); + let $zindexer = $('
    '); + $zindexer.append($indexUp); + $zindexer.append($indexDown); - $zindexed.append($zindexer); + $zindexed.append($zindexer); } diff --git a/core/interface/static/js/widgets.js b/core/interface/static/js/widgets.js index c76110d5d..f9eb7204b 100644 --- a/core/interface/static/js/widgets.js +++ b/core/interface/static/js/widgets.js @@ -32,7 +32,11 @@ $(function () { widget.draggable('disable'); widget.resizable('disable'); $('.zindexer').hide(); + saveWidgets(); + }); + function saveWidgets() { + let widget = $('.widget'); let data = {}; widget.each(function () { data[$(this).attr('id')] = { @@ -52,7 +56,7 @@ $(function () { dataType : 'json', type : 'POST' }); - }); + } $('.widgetOptions').dialog({ autoOpen : false, @@ -95,6 +99,7 @@ $(function () { $('#configToggle').on('click touch', function () { $('.widgetConfig').show(); + $('.zindexer').hide(); $('#toolbar_checkmark').show(); $('#toolbar_full').hide(); return false; @@ -115,6 +120,9 @@ $(function () { $('#toolbar_checkmark').hide(); $('.widgetDelete').hide(); $('.widgetConfig').hide(); + + saveWidgets(); + location.reload(); return false; }); diff --git a/core/server/AudioServer.py b/core/server/AudioServer.py index 7b17a6680..1f56d5b5a 100644 --- a/core/server/AudioServer.py +++ b/core/server/AudioServer.py @@ -4,7 +4,8 @@ from pathlib import Path from typing import Dict, Optional -import pyaudio +import sounddevice as sd +# noinspection PyUnresolvedReferences from webrtcvad import Vad from core.ProjectAliceExceptions import PlayBytesStopped @@ -22,41 +23,49 @@ class AudioManager(Manager): LAST_USER_SPEECH = 'var/cache/lastUserpeech_{}_{}.wav' SECOND_LAST_USER_SPEECH = 'var/cache/secondLastUserSpeech_{}_{}.wav' - # Inspired by https://github.com/koenvervloesem/hermes-audio-server - def __init__(self): super().__init__() self._stopPlayingFlag: Optional[AliceEvent] = None self._playing = False self._waves: Dict[str, wave.Wave_write] = dict() + self._audioInputStream = None if self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): return - with self.Commons.shutUpAlsaFFS(): - self._audio = pyaudio.PyAudio() - self._vad = Vad(2) + self._audioInput = None + self._audioOutput = None - try: - self._audioOutput = self._audio.get_default_output_device_info() - except: - self.logFatal('Audio output not found, cannot continue') - return + + def onStart(self): + super().onStart() + + if not self.ConfigManager.getAliceConfigByName('inputDevice'): + self.logWarning('Input device not set in config, trying to find default device') + try: + self._audioInput = sd.query_devices(kind='input')['name'] + except: + self.logFatal('Audio input not found, cannot continue') + return + self.ConfigManager.updateAliceConfiguration(key='inputDevice', value=self._audioInput) else: - self.logInfo(f'Using **{self._audioOutput["name"]}** for audio output') + self._audioInput = self.ConfigManager.getAliceConfigByName('inputDevice') - try: - self._audioInput = self._audio.get_default_input_device_info() - except: - self.logFatal('Audio input not found, cannot continue') + if not self.ConfigManager.getAliceConfigByName('outputDevice'): + self.logWarning('Output device not set in config, trying to find default device') + try: + self._audioOutput = sd.query_devices(kind='output')['name'] + except: + self.logFatal('Audio output not found, cannot continue') + return + self.ConfigManager.updateAliceConfiguration(key='outputDevice', value=self._audioOutput) else: - self.logInfo(f'Using **{self._audioInput["name"]}** for audio input') + self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') + self.setDefaults() - def onStart(self): - super().onStart() self._stopPlayingFlag = self.ThreadManager.newEvent('stopPlaying') self.MqttManager.mqttClient.subscribe(constants.TOPIC_AUDIO_FRAME.format(self.ConfigManager.getAliceConfigByName('uuid'))) @@ -64,13 +73,19 @@ def onStart(self): self.ThreadManager.newThread(name='audioPublisher', target=self.publishAudio) + def setDefaults(self): + self.logInfo(f'Using **{self._audioInput}** for audio input') + self.logInfo(f'Using **{self._audioOutput}** for audio output') + + sd.default.device = self._audioInput, self._audioOutput + + def onStop(self): super().onStop() + self._audioInputStream.stop(ignore_errors=True) + self._audioInputStream.close(ignore_errors=True) self.MqttManager.mqttClient.unsubscribe(constants.TOPIC_AUDIO_FRAME.format(self.ConfigManager.getAliceConfigByName('uuid'))) - if not self.ConfigManager.getAliceConfigByName('disableSoundAndMic'): - self._audio.terminate() - def onStartListening(self, session: DialogSession): if not self.ConfigManager.getAliceConfigByName('recordAudioAfterWakeword'): @@ -104,13 +119,13 @@ def recordFrame(self, siteId: str, frame: bytes): def publishAudio(self): self.logInfo('Starting audio publisher') - audioStream = self._audio.open( - format=pyaudio.paInt16, + self._audioInputStream = sd.RawInputStream( + dtype='int16', channels=1, - rate=self.SAMPLERATE, - frames_per_buffer=self.FRAMES_PER_BUFFER, - input=True + samplerate=self.SAMPLERATE, + blocksize=self.FRAMES_PER_BUFFER, ) + self._audioInputStream.start() speech = False silence = self.SAMPLERATE / self.FRAMES_PER_BUFFER @@ -122,7 +137,7 @@ def publishAudio(self): break try: - frames = audioStream.read(num_frames=self.FRAMES_PER_BUFFER, exception_on_overflow=False) + frames = self._audioInputStream.read(frames=self.FRAMES_PER_BUFFER)[0] if self._vad.is_speech(frames, self.SAMPLERATE): if not speech and speechFrames < minSpeechFrames: @@ -175,30 +190,31 @@ def onPlayBytes(self, requestId: str, payload: bytearray, siteId: str, sessionId with io.BytesIO(payload) as buffer: try: with wave.open(buffer, 'rb') as wav: - sampleWidth = wav.getsampwidth() - nFormat = self._audio.get_format_from_width(sampleWidth) channels = wav.getnchannels() framerate = wav.getframerate() - def streamCallback(_inData, frameCount, _timeInfo, _status) -> tuple: + def streamCallback(outdata, frameCount, _timeInfo, _status): data = wav.readframes(frameCount) - return data, pyaudio.paContinue + if len(data) < len(outdata): + outdata[:len(data)] = data + outdata[len(data):] = b'\x00' * (len(outdata) - len(data)) + raise sd.CallbackStop + else: + outdata[:] = data - audioStream = self._audio.open( - format=nFormat, + stream = sd.RawOutputStream( + dtype='int16', channels=channels, - rate=framerate, - output=True, - output_device_index=self._audioOutput['index'], - stream_callback=streamCallback + samplerate=framerate, + callback=streamCallback ) - self.logDebug(f'Playing wav stream using **{self._audioOutput["name"]}** audio output from site id **{self.DeviceManager.siteIdToDeviceName(siteId)}** (Format: {nFormat}, channels: {channels}, rate: {framerate})') - audioStream.start_stream() - while audioStream.is_active(): + self.logDebug(f'Playing wav stream using **{self._audioOutput}** audio output from site id **{self.DeviceManager.siteIdToDeviceName(siteId)}** (channels: {channels}, rate: {framerate})') + stream.start() + while stream.active: if self._stopPlayingFlag.is_set(): - audioStream.stop_stream() - audioStream.close() + stream.stop() + stream.close() if sessionId: self.MqttManager.publish( @@ -214,8 +230,8 @@ def streamCallback(_inData, frameCount, _timeInfo, _status) -> tuple: raise PlayBytesStopped time.sleep(0.1) - audioStream.stop_stream() - audioStream.close() + stream.stop() + stream.close() except PlayBytesStopped: self.logDebug('Playing bytes stopped') except Exception as e: @@ -228,7 +244,7 @@ def streamCallback(_inData, frameCount, _timeInfo, _status) -> tuple: self.MqttManager.publish( topic=constants.TOPIC_PLAY_BYTES_FINISHED.format(siteId), payload={ - 'id': requestId, + 'id' : requestId, 'sessionId': sessionId } ) @@ -238,6 +254,12 @@ def stopPlaying(self): self._stopPlayingFlag.set() + def updateAudioDevices(self): + self._audioInput = self.ConfigManager.getAliceConfigByName('inputDevice') + self._audioOutput = self.ConfigManager.getAliceConfigByName('outputDevice') + self.setDefaults() + + @property def isPlaying(self) -> bool: return self._playing diff --git a/core/util/InternetManager.py b/core/util/InternetManager.py index 4dfeac28a..31201596d 100644 --- a/core/util/InternetManager.py +++ b/core/util/InternetManager.py @@ -1,5 +1,4 @@ import requests -from requests.exceptions import RequestException from core.base.model.Manager import Manager from core.commons import constants @@ -47,7 +46,7 @@ def checkOnlineState(self, addr: str = 'https://clients3.google.com/generate_204 try: online = requests.get(addr).status_code == 204 - except RequestException: + except: online = False if silent: diff --git a/requirements.txt b/requirements.txt index 02d064f38..f312451e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,3 +26,4 @@ toml==0.10.1 markdown==3.2.2 ProjectAlice-sk #pyopenssl==19.1.0 +sounddevice==0.4.1 diff --git a/requirements_test.txt b/requirements_test.txt index 79392e84a..e9cba619e 100644 --- a/requirements_test.txt +++ b/requirements_test.txt @@ -27,3 +27,4 @@ webrtcvad~=2.0.10 pyjwt~=1.7.1 toml~=0.10.1 markdown~=3.2.2 +sounddevice==0.4.1 From a53b6242ad153f8a5e7c16b0aacb6a4e1e19a3e4 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Thu, 5 Nov 2020 11:22:59 +0100 Subject: [PATCH 119/126] :bug: disable snips-asr service after installing it --- core/asr/model/SnipsAsr.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/asr/model/SnipsAsr.py b/core/asr/model/SnipsAsr.py index c021e0cc9..558948bc6 100644 --- a/core/asr/model/SnipsAsr.py +++ b/core/asr/model/SnipsAsr.py @@ -18,9 +18,10 @@ class SnipsAsr(Asr): 'system': [ 'libgfortran3' ], - 'pip' : [] + 'pip' : [] } + def __init__(self): super().__init__() self._capableOfArbitraryCapture = True @@ -28,6 +29,12 @@ def __init__(self): self._listening = False + def installDependencies(self): + super().installDependencies() + self.Commons.runRootSystemCommand(['systemctl', 'stop', 'snips-asr']) + self.Commons.runRootSystemCommand(['systemctl', 'disable', 'snips-asr']) + + def onStartListening(self, session): self._listening = True From 52476216ad66e2c29c4de08eff8815d829437080 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 6 Nov 2020 20:59:31 +0100 Subject: [PATCH 120/126] :bug: do not download skills while NLU is training --- core/base/SkillManager.py | 2 +- core/nlu/NluManager.py | 12 +++++ core/nlu/model/SnipsNlu.py | 100 +++++++++++++++++++++---------------- 3 files changed, 69 insertions(+), 45 deletions(-) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index 686b7580c..ae5bbcd38 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -609,7 +609,7 @@ def _checkForSkillInstall(self): root = Path(self.Commons.rootDir(), constants.SKILL_INSTALL_TICKET_PATH) files = [f for f in root.iterdir() if f.suffix == '.install'] - if self._busyInstalling.isSet() or not files or self.ProjectAlice.restart or self.ProjectAlice.updating: + if self._busyInstalling.isSet() or not files or self.ProjectAlice.restart or self.ProjectAlice.updating or self.NluManager.training: return self.logInfo(f'Found {len(files)} install ticket', plural='ticket') diff --git a/core/nlu/NluManager.py b/core/nlu/NluManager.py index b6c660f5b..5e6351e29 100644 --- a/core/nlu/NluManager.py +++ b/core/nlu/NluManager.py @@ -12,6 +12,7 @@ def __init__(self): self._pathToCache = Path(self.Commons.rootDir(), 'var/cache/nlu/trainingData') if not self._pathToCache.exists(): self._pathToCache.mkdir(parents=True) + self._training = True def onStart(self): @@ -66,9 +67,20 @@ def train(self): def trainNLU(self): + self._training = True self._nluEngine.train() def clearCache(self): shutil.rmtree(self._pathToCache) self._pathToCache.mkdir() + + + @property + def training(self) -> bool: + return self._training + + + @training.setter + def training(self, value: bool): + self._training = value diff --git a/core/nlu/model/SnipsNlu.py b/core/nlu/model/SnipsNlu.py index 24ae3138b..5791f55c8 100644 --- a/core/nlu/model/SnipsNlu.py +++ b/core/nlu/model/SnipsNlu.py @@ -97,69 +97,81 @@ def convertDialogTemplate(self, file: Path): def train(self): + if self.NluManager.training: + self.logWarning("NLU is already training, can't train again now") + return + self.logInfo('Training Snips NLU') - dataset = { - 'entities': dict(), - 'intents' : dict(), - 'language': self.LanguageManager.activeLanguage, - } + try: + dataset = { + 'entities': dict(), + 'intents' : dict(), + 'language': self.LanguageManager.activeLanguage, + } - with Path(self._cachePath / f'{self.LanguageManager.activeLanguage}.json').open() as fp: - trainingData = json.load(fp) - dataset['entities'].update(trainingData['entities']) - dataset['intents'].update(trainingData['intents']) + with Path(self._cachePath / f'{self.LanguageManager.activeLanguage}.json').open() as fp: + trainingData = json.load(fp) + dataset['entities'].update(trainingData['entities']) + dataset['intents'].update(trainingData['intents']) - datasetFile = Path('/tmp/snipsNluDataset.json') + datasetFile = Path('/tmp/snipsNluDataset.json') - with datasetFile.open('w') as fp: - json.dump(dataset, fp, ensure_ascii=False, indent='\t') + with datasetFile.open('w') as fp: + json.dump(dataset, fp, ensure_ascii=False, indent='\t') - self.logInfo('Generated dataset for training') + self.logInfo('Generated dataset for training') - # Now that we have generated the dataset, let's train in the background if we are already booted, else do it directly - if self.ProjectAlice.isBooted: - self.ThreadManager.newThread(name='NLUTraining', target=self.nluTrainingThread, args=[datasetFile]) - else: - self.nluTrainingThread(datasetFile) + # Now that we have generated the dataset, let's train in the background if we are already booted, else do it directly + if self.ProjectAlice.isBooted: + self.ThreadManager.newThread(name='NLUTraining', target=self.nluTrainingThread, args=[datasetFile]) + else: + self.nluTrainingThread(datasetFile) + except: + self.NluManager.training = False def nluTrainingThread(self, datasetFile: Path): - with Stopwatch() as stopWatch: - self.logInfo('Begin training...') - self._timer = self.ThreadManager.newTimer(interval=0.25, func=self.trainingStatus) + try: + with Stopwatch() as stopWatch: + self.logInfo('Begin training...') + self._timer = self.ThreadManager.newTimer(interval=0.25, func=self.trainingStatus) - tempTrainingData = Path('/tmp/snipsNLU') + tempTrainingData = Path('/tmp/snipsNLU') - if tempTrainingData.exists(): - shutil.rmtree(tempTrainingData) + if tempTrainingData.exists(): + shutil.rmtree(tempTrainingData) - training: CompletedProcess = self.Commons.runSystemCommand([f'./venv/bin/snips-nlu', 'train', str(datasetFile), str(tempTrainingData)]) - if training.returncode != 0: - self.logError(f'Error while training Snips NLU: {training.stderr.decode()}') + training: CompletedProcess = self.Commons.runSystemCommand([f'./venv/bin/snips-nlu', 'train', str(datasetFile), str(tempTrainingData)]) + if training.returncode != 0: + self.logError(f'Error while training Snips NLU: {training.stderr.decode()}') - assistantPath = Path(self.Commons.rootDir(), f'trained/assistants/{self.LanguageManager.activeLanguage}/nlu_engine') + assistantPath = Path(self.Commons.rootDir(), f'trained/assistants/{self.LanguageManager.activeLanguage}/nlu_engine') - if not tempTrainingData.exists(): - self.logError('Snips NLU training failed') - self.MqttManager.publish(constants.TOPIC_NLU_TRAINING_STATUS, payload={'status': 'failed'}) - if not assistantPath.exists(): - self.logFatal('No NLU engine found, cannot start') + if not tempTrainingData.exists(): + self.logError('Snips NLU training failed') + self.MqttManager.publish(constants.TOPIC_NLU_TRAINING_STATUS, payload={'status': 'failed'}) + if not assistantPath.exists(): + self.logFatal('No NLU engine found, cannot start') - self._timer.cancel() - return + self._timer.cancel() + return - if assistantPath.exists(): - shutil.rmtree(assistantPath) + if assistantPath.exists(): + shutil.rmtree(assistantPath) - shutil.move(tempTrainingData, assistantPath) + shutil.move(tempTrainingData, assistantPath) - self.broadcast(method=constants.EVENT_NLU_TRAINED, exceptions=[constants.DUMMY], propagateToSkills=True) - self.Commons.runRootSystemCommand(['systemctl', 'restart', 'snips-nlu']) + self.broadcast(method=constants.EVENT_NLU_TRAINED, exceptions=[constants.DUMMY], propagateToSkills=True) + self.Commons.runRootSystemCommand(['systemctl', 'restart', 'snips-nlu']) - self._timer.cancel() - self.MqttManager.publish(constants.TOPIC_NLU_TRAINING_STATUS, payload={'status': 'done'}) - self.ThreadManager.getEvent('TrainAssistant').clear() - self.logInfo(f'Snips NLU trained in {stopWatch} seconds') + self._timer.cancel() + self.MqttManager.publish(constants.TOPIC_NLU_TRAINING_STATUS, payload={'status': 'done'}) + self.ThreadManager.getEvent('TrainAssistant').clear() + self.logInfo(f'Snips NLU trained in {stopWatch} seconds') + except: + pass # Do nothing, the finally clause will finish the work + finally: + self.NluManager.training = False def trainingStatus(self): From 627ed736542a3991f4c35a2a47900e4149d286e3 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 6 Nov 2020 21:09:23 +0100 Subject: [PATCH 121/126] :bug: sry --- core/nlu/NluManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/nlu/NluManager.py b/core/nlu/NluManager.py index 5e6351e29..72147d1e8 100644 --- a/core/nlu/NluManager.py +++ b/core/nlu/NluManager.py @@ -12,7 +12,7 @@ def __init__(self): self._pathToCache = Path(self.Commons.rootDir(), 'var/cache/nlu/trainingData') if not self._pathToCache.exists(): self._pathToCache.mkdir(parents=True) - self._training = True + self._training = False def onStart(self): From 09c4bb30062db70a74c9705def642be507bf2b6b Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Fri, 6 Nov 2020 21:12:38 +0100 Subject: [PATCH 122/126] :bug: checked working --- core/nlu/NluManager.py | 1 - core/nlu/model/SnipsNlu.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/nlu/NluManager.py b/core/nlu/NluManager.py index 72147d1e8..20b82fc19 100644 --- a/core/nlu/NluManager.py +++ b/core/nlu/NluManager.py @@ -67,7 +67,6 @@ def train(self): def trainNLU(self): - self._training = True self._nluEngine.train() diff --git a/core/nlu/model/SnipsNlu.py b/core/nlu/model/SnipsNlu.py index 5791f55c8..4dbf4f0d5 100644 --- a/core/nlu/model/SnipsNlu.py +++ b/core/nlu/model/SnipsNlu.py @@ -103,6 +103,7 @@ def train(self): self.logInfo('Training Snips NLU') try: + self.NluManager.training = True dataset = { 'entities': dict(), 'intents' : dict(), From 93219cba71d9f7a5ab52aff777d6f2ef685cb984 Mon Sep 17 00:00:00 2001 From: LazzaAU Date: Sat, 7 Nov 2020 14:24:19 +1000 Subject: [PATCH 123/126] :children_crossing: Improve ASR capture if a slow internet causes google to not confirm capture within 3 seconds --- core/asr/model/GoogleAsr.py | 40 +++++++++++++++++++++++++------------ 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/core/asr/model/GoogleAsr.py b/core/asr/model/GoogleAsr.py index 57126beb8..189267c1f 100644 --- a/core/asr/model/GoogleAsr.py +++ b/core/asr/model/GoogleAsr.py @@ -37,11 +37,11 @@ def __init__(self): self._client: Optional[SpeechClient] = None self._streamingConfig: Optional[types.StreamingRecognitionConfig] = None - self._internetLostFlag = Event() # Set if internet goes down, cut the decoding - self._lastResultCheck = 0 # The time the intermediate results were last checked. If actual time is greater than this value + 3, stop processing, internet issues - - self._previousCapture = '' # The text that was last captured in the iteration + self._internetLostFlag = Event() # Set if internet goes down, cut the decoding + self._lastResultCheck = 0 # The time the intermediate results were last checked. If actual time is greater than this value + 3, stop processing, internet issues + self._previousCapture = '' # The text that was last captured in the iteration + self._delayedGoogleConfirmation = False # set whether slow internet is detected or not def onStart(self): super().onStart() @@ -113,22 +113,36 @@ def _checkResponses(self, session: DialogSession, responses: Generator) -> Optio continue if result.is_final: + self._lastResultCheck = 0 + self._delayedGoogleConfirmation = False + # print(f'Text confirmed by Google') return result.alternatives[0].transcript, result.alternatives[0].confidence elif result.alternatives[0].transcript != self._previousCapture: self.partialTextCaptured(session=session, text=result.alternatives[0].transcript, likelihood=result.alternatives[0].confidence, seconds=0) - self._previousCapture = result.alternatives[0].transcript + # below function captures the "potential" full utterance not just one word from it + if len(self._previousCapture) <= len(result.alternatives[0].transcript): + self._previousCapture = result.alternatives[0].transcript elif result.alternatives[0].transcript == self._previousCapture: - now = int(time()) - if self._lastResultCheck == 0: - self._lastResultCheck = 0 + # If we are here it's cause google hasn't responded yet with confirmation on captured text + # Store the time in seconds since epoch + now = int(time()) + # Set a reference to nows time plus 3 seconds + self._lastResultCheck = now + 3 + # wait 3 seconds and see if google responds + if not self._delayedGoogleConfirmation: + # print(f'Text of "{self._previousCapture}" captured but not confirmed by GoogleASR yet') + while now <= self._lastResultCheck: + now = int(time()) + self._delayedGoogleConfirmation = True + # Give google the option to still process the utterance continue - - if now > self._lastResultCheck + 3: + # During next iteration, If google hasn't responded in 3 seconds assume intent is correct + if self._delayedGoogleConfirmation: self.logDebug(f'Stopping process as there seems to be connectivity issues') + self._lastResultCheck = 0 + self._delayedGoogleConfirmation = False return result.alternatives[0].transcript, result.alternatives[0].confidence - self._lastResultCheck = now - continue - return None + From 04c99ddbeb70732c5ebc8acef844b5c05faf0872 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 7 Nov 2020 12:45:09 +0100 Subject: [PATCH 124/126] :sparkles: Added supportsSSML to TTS engines, without auto rewrite to remove ssml tags if needed --- core/interface/static/css/projectalice.css | 3 --- core/voice/model/AmazonTts.py | 10 +++++----- core/voice/model/GoogleTts.py | 11 +---------- core/voice/model/MycroftTts.py | 6 ------ core/voice/model/PicoTts.py | 6 ------ core/voice/model/Tts.py | 21 +++++++++++++++++++-- core/voice/model/WatsonTts.py | 11 +---------- 7 files changed, 26 insertions(+), 42 deletions(-) diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css index 5c2da7b96..f2eef6c3a 100644 --- a/core/interface/static/css/projectalice.css +++ b/core/interface/static/css/projectalice.css @@ -113,9 +113,6 @@ button.button{ font-size: 1.6em; height: 2em; } .clickable:hover { color: var(--hover); } -/*.zindexer { position: absolute; top: -15px; right: -15px; color: var(--accent); font-weight: bold; font-size: 1.3em; z-index: 999; cursor: pointer; } -[class^='zindexer-']:hover { color: var(--hover); }*/ - /* Utility screen for administration */ .utility { width: 12em; height: 12em; flex-flow: column; align-items: center; } diff --git a/core/voice/model/AmazonTts.py b/core/voice/model/AmazonTts.py index bc5ffef9e..6c091349f 100644 --- a/core/voice/model/AmazonTts.py +++ b/core/voice/model/AmazonTts.py @@ -27,6 +27,7 @@ def __init__(self, user: User = None): self._online = True self._privacyMalus = -20 self._client = None + self._supportsSSML = True # TODO implement the others # https://docs.aws.amazon.com/polly/latest/dg/voicelist.html @@ -255,12 +256,11 @@ def getWhisperMarkup() -> tuple: return '', '' - @staticmethod - def _checkText(session: DialogSession) -> str: - text = session.payload['text'] + def _checkText(self, session: DialogSession) -> str: + text = super()._checkText(session) - if not re.search('', text): - text = f'{text}' + if not re.search('', text): + text = re.sub(r'(.*)', r'\1', text) return text diff --git a/core/voice/model/GoogleTts.py b/core/voice/model/GoogleTts.py index 72267c88c..354eb3b50 100644 --- a/core/voice/model/GoogleTts.py +++ b/core/voice/model/GoogleTts.py @@ -31,6 +31,7 @@ def __init__(self, user: User = None): self._online = True self._privacyMalus = -20 self._client = None + self._supportsSSML = True # TODO implement the others # https://cloud.google.com/text-to-speech/docs/voices @@ -201,16 +202,6 @@ def onStart(self): ) - @staticmethod - def _checkText(session: DialogSession) -> str: - text = session.payload['text'] - - if not '' in text: - text = f'{text}' - - return text - - def onSay(self, session: DialogSession): super().onSay(session) diff --git a/core/voice/model/MycroftTts.py b/core/voice/model/MycroftTts.py index 763eafc59..b1157b1dc 100644 --- a/core/voice/model/MycroftTts.py +++ b/core/voice/model/MycroftTts.py @@ -107,12 +107,6 @@ def installDependencies(self) -> bool: return True - @staticmethod - def _checkText(session: DialogSession) -> str: - text = session.payload['text'] - return ' '.join(re.sub('<.*?>', ' ', text).split()) - - def onSay(self, session: DialogSession): super().onSay(session) diff --git a/core/voice/model/PicoTts.py b/core/voice/model/PicoTts.py index 61835dc2f..5f4ccfa96 100644 --- a/core/voice/model/PicoTts.py +++ b/core/voice/model/PicoTts.py @@ -60,12 +60,6 @@ def __init__(self, user: User = None): } - @staticmethod - def _checkText(session: DialogSession) -> str: - text = session.payload['text'] - return ' '.join(re.sub('<.*?>', ' ', text).split()) - - def onSay(self, session: DialogSession): super().onSay(session) diff --git a/core/voice/model/Tts.py b/core/voice/model/Tts.py index 7b29ee3a0..98fbc4e40 100644 --- a/core/voice/model/Tts.py +++ b/core/voice/model/Tts.py @@ -1,6 +1,8 @@ import getpass +import re import uuid from pathlib import Path +from re import Match from typing import Optional import hashlib @@ -17,6 +19,7 @@ class Tts(ProjectAliceObject): TEMP_ROOT = Path(tempfile.gettempdir(), '/tempTTS') TTS = None + SPELL_OUT = re.compile(r'(.+)') def __init__(self, user: User = None, *args, **kwargs): @@ -37,6 +40,8 @@ def __init__(self, user: User = None, *args, **kwargs): self._text = '' self._speaking = False + self._supportsSSML = False + def onStart(self): if self._user and self._user.ttsLanguage: @@ -179,9 +184,21 @@ def _sayFinished(self, session: DialogSession, uid: str): ) + def _checkText(self, session: DialogSession) -> str: + text = session.payload['text'] + if not self._supportsSSML: + # We need to remove all ssml tags but transform some first + text = re.sub(self.SPELL_OUT, self._replaceSpellOuts, text) + return ' '.join(re.sub('<.*?>', ' ', text).split()) + else: + if not '' in text: + text = f'{text}' + return text + + @staticmethod - def _checkText(session: DialogSession) -> str: - return session.payload['text'] + def _replaceSpellOuts(matching: Match) -> str: + return ''.join(matching.group(1)) def onSay(self, session: DialogSession): diff --git a/core/voice/model/WatsonTts.py b/core/voice/model/WatsonTts.py index b63dc18d3..a12201845 100644 --- a/core/voice/model/WatsonTts.py +++ b/core/voice/model/WatsonTts.py @@ -25,6 +25,7 @@ def __init__(self, user: User = None): self._online = True self._privacyMalus = -20 self._client = None + self._supportsSSML = True # TODO implement the others # https://cloud.ibm.com/apidocs/text-to-speech?code=python#list-voices @@ -117,16 +118,6 @@ def onStart(self): self._client.set_service_url(self.ConfigManager.getAliceConfigByName('ibmCloudAPIURL')) - @staticmethod - def _checkText(session: DialogSession) -> str: - text = session.payload['text'] - - if not '' in text: - text = f'{text}' - - return text - - def onSay(self, session: DialogSession): super().onSay(session) From cebf20257f448af9b06da3091161400ec71ed91c Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 7 Nov 2020 16:17:25 +0100 Subject: [PATCH 125/126] :bug: fixes widgets not appearing --- core/base/SkillManager.py | 39 +++++++++++++++++++++---------- core/base/model/Widget.py | 2 +- core/interface/views/IndexView.py | 4 ++-- 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index ae5bbcd38..ef89b482d 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -70,6 +70,7 @@ def __init__(self): self._postBootSkillActions = dict() self._widgets = dict() + self._widgetsByIndex = dict() def onStart(self): @@ -96,6 +97,7 @@ def onStart(self): self.ConfigManager.loadCheckAndUpdateSkillConfigurations() self.startAllSkills() + self.sortWidgetZIndexes() # noinspection SqlResolve @@ -174,7 +176,7 @@ def changeSkillStateInDB(self, skillName: str, newState: bool): 'state' : 0, 'posx' : 0, 'posy' : 0, - 'zindex': 9999 + 'zindex': -1 }, row=('parent', skillName) ) @@ -232,21 +234,34 @@ def onAssistantInstalled(self, **kwargs): def sortWidgetZIndexes(self): - widgets = dict() + # Create a list of skills with their z index as key + self._widgetsByIndex = dict() for skillName, widgetList in self._widgets.items(): for widget in widgetList.values(): - widgets[int(widget.zindex)] = widget + if widget.state != 1: + continue + + if int(widget.zindex) not in self._widgetsByIndex: + self._widgetsByIndex[int(widget.zindex)] = widget + else: + i = 1000 + while True: + if i not in self._widgetsByIndex: + self._widgetsByIndex[i] = widget + break + i += 1 + + print(self._widgetsByIndex) + + # Rewrite a logical zindex flow + for i, widget in enumerate(self._widgetsByIndex.values()): + print(i) + widget.zindex = i + widget.saveToDB() - counter = 0 - for i in sorted(widgets.keys()): - if widgets[i].state == 0: - widgets[i].zindex = -1 - widgets[i].saveToDB() - continue - widgets[i].zindex = counter - counter += 1 - widgets[i].saveToDB() + def nextZIndex(self) -> int: + return len(self._widgetsByIndex) @property diff --git a/core/base/model/Widget.py b/core/base/model/Widget.py index 210914802..d036a687f 100644 --- a/core/base/model/Widget.py +++ b/core/base/model/Widget.py @@ -77,7 +77,7 @@ def __init__(self, data: sqlite3.Row): if 'zindex' in data.keys() and data['zindex'] is not None: self._zindex = data['zindex'] else: - self._zindex = 999 + self._zindex = -1 updateWidget = True if updateWidget: diff --git a/core/interface/views/IndexView.py b/core/interface/views/IndexView.py index 98431190a..d59f638bb 100644 --- a/core/interface/views/IndexView.py +++ b/core/interface/views/IndexView.py @@ -54,7 +54,7 @@ def removeWidget(self): widget = self.SkillManager.widgets[parent][widgetName] widget.state = 0 - widget.saveToDB() + widget.zindex = -1 self.SkillManager.sortWidgetZIndexes() return jsonify(success=True) @@ -70,7 +70,7 @@ def addWidget(self): widget = self.SkillManager.widgets[parent][widgetName] widget.state = 1 - widget.saveToDB() + widget.zindex = self.SkillManager.nextZIndex() self.SkillManager.sortWidgetZIndexes() return redirect('home.html') From 5dfcbd0a1f3be49153465c0e570577ec75d5c027 Mon Sep 17 00:00:00 2001 From: Psychokiller1888 Date: Sat, 7 Nov 2020 16:20:01 +0100 Subject: [PATCH 126/126] :art: don't display z-indexer when remove widget active --- core/base/SkillManager.py | 3 --- core/interface/static/js/widgets.js | 2 ++ 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py index ef89b482d..fe2d62cc0 100644 --- a/core/base/SkillManager.py +++ b/core/base/SkillManager.py @@ -251,11 +251,8 @@ def sortWidgetZIndexes(self): break i += 1 - print(self._widgetsByIndex) - # Rewrite a logical zindex flow for i, widget in enumerate(self._widgetsByIndex.values()): - print(i) widget.zindex = i widget.saveToDB() diff --git a/core/interface/static/js/widgets.js b/core/interface/static/js/widgets.js index f9eb7204b..e651f61f0 100644 --- a/core/interface/static/js/widgets.js +++ b/core/interface/static/js/widgets.js @@ -87,6 +87,8 @@ $(function () { $('#removeWidget').on('click touch', function () { $('.widgetDelete').show(); + $('.widgetConfig').hide(); + $('.zindexer').hide(); $('#toolbar_checkmark').show(); $('#toolbar_full').hide(); return false;