class WakewordUploadThread(Thread): def __init__(self, host: str, port: int, zipPath: str): super().__init__() self._logger = Logger(prepend='[HotwordUploadThread]') self.setDaemon(True) self._host = host self._port = port self._zipPath = Path(zipPath) def run(self): try: wakewordName = self._zipPath.stem with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.bind((self._host, self._port)) self._logger.logInfo('Waiting for a device to connect') sock.listen() conn, addr = sock.accept() self._logger.logInfo( f'New device connected with address **{addr}**') with self._zipPath.open(mode='rb') as f: data = f.read(1024) while data: conn.send(data) data = f.read(1024) self._logger.logInfo( f'Waiting on a feedback from **{addr[0]}**') conn.settimeout(20) try: while True: answer = conn.recv(1024).decode() if not answer: raise Exception( 'The device closed the connection before confirming...' ) if answer == '0': self._logger.logInfo( f'Wakeword **{wakewordName}** upload to **{addr[0]}** success' ) break elif answer == '-1': raise Exception( 'The device failed downloading the hotword') elif answer == '-2': raise Exception( 'The device failed installing the hotword') except timeout: self._logger.logWarning( 'The device did not confirm the operation as successfull in time. The hotword installation might have failed' ) except Exception as e: self._logger.logError(f'Error uploading wakeword: {e}')
class Light: state: dict swupdate: dict type: str name: str modelid: str manufacturername: str productname: str capabilities: dict config: dict uniqueid: str swversion: str swconfigid: str = '' productid: str = '' id: int = 0 bridge: Optional[Bridge] = None myScenes: list = field(default_factory=list) logger: Optional[Logger] = None def init(self, lightId: int, bridgeInstance: Bridge): self.id = lightId self.bridge = bridgeInstance self.name = self.name.lower() self.logger = Logger(prepend='[Phue Light]') def __str__(self) -> str: return f'Light id {self.id} named "{self.name}" of type {self.type}.' def on(self): #NOSONAR self.request(url=f'/{self.id}/state', method='PUT', data={'on': True}) def off(self): self.request(url=f'/{self.id}/state', method='PUT', data={'on': False}) @property def isOn(self) -> bool: return self.state['on'] @property def isOff(self) -> bool: return not self.state['on'] def alert(self, state: str = 'lselect'): self.request(url=f'/{self.id}/state', method='PUT', data={'alert': state}) def effect(self, effect: str = 'colorloop'): self.request(url=f'/{self.id}/state', method='PUT', data={'effect': effect}) def configure(self, data: dict, sendToBridge: bool = True): for key, value in data.items(): if not key in self.state: continue self.state[key] = value if sendToBridge: self.request(url=f'/{self.id}/state', method='PUT', data=data) @property def brightness(self) -> int: return self.state['bri'] @brightness.setter def brightness(self, value: int): if value == 0: self.off() self.state['bri'] = 0 return value = sorted((1, value, 254))[1] self.state['bri'] = value self.request(url=f'/{self.id}/state', method='PUT', data={'bri': value}) @property def saturation(self) -> int: return self.state['sat'] @saturation.setter def saturation(self, value: int): value = sorted((1, value, 254))[1] self.state['sat'] = value self.request(url=f'/{self.id}/state', method='PUT', data={'sat': value}) @property def hue(self) -> int: return self.state['hue'] @hue.setter def hue(self, value: int): value = sorted((0, value, 65535))[1] self.state['hue'] = value self.request(url=f'/{self.id}/state', method='PUT', data={'hue': value}) @property def xy(self) -> list: #NOSONAR return self.state['xy'] @xy.setter def xy(self, value: list): #NOSONAR x = sorted((0, value[0], 1))[1] #NOSONAR y = sorted((0, value[1], 1))[1] #NOSONAR self.state['xy'] = [x, y] self.request(url=f'/{self.id}/state', method='PUT', data={'xy': value}) @property def mired(self) -> int: return self.state['ct'] @mired.setter def mired(self, value: int): self.state['ct'] = value self.request(url=f'/{self.id}/state', method='PUT', data={'ct': value}) @property def colormode(self) -> str: return self.state.get('colormode', None) @colormode.setter def colormode(self, mode: str): if 'colormode' not in self.state: self.logger.logWarning( f'Light {self.name} with id {self.id} does not support colormode changing' ) return if mode not in ('hs', 'xy', 'ct'): mode = 'ct' self.logger.logWarning( 'Invalid color mode specified. Allowed value are "hs", "ct", "xy"' ) self.state['colormode'] = mode self.request(url=f'/{self.id}/state', method='PUT', data={'colormode': mode}) @property def reachable(self) -> bool: return self.state['reachable'] def delete(self): self.request(url=f'/{self.id}', method='DELETE') def request(self, url: str, data: dict = None, method: str = 'GET'): if not self.reachable or not self.bridge: raise LightNotReachable self.bridge.sendAuthRequest( url=f'/lights{"/" if not url.startswith("/") else ""}{url}', method=method, data=data)
class ProjectAlice(Singleton): NAME = 'ProjectAlice' def __init__(self, restartHandler: callable): Singleton.__init__(self, self.NAME) self._logger = Logger(prepend='[Project Alice]') self._logger.logInfo('Starting Alice main unit') self._booted = False self._isUpdating = False self._shuttingDown = False self._restart = False self._restartHandler = restartHandler if not self.checkDependencies(): self._restart = True self._restartHandler() else: with Stopwatch() as stopWatch: self._superManager = SuperManager(self) self._superManager.initManagers() self._superManager.onStart() if self._superManager.configManager.getAliceConfigByName( 'useHLC'): self._superManager.commons.runRootSystemCommand( ['systemctl', 'start', 'hermesledcontrol']) self._superManager.onBooted() self._logger.logInfo(f'Started in {stopWatch} seconds') self._booted = True def checkDependencies(self) -> bool: """ Compares .hash files against requirements.txt and sysrequirement.txt. Updates dependencies if necessary :return: boolean False if the check failed, new deps were installed (reboot maybe? :) ) """ HASH_SUFFIX = '.hash' TXT_SUFFIX = '.txt' path = Path('requirements') savedHash = path.with_suffix(HASH_SUFFIX) reqHash = hashlib.blake2b( path.with_suffix(TXT_SUFFIX).read_bytes()).hexdigest() if not savedHash.exists() or savedHash.read_text() != reqHash: self._logger.logInfo( 'Pip dependencies added or removed, updating virtual environment' ) subprocess.run([ './venv/bin/pip', 'install', '-r', str(path.with_suffix(TXT_SUFFIX)) ]) savedHash.write_text(reqHash) return False path = Path('sysrequirements') savedHash = path.with_suffix(HASH_SUFFIX) reqHash = hashlib.blake2b( path.with_suffix(TXT_SUFFIX).read_bytes()).hexdigest() if not savedHash.exists() or savedHash.read_text() != reqHash: self._logger.logInfo( 'System dependencies added or removed, updating system') reqs = [ line.rstrip('\n') for line in open(path.with_suffix(TXT_SUFFIX)) ] subprocess.run([ 'sudo', 'apt-get', 'install', '-y', '--allow-unauthenticated' ] + reqs) savedHash.write_text(reqHash) return False path = Path('pipuninstalls') savedHash = path.with_suffix(HASH_SUFFIX) reqHash = hashlib.blake2b( path.with_suffix(TXT_SUFFIX).read_bytes()).hexdigest() if not savedHash.exists() or savedHash.read_text() != reqHash: self._logger.logInfo( 'Pip conflicting dependencies added, updating virtual environment' ) subprocess.run([ './venv/bin/pip', 'uninstall', '-y', '-r', str(path.with_suffix(TXT_SUFFIX)) ]) savedHash.write_text(reqHash) return False return True @property def name(self) -> str: # NOSONAR return self.NAME @property def isBooted(self) -> bool: return self._booted @property def restart(self) -> bool: return self._restart @restart.setter def restart(self, value: bool): self._restart = value def doRestart(self): self._restart = True self.onStop() def onStop(self, withReboot: bool = False): self._logger.logInfo('Shutting down') self._shuttingDown = True self._superManager.onStop() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand( ['systemctl', 'stop', 'hermesledcontrol']) self._booted = False self.INSTANCE = None if withReboot: subprocess.run(['sudo', 'shutdown', '-r', 'now']) else: self._restartHandler() def wipeAll(self): # Set as restarting so skills don't install / update self._restart = True self._superManager.skillManager.wipeSkills() self._superManager.databaseManager.clearDB() self._superManager.assistantManager.clearAssistant() self._superManager.dialogTemplateManager.clearCache(False) self._superManager.nluManager.clearCache() def updateProjectAlice(self): self._logger.logInfo('Checking for core updates') STATE = 'projectalice.core.updating' state = self._superManager.stateManager.getState(STATE) if not state: self._superManager.stateManager.register( STATE, initialState=StateType.RUNNING) elif state.currentState == StateType.RUNNING: self._logger.logInfo('Update cancelled, already running') return self._superManager.stateManager.setState(STATE, newState=StateType.RUNNING) self._isUpdating = True req = requests.get( url=f'{constants.GITHUB_API_URL}/ProjectAlice/branches', auth=SuperManager.getInstance().configManager.githubAuth) if req.status_code != 200: self._logger.logWarning('Failed checking for updates') self._superManager.stateManager.setState(STATE, newState=StateType.ERROR) return userUpdatePref = SuperManager.getInstance( ).configManager.getAliceConfigByName('aliceUpdateChannel') if userUpdatePref == 'master': candidate = 'master' else: candidate = Version.fromString(constants.VERSION) for branch in req.json(): if 'dependabot' in branch['name']: continue repoVersion = Version.fromString(branch['name']) if not repoVersion.isVersionNumber: continue releaseType = repoVersion.releaseType if userUpdatePref == 'rc' and releaseType in { 'b', 'a' } or userUpdatePref == 'beta' and releaseType == 'a': continue if repoVersion > candidate: candidate = repoVersion self._logger.logInfo(f'Checking on "{str(candidate)}" update channel') commons = SuperManager.getInstance().commons currentHash = subprocess.check_output( ['git', 'rev-parse', '--short HEAD']) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'stash']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'clean', '-df']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'checkout', str(candidate)]) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'pull']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'submodule', 'init']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'submodule', 'update']) commons.runSystemCommand([ 'git', '-C', commons.rootDir(), 'submodule', 'foreach', 'git', 'checkout', f'builds_{str(candidate)}' ]) commons.runSystemCommand([ 'git', '-C', commons.rootDir(), 'submodule', 'foreach', 'git', 'pull' ]) newHash = subprocess.check_output(['git', 'rev-parse', '--short HEAD']) # Remove install tickets [ file.unlink() for file in Path( commons.rootDir(), 'system/skillInstallTickets').glob('*') if file.is_file() ] self._superManager.stateManager.setState(STATE, newState=StateType.FINISHED) if currentHash != newHash: self._logger.logWarning( 'New Alice version installed, need to restart...') self._superManager.webUINotificationManager.newNotification( typ=UINotificationType.INFO, notification='aliceUpdated') self.doRestart() self._logger.logInfo('Update checks completed.') self._isUpdating = False @property def updating(self) -> bool: return self._isUpdating @property def shuttingDown(self) -> bool: return self._shuttingDown
class ProjectAlice(Singleton): NAME = 'ProjectAlice' def __init__(self, restartHandler: callable): Singleton.__init__(self, self.NAME) self._logger = Logger() self._logger.logInfo('Starting up Project Alice') self._booted = False with Stopwatch() as stopWatch: self._restart = False self._restartHandler = restartHandler self._superManager = SuperManager(self) self._superManager.initManagers() self._superManager.onStart() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand( ['systemctl', 'start', 'hermesledcontrol']) self._superManager.onBooted() self._logger.logInfo(f'- Started Project Alice in {stopWatch} seconds') self._booted = True @property def name(self) -> str: return self.NAME @property def isBooted(self) -> bool: return self._booted @property def restart(self) -> bool: return self._restart @restart.setter def restart(self, value: bool): self._restart = value def doRestart(self): self._restart = True self.onStop() def onStop(self): self._logger.logInfo('Shutting down Project Alice') self._superManager.onStop() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand( ['systemctl', 'stop', 'hermesledcontrol']) self.INSTANCE = None self._restartHandler() def wipeAll(self): # Set as restarting so skills don't install / update self._restart = True self._superManager.skillManager.wipeSkills() self._superManager.databaseManager.clearDB() self._superManager.dialogTemplateManager.clearCache(False) self._superManager.nluManager.clearCache() self._superManager.snipsAssistantManager.clearData() def updateProjectAlice(self): self._logger.logInfo('Checking Project Alice updates') req = requests.get( url=f'{constants.GITHUB_API_URL}/ProjectAlice/branches', auth=SuperManager.getInstance().configManager.getGithubAuth()) if req.status_code != 200: self._logger.logWarning('Failed checking for updates') return userUpdatePref = SuperManager.getInstance( ).configManager.getAliceConfigByName('aliceUpdateChannel') if userUpdatePref == 'master': candidate = 'master' else: candidate = Version.fromString(constants.VERSION) for branch in req.json(): repoVersion = Version.fromString(branch['name']) if not repoVersion.isVersionNumber: continue releaseType = repoVersion.releaseType if userUpdatePref == 'rc' and releaseType in { 'b', 'a' } or userUpdatePref == 'beta' and releaseType == 'a': continue if repoVersion > candidate: candidate = repoVersion self._logger.logInfo(f'Checking on "{str(candidate)}" update channel') commons = SuperManager.getInstance().commons commons.runSystemCommand(['git', '-C', commons.rootDir(), 'stash']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'clean', '-df']) commons.runSystemCommand( ['git', '-C', commons.rootDir(), 'checkout', str(candidate)]) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'pull']) # Remove install tickets [ file.unlink() for file in Path( commons.rootDir(), 'system/skillInstallTickets').glob('*') if file.is_file() ]
class ProjectAlice(Singleton): NAME = 'ProjectAlice' def __init__(self, restartHandler: callable): Singleton.__init__(self, self.NAME) self._logger = Logger(prepend='[Project Alice]') self._logger.logInfo('Starting Alice satellite unit') self._booted = False with Stopwatch() as stopWatch: self._restart = False self._restartHandler = restartHandler self._superManager = SuperManager(self) self._superManager.initManagers() self._superManager.onStart() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand(['systemctl', 'start', 'hermesledcontrol']) self._superManager.onBooted() self._logger.logInfo(f'Started in {stopWatch} seconds') self._booted = True @property def name(self) -> str: return self.NAME @property def isBooted(self) -> bool: return self._booted @property def restart(self) -> bool: return self._restart @restart.setter def restart(self, value: bool): self._restart = value def doRestart(self): self._restart = True self.onStop() def onStop(self): self._logger.logInfo('Shutting down') self._superManager.onStop() if self._superManager.configManager.getAliceConfigByName('useHLC'): self._superManager.commons.runRootSystemCommand(['systemctl', 'stop', 'hermesledcontrol']) self.INSTANCE = None self._restartHandler() def updateProjectAlice(self): self._logger.logInfo('Checking for satellite updates') req = requests.get(url=f'{constants.GITHUB_API_URL}/ProjectAliceSatellite/branches', auth=SuperManager.getInstance().configManager.getGithubAuth()) if req.status_code != 200: self._logger.logWarning('Failed checking for updates') return userUpdatePref = SuperManager.getInstance().configManager.getAliceConfigByName('aliceUpdateChannel') if userUpdatePref == 'master': candidate = 'master' else: candidate = Version.fromString(constants.VERSION) for branch in req.json(): repoVersion = Version.fromString(branch['name']) if not repoVersion.isVersionNumber: continue releaseType = repoVersion.releaseType if userUpdatePref == 'rc' and releaseType in {'b', 'a'} or userUpdatePref == 'beta' and releaseType == 'a': continue if repoVersion > candidate: candidate = repoVersion self._logger.logInfo(f'Checking on "{str(candidate)}" update channel') commons = SuperManager.getInstance().commons commons.runSystemCommand(['git', '-C', commons.rootDir(), 'stash']) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'clean', '-df']) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'checkout', str(candidate)]) commons.runSystemCommand(['git', '-C', commons.rootDir(), 'pull'])