meshcore-cli 1.3.0__tar.gz → 1.3.3__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.3.0
3
+ Version: 1.3.3
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.24
13
+ Requires-Dist: meshcore>=2.2.1
14
14
  Requires-Dist: prompt-toolkit>=3.0.50
15
15
  Requires-Dist: pycryptodome
16
16
  Requires-Dist: requests>=2.28.0
@@ -198,6 +198,18 @@ f1down/#fdl|*> 8
198
198
  #fdl f1down: 8 [2521] 1.00-109
199
199
  ```
200
200
 
201
+ ### Contact management
202
+
203
+ To receive a message from another user, it is necessary to have its public key. This key is stored on a contact list in the device, and this list has a finite size (50 when meshcore started, now over 350 for most devices).
204
+
205
+ By default contacts are automatically added to the device contact list when an advertisement is received, so as soon as you receive an advert, you can talk with your buddy.
206
+
207
+ With growing number of users, it becomes necessary to manage contact list and one of the ways is to add contacts manually to the device. This is done by turning on `manual_add_contacts`. Once this option has been turned on, a pending list is built by meshcore-cli from the received adverts. You can view the list issuing a `pending_contacts` command, flush the list using `flush_pending` or add a contact from the list with `add_pending` followed by the key of the contact or its name (both will be auto-completed with tab).
208
+
209
+ This feature only really works in interactive mode.
210
+
211
+ Note: There is also an `auto_update_contacts` setting that has nothing to do with adding contacts, it permits to automatically sync contact lists between device and meshcore-cli (when there is an update in name, location or path).
212
+
201
213
  ### Issuing batch commands to contacts with apply to
202
214
 
203
215
  `apply_to <f> <cmd>` : applies cmd to contacts matching filter `<f>` it can be used to apply the same command to a pool of repeaters, or remove some contacts matching a condition.
@@ -180,6 +180,18 @@ f1down/#fdl|*> 8
180
180
  #fdl f1down: 8 [2521] 1.00-109
181
181
  ```
182
182
 
183
+ ### Contact management
184
+
185
+ To receive a message from another user, it is necessary to have its public key. This key is stored on a contact list in the device, and this list has a finite size (50 when meshcore started, now over 350 for most devices).
186
+
187
+ By default contacts are automatically added to the device contact list when an advertisement is received, so as soon as you receive an advert, you can talk with your buddy.
188
+
189
+ With growing number of users, it becomes necessary to manage contact list and one of the ways is to add contacts manually to the device. This is done by turning on `manual_add_contacts`. Once this option has been turned on, a pending list is built by meshcore-cli from the received adverts. You can view the list issuing a `pending_contacts` command, flush the list using `flush_pending` or add a contact from the list with `add_pending` followed by the key of the contact or its name (both will be auto-completed with tab).
190
+
191
+ This feature only really works in interactive mode.
192
+
193
+ Note: There is also an `auto_update_contacts` setting that has nothing to do with adding contacts, it permits to automatically sync contact lists between device and meshcore-cli (when there is an update in name, location or path).
194
+
183
195
  ### Issuing batch commands to contacts with apply to
184
196
 
185
197
  `apply_to <f> <cmd>` : applies cmd to contacts matching filter `<f>` it can be used to apply the same command to a pool of repeaters, or remove some contacts matching a condition.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meshcore-cli"
