meshcore-cli 1.2.11__tar.gz → 1.2.14__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcore-cli
3
- Version: 1.2.11
3
+ Version: 1.2.14
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
@@ -10,7 +10,7 @@ License-File: LICENSE
10
10
  Classifier: Operating System :: OS Independent
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Requires-Python: >=3.10
13
- Requires-Dist: meshcore>=2.1.23
13
+ Requires-Dist: meshcore>=2.1.24
14
14
  Requires-Dist: prompt-toolkit>=3.0.50
15
15
  Requires-Dist: pycryptodome
16
16
  Requires-Dist: requests>=2.28.0
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meshcore-cli"
7
- version = "1.2.11"
7
+ version = "1.2.14"
8
8
  authors = [
9
9
  { name="Florent de Lamotte", email="florent@frizoncorrea.fr" },
10
10
  ]
@@ -17,7 +17,7 @@ classifiers = [
17
17
  ]
18
18
  license = "MIT"
19
19
  license-files = ["LICEN[CS]E*"]
20
- dependencies = [ "meshcore >= 2.1.23", "prompt_toolkit >= 3.0.50", "requests >= 2.28.0", "pycryptodome" ]
20
+ dependencies = [ "meshcore >= 2.1.24", "prompt_toolkit >= 3.0.50", "requests >= 2.28.0", "pycryptodome" ]
21
21
 
22
22
  [project.urls]
23
23
  Homepage = "https://github.com/fdlamotte/meshcore-cli"
@@ -32,7 +32,7 @@ import re
32
32
  from meshcore import MeshCore, EventType, logger
33
33
 
34
34
  # Version
35
- VERSION = "v1.2.11"
35
+ VERSION = "v1.2.14"
36
36
 
37
37
  # default ble address is stored in a config file
38
38
  MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
@@ -76,13 +76,11 @@ ANSI_YELLOW = "\033[0;33m"
76
76
  ANSI_BYELLOW = "\033[1;33m"
77
77
 
78
78
  #Unicode chars
79
- # some possible symbols for prompts 🭬🬛🬗🭬🬛🬃🬗🭬🬛🬃🬗🬏🭀🭋🭨🮋
80
- ARROW_TAIL = "🭨"
81
- ARROW_HEAD = "🭬"
82
-
83
- if platform.system() == 'Windows' or platform.system() == 'Darwin':
84
- ARROW_TAIL = ""
85
- ARROW_HEAD = " "
79
+ # some possible symbols for prompts 🭬🬛🬗🭬🬛🬃🬗🭬🬛🬃🬗🬏🭀🭋🭨🮋
80
+ ARROW_HEAD = ""
81
+ SLASH_END = ""
82
+ SLASH_START = ""
83
+ INVERT_SLASH = False
86
84
 
87
85
  def escape_ansi(line):
88
86
  ansi_escape = re.compile(r'(?:\x1B[@-_]|[\x80-\x9F])[0-?]*[ -/]*[@-~]')
@@ -470,7 +468,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
470
468
  "login" : contact_list,
471
469
  "cmd" : contact_list,
472
470
  "req_status" : contact_list,
473
- "req_bstatus" : contact_list,
471
+ "req_neighbours": contact_list,
474
472
  "logout" : contact_list,
475
473
  "req_telemetry" : contact_list,
476
474
  "req_binary" : contact_list,
@@ -495,7 +493,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
495
493
  "print_snr" : {"on":None, "off": None},
496
494
  "json_msgs" : {"on":None, "off": None},
497
495
  "color" : {"on":None, "off":None},
498
- "print_name" : {"on":None, "off":None},
499
496
  "print_adverts" : {"on":None, "off":None},
500
497
  "json_log_rx" : {"on":None, "off":None},
501
498
  "channel_echoes" : {"on":None, "off":None},
@@ -525,7 +522,6 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
525
522
  "print_snr":None,
526
523
  "json_msgs":None,
527
524
  "color":None,
528
- "print_name":None,
529
525
  "print_adverts":None,
530
526
  "json_log_rx":None,
531
527
  "channel_echoes":None,
@@ -577,7 +573,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
577
573
  "login" : None,
578
574
  "logout" : None,
579
575
  "req_status" : None,
580
- "req_bstatus" : None,
576
+ "req_neighbours": None,
581
577
  "cmd" : None,
582
578
  "ver" : None,
583
579
  "advert" : None,
@@ -764,26 +760,32 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
764
760
 
765
761
  color = process_event_message.color
766
762
  classic = interactive_loop.classic or not color
767
- print_name = interactive_loop.print_name
768
763
 
769
764
  if classic:
770
765
  prompt = ""
771
766
  else:
772
767
  prompt = f"{ANSI_INVERT}"
773
768
 
