meshcore-cli 1.3.7__py3-none-any.whl → 1.3.8__py3-none-any.whl

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.
@@ -32,7 +32,7 @@ import re
32
32
  from meshcore import MeshCore, EventType, logger
33
33
 
34
34
  # Version
35
- VERSION = "v1.3.7"
35
+ VERSION = "v1.3.8"
36
36
 
37
37
  # default ble address is stored in a config file
38
38
  MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/meshcore/"
@@ -130,10 +130,23 @@ async def process_event_message(mc, ev, json_output, end="\n", above=False):
130
130
  await mc.ensure_contacts()
131
131
  data = ev.payload
132
132
 
133
+ path_str = ""
134
+
135
+ if process_event_message.timestamp != "" and process_event_message.timestamp != "off":
136
+ ts = data["sender_timestamp"]
137
+ if process_event_message.timestamp == "on":
138
+ if (abs(time.time()-ts) < 86400):
139
+ fmt = "%H:%S"
140
+ else:
141
+ fmt = "%y-%m-%d %H:%S"
142
+ else:
143
+ fmt = process_event_message.timestamp
144
+ path_str += f'{datetime.datetime.fromtimestamp(ts).strftime(fmt)},'
145
+
133
146
  if data['path_len'] == 255 :
134
- path_str = "D"
147
+ path_str += "D"
135
148
  else :
136
- path_str = f"{data['path_len']}"
149
+ path_str += f"{data['path_len']}"
137
150
  if "SNR" in data and process_event_message.print_snr:
138
151
  path_str = path_str + f",{data['SNR']}dB"
139
152
 
@@ -206,6 +219,7 @@ async def process_event_message(mc, ev, json_output, end="\n", above=False):
206
219
  process_event_message.print_snr=False
207
220
  process_event_message.color=True
208
221
  process_event_message.last_node=None
222
+ process_event_message.timestamp=""
209
223
 
210
224
  async def handle_log_rx(event):
211
225
  mc = handle_log_rx.mc
@@ -292,6 +306,57 @@ async def handle_log_rx(event):
292
306
  else:
293
307
  print(txt)
294
308
 
309
+ elif payload_type == 0x04: # Advert
310
+ if handle_log_rx.advert_echoes:
311
+ pk_buf = io.BytesIO(pkt_payload)
312
+ adv_key = pk_buf.read(32).hex()
313
+ adv_timestamp = int.from_bytes(pk_buf.read(4), "little", signed=False)
314
+ signature = pk_buf.read(64).hex()
315
+ flags = pk_buf.read(1)[0]
316
+ adv_type = flags & 0x0F
317
+ adv_lat = None
318
+ adv_lon = None
319
+ if flags & 0x10 > 0: #has location
320
+ adv_lat = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
321
+ adv_lon = int.from_bytes(pk_buf.read(4), "little", signed=True)/1000000.0
322
+ if flags & 0x20 > 0: #has feature1
323
+ adv_feat1 = pk_buf.read(2).hex()
324
+ if flags & 0x40 > 0: #has feature2
325
+ adv_feat2 = pk_buf.read(2).hex()
326
+ if flags & 0x80 > 0: #has name
327
+ adv_name = pk_buf.read().decode("utf-8").strip("\x00")
328
+
329
+ if adv_name is None:
330
+ # try to get the name from the contact
331
+ ct = handle_log_rx.mc.get_contact_by_key_prefix(adv_key)
332
+ if ct is None:
333
+ adv_name = adv_key[0:12]
334
+ else:
335
+ adv_name = ct["adv_name"]
336
+
337
+ ts_string = ""
338
+ if process_event_message.timestamp != "" and process_event_message.timestamp != "off":
339
+ ts = adv_timestamp
340
+ if process_event_message.timestamp == "on":
341
+ if (abs(time.time()-ts) < 86400):
342
+ fmt = "%H:%S"
343
+ else:
344
+ fmt = "%y-%m-%d %H:%S"
345
+ else:
346
+ fmt = process_event_message.timestamp
347
+ ts_str = f' at {datetime.datetime.fromtimestamp(ts).strftime(fmt)}'
348
+
349
+ txt = f"{ANSI_LIGHT_GRAY}Advert for{ANSI_END} {adv_name}{ANSI_GREEN}/{CONTACT_TYPENAMES[adv_type]}{ts_str}{ANSI_END}"
350
+ if not adv_lat is None:
351
+ txt += f" {ANSI_LIGHT_GRAY}coords: {adv_lat},{adv_lon}"
352
+ txt += f" {ANSI_YELLOW}path: [{path}] {ANSI_LIGHT_GRAY}snr: {event.payload['snr']:.2f}dB{ANSI_END}"
353
+
354
+ if handle_message.above:
355
+ print_above(txt)
356
+ else:
357
+ print(txt)
358
+
359
+
295
360
  if handle_log_rx.json_log_rx: # json mode ... raw dump
296
361
  msg = json.dumps(event.payload)
297
362
  if handle_message.above:
@@ -302,6 +367,7 @@ async def handle_log_rx(event):
302
367
 
303
368
  handle_log_rx.json_log_rx = False
304
369
  handle_log_rx.channel_echoes = False
370
+ handle_log_rx.advert_echoes = False
305
371
  handle_log_rx.mc = None
306
372
  handle_log_rx.echo_unk_chans=False
307
373
 
@@ -518,6 +584,7 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
518
584
  "self_telemetry" : None,
519
585
  "get_channel": None,
520
586
  "set_channel": None,
587
+ "add_channel": None,
521
588
  "get_channels": None,
522
589
  "remove_channel": None,
523
590
  "apply_to": None,
@@ -533,11 +600,13 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
533
600
  "lon" : None,
534
601
  "coords" : None,
535
602
  "print_snr" : {"on":None, "off": None},
603
+ "print_timestamp" : {"on":None, "off": None, "%Y:%M":None},
536
604
  "json_msgs" : {"on":None, "off": None},
537
605
  "color" : {"on":None, "off":None},
538
606
  "print_adverts" : {"on":None, "off":None},
539
607
  "json_log_rx" : {"on":None, "off":None},
540
608
  "channel_echoes" : {"on":None, "off":None},
609
+ "advert_echoes" : {"on":None, "off":None},
541
610
  "echo_unk_chans" : {"on":None, "off":None},
542
611
  "print_new_contacts" : {"on": None, "off":None},
543
612
  "print_path_updates" : {"on":None,"off":None},
@@ -562,11 +631,13 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
562
631
  "lat":None,
563
632
  "lon":None,
564
633
  "print_snr":None,
634
+ "print_timestamp":None,
565
635
  "json_msgs":None,
566
636
  "color":None,
567
637
  "print_adverts":None,
568
638
  "json_log_rx":None,
569
639
  "channel_echoes":None,
640
+ "advert_echoes":None,
570
641
  "echo_unk_chans":None,
571
642
  "print_path_updates":None,
572
643
  "print_new_contacts":None,
@@ -594,6 +665,12 @@ def make_completion_dict(contacts, pending={}, to=None, channels=None):
594
665
  "?pending_contacts":None,
595
666
  "?add_pending":None,
596
667
  "?flush_pending":None,
668
+ "?get_channels":None,
669
+ "?set_channel":None,
670
+ "?get_channel":None,
671
+ "?set_channel":None,
672
+ "?add_channel":None,
673
+ "?remove_channel":None,
597
674
  }
598
675
 
599
676
  contact_completion_list = {
@@ -1556,13 +1633,18 @@ async def get_channel (mc, chan) :
1556
1633
 
1557
1634
  async def set_channel (mc, chan, name, key=None):
1558
1635
 
1559
- if chan.isnumeric():
1560
- nb = int(chan)
1636
+ if isinstance(chan, str):
1637
+ if chan.isnumeric():
1638
+ nb = int(chan)
1639
+ else:
1640
+ c = await get_channel_by_name(mc, chan)
1641
+ if c is None:
1642
+ return None
1643
+ nb = c['channel_idx']
1644
+ elif isinstance(chan, int):
1645
+ nb = chan
1561
1646
  else:
1562
- c = await get_channel_by_name(mc, chan)
1563
- if c is None:
1564
- return None
1565
- nb = c['channel_idx']
1647
+ return None
1566
1648
 
1567
1649
  res = await mc.commands.set_channel(nb, name, key)
1568
1650
 
@@ -1874,6 +1956,10 @@ async def next_cmd(mc, cmds, json_output=False):
1874
1956
  process_event_message.color = (cmds[2] == "on")
1875
1957
  if json_output :
1876
1958
  print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
1959
+ case "print_timestamp" :
1960
+ process_event_message.timestamp = cmds[2]
1961
+ if json_output :
1962
+ print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
1877
1963
  case "print_snr" :
1878
1964
  process_event_message.print_snr = (cmds[2] == "on")
1879
1965
  if json_output :
@@ -1886,6 +1972,10 @@ async def next_cmd(mc, cmds, json_output=False):
1886
1972
  handle_log_rx.channel_echoes = (cmds[2] == "on")
1887
1973
  if json_output :
1888
1974
  print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
1975
+ case "advert_echoes" :
1976
+ handle_log_rx.advert_echoes = (cmds[2] == "on")
1977
+ if json_output :
1978
+ print(json.dumps({"cmd" : cmds[1], "param" : cmds[2]}))
1889
1979
  case "echo_unk_chans" :
1890
1980
  handle_log_rx.echo_unk_chans = (cmds[2] == "on")
1891
1981
  if json_output :
@@ -2101,6 +2191,11 @@ async def next_cmd(mc, cmds, json_output=False):
2101
2191
  print(json.dumps({"color" : process_event_message.color}))
2102
2192
  else:
2103
2193
  print(f"{'on' if process_event_message.color else 'off'}")
2194
+ case "print_timestamp":
2195
+ if json_output :
2196
+ print(json.dumps({"timestamp" : process_event_message.timestamp}))
2197
+ else:
2198
+ print(f"{process_event_message.timestamp}")
2104
2199
  case "json_log_rx":
2105
2200
  if json_output :
2106
2201
  print(json.dumps({"json_log_rx" : handle_log_rx.json_log_rx}))
@@ -2111,6 +2206,11 @@ async def next_cmd(mc, cmds, json_output=False):
2111
2206
  print(json.dumps({"channel_echoes" : handle_log_rx.channel_echoes}))
2112
2207
  else:
2113
2208
  print(f"{'on' if handle_log_rx.channel_echoes else 'off'}")
2209
+ case "advert_echoes":
2210
+ if json_output :
2211
+ print(json.dumps({"advert_echoes" : handle_log_rx.channel_echoes}))
2212
+ else:
2213
+ print(f"{'on' if handle_log_rx.advert_echoes else 'off'}")
2114
2214
  case "echo_unk_chans":
2115
2215
  if json_output :
2116
2216
  print(json.dumps({"echo_unk_chans" : handle_log_rx.echo_unk_chans}))
@@ -2313,11 +2413,17 @@ async def next_cmd(mc, cmds, json_output=False):
2313
2413
  if res is None:
2314
2414
  print("Error setting channel")
2315
2415
 
2316
- case "scope":
2317
- argnum = 1
2318
- res = await set_scope(mc, cmds[1])
2416
+ case "add_channel":
2417
+ argnum = 2
2418
+ if cmds[1].startswith("#") or len(cmds) == 2:
2419
+ argnum = 1
2420
+ res = await set_channel(mc, "", cmds[1])
2421
+ elif len(cmds[2]) != 32:
2422
+ res = None
2423
+ else:
2424
+ res = await set_channel(mc, "", cmds[1], bytes.fromhex(cmds[3]))
2319
2425
  if res is None:
2320
- print(f"Error while setting scope")
2426
+ print("Error adding channel")
2321
2427
 
2322
2428
  case "remove_channel":
2323
2429
  argnum = 1
@@ -2325,6 +2431,12 @@ async def next_cmd(mc, cmds, json_output=False):
2325
2431
  if res is None:
2326
2432
  print("Error deleting channel")
2327
2433
 
2434
+ case "scope":
2435
+ argnum = 1
2436
+ res = await set_scope(mc, cmds[1])
2437
+ if res is None:
2438
+ print(f"Error while setting scope")
2439
+
2328
2440
  case "reboot" :
2329
2441
  res = await mc.commands.reboot()
2330
2442
  logger.debug(res)
@@ -3202,7 +3314,8 @@ def command_help():
3202
3314
  msgs_subscribe : display msgs as they arrive ms
3203
3315
  get_channels : prints all channel info
3204
3316
  get_channel <n> : get info for channel (by number or name)
3205
- set_channel n nm k : set channel info (nb, name, key)
3317
+ set_channel n nm [k] : set channel info (nb, name, key)
3318
+ add_channel name [key] : add new channel with optional key
3206
3319
  remove_channel <n> : remove channel (by number or name)
3207
3320
  scope <s> : sets scope for flood messages
3208
3321
  Management
@@ -3347,12 +3460,14 @@ def get_help_for (cmdname, context="line") :
3347
3460
  - when on contacts must be added manually using add_pending
3348
3461
  (pending contacts list is built by meshcli from adverts while connected)
3349
3462
  display:
3463
+ print_timestamp <on/off/fmt>: toggle printing of timestamp, can be strftime format
3350
3464
  print_snr <on/off> : toggle snr display in messages
3351
3465
  print_adverts <on/off> : display adverts as they come
3352
3466
  print_new_contacts <on/off> : display new pending contacts when available
3353
3467
  print_path_updates <on/off> : display path updates as they come
3354
3468
  json_log_rx <on/off> : logs packets incoming to device as json
3355
3469
  channel_echoes <on/off> : print repeats for channel data
3470
+ advert_echoes <on/off> : print repeats for adverts
3356
3471
  echo_unk_channels <on/off> : also dump unk channels (encrypted)
3357
3472
  color <on/off> : color off should remove ANSI codes from output
3358
3473
  meshcore-cli behaviour:
@@ -3397,6 +3512,30 @@ With growing number of users, it becomes necessary to manage contact list and on
3397
3512
  This feature only really works in interactive mode.
3398
3513
 
3399
3514
  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).
