コード例 #1
0
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)
コード例 #2
0
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)
コード例 #3
0
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)
コード例 #4
0
ファイル: alice.py プロジェクト: LazzaAU/AliceCLI
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)
コード例 #5
0
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)
コード例 #6
0
ファイル: alice.py プロジェクト: LazzaAU/AliceCLI
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)
コード例 #7
0
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)
コード例 #8
0
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)
コード例 #9
0
def displayLogs(ctx: click.Context, file: str):
    try:
        commons.sshCmd(f'tail -n 250 -f {file} & {{ read ; kill %1; }}')
    except:
        try:
            commons.SSH.exec_command('\r')
        except:
            address = commons.CONNECTED_TO
            ctx.invoke(commons.disconnect)
            if commons.tryReconnect(ctx, address):
                ctx.invoke(displayLogs, file)
            else:
                commons.printError('Connection to Alice lost')

    commons.returnToMainMenu(ctx)
コード例 #10
0
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)
コード例 #11
0
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)
コード例 #12
0
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)
コード例 #13
0
def getDeviceName(ctx: click.Context) -> str:
    answer = prompt(
        questions={
            'type':
            'list',
            'name':
            'device',
            'message':
            'Select your device',
            'choices': [
                'respeaker2', 'respeaker4', 'respeaker4MicLinearArray',
                'respeaker6MicArray'
            ]
        })

    if not answer:
        commons.printError('Cannot continue without device information')
        commons.returnToMainMenu(ctx)
        return ''

    return answer['device']