774
- if print_name or contact is None :
775
- if color:
776
- prompt = prompt + f"{ANSI_BGRAY}"
777
- prompt = prompt + f"{mc.self_info['name']}"
778
- if contact is None: # display scope
779
- if not scope is None:
780
- prompt = prompt + f"|{scope}"
769
+ prompt = prompt + f"{ANSI_BGRAY}"
770
+ prompt = prompt + f"{mc.self_info['name']}"
771
+ if contact is None: # display scope
772
+ if not scope is None:
773
+ prompt = prompt + f"|{scope}"
774
+
775
+ if contact is None :
781
776
  if classic :
782
- prompt = prompt + "> "
777
+ prompt = prompt + ">"
783
778
  else :
784
- prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}{ANSI_INVERT}"
785
-
786
- if not contact is None :
779
+ prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}"
780
+ else:
781
+ if classic :
782
+ prompt = prompt + "/"
783
+ else :
784
+ if INVERT_SLASH:
785
+ prompt = prompt + f"{ANSI_INVERT}"
786
+ else:
787
+ prompt = prompt + f"{ANSI_NORMAL}"
788
+ prompt = prompt + f"{SLASH_START}"
787
789
  if not last_ack:
788
790
  prompt = prompt + f"{ANSI_BRED}"
789
791
  if classic :
@@ -799,11 +801,9 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
799
801
  else :
800
802
  prompt = prompt + f"{ANSI_BBLUE}"
801
803
  if not classic:
804
+ prompt = prompt + f"{SLASH_END}"
802
805
  prompt = prompt + f"{ANSI_INVERT}"
803
806
 
804
- if print_name and not classic :
805
- prompt = prompt + f"{ANSI_NORMAL}{ARROW_TAIL}{ANSI_INVERT}"
806
-
807
807
  prompt = prompt + f"{contact['adv_name']}"
808
808
  if contact["type"] == 0 or contact["out_path_len"]==-1:
809
809
  if scope is None:
@@ -817,14 +817,15 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
817
817
  prompt = prompt + "|" + contact["out_path"]
818
818
 
819
819
  if classic :
820
- prompt = prompt + f"{ANSI_NORMAL}> "
820
+ prompt = prompt + f"{ANSI_NORMAL}>"
821
821
  else:
822
822
  prompt = prompt + f"{ANSI_NORMAL}{ARROW_HEAD}"
823
823
 
824
824
  prompt = prompt + f"{ANSI_END}"
825
825
 
826
- if not color :
827
- prompt=escape_ansi(prompt)
826
+ prompt = prompt + " "
827
+ if not color :
828
+ prompt=escape_ansi(prompt)
828
829
 
829
830
  session.app.ttimeoutlen = 0.2
830
831
  session.app.timeoutlen = 0.2
@@ -1035,8 +1036,10 @@ Line starting with \"$\" or \".\" will issue a meshcli command.
1035
1036
  except asyncio.CancelledError:
1036
1037
  # Handle task cancellation from KeyboardInterrupt in asyncio.run()
1037
1038
  print("Exiting cli")
1038
- interactive_loop.classic = False
1039
- interactive_loop.print_name = True
1039
+ if platform.system() == "Darwin" or platform.system() == "Windows":
1040
+ interactive_loop.classic = True
1041
+ else:
1042
+ interactive_loop.classic = False
1040
1043
 
1041
1044
  async def process_contact_chat_line(mc, contact, line):
1042
1045
  if contact["type"] == 0:
@@ -1083,7 +1086,7 @@ async def process_contact_chat_line(mc, contact, line):
1083
1086
  line == "dp" or line == "disc_path" or\
1084
1087
  line == "contact_info" or line == "ci" or\
1085
1088
  line == "req_status" or line == "rs" or\
1086
- line == "req_bstatus" or line == "rbs" or\
1089
+ line == "req_neighbours" or line == "rn" or\
1087
1090
  line == "req_telemetry" or line == "rt" or\
1088
1091
  line == "req_acl" or\
1089
1092
  line == "path" or\
@@ -1613,7 +1616,7 @@ async def print_disc_trace_to (mc, contact):
1613
1616
 
1614
1617
  async def next_cmd(mc, cmds, json_output=False):
1615
1618
  """ process next command """
1616
- global ARROW_TAIL, ARROW_HEAD
1619
+ global ARROW_HEAD, SLASH_START, SLASH_END, INVERT_SLASH
1617
1620
  try :
1618
1621
  argnum = 0
1619
1622
 
@@ -1739,18 +1742,18 @@ async def next_cmd(mc, cmds, json_output=False):
1739
1742
  msg_ack.max_attempts=int(cmds[2])
1740
1743
  case "flood_after":
1741
1744
  msg_ack.flood_after=int(cmds[2])
