def upgradeSystem(ctx: click.Context): click.secho('Upgrading device\'s system, please wait', fg='yellow') commons.waitAnimation() commons.sshCmd('sudo apt-get update && sudo apt-get dist-upgrade -y') commons.printSuccess('Device upgraded!') ctx.invoke(reboot)
def systemctl(ctx: click.Context, option: str): click.secho(f'Service "{option}", please wait', fg='yellow') commons.waitAnimation() commons.sshCmd(f'sudo systemctl {option} ProjectAlice') commons.printSuccess('Done!') commons.returnToMainMenu(ctx, pause=True)
def installSoundDevice(ctx: click.Context, device: str): click.secho('Installing audio hardware', color='yellow') commons.waitAnimation() if not device: device = getDeviceName(ctx) if not device: return ctx.invoke(uninstallSoundDevice, device=device, return_to_main_menu=False) commons.waitAnimation() if device.lower() in { 'respeaker2', 'respeaker4', 'respeaker4miclineararray', 'respeaker6micarray' }: sshCmd( 'git clone https://github.com/HinTak/seeed-voicecard.git ~/seeed-voicecard/' ) sshCmd( 'git -C ~/seeed-voicecard/ checkout v5.9 && git -C ~/seeed-voicecard/ pull' ) sshCmd('cd ~/seeed-voicecard/ && sudo ./install.sh') ctx.invoke(reboot, return_to_main_menu=False) commons.printSuccess('Device installed!') commons.returnToMainMenu(ctx)
def updateSystem(ctx: click.Context): click.secho('Updating device\'s system, please wait', fg='yellow') commons.waitAnimation() commons.sshCmd('sudo apt-get update && sudo apt-get upgrade -y') commons.printSuccess('Device updated!') commons.returnToMainMenu(ctx, pause=True)
def reportBug(ctx: click.Context): click.secho('Enabling inbuilt bug reporter', fg='yellow') commons.waitAnimation() commons.sshCmd('touch ~/ProjectAlice/alice.bugreport') click.secho('Restarting Alice', fg='yellow') commons.sshCmd('sudo systemctl restart ProjectAlice') commons.printSuccess( 'Bug reporter enabled and Alice restart. As soon as a fatal error occurs and/or she is stopped, the session report will be posted to Github!' ) commons.returnToMainMenu(ctx, pause=True)
def systemctl(ctx: click.Context, option: str): click.secho(f'Service "{option}", please wait', color='yellow') commons.waitAnimation() stdin, stdout, stderr = commons.SSH.exec_command(f'sudo systemctl {option} ProjectAlice') line = stdout.readline() while line: click.secho(line, nl=False, color='yellow') line = stdout.readline() commons.printSuccess('Done!') commons.returnToMainMenu(ctx)
def updateAlice(ctx: click.Context): click.secho('Updating Alice, please wait', color='yellow') commons.waitAnimation() commons.SSH.exec_command('sudo systemctl stop ProjectAlice') stdin, stdout, stderr = commons.SSH.exec_command('cd ~/ProjectAlice && git pull && git submodules foreach git pull') line = stdout.readline() while line: click.secho(line, nl=False, color='yellow') line = stdout.readline() commons.SSH.exec_command('sudo systemctl start ProjectAlice') commons.printSuccess('Alice updated!') commons.returnToMainMenu(ctx)
def changePassword(ctx: click.Context, current_password: str = None, password: str = None): click.secho('Changing password', fg='yellow') if not password or not current_password: current_password = inquirer.secret( message='Enter current password (default: raspberry)', default='raspberry', transformer=lambda _: commons.HIDDEN, ).execute() retry = True while retry: password = inquirer.secret( message='Enter new password', transformer=lambda _: commons.HIDDEN, validate=PasswordValidator(length=8, cap=True, number=True, special=False), long_instruction= 'Password must be at least 8 characters long, contain a number and a capital letter. All this.... for your safety!' ).execute() confirm_password = inquirer.secret( message='Confirm new password', transformer=lambda _: commons.HIDDEN, ).execute() if password != confirm_password: commons.printError('New passwords do not match') retry = inquirer.confirm(message='Try again?', default=True).execute() if not retry: commons.returnToMainMenu(ctx) return else: break commons.waitAnimation() stdout, stderr = commons.sshCmdWithReturn( f'echo -e "{current_password}\n{password}\n{password}" | passwd') error = stderr.readline() if 'successfully' in error.lower(): commons.printSuccess('Password changed!') else: commons.printError(f'Something went wrong: {error}') commons.returnToMainMenu(ctx, pause=True)
def updateAlice(ctx: click.Context): click.secho('Updating Alice, please wait', fg='yellow') commons.waitAnimation() commons.sshCmd('sudo systemctl stop ProjectAlice') time.sleep(2) commons.sshCmd('rm ~/ProjectAlice/requirements.hash') commons.sshCmd('rm ~/ProjectAlice/sysrequirements.hash') commons.sshCmd('rm ~/ProjectAlice/pipuninstalls.hash') commons.sshCmd( 'cd ~/ProjectAlice && git pull && git submodules foreach git pull') time.sleep(2) commons.sshCmd('sudo systemctl start ProjectAlice') commons.printSuccess('Alice updated!') commons.returnToMainMenu(ctx, pause=True)
def reboot(ctx: click.Context, return_to_main_menu: bool = True): # NOSONAR click.secho('Rebooting device, please wait', fg='yellow') commons.waitAnimation() commons.sshCmd('sudo reboot') address = commons.CONNECTED_TO ctx.invoke(commons.disconnect) rebooted = commons.tryReconnect(ctx, address) if not rebooted: commons.printError('Failed rebooting device') else: commons.printSuccess('Device rebooted!') if return_to_main_menu: commons.returnToMainMenu(ctx, pause=True)
def disableRespeakerLeds(ctx: click.Context): click.echo('Turning off Respeaker always on leds') commons.waitAnimation() click.echo('Make sure we have pip') commons.sshCmd('sudo apt-get install python3-pip -y') click.echo('Install pixel ring our savior') commons.sshCmd( 'sudo pip3 install pixel_ring' ) # sudo is required here, that's bad, but we uninstall directly after click.echo('Testing the leds and turning off') commons.sshCmd('pixel_ring_check', hide=True) time.sleep(3) click.echo('Uninstall pixel ring as we don\'t need it anymore') commons.sshCmd('sudo pip3 uninstall pixel_ring -y') commons.stopAnimation() commons.printSuccess('Should be done!') commons.returnToMainMenu(ctx, pause=True)
def uninstallSoundDevice(ctx: click.Context, device: str, return_to_main_menu: bool = True): # NOSONAR click.secho('Uninstalling audio hardware', fg='yellow') commons.waitAnimation() if not device: device = getDeviceName() if not device: return if device.lower() in {'respeaker2', 'respeaker4', 'respeaker4miclineararray', 'respeaker6micarray'}: result = sshCmdWithReturn('test -d ~/seeed-voicecard/ && echo "1"')[0].readline() if result: sshCmd('cd ~/seeed-voicecard/ && sudo ./uninstall.sh') sshCmd('sudo rm -rf ~/seeed-voicecard/') ctx.invoke(reboot, return_to_main_menu=return_to_main_menu) commons.printSuccess('Sound device uninstalled!') if return_to_main_menu: commons.returnToMainMenu(ctx, pause=True)
def changeHostname(ctx: click.Context, hostname: str): click.secho('Changing device\'s hostname', fg='yellow') if not hostname: hostname = inquirer.text(message='Enter new device name', default='ProjectAlice', validate=lambda name: commons. validateHostname(name) is not None).execute() commons.waitAnimation() commons.sshCmd(f"sudo hostnamectl set-hostname '{hostname}'") ctx.invoke(reboot, ctx, return_to_main_menu=False) commons.waitAnimation() stdout, stderr = commons.sshCmdWithReturn('hostname') if stdout.readline().lower().strip() == hostname.lower().strip(): commons.printSuccess('Device name changed!') else: commons.printError('Failed changing device name...') commons.returnToMainMenu(ctx, pause=True)
def installAlice(ctx: click.Context, force: bool): click.secho('\nInstalling Alice, yayyyy!', color='yellow') result = sshCmdWithReturn( 'test -d ~/ProjectAlice/ && echo "1"')[0].readline() if result: if not force: commons.printError('Alice seems to already exist on that host') answer = prompt( questions={ 'type': 'confirm', 'message': 'Erase and reinstall', 'name': 'confirm', 'default': False }) if not answer['confirm']: commons.returnToMainMenu(ctx) return sshCmd('sudo systemctl stop ProjectAlice') sshCmd('sudo rm -rf ~/ProjectAlice') questions = [{ 'type': 'password', 'name': 'adminPinCode', 'message': 'Enter an admin pin code. It must be made of 4 characters, all digits only. (default: 1234)', 'default': '1234', 'validate': lambda code: code.isdigit() and int(code) < 10000 }, { 'type': 'input', 'name': 'mqttHost', 'message': 'Mqtt host:', 'default': 'localhost', 'validate': lambda string: len(string) > 0 }, { 'type': 'input', 'name': 'mqttPort', 'message': 'Mqtt port:', 'default': '1883', 'validate': lambda port: port.isdigit() }, { 'type': 'list', 'name': 'activeLanguage', 'message': 'What language should Alice be using?', 'default': 'en', 'choices': ['en', 'de', 'fr', 'it'] }, { 'type': 'input', 'name': 'activeCountryCode', 'message': 'What country code should Alice be using?', 'default': 'US', 'validate': lambda string: len(string) > 0 }] answers = prompt(questions) if len(answers) < 5: commons.returnToMainMenu(ctx) return commons.waitAnimation() confFile = Path(Path.home(), '.pacli/projectalice.yaml') confFile.parent.mkdir(parents=True, exist_ok=True) try: with requests.get( url= 'https://raw.githubusercontent.com/project-alice-assistant/ProjectAlice/master/ProjectAlice.yaml', stream=True) as r: r.raise_for_status() with confFile.open('wb') as fp: for chunk in r.iter_content(chunk_size=8192): if chunk: fp.write(chunk) except Exception as e: commons.printError(f'Failed downloading ProjectAlice.yaml {e}') commons.returnToMainMenu(ctx) with confFile.open(mode='r') as f: try: confs = yaml.safe_load(f) except yaml.YAMLError as e: commons.printError(f'Failed reading projectalice.yaml {e}') commons.returnToMainMenu(ctx) confs['adminPinCode'] = int(answers['adminPinCode']) confs['mqttHost'] = answers['mqttHost'] confs['mqttPort'] = int(answers['mqttPort']) confs['activeLanguage'] = answers['activeLanguage'] confs['activeCountryCode'] = answers['activeCountryCode'] confs['useHLC'] = 'no' with confFile.open(mode='w') as f: yaml.dump(confs, f) commons.printSuccess('Generated ProjectAlice.yaml') commons.printInfo('Updating system') sshCmd('sudo apt-get update') sshCmd('sudo apt-get install git -y') sshCmd('git config --global user.name "Han Oter"') sshCmd('git config --global user.email "*****@*****.**"') commons.printInfo('Cloning Alice') sshCmd( 'git clone https://github.com/project-alice-assistant/ProjectAlice.git ~/ProjectAlice' ) sshCmd(f'echo "{confFile.read_text()}" > ~/ProjectAlice/ProjectAlice.yaml') sshCmd('sudo cp ~/ProjectAlice/ProjectAlice.yaml /boot/ProjectAlice.yaml') commons.printInfo('Start install process') sshCmd('cd ~/ProjectAlice/ && python3 main.py') commons.printSuccess( 'Alice has completed the basic installation! She\'s now working further to complete the installation, let\'s see what she does!' ) commons.ctrlCExplained() try: sshCmd('tail -f /var/log/syslog & { read ; kill %1; }') except KeyboardInterrupt: commons.SSH.exec_command('\r') commons.returnToMainMenu(ctx)
def soundTest(ctx: click.Context): commons.printInfo( 'This will test both the recording and playback of your device') click.pause( 'Press enter and read aloud: I am testing my sound input and output') commons.printInfo('Now recording...') commons.sshCmd('arecord /tmp/test.wav -d 3') time.sleep(3.5) commons.printInfo('Now playing...') commons.sshCmd('aplay /tmp/test.wav') time.sleep(3.5) confirm = inquirer.confirm( message='Did you hear yourself speaking through the device?', default=True).execute() if confirm: commons.printSuccess( 'Great, so both your mic and speakers are working!') else: commons.printInfo( "Ok... Sorry about that... Let's try the audio output only") click.pause() commons.printInfo('Testing sound output...') commons.waitAnimation() commons.sshCmd('sudo aplay /usr/share/sounds/alsa/Front_Center.wav') commons.stopAnimation() confirm = inquirer.confirm( message= 'Ok, I played it and you should have heard the common "front, center" sound test, did you hear it?', default=True).execute() if confirm: commons.printInfo( "Ok, so this means your audio output is fine, but your mic doesn't capture anything" ) click.pause() commons.sshCmd('arecord -l') confirm = inquirer.confirm( message='Do you see your mic listed in the list above?', default=True).execute() if confirm: commons.printInfo( 'Mmmh, here some potential fixes:\n- Use "alsamixer" to raise the capture volume of your device.\n- Edit your "/etc/asound.conf" to set it up correctly.\n- Try reaching out on our Discord server, maybe others had the same issue?' ) else: commons.printInfo( 'Mmmh, here some potential fixes:\n Edit your "/etc/asound.conf" to set it up correctly.\n- Try reaching out on our Discord server, maybe others had the same issue?' ) else: commons.printInfo( "Ok, so this would mean the output doesn't work, and maybe the input works, but you can't hear the result..." ) click.pause() commons.sshCmd('aplay -l') confirm = inquirer.confirm( message= 'Do you see your audio output device listed in the list above?', default=True).execute() if confirm: commons.printInfo( 'Mmmh, here some potential fixes:\n- Use "alsamixer" to raise the output volume of your device.\n- Edit your "/etc/asound.conf" to set it up correctly.\n- Try reaching out on our Discord server, maybe others had the same issue?' ) else: commons.printInfo( 'Mmmh, here some potential fixes:\n Edit your "/etc/asound.conf" to set it up correctly.\n- Try reaching out on our Discord server, maybe others had the same issue?' ) commons.returnToMainMenu(ctx, pause=True)
def installAlice(ctx: click.Context, force: bool): click.secho('\nInstalling Alice!', fg='yellow') result = sshCmdWithReturn('test -d ~/ProjectAlice/ && echo "1"')[0].readline() if result: if not force: commons.printError('Alice seems to already exist on that host') confirm = inquirer.confirm( message='Erase and reinstall?', default=False ).execute() if confirm: commons.returnToMainMenu(ctx) return sshCmd('sudo systemctl stop ProjectAlice') sshCmd('sudo rm -rf ~/ProjectAlice') adminPinCode = inquirer.secret( message='Enter an admin pin code. It must be made of 4 characters, all digits only. (default: 1234)', default='1234', validate=lambda code: code.isdigit() and int(code) < 10000, invalid_message='Pin must be 4 numbers', transformer=lambda _: commons.HIDDEN ).execute() mqttHost = inquirer.text( message='Mqtt hostname', default='localhost', validate=EmptyInputValidator(commons.NO_EMPTY) ).execute() mqttPort = inquirer.number( message='Mqtt port', default=1883, validate=EmptyInputValidator(commons.NO_EMPTY) ).execute() activeLanguage = inquirer.select( message='What language should Alice be using?', default='en', choices=[ Choice('en', name='English'), Choice('de', name='German'), Choice('fr', name='French'), Choice('it', name='Italian'), Choice('pl', name='Polish'), ] ).execute() activeCountryCode = inquirer.select( message='Enter the country code to use.', default='EN', choices=commons.COUNTRY_CODES ).execute() releaseType = inquirer.select( message='What releases do you want to receive? If you are unsure, leave this to Stable releases!', default='master', choices=[ Choice('master', name='Stable releases'), Choice('rc', name='Release candidates'), Choice('beta', name='Beta releases'), Choice('alpha', name='Alpha releases') ] ).execute() audioDevice = inquirer.select( message='Select your audio hardware if listed', default='respeaker2', choices=[ Choice('respeaker2', name='Respeaker 2 mics'), Choice('respeaker4', name='Respeaker 4 mic array'), Choice('respeaker4MicLinear', name='Respeaker 4 mic linear array'), Choice('respeaker6', name='Respeaker 6 mic array'), Choice('respeaker7', name='Respeaker 7'), Choice('respeakerCoreV2', name='Respeaker Core version 2'), Choice('googleAIY', name='Google AIY'), Choice('googleAIY2', name='Google AIY 2'), Choice('matrixCreator', name='Matrix Creator'), Choice('matrixVoice', name='Matrix Voice'), Choice('ps3eye', name='PS3 Eye'), Choice('jabra410', name='Jabra 410'), Choice(None) ] ).execute() soundInstalled = inquirer.confirm( message='Did you already install your sound hardware using Alice CLI or confirmed it to be working?', default=False ).execute() installHLC = inquirer.confirm( message='Do you want to install HLC? HLC can pilot leds such as the ones on Respeakers to provide visual feedback.', default=False ).execute() advancedConfigs = inquirer.confirm( message='Do you want to set more advanced configs? If you do, DO NOT ABORT IN THE MIDDLE!', default=False ).execute() asr = 'coqui' tts = 'pico' awsAccessKey = '' awsSecretKey = '' googleServiceFile = '' shortReplies = False devMode = False githubUsername = '' githubToken = '' enableDataStoring = True skillAutoUpdate = True if advancedConfigs: asr = inquirer.select( message='Select the ASR engine you want to use', default='coqui', choices=[ Choice('coqui', name='Coqui'), Choice('snips', name='Snips (English only!)'), Choice('google', name='Google (Online!)'), Choice('deepspeech', name='Deepspeech'), Choice('pocketsphinx', name='PocketSphinx') ] ).execute() tts = inquirer.select( message='Select the TTS engine you want to use', default='pico', choices=[ Choice('pico', name='Coqui'), Choice('mycroft', name='Mycroft'), Choice('amazon', name='Amazon'), Choice('google', name='Google'), Choice('watson', name='Watson') ] ).execute() if tts == 'amazon': awsAccessKey = inquirer.secret( message='Enter your AWS access key', validate=EmptyInputValidator(commons.NO_EMPTY), transformer=lambda _: commons.HIDDEN ).execute() awsSecretKey = inquirer.secret( message='Enter your AWS secret key', validate=EmptyInputValidator(commons.NO_EMPTY), transformer=lambda _: commons.HIDDEN ).execute() if tts == 'google' or asr == 'google': googleServiceFile = inquirer.filepath( message='Enter your Google service file path', default='~/', validate=PathValidator(is_file=True, message='Input is not a file') ).execute() shortReplies = inquirer.confirm( message='Do you want Alice to use short replies?', default=False ).execute() devMode = inquirer.confirm( message='Do you want to activate the developer mode?', default=False ).execute() if devMode: githubUsername = inquirer.text( message='Enter your Github username. This is required for Dev Mode.', validate=EmptyInputValidator(commons.NO_EMPTY) ).execute() githubToken = inquirer.secret( message='Enter your Github access token. This is required for Dev Mode.', validate=EmptyInputValidator(commons.NO_EMPTY), transformer=lambda _: commons.HIDDEN ).execute() enableDataStoring = inquirer.confirm( message='Enable telemetry data storing?', default=True ).execute() skillAutoUpdate = inquirer.confirm( message='Enable skill auto update?', default=True ).execute() commons.waitAnimation() confFile = Path(Path.home(), '.pacli/projectalice.yaml') confFile.parent.mkdir(parents=True, exist_ok=True) updateSource = commons.getUpdateSource(releaseType) try: with requests.get(url=f'https://raw.githubusercontent.com/project-alice-assistant/ProjectAlice/{updateSource}/ProjectAlice.yaml', stream=True) as r: r.raise_for_status() with confFile.open('wb') as fp: for chunk in r.iter_content(chunk_size=8192): if chunk: fp.write(chunk) except Exception as e: commons.printError(f'Failed downloading ProjectAlice.yaml {e}') commons.returnToMainMenu(ctx, pause=True) with confFile.open(mode='r') as f: try: confs = yaml.safe_load(f) except yaml.YAMLError as e: commons.printError(f'Failed reading projectalice.yaml {e}') commons.returnToMainMenu(ctx, pause=True) confs['adminPinCode'] = str(int(adminPinCode)).zfill(4) confs['mqttHost'] = mqttHost confs['mqttPort'] = int(mqttPort) confs['activeLanguage'] = activeLanguage confs['activeCountryCode'] = activeCountryCode confs['useHLC'] = installHLC confs['aliceUpdateChannel'] = releaseType confs['skillsUpdateChannel'] = releaseType confs['ttsLanguage'] = f'{activeLanguage}-{activeCountryCode}' if soundInstalled: confs['installSound'] = False if audioDevice: confs['audioHardware'][audioDevice] = True else: confs['installSound'] = True confs['asr'] = asr confs['awsAccessKey'] = awsAccessKey confs['awsSecretKey'] = awsSecretKey confs['tts'] = tts confs['shortReplies'] = shortReplies confs['devMode'] = devMode confs['githubUsername'] = githubUsername confs['githubToken'] = githubToken confs['enableDataStoring'] = enableDataStoring confs['skillAutoUpdate'] = skillAutoUpdate if googleServiceFile and Path(googleServiceFile).exists(): confs['googleServiceFile'] = Path(googleServiceFile).read_text() if asr == 'google': confs['keepASROffline'] = False if tts in ['amazon', 'google', 'watson']: confs['keepTTSOffline'] = False with confFile.open(mode='w') as f: yaml.dump(confs, f) commons.printSuccess('Generated ProjectAlice.yaml') commons.printInfo('Updating system') sshCmd('sudo apt-get update') sshCmd('sudo apt-get install git -y') sshCmd('git config --global user.name "Han Oter"') sshCmd('git config --global user.email "*****@*****.**"') commons.printInfo('Cloning Alice') sshCmd('git clone https://github.com/project-alice-assistant/ProjectAlice.git ~/ProjectAlice') sftp = commons.SSH.open_sftp() sftp.put(str(confFile), '/home/pi/ProjectAlice/ProjectAlice.yaml') sftp.close() sshCmd('sudo rm /boot/ProjectAlice.yaml') sshCmd('sudo cp ~/ProjectAlice/ProjectAlice.yaml /boot/ProjectAlice.yaml') commons.printInfo('Start install process') sshCmd('cd ~/ProjectAlice/ && python3 main.py') commons.printSuccess('Alice has completed the basic installation! She\'s now working further to complete the installation, let\'s see what she does!') ctx.invoke(systemLogs)