コード例 #14
0
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)
コード例 #15
0
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)
コード例 #16
0
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)
コード例 #17
0
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)
コード例 #18
0
def prepareSdCard(ctx: click.Context):  # NOSONAR
	if not commons.isAdmin():
		commons.returnToMainMenu(ctx=ctx, pause=True, message='You need admin rights for this, please restart Alice CLI with admin elevated rights.')
		return

	operatingSystem = platform.system().lower()

	balenaExecutablePath = getBalenaPath()
	flasherAvailable = balenaExecutablePath != None

	downloadsPath = Path.home() / 'Downloads'

	doFlash = inquirer.confirm(
		message='Do you want to flash your SD card with Raspberry PI OS?',
		default=False
	).execute()

	installBalena = False
	if not flasherAvailable:
		installBalena = inquirer.confirm(
			message='balena-cli was not found on your system. It is required for working with SD cards, do you want to install it?',
			default=True
		)

	if not flasherAvailable and not installBalena:
		commons.returnToMainMenu(ctx, pause=True, message='Well then, I cannot work with your SD card without the appropriate tool to do it')
		return
	elif not flasherAvailable and installBalena:
		commons.printInfo('Installing Balena-cli, please wait...')
		balenaVersion = 'v13.1.1'
		if operatingSystem == 'windows':
			url = f'https://github.com/balena-io/balena-cli/releases/download/{balenaVersion}/balena-cli-{balenaVersion}-windows-x64-installer.exe'
		elif operatingSystem == 'linux':
			url = f'https://github.com/balena-io/balena-cli/releases/download/{balenaVersion}/balena-cli-{balenaVersion}-linux-x64-standalone.zip'
		else:
			url = f'https://github.com/balena-io/balena-cli/releases/download/{balenaVersion}/balena-cli-{balenaVersion}-macOS-x64-installer.pkg'

		destination = downloadsPath / url.split('/')[-1]

		if destination.exists():
			commons.printInfo(f'Skipping download, using existing: {destination}')
		else:
			commons.printInfo('Downloading...')
			doDownload(url=url, destination=destination)

		if operatingSystem == 'windows':
			commons.printInfo("Starting the installation, please follow the instructions and come back when it's done!")
			subprocess.Popen(str(destination).split(), shell=True)
			click.pause('Press a key when the installation process is done! Please close your terminal and restart it to continue the flashing process')
			exit(0)
		elif operatingSystem == 'linux':
			executablePath = Path(balenaExecutablePath)
			commons.printInfo(f"Install dir: {executablePath.parent}")
			commons.printInfo(f'Extracting {destination} to {executablePath.name}...')
			archive = zipfile.ZipFile(destination)
			archive.extractall()  # extract to ./balena-cli/ i.e. sub dir of working directory.
			commons.printInfo('Setting ./balena-cli/balena as executable...')
			# set executable permission
			# from https://stackoverflow.com/questions/12791997/how-do-you-do-a-simple-chmod-x-from-within-python
			executablePath.chmod(509)  # now shell `./balena-cli/balena version` works
			commons.printInfo('Adding ./balena-cli to PATH...')
			os.environ['PATH'] += os.pathsep + str(executablePath.parent)
			sysPath = os.environ['PATH']
			commons.printInfo(f'New PATH: {sysPath}')
			click.pause('Installation Done. Press a key')
		else:
			click.pause('I have no idea how to install stuff on Mac, so I have downloaded the tool for you, please install it. Oh, and contact Psycho to let him know how to install a pkg file on Mac ;-)')
			exit(0)

	if doFlash:
		images = list()
		commons.printInfo('Checking for Raspberry PI OS images, please wait....')
		# Potential local files
		downloadsPath = Path.home() / 'Downloads'
		for file in downloadsPath.glob('*raspi*.zip'):
			images.append(str(file))

		images.append('https://downloads.raspberrypi.org/raspios_lite_armhf/images/raspios_lite_armhf-2021-05-28/2021-05-07-raspios-buster-armhf-lite.zip')

		# Deactivated for now, we enforce Buster only!
		# directories = list()
		# Get a list of available images online
		# url = 'https://downloads.raspberrypi.org/raspios_lite_armhf/images/'
		# page = requests.get(url)
		# if page.status_code == 200:
		# 	soup = BeautifulSoup(page.text, features='html.parser')
		# 	directories = [url + node.get('href') for node in soup.find_all('a') if node.get('href').endswith('/')]
		# 	if directories:
		# 		directories.pop(0)  # This is the return link, remove it...
		#
		# for directory in directories:
		# 	page = requests.get(directory)
		# 	if page.status_code == 200:
		# 		soup = BeautifulSoup(page.text, features='html.parser')
		# 		images.extend([directory + node.get('href') for node in soup.find_all('a') if node.get('href').endswith('.zip')])

		commons.printInfo('Checking for available SD card drives, please wait....')
		drives = list()

		sdCards = getSdCards()
		for sdCard in sdCards:
			drives.append(Choice(sdCard, name=sdCard))

		if not drives:
			commons.returnToMainMenu(ctx, pause=True, message='Please insert your SD card first')
			return

		image = inquirer.select(
			message='Select the image you want to flash. Keep in mind we only officially support the "Buster" Raspberry pi OS distro!',
			choices=images
		).execute()

		drive = inquirer.select(
			message='Select your SD card drive',
			choices=drives
		).execute()

		commons.printInfo("Flashing SD card, please wait")

		if image.startswith('https'):
			file = downloadsPath / image.split('/')[-1]
			doDownload(url=image, destination=file)
		else:
			file = image

		if operatingSystem == 'windows' or operatingSystem == 'linux':
			if operatingSystem == 'linux':
				# this only works on distros with "sudo" support.
				balenaCommand = f'sudo {balenaExecutablePath} local flash {str(file)} --drive {drive} --yes'
			else:
				balenaCommand = f'balena local flash {str(file)} --drive {drive} --yes'.split()
			subprocess.run(balenaCommand, shell=True)
			time.sleep(5)
		else:
			commons.returnToMainMenu(ctx, pause=True, message='Flashing only supported on Windows and Linux systems for now. If you have the knowledge to implement it on other systems, feel free to pull request!')
			return

		click.pause('Flashing complete. Please eject, unplug and replug your SD back, then press any key to continue...')

	ssid = inquirer.text(
		message='Enter the name of your Wifi network',
		validate=EmptyInputValidator(commons.NO_EMPTY)
	).execute()

	password = inquirer.secret(
		message='Enter your Wifi network\'s key',
		transformer=lambda _: commons.HIDDEN
	).execute()

	country = inquirer.select(
		message='Select your country. This is used for your Wi-Fi settings!',
		default='EN',
		choices=commons.COUNTRY_CODES
	).execute()

	drives = list()
	drive = ''
	if operatingSystem == 'linux':
		# typically, boot partition is the first increment of SD device
		# e.g. on /dev/sda drive /dev/sda1 is "boot" and /dev/sda2 is "rootfs"
		# Lookup up the boot mount point path via lsblk

		sdCards = getSdCards()
		command = f'sudo lsblk -o PATH,FSTYPE,LABEL,MOUNTPOINT --json'
		output = subprocess.run(command, capture_output=True, shell=True).stdout.decode()
		blkDevices = json.loads(output)
		for device in blkDevices['blockdevices']:
			if device["path"].startswith(tuple(sdCards)) and device['fstype'] == 'vfat' and device['label'] == 'boot':
				drives.append(Choice(value=device, name=device['path']))

		if len(drives) == 0:
			commons.printError(f'For some reason I cannot find the SD boot partition mount point {drive}.')
			commons.returnToMainMenu(ctx, pause=True, message="I'm really sorry, but I just can't continue without this info, sorry for wasting your time...")

		if len(drives) == 1:
			device = drives[0].value
			commons.printInfo(f'Auto-selected {device["path"]}.')
			drive = device
	else:
		j = 0
		while len(drives) <= 0:
			j += 1
			for dp in psutil.disk_partitions():
				if 'rw,removable' not in dp.opts.lower():
					continue

				drives.append(dp.device)

			if not drives:
				if j < 3:
					drives = list()
					commons.printError('For some reason I cannot find any writable SD partition. Please eject then unplug, replug your SD back and press any key to continue')
					click.pause()
				else:
					break

	if not drive:
		drive = inquirer.select(
			message='Please select the correct SD `boot` partition',
			choices=drives
		).execute()

	needToUnmount = False
	mountDir = ''
	if operatingSystem == 'linux':
		# if device has not been mounted yet, mount in temp directory
		if drive['mountpoint'] is None:
			needToUnmount = True
			mountDir = tempfile.mkdtemp(prefix='alice-cli-mount-')
			command = f"sudo mount {drive['path']} {mountDir}"
			result = subprocess.run(command, capture_output=True, shell=True)
			if not result.returncode == 0:
				commons.printError(f"Could not mount {drive['path']} to {mountDir}.")
				commons.returnToMainMenu(ctx, pause=True)
			drive['mountpoint'] = mountDir
			commons.printInfo(f"Mounted {drive['path']} to {mountDir} temporarily.")
		else:
			commons.printInfo(f"{drive['path']} is already mounted to {drive['mountpoint']}.")
		drive = drive['mountpoint']

	# Now let's enable SSH and Wi-Fi on boot.
	commons.printInfo('Adding ssh & wifi to SD boot....')
	sshFile = Path(drive, 'ssh')
	sshFile.unlink(missing_ok=True)
	sshFile.touch()

	content = f'country={country}\n'
	content += 'ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n'
	content += 'update_config=1\n'
	content += 'network={\n'
	content += f'\tssid="{ssid}"\n'
	content += '\tscan_ssid=1\n'
	content += f'\tpsk="{password}"\n'
	content += '\tkey_mgmt=WPA-PSK\n'
	content += '}'
	Path(drive, 'wpa_supplicant.conf').write_text(content)

	if operatingSystem == 'linux' and needToUnmount and mountDir:
		command = f'sudo umount {drive}'
		result = subprocess.run(command, capture_output=True, shell=True)
		if not result.returncode == 0:
			commons.printError(f'Could not unmount {drive}.')
			commons.returnToMainMenu(ctx, pause=True)
		commons.printInfo(f'Unmounted {drive}')
		# only deletes empty dirs, so if unmounting failed for whatever reasons, we don't destroy anything
		os.rmdir(mountDir)

	commons.returnToMainMenu(ctx, pause=True, message='SD card is ready. Please plug it in your device and boot it!')