1742
- case "print_name":
1743
- interactive_loop.print_name = (cmds[2] == "on")
1744
- if json_output :
1745
- print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
1746
1745
  case "classic_prompt":
1747
1746
  interactive_loop.classic = (cmds[2] == "on")
1748
1747
  if json_output :
1749
1748
  print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
1750
- case "arrow_tail":
1751
- ARROW_TAIL = cmds[2]
1752
1749
  case "arrow_head":
1753
1750
  ARROW_HEAD = cmds[2]
1751
+ case "slash_start":
1752
+ SLASH_START = cmds[2]
1753
+ case "slash_end":
1754
+ SLASH_END = cmds[2]
1755
+ case "invert_slash":
1756
+ INVERT_SLASH = cmds[2] == "on"
1754
1757
  case "color" :
1755
1758
  process_event_message.color = (cmds[2] == "on")
1756
1759
  if json_output :
@@ -1981,11 +1984,6 @@ async def next_cmd(mc, cmds, json_output=False):
1981
1984
  print(json.dumps({"flood_after" : msg_ack.flood_after}))
1982
1985
  else:
1983
1986
  print(f"flood_after: {msg_ack.flood_after}")
1984
- case "print_name":
1985
- if json_output :
1986
- print(json.dumps({"print_name" : interactive_loop.print_name}))
1987
- else:
1988
- print(f"{'on' if interactive_loop.print_name else 'off'}")
1989
1987
  case "classic_prompt":
1990
1988
  if json_output :
1991
1989
  print(json.dumps({"classic_prompt" : interactive_loop.classic}))
@@ -2342,12 +2340,11 @@ async def next_cmd(mc, cmds, json_output=False):
2342
2340
  else :
2343
2341
  color = process_event_message.color
2344
2342
  classic = interactive_loop.classic or not color
2345
- print("]",end="")
2346
2343
  for t in ev.payload["path"]:
2347
2344
  if classic :
2348
2345
  print("→",end="")
2349
2346
  else:
2350
- print(f" {ANSI_INVERT}", end="")
2347
+ print(f"{ANSI_INVERT}", end="")
2351
2348
  snr = t['snr']
2352
2349
  if color:
2353
2350
  if snr >= 10 :
@@ -2366,7 +2363,7 @@ async def next_cmd(mc, cmds, json_output=False):
2366
2363
  if "hash" in t:
2367
2364
  print(f"[{t['hash']}]",end="")
2368
2365
  else:
2369
- print("[")
2366
+ print()
2370
2367
 
2371
2368
  case "login" | "l" :
2372
2369
  argnum = 2
@@ -2426,46 +2423,6 @@ async def next_cmd(mc, cmds, json_output=False):
2426
2423
  contact = mc.get_contact_by_name(cmds[1])
2427
2424
  contact["timeout"] = float(cmds[2])
2428
2425
 
2429
- case "req_status" | "rs" :
2430
- argnum = 1
2431
- await mc.ensure_contacts()
2432
- contact = mc.get_contact_by_name(cmds[1])
2433
- res = await mc.commands.send_statusreq(contact)
2434
- logger.debug(res)
2435
- if res.type == EventType.ERROR:
2436
- print(f"Error while requesting status: {res}")
2437
- else :
2438
- timeout = res.payload["suggested_timeout"]/800 if not "timeout" in contact or contact['timeout']==0 else contact["timeout"]
2439
- res = await mc.wait_for_event(EventType.STATUS_RESPONSE, timeout=timeout)
2440
- logger.debug(res)
2441
- if res is None:
2442
- if json_output :
2443
- print(json.dumps({"error" : "Timeout waiting status"}))
2444
- else:
2445
- print("Timeout waiting status")
2446
- else :
2447
- print(json.dumps(res.payload, indent=4))
2448
-
2449
- case "req_telemetry" | "rt" :
2450
- argnum = 1
2451
- await mc.ensure_contacts()
2452
- contact = mc.get_contact_by_name(cmds[1])
2453
- res = await mc.commands.send_telemetry_req(contact)
2454
- logger.debug(res)
2455
- if res.type == EventType.ERROR:
2456
- print(f"Error while requesting telemetry")
2457
- else:
2458
- timeout = res.payload["suggested_timeout"]/800 if not "timeout" in contact or contact['timeout']==0 else contact["timeout"]
2459
- res = await mc.wait_for_event(EventType.TELEMETRY_RESPONSE, timeout=timeout)
2460
- logger.debug(res)
2461
- if res is None:
2462
- if json_output :
2463
- print(json.dumps({"error" : "Timeout waiting telemetry"}))
2464
- else:
2465
- print("Timeout waiting telemetry")
2466
- else :
2467
- print(json.dumps(res.payload, indent=4))
2468
-
2469
2426
  case "disc_path" | "dp" :
