Skip to content

Commit

Permalink
Start migration over to Python for emu command wrapper (#21)
Browse files Browse the repository at this point in the history
* readme

* test starting emu_utils.py from emu command

* fixs

* test

* test

* pass all!

* test

* test

* functionality for Emu python class

* functionality for Emu python class

* fixup for DEBUG mode

* test colors!

* add formatting

* add update function

* make sure update.sh is executable

* use update path

* temp

* does this fix it?

* make executable

* debug

* debug

* that works! use oh_my_comma_path for git pull

* test

* use subprocess

* debug

* fix

* better

* update using emu-utils.sh

* fix

* use python

* test

* test

* test

* debug

* debug

* only source if running first-install

* updates to install.sh

* add pandaflash2

* Add run and get_next_arg functions

* debug

* should fix it

* should fix it

* add newline

* simplify newline

* move newline

* add newline

* test

* test

* test

* test

* whoops

* whoops

* test

* test

* test

* test

* test

* work on debug command

* add controlsd

* test

* debug

* debug

* debug

* debug

* debug

* debug

* debug

* test uploader

* test logmessaged with call

* write to file

* debug

* debug

* debug

* debug

* debug

* debug

* debug

* debug

* without shell

* test check_call

* debug

* weird, that worked. how about this?

* test

* test

* ?

* test killing updated

* debug

* debug

* fix

* test more

* debug

* debug

* debug

* debug

* debug

* clean up controlsd func, add warning

* use prompt

* add orange

* add better warning

* detect if python script failed

* cause an error

* will this work?

* debug

* debug

* now cause error

* change

* test

* add comment

* add comment

* add comment

* now fix update!

* fix name

* use openpilot method of starting processes

* test

* test

* test

* test

* use old method, add backup of openpilot's process method in case this doesn't work out

* only attempt update if user tried to update and failed

* add additional message

* simplify

* add color to success message

* installfork command support

* move static methods to emu_utils.py, verify fork URl and clone to temp fork folder and move once complete

* use .bak instead of .old

* catch ctrl+c

* add end arg

* debug

* debug

* fix

* fix

* fix

* add flags to each command

* change to flag

* much better flag implementation lol

* don't need to specify arg

* add flags to help

* test help interface

* test help interface

* try blue

* test different colors

* this looks good

* formatting for list

* add test flag

* colors

* colors

* add branch alias

* print commands in help

* final updates before click

* backup emu.py

* test click

* test click

* test click

* test click

* test click

* test click

* test click

* test click

* test click

* fix old/emu.py so it at least works. flags do not

* add old emu.py back

* use click

* move

* move

* move

* add 🐼's

* add more

* more emoji

* change clone url back

* temporarily change branch

* Master cli updates patch1 (#22)

* use prettier

* allow for easier local dev?

* more consistent use of env variables

* prettier

* warn about update source

* easier local debug?

* add encoding to colors

* Update README.md

* Update README.md

* Update README.md

* add sexy ascii

* Update install.sh

* Set theme jekyll-theme-slate

* Update README.md

* use error_msg arg of print_commands for all

Co-authored-by: Shane <[email protected]>

* remove old from emu-utils.sh

* clean ups for emu.py

* add base and fork commands

* fixes for install.sh

* emu_utils updates

* remove space

* move main to base

* add update function to new format

* test

* fix for commands with no subcommands

* rename to CommandBase, add more emoji!

* switch emoji

* add emoji to commands

* fix emoji

* move

* add panda command

* add better emoji for panda to match fork's format

* add better emoji for panda to match fork's format

* fix for panda cmd

* remove an extra line from art, update longest command length

* automatically format based on longest command

* add debug to new command format, clean up imports, switch around panda messages

* remove old

* test

* test

* remove since it inherits the function

* emu.py is only 49 lines! clean up imports

* remove commented

* update aliases

* fix

* add log file flag to debug

* fix

* debug

* fix

* debug

* debug

* debug

* debug

* test required

* debug

* add flag's description to the argparser

* fix

* add final functionality for using user-supplied output file path

* display version

* display version

* remove old

* debug the wait time

* debug the wait time

* debug the wait time

* debug the wait time

* debug the wait time

* debug the wait time

* debug the wait time

* debug the wait time

* debug the wait time

* i couldn't figure it out... :(

* test showing flags

* debug

* debug

* fix

* test

* test

* test

* test

* test

* test

* don't show flags if main

* better

* add leading

* add leading

* just a test of the new menu format

* just a test of the new menu format

* test no leading

* test no leading

* remove

* add todo

* add todo

Co-authored-by: Comma Device <[email protected]>
Co-authored-by: AskAlice <[email protected]>
Co-authored-by: Alice Knag <[email protected]>
  • Loading branch information
4 people authored Jun 18, 2020
1 parent b590892 commit 9eaa44e
Show file tree
Hide file tree
Showing 12 changed files with 476 additions and 103 deletions.
4 changes: 2 additions & 2 deletions aliases.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
alias ll="ls -lhA"
alias pf="emu pandaflash"
alias controlsd="emu debug controls"
alias pf="emu panda flash"
alias controlsd="emu debug controlsd"
90 changes: 13 additions & 77 deletions emu-utils.sh
Original file line number Diff line number Diff line change
@@ -1,93 +1,29 @@
#!/bin/bash
SYSTEM_BASHRC_PATH=/home/.bashrc
export COMMUNITY_PATH=/data/community
export COMMUNITY_BASHRC_PATH=/data/community/.bashrc
export COMMUNITY_BASHRC_PATH=${COMMUNITY_PATH}/.bashrc
export OH_MY_COMMA_PATH="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"

source ${OH_MY_COMMA_PATH}/powerline.sh

source ${OH_MY_COMMA_PATH}/aliases.sh

commands="
- update: updates this tool, requires restart of ssh session
- pandaflash: flashes panda
- pandaflash2: flashes panda without make recover
- debug: debugging tools
- installfork: Specify the fork URL after. Moves openpilot to openpilot.old"
debugging_commands="
- controls: logs controlsd to /data/output.log"

function _pandaflash() {
cd /data/openpilot/panda/board && make recover
}

function _pandaflash2() {
cd /data/openpilot/panda; pkill -f boardd; PYTHONPATH=..; python -c "from panda import Panda; Panda().flash()"
}

function _controlsdebug(){
pkill -f controlsd ; PYTHONPATH=/data/openpilot python /data/openpilot/selfdrive/controls/controlsd.py 2>&1 | tee /data/output.log
}

function _installfork(){
if [ $# -lt 1 ]; then
echo "You must specify a fork URL to clone!"
return 1
fi

old_dir="/data/openpilot.old"
old_count=0
if [ -d $old_dir ]; then
while [ -d "/data/openpilot.old.${old_count}" ]; do
old_count=$((old_count+1)) # counts until we find an unused dir name
done
old_dir="${old_dir}.${old_count}"
fi

echo "Moving current openpilot installation to ${old_dir}"
mv /data/openpilot ${old_dir}
echo "Fork will be installed to /data/openpilot"
git clone $1 /data/openpilot
}

function _debug(){
if [ $# -lt 1 ]; then # verify at least two arguments
printf "You must specify a command for emu debug. Some options are:"
printf '%s\n' "$debugging_commands"
return 1
fi

if [ $1 = "controls" ]; then
_controlsdebug
else
printf "Unsupported debugging command! Try one of these:"
printf '%s\n' "$debugging_commands"
fi
}

function _updateohmycomma(){
source /data/community/.oh-my-comma/update.sh
function _updateohmycomma(){ # good to keep a backup in case python CLI is broken
source ${OH_MY_COMMA_PATH}/update.sh
source ${OH_MY_COMMA_PATH}/emu-utils.sh
}

function emu(){ # main wrapper function
if [ $# -lt 1 ]; then
printf "You must specify a command for emu. Some options are:"
printf '%s\n' "$commands"
return 1
if $(python -c 'import sys; print(".".join(map(str, sys.version_info[:3])))' | grep -q -e '^2')
then
python3 ${OH_MY_COMMA_PATH}/emu.py "$@"
else
python ${OH_MY_COMMA_PATH}/emu.py "$@"
fi

if [ $1 = "update" ]; then
if [ $? = 1 ] && [ "$1" = "update" ]; then # fallback to updating immediately if CLI crashed updating
printf "\033[91mAn error occurred in the Python CLI, attempting to manually update .oh-my-comma...\n"
printf "Press Ctrl+C to cancel!\033[0m\n"
sleep 5
_updateohmycomma
elif [ $1 = "pandaflash" ]; then
_pandaflash
elif [ $1 = "pandaflash2" ]; then
_pandaflash2
elif [ $1 = "installfork" ]; then
_installfork $2
elif [ $1 = "debug" ]; then
_debug $2
else
printf "Unsupported command! Try one of these:"
printf '%s\n' "$commands"
fi
}
48 changes: 48 additions & 0 deletions emu.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
import sys
if __package__ is None:
import sys
from os import path
sys.path.append(path.abspath(path.join(path.dirname(__file__), 'py_utils')))
sys.path.append(path.abspath(path.join(path.dirname(__file__), 'emu_commands')))

from py_utils.emu_utils import BaseFunctions
from py_utils.emu_utils import OPENPILOT_PATH
from emu_commands.fork import Fork
from emu_commands.update import Update
from emu_commands.panda import Panda
from emu_commands.debug import Debug

sys.path.append(OPENPILOT_PATH) # for importlib
DEBUG = not path.exists('/data/params/d')

class Emu(BaseFunctions):
def __init__(self, args):
self.args = args
self.commands = {'fork': Fork('🍴 control installed forks, or clone a new one'),
'update': Update('🎉 updates this tool, recommended to restart ssh session'),
'panda': Panda('🐼 panda interfacing tools'),
'debug': Debug('de-🐛-ing tools')}
self.parse()

def parse(self):
cmd = self.next_arg()

if cmd is None:
self.print_commands(error_msg='You must specify a command for emu. Some options are:', ascii_art=True)
return
if cmd not in self.commands:
self.print_commands(error_msg='Unknown command! Try one of these:')
return

self.commands[cmd].main(self.args, cmd)


if __name__ == "__main__":
args = sys.argv[1:]
if DEBUG:
args = input().split(' ')
if '' in args:
args.remove('')
emu = Emu(args)
78 changes: 78 additions & 0 deletions emu_commands/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
from py_utils.colors import COLORS
from py_utils.emu_utils import ArgumentParser, BaseFunctions, warning, success

class CommandBase(BaseFunctions):
def __init__(self, description):
self.description = description
self.commands = {}

def main(self, args, cmd_name):
self.args = args
cmd = self.next_arg()
if len(self.commands) > 0:
if cmd is None:
self.print_commands(error_msg='You must specify a command for emu {}. Some options are:'.format(cmd_name))
return
if cmd not in self.commands:
self.print_commands(error_msg='Unknown command! Try one of these:')
return
self.start_function_from_str(cmd)
else:
self.start_function_from_str(cmd_name)

def start_function_from_str(self, cmd):
cmd = '_' + cmd
if not hasattr(self, cmd):
print('Command has not been implemented yet, please try updating.')
return
getattr(self, cmd)() # call command's function

def parse_flags(self, parser):
try:
return parser.parse_args(self.args), None
except Exception as e:
return None, e

def _help(self, cmd, show_description=True, leading=''):
description = self.commands[cmd].description
if show_description:
print('{}>> Description 📚: {}{}'.format(COLORS.CYAN, description, COLORS.ENDC))

flags = self.commands[cmd].flags

flags_to_print = []
if flags is not None and len(flags) > 0:
print(leading + '{}>> Flags 🎌:{}'.format(COLORS.WARNING, COLORS.ENDC))
for flag in flags:
aliases = COLORS.SUCCESS + ', '.join(flag.aliases) + COLORS.WARNING
flags_to_print.append(leading + COLORS.WARNING + ' - {}: {}'.format(aliases, flag.description) + COLORS.ENDC)
print('\n'.join(flags_to_print))

commands = self.commands[cmd].commands
cmds_to_print = []
if commands is not None and len(commands) > 0:
print(leading + '{}>> Commands 💻:{}'.format(COLORS.OKGREEN, COLORS.ENDC))
for cmd in commands:
cmds_to_print.append(leading + COLORS.FAIL + ' - {}: {}'.format(cmd, success(commands[cmd].description, ret=True)) + COLORS.ENDC)
print('\n'.join(cmds_to_print))

class Flag:
def __init__(self, aliases, description, has_value=False):
self.aliases = aliases
self.description = description
self.has_value = has_value

class Command:
def __init__(self, description=None, commands=None, flags=None):
self.parser = ArgumentParser()
self.description = description
self.commands = commands
self.has_flags = False
self.flags = flags
if flags is not None:
self.has_flags = True
for flag in flags:
# for each flag, add it as argument with aliases.
# if flag.has_value, parse value as string, if not, assume flag is boolean
action = 'store_true' if not flag.has_value else None
self.parser.add_argument(*flag.aliases, help=flag.description, action=action)
25 changes: 25 additions & 0 deletions emu_commands/debug.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from emu_commands.base import CommandBase, Command, Flag
from py_utils.emu_utils import run, kill, warning, error
from py_utils.emu_utils import OPENPILOT_PATH

class Debug(CommandBase):
def __init__(self, description):
super().__init__(description)
self.commands = {'controlsd': Command(description='🔬 logs controlsd to /data/output.log by default',
flags=[Flag(['-o', '--output'], 'Name of file to save log to', has_value=True)])}
self.default_path = '/data/output.log'

def _controlsd(self):
flags, e = self.parse_flags(self.commands['controlsd'].parser)
if e is not None:
error(e)
return

out_file = self.default_path
if flags.output is not None:
out_file = flags.output
# r = run('pkill -f controlsd') # terminates file for some reason # todo: remove me if not needed
r = kill('selfdrive.controls.controlsd') # seems to work, some process names are weird
if r is None:
warning('controlsd is already dead! (continuing...)')
run('python {}/selfdrive/controls/controlsd.py'.format(OPENPILOT_PATH), out_file=out_file)
78 changes: 78 additions & 0 deletions emu_commands/fork.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import shutil
from os import path
from emu_commands.base import CommandBase, Command, Flag
from py_utils.emu_utils import run, error, warning, success, verify_fork_url, is_affirmative
from py_utils.emu_utils import OPENPILOT_PATH

class Fork(CommandBase):
def __init__(self, description):
super().__init__(description)
self.commands = {'install': Command(description='🦉 Whoooose fork do you wanna install?',
flags=[Flag(['clone_url'], '🍴 URL of fork to clone', has_value=True),
Flag(['-l', '--lite'], '💡 Clones only the default branch with all commits flattened for quick cloning'),
Flag(['-b', '--branch'], '🌿 Specify the branch to clone after this flag', has_value=True)])}

def _install(self):
if self.next_arg(ingest=False) is None:
error('You must supply command arguments!')
self._help('install')
return

flags, e = self.parse_flags(self.commands['install'].parser)
if e is not None:
error(e)
return

if flags.clone_url is None:
error('You must specify a fork URL to clone!')
return
if not verify_fork_url(flags.clone_url): # verify we can clone before moving folder!
error('The specified fork URL is not valid!')
return

OPENPILOT_TEMP_PATH = '{}.temp'.format(OPENPILOT_PATH)
if path.exists(OPENPILOT_TEMP_PATH):
warning('{} already exists, should it be deleted to continue?'.format(OPENPILOT_TEMP_PATH))
if is_affirmative():
shutil.rmtree(OPENPILOT_TEMP_PATH)
else:
error('Exiting...')
return

# Clone fork to temp folder
warning('Fork will be installed to {}'.format(OPENPILOT_PATH))
clone_flags = []
if flags.lite:
warning('- Performing a lite clone! (--depth 1)')
clone_flags.append('--depth 1')
if flags.branch is not None:
warning('- Only cloning branch: {}'.format(flags.branch))
clone_flags.append('-b {} --single-branch'.format(flags.branch))
if len(clone_flags):
clone_flags.append('')
try: # catch ctrl+c and clean up after
r = run('git clone {}{} {}'.format(' '.join(clone_flags), flags.clone_url, OPENPILOT_TEMP_PATH)) # clone to temp folder
except:
r = False

# If openpilot.bak exists, determine a good non-exiting path
# todo: make a folder that holds all installed forks and provide an interface of switching between them
bak_dir = '{}.bak'.format(OPENPILOT_PATH)
bak_count = 0
while path.exists(bak_dir):
bak_count += 1
bak_dir = '{}.{}'.format(bak_dir, bak_count)

if r:
success('Cloned successfully! Installing fork...')
if path.exists(OPENPILOT_PATH):
shutil.move(OPENPILOT_PATH, bak_dir) # move current installation to old dir
shutil.move(OPENPILOT_TEMP_PATH, OPENPILOT_PATH) # move new clone temp folder to main installation dir
success("Installed! Don't forget to restart your device")
else:
error('\nError cloning specified fork URL!', end='')
if path.exists(OPENPILOT_TEMP_PATH): # git usually does this for us
error(' Cleaning up...')
shutil.rmtree(OPENPILOT_TEMP_PATH)
else:
print()
20 changes: 20 additions & 0 deletions emu_commands/panda.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import importlib
from emu_commands.base import CommandBase, Command
from py_utils.emu_utils import run, error
from py_utils.emu_utils import OPENPILOT_PATH

class Panda(CommandBase):
def __init__(self, description):
super().__init__(description)
self.commands = {'flash': Command(description='🐼 flashes panda with make recover (usually works with the C2)'),
'flash2': Command(description='🎍 flashes panda using Panda module (usually works with the EON)')}

def _flash(self):
r = run('make -C {}/panda/board recover'.format(OPENPILOT_PATH))
if not r:
error('Error running make command!')

def _flash2(self):
if not run('pkill -f boardd'):
error('Error killing boardd! Is it running? (continuing...)')
importlib.import_module('panda', 'Panda').Panda().flash()
11 changes: 11 additions & 0 deletions emu_commands/update.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from emu_commands.base import CommandBase
from py_utils.emu_utils import run, error
from py_utils.emu_utils import UPDATE_PATH

class Update(CommandBase):
def __init__(self, description):
super().__init__(description)

def _update(self):
if not run(['sh', UPDATE_PATH]):
error('Error updating!')
Loading

0 comments on commit 9eaa44e

Please sign in to comment.