コード例 #19
0
def prepareSdCard(ctx: click.Context):

    drives = list()
    for dp in psutil.disk_partitions():
        if 'removable' not in dp.opts.lower():
            continue
        try:
            if psutil.disk_usage(
                    dp.device
            ).total / 1024 / 1024 < 300:  # boot partitions are usually tiny
                drives.append(dp.device)
        except:
            continue  # If we can't read the partition, we can't write it either

    if not drives:
        commons.printError('Please insert your SD card first')
        commons.returnToMainMenu(ctx)
        return

    questions = [{
        'type': 'list',
        'name': 'drive',
        'message': 'Select your SD card drive (boot partition)',
        'choices': drives
    }, {
        'type': 'input',
        'name': 'ssid',
        'message': 'Please enter the name of your Wifi network',
        'validate': lambda c: len(c) > 0
    }, {
        'type': 'input',
        'name': 'country',
        'message':
        'Please enter your country code (example: CH, US, DE, FR etc)',
        'validate': lambda c: len(c) == 2
    }, {
        'type': 'password',
        'name': 'password',
        'message': 'Please enter your Wifi network\'s key'
    }]

    answers = prompt(questions=questions)

    if not answers:
        commons.returnToMainMenu(ctx)

    Path(answers['drive'], 'ssh').touch()

    content = f'country={answers["country"]}\n'
    content += 'ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev\n'
    content += 'update_config=1\n'
    content += 'network={\n'
    content += f'\tssid="{answers["ssid"]}"\n'
    content += '\tscan_ssid=1\n'
    content += f'\tpsk="{answers["password"]}"\n'
    content += '\tkey_mgmt=WPA-PSK\n'
    content += '}'
    Path(answers['drive'], 'wpa_supplicant.conf').write_text(content)

    commons.printSuccess(
        'SD card ready, please plug it in your device and boot it!')
    commons.returnToMainMenu(ctx)