mirror of
https://github.com/RfidResearchGroup/ChameleonUltra.git
synced 2026-05-12 11:22:59 -07:00
Use prompt-toolkit and switch to a different command registration system
This commit is contained in:
@@ -8,7 +8,10 @@ import chameleon_com
|
||||
import chameleon_cmd
|
||||
import colorama
|
||||
import chameleon_cli_unit
|
||||
import chameleon_utils
|
||||
import os
|
||||
import prompt_toolkit
|
||||
from prompt_toolkit.formatted_text import ANSI
|
||||
|
||||
if os.name == 'posix':
|
||||
import readline
|
||||
@@ -37,94 +40,44 @@ BANNER = f"""
|
||||
"""
|
||||
|
||||
|
||||
def new_unit(unit_clz, help_msg):
|
||||
"""
|
||||
Create a new unit dict object
|
||||
:param unit_clz: unit implement class
|
||||
:param help_msg: unit usage
|
||||
:return: a dict...
|
||||
"""
|
||||
return {
|
||||
'unit': unit_clz,
|
||||
'help': help_msg,
|
||||
}
|
||||
|
||||
|
||||
class ChameleonCLI:
|
||||
"""
|
||||
CLI for chameleon
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.cmd_maps = {
|
||||
'hw': {
|
||||
'connect': new_unit(chameleon_cli_unit.HWConnect, "Connect to chameleon by serial port"),
|
||||
'chipid': {
|
||||
'get': new_unit(chameleon_cli_unit.HWChipIdGet, "Get device chipset ID"),
|
||||
'help': "Device chipsed ID get"
|
||||
},
|
||||
'address': {
|
||||
'get': new_unit(chameleon_cli_unit.HWAddressGet, "Get device address (used with Bluetooth)"),
|
||||
'help': "Device address get"
|
||||
},
|
||||
'mode': {
|
||||
'set': new_unit(chameleon_cli_unit.HWModeSet, "Change device mode to tag reader or tag emulator"),
|
||||
'get': new_unit(chameleon_cli_unit.HWModeGet, "Get current device mode"),
|
||||
'help': "Device mode get/set"
|
||||
},
|
||||
'slot': {
|
||||
'change': new_unit(chameleon_cli_unit.HWSlotSet, "Set emulation tag slot activated."),
|
||||
'type': new_unit(chameleon_cli_unit.HWSlotTagType, "Set emulation tag type"),
|
||||
'init': new_unit(chameleon_cli_unit.HWSlotDataDefault, "Set emulation tag data to default"),
|
||||
'enable': new_unit(chameleon_cli_unit.HWSlotEnableSet, "Set emulation tag slot enable or disable"),
|
||||
'nick': {
|
||||
'set': new_unit(chameleon_cli_unit.HWSlotNickSet, "Set tag nick name for slot"),
|
||||
'get': new_unit(chameleon_cli_unit.HWSlotNickGet, "Get tag nick name for slot"),
|
||||
'help': "Get/Set tag nick name for slot",
|
||||
},
|
||||
'update': new_unit(chameleon_cli_unit.HWSlotUpdate, "Update config & data to device flash"),
|
||||
'openall': new_unit(chameleon_cli_unit.HWSlotOpenAll, "Open all slot and set to default data"),
|
||||
'help': "Emulation tag slot.",
|
||||
},
|
||||
'dfu': new_unit(chameleon_cli_unit.HWDFU, "Restart application to bootloader mode(Not yet implement dfu)."),
|
||||
'help': "hardware controller",
|
||||
},
|
||||
'hf': {
|
||||
'14a': {
|
||||
'scan': new_unit(chameleon_cli_unit.HF14AScan, "Scan 14a tag, and print basic information"),
|
||||
'info': new_unit(chameleon_cli_unit.HF14AInfo, "Scan 14a tag, and print detail information"),
|
||||
'help': "ISO14443-a tag read/write/info...",
|
||||
},
|
||||
'mf': {
|
||||
'nested': new_unit(chameleon_cli_unit.HFMFNested, "Mifare Classic nested recover key"),
|
||||
'darkside': new_unit(chameleon_cli_unit.HFMFDarkside, "Mifare Classic darkside recover key"),
|
||||
'rdbl': new_unit(chameleon_cli_unit.HFMFRDBL, "MiFARE Classic read one block"),
|
||||
'wrbl': new_unit(chameleon_cli_unit.HFMFWRBL, "MiFARE Classic write one block"),
|
||||
'detection': {
|
||||
'enable': new_unit(chameleon_cli_unit.HFMFDetectionEnable, "Detection enable"),
|
||||
'count': new_unit(chameleon_cli_unit.HFMFDetectionLogCount, "Detection log count"),
|
||||
'decrypt': new_unit(chameleon_cli_unit.HFMFDetectionDecrypt, "Download log and decrypt keys"),
|
||||
'help': "Mifare Classic detection log"
|
||||
},
|
||||
'sim': new_unit(chameleon_cli_unit.HFMFSim, "Simulation a mifare classic card"),
|
||||
'eload': new_unit(chameleon_cli_unit.HFMFELoad, "Load data to emulator memory"),
|
||||
'help': "Mifare Classic mini/1/2/4, attack/read/write"
|
||||
},
|
||||
'help': "high frequency tag/reader",
|
||||
},
|
||||
'lf': {
|
||||
'em': {
|
||||
'read': new_unit(chameleon_cli_unit.LFEMRead, "Scan em410x tag and print id"),
|
||||
'write': new_unit(chameleon_cli_unit.LFEMWriteT55xx, "Write em410x id to t55xx"),
|
||||
'sim': new_unit(chameleon_cli_unit.LFEMSim, "Simulation a em410x id card"),
|
||||
'help': "EM410x read/write/emulator",
|
||||
},
|
||||
'help': "low frequency tag/reader",
|
||||
}
|
||||
}
|
||||
self.session = prompt_toolkit.PromptSession()
|
||||
|
||||
# new a device communication instance(only communication)
|
||||
self.device_com = chameleon_com.ChameleonCom()
|
||||
|
||||
def get_cmd_node(self, node: chameleon_utils.CLITree, cmdline: list[str]) -> tuple[chameleon_utils.CLITree, list[str]]:
|
||||
"""
|
||||
Recursively traverse the command line tree to get to the matching node
|
||||
|
||||
:return: last matching CLITree node, remaining tokens
|
||||
"""
|
||||
# No more subcommands to parse, return node
|
||||
if cmdline == []:
|
||||
return node, []
|
||||
|
||||
for child in node.children:
|
||||
if cmdline[0] == child.name:
|
||||
return self.get_cmd_node(child, cmdline[1:])
|
||||
|
||||
# No matching child node
|
||||
return node, cmdline[:]
|
||||
|
||||
def get_prompt(self):
|
||||
"""
|
||||
Retrieve the cli prompt
|
||||
|
||||
:return: current cmd prompt
|
||||
"""
|
||||
device_string = f"{colorama.Fore.GREEN}USB" if self.device_com.isOpen(
|
||||
) else f"{colorama.Fore.RED}Offline"
|
||||
status = f"[{device_string}{colorama.Style.RESET_ALL}] chameleon --> "
|
||||
return status
|
||||
|
||||
@staticmethod
|
||||
def print_banner():
|
||||
@@ -134,24 +87,6 @@ class ChameleonCLI:
|
||||
"""
|
||||
print(colorama.Fore.YELLOW + BANNER)
|
||||
|
||||
def parse_cli_cmd(self, cmd_str):
|
||||
"""
|
||||
parse cmd from str
|
||||
:param cmd_str:
|
||||
:return:
|
||||
"""
|
||||
cmds = cmd_str.split(" ")
|
||||
cmd_maps: dict or types.FunctionType = self.cmd_maps
|
||||
cmd_end = ""
|
||||
for cmd in cmds:
|
||||
if cmd in cmd_maps: # CMD found in map, we can continue find next
|
||||
cmd_maps = cmd_maps[cmd]
|
||||
cmd_end = cmd
|
||||
else: # CMD not found
|
||||
break
|
||||
cmd_end_position = cmd_str.index(cmd_end) + len(cmd_end) + 1
|
||||
return cmd_maps, (cmd_str[:cmd_end_position], cmd_str[cmd_end_position:])
|
||||
|
||||
def startCLI(self):
|
||||
"""
|
||||
start listen input.
|
||||
@@ -161,74 +96,73 @@ class ChameleonCLI:
|
||||
closing = False
|
||||
while True:
|
||||
# wait user input
|
||||
status = f"{colorama.Fore.GREEN}USB" if self.device_com.isOpen() else f"{colorama.Fore.RED}Offline"
|
||||
print(f"[{status}{colorama.Style.RESET_ALL}] chameleon --> ", end="")
|
||||
try:
|
||||
cmd_str = input().strip()
|
||||
cmd_str = self.session.prompt(ANSI(self.get_prompt())).strip()
|
||||
except EOFError:
|
||||
print("")
|
||||
closing = True
|
||||
|
||||
if closing or cmd_str == "exit" or cmd_str == "quit" or cmd_str.startswith('q', 0) or cmd_str.startswith('e', 0):
|
||||
except KeyboardInterrupt:
|
||||
closing = True
|
||||
|
||||
if cmd_str in ["exit", "quit", "q", "e"] or closing:
|
||||
print("Bye, thank you. ^.^ ")
|
||||
self.device_com.close()
|
||||
sys.exit(996)
|
||||
|
||||
# clear screen
|
||||
if cmd_str == "clear":
|
||||
if platform.system() == 'Windows':
|
||||
os.system("cls")
|
||||
elif platform.system() == 'Linux':
|
||||
os.system("clear")
|
||||
else:
|
||||
print("No screen clear implement")
|
||||
elif cmd_str == "clear":
|
||||
os.system('clear' if os.name == 'posix' else 'cls')
|
||||
continue
|
||||
elif cmd_str == "":
|
||||
continue
|
||||
|
||||
# parse cmd
|
||||
cmd_map, args_str = self.parse_cli_cmd(cmd_str)
|
||||
is_exec_map = 'unit' in cmd_map
|
||||
if is_exec_map:
|
||||
# new a unit instance
|
||||
unit_clz = cmd_map['unit']
|
||||
if callable(unit_clz):
|
||||
unit: chameleon_cli_unit.BaseCLIUnit = unit_clz()
|
||||
else:
|
||||
raise TypeError("CMD unit is not a 'BaseCLIUnit'")
|
||||
# set variables of required
|
||||
unit.device_com = self.device_com
|
||||
# parse args
|
||||
args_parse_result = unit.args_parser()
|
||||
if args_parse_result is not None:
|
||||
args: argparse.ArgumentParser = args_parse_result
|
||||
args.prog = args_str[0]
|
||||
try:
|
||||
args_parse_result = args.parse_args(args_str[1].split())
|
||||
except chameleon_cli_unit.ArgsParserError as e:
|
||||
args.print_usage()
|
||||
print(str(e).strip(), end="\n\n")
|
||||
continue
|
||||
except chameleon_cli_unit.ParserExitIntercept:
|
||||
# don't exit process.
|
||||
continue
|
||||
# noinspection PyBroadException
|
||||
try:
|
||||
# before process cmd, we need to do something...
|
||||
if not unit.before_exec(args_parse_result):
|
||||
continue
|
||||
# start process cmd
|
||||
unit.on_exec(args_parse_result)
|
||||
except (chameleon_cmd.UnexpectedResponseError, chameleon_cli_unit.ArgsParserError) as e:
|
||||
print(f"{colorama.Fore.RED}{str(e)}{colorama.Style.RESET_ALL}")
|
||||
except Exception:
|
||||
print(f"CLI exception: {colorama.Fore.RED}{traceback.format_exc()}{colorama.Style.RESET_ALL}")
|
||||
elif isinstance(cmd_map, dict):
|
||||
argv = cmd_str.split()
|
||||
root_cmd = argv[0]
|
||||
if root_cmd not in chameleon_cli_unit.root_commands:
|
||||
# No matching command group
|
||||
print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-"))
|
||||
for map_key in cmd_map:
|
||||
map_item = cmd_map[map_key]
|
||||
if 'help' in map_item:
|
||||
cmd_title = f"{colorama.Fore.GREEN}{map_key}{colorama.Style.RESET_ALL}"
|
||||
help_line = (f" - {cmd_title}".ljust(37)) + f"[ {map_item['help']} ]"
|
||||
print(help_line)
|
||||
for cmd_name, cmd_node in chameleon_cli_unit.root_commands.items():
|
||||
cmd_title = f"{colorama.Fore.GREEN}{cmd_name}{colorama.Style.RESET_ALL}"
|
||||
help_line = (f" - {cmd_title}".ljust(37)) + f"[ {cmd_node.helptext} ]"
|
||||
print(help_line)
|
||||
continue
|
||||
|
||||
tree_node, arg_list = self.get_cmd_node(chameleon_cli_unit.root_commands[root_cmd], argv[1:])
|
||||
|
||||
if not tree_node.cls:
|
||||
# Found tree node is a group without an implementation, print children
|
||||
print("".ljust(18, "-") + "".ljust(10) + "".ljust(30, "-"))
|
||||
for child in tree_node.children:
|
||||
cmd_title = f"{colorama.Fore.GREEN}{child.name}{colorama.Style.RESET_ALL}"
|
||||
help_line = (f" - {cmd_title}".ljust(37)) + f"[ {child.helptext} ]"
|
||||
print(help_line)
|
||||
continue
|
||||
|
||||
unit: chameleon_cli_unit.BaseCLIUnit = tree_node.cls()
|
||||
unit.device_com = self.device_com
|
||||
args_parse_result = unit.args_parser()
|
||||
|
||||
if args_parse_result is not None:
|
||||
args: argparse.ArgumentParser = args_parse_result
|
||||
args.prog = tree_node.fullname
|
||||
try:
|
||||
args_parse_result = args.parse_args(arg_list)
|
||||
except chameleon_utils.ArgsParserError as e:
|
||||
args.print_usage()
|
||||
print(str(e).strip(), end="\n\n")
|
||||
continue
|
||||
except chameleon_utils.ParserExitIntercept:
|
||||
# don't exit process.
|
||||
continue
|
||||
try:
|
||||
# before process cmd, we need to do something...
|
||||
if not unit.before_exec(args_parse_result):
|
||||
continue
|
||||
|
||||
# start process cmd
|
||||
unit.on_exec(args_parse_result)
|
||||
except (chameleon_utils.UnexpectedResponseError, chameleon_utils.ArgsParserError) as e:
|
||||
print(f"{colorama.Fore.RED}{str(e)}{colorama.Style.RESET_ALL}")
|
||||
except Exception:
|
||||
print(f"CLI exception: {colorama.Fore.RED}{traceback.format_exc()}{colorama.Style.RESET_ALL}")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
@@ -12,17 +12,11 @@ import chameleon_com
|
||||
import chameleon_cmd
|
||||
import chameleon_cstruct
|
||||
import chameleon_status
|
||||
from chameleon_utils import *
|
||||
|
||||
description_public = "Please enter correct parameters"
|
||||
|
||||
|
||||
class ArgsParserError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ParserExitIntercept(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ArgumentParserNoExit(argparse.ArgumentParser):
|
||||
"""
|
||||
@@ -180,6 +174,29 @@ class ReaderRequiredUnit(DeviceRequiredUnit):
|
||||
raise NotImplementedError("Please implement this")
|
||||
|
||||
|
||||
hw = CLITree('hw', 'hardware controller')
|
||||
hw_chipid = hw.subgroup('chipid', 'Device chipsed ID get')
|
||||
hw_address = hw.subgroup('address', 'Device address get')
|
||||
hw_mode = hw.subgroup('mode', 'Device mode get/set')
|
||||
hw_slot = hw.subgroup('slot', 'Emulation tag slot.')
|
||||
hw_slot_nick = hw_slot.subgroup('nick', 'Get/Set tag nick name for slot')
|
||||
|
||||
hf = CLITree('hf', 'high frequency tag/reader')
|
||||
hf_14a = hf.subgroup('14a', 'ISO14443-a tag read/write/info...')
|
||||
hf_mf = hf.subgroup('mf', 'Mifare Classic mini/1/2/4, attack/read/write')
|
||||
hf_mf_detection = hf.subgroup(
|
||||
'detection', 'Mifare Classic detection log')
|
||||
|
||||
lf = CLITree('lf', 'low frequency tag/reader')
|
||||
lf_em = lf.subgroup('em', 'EM410x read/write/emulator')
|
||||
|
||||
root_commands: dict[str, CLITree] = {
|
||||
'hw': hw,
|
||||
'hf': hf,
|
||||
'lf': lf,
|
||||
}
|
||||
|
||||
@hw.command('connect', 'Connect to chameleon by serial port')
|
||||
class HWConnect(BaseCLIUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
parser = ArgumentParserNoExit()
|
||||
@@ -206,6 +223,7 @@ class HWConnect(BaseCLIUnit):
|
||||
print(f"Chameleon Connect fail: {str(e)}")
|
||||
|
||||
|
||||
@hw_mode.command('set', 'Change device mode to tag reader or tag emulator')
|
||||
class HWModeSet(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -224,6 +242,7 @@ class HWModeSet(DeviceRequiredUnit):
|
||||
print("Switch to { Tag Emulator } mode successfully.")
|
||||
|
||||
|
||||
@hw_mode.command('get', 'Get current device mode')
|
||||
class HWModeGet(DeviceRequiredUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
pass
|
||||
@@ -232,6 +251,7 @@ class HWModeGet(DeviceRequiredUnit):
|
||||
print(f"- Device Mode ( Tag {'Reader' if self.cmd.is_reader_device_mode() else 'Emulator'} )")
|
||||
|
||||
|
||||
@hw_chipid.command('get', 'Get device chipset ID')
|
||||
class HWChipIdGet(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -241,6 +261,7 @@ class HWChipIdGet(DeviceRequiredUnit):
|
||||
print(f' - Device chip ID: ' + self.cmd.get_device_chip_id())
|
||||
|
||||
|
||||
@hw_address.command('get', 'Get device address (used with Bluetooth)')
|
||||
class HWAddressGet(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -250,6 +271,7 @@ class HWAddressGet(DeviceRequiredUnit):
|
||||
print(f' - Device address: ' + self.cmd.get_device_address())
|
||||
|
||||
|
||||
@hf_14a.command('scan', 'Scan 14a tag, and print basic information')
|
||||
class HF14AScan(ReaderRequiredUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
pass
|
||||
@@ -271,6 +293,7 @@ class HF14AScan(ReaderRequiredUnit):
|
||||
return self.scan()
|
||||
|
||||
|
||||
@hf_14a.command('info', 'Scan 14a tag, and print detail information')
|
||||
class HF14AInfo(ReaderRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -301,6 +324,7 @@ class HF14AInfo(ReaderRequiredUnit):
|
||||
self.info()
|
||||
|
||||
|
||||
@hf_mf.command('nested', 'Mifare Classic nested recover key')
|
||||
class HFMFNested(ReaderRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -406,6 +430,7 @@ class HFMFNested(ReaderRequiredUnit):
|
||||
return
|
||||
|
||||
|
||||
@hf_mf.command('darkside', 'Mifare Classic darkside recover key')
|
||||
class HFMFDarkside(ReaderRequiredUnit):
|
||||
|
||||
def __init__(self):
|
||||
@@ -500,6 +525,7 @@ class BaseMF1AuthOpera(ReaderRequiredUnit):
|
||||
raise NotImplementedError("Please implement this")
|
||||
|
||||
|
||||
@hf_mf.command('rdbl', 'MiFARE Classic read one block')
|
||||
class HFMFRDBL(BaseMF1AuthOpera):
|
||||
|
||||
# hf mf rdbl -b 2 -t A -k FFFFFFFFFFFF
|
||||
@@ -509,6 +535,7 @@ class HFMFRDBL(BaseMF1AuthOpera):
|
||||
print(f" - Data: {resp.data.hex()}")
|
||||
|
||||
|
||||
@hf_mf.command('wrbl', 'MiFARE Classic write one block')
|
||||
class HFMFWRBL(BaseMF1AuthOpera):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -530,6 +557,7 @@ class HFMFWRBL(BaseMF1AuthOpera):
|
||||
print(f" - {colorama.Fore.RED}Write fail.{colorama.Style.RESET_ALL}")
|
||||
|
||||
|
||||
@hf_mf_detection.command('enable', 'Detection enable')
|
||||
class HFMFDetectionEnable(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -545,6 +573,7 @@ class HFMFDetectionEnable(DeviceRequiredUnit):
|
||||
print(f" - Set mf1 detection { 'enable' if enable else 'disable'}.")
|
||||
|
||||
|
||||
@hf_mf_detection.command('count', 'Detection log count')
|
||||
class HFMFDetectionLogCount(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -557,6 +586,7 @@ class HFMFDetectionLogCount(DeviceRequiredUnit):
|
||||
print(f" - MF1 detection log count = {count}")
|
||||
|
||||
|
||||
@hf_mf_detection.command('decrypt', 'Download log and decrypt keys')
|
||||
class HFMFDetectionDecrypt(DeviceRequiredUnit):
|
||||
|
||||
detection_log_size = 18
|
||||
@@ -639,6 +669,7 @@ class HFMFDetectionDecrypt(DeviceRequiredUnit):
|
||||
return
|
||||
|
||||
|
||||
@hf_mf.command('eload', 'Load data to emulator memory')
|
||||
class HFMFELoad(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -686,6 +717,7 @@ class HFMFELoad(DeviceRequiredUnit):
|
||||
print("\n - Load success")
|
||||
|
||||
|
||||
@hf_mf.command('sim', 'Simulation a mifare classic card')
|
||||
class HFMFSim(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -723,6 +755,7 @@ class HFMFSim(DeviceRequiredUnit):
|
||||
print(" - Set anti-collision resources success")
|
||||
|
||||
|
||||
@lf_em.command('read', 'Scan em410x tag and print id')
|
||||
class LFEMRead(ReaderRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -755,6 +788,7 @@ class LFEMCardRequiredUnit(DeviceRequiredUnit):
|
||||
raise NotImplementedError("Please implement this")
|
||||
|
||||
|
||||
@lf_em.command('write', 'Write em410x id to t55xx')
|
||||
class LFEMWriteT55xx(LFEMCardRequiredUnit, ReaderRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -805,6 +839,7 @@ class SenseTypeRequireUnit(DeviceRequiredUnit):
|
||||
return parser
|
||||
|
||||
|
||||
@hw_slot.command('change', 'Set emulation tag slot activated.')
|
||||
class HWSlotSet(SlotIndexRequireUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -839,6 +874,7 @@ class TagTypeRequiredUnit(DeviceRequiredUnit):
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
@hw_slot.command('type', 'Set emulation tag type')
|
||||
class HWSlotTagType(TagTypeRequiredUnit, SlotIndexRequireUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -855,6 +891,7 @@ class HWSlotTagType(TagTypeRequiredUnit, SlotIndexRequireUnit):
|
||||
print(f' - Set slot tag type success.')
|
||||
|
||||
|
||||
@hw_slot.command('init', 'Set emulation tag data to default')
|
||||
class HWSlotDataDefault(TagTypeRequiredUnit, SlotIndexRequireUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -872,6 +909,7 @@ class HWSlotDataDefault(TagTypeRequiredUnit, SlotIndexRequireUnit):
|
||||
print(f' - Set slot tag data init success.')
|
||||
|
||||
|
||||
@hw_slot.command('enable', 'Set emulation tag slot enable or disable')
|
||||
class HWSlotEnableSet(SlotIndexRequireUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
parser = ArgumentParserNoExit()
|
||||
@@ -887,6 +925,7 @@ class HWSlotEnableSet(SlotIndexRequireUnit):
|
||||
print(f' - Set slot {slot_num} {"enable" if enable else "disable"} success.')
|
||||
|
||||
|
||||
@lf_em.command('sim', 'Simulation a em410x id card')
|
||||
class LFEMSim(LFEMCardRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -901,6 +940,7 @@ class LFEMSim(LFEMCardRequiredUnit):
|
||||
print(f' - Set em410x tag id success.')
|
||||
|
||||
|
||||
@hw_slot_nick.command('set', 'Set tag nick name for slot')
|
||||
class HWSlotNickSet(SlotIndexRequireUnit, SenseTypeRequireUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
parser = ArgumentParserNoExit()
|
||||
@@ -920,6 +960,7 @@ class HWSlotNickSet(SlotIndexRequireUnit, SenseTypeRequireUnit):
|
||||
print(f' - Set tag nick name for slot {slot_num} success.')
|
||||
|
||||
|
||||
@hw_slot_nick.command('get', 'Get tag nick name for slot')
|
||||
class HWSlotNickGet(SlotIndexRequireUnit, SenseTypeRequireUnit):
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
parser = ArgumentParserNoExit()
|
||||
@@ -935,6 +976,7 @@ class HWSlotNickGet(SlotIndexRequireUnit, SenseTypeRequireUnit):
|
||||
print(f' - Get tag nick name for slot {slot_num}: {res.data.decode(encoding="gbk")}')
|
||||
|
||||
|
||||
@hw_slot.command('update', 'Update config & data to device flash')
|
||||
class HWSlotUpdate(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -946,6 +988,7 @@ class HWSlotUpdate(DeviceRequiredUnit):
|
||||
print(f' - Update config and data from device memory to flash success.')
|
||||
|
||||
|
||||
@hw_slot.command('openall', 'Open all slot and set to default data')
|
||||
class HWSlotOpenAll(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
@@ -975,6 +1018,7 @@ class HWSlotOpenAll(DeviceRequiredUnit):
|
||||
print(f' - Open all slot and set data to default success.')
|
||||
|
||||
|
||||
@hw.command('dfu', 'Restart application to bootloader mode(Not yet implement dfu).')
|
||||
class HWDFU(DeviceRequiredUnit):
|
||||
|
||||
def args_parser(self) -> ArgumentParserNoExit or None:
|
||||
|
||||
@@ -4,6 +4,14 @@ from typing import Union
|
||||
import chameleon_status
|
||||
|
||||
|
||||
class ArgsParserError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class ParserExitIntercept(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class UnexpectedResponseError(Exception):
|
||||
"""
|
||||
Unexpected response exception
|
||||
@@ -33,3 +41,46 @@ def expect_response(accepted_responses: Union[int, list[int]]):
|
||||
return error_throwing_func
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
class CLITree:
|
||||
"""
|
||||
Class holding a
|
||||
|
||||
:param name: Name of the command (e.g. "set")
|
||||
:param helptext: Hint displayed for the command
|
||||
:param fullname: Full name of the command that includes previous commands (e.g. "hw mode set")
|
||||
:param cls: A BaseCLIUnit instance handling the command
|
||||
"""
|
||||
|
||||
def __init__(self, name=None, helptext=None, fullname=None, children=None, cls=None) -> None:
|
||||
self.name: str = name
|
||||
self.helptext: str = helptext
|
||||
self.fullname: str = fullname if fullname else name
|
||||
self.children: list[CLITree] = children if children else list()
|
||||
self.cls = cls
|
||||
|
||||
def subgroup(self, name, helptext=None):
|
||||
"""
|
||||
Create a child command group
|
||||
|
||||
:param name: Name of the command group
|
||||
:param helptext: Hint displayed for the group
|
||||
"""
|
||||
child = CLITree(
|
||||
name=name, fullname=f'{self.fullname} {name}', helptext=helptext)
|
||||
self.children.append(child)
|
||||
return child
|
||||
|
||||
def command(self, name, helptext=None):
|
||||
"""
|
||||
Create a child command
|
||||
|
||||
:param name: Name of the command
|
||||
:param helptext: Hint displayed for the command
|
||||
"""
|
||||
def decorator(cls):
|
||||
self.children.append(
|
||||
CLITree(name=name, fullname=f'{self.fullname} {name}', helptext=helptext, cls=cls))
|
||||
return cls
|
||||
return decorator
|
||||
|
||||
Reference in New Issue
Block a user