mirror of
https://github.com/meshcore-dev/meshcore-cli.git
synced 2026-04-20 22:13:48 +00:00
added scripts to repeater mode
This commit is contained in:
parent
3804f382f1
commit
f76be35e88
2 changed files with 71 additions and 19 deletions
|
|
@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "meshcore-cli"
|
name = "meshcore-cli"
|
||||||
version = "1.3.20"
|
version = "1.3.21"
|
||||||
authors = [
|
authors = [
|
||||||
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
|
{ name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
|
||||||
]
|
]
|
||||||
|
|
|
||||||
|
|
@ -32,7 +32,7 @@ import re
|
||||||
from meshcore import MeshCore, EventType, logger
|
from meshcore import MeshCore, EventType, logger
|
||||||
|
|
||||||
# Version
|
# Version
|
||||||
VERSION = "v1.3.20"
|
VERSION = "v1.3.21"
|
||||||
|
|
||||||
# default ble address is stored in a config file
|
# default ble address is stored in a config file
|
||||||
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
|
||||||
|
|
@ -3628,7 +3628,8 @@ def command_usage() :
|
||||||
-h : prints help for arguments and commands
|
-h : prints help for arguments and commands
|
||||||
-v : prints version
|
-v : prints version
|
||||||
-j : json output (disables init file)
|
-j : json output (disables init file)
|
||||||
-D : debug
|
-D : debug (sets logging to DEBUG)
|
||||||
|
-q : quiet (sets logging to ERROR)
|
||||||
-S : scan for devices and show a selector
|
-S : scan for devices and show a selector
|
||||||
-l : list available ble/serial devices and exit
|
-l : list available ble/serial devices and exit
|
||||||
-T <timeout> : timeout for the ble scan (-S and -l) default 2s
|
-T <timeout> : timeout for the ble scan (-S and -l) default 2s
|
||||||
|
|
@ -3938,6 +3939,7 @@ REPEATER_HELP = f"""
|
||||||
{ANSI_BGREEN}System:{ANSI_END}
|
{ANSI_BGREEN}System:{ANSI_END}
|
||||||
reboot - Reboot device
|
reboot - Reboot device
|
||||||
erase - Erase filesystem (serial only)
|
erase - Erase filesystem (serial only)
|
||||||
|
script <file> - Execute script
|
||||||
|
|
||||||
{ANSI_BYELLOW}Type 'quit' or 'q' to exit, Ctrl+C to abort{ANSI_END}
|
{ANSI_BYELLOW}Type 'quit' or 'q' to exit, Ctrl+C to abort{ANSI_END}
|
||||||
"""
|
"""
|
||||||
|
|
@ -3976,10 +3978,31 @@ async def prompt_for_file():
|
||||||
|
|
||||||
return file_path
|
return file_path
|
||||||
|
|
||||||
async def process_repeater_line(ser, cmd) :
|
async def process_repeater_script(ser, file):
|
||||||
|
if not os.path.exists(file) :
|
||||||
|
logger.info(f"file {file} not found")
|
||||||
|
return
|
||||||
|
|
||||||
|
with open(file, "r") as f :
|
||||||
|
lines=f.readlines()
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
line = line.strip()
|
||||||
|
if not (line == "" or line[0] == "#"):
|
||||||
|
logger.debug(f"processing {line}")
|
||||||
|
try :
|
||||||
|
res = await process_repeater_line(ser, line, echo=True)
|
||||||
|
if not res:
|
||||||
|
logger.info("Error during script execution, exiting")
|
||||||
|
break
|
||||||
|
except ValueError:
|
||||||
|
logger.error(f"Error processing {line}")
|
||||||
|
break
|
||||||
|
|
||||||
|
async def process_repeater_line(ser, cmd, echo=False) :
|
||||||
if cmd.lower() == "help":
|
if cmd.lower() == "help":
|
||||||
print(REPEATER_HELP)
|
print(REPEATER_HELP)
|
||||||
return
|
return True
|
||||||
|
|
||||||
if cmd.lower() == "clock sync" or cmd.lower() == "st" or cmd.lower() == "sync_time":
|
if cmd.lower() == "clock sync" or cmd.lower() == "st" or cmd.lower() == "sync_time":
|
||||||
cur_time = int(time.time())
|
cur_time = int(time.time())
|
||||||
|
|
@ -4007,8 +4030,10 @@ async def process_repeater_line(ser, cmd) :
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.error("File not found")
|
logger.error("File not found")
|
||||||
|
return False
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
logger.info("Region upload canceled")
|
logger.info("Region upload canceled")
|
||||||
|
return False
|
||||||
|
|
||||||
# in any case, send an empty line and clean buffer
|
# in any case, send an empty line and clean buffer
|
||||||
cmd = ""
|
cmd = ""
|
||||||
|
|
@ -4035,13 +4060,32 @@ async def process_repeater_line(ser, cmd) :
|
||||||
while line.rstrip() != "":
|
while line.rstrip() != "":
|
||||||
file.write(line)
|
file.write(line)
|
||||||
line = ser.readline().decode(errors='ignore')
|
line = ser.readline().decode(errors='ignore')
|
||||||
|
except FileNotFoundError:
|
||||||
|
logger.error("File not found")
|
||||||
|
return False
|
||||||
|
except (EOFError, KeyboardInterrupt):
|
||||||
|
logger.info("Region download canceled")
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
if cmd.lower().startswith("script"):
|
||||||
|
try:
|
||||||
|
if cmd.lower() == "script": # prompt for a filename
|
||||||
|
file_path = await prompt_for_file()
|
||||||
|
else :
|
||||||
|
file_path = cmd.lower().split(" ", 2)[1]
|
||||||
|
|
||||||
|
file_path = file_path.replace("~", str(Path.home()))
|
||||||
|
|
||||||
|
return await process_repeater_script(ser, file_path)
|
||||||
|
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
logger.error("File not found")
|
logger.error("File not found")
|
||||||
|
return False
|
||||||
except (EOFError, KeyboardInterrupt):
|
except (EOFError, KeyboardInterrupt):
|
||||||
logger.info("Region download canceled")
|
logger.info("Script canceled")
|
||||||
|
return False
|
||||||
return
|
|
||||||
|
|
||||||
# Send command with CR terminator
|
# Send command with CR terminator
|
||||||
if cmd != "":
|
if cmd != "":
|
||||||
|
|
@ -4049,36 +4093,41 @@ async def process_repeater_line(ser, cmd) :
|
||||||
await asyncio.sleep(0.3)
|
await asyncio.sleep(0.3)
|
||||||
|
|
||||||
# Read response
|
# Read response
|
||||||
|
result = True
|
||||||
response = ser.read(ser.in_waiting or 4096).decode(errors='ignore')
|
response = ser.read(ser.in_waiting or 4096).decode(errors='ignore')
|
||||||
if response:
|
if response:
|
||||||
# Clean up echo and format response
|
# Clean up echo and format response
|
||||||
lines = response.rstrip().split('\n')
|
lines = response.rstrip().split('\n')
|
||||||
for line in lines:
|
for line in lines:
|
||||||
line = line.rstrip()
|
line = line.rstrip()
|
||||||
if line and line != cmd: # Skip echo of command
|
if line and (echo or line != cmd): # Skip echo of command
|
||||||
# Color code certain responses
|
# Color code certain responses
|
||||||
if line.strip().startswith("OK") or line.strip().startswith("ok"):
|
if line.strip().startswith("OK") or line.strip().startswith("ok"):
|
||||||
print(f"{ANSI_GREEN}{line}{ANSI_END}")
|
print(f"{ANSI_GREEN}{line}{ANSI_END}")
|
||||||
elif line.strip().startswith("Error") or line.startswith("ERR"):
|
elif line.strip().startswith("Error") or line.strip().startswith("ERR"):
|
||||||
print(f"{ANSI_RED}{line}{ANSI_END}")
|
print(f"{ANSI_RED}{line}{ANSI_END}")
|
||||||
elif line.strip().startswith("->"):
|
elif line.strip().startswith("->"):
|
||||||
print(f"{ANSI_CYAN}{line}{ANSI_END}")
|
print(f"{ANSI_CYAN}{line}{ANSI_END}")
|
||||||
else:
|
else:
|
||||||
print(line)
|
print(line)
|
||||||
|
if "-> Unknown command" in line or \
|
||||||
|
"-> Error" in line :
|
||||||
|
result = False
|
||||||
|
return result
|
||||||
|
|
||||||
async def setup_repeater_serial(port, baudrate):
|
async def setup_repeater_serial(port, baudrate):
|
||||||
"""Interactive loop for repeater text CLI (raw serial commands)"""
|
"""Interactive loop for repeater text CLI (raw serial commands)"""
|
||||||
import serial as pyserial
|
import serial as pyserial
|
||||||
|
|
||||||
print(f"{ANSI_BCYAN}Connecting to repeater at {port} ({baudrate} baud)...{ANSI_END}")
|
logger.info(f"{ANSI_BCYAN}Connecting to repeater at {port} ({baudrate} baud)...{ANSI_END}")
|
||||||
try:
|
try:
|
||||||
ser = pyserial.Serial(port, baudrate, timeout=1)
|
ser = pyserial.Serial(port, baudrate, timeout=1)
|
||||||
except PermissionError:
|
except PermissionError:
|
||||||
print(f"{ANSI_BRED}Error: Permission denied. Try running with sudo or add user to dialout group.{ANSI_END}")
|
print(f"{ANSI_BRED}Error: Permission denied. Try running with sudo or add user to dialout group.{ANSI_END}")
|
||||||
return
|
return None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"{ANSI_BRED}Error opening serial port: {e}{ANSI_END}")
|
print(f"{ANSI_BRED}Error opening serial port: {e}{ANSI_END}")
|
||||||
return
|
return None
|
||||||
|
|
||||||
await asyncio.sleep(0.5) # Wait for connection to stabilize
|
await asyncio.sleep(0.5) # Wait for connection to stabilize
|
||||||
ser.reset_input_buffer()
|
ser.reset_input_buffer()
|
||||||
|
|
@ -4182,6 +4231,7 @@ async def main(argv):
|
||||||
timeout = 2
|
timeout = 2
|
||||||
pin = None
|
pin = None
|
||||||
first_device = False
|
first_device = False
|
||||||
|
quiet = False
|
||||||
# If there is an address in config file, use it by default
|
# If there is an address in config file, use it by default
|
||||||
# unless an arg is explicitely given
|
# unless an arg is explicitely given
|
||||||
if os.path.exists(MCCLI_ADDRESS) :
|
if os.path.exists(MCCLI_ADDRESS) :
|
||||||
|
|
@ -4189,7 +4239,7 @@ async def main(argv):
|
||||||
address = f.readline().strip()
|
address = f.readline().strip()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:Cr")
|
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:Crq")
|
||||||
except getopt.GetoptError:
|
except getopt.GetoptError:
|
||||||
print("Unrecognized option, use -h to get more help")
|
print("Unrecognized option, use -h to get more help")
|
||||||
command_usage()
|
command_usage()
|
||||||
|
|
@ -4233,6 +4283,8 @@ async def main(argv):
|
||||||
case "-f": # connect to first encountered device
|
case "-f": # connect to first encountered device
|
||||||
address = ""
|
address = ""
|
||||||
first_device = True
|
first_device = True
|
||||||
|
case "-q": # quiet (turns logger to ERROR only)
|
||||||
|
quiet = True
|
||||||
case "-l" :
|
case "-l" :
|
||||||
print("BLE devices:")
|
print("BLE devices:")
|
||||||
try :
|
try :
|
||||||
|
|
@ -4287,19 +4339,19 @@ async def main(argv):
|
||||||
|
|
||||||
if (debug==True):
|
if (debug==True):
|
||||||
logger.setLevel(logging.DEBUG)
|
logger.setLevel(logging.DEBUG)
|
||||||
elif (json_output) :
|
elif (json_output or quiet) :
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(logging.ERROR)
|
||||||
|
|
||||||
# Repeater mode - raw text CLI over serial
|
# Repeater mode - raw text CLI over serial
|
||||||
if repeater_mode:
|
if repeater_mode:
|
||||||
if serial_port is None:
|
if serial_port is None:
|
||||||
print("Error: Repeater mode (-r) requires serial port (-s)")
|
logger.error("Repeater mode (-r) requires serial port (-s)")
|
||||||
command_usage()
|
command_usage()
|
||||||
return
|
return
|
||||||
|
|
||||||
ser = await setup_repeater_serial(serial_port, baudrate)
|
ser = await setup_repeater_serial(serial_port, baudrate)
|
||||||
|
|
||||||
print(ser)
|
logger.debug(f"Serial port opened: {ser}")
|
||||||
|
|
||||||
if (len(args) > 0) :
|
if (len(args) > 0) :
|
||||||
await process_repeater_line(ser, " ".join(args))
|
await process_repeater_line(ser, " ".join(args))
|
||||||
|
|
@ -4307,7 +4359,7 @@ async def main(argv):
|
||||||
await repeater_loop(ser)
|
await repeater_loop(ser)
|
||||||
|
|
||||||
ser.close()
|
ser.close()
|
||||||
print(f"\n{ANSI_BGRAY}Disconnected from repeater.{ANSI_END}")
|
logger.info(f"{ANSI_BGRAY}Disconnected from repeater.{ANSI_END}")
|
||||||
return
|
return
|
||||||
|
|
||||||
mc = None
|
mc = None
|
||||||
|
|
@ -4419,7 +4471,7 @@ async def main(argv):
|
||||||
if os.path.isdir(MCCLI_CONFIG_DIR) :
|
if os.path.isdir(MCCLI_CONFIG_DIR) :
|
||||||
log_message.file = MCCLI_CONFIG_DIR + mc.self_info["name"] + ".msgs"
|
log_message.file = MCCLI_CONFIG_DIR + mc.self_info["name"] + ".msgs"
|
||||||
|
|
||||||
if (json_output) :
|
if (json_output or quiet) :
|
||||||
logger.setLevel(logging.ERROR)
|
logger.setLevel(logging.ERROR)
|
||||||
else :
|
else :
|
||||||
if res.payload["fw ver"] > 2 :
|
if res.payload["fw ver"] > 2 :
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue