parent
							
								
									03cfc4c80a
								
							
						
					
					
						commit
						876c8b53cc
					
				
				 5 changed files with 564 additions and 8 deletions
			
			
		@ -0,0 +1,36 @@ | 
				
			||||
class COLORS: | 
				
			||||
  def __init__(self): | 
				
			||||
    self.HEADER = '\033[95m' | 
				
			||||
    self.OKBLUE = '\033[94m' | 
				
			||||
    self.CBLUE = '\33[44m' | 
				
			||||
    self.BOLD = '\033[1m' | 
				
			||||
    self.CITALIC = '\33[3m' | 
				
			||||
    self.OKGREEN = '\033[92m' | 
				
			||||
    self.CWHITE = '\33[37m' | 
				
			||||
    self.ENDC = '\033[0m' + self.CWHITE | 
				
			||||
    self.UNDERLINE = '\033[4m' | 
				
			||||
    self.PINK = '\33[38;5;207m' | 
				
			||||
    self.PRETTY_YELLOW = self.BASE(220) | 
				
			||||
 | 
				
			||||
    self.RED = '\033[91m' | 
				
			||||
    self.PURPLE_BG = '\33[45m' | 
				
			||||
    self.YELLOW = '\033[93m' | 
				
			||||
    self.BLUE_GREEN = self.BASE(85) | 
				
			||||
 | 
				
			||||
    self.FAIL = self.RED | 
				
			||||
    # self.INFO = self.PURPLE_BG | 
				
			||||
    self.INFO = self.BASE(207) | 
				
			||||
    self.SUCCESS = self.OKGREEN | 
				
			||||
    self.PROMPT = self.YELLOW | 
				
			||||
    self.DBLUE = '\033[36m' | 
				
			||||
    self.CYAN = self.BASE(39) | 
				
			||||
    self.WARNING = '\033[33m' | 
				
			||||
 | 
				
			||||
  def BASE(self, col):  # seems to support more colors | 
				
			||||
    return '\33[38;5;{}m'.format(col) | 
				
			||||
 | 
				
			||||
  def BASEBG(self, col):  # seems to support more colors | 
				
			||||
    return '\33[48;5;{}m'.format(col) | 
				
			||||
 | 
				
			||||
 | 
				
			||||
COLORS = COLORS() | 
				
			||||
@ -0,0 +1,193 @@ | 
				
			||||
#!/usr/bin/env python3 | 
				
			||||
import os | 
				
			||||
import json | 
				
			||||
from atomicwrites import atomic_write | 
				
			||||
from common.colors import COLORS | 
				
			||||
from common.basedir import BASEDIR | 
				
			||||
from selfdrive.hardware import TICI | 
				
			||||
try: | 
				
			||||
  from common.realtime import sec_since_boot | 
				
			||||
except ImportError: | 
				
			||||
  import time | 
				
			||||
  sec_since_boot = time.time | 
				
			||||
 | 
				
			||||
warning = lambda msg: print('{}opParams WARNING: {}{}'.format(COLORS.WARNING, msg, COLORS.ENDC)) | 
				
			||||
error = lambda msg: print('{}opParams ERROR: {}{}'.format(COLORS.FAIL, msg, COLORS.ENDC)) | 
				
			||||
 | 
				
			||||
NUMBER = [float, int]  # value types | 
				
			||||
NONE_OR_NUMBER = [type(None), float, int] | 
				
			||||
 | 
				
			||||
BASEDIR = os.path.dirname(BASEDIR) | 
				
			||||
PARAMS_DIR = os.path.join(BASEDIR, 'community', 'params') | 
				
			||||
IMPORTED_PATH = os.path.join(PARAMS_DIR, '.imported') | 
				
			||||
OLD_PARAMS_FILE = os.path.join(BASEDIR, 'op_params.json') | 
				
			||||
 | 
				
			||||
 | 
				
			||||