3515
+ """)
3516
+
3517
+ elif "channel" in cmdname:
3518
+ print("""Channel management
3519
+
3520
+ Channels are used to send messages to a group of people. This group of people share a common key, used to encrypt, identify and decrypt the messages that are sent flood over the network (possibly with a scope).
3521
+
3522
+ Channel commands are the following:
3523
+ - get_channels
3524
+ - get_channel chan
3525
+ - add_channel name [key]
3526
+ - set_channel chan name [key]
3527
+ - remove_channel chan
3528
+
3529
+ There is a fixed number of slots on companions to store channel messages, each channel has a number, a name and a key, the get_channels command lists theses slots.
3530
+
3531
+ You can also call get_channel (with number or name) to get information about one channel.
3532
+
3533
+ Adding a channel can be done using the set_channel command, taking as parameters the channel number, the name and the key. Key is optional, if not provided, it will be computed from the name.
3534
+ The add_channel command won't take a number as it will use first available slot.
3535
+
3536
+ There is a special case for auto channels, which starts with a #, these have always their key computed from the name (note that mccli does not lowercase and strip characters so you should be carefull when sharing when users of the android app or ripple).
3537
+
3538
+ To remove a channel, use remove_channel, either with channel name or number.
3400
3539
  """)
3401
3540
 
3402
3541
  else:
@@ -3537,7 +3676,14 @@ async def main(argv):
3537
3676
  logger.info(f"Searching first MC BLE device")
3538
3677
  else:
3539
3678
  logger.info(f"Scanning BLE for device matching {address}")
3540
- devices = await BleakScanner.discover(timeout=timeout)
3679
+ try:
3680
+ devices = await BleakScanner.discover(timeout=timeout)
3681
+ except (BleakError, BleakDBusError):
3682
+ print("BLE connection asked (default behaviour), but no BLE HW found")
3683
+ print("Call meshcore-cli with -h for some more help (on commands)")
3684
+ command_usage()
3685
+ return
3686
+
3541
3687
  found = False
3542
3688
  for d in devices:
3543
3689
  if not d.name is None and d.name.startswith("MeshCore-") and\
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcore-cli
3
- Version: 1.3.7
3
+ Version: 1.3.8
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
@@ -0,0 +1,8 @@
1
+ meshcore_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ meshcore_cli/__main__.py,sha256=PfYgibmu2LEtC-OV7L1UgmvV3swJ5rQ4bbXHlwUFlgE,83
3
+ meshcore_cli/meshcore_cli.py,sha256=lBuixjTPVJm6ijXuB8RojArSjSDu6k8xCTOtpPAAgs4,158679
4
+ meshcore_cli-1.3.8.dist-info/METADATA,sha256=PTtqAGUv5-37BMScJY5ErRy4gLFs9jIoBNbxnzSvgME,17137
5
+ meshcore_cli-1.3.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
6
+ meshcore_cli-1.3.8.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
7
+ meshcore_cli-1.3.8.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
8
+ meshcore_cli-1.3.8.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.27.0
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,8 +0,0 @@
1
- meshcore_cli/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- meshcore_cli/__main__.py,sha256=PfYgibmu2LEtC-OV7L1UgmvV3swJ5rQ4bbXHlwUFlgE,83
3
- meshcore_cli/meshcore_cli.py,sha256=iRrICDi9pCAngyuY6_Jc6teubgi4gomJ8l_NHXYNFrE,151869
4
- meshcore_cli-1.3.7.dist-info/METADATA,sha256=0o6ju0KlttNIOFoy3_0-rUEIlVwSymgsHDLkkr-So5w,17137
5
- meshcore_cli-1.3.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
6
- meshcore_cli-1.3.7.dist-info/entry_points.txt,sha256=77V29Pyth11GteDk7tneBN3MMk8JI7bTlS-BGSmxCmI,103
7
- meshcore_cli-1.3.7.dist-info/licenses/LICENSE,sha256=F9s987VtS0AKxW7LdB2EkLMkrdeERI7ICdLJR60A9M4,1066
8
- meshcore_cli-1.3.7.dist-info/RECORD,,