meshcore-cli 1.3.12__tar.gz → 1.3.13__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/PKG-INFO +1 -1
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/pyproject.toml +1 -1
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/src/meshcore_cli/meshcore_cli.py +275 -50
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/.gitignore +0 -0
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/LICENSE +0 -0
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/README.md +0 -0
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/flake.lock +0 -0
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/flake.nix +0 -0
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/src/meshcore_cli/__init__.py +0 -0
- {meshcore_cli-1.3.12 → meshcore_cli-1.3.13}/src/meshcore_cli/__main__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: meshcore-cli
|
|
3
|
-
Version: 1.3.
|
|
3
|
+
Version: 1.3.13
|
|
4
4
|
Summary: Command line interface to meshcore companion radios
|
|
5
5
|
Project-URL: Homepage, https://github.com/fdlamotte/meshcore-cli
|
|
6
6
|
Project-URL: Issues, https://github.com/fdlamotte/meshcore-cli/issues
|
|
@@ -1028,7 +1028,7 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
1028
1028
|
if '%' in dest and scope!=None :
|
|
1029
1029
|
dest_scope = dest.split("%")[-1]
|
|
1030
1030
|
dest = dest[:-len(dest_scope)-1]
|
|
1031
|
-
nc = mc
|
|
1031
|
+
nc = await get_contact_from_arg(mc, dest)
|
|
1032
1032
|
if nc is None:
|
|
1033
1033
|
if dest == "public" :
|
|
1034
1034
|
nc = {"adv_name" : "public", "type" : 0, "chan_nb" : 0}
|
|
@@ -1083,7 +1083,7 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
1083
1083
|
dest_scope = dest.split("%")[-1]
|
|
1084
1084
|
dest = dest[:-len(dest_scope)-1]
|
|
1085
1085
|
await set_scope (mc, dest_scope)
|
|
1086
|
-
tct = mc
|
|
1086
|
+
tct = get_contact_from_arg(mc, dest)
|
|
1087
1087
|
if len(args)>1 and not tct is None: # a contact, send a message
|
|
1088
1088
|
if tct["type"] == 1 or tct["type"] == 3: # client or room
|
|
1089
1089
|
last_ack = await msg_ack(mc, tct, line.split(" ", 1)[1])
|
|
@@ -1106,7 +1106,7 @@ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
|
|
|
1106
1106
|
dest_scope = contact_name.split("%")[-1]
|
|
1107
1107
|
contact_name = contact_name[:-len(dest_scope)-1]
|
|
1108
1108
|
await set_scope (mc, dest_scope)
|
|
1109
|
-
tct = mc.
|
|
1109
|
+
tct = mc.get_contact_from_arg(mc, contact_name)
|
|
1110
1110
|
if tct is None:
|
|
1111
1111
|
print(f"{contact_name} is not a contact")
|
|
1112
1112
|
else:
|
|
@@ -1346,9 +1346,7 @@ async def process_contact_chat_line(mc, contact, line):
|
|
|
1346
1346
|
perm = int(perm_string[1:])
|
|
1347
1347
|
else:
|
|
1348
1348
|
perm = int(perm_string,16)
|
|
1349
|
-
ct=mc
|
|
1350
|
-
if ct is None:
|
|
1351
|
-
ct=mc.get_contact_by_key_prefix(name)
|
|
1349
|
+
ct= await get_contact_from_arg(mc, name)
|
|
1352
1350
|
if ct is None:
|
|
1353
1351
|
if name == "self" or mc.self_info["public_key"].startswith(name):
|
|
1354
1352
|
key = mc.self_info["public_key"]
|
|
@@ -1847,6 +1845,22 @@ async def print_disc_trace_to (mc, contact):
|
|
|
1847
1845
|
|
|
1848
1846
|
await next_cmd(mc, ["trace", trace])
|
|
1849
1847
|
|
|
1848
|
+
|
|
1849
|
+
async def get_contact_from_arg(mc, arg):
|
|
1850
|
+
contact = None
|
|
1851
|
+
await mc.ensure_contacts()
|
|
1852
|
+
|
|
1853
|
+
# first try with key prefix
|
|
1854
|
+
try: # try only if its a valid hex
|
|
1855
|
+
int(arg ,16)
|
|
1856
|
+
contact = mc.get_contact_by_key_prefix(arg)
|
|
1857
|
+
except ValueError:
|
|
1858
|
+
pass
|
|
1859
|
+
if contact is None: # try by name
|
|
1860
|
+
contact = mc.get_contact_by_name(arg)
|
|
1861
|
+
|
|
1862
|
+
return contact
|
|
1863
|
+
|
|
1850
1864
|
async def next_cmd(mc, cmds, json_output=False):
|
|
1851
1865
|
""" process next command """
|
|
1852
1866
|
global ARROW_HEAD, SLASH_START, SLASH_END, INVERT_SLASH
|
|
@@ -2497,8 +2511,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2497
2511
|
dest = None
|
|
2498
2512
|
|
|
2499
2513
|
if dest is None:
|
|
2500
|
-
await mc
|
|
2501
|
-
dest = mc.get_contact_by_name(cmds[1])
|
|
2514
|
+
dest = await get_contact_from_arg(mc, cmds[1])
|
|
2502
2515
|
|
|
2503
2516
|
if dest is None:
|
|
2504
2517
|
if json_output :
|
|
@@ -2549,8 +2562,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2549
2562
|
dest = None
|
|
2550
2563
|
|
|
2551
2564
|
if dest is None:
|
|
2552
|
-
await mc
|
|
2553
|
-
dest = mc.get_contact_by_name(cmds[1])
|
|
2565
|
+
dest = await get_contact_from_arg(mc, cmds[1])
|
|
2554
2566
|
|
|
2555
2567
|
if dest is None:
|
|
2556
2568
|
if json_output :
|
|
@@ -2627,9 +2639,9 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2627
2639
|
|
|
2628
2640
|
case "login" | "l" :
|
|
2629
2641
|
argnum = 2
|
|
2630
|
-
await mc
|
|
2631
|
-
|
|
2632
|
-
if contact is None:
|
|
2642
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2643
|
+
|
|
2644
|
+
if contact is None: # still none ? contact not found
|
|
2633
2645
|
if json_output :
|
|
2634
2646
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
2635
2647
|
else:
|
|
@@ -2666,8 +2678,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2666
2678
|
|
|
2667
2679
|
case "logout" :
|
|
2668
2680
|
argnum = 1
|
|
2669
|
-
await mc
|
|
2670
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2681
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2671
2682
|
res = await mc.commands.send_logout(contact)
|
|
2672
2683
|
logger.debug(res)
|
|
2673
2684
|
if res.type == EventType.ERROR:
|
|
@@ -2679,14 +2690,12 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2679
2690
|
|
|
2680
2691
|
case "contact_timeout" :
|
|
2681
2692
|
argnum = 2
|
|
2682
|
-
await mc
|
|
2683
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2693
|
+
contact = await get_contact_from_args(mc, cmds[1])
|
|
2684
2694
|
contact["timeout"] = float(cmds[2])
|
|
2685
2695
|
|
|
2686
2696
|
case "disc_path" | "dp" :
|
|
2687
2697
|
argnum = 1
|
|
2688
|
-
await mc
|
|
2689
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2698
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2690
2699
|
res = await discover_path(mc, contact)
|
|
2691
2700
|
if res is None:
|
|
2692
2701
|
print(f"Error while discovering path")
|
|
@@ -2767,7 +2776,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2767
2776
|
case "req_telemetry"|"rt" :
|
|
2768
2777
|
argnum = 1
|
|
2769
2778
|
await mc.ensure_contacts()
|
|
2770
|
-
contact = mc
|
|
2779
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2771
2780
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2772
2781
|
res = await mc.commands.req_telemetry_sync(contact, timeout)
|
|
2773
2782
|
if res is None :
|
|
@@ -2784,8 +2793,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2784
2793
|
|
|
2785
2794
|
case "req_status"|"rs" :
|
|
2786
2795
|
argnum = 1
|
|
2787
|
-
await mc
|
|
2788
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2796
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2789
2797
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2790
2798
|
res = await mc.commands.req_status_sync(contact, timeout)
|
|
2791
2799
|
if res is None :
|
|
@@ -2799,7 +2807,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2799
2807
|
case "req_mma" | "rm":
|
|
2800
2808
|
argnum = 3
|
|
2801
2809
|
await mc.ensure_contacts()
|
|
2802
|
-
contact = mc
|
|
2810
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2803
2811
|
if cmds[2][-1] == "s":
|
|
2804
2812
|
from_secs = int(cmds[2][0:-1])
|
|
2805
2813
|
elif cmds[2][-1] == "m":
|
|
@@ -2828,8 +2836,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2828
2836
|
|
|
2829
2837
|
case "req_acl" :
|
|
2830
2838
|
argnum = 1
|
|
2831
|
-
await mc
|
|
2832
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2839
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2833
2840
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2834
2841
|
res = await mc.commands.req_acl_sync(contact, timeout)
|
|
2835
2842
|
if res is None :
|
|
@@ -2853,8 +2860,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2853
2860
|
|
|
2854
2861
|
case "req_neighbours"|"rn" :
|
|
2855
2862
|
argnum = 1
|
|
2856
|
-
await mc
|
|
2857
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2863
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2858
2864
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2859
2865
|
res = await mc.commands.fetch_all_neighbours(contact, timeout=timeout)
|
|
2860
2866
|
if res is None :
|
|
@@ -2893,8 +2899,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2893
2899
|
|
|
2894
2900
|
case "req_binary" :
|
|
2895
2901
|
argnum = 2
|
|
2896
|
-
await mc
|
|
2897
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2902
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2898
2903
|
timeout = 0 if not "timeout" in contact else contact["timeout"]
|
|
2899
2904
|
res = await mc.commands.req_binary(contact, bytes.fromhex(cmds[2]), timeout)
|
|
2900
2905
|
if res is None :
|
|
@@ -2968,8 +2973,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2968
2973
|
|
|
2969
2974
|
case "path":
|
|
2970
2975
|
argnum = 1
|
|
2971
|
-
|
|
2972
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
2976
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2973
2977
|
if contact is None:
|
|
2974
2978
|
if json_output :
|
|
2975
2979
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -2993,7 +2997,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
2993
2997
|
case "contact_info" | "ci":
|
|
2994
2998
|
argnum = 1
|
|
2995
2999
|
res = await mc.ensure_contacts(follow=True)
|
|
2996
|
-
contact = mc
|
|
3000
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
2997
3001
|
if contact is None:
|
|
2998
3002
|
if json_output :
|
|
2999
3003
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3004,8 +3008,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3004
3008
|
|
|
3005
3009
|
case "change_path" | "cp":
|
|
3006
3010
|
argnum = 2
|
|
3007
|
-
await mc
|
|
3008
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3011
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3009
3012
|
if contact is None:
|
|
3010
3013
|
if json_output :
|
|
3011
3014
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3027,8 +3030,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3027
3030
|
|
|
3028
3031
|
case "change_flags" | "cf":
|
|
3029
3032
|
argnum = 2
|
|
3030
|
-
await mc
|
|
3031
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3033
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3032
3034
|
if contact is None:
|
|
3033
3035
|
if json_output :
|
|
3034
3036
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3044,8 +3046,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3044
3046
|
|
|
3045
3047
|
case "reset_path" | "rp" :
|
|
3046
3048
|
argnum = 1
|
|
3047
|
-
await mc
|
|
3048
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3049
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3049
3050
|
if contact is None:
|
|
3050
3051
|
if json_output :
|
|
3051
3052
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3064,8 +3065,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3064
3065
|
|
|
3065
3066
|
case "share_contact" | "sc":
|
|
3066
3067
|
argnum = 1
|
|
3067
|
-
await mc
|
|
3068
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3068
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3069
3069
|
if contact is None:
|
|
3070
3070
|
if json_output :
|
|
3071
3071
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3081,8 +3081,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3081
3081
|
|
|
3082
3082
|
case "export_contact"|"ec":
|
|
3083
3083
|
argnum = 1
|
|
3084
|
-
await mc
|
|
3085
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3084
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3086
3085
|
if contact is None:
|
|
3087
3086
|
if json_output :
|
|
3088
3087
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3112,8 +3111,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3112
3111
|
|
|
3113
3112
|
case "upload_contact" | "uc" :
|
|
3114
3113
|
argnum = 1
|
|
3115
|
-
await mc
|
|
3116
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3114
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3117
3115
|
if contact is None:
|
|
3118
3116
|
if json_output :
|
|
3119
3117
|
print(json.dumps({"error" : "contact unknown", "name" : cmds[1]}))
|
|
@@ -3157,7 +3155,6 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3157
3155
|
|
|
3158
3156
|
case "remove_contact" :
|
|
3159
3157
|
argnum = 1
|
|
3160
|
-
await mc.ensure_contacts()
|
|
3161
3158
|
contact = mc.get_contact_by_name(cmds[1])
|
|
3162
3159
|
if contact is None:
|
|
3163
3160
|
if json_output :
|
|
@@ -3279,8 +3276,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3279
3276
|
|
|
3280
3277
|
case "chat_to" | "imto" | "to" :
|
|
3281
3278
|
argnum = 1
|
|
3282
|
-
await mc
|
|
3283
|
-
contact = mc.get_contact_by_name(cmds[1])
|
|
3279
|
+
contact = await get_contact_from_arg(mc, cmds[1])
|
|
3284
3280
|
await interactive_loop(mc, to=contact)
|
|
3285
3281
|
|
|
3286
3282
|
case "script" :
|
|
@@ -3288,8 +3284,7 @@ async def next_cmd(mc, cmds, json_output=False):
|
|
|
3288
3284
|
await process_script(mc, cmds[1], json_output=json_output)
|
|
3289
3285
|
|
|
3290
3286
|
case _ :
|
|
3291
|
-
await mc
|
|
3292
|
-
contact = mc.get_contact_by_name(cmds[0])
|
|
3287
|
+
contact = await get_contact_from_arg(mc, cmds[0])
|
|
3293
3288
|
if contact is None:
|
|
3294
3289
|
logger.error(f"Unknown command : {cmd}, {cmds} not executed ...")
|
|
3295
3290
|
return None
|
|
@@ -3429,6 +3424,7 @@ def command_usage() :
|
|
|
3429
3424
|
-b <baudrate> : specify baudrate
|
|
3430
3425
|
-C : toggles classic mode for prompt
|
|
3431
3426
|
-c <on/off> : disables most of color output if off
|
|
3427
|
+
-r : repeater mode (raw text CLI, use with -s)
|
|
3432
3428
|
""")
|
|
3433
3429
|
|
|
3434
3430
|
def get_help_for (cmdname, context="line") :
|
|
@@ -3588,6 +3584,223 @@ To remove a channel, use remove_channel, either with channel name or number.
|
|
|
3588
3584
|
else:
|
|
3589
3585
|
print(f"Sorry, no help yet for {cmdname}")
|
|
3590
3586
|
|
|
3587
|
+
# Repeater mode history file
|
|
3588
|
+
MCCLI_REPEATER_HISTORY_FILE = MCCLI_CONFIG_DIR + "repeater_history"
|
|
3589
|
+
|
|
3590
|
+
# Repeater command completion dictionary
|
|
3591
|
+
REPEATER_COMMANDS = {
|
|
3592
|
+
"ver": None,
|
|
3593
|
+
"board": None,
|
|
3594
|
+
"reboot": None,
|
|
3595
|
+
"advert": None,
|
|
3596
|
+
"clock": {"sync": None},
|
|
3597
|
+
"time": None,
|
|
3598
|
+
"neighbors": None,
|
|
3599
|
+
"stats-core": None,
|
|
3600
|
+
"stats-radio": None,
|
|
3601
|
+
"stats-packets": None,
|
|
3602
|
+
"clear": {"stats": None},
|
|
3603
|
+
"log": {"start": None, "stop": None, "erase": None},
|
|
3604
|
+
"get": {
|
|
3605
|
+
"name": None, "radio": None, "tx": None, "freq": None,
|
|
3606
|
+
"public.key": None, "prv.key": None, "repeat": None, "role": None,
|
|
3607
|
+
"lat": None, "lon": None, "af": None,
|
|
3608
|
+
"rxdelay": None, "txdelay": None, "direct.txdelay": None,
|
|
3609
|
+
"flood.max": None, "flood.advert.interval": None,
|
|
3610
|
+
"advert.interval": None, "guest.password": None,
|
|
3611
|
+
"allow.read.only": None, "multi.acks": None,
|
|
3612
|
+
"int.thresh": None, "agc.reset.interval": None,
|
|
3613
|
+
"bridge.enabled": None, "bridge.delay": None,
|
|
3614
|
+
"bridge.source": None, "bridge.baud": None,
|
|
3615
|
+
"bridge.channel": None, "bridge.secret": None, "bridge.type": None,
|
|
3616
|
+
"adc.multiplier": None, "acl": None,
|
|
3617
|
+
},
|
|
3618
|
+
"set": {
|
|
3619
|
+
"name": None, "radio": None, "tx": None, "freq": None,
|
|
3620
|
+
"prv.key": None, "repeat": {"on": None, "off": None},
|
|
3621
|
+
"lat": None, "lon": None, "af": None,
|
|
3622
|
+
"rxdelay": None, "txdelay": None, "direct.txdelay": None,
|
|
3623
|
+
"flood.max": None, "flood.advert.interval": None,
|
|
3624
|
+
"advert.interval": None, "guest.password": None,
|
|
3625
|
+
"allow.read.only": {"on": None, "off": None},
|
|
3626
|
+
"multi.acks": None, "int.thresh": None, "agc.reset.interval": None,
|
|
3627
|
+
"bridge.enabled": {"on": None, "off": None},
|
|
3628
|
+
"bridge.delay": None, "bridge.source": None,
|
|
3629
|
+
"bridge.baud": None, "bridge.channel": None, "bridge.secret": None,
|
|
3630
|
+
"adc.multiplier": None,
|
|
3631
|
+
},
|
|
3632
|
+
"password": None,
|
|
3633
|
+
"erase": None,
|
|
3634
|
+
"gps": {"on": None, "off": None, "sync": None, "setloc": None, "advert": {"none": None, "share": None, "prefs": None}},
|
|
3635
|
+
"sensor": {"list": None, "get": None, "set": None},
|
|
3636
|
+
"region": {"get": None, "put": None, "remove": None, "save": None, "load": None, "home": None, "allowf": None, "denyf": None},
|
|
3637
|
+
"setperm": None,
|
|
3638
|
+
"tempradio": None,
|
|
3639
|
+
"neighbor.remove": None,
|
|
3640
|
+
"quit": None,
|
|
3641
|
+
"q": None,
|
|
3642
|
+
"help": None,
|
|
3643
|
+
}
|
|
3644
|
+
|
|
3645
|
+
REPEATER_HELP = f"""
|
|
3646
|
+
{ANSI_BCYAN}Repeater CLI Commands:{ANSI_END}
|
|
3647
|
+
|
|
3648
|
+
{ANSI_BGREEN}Info:{ANSI_END}
|
|
3649
|
+
ver - Firmware version
|
|
3650
|
+
board - Board name
|
|
3651
|
+
clock - Show current time
|
|
3652
|
+
|
|
3653
|
+
{ANSI_BGREEN}Stats:{ANSI_END}
|
|
3654
|
+
stats-core - Core stats (uptime, battery, queue)
|
|
3655
|
+
stats-radio - Radio stats (RSSI, SNR, noise floor)
|
|
3656
|
+
stats-packets - Packet statistics (sent/recv counts)
|
|
3657
|
+
clear stats - Reset all statistics
|
|
3658
|
+
|
|
3659
|
+
{ANSI_BGREEN}Network:{ANSI_END}
|
|
3660
|
+
neighbors - Show neighboring repeaters (zero-hop)
|
|
3661
|
+
advert - Send advertisement now
|
|
3662
|
+
|
|
3663
|
+
{ANSI_BGREEN}Logging:{ANSI_END}
|
|
3664
|
+
log start - Enable packet logging
|
|
3665
|
+
log stop - Disable packet logging
|
|
3666
|
+
log - Dump log file to console
|
|
3667
|
+
log erase - Erase log file
|
|
3668
|
+
|
|
3669
|
+
{ANSI_BGREEN}Configuration (get/set):{ANSI_END}
|
|
3670
|
+
get name - Node name
|
|
3671
|
+
get radio - Radio params (freq,bw,sf,cr)
|
|
3672
|
+
get tx - TX power (dBm)
|
|
3673
|
+
get repeat - Repeat mode on/off
|
|
3674
|
+
get public.key - Node public key
|
|
3675
|
+
get advert.interval - Advertisement interval (minutes)
|
|
3676
|
+
|
|
3677
|
+
set name <name> - Set node name
|
|
3678
|
+
set tx <power> - Set TX power (dBm)
|
|
3679
|
+
set repeat on|off - Enable/disable repeating
|
|
3680
|
+
set radio f,bw,sf,cr - Set radio params (reboot to apply)
|
|
3681
|
+
set advert.interval <min> - Set advert interval (60-240 min)
|
|
3682
|
+
|
|
3683
|
+
{ANSI_BGREEN}System:{ANSI_END}
|
|
3684
|
+
reboot - Reboot device
|
|
3685
|
+
erase - Erase filesystem (serial only)
|
|
3686
|
+
|
|
3687
|
+
{ANSI_BYELLOW}Type 'quit' or 'q' to exit, Ctrl+C to abort{ANSI_END}
|
|
3688
|
+
"""
|
|
3689
|
+
|
|
3690
|
+
async def repeater_loop(port, baudrate):
|
|
3691
|
+
"""Interactive loop for repeater text CLI (raw serial commands)"""
|
|
3692
|
+
import serial as pyserial
|
|
3693
|
+
|
|
3694
|
+
print(f"{ANSI_BCYAN}Connecting to repeater at {port} ({baudrate} baud)...{ANSI_END}")
|
|
3695
|
+
try:
|
|
3696
|
+
ser = pyserial.Serial(port, baudrate, timeout=1)
|
|
3697
|
+
except PermissionError:
|
|
3698
|
+
print(f"{ANSI_BRED}Error: Permission denied. Try running with sudo or add user to dialout group.{ANSI_END}")
|
|
3699
|
+
return
|
|
3700
|
+
except Exception as e:
|
|
3701
|
+
print(f"{ANSI_BRED}Error opening serial port: {e}{ANSI_END}")
|
|
3702
|
+
return
|
|
3703
|
+
|
|
3704
|
+
await asyncio.sleep(0.5) # Wait for connection to stabilize
|
|
3705
|
+
ser.reset_input_buffer()
|
|
3706
|
+
|
|
3707
|
+
# Send initial CR to wake up CLI
|
|
3708
|
+
ser.write(b"\r")
|
|
3709
|
+
await asyncio.sleep(0.2)
|
|
3710
|
+
ser.reset_input_buffer()
|
|
3711
|
+
|
|
3712
|
+
# Try to get device info
|
|
3713
|
+
ser.write(b"ver\r")
|
|
3714
|
+
await asyncio.sleep(0.3)
|
|
3715
|
+
ver_response = ser.read(ser.in_waiting or 256).decode(errors='ignore').strip()
|
|
3716
|
+
device_name = "Repeater"
|
|
3717
|
+
for line in ver_response.split('\n'):
|
|
3718
|
+
line = line.strip()
|
|
3719
|
+
if line and not line.startswith("ver") and ">" not in line[:3]:
|
|
3720
|
+
device_name = line.split('(')[0].strip() if '(' in line else line
|
|
3721
|
+
break
|
|
3722
|
+
|
|
3723
|
+
print(f"{ANSI_BGREEN}Connected!{ANSI_END} Device: {ANSI_BMAGENTA}{device_name}{ANSI_END}")
|
|
3724
|
+
print(f"Type {ANSI_BCYAN}help{ANSI_END} for commands, {ANSI_BCYAN}quit{ANSI_END} to exit, {ANSI_BCYAN}Tab{ANSI_END} for completion")
|
|
3725
|
+
print("-" * 50)
|
|
3726
|
+
|
|
3727
|
+
# Setup history and session
|
|
3728
|
+
try:
|
|
3729
|
+
if os.path.isdir(MCCLI_CONFIG_DIR):
|
|
3730
|
+
our_history = FileHistory(MCCLI_REPEATER_HISTORY_FILE)
|
|
3731
|
+
else:
|
|
3732
|
+
our_history = None
|
|
3733
|
+
except Exception:
|
|
3734
|
+
our_history = None
|
|
3735
|
+
|
|
3736
|
+
session = PromptSession(
|
|
3737
|
+
history=our_history,
|
|
3738
|
+
wrap_lines=False,
|
|
3739
|
+
mouse_support=False,
|
|
3740
|
+
complete_style=CompleteStyle.MULTI_COLUMN
|
|
3741
|
+
)
|
|
3742
|
+
|
|
3743
|
+
# Setup key bindings
|
|
3744
|
+
bindings = KeyBindings()
|
|
3745
|
+
|
|
3746
|
+
@bindings.add("escape")
|
|
3747
|
+
def _(event):
|
|
3748
|
+
event.app.current_buffer.cancel_completion()
|
|
3749
|
+
|
|
3750
|
+
# Build prompt
|
|
3751
|
+
prompt_base = f"{ANSI_BGRAY}{device_name}{ANSI_MAGENTA}>{ANSI_END} "
|
|
3752
|
+
|
|
3753
|
+
# Setup completer
|
|
3754
|
+
completer = NestedCompleter.from_nested_dict(REPEATER_COMMANDS)
|
|
3755
|
+
|
|
3756
|
+
while True:
|
|
3757
|
+
try:
|
|
3758
|
+
cmd = await session.prompt_async(
|
|
3759
|
+
ANSI(prompt_base),
|
|
3760
|
+
completer=completer,
|
|
3761
|
+
complete_while_typing=False,
|
|
3762
|
+
key_bindings=bindings
|
|
3763
|
+
)
|
|
3764
|
+
except (KeyboardInterrupt, EOFError):
|
|
3765
|
+
break
|
|
3766
|
+
|
|
3767
|
+
cmd = cmd.strip()
|
|
3768
|
+
|
|
3769
|
+
if not cmd:
|
|
3770
|
+
continue
|
|
3771
|
+
|
|
3772
|
+
if cmd.lower() in ("quit", "exit", "q"):
|
|
3773
|
+
break
|
|
3774
|
+
|
|
3775
|
+
if cmd.lower() == "help":
|
|
3776
|
+
print(REPEATER_HELP)
|
|
3777
|
+
continue
|
|
3778
|
+
|
|
3779
|
+
# Send command with CR terminator
|
|
3780
|
+
ser.write(f"{cmd}\r".encode())
|
|
3781
|
+
await asyncio.sleep(0.3)
|
|
3782
|
+
|
|
3783
|
+
# Read response
|
|
3784
|
+
response = ser.read(ser.in_waiting or 4096).decode(errors='ignore')
|
|
3785
|
+
if response:
|
|
3786
|
+
# Clean up echo and format response
|
|
3787
|
+
lines = response.strip().split('\n')
|
|
3788
|
+
for line in lines:
|
|
3789
|
+
line = line.strip()
|
|
3790
|
+
if line and line != cmd: # Skip echo of command
|
|
3791
|
+
# Color code certain responses
|
|
3792
|
+
if line.startswith("OK") or line.startswith("ok"):
|
|
3793
|
+
print(f"{ANSI_GREEN}{line}{ANSI_END}")
|
|
3794
|
+
elif line.startswith("Error") or line.startswith("ERR"):
|
|
3795
|
+
print(f"{ANSI_RED}{line}{ANSI_END}")
|
|
3796
|
+
elif line.startswith("->"):
|
|
3797
|
+
print(f"{ANSI_CYAN}{line}{ANSI_END}")
|
|
3798
|
+
else:
|
|
3799
|
+
print(line)
|
|
3800
|
+
|
|
3801
|
+
ser.close()
|
|
3802
|
+
print(f"\n{ANSI_BGRAY}Disconnected from repeater.{ANSI_END}")
|
|
3803
|
+
|
|
3591
3804
|
async def main(argv):
|
|
3592
3805
|
""" Do the job """
|
|
3593
3806
|
json_output = JSON
|
|
@@ -3598,6 +3811,7 @@ async def main(argv):
|
|
|
3598
3811
|
hostname = None
|
|
3599
3812
|
serial_port = None
|
|
3600
3813
|
baudrate = 115200
|
|
3814
|
+
repeater_mode = False
|
|
3601
3815
|
timeout = 2
|
|
3602
3816
|
pin = None
|
|
3603
3817
|
first_device = False
|
|
@@ -3608,7 +3822,7 @@ async def main(argv):
|
|
|
3608
3822
|
address = f.readline().strip()
|
|
3609
3823
|
|
|
3610
3824
|
try:
|
|
3611
|
-
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:
|
|
3825
|
+
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:Cr")
|
|
3612
3826
|
except getopt.GetoptError:
|
|
3613
3827
|
print("Unrecognized option, use -h to get more help")
|
|
3614
3828
|
command_usage()
|
|
@@ -3620,6 +3834,8 @@ async def main(argv):
|
|
|
3620
3834
|
process_event_message.color = False
|
|
3621
3835
|
case "-C":
|
|
3622
3836
|
interactive_loop.classic = not interactive_loop.classic
|
|
3837
|
+
case "-r": # repeater mode (raw text CLI)
|
|
3838
|
+
repeater_mode = True
|
|
3623
3839
|
case "-d" : # name specified on cmdline
|
|
3624
3840
|
address = arg
|
|
3625
3841
|
case "-a" : # address specified on cmdline
|
|
@@ -3707,6 +3923,15 @@ async def main(argv):
|
|
|
3707
3923
|
elif (json_output) :
|
|
3708
3924
|
logger.setLevel(logging.ERROR)
|
|
3709
3925
|
|
|
3926
|
+
# Repeater mode - raw text CLI over serial
|
|
3927
|
+
if repeater_mode:
|
|
3928
|
+
if serial_port is None:
|
|
3929
|
+
print("Error: Repeater mode (-r) requires serial port (-s)")
|
|
3930
|
+
command_usage()
|
|
3931
|
+
return
|
|
3932
|
+
await repeater_loop(serial_port, baudrate)
|
|
3933
|
+
return
|
|
3934
|
+
|
|
3710
3935
|
mc = None
|
|
3711
3936
|
if not hostname is None : # connect via tcp
|
|
3712
3937
|
mc = await MeshCore.create_tcp(host=hostname, port=port, debug=debug, only_error=json_output)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|