class Param: | 
				
			||||
  def __init__(self, default, allowed_types=[], description=None, *, static=False, live=False, hidden=False):  # pylint: disable=dangerous-default-value | 
				
			||||
    self.default_value = default  # value first saved and returned if actual value isn't a valid type | 
				
			||||
    if not isinstance(allowed_types, list): | 
				
			||||
      allowed_types = [allowed_types] | 
				
			||||
    self.allowed_types = allowed_types  # allowed python value types for opEdit | 
				
			||||
    self.description = description  # description to be shown in opEdit | 
				
			||||
    self.hidden = hidden  # hide this param to user in opEdit | 
				
			||||
    self.live = live  # show under the live menu in opEdit | 
				
			||||
    self.static = static  # use cached value, never reads to update | 
				
			||||
    self._create_attrs() | 
				
			||||
 | 
				
			||||
  def is_valid(self, value): | 
				
			||||
    if not self.has_allowed_types:  # always valid if no allowed types, otherwise checks to make sure | 
				
			||||
      return True | 
				
			||||
    return type(value) in self.allowed_types | 
				
			||||
 | 
				
			||||
  def _create_attrs(self):  # Create attributes and check Param is valid | 
				
			||||
    self.has_allowed_types = isinstance(self.allowed_types, list) and len(self.allowed_types) > 0 | 
				
			||||
    self.has_description = self.description is not None | 
				
			||||
    self.is_list = list in self.allowed_types | 
				
			||||
    self.read_frequency = None if self.static else (1 if self.live else 10)  # how often to read param file (sec) | 
				
			||||
    self.last_read = -1 | 
				
			||||
    if self.has_allowed_types: | 
				
			||||
      assert type(self.default_value) in self.allowed_types, 'Default value type must be in specified allowed_types!' | 
				
			||||
    if self.is_list: | 
				
			||||
      self.allowed_types.remove(list) | 
				
			||||
 | 
				
			||||
 | 
				
			||||
def _read_param(key):  # Returns None, False if a json error occurs | 
				
			||||
  try: | 
				
			||||
    with open(os.path.join(PARAMS_DIR, key), 'r') as f: | 
				
			||||
      value = json.loads(f.read()) | 
				
			||||
    return value, True | 
				
			||||
  except json.decoder.JSONDecodeError: | 
				
			||||
    return None, False | 
				
			||||
 | 
				
			||||
 | 
				
			||||
def _write_param(key, value): | 
				
			||||
  param_path = os.path.join(PARAMS_DIR, key) | 
				
			||||
  with atomic_write(param_path, overwrite=True) as f: | 
				
			||||
    f.write(json.dumps(value)) | 
				
			||||
 | 
				
			||||
 | 
				
			||||
def _import_params(): | 
				
			||||
  if os.path.exists(OLD_PARAMS_FILE) and not os.path.exists(IMPORTED_PATH):  # if opParams needs to import from old params file | 
				
			||||
    try: | 
				
			||||
      with open(OLD_PARAMS_FILE, 'r') as f: | 
				
			||||
        old_params = json.loads(f.read()) | 
				
			||||
      for key in old_params: | 
				
			||||
        _write_param(key, old_params[key]) | 
				
			||||
      open(IMPORTED_PATH, 'w').close() | 
				
			||||
    except:  # pylint: disable=bare-except | 
				
			||||
      pass | 
				
			||||
 | 
				
			||||
 | 
				
			||||
class opParams: | 
				
			||||
  def __init__(self): | 
				
			||||
    """ | 
				
			||||
      To add your own parameter to opParams in your fork, simply add a new entry in self.fork_params, instancing a new Param class with at minimum a default value. | 
				
			||||
      The allowed_types and description args are not required but highly recommended to help users edit their parameters with opEdit safely. | 
				
			||||
        - The description value will be shown to users when they use opEdit to change the value of the parameter. | 
				
			||||
        - The allowed_types arg is used to restrict what kinds of values can be entered with opEdit so that users can't crash openpilot with unintended behavior. | 
				
			||||
          (setting a param intended to be a number with a boolean, or viceversa for example) | 
				
			||||
          Limiting the range of floats or integers is still recommended when `.get`ting the parameter. | 
				
			||||
          When a None value is allowed, use `type(None)` instead of None, as opEdit checks the type against the values in the arg with `isinstance()`. | 
				
			||||
        - If you want your param to update within a second, specify live=True. If your param is designed to be read once, specify static=True. | 
				
			||||
          Specifying neither will have the param update every 10 seconds if constantly .get() | 
				
			||||
          If the param is not static, call the .get() function on it in the update function of the file you're reading from to use live updating | 
				
			||||
 | 
				
			||||
      Here's an example of a good fork_param entry: | 
				
			||||
      self.fork_params = {'camera_offset': Param(0.06, allowed_types=NUMBER), live=True}  # NUMBER allows both floats and ints | 
				
			||||
    """ | 
				
			||||
 | 
				
			||||
    self.fork_params = { | 
				
			||||
      'SETME_X1': Param(1, NUMBER, 'Always 1', live=True), | 
				
			||||
      'SETME_X3': Param(1, NUMBER, 'Sometimes 3, mostly 1?', live=True), | 
				
			||||
      'PERCENTAGE': Param(100, NUMBER, '100 when not touching wheel, 0 when touching wheel', live=True), | 
				
			||||
      'SETME_X64': Param(100, NUMBER, 'Unsure', live=True), | 
				
			||||
      'ANGLE': Param(0, NUMBER, 'Rate limit? Lower is better?', live=True), | 
				
			||||
      'LTA_REQUEST_TYPE': Param(1, NUMBER, '1: LTA, 3: LTA for lane keeping', live=True), | 
				
			||||
      'BIT': Param(0, NUMBER, '1: LTA, 2: LTA for lane keeping', live=True), | 
				
			||||
    } | 
				
			||||
 | 
				
			||||
    self._to_delete = []  # a list of unused params you want to delete from users' params file | 
				
			||||
    self._to_reset = []  # a list of params you want reset to their default values | 
				
			||||
    self._run_init()  # restores, reads, and updates params | 
				
			||||
 | 
				
			||||
  def _run_init(self):  # does first time initializing of default params | 
				
			||||
    # Two required parameters for opEdit | 
				
			||||
    self.fork_params['username'] = Param(None, [type(None), str, bool], 'Your identifier provided with any crash logs sent to Sentry.\nHelps the developer reach out to you if anything goes wrong') | 
				
			||||
    self.fork_params['op_edit_live_mode'] = Param(False, bool, 'This parameter controls which mode opEdit starts in', hidden=True) | 
				
			||||
 | 
				
			||||
    self.params = self._load_params(can_import=True) | 
				
			||||
    self._add_default_params()  # adds missing params and resets values with invalid types to self.params | 
				
			||||
    self._delete_and_reset()  # removes old params | 
				
			||||
 | 
				
			||||
  def get(self, key=None, *, force_update=False):  # key=None returns dict of all params | 
				
			||||
    if key is None: | 
				
			||||
      return self._get_all_params(to_update=force_update) | 
				
			||||
    self._check_key_exists(key, 'get') | 
				
			||||
    param_info = self.fork_params[key] | 
				
			||||
    rate = param_info.read_frequency  # will be None if param is static, so check below | 
				
			||||
 | 
				
			||||
    if (not param_info.static and sec_since_boot() - self.fork_params[key].last_read >= rate) or force_update: | 
				
			||||
      value, success = _read_param(key) | 
				
			||||
      self.fork_params[key].last_read = sec_since_boot() | 
				
			||||
      if not success:  # in case of read error, use default and overwrite param | 
				
			||||
        value = param_info.default_value | 
				
			||||
        _write_param(key, value) | 
				
			||||
      self.params[key] = value | 
				
			||||
 | 
				
			||||
    if param_info.is_valid(value := self.params[key]): | 
				
			||||
      return value  # all good, returning user's value | 
				
			||||
    print(warning('User\'s value type is not valid! Returning default'))  # somehow... it should always be valid | 
				
			||||
    return param_info.default_value  # return default value because user's value of key is not in allowed_types to avoid crashing openpilot | 
				
			||||
 | 
				
			||||
  def put(self, key, value): | 
				
			||||
    self._check_key_exists(key, 'put') | 
				
			||||
    if not self.fork_params[key].is_valid(value): | 
				
			||||
      raise Exception('opParams: Tried to put a value of invalid type!') | 
				
			||||
    self.params.update({key: value}) | 
				
			||||
    _write_param(key, value) | 
				
			||||
 | 
				
			||||
  def _load_params(self, can_import=False): | 
				
			||||
    if not os.path.exists(PARAMS_DIR): | 
				
			||||
      os.makedirs(PARAMS_DIR) | 
				
			||||
      if can_import: | 
				
			||||
        _import_params()  # just imports old params. below we read them in | 
				
			||||
 | 
				
			||||
    params = {} | 
				
			||||
    for key in os.listdir(PARAMS_DIR):  # PARAMS_DIR is guaranteed to exist | 
				
			||||
      if key.startswith('.') or key not in self.fork_params: | 
				
			||||
        continue | 
				
			||||
      value, success = _read_param(key) | 
				
			||||
      if not success: | 
				
			||||
        value = self.fork_params[key].default_value | 
				
			||||
        _write_param(key, value) | 
				
			||||
      params[key] = value | 
				
			||||
    return params | 
				
			||||
 | 
				
			||||
  def _get_all_params(self, to_update=False): | 
				
			||||
    if to_update: | 
				
			||||
      self.params = self._load_params() | 
				
			||||
    return {k: self.params[k] for k, p in self.fork_params.items() if k in self.params and not p.hidden} | 
				
			||||
 | 
				
			||||
  def _check_key_exists(self, key, met): | 
				
			||||
    if key not in self.fork_params: | 
				
			||||
      raise Exception('opParams: Tried to {} an unknown parameter! Key not in fork_params: {}'.format(met, key)) | 
				
			||||
 | 
				
			||||
  def _add_default_params(self): | 
				
			||||
    for key, param in self.fork_params.items(): | 
				
			||||
      if key not in self.params: | 
				
			||||
        self.params[key] = param.default_value | 
				
			||||
        _write_param(key, self.params[key]) | 
				
			||||
      elif not param.is_valid(self.params[key]): | 
				
			||||
        print(warning('Value type of user\'s {} param not in allowed types, replacing with default!'.format(key))) | 
				
			||||
        self.params[key] = param.default_value | 
				
			||||
        _write_param(key, self.params[key]) | 
				
			||||
 | 
				
			||||
  def _delete_and_reset(self): | 
				
			||||
    for key in list(self.params): | 
				
			||||
      if key in self._to_delete: | 
				
			||||
        del self.params[key] | 
				
			||||
        os.remove(os.path.join(PARAMS_DIR, key)) | 
				
			||||
      elif key in self._to_reset and key in self.fork_params: | 
				
			||||
        self.params[key] = self.fork_params[key].default_value | 
				
			||||
        _write_param(key, self.params[key]) | 
				
			||||
@ -0,0 +1,325 @@ | 
				
			||||
#!/usr/bin/env python3 | 
				
			||||
import time | 
				
			||||
from common.op_params import opParams | 
				
			||||
import ast | 
				
			||||
import difflib | 
				
			||||
from common.colors import COLORS | 
				
			||||
 | 
				
			||||
 | 
				
			||||
class opEdit:  # use by running `python /data/openpilot/op_edit.py` | 
				
			||||
  def __init__(self): | 
				
			||||
    self.op_params = opParams() | 
				
			||||
    self.params = None | 
				
			||||
    self.sleep_time = 0.5 | 
				
			||||
    self.live_tuning = self.op_params.get('op_edit_live_mode') | 
				
			||||
    self.username = self.op_params.get('username') | 
				
			||||
    self.type_colors = {int: COLORS.BASE(179), float: COLORS.BASE(179), | 
				
			||||
                        bool: {False: COLORS.RED, True: COLORS.OKGREEN}, | 
				
			||||
                        type(None): COLORS.BASE(177), | 
				
			||||
                        str: COLORS.BASE(77)} | 
				
			||||
 | 
				
			||||
    self.last_choice = None | 
				
			||||
 | 
				
			||||
    self.run_init() | 
				
			||||
 | 
				
			||||
  def run_init(self): | 
				
			||||
    if self.username is None: | 
				
			||||
      self.success('\nWelcome to the {}opParams{} command line editor!'.format(COLORS.CYAN, COLORS.SUCCESS), sleep_time=0) | 
				
			||||
      self.prompt('Would you like to add your Discord username for easier crash debugging for the fork owner?') | 
				
			||||
      self.prompt('Your username is only used for reaching out if a crash occurs.') | 
				
			||||
 | 
				
			||||
      username_choice = self.input_with_options(['Y', 'N', 'don\'t ask again'], default='n')[0] | 
				
			||||
      if username_choice == 0: | 
				
			||||
        self.prompt('Enter a unique identifer/Discord username:') | 
				
			||||
        username = '' | 
				
			||||
        while username == '': | 
				
			||||
          username = input('>> ').strip() | 
				
			||||
        self.op_params.put('username', username) | 
				
			||||
        self.username = username | 
				
			||||
        self.success('Thanks! Saved your username\n' | 
				
			||||
                     'Edit the \'username\' parameter at any time to update', sleep_time=1.5) | 
				
			||||
      elif username_choice == 2: | 
				
			||||
        self.op_params.put('username', False) | 
				
			||||
        self.info('Got it, bringing you into opEdit\n' | 
				
			||||
                  'Edit the \'username\' parameter at any time to update', sleep_time=1.0) | 
				
			||||
    else: | 
				
			||||
      self.success('\nWelcome to the {}opParams{} command line editor, {}!'.format(COLORS.CYAN, COLORS.SUCCESS, self.username), sleep_time=0) | 
				
			||||
 | 
				
			||||
    self.run_loop() | 
				
			||||
 | 
				
			||||
  def run_loop(self): | 
				
			||||
    while True: | 
				
			||||
      if not self.live_tuning: | 
				
			||||
        self.info('Here are all your parameters:', sleep_time=0) | 
				
			||||
        self.info('(non-static params update while driving)', end='\n', sleep_time=0) | 
				
			||||
      else: | 
				
			||||
        self.info('Here are your live parameters:', sleep_time=0) | 
				
			||||
        self.info('(changes take effect within a second)', end='\n', sleep_time=0) | 
				
			||||
      self.params = self.op_params.get(force_update=True) | 
				
			||||
      if self.live_tuning:  # only display live tunable params | 
				
			||||
        self.params = {k: v for k, v in self.params.items() if self.op_params.fork_params[k].live} | 
				
			||||
 | 
				
			||||
      values_list = [] | 
				
			||||
      for k, v in self.params.items(): | 
				
			||||
        if len(str(v)) < 20: | 
				
			||||
          v = self.color_from_type(v) | 
				
			||||
        else: | 
				
			||||
          v = '{} ... {}'.format(str(v)[:30], str(v)[-15:]) | 
				
			||||
        values_list.append(v) | 
				
			||||
 | 
				
			||||
      static = [COLORS.INFO + '(static)' + COLORS.ENDC if self.op_params.fork_params[k].static else '' for k in self.params] | 
				
			||||
 | 
				
			||||
      to_print = [] | 
				
			||||
      blue_gradient = [33, 39, 45, 51, 87] | 
				
			||||
      for idx, param in enumerate(self.params): | 
				
			||||
        line = '{}. {}: {}  {}'.format(idx + 1, param, values_list[idx], static[idx]) | 
				
			||||
        if idx == self.last_choice and self.last_choice is not None: | 
				
			||||
          line = COLORS.OKGREEN + line | 
				
			||||
        else: | 
				
			||||
          _color = blue_gradient[min(round(idx / len(self.params) * len(blue_gradient)), len(blue_gradient) - 1)] | 
				
			||||
          line = COLORS.BASE(_color) + line | 
				
			||||
        to_print.append(line) | 
				
			||||
 | 
				
			||||
      extras = {'l': ('Toggle live params', COLORS.WARNING), | 
				
			||||
                'e': ('Exit opEdit', COLORS.PINK)} | 
				
			||||
 | 
				
			||||
      to_print += ['---'] + ['{}. {}'.format(ext_col + e, ext_txt + COLORS.ENDC) for e, (ext_txt, ext_col) in extras.items()] | 
				
			||||
      print('\n'.join(to_print)) | 
				
			||||
      self.prompt('\nChoose a parameter to edit (by index or name):') | 
				
			||||
 | 
				
			||||
      choice = input('>> ').strip().lower() | 
				
			||||
      parsed, choice = self.parse_choice(choice, len(to_print) - len(extras)) | 
				
			||||
      if parsed == 'continue': | 
				
			||||
        continue | 
				
			||||
      elif parsed == 'change': | 
				
			||||
        self.last_choice = choice | 
				
			||||
        self.change_parameter(choice) | 
				
			||||
      elif parsed == 'live': | 
				
			||||
        self.last_choice = None | 
				
			||||
        self.live_tuning = not self.live_tuning | 
				
			||||
        self.op_params.put('op_edit_live_mode', self.live_tuning)  # for next opEdit startup | 
				
			||||
      elif parsed == 'exit': | 
				
			||||
        return | 
				
			||||
 | 
				
			||||
  def parse_choice(self, choice, opt_len): | 
				
			||||
    if choice.isdigit(): | 
				
			||||
      choice = int(choice) | 
				
			||||
      choice -= 1 | 
				
			||||
      if choice not in range(opt_len):  # number of options to choose from | 
				
			||||
        self.error('Not in range!') | 
				
			||||
        return 'continue', choice | 
				
			||||
      return 'change', choice | 
				
			||||
 | 
				
			||||
    if choice in ['l', 'live']:  # live tuning mode | 
				
			||||
      return 'live', choice | 
				
			||||
    elif choice in ['exit', 'e', '']: | 
				
			||||
      self.error('Exiting opEdit!', sleep_time=0) | 
				
			||||
      return 'exit', choice | 
				
			||||
    else:  # find most similar param to user's input | 
				
			||||
      param_sims = [(idx, self.str_sim(choice, param.lower())) for idx, param in enumerate(self.params)] | 
				
			||||
      param_sims = [param for param in param_sims if param[1] > 0.33] | 
				
			||||
      if len(param_sims) > 0: | 
				
			||||
        chosen_param = sorted(param_sims, key=lambda param: param[1], reverse=True)[0] | 
				
			||||
        return 'change', chosen_param[0]  # return idx | 
				
			||||
 | 
				
			||||
    self.error('Invalid choice!') | 
				
			||||
    return 'continue', choice | 
				
			||||
 | 
				
			||||
  def str_sim(self, a, b): | 
				
			||||
    return difflib.SequenceMatcher(a=a, b=b).ratio() | 
				
			||||
 | 
				
			||||
  def change_parameter(self, choice): | 
				
			||||
    while True: | 
				
			||||
      chosen_key = list(self.params)[choice] | 
				
			||||
      param_info = self.op_params.fork_params[chosen_key] | 
				
			||||
 | 
				
			||||
      old_value = self.params[chosen_key] | 
				
			||||
      if not param_info.static: | 
				
			||||
        self.info2('Chosen parameter: {}{} (live!)'.format(chosen_key, COLORS.BASE(207)), sleep_time=0) | 
				
			||||
      else: | 
				
			||||
        self.info2('Chosen parameter: {}{} (static)'.format(chosen_key, COLORS.BASE(207)), sleep_time=0) | 
				
			||||
 | 
				
			||||
      to_print = [] | 
				
			||||
      if param_info.has_description: | 
				
			||||
        to_print.append(COLORS.OKGREEN + '>>  Description: {}'.format(param_info.description.replace('\n', '\n  > ')) + COLORS.ENDC) | 
				
			||||
      if param_info.static: | 
				
			||||
        to_print.append(COLORS.WARNING + '>>  A reboot is required for changes to this parameter!' + COLORS.ENDC) | 
				
			||||
      if not param_info.static and not param_info.live: | 
				
			||||
        to_print.append(COLORS.WARNING + '>>  Changes take effect within 10 seconds for this parameter!' + COLORS.ENDC) | 
				
			||||
      if param_info.has_allowed_types: | 
				
			||||
        to_print.append(COLORS.RED + '>>  Allowed types: {}'.format(', '.join([at.__name__ for at in param_info.allowed_types])) + COLORS.ENDC) | 
				
			||||
      to_print.append(COLORS.WARNING + '>>  Default value: {}'.format(self.color_from_type(param_info.default_value)) + COLORS.ENDC) | 
				
			||||
 | 
				
			||||
      if to_print: | 
				
			||||
        print('\n{}\n'.format('\n'.join(to_print))) | 
				
			||||
 | 
				
			||||
      if param_info.is_list: | 
				
			||||
        self.change_param_list(old_value, param_info, chosen_key)  # TODO: need to merge the code in this function with the below to reduce redundant code | 
				
			||||
        return | 
				
			||||
 | 
				
			||||
      self.info('Current value: {}{} (type: {})'.format(self.color_from_type(old_value), COLORS.INFO, type(old_value).__name__), sleep_time=0) | 
				
			||||
 | 
				
			||||
      while True: | 
				
			||||
        self.prompt('\nEnter your new value (enter to exit):') | 
				
			||||
        new_value = input('>> ').strip() | 
				
			||||
        if new_value == '': | 
				
			||||
          self.info('Exiting this parameter...\n') | 
				
			||||
          return | 
				
			||||
 | 
				
			||||
        new_value = self.str_eval(new_value) | 
				
			||||
        if not param_info.is_valid(new_value): | 
				
			||||
          self.error('The type of data you entered ({}) is not allowed with this parameter!'.format(type(new_value).__name__)) | 
				
			||||
          continue | 
				
			||||
 | 
				
			||||
        if not param_info.static:  # stay in live tuning interface | 
				
			||||
          self.op_params.put(chosen_key, new_value) | 
				
			||||
          self.success('Saved {} with value: {}{}! (type: {})'.format(chosen_key, self.color_from_type(new_value), COLORS.SUCCESS, type(new_value).__name__)) | 
				
			||||
        else:  # else ask to save and break | 
				
			||||
          self.warning('\nOld value: {}{} (type: {})'.format(self.color_from_type(old_value), COLORS.WARNING, type(old_value).__name__)) | 
				
			||||
          self.success('New value: {}{} (type: {})'.format(self.color_from_type(new_value), COLORS.OKGREEN, type(new_value).__name__), sleep_time=0) | 
				
			||||
          self.prompt('\nDo you want to save this?') | 
				
			||||
          if self.input_with_options(['Y', 'N'], 'N')[0] == 0: | 
				
			||||
            self.op_params.put(chosen_key, new_value) | 
				
			||||
            self.success('Saved!') | 
				
			||||
          else: | 
				
			||||
            self.info('Not saved!') | 
				
			||||
          return | 
				
			||||
 | 
				
			||||
  def change_param_list(self, old_value, param_info, chosen_key): | 
				
			||||
    while True: | 
				
			||||
      self.info('Current value: {} (type: {})'.format(old_value, type(old_value).__name__), sleep_time=0) | 
				
			||||
      self.prompt('\nEnter index to edit (0 to {}):'.format(len(old_value) - 1)) | 
				
			||||
      choice_idx = self.str_eval(input('>> ')) | 
				
			||||
      if choice_idx == '': | 
				
			||||
        self.info('Exiting this parameter...') | 
				
			||||
        return | 
				
			||||
 | 
				
			||||
      if not isinstance(choice_idx, int) or choice_idx not in range(len(old_value)): | 
				
			||||
        self.error('Must be an integar within list range!') | 
				
			||||
        continue | 
				
			||||
 | 
				
			||||
      while True: | 
				
			||||
        self.info('Chosen index: {}'.format(choice_idx), sleep_time=0) | 
				
			||||
        self.info('Value: {} (type: {})'.format(old_value[choice_idx], type(old_value[choice_idx]).__name__), sleep_time=0) | 
				
			||||
        self.prompt('\nEnter your new value:') | 
				
			||||
        new_value = input('>> ').strip() | 
				
			||||
        if new_value == '': | 
				
			||||
          self.info('Exiting this list item...') | 
				
			||||
          break | 
				
			||||
 | 
				
			||||
        new_value = self.str_eval(new_value) | 
				
			||||
        if not param_info.is_valid(new_value): | 
				
			||||
          self.error('The type of data you entered ({}) is not allowed with this parameter!'.format(type(new_value).__name__)) | 
				
			||||
          continue | 
				
			||||
 | 
				
			||||
        old_value[choice_idx] = new_value | 
				
			||||
 | 
				
			||||
        self.op_params.put(chosen_key, old_value) | 
				
			||||
        self.success('Saved {} with value: {}{}! (type: {})'.format(chosen_key, self.color_from_type(new_value), COLORS.SUCCESS, type(new_value).__name__), end='\n') | 
				
			||||
        break | 
				
			||||
 | 
				
			||||
  def color_from_type(self, v): | 
				
			||||
    v_color = '' | 
				
			||||
    if type(v) in self.type_colors: | 
				
			||||
      v_color = self.type_colors[type(v)] | 
				
			||||
      if isinstance(v, bool): | 
				
			||||
        v_color = v_color[v] | 
				
			||||
    v = '{}{}{}'.format(v_color, v, COLORS.ENDC) | 
				
			||||
    return v | 
				
			||||
 | 
				
			||||
  def cyan(self, msg, end=''): | 
				
			||||
    msg = self.str_color(msg, style='cyan') | 
				
			||||
    # print(msg, flush=True, end='\n' + end) | 
				
			||||
    return msg | 
				
			||||
 | 
				
			||||
  def prompt(self, msg, end=''): | 
				
			||||
    msg = self.str_color(msg, style='prompt') | 
				
			||||
    print(msg, flush=True, end='\n' + end) | 
				
			||||
 | 
				
			||||
  def warning(self, msg, end=''): | 
				
			||||
    msg = self.str_color(msg, style='warning') | 
				
			||||
    print(msg, flush=True, end='\n' + end) | 
				
			||||
 | 
				
			||||
  def info(self, msg, sleep_time=None, end=''): | 
				
			||||
    if sleep_time is None: | 
				
			||||
      sleep_time = self.sleep_time | 
				
			||||
    msg = self.str_color(msg, style='info') | 
				
			||||
 | 
				
			||||
    print(msg, flush=True, end='\n' + end) | 
				
			||||
    time.sleep(sleep_time) | 
				
			||||
 | 
				
			||||
  def info2(self, msg, sleep_time=None, end=''): | 
				
			||||
    if sleep_time is None: | 
				
			||||
      sleep_time = self.sleep_time | 
				
			||||
    msg = self.str_color(msg, style=86) | 
				
			||||
 | 
				
			||||
    print(msg, flush=True, end='\n' + end) | 
				
			||||
    time.sleep(sleep_time) | 
				
			||||
 | 
				
			||||
  def error(self, msg, sleep_time=None, end='', surround=True): | 
				
			||||
    if sleep_time is None: | 
				
			||||
      sleep_time = self.sleep_time | 
				
			||||
    msg = self.str_color(msg, style='fail', surround=surround) | 
				
			||||
 | 
				
			||||
    print(msg, flush=True, end='\n' + end) | 
				
			||||
    time.sleep(sleep_time) | 
				
			||||
 | 
				
			||||
  def success(self, msg, sleep_time=None, end=''): | 
				
			||||
    if sleep_time is None: | 
				
			||||
      sleep_time = self.sleep_time | 
				
			||||
    msg = self.str_color(msg, style='success') | 
				
			||||
 | 
				
			||||
    print(msg, flush=True, end='\n' + end) | 
				
			||||
    time.sleep(sleep_time) | 
				
			||||
 | 
				
			||||
  @staticmethod | 
				
			||||
  def str_color(msg, style, surround=False): | 
				
			||||
    if style == 'success': | 
				
			||||
      style = COLORS.SUCCESS | 
				
			||||
    elif style == 'fail': | 
				
			||||
      style = COLORS.FAIL | 
				
			||||
    elif style == 'prompt': | 
				
			||||
      style = COLORS.PROMPT | 
				
			||||
    elif style == 'info': | 
				
			||||
      style = COLORS.INFO | 
				
			||||
    elif style == 'cyan': | 
				
			||||
      style = COLORS.CYAN | 
				
			||||
    elif style == 'warning': | 
				
			||||
      style = COLORS.WARNING | 
				
			||||
    elif isinstance(style, int): | 
				
			||||
      style = COLORS.BASE(style) | 
				
			||||
 | 
				
			||||
    if surround: | 
				
			||||
      msg = '{}--------\n{}\n{}--------{}'.format(style, msg, COLORS.ENDC + style, COLORS.ENDC) | 
				
			||||
    else: | 
				
			||||
      msg = '{}{}{}'.format(style, msg, COLORS.ENDC) | 
				
			||||
 | 
				
			||||
    return msg | 
				
			||||
 | 
				
			||||
  def input_with_options(self, options, default=None): | 
				
			||||
    """ | 
				
			||||
    Takes in a list of options and asks user to make a choice. | 
				
			||||
    The most similar option list index is returned along with the similarity percentage from 0 to 1 | 
				
			||||
    """ | 
				
			||||
    user_input = input('[{}]: '.format('/'.join(options))).lower().strip() | 
				
			||||
    if not user_input: | 
				
			||||
      return default, 0.0 | 
				
			||||
    sims = [self.str_sim(i.lower().strip(), user_input) for i in options] | 
				
			||||
    argmax = sims.index(max(sims)) | 
				
			||||
    return argmax, sims[argmax] | 
				
			||||
 | 
				
			||||
  def str_eval(self, dat): | 
				
			||||
    dat = dat.strip() | 
				
			||||
    try: | 
				
			||||
      dat = ast.literal_eval(dat) | 
				
			||||
    except: | 
				
			||||
      if dat.lower() == 'none': | 
				
			||||
        dat = None | 
				
			||||
      elif dat.lower() == 'false': | 
				
			||||
        dat = False | 
				
			||||
      elif dat.lower() == 'true':  # else, assume string | 
				
			||||
        dat = True | 
				
			||||
    return dat | 
				
			||||
 | 
				
			||||
 | 
				
			||||
opEdit() | 
				
			||||
					Loading…
					
					
				
		Reference in new issue