7
- version = "1.3.0"
7
+ version = "1.3.3"
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.24", "prompt_toolkit >= 3.0.50", "requests >= 2.28.0", "pycryptodome" ]
20
+ dependencies = [ "meshcore >= 2.2.1", "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.3.0"
35
+ VERSION = "v1.3.3"
36
36
 
37
37
  # default ble address is stored in a config file
38
38
  MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
@@ -40,6 +40,10 @@ MCCLI_ADDRESS = MCCLI_CONFIG_DIR + "default_address"
40
40
  MCCLI_HISTORY_FILE = MCCLI_CONFIG_DIR + "history"
41
41
  MCCLI_INIT_SCRIPT = MCCLI_CONFIG_DIR + "init"
42
42
 
43
+ PAYLOAD_TYPENAMES = ["REQ", "RESPONSE", "TEXT_MSG", "ACK", "ADVERT", "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", "TRACE", "MULTIPART", "CONTROL"]
44
+ ROUTE_TYPENAMES = ["TC_FLOOD", "FLOOD", "DIRECT", "TC_DIRECT"]
45
+ CONTACT_TYPENAMES = ["NONE","CLI","REP","ROOM","SENS"]
46
+
43
47
  # Fallback address if config file not found
44
48
  # if None or "" then a scan is performed
45
49
  ADDRESS = ""
@@ -203,9 +207,6 @@ process_event_message.print_snr=False
203
207
  process_event_message.color=True
204
208
  process_event_message.last_node=None
205
209
 
206
- PAYLOAD_TYPENAMES = ["REQ", "RESPONSE", "TEXT_MSG", "ACK", "ADVERT", "GRP_TXT", "GRP_DATA", "ANON_REQ", "PATH", "TRACE", "MULTIPART", "CONTROL"]
207
- ROUTE_TYPENAMES = ["TC_FLOOD", "FLOOD", "DIRECT", "TC_DIRECT"]
208
-
209
210
  async def handle_log_rx(event):
210
211
  mc = handle_log_rx.mc
211
212
 
@@ -284,7 +285,7 @@ async def handle_log_rx(event):
284
285
  if chan_name != "" :
285
286
  width = os.get_terminal_size().columns
286
287
  cars = width - 13 - 2 * path_len - len(chan_name) - 1
287
- dispmsg = message[0:cars]
288
+ dispmsg = message.replace("\n","")[0:cars]
288
289
  txt = f"{ANSI_LIGHT_GRAY}{chan_name} {ANSI_DGREEN}{dispmsg+(cars-len(dispmsg))*' '} {ANSI_YELLOW}[{path}]{ANSI_LIGHT_GRAY}{event.payload['snr']:6,.2f}{event.payload['rssi']:4}{ANSI_END}"
289
290
  if handle_message.above:
290
291
  print_above(txt)
@@ -431,7 +432,7 @@ class MyNestedCompleter(NestedCompleter):
431
432
  opts = self.options.keys()
432
433
  completer = WordCompleter(
433
434
  opts, ignore_case=self.ignore_case,
434
- pattern=re.compile(r"([a-zA-Z0-9_\\/\#]+|[^a-zA-Z0-9_\s\#]+)"))
435
+ pattern=re.compile(r"([a-zA-Z0-9_\\/\#\?]+|[^a-zA-Z0-9_\s\#\?]+)"))
435
436
  yield from completer.get_completions(document, complete_event)
436
437
  else: # normal behavior for remainder
437
438
  yield from super().get_completions(document, complete_event)
@@ -582,11 +583,24 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
582
583
  "flood_after":None,
583
584
  "custom":None,
584
585
  },
586
+ "?get":None,
587
+ "?set":None,
588
+ "?scope":None,
589
+ "?contact_info":None,
590
+ "?apply_to":None,
591
+ "?at":None,
592
+ "?node_discover":None,
593
+ "?nd":None,
594
+ "?pending_contacts":None,
595
+ "?add_pending":None,
596
+ "?flush_pending":None,
585
597
  }
586
598
 