2470
2427
  argnum = 1
2471
2428
  await mc.ensure_contacts()
@@ -2551,7 +2508,7 @@ async def next_cmd(mc, cmds, json_output=False):
2551
2508
 
2552
2509
  print(f" {name:16} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
2553
2510
 
2554
- case "req_btelemetry"|"rbt" :
2511
+ case "req_telemetry"|"rt" :
2555
2512
  argnum = 1
2556
2513
  await mc.ensure_contacts()
2557
2514
  contact = mc.get_contact_by_name(cmds[1])
@@ -2563,9 +2520,13 @@ async def next_cmd(mc, cmds, json_output=False):
2563
2520
  else:
2564
2521
  print("Error getting data")
2565
2522
  else :
2566
- print(json.dumps(res))
2523
+ print(json.dumps({
2524
+ "name": contact["adv_name"],
2525
+ "pubkey_pre": contact["public_key"][0:12],
2526
+ "lpp": res,
2527
+ }, indent = 4))
2567
2528
 
2568
- case "req_bstatus"|"rbs" :
2529
+ case "req_status"|"rs" :
2569
2530
  argnum = 1
2570
2531
  await mc.ensure_contacts()
2571
2532
  contact = mc.get_contact_by_name(cmds[1])
@@ -2634,6 +2595,31 @@ async def next_cmd(mc, cmds, json_output=False):
2634
2595
  name = f"{ct['adv_name']:<20} [{e['key']}]"
2635
2596
  print(f"{name:{' '}<35}: {e['perm']:02x}")
2636
2597
 
2598
+ case "req_neighbours"|"rn" :
2599
+ argnum = 1
2600
+ await mc.ensure_contacts()
2601
+ contact = mc.get_contact_by_name(cmds[1])
2602
+ timeout = 0 if not "timeout" in contact else contact["timeout"]
2603
+ res = await mc.commands.fetch_all_neighbours(contact, timeout=timeout)
2604
+ if res is None :
2605
+ if json_output :
2606
+ print(json.dumps({"error" : "Getting data"}))
2607
+ else:
2608
+ print("Error getting data")
2609
+ else :
2610
+ if json_output:
2611
+ print(json.dumps(res, indent=4))
2612
+ else:
2613
+ print(f"Got {res['results_count']} neighbours out of {res['neighbours_count']} from {contact['adv_name']}:")
2614
+ for n in res['neighbours']:
2615
+ ct = mc.get_contact_by_key_prefix(n["pubkey"])
2616
+ if ct :
2617
+ name = f"[{n['pubkey'][0:8]}] {ct['adv_name']}"
2618
+ else:
2619
+ name = f"[{n['pubkey']}]"
2620
+
2621
+ print(f" {name:30} last viewed {n['secs_ago']} sec ago at {n['snr']} ")
2622
+
2637
2623
  case "req_binary" :
2638
2624
  argnum = 2
2639
2625
  await mc.ensure_contacts()
@@ -3131,6 +3117,7 @@ def command_help():
3131
3117
  cmd <name> <cmd> : sends a command to a repeater (no ack) c [
3132
3118
  wmt8 : wait for a msg (reply) with a timeout ]
3133
3119
  req_status <name> : requests status from a node rs
3120
+ req_neighbours <name> : requests for neighbours in binary form rn
3134
3121
  trace <path> : run a trace, path is comma separated""")
3135
3122
 
3136
3123
  def usage () :
@@ -3146,6 +3133,7 @@ def usage () :
3146
3133
  -D : debug
3147
3134
  -S : scan for devices and show a selector
3148
3135
  -l : list available ble/serial devices and exit
3136
+ -C : toggles classic mode for prompt
3149
3137
  -c <on/off> : disables most of color output if off
3150
3138
  -T <timeout> : timeout for the ble scan (-S and -l) default 2s
3151
3139
  -a <address> : specifies device address (can be a name)
@@ -3214,12 +3202,14 @@ async def main(argv):
3214
3202
  with open(MCCLI_ADDRESS, encoding="utf-8") as f :
3215
3203
  address = f.readline().strip()
3216
3204
 
3217
- opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:")
3205
+ opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:fjDhvSlT:Pc:C")
3218
3206
  for opt, arg in opts :
3219
3207
  match opt:
3220
3208
  case "-c" :
3221
3209
  if arg == "off":
3222
3210
  process_event_message.color = False
3211
+ case "-C":
3212
+ interactive_loop.classic = not interactive_loop.classic
3223
3213
  case "-d" : # name specified on cmdline
3224
3214
  address = arg
3225
3215
  case "-a" : # address specified on cmdline
File without changes
File without changes
File without changes
File without changes
File without changes