meshcore-cli/mccli.py

284 lines
11 KiB
Python
Raw Normal View History

2025-02-02 17:51:09 +01:00
#!/usr/bin/python
2025-02-04 15:03:44 +01:00
"""
mccli.py : CLI interface to MeschCore BLE companion app
"""
2025-02-02 17:51:09 +01:00
import asyncio
import serial_asyncio
2025-02-04 15:03:44 +01:00
import os
2025-02-02 17:51:09 +01:00
import sys
2025-02-04 15:03:44 +01:00
import getopt
2025-02-02 17:51:09 +01:00
import json
2025-02-04 09:53:07 +01:00
import datetime
2025-02-04 15:03:44 +01:00
import time
from pathlib import Path
2025-02-02 17:51:09 +01:00
2025-03-23 07:40:31 +01:00
from mclib import TCPConnection
from mclib import BLEConnection
from mclib import SerialConnection
2025-03-23 08:05:11 +01:00
from mclib import printerr
from mclib import MeshCore
2025-02-02 17:51:09 +01:00
2025-02-04 15:03:44 +01:00
# default address is stored in a config file
MCCLI_CONFIG_DIR = str(Path.home()) + "/.config/mc-cli/"
MCCLI_ADDRESS = MCCLI_CONFIG_DIR + "default_address"
# Fallback address if config file not found
# if None or "" then a scan is performed
ADDRESS = ""
2025-02-02 17:51:09 +01:00
2025-02-04 10:05:37 +01:00
async def next_cmd(mc, cmds):
2025-02-04 15:03:44 +01:00
""" process next command """
2025-02-04 10:05:37 +01:00
argnum = 0
match cmds[0] :
2025-03-20 17:59:03 +01:00
case "q":
print(await mc.send_device_qeury())
2025-03-18 21:17:30 +01:00
case "get_time" | "clock" :
if len(cmds) > 1 and cmds[1] == "sync" :
argnum=1
print(await mc.set_time(int(time.time())))
else:
timestamp = await mc.get_time()
print('Current time :'
f' {datetime.datetime.fromtimestamp(timestamp).strftime("%Y-%m-%d %H:%M:%S")}'
f' ({timestamp})')
2025-03-20 17:59:03 +01:00
case "sync_time"|"clock sync"|"st":
2025-02-04 09:53:07 +01:00
print(await mc.set_time(int(time.time())))
case "set_time" :
2025-02-04 10:05:37 +01:00
argnum = 1
print(await mc.set_time(cmds[1]))
2025-03-06 22:42:12 +01:00
case "set_txpower"|"txp" :
argnum = 1
print(await mc.set_tx_power(cmds[1]))
case "set_radio"|"rad" :
argnum = 4
print(await mc.set_radio(cmds[1], cmds[2], cmds[3], cmds[4]))
2025-03-18 21:17:30 +01:00
case "set_name" :
argnum = 1
print(await mc.set_name(cmds[1]))
case "set":
argnum = 2
match cmds[1]:
2025-03-20 21:52:48 +01:00
case "pin":
print (await mc.set_devicepin(cmds[2]))
2025-03-18 21:17:30 +01:00
case "radio":
params=cmds[2].split(",")
print (await mc.set_radio(params[0], params[1], params[2], params[3]))
case "name":
print (await mc.set_name(cmds[2]))
case "tx":
print (await mc.set_tx_power(cmds[2]))
2025-03-18 22:10:41 +01:00
case "lat":
print (await mc.set_coords(\
float(cmds[2]),\
mc.self_infos['adv_lon']))
case "lon":
print (await mc.set_coords(\
mc.self_infos['adv_lat'],\
float(cmds[2])))
case "coords":
params=cmds[2].split(",")
print (await mc.set_coords(\
float(params[0]),\
float(params[1])))
2025-03-06 22:42:12 +01:00
case "set_tuning"|"tun" :
argnum = 2
print(await mc.set_tuning(cmds[1], cmds[2]))
2025-03-05 10:55:44 +01:00
case "get_bat" | "b":
2025-03-03 20:35:20 +01:00
print(await mc.get_bat())
case "reboot" :
print(await mc.reboot())
2025-02-04 13:02:00 +01:00
case "send" :
2025-02-04 10:05:37 +01:00
argnum = 2
print(await mc.send_msg(bytes.fromhex(cmds[1]), cmds[2]))
2025-03-06 16:37:43 +01:00
case "msg" | "sendto" | "m" | "{" : # sends to a contact from name
2025-02-04 10:05:37 +01:00
argnum = 2
2025-03-03 16:57:16 +01:00
await mc.ensure_contacts()
2025-02-04 15:03:44 +01:00
print(await mc.send_msg(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])[0:6],
cmds[2]))
2025-03-13 20:32:49 +01:00
case "chan_msg"|"ch" :
argnum = 2
print(await mc.send_chan_msg(cmds[1], cmds[2]))
case "def_chan_msg"|"def_chan"|"dch" : # default chan
argnum = 1
print(await mc.send_chan_msg(0, cmds[1]))
2025-03-05 20:09:57 +01:00
case "cmd" | "c" | "[" :
2025-02-28 13:36:35 +01:00
argnum = 2
2025-03-03 16:57:16 +01:00
await mc.ensure_contacts()
2025-02-28 13:36:35 +01:00
print(await mc.send_cmd(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])[0:6],
cmds[2]))
2025-03-06 16:37:43 +01:00
case "login" | "l" | "[[" :
2025-02-28 13:36:35 +01:00
argnum = 2
2025-03-03 16:57:16 +01:00
await mc.ensure_contacts()
2025-02-28 13:36:35 +01:00
print(await mc.send_login(bytes.fromhex(mc.contacts[cmds[1]]["public_key"]),
cmds[2]))
2025-03-06 16:37:43 +01:00
case "wait_login" | "wl" | "]]":
2025-03-04 21:54:07 +01:00
print(await mc.wait_login())
2025-03-05 10:55:44 +01:00
case "req_status" | "rs" :
2025-02-28 13:36:35 +01:00
argnum = 1
2025-03-03 16:57:16 +01:00
await mc.ensure_contacts()
2025-02-28 13:36:35 +01:00
print(await mc.send_statusreq(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
2025-03-05 10:55:44 +01:00
case "wait_status" | "ws" :
2025-03-04 21:54:07 +01:00
print(await mc.wait_status())
2025-03-05 10:55:44 +01:00
case "contacts" | "lc":
2025-02-02 21:58:59 +01:00
print(json.dumps(await mc.get_contacts(),indent=4))
2025-03-05 10:55:44 +01:00
case "change_path" | "cp":
2025-03-04 17:11:16 +01:00
argnum = 2
await mc.ensure_contacts()
await mc.set_out_path(mc.contacts[cmds[1]], cmds[2])
print(await mc.update_contact(mc.contacts[cmds[1]]))
2025-03-05 10:55:44 +01:00
case "reset_path" | "rp" :
2025-03-04 08:59:22 +01:00
argnum = 1
await mc.ensure_contacts()
print(await mc.reset_path(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
await mc.get_contacts()
2025-03-05 10:55:44 +01:00
case "share_contact" | "sc":
argnum = 1
await mc.ensure_contacts()
print(await mc.share_contact(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
2025-03-08 11:08:31 +01:00
case "export_contact"|"ec":
2025-03-08 11:07:26 +01:00
argnum = 1
await mc.ensure_contacts()
print(await mc.export_contact(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
2025-03-08 11:08:31 +01:00
case "export_myself"|"e":
2025-03-08 11:07:26 +01:00
print(await mc.export_contact())
2025-03-05 11:03:36 +01:00
case "remove_contact" :
argnum = 1
await mc.ensure_contacts()
print(await mc.remove_contact(bytes.fromhex(mc.contacts[cmds[1]]["public_key"])))
2025-03-05 10:55:44 +01:00
case "recv" | "r" :
2025-02-02 17:51:09 +01:00
print(await mc.get_msg())
2025-03-05 10:55:44 +01:00
case "sync_msgs" | "sm":
res=True
while res:
res = await mc.get_msg()
print (res)
2025-03-06 16:37:43 +01:00
case "wait_msg" | "wm" :
2025-02-10 09:55:56 +01:00
await mc.wait_msg()
res = await mc.get_msg()
print (res)
2025-03-06 16:37:43 +01:00
case "trywait_msg" | "wmt" :
argnum = 1
if await mc.wait_msg(timeout=int(cmds[1])) :
print (await mc.get_msg())
case "wmt8"|"]":
if await mc.wait_msg(timeout=8) :
print (await mc.get_msg())
case "wait_ack" | "wa" | "}":
2025-03-04 18:33:58 +01:00
await mc.wait_ack()
2025-03-05 10:55:44 +01:00
case "infos" | "i" :
2025-02-14 13:20:16 +01:00
print(json.dumps(mc.self_info,indent=4))
2025-03-05 10:55:44 +01:00
case "advert" | "a":
2025-02-02 18:12:26 +01:00
print(await mc.send_advert())
2025-03-05 10:55:44 +01:00
case "sleep" | "s" :
2025-02-04 10:05:37 +01:00
argnum = 1
await asyncio.sleep(int(cmds[1]))
2025-02-14 13:19:01 +01:00
printerr (f"cmd {cmds[0:argnum+1]} processed ...")
2025-02-04 10:05:37 +01:00
return cmds[argnum+1:]
2025-02-04 13:02:00 +01:00
def usage () :
2025-02-04 15:03:44 +01:00
""" Prints some help """
2025-02-04 15:16:08 +01:00
print("""mccli.py : CLI interface to MeschCore BLE companion app
2025-02-04 13:02:00 +01:00
2025-02-04 15:16:08 +01:00
Usage : mccli.py <args> <commands>
2025-02-04 13:02:00 +01:00
Arguments :
-h : prints this help
2025-02-26 09:47:13 +01:00
-a <address> : specifies device address (can be a name)
-d <name> : filter meshcore devices with name or address
2025-03-03 10:16:32 +01:00
-t <hostname> : connects via tcp/ip
-p <port> : specifies tcp port (default 5000)
-s <port> : use serial port <port>
2025-03-04 10:53:49 +01:00
-b <baudrate> : specify baudrate
2025-02-04 13:02:00 +01:00
2025-03-05 11:03:36 +01:00
Available Commands and shorcuts (can be chained) :
2025-03-05 11:24:41 +01:00
infos : print informations about the node i
reboot : reboots node
send <key> <msg> : sends msg to node using pubkey[0:6]
sendto <name> <msg> : sends msg to node with given name
msg <name> <msg> : same as sendto m
wait_ack : wait an ack for last sent msg wa
recv : reads next msg r
sync_msgs : gets all unread msgs from the node sm
wait_msg : wait for a message and read it wm
advert : sends advert a
contacts : gets contact list lc
share_contact <ct> : share a contact with others sc
remove_contact <ct> : removes a contact from this node
reset_path <ct> : resets path to a contact to flood rp
change_path <ct> <path>: change the path to a contact cp
get_time : gets current time
set_time <epoch> : sets time to given epoch
sync_time : sync time with system
set_name <name> : sets node name
get_bat : gets battery level b
login <name> <pwd> : log into a node (rep) with given pwd l
wait_login : wait for login (timeouts after 5sec) wl
cmd <name> <cmd> : sends a command to a repeater (no ack) c
req_status <name> : requests status from a node rs
wait_status : wait and print reply ws
sleep <secs> : sleeps for a given amount of secs s""")
2025-03-04 21:54:07 +01:00
async def main(argv):
""" Do the job """
address = ADDRESS
2025-03-03 10:16:32 +01:00
port = 5000
hostname = None
serial_port = None
baudrate = 115200
2025-02-04 15:03:44 +01:00
# If there is an address in config file, use it by default
# unless an arg is explicitely given
if os.path.exists(MCCLI_ADDRESS) :
with open(MCCLI_ADDRESS, encoding="utf-8") as f :
address = f.readline().strip()
2025-03-04 10:53:49 +01:00
opts, args = getopt.getopt(argv, "a:d:s:ht:p:b:")
for opt, arg in opts :
match opt:
2025-02-26 09:47:13 +01:00
case "-d" : # name specified on cmdline
address = arg
case "-a" : # address specified on cmdline
address = arg
case "-s" : # serial port
serial_port = arg
2025-03-04 10:53:49 +01:00
case "-b" :
baudrate = int(arg)
2025-03-03 10:16:32 +01:00
case "-t" :
hostname = arg
case "-p" :
2025-03-04 10:53:49 +01:00
port = int(arg)
2025-02-04 10:05:37 +01:00
2025-02-04 15:03:44 +01:00
if len(args) == 0 : # no args, no action
2025-02-04 13:02:00 +01:00
usage()
2025-02-04 10:05:37 +01:00
return
2025-03-03 10:16:32 +01:00
con = None
if not hostname is None : # connect via tcp
con = TCPConnection(hostname, port)
await con.connect()
elif not serial_port is None : # connect via serial port
con = SerialConnection(serial_port, baudrate)
await con.connect()
await asyncio.sleep(0.1)
else : #connect via ble
2025-03-03 10:16:32 +01:00
con = BLEConnection(address)
address = await con.connect()
if address is None or address == "" : # no device, no action
printerr ("No device found, exiting ...")
return
# Store device address in configuration
if os.path.isdir(MCCLI_CONFIG_DIR) :
with open(MCCLI_ADDRESS, "w", encoding="utf-8") as f :
f.write(address)
mc = MeshCore(con)
await mc.connect()
2025-02-04 10:05:37 +01:00
cmds = args
while len(cmds) > 0 :
2025-02-04 10:05:37 +01:00
cmds = await next_cmd(mc, cmds)
2025-02-02 17:51:09 +01:00
2025-02-04 15:03:44 +01:00
asyncio.run(main(sys.argv[1:]))