587
599
  contact_completion_list = {
588
600
  "contact_info": None,
589
601
  "contact_name": None,
602
+ "contact_key": None,
603
+ "contact_type": None,
590
604
  "contact_lastmod": None,
591
605
  "export_contact" : None,
592
606
  "share_contact" : None,
@@ -749,7 +763,7 @@ make_completion_dict.custom_vars = {}
749
763
  async def interactive_loop(mc, to=None) :
750
764
  print("""Interactive mode, most commands from terminal chat should work.
751
765
  Use \"to\" to select recipient, use Tab to complete name ...
752
- Line starting with \"$\" or \".\" will issue a meshcli command.
766
+ Some cmds have an help accessible with ?<cmd>. Do ?[Tab] to get a list.
753
767
  \"quit\", \"q\", CTRL+D will end interactive mode""")
754
768
 
755
769
  contact = to
@@ -1102,6 +1116,26 @@ async def process_contact_chat_line(mc, contact, line):
1102
1116
  await process_cmds(mc, args)
1103
1117
  return True
1104
1118
 
1119
+ if line.startswith("contact_key") or line.startswith("ck"):
1120
+ print(contact['public_key'],end="")
1121
+ if " " in line:
1122
+ print(" ", end="", flush=True)
1123
+ secline = line.split(" ", 1)[1]
1124
+ await process_contact_chat_line(mc, contact, secline)
1125
+ else:
1126
+ print("")
1127
+ return True
1128
+
1129
+ if line.startswith("contact_type") or line.startswith("ct"):
1130
+ print(f"{CONTACT_TYPENAMES[contact['type']]:4}",end="")
1131
+ if " " in line:
1132
+ print(" ", end="", flush=True)
1133
+ secline = line.split(" ", 1)[1]
1134
+ await process_contact_chat_line(mc, contact, secline)
1135
+ else:
1136
+ print("")
1137
+ return True
1138
+
1105
1139
  if line.startswith("contact_name") or line.startswith("cn"):
1106
1140
  print(contact['adv_name'],end="")
1107
1141
  if " " in line:
@@ -1112,7 +1146,22 @@ async def process_contact_chat_line(mc, contact, line):
1112
1146
  print("")
1113
1147
  return True
1114
1148
 
1115
- if line.startswith("sleep") or line.startswith("s"):
1149
+ if line.startswith("path") :
1150
+ if contact['out_path_len'] == -1:
1151
+ print("Flood", end="")
1152
+ elif contact['out_path_len'] == 0:
1153
+ print("0 hop", end="")
1154
+ else:
1155
+ print(contact['out_path'],end="")
1156
+ if " " in line:
1157
+ print(" ", end="", flush=True)
1158
+ secline = line.split(" ", 1)[1]
1159
+ await process_contact_chat_line(mc, contact, secline)
1160
+ else:
1161
+ print("")
1162
+ return True
1163
+
1164
+ if line.startswith("sleep ") or line.startswith("s "):
1116
1165
  try:
1117
1166
  sleeptime = int(line.split(" ",2)[1])
1118
1167
  cmd_pos = 2
@@ -1143,20 +1192,24 @@ async def process_contact_chat_line(mc, contact, line):
1143
1192
  return True
1144
1193
 
1145
1194
  # commands that take contact as second arg will be sent to recipient
1146
- if line == "sc" or line == "share_contact" or\
1147
- line == "ec" or line == "export_contact" or\
1148
- line == "uc" or line == "upload_contact" or\
1149
- line == "rp" or line == "reset_path" or\
1150
- line == "dp" or line == "disc_path" or\
1151
- line == "contact_info" or line == "ci" or\
1152
- line == "req_status" or line == "rs" or\
1153
- line == "req_neighbours" or line == "rn" or\
1154
- line == "req_telemetry" or line == "rt" or\
1155
- line == "req_acl" or\
1156
- line == "path" or\
1157
- line == "logout" :
1158
- args = [line, contact['adv_name']]
1195
+ # and can be chained ...
1196
+ if line.startswith("sc") or line.startswith("share_contact") or\
1197
+ line.startswith("ec") or line.startswith("export_contact") or\
1198
+ line.startswith("uc") or line.startswith("upload_contact") or\
1199
+ line.startswith("rp") or line.startswith("reset_path") or\
1200
+ line.startswith("dp") or line.startswith("disc_path") or\
1201
+ line.startswith("contact_info") or line.startswith("ci") or\
1202
+ line.startswith("req_status") or line.startswith("rs") or\
1203
+ line.startswith("req_neighbours") or line.startswith("rn") or\
1204
+ line.startswith("req_telemetry") or line.startswith("rt") or\
1205
+ line.startswith("req_acl") or\
1206
+ line.startswith("path") or\
1207
+ line.startswith("logout") :
1208
+ args = [line.split()[0], contact['adv_name']]
1159
1209
  await process_cmds(mc, args)
1210
+ if " " in line:
1211
+ secline = line.split(" ", 1)[1]
1212
+ await process_contact_chat_line(mc, contact, secline)
1160
1213
  return True
1161
1214
 
1162
1215
  # special case for rp that can be chained from cmdline
@@ -1169,6 +1222,8 @@ async def process_contact_chat_line(mc, contact, line):
1169
1222
 
1170
1223
  if line.startswith("set timeout "):
1171
1224
  cmds=line.split(" ")
1225
+ #args = ["contact_timeout", contact['adv_name'], cmds[2]]
1226
+ #await process_cmds(mc, args)
1172
1227
  contact["timeout"] = float(cmds[2])
1173
1228
  return True
1174
1229
 
@@ -1306,12 +1361,13 @@ async def process_contact_chat_line(mc, contact, line):
1306
1361
 
1307
1362
  return False
1308
1363
 
1309
- async def apply_command_to_contacts(mc, contact_filter, line):
1364
+ async def apply_command_to_contacts(mc, contact_filter, line, json_output=False):
1310
1365
  upd_before = None
1311
1366
  upd_after = None
1312
1367
  contact_type = None
1313
1368
  min_hops = None
1314
1369
  max_hops = None
1370
+ count = 0
1315
1371
 
1316
1372
  await mc.ensure_contacts()
1317
1373
 
@@ -1366,6 +1422,9 @@ async def apply_command_to_contacts(mc, contact_filter, line):
1366
1422
  (upd_after is None or contact["lastmod"] > upd_after) and\
1367
1423
  (min_hops is None or contact["out_path_len"] >= min_hops) and\
1368
1424
  (max_hops is None or contact["out_path_len"] <= max_hops):
1425
+
1426
+ count = count + 1
1427
+
1369
1428
  if await process_contact_chat_line(mc, contact, line):
1370
1429
  pass
1371
1430
 
@@ -1390,6 +1449,9 @@ async def apply_command_to_contacts(mc, contact_filter, line):
1390
1449
  else:
1391
1450
  logger.error(f"Can't send {line} to {contact['adv_name']}")
1392
1451
 
1452
+ if not json_output:
1453
+ print(f"> {count} matches in contacts")
1454
+
1393
1455
  async def send_cmd (mc, contact, cmd) :
1394
1456
  res = await mc.commands.send_cmd(contact, cmd)
1395
1457
  if not res is None and not res.type == EventType.ERROR:
@@ -1780,7 +1842,7 @@ async def next_cmd(mc, cmds, json_output=False):
1780
1842
 
1781
1843
  case "apply_to"|"at":
1782
1844
  argnum = 2
1783
- await apply_command_to_contacts(mc, cmds[1], cmds[2])
1845
+ await apply_command_to_contacts(mc, cmds[1], cmds[2], json_output=json_output)
1784
1846
 
1785
1847
  case "set":
1786
1848
  argnum = 2
@@ -2532,20 +2594,16 @@ async def next_cmd(mc, cmds, json_output=False):
2532
2594
  await mc.ensure_contacts()
2533
2595
  print(f"Discovered {len(dn)} nodes:")
2534
2596
  for n in dn:
2535
- name = f"{n['pubkey'][0:2]} {mc.get_contact_by_key_prefix(n['pubkey'])['adv_name']}"
2536
- if name is None:
2597
+ try :
2598
+ name = f"{n['pubkey'][0:2]} {mc.get_contact_by_key_prefix(n['pubkey'])['adv_name']}"
2599
+ except TypeError:
2537
2600
  name = n["pubkey"][0:16]
2538
- type = f"t:{n['node_type']}"
2539
- if n['node_type'] == 1:
2540
- type = "CLI"
2541
- elif n['node_type'] == 2:
2542
- type = "REP"
2543
- elif n['node_type'] == 3:
2544
- type = "ROOM"
2545
- elif n['node_type'] == 4:
2546
- type = "SENS"
2547
-
2548
- print(f" {name:16} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
2601
+ if n['node_type'] >= len(CONTACT_TYPENAMES):
2602
+ type = f"t:{n['node_type']}"
2603
+ else:
2604
+ type = CONTACT_TYPENAMES[n['node_type']]
2605
+
2606
+ print(f" {name:22} {type:>4} SNR: {n['SNR_in']:6,.2f}->{n['SNR']:6,.2f} RSSI: ->{n['RSSI']:4}")
2549
2607
 
2550
2608
  case "req_telemetry"|"rt" :
2551
2609
  argnum = 1
@@ -2561,7 +2619,7 @@ async def next_cmd(mc, cmds, json_output=False):
2561
2619
  else :
2562
2620
  print(json.dumps({
2563
2621
  "name": contact["adv_name"],
2564
- "pubkey_pre": contact["public_key"][0:12],
2622
+ "pubkey_pre": contact["public_key"][0:16],
2565
2623
  "lpp": res,
2566
2624
  }, indent = 4))
2567
2625
 
@@ -2695,7 +2753,13 @@ async def next_cmd(mc, cmds, json_output=False):
2695
2753
  print(json.dumps(res, indent=4))
2696
2754
  else :
2697
2755
  for c in res.items():
2698
- print(c[1]["adv_name"])
2756
+ if c[1]['out_path_len'] == -1:
2757
+ path_str = "Flood"
2758
+ elif c[1]['out_path_len'] == 0:
2759
+ path_str = "0 hop"
2760
+ else:
2761
+ path_str = f"{c[1]['out_path']}"
2762
+ print(f"{c[1]['adv_name']:30} {CONTACT_TYPENAMES[c[1]['type']]:4} {c[1]['public_key'][:12]}  {path_str}")
2699
2763
  print(f"> {len(mc.contacts)} contacts in device")
2700
2764
 
2701
2765
  case "reload_contacts" | "rc":
@@ -2763,7 +2827,7 @@ async def next_cmd(mc, cmds, json_output=False):
2763
2827
  if (path_len == 0) :
2764
2828
  print("0 hop")
2765
2829
  elif (path_len == -1) :
2766
- print("Path not set")
2830
+ print("Flood")
2767
2831
  else:
2768
2832
  print(path)
2769
2833
 
@@ -2790,6 +2854,8 @@ async def next_cmd(mc, cmds, json_output=False):
2790
2854
  print(f"Unknown contact {cmds[1]}")
2791
2855
  else:
2792
2856
  path = cmds[2].replace(",","") # we'll accept path with ,
2857
+ if path == "0":
2858
+ path = ""
2793
2859
  try:
2794
2860
  res = await mc.commands.change_contact_path(contact, path)
2795
2861
  logger.debug(res)
@@ -3213,7 +3279,7 @@ def get_help_for (cmdname, context="line") :
3213
3279
  - d, direct, similar to h>-1
3214
3280
  - f, flood, similar to h<0 or h=-1
3215
3281
 
3216
- Note: Some commands like contact_name (aka cn), reset_path (aka rp), forget_password (aka fp) can be chained. There is also a sleep command taking an optional event. The sleep will be issued after the command, it helps limiting rate through repeaters ...
3282
+ Note: Some commands like contact_name (aka cn), contact_key (aka ck), contact_type (aka ct), reset_path (aka rp), forget_password (aka fp) can be chained. There is also a sleep command taking an optional event. The sleep will be issued after the command, it helps limiting rate through repeaters ...
3217
3283
 
3218
3284
  Examples:
3219
3285
  # removes all clients that have not been updated in last 2 days
@@ -3252,7 +3318,8 @@ def get_help_for (cmdname, context="line") :
3252
3318
  print_new_contacts : display new pending contacts when available
3253
3319
  print_path_updates : display path updates as they come
3254
3320
  custom : all custom variables in json format
3255
- each custom var can also be get/set directly""")
3321
+ each custom var can also be get/set directly
3322
+ """)
3256
3323
 
3257
3324
  elif cmdname == "set" :
3258
3325
  print("""Available parameters :
@@ -3265,12 +3332,15 @@ def get_help_for (cmdname, context="line") :
3265
3332
  lat <lat> : latitude
3266
3333
  lon <lon> : longitude
3267
3334
  coords <lat,lon> : coordinates
3268
- auto_update_contacts <> : automatically updates contact list
3269
3335
  multi_ack <on/off> : multi-acks feature
3270
3336
  telemetry_mode_base <mode> : set basic telemetry mode all/selected/off
3271
3337
  telemetry_mode_loc <mode> : set location telemetry mode all/selected/off
3272
3338
  telemetry_mode_env <mode> : set env telemetry mode all/selected/off
3273
3339
  advert_loc_policy <policy> : "share" means loc will be shared in adv
3340
+ manual_add_contacts <on/off>: let user manually add contacts to device
3341
+ - when off device automatically adds contacts from adverts
3342
+ - when on contacts must be added manually using add_pending
3343
+ (pending contacts list is built by meshcli from adverts while connected)
3274
3344
  display:
3275
3345
  print_snr <on/off> : toggle snr display in messages
3276
3346
  print_adverts <on/off> : display adverts as they come
@@ -3280,12 +3350,14 @@ def get_help_for (cmdname, context="line") :
3280
3350
  channel_echoes <on/off> : print repeats for channel data
3281
3351
  echo_unk_channels <on/off> : also dump unk channels (encrypted)
3282
3352
  color <on/off> : color off should remove ANSI codes from output
3283
- prompt:
3353
+ meshcore-cli behaviour:
3284
3354
  classic_prompt <on/off> : activates less fancier prompt
3285
3355
  arrow_head <string> : change arrow head in prompt
3286
3356
  slash_start <string> : idem for slash start
3287
3357
  slash_end <string> : slash end
3288
- invert_slash <on/off> : apply color inversion to slash """)
3358
+ invert_slash <on/off> : apply color inversion to slash
3359
+ auto_update_contacts <on/of>: auto sync contact list with device
3360
+ """)
3289
3361
 
3290
3362
  elif cmdname == "scope":
3291
3363
  print("""scope <scope> : changes flood scope of the node
@@ -3296,7 +3368,31 @@ Managing Flood Scope in interactive mode
3296
3368
  Flood scope has recently been introduced in meshcore (from v1.10.0). It limits the scope of packets to regions, using transport codes in the frame.
3297
3369
  When entering chat mode, scope will be reset to *, meaning classic flood.
3298
3370
  You can switch scope using the scope command, or postfixing the to command with %<scope>.
3299
- Scope can also be applied to a command using % before the scope name. For instance login%#Morbihan will limit diffusion of the login command (which is usually sent flood to get the path to a repeater) to the #Morbihan region.""")
3371
+ Scope can also be applied to a command using % before the scope name. For instance login%#Morbihan will limit diffusion of the login command (which is usually sent flood to get the path to a repeater) to the #Morbihan region.
3372
+ """)
3373
+
3374
+ elif cmdname == "contact_info":
3375
+ print("""contact_info <ct> : displays contact info
3376
+
3377
+ in interactive mode, there are some lighter commands that can be chained to give more compact information
3378
+ - contact_name (cn)
3379
+ - contact_key (ck)
3380
+ - contact_type (ct)
3381
+ """)
3382
+
3383
+ elif cmdname == "pending_contacts" or cmdname == "flush_pending" or cmdname == "add_pending":
3384
+ print("""Contact management
3385
+
3386
+ To receive a message from another user, it is necessary to have its public key. This key is stored on a contact list in the device, and this list has a finite size (50 when meshcore started, now over 350 for most devices).
3387
+
3388
+ By default contacts are automatically added to the device contact list when an advertisement is received, so as soon as you receive an advert, you can talk with your buddy.
3389
+
3390
+ With growing number of users, it becomes necessary to manage contact list and one of the ways is to add contacts manually to the device. This is done by turning on manual_add_contacts. Once this option has been turned on, a pending list is built by meshcore-cli from the received adverts. You can view the list issuing a pending_contacts command, flush the list using flush_pending or add a contact from the list with add_pending followed by the key of the contact or its name (both will be auto-completed with tab).
3391
+
3392
+ This feature only really works in interactive mode.
3393
+
3394
+ Note: There is also an auto_update_contacts setting that has nothing to do with adding contacts, it permits to automatically sync contact lists between device and meshcore-cli (when there is an update in name, location or path).
3395
+ """)
3300
3396
 
3301
3397
  else:
3302
3398
  print(f"Sorry, no help yet for {cmdname}")
File without changes
File without changes
File without changes
File without changes