mirror of
https://github.com/espressif/openthread.git
synced 2026-07-08 13:20:25 +00:00
1975 lines
53 KiB
Python
Executable File
1975 lines
53 KiB
Python
Executable File
#!/usr/bin/python
|
|
#
|
|
# Copyright (c) 2016, The OpenThread Authors.
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions are met:
|
|
# 1. Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# 2. Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in the
|
|
# documentation and/or other materials provided with the distribution.
|
|
# 3. Neither the name of the copyright holder nor the
|
|
# names of its contributors may be used to endorse or promote products
|
|
# derived from this software without specific prior written permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
|
|
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
#
|
|
"""
|
|
spinel-cli.py
|
|
|
|
available commands (type help <name> for more information):
|
|
============================================================
|
|
channel diag-sleep leaderdata releaserouterid
|
|
child diag-start leaderweight rloc16
|
|
childmax diag-stats masterkey route
|
|
childtimeout diag-stop mode router
|
|
clear discover ncp-ll64 routerupgradethreshold
|
|
commissioner eidcache ncp-ml64 scan
|
|
contextreusedelay exit ncp-tun state
|
|
counter extaddr netdataregister thread
|
|
debug extpanid networkidtimeout tun
|
|
debug-mem h networkname v
|
|
diag help panid version
|
|
diag-channel history ping whitelist
|
|
diag-power ifconfig prefix
|
|
diag-repeat ipaddr q
|
|
diag-send keysequence quit
|
|
"""
|
|
|
|
from __future__ import print_function
|
|
|
|
import os
|
|
import sys
|
|
import time
|
|
import traceback
|
|
|
|
import optparse
|
|
|
|
import struct
|
|
import string
|
|
import textwrap
|
|
|
|
import logging
|
|
import logging.config
|
|
import logging.handlers
|
|
|
|
from cmd import Cmd
|
|
|
|
from spinel.const import SPINEL
|
|
from spinel.const import kThread
|
|
from spinel.codec import WpanApi
|
|
from spinel.codec import SpinelCodec
|
|
from spinel.stream import StreamOpen
|
|
from spinel.tun import TunInterface
|
|
import spinel.config as CONFIG
|
|
import spinel.util as util
|
|
|
|
import ipaddress
|
|
|
|
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
|
|
from scapy.layers.inet6 import IPv6
|
|
from scapy.layers.inet6 import ICMPv6EchoReply
|
|
from scapy.layers.inet6 import ICMPv6EchoRequest
|
|
|
|
|
|
__copyright__ = "Copyright (c) 2016 The OpenThread Authors."
|
|
__version__ = "0.1.0"
|
|
|
|
MASTER_PROMPT = "spinel-cli"
|
|
|
|
|
|
class SpinelCliCmd(Cmd, SpinelCodec):
|
|
"""
|
|
A command line shell for controlling OpenThread NCP nodes
|
|
via the Spinel protocol.
|
|
"""
|
|
|
|
def __init__(self, stream_desc, nodeid, *_a, **kw):
|
|
|
|
self.nodeid = kw.get('nodeid', '1')
|
|
self.tun_if = None
|
|
|
|
self.wpan_api = WpanApi(stream_desc, nodeid)
|
|
self.wpan_api.queue_register(SPINEL.HEADER_DEFAULT)
|
|
self.wpan_api.callback_register(SPINEL.PROP_STREAM_NET,
|
|
self.wpan_callback)
|
|
|
|
Cmd.__init__(self)
|
|
Cmd.identchars = string.ascii_letters + string.digits + '-'
|
|
|
|
if sys.stdin.isatty():
|
|
self.prompt = MASTER_PROMPT + " > "
|
|
else:
|
|
self.use_rawinput = 0
|
|
self.prompt = ""
|
|
|
|
SpinelCliCmd.command_names.sort()
|
|
|
|
self.history_filename = os.path.expanduser("~/.spinel-cli-history")
|
|
|
|
try:
|
|
import readline
|
|
try:
|
|
readline.read_history_file(self.history_filename)
|
|
except IOError:
|
|
pass
|
|
except ImportError:
|
|
print("Module readline unavailable")
|
|
else:
|
|
#import rlcompleter
|
|
readline.parse_and_bind("tab: complete")
|
|
if sys.platform == 'darwin':
|
|
readline.parse_and_bind("bind ^I rl_complete")
|
|
|
|
self.prop_set_value(SPINEL.PROP_IPv6_ICMP_PING_OFFLOAD, 1)
|
|
self.prop_set_value(SPINEL.PROP_THREAD_RLOC16_DEBUG_PASSTHRU, 1)
|
|
|
|
command_names = [
|
|
# Shell commands
|
|
'exit',
|
|
'quit',
|
|
'clear',
|
|
'history',
|
|
'debug',
|
|
'debug-mem',
|
|
|
|
'v',
|
|
'h',
|
|
'q',
|
|
|
|
# OpenThread CLI commands
|
|
'help',
|
|
'channel',
|
|
'child',
|
|
'childmax',
|
|
'childtimeout',
|
|
'commissioner',
|
|
'contextreusedelay',
|
|
'counter',
|
|
'discover',
|
|
'eidcache',
|
|
'extaddr',
|
|
'extpanid',
|
|
'ifconfig',
|
|
'ipaddr',
|
|
'joiner',
|
|
'keysequence',
|
|
'leaderdata',
|
|
'leaderweight',
|
|
'masterkey',
|
|
'mode',
|
|
'netdataregister',
|
|
'networkidtimeout',
|
|
'networkname',
|
|
'panid',
|
|
'ping',
|
|
'prefix',
|
|
'releaserouterid',
|
|
'rloc16',
|
|
'route',
|
|
'router',
|
|
'routerselectionjitter',
|
|
'routerupgradethreshold',
|
|
'routerdowngradethreshold',
|
|
'scan',
|
|
'state',
|
|
'thread',
|
|
'version',
|
|
'whitelist',
|
|
|
|
# OpenThread Diagnostics Module CLI
|
|
'diag',
|
|
'diag-start',
|
|
'diag-channel',
|
|
'diag-power',
|
|
'diag-send',
|
|
'diag-repeat',
|
|
'diag-sleep',
|
|
'diag-stats',
|
|
'diag-stop',
|
|
|
|
# OpenThread Spinel-specific commands
|
|
'ncp-ml64',
|
|
'ncp-ll64',
|
|
'ncp-tun',
|
|
'ncp-raw',
|
|
'ncp-filter',
|
|
|
|
]
|
|
|
|
@classmethod
|
|
def wpan_callback(cls, prop, value, tid):
|
|
consumed = False
|
|
|
|
if prop == SPINEL.PROP_STREAM_NET:
|
|
consumed = True
|
|
|
|
pkt = IPv6(value[2:])
|
|
|
|
if CONFIG.DEBUG_LOG_PKT:
|
|
pkt.show()
|
|
|
|
if ICMPv6EchoReply in pkt:
|
|
timenow = int(round(time.time() * 1000)) & 0xFFFFFFFF
|
|
timedelta = (timenow - struct.unpack('>I', pkt.data)[0])
|
|
print("\n%d bytes from %s: icmp_seq=%d hlim=%d time=%dms" % (
|
|
pkt.plen, pkt.src, pkt.seq, pkt.hlim, timedelta))
|
|
|
|
return consumed
|
|
|
|
@classmethod
|
|
def log(cls, text):
|
|
""" Common log handler. """
|
|
logging.info(text)
|
|
|
|
def parseline(self, line):
|
|
cmd, arg, line = Cmd.parseline(self, line)
|
|
if cmd:
|
|
cmd = self.short_command_name(cmd)
|
|
line = cmd + ' ' + arg
|
|
return cmd, arg, line
|
|
|
|
def completenames(self, text, *ignored):
|
|
return [name + ' ' for name in SpinelCliCmd.command_names
|
|
if name.startswith(text)
|
|
or self.short_command_name(name).startswith(text)]
|
|
|
|
@classmethod
|
|
def short_command_name(cls, cmd):
|
|
return cmd.replace('-', '')
|
|
|
|
def postloop(self):
|
|
try:
|
|
import readline
|
|
try:
|
|
readline.write_history_file(self.history_filename)
|
|
except IOError:
|
|
pass
|
|
except ImportError:
|
|
pass
|
|
|
|
def prop_get_value(self, prop_id):
|
|
""" Blocking helper to return value for given propery identifier. """
|
|
return self.wpan_api.prop_get_value(prop_id)
|
|
|
|
def prop_set_value(self, prop_id, value, py_format='B'):
|
|
""" Blocking helper to set value for given propery identifier. """
|
|
return self.wpan_api.prop_set_value(prop_id, value, py_format)
|
|
|
|
def prop_insert_value(self, prop_id, value, py_format='B'):
|
|
""" Blocking helper to insert entry for given list property. """
|
|
return self.wpan_api.prop_insert_value(prop_id, value, py_format)
|
|
|
|
def prop_remove_value(self, prop_id, value, py_format='B'):
|
|
""" Blocking helper to remove entry for given list property. """
|
|
return self.wpan_api.prop_remove_value(prop_id, value, py_format)
|
|
|
|
def prop_get_or_set_value(self, prop_id, line, mixed_format='B'):
|
|
""" Helper to get or set a property value based on line arguments. """
|
|
if line:
|
|
value = self.prep_line(line, mixed_format)
|
|
py_format = self.prep_format(value, mixed_format)
|
|
value = self.prop_set_value(prop_id, value, py_format)
|
|
else:
|
|
value = self.prop_get_value(prop_id)
|
|
return value
|
|
|
|
@classmethod
|
|
def prep_line(cls, line, mixed_format='B'):
|
|
""" Convert a command line argument to proper binary encoding (pre-pack). """
|
|
value = line
|
|
if line != None:
|
|
if mixed_format == 'U': # For UTF8, just a pass through line unmodified
|
|
value = line.encode('utf-8') + '\0'
|
|
elif mixed_format == 'D': # Expect raw data to be hex string w/o delimeters
|
|
value = util.hex_to_bytes(line)
|
|
else:
|
|
# Most everything else is some type of integer
|
|
value = int(line)
|
|
return value
|
|
|
|
@classmethod
|
|
def prep_format(cls, value, mixed_format='B'):
|
|
""" Convert a spinel format to a python pack format. """
|
|
py_format = mixed_format
|
|
if value == "":
|
|
py_format = '0s'
|
|
elif mixed_format == 'D' or mixed_format == 'U':
|
|
py_format = str(len(value)) + 's'
|
|
return py_format
|
|
|
|
def prop_get(self, prop_id, mixed_format='B'):
|
|
""" Helper to get a propery and output the value with Done or Error. """
|
|
value = self.prop_get_value(prop_id)
|
|
if value is None:
|
|
print("Error")
|
|
return None
|
|
|
|
if (mixed_format == 'D') or (mixed_format == 'E'):
|
|
print(util.hexify_str(value, ''))
|
|
else:
|
|
print(str(value))
|
|
print("Done")
|
|
|
|
return value
|
|
|
|
def prop_set(self, prop_id, line, mixed_format='B'):
|
|
""" Helper to set a propery and output Done or Error. """
|
|
value = self.prep_line(line, mixed_format)
|
|
py_format = self.prep_format(value, mixed_format)
|
|
result = self.prop_set_value(prop_id, value, py_format)
|
|
|
|
if result is None:
|
|
print("Error")
|
|
else:
|
|
print("Done")
|
|
|
|
return result
|
|
|
|
def handle_property(self, line, prop_id, mixed_format='B', output=True):
|
|
""" Helper to set property when line argument passed, get otherwise. """
|
|
value = self.prop_get_or_set_value(prop_id, line, mixed_format)
|
|
if not output:
|
|
return value
|
|
|
|
if value is None or value == "":
|
|
print("Error")
|
|
return None
|
|
|
|
if line is None or line == "":
|
|
# Only print value on PROP_VALUE_GET
|
|
if mixed_format == '6':
|
|
print(str(ipaddress.IPv6Address(value)))
|
|
elif (mixed_format == 'D') or (mixed_format == 'E'):
|
|
print(util.hexify_str(value, ''))
|
|
elif mixed_format == 'H':
|
|
print("%04x" % value)
|
|
else:
|
|
print(str(value))
|
|
|
|
print("Done")
|
|
return value
|
|
|
|
def do_help(self, line):
|
|
if line:
|
|
cmd, _arg, _unused = self.parseline(line)
|
|
try:
|
|
doc = getattr(self, 'do_' + cmd).__doc__
|
|
except AttributeError:
|
|
doc = None
|
|
if doc:
|
|
self.log("%s\n" % textwrap.dedent(doc))
|
|
else:
|
|
self.log("No help on %s\n" % (line))
|
|
else:
|
|
self.print_topics(
|
|
"\nAvailable commands (type help <name> for more information):",
|
|
SpinelCliCmd.command_names, 15, 80)
|
|
|
|
def do_v(self, _line):
|
|
"""
|
|
version
|
|
Shows detailed version information on spinel-cli tool:
|
|
"""
|
|
self.log(MASTER_PROMPT + " ver. " + __version__)
|
|
self.log(__copyright__)
|
|
|
|
@classmethod
|
|
def do_clear(cls, _line):
|
|
""" Clean up the display. """
|
|
os.system('reset')
|
|
|
|
def do_history(self, _line):
|
|
"""
|
|
history
|
|
|
|
Show previously executed commands.
|
|
"""
|
|
|
|
try:
|
|
import readline
|
|
hist = readline.get_current_history_length()
|
|
for idx in range(1, hist + 1):
|
|
self.log(readline.get_history_item(idx))
|
|
except ImportError:
|
|
pass
|
|
|
|
def do_h(self, line):
|
|
""" Shortcut for history. """
|
|
self.do_history(line)
|
|
|
|
def do_exit(self, _line):
|
|
""" Exit the shell. """
|
|
self.log("exit")
|
|
return True
|
|
|
|
def do_quit(self, line):
|
|
""" Exit the shell. """
|
|
return self.do_exit(line)
|
|
|
|
def do_q(self, line):
|
|
""" Exit the shell. """
|
|
return self.do_exit(line)
|
|
|
|
def do_EOF(self, _line):
|
|
""" End of file handler for when commands are piped into shell. """
|
|
self.log("\n")
|
|
return True
|
|
|
|
def emptyline(self):
|
|
pass
|
|
|
|
def default(self, line):
|
|
if line[0] == "#":
|
|
logging.debug(line)
|
|
else:
|
|
logging.info(line + ": command not found")
|
|
# exec(line)
|
|
|
|
def do_debug(self, line):
|
|
"""
|
|
Enables detail logging of bytes over the wire to the radio modem.
|
|
Usage: debug <1=enable | 0=disable>
|
|
"""
|
|
|
|
if line != None and line != "":
|
|
level = int(line)
|
|
else:
|
|
level = 0
|
|
|
|
CONFIG.debug_set_level(level)
|
|
|
|
def do_debugmem(self, _line):
|
|
""" Profile python memory usage. """
|
|
from guppy import hpy
|
|
heap_stats = hpy()
|
|
print(heap_stats.heap())
|
|
print()
|
|
print(heap_stats.heap().byrcs)
|
|
|
|
def do_channel(self, line):
|
|
"""
|
|
\033[1mchannel\033[0m
|
|
|
|
Get the IEEE 802.15.4 Channel value.
|
|
\033[2m
|
|
> channel
|
|
11
|
|
Done
|
|
\033[0m
|
|
\033[1mchannel <channel>\033[0m
|
|
|
|
Set the IEEE 802.15.4 Channel value.
|
|
\033[2m
|
|
> channel 11
|
|
Done
|
|
\033[0m
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_PHY_CHAN)
|
|
|
|
def do_child(self, line):
|
|
"""\033[1m
|
|
child list
|
|
\033[0m
|
|
List attached Child IDs
|
|
\033[2m
|
|
> child list
|
|
1 2 3 6 7 8
|
|
Done
|
|
\033[0m\033[1m
|
|
child <id>
|
|
\033[0m
|
|
Print diagnostic information for an attached Thread Child.
|
|
The id may be a Child ID or an RLOC16.
|
|
\033[2m
|
|
> child 1
|
|
Child ID: 1
|
|
Rloc: 9c01
|
|
Ext Addr: e2b3540590b0fd87
|
|
Mode: rsn
|
|
Net Data: 184
|
|
Timeout: 100
|
|
Age: 0
|
|
LQI: 3
|
|
RSSI: -20
|
|
Done
|
|
\033[0m
|
|
"""
|
|
pass
|
|
|
|
def do_childmax(self, line):
|
|
"""\033[1m
|
|
childmax
|
|
\033[0m
|
|
Get the Thread Child Count Max value.
|
|
\033[2m
|
|
> childmax
|
|
10
|
|
Done
|
|
\033[0m\033[1m
|
|
childmax <timeout>
|
|
\033[0m
|
|
Set the Thread Child Count Max value.
|
|
\033[2m
|
|
> childmax 5
|
|
Done
|
|
\033[0m
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_THREAD_CHILD_COUNT_MAX)
|
|
|
|
def do_childtimeout(self, line):
|
|
"""\033[1m
|
|
childtimeout
|
|
\033[0m
|
|
Get the Thread Child Timeout value.
|
|
\033[2m
|
|
> childtimeout
|
|
300
|
|
Done
|
|
\033[0m\033[1m
|
|
childtimeout <timeout>
|
|
\033[0m
|
|
Set the Thread Child Timeout value.
|
|
\033[2m
|
|
> childtimeout 300
|
|
Done
|
|
\033[0m
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_THREAD_CHILD_TIMEOUT, 'L')
|
|
|
|
def do_commissioner(self, line):
|
|
"""
|
|
These commands are enabled when configuring with --enable-commissioner.
|
|
|
|
\033[1m
|
|
commissioner start
|
|
\033[0m
|
|
Start the Commissioner role on this node.
|
|
\033[2m
|
|
> commissioner start
|
|
Done
|
|
\033[0m\033[1m
|
|
commissioner stop
|
|
\033[0m
|
|
Stop the Commissioner role on this node.
|
|
\033[2m
|
|
> commissioner stop
|
|
Done
|
|
\033[0m\033[1m
|
|
commissioner panid <panid> <mask> <destination>
|
|
\033[0m
|
|
Perform panid query.
|
|
\033[2m
|
|
> commissioner panid 57005 4294967295 ff33:0040:fdde:ad00:beef:0:0:1
|
|
Conflict: dead, 00000800
|
|
Done
|
|
\033[0m\033[1m
|
|
commissioner energy <mask> <count> <period> <scanDuration>
|
|
\033[0m
|
|
Perform energy scan.
|
|
\033[2m
|
|
> commissioner energy 327680 2 32 1000 fdde:ad00:beef:0:0:ff:fe00:c00
|
|
Energy: 00050000 0 0 0 0
|
|
Done
|
|
\033[0m
|
|
"""
|
|
pass
|
|
|
|
def do_contextreusedelay(self, line):
|
|
"""
|
|
contextreusedelay
|
|
|
|
Get the CONTEXT_ID_REUSE_DELAY value.
|
|
|
|
> contextreusedelay
|
|
11
|
|
Done
|
|
|
|
contextreusedelay <delay>
|
|
|
|
Set the CONTEXT_ID_REUSE_DELAY value.
|
|
|
|
> contextreusedelay 11
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_THREAD_CONTEXT_REUSE_DELAY, 'L')
|
|
|
|
def do_counter(self, line):
|
|
"""
|
|
counter
|
|
|
|
Get the supported counter names.
|
|
|
|
>counter
|
|
mac
|
|
Done
|
|
|
|
counter <countername>
|
|
|
|
Get the counter value.
|
|
|
|
>counter mac
|
|
TxTotal: 10
|
|
TxAckRequested: 4
|
|
TxAcked: 4
|
|
TxNoAckRequested: 6
|
|
TxData: 10
|
|
TxDataPoll: 0
|
|
TxBeacon: 0
|
|
TxBeaconRequest: 0
|
|
TxOther: 0
|
|
TxRetry: 0
|
|
TxErrCca: 0
|
|
RxTotal: 11
|
|
RxData: 11
|
|
RxDataPoll: 0
|
|
RxBeacon: 0
|
|
RxBeaconRequest: 0
|
|
RxOther: 0
|
|
RxWhitelistFiltered: 0
|
|
RxDestAddrFiltered: 0
|
|
RxErrNoFrame: 0
|
|
RxErrNoUnknownNeighbor: 0
|
|
RxErrInvalidSrcAddr: 0
|
|
RxErrSec: 0
|
|
RxErrFcs: 0
|
|
RxErrOther: 0
|
|
"""
|
|
pass
|
|
|
|
def do_discover(self, line):
|
|
"""
|
|
discover [channel]
|
|
|
|
Perform an MLE Discovery operation.
|
|
|
|
channel: The channel to discover on. If no channel is provided,
|
|
the discovery will cover all valid channels.
|
|
|
|
> discover
|
|
| J | Network Name | Extended PAN | PAN | MAC Address | Ch | dBm | LQI |
|
|
+---+------------------+------------------+------+------------------+----+-----+-----+
|
|
| 0 | OpenThread | dead00beef00cafe | ffff | f1d92a82c8d8fe43 | 11 | -20 | 0 |
|
|
Done
|
|
"""
|
|
pass
|
|
|
|
def do_eidcache(self, line):
|
|
"""
|
|
eidcache
|
|
|
|
Print the EID-to-RLOC cache entries.
|
|
|
|
> eidcache
|
|
fdde:ad00:beef:0:bb1:ebd6:ad10:f33 ac00
|
|
fdde:ad00:beef:0:110a:e041:8399:17cd 6000
|
|
Done
|
|
"""
|
|
pass
|
|
|
|
def do_extaddr(self, line):
|
|
"""
|
|
extaddr
|
|
|
|
Get the IEEE 802.15.4 Extended Address.
|
|
|
|
> extaddr
|
|
dead00beef00cafe
|
|
Done
|
|
|
|
extaddr <extaddr>
|
|
|
|
Set the IEEE 802.15.4 Extended Address.
|
|
|
|
> extaddr dead00beef00cafe
|
|
dead00beef00cafe
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_MAC_15_4_LADDR, 'E')
|
|
|
|
def do_extpanid(self, line):
|
|
"""
|
|
extpanid
|
|
|
|
Get the Thread Extended PAN ID value.
|
|
|
|
> extpanid
|
|
dead00beef00cafe
|
|
Done
|
|
|
|
extpanid <extpanid>
|
|
|
|
Set the Thread Extended PAN ID value.
|
|
|
|
> extpanid dead00beef00cafe
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_NET_XPANID, 'D')
|
|
|
|
def do_joiner(self, line):
|
|
"""
|
|
These commands are enabled when configuring with --enable-joiner.
|
|
|
|
joiner start <pskd> <provisioningUrl>
|
|
|
|
Start the Joiner role.
|
|
|
|
* pskd: Pre-Shared Key for the Joiner.
|
|
* provisioningUrl: Provisioning URL for the Joiner (optional).
|
|
|
|
This command will cause the device to perform an MLE Discovery and
|
|
initiate the Thread Commissioning process.
|
|
|
|
> joiner start PSK
|
|
Done
|
|
|
|
joiner stop
|
|
|
|
Stop the Joiner role.
|
|
|
|
> joiner stop
|
|
Done
|
|
"""
|
|
PSKd = ""
|
|
|
|
params = line.split(" ")
|
|
if len(params) > 0:
|
|
sub_command = params[0]
|
|
if len(params) > 1:
|
|
PSKd = params[1]
|
|
|
|
PSKd = self.prep_line(PSKd, 'U')
|
|
|
|
if sub_command == "":
|
|
pass
|
|
|
|
elif sub_command == "start":
|
|
py_format = self.prep_format(PSKd, 'U')
|
|
self.prop_set_value(SPINEL.PROP_MESHCOP_JOINER_CREDENTIAL,
|
|
PSKd, py_format)
|
|
self.prop_set_value(SPINEL.PROP_MESHCOP_JOINER_ENABLE, 1)
|
|
print("Done")
|
|
return
|
|
|
|
elif sub_command == "stop":
|
|
self.prop_set_value(SPINEL.PROP_MESHCOP_JOINER_ENABLE, 0)
|
|
print("Done")
|
|
return
|
|
|
|
print("Error")
|
|
|
|
def complete_ifconfig(self, text, _line, _begidx, _endidx):
|
|
""" Subcommand completion handler for ifconfig command. """
|
|
map_sub_commands = ('up', 'down')
|
|
return [i for i in map_sub_commands if i.startswith(text)]
|
|
|
|
def do_ifconfig(self, line):
|
|
"""
|
|
ifconfig up
|
|
|
|
Bring up the IPv6 interface.
|
|
|
|
> ifconfig up
|
|
Done
|
|
|
|
ifconfig down
|
|
|
|
Bring down the IPv6 interface.
|
|
|
|
> ifconfig down
|
|
Done
|
|
|
|
ifconfig
|
|
|
|
Show the status of the IPv6 interface.
|
|
|
|
> ifconfig
|
|
down
|
|
Done
|
|
"""
|
|
|
|
params = line.split(" ")
|
|
|
|
if params[0] == "":
|
|
value = self.prop_get_value(SPINEL.PROP_NET_IF_UP)
|
|
if value != None:
|
|
map_arg_value = {
|
|
0: "down",
|
|
1: "up",
|
|
}
|
|
print(map_arg_value[value])
|
|
|
|
elif params[0] == "up":
|
|
self.prop_set(SPINEL.PROP_NET_IF_UP, '1')
|
|
return
|
|
|
|
elif params[0] == "down":
|
|
self.prop_set(SPINEL.PROP_NET_IF_UP, '0')
|
|
return
|
|
|
|
print("Done")
|
|
|
|
def complete_ipaddr(self, text, _line, _begidx, _endidx):
|
|
""" Subcommand completion handler for ipaddr command. """
|
|
map_sub_commands = ('add', 'remove')
|
|
return [i for i in map_sub_commands if i.startswith(text)]
|
|
|
|
def do_ipaddr(self, line):
|
|
"""
|
|
ipaddr
|
|
|
|
List all IPv6 addresses assigned to the Thread interface.
|
|
|
|
> ipaddr
|
|
fdde:ad00:beef:0:0:ff:fe00:0
|
|
fe80:0:0:0:0:ff:fe00:0
|
|
fdde:ad00:beef:0:558:f56b:d688:799
|
|
fe80:0:0:0:f3d9:2a82:c8d8:fe43
|
|
Done
|
|
|
|
ipaddr add <ipaddr>
|
|
|
|
Add an IPv6 address to the Thread interface.
|
|
|
|
> ipaddr add 2001::dead:beef:cafe
|
|
Done
|
|
|
|
ipaddr del <ipaddr>
|
|
|
|
Delete an IPv6 address from the Thread interface.
|
|
|
|
> ipaddr del 2001::dead:beef:cafe
|
|
Done
|
|
"""
|
|
params = line.split(" ")
|
|
valid = 1
|
|
preferred = 1
|
|
flags = 0
|
|
# always use /64, as prefix.network.prefixlen returns /128.
|
|
prefix_len = 64
|
|
|
|
num = len(params)
|
|
if num > 1:
|
|
ipaddr = params[1]
|
|
prefix = ipaddress.IPv6Interface(unicode(ipaddr))
|
|
arr = prefix.ip.packed
|
|
|
|
if params[0] == "":
|
|
addrs = self.wpan_api.get_ipaddrs()
|
|
for addr in addrs:
|
|
print(str(addr))
|
|
|
|
elif params[0] == "add":
|
|
arr += self.wpan_api.encode_fields('CLLC',
|
|
prefix_len,
|
|
valid,
|
|
preferred,
|
|
flags)
|
|
|
|
self.prop_insert_value(SPINEL.PROP_IPV6_ADDRESS_TABLE,
|
|
arr, str(len(arr)) + 's')
|
|
|
|
if self.tun_if:
|
|
self.tun_if.addr_add(ipaddr)
|
|
|
|
elif params[0] == "remove":
|
|
arr += self.wpan_api.encode_fields('CLLC',
|
|
prefix_len,
|
|
valid,
|
|
preferred,
|
|
flags)
|
|
|
|
self.prop_remove_value(SPINEL.PROP_IPV6_ADDRESS_TABLE,
|
|
arr, str(len(arr)) + 's')
|
|
if self.tun_if:
|
|
self.tun_if.addr_del(ipaddr)
|
|
|
|
print("Done")
|
|
|
|
def do_keysequence(self, line):
|
|
"""
|
|
keysequence counter
|
|
|
|
Get the Thread Key Sequence Counter.
|
|
|
|
> keysequence counter
|
|
10
|
|
Done
|
|
|
|
keysequence counter <counter>
|
|
|
|
Set the Thread Key Sequence Counter.
|
|
|
|
> keysequence counter 10
|
|
Done
|
|
|
|
keysequence guardtime
|
|
|
|
Get the thrKeySwitchGuardTime (in hours).
|
|
|
|
> keysequence guardtime
|
|
0
|
|
Done
|
|
|
|
keysequence guardtime <guardtime>
|
|
|
|
Set the thrKeySwitchGuardTime (in hours).
|
|
|
|
> keysequence guardtime 0
|
|
Done
|
|
"""
|
|
|
|
args = line.split(" ")
|
|
|
|
if args[0] == "counter":
|
|
newline = line.replace("counter", "")
|
|
self.handle_property(newline, SPINEL.PROP_NET_KEY_SEQUENCE_COUNTER, 'L')
|
|
|
|
elif args[0] == "guardtime":
|
|
newline = line.replace("guardtime", "")
|
|
self.handle_property(newline, SPINEL.PROP_NET_KEY_SWITCH_GUARDTIME, 'L')
|
|
|
|
def do_leaderdata(self, line):
|
|
"""
|
|
leaderdata
|
|
"""
|
|
pass
|
|
|
|
def do_leaderweight(self, line):
|
|
"""
|
|
leaderweight
|
|
|
|
Get the Thread Leader Weight.
|
|
|
|
> leaderweight
|
|
128
|
|
Done
|
|
|
|
leaderweight <weight>
|
|
|
|
Set the Thread Leader Weight.
|
|
|
|
> leaderweight 128
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_THREAD_LOCAL_LEADER_WEIGHT)
|
|
|
|
def do_masterkey(self, line):
|
|
"""
|
|
masterkey
|
|
|
|
Get the Thread Master Key value.
|
|
|
|
> masterkey
|
|
00112233445566778899aabbccddeeff
|
|
Done
|
|
|
|
masterkey <key>
|
|
|
|
Set the Thread Master Key value.
|
|
|
|
> masterkey 00112233445566778899aabbccddeeff
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_NET_MASTER_KEY, 'D')
|
|
|
|
def do_mode(self, line):
|
|
"""
|
|
mode
|
|
|
|
Get the Thread Device Mode value.
|
|
|
|
r: rx-on-when-idle
|
|
s: Secure IEEE 802.15.4 data requests
|
|
d: Full Function Device
|
|
n: Full Network Data
|
|
|
|
> mode
|
|
rsdn
|
|
Done
|
|
|
|
mode [rsdn]
|
|
|
|
Set the Thread Device Mode value.
|
|
|
|
r: rx-on-when-idle
|
|
s: Secure IEEE 802.15.4 data requests
|
|
d: Full Function Device
|
|
n: Full Network Data
|
|
|
|
> mode rsdn
|
|
Done
|
|
"""
|
|
map_arg_value = {
|
|
0x00: "0",
|
|
0x01: "n",
|
|
0x02: "d",
|
|
0x03: "dn",
|
|
0x04: "s",
|
|
0x05: "sn",
|
|
0x06: "sd",
|
|
0x07: "sdn",
|
|
0x08: "r",
|
|
0x09: "rn",
|
|
0x0A: "rd",
|
|
0x0B: "rdn",
|
|
0x0C: "rs",
|
|
0x0D: "rsn",
|
|
0x0E: "rsd",
|
|
0x0F: "rsdn",
|
|
}
|
|
|
|
map_arg_name = {
|
|
"0": 0x00,
|
|
"n": 0x01,
|
|
"d": 0x02,
|
|
"dn": 0x03,
|
|
"s": 0x04,
|
|
"sn": 0x05,
|
|
"sd": 0x06,
|
|
"sdn": 0x07,
|
|
"r": 0x08,
|
|
"rn": 0x09,
|
|
"rd": 0x0A,
|
|
"rdn": 0x0B,
|
|
"rs": 0x0C,
|
|
"rsn": 0x0D,
|
|
"rsd": 0x0E,
|
|
"rsdn": 0x0F
|
|
}
|
|
|
|
if line:
|
|
try:
|
|
# remap string state names to integer
|
|
line = map_arg_name[line]
|
|
except KeyError:
|
|
print("Error")
|
|
return
|
|
|
|
result = self.prop_get_or_set_value(SPINEL.PROP_THREAD_MODE, line)
|
|
if result != None:
|
|
if not line:
|
|
print(map_arg_value[result])
|
|
print("Done")
|
|
else:
|
|
print("Error")
|
|
|
|
def do_netdataregister(self, _line):
|
|
"""
|
|
netdataregister
|
|
|
|
Register local network data with Thread Leader.
|
|
|
|
> netdataregister
|
|
Done
|
|
"""
|
|
self.prop_set_value(SPINEL.PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE, 1)
|
|
self.handle_property(
|
|
"0", SPINEL.PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE)
|
|
|
|
def do_networkidtimeout(self, line):
|
|
"""
|
|
networkidtimeout
|
|
|
|
Get the NETWORK_ID_TIMEOUT parameter used in the Router role.
|
|
|
|
> networkidtimeout
|
|
120
|
|
Done
|
|
|
|
networkidtimeout <timeout>
|
|
|
|
Set the NETWORK_ID_TIMEOUT parameter used in the Router role.
|
|
|
|
> networkidtimeout 120
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_THREAD_NETWORK_ID_TIMEOUT)
|
|
|
|
def do_networkname(self, line):
|
|
"""
|
|
networkname
|
|
|
|
Get the Thread Network Name.
|
|
|
|
> networkname
|
|
OpenThread
|
|
Done
|
|
|
|
networkname <name>
|
|
|
|
Set the Thread Network Name.
|
|
|
|
> networkname OpenThread
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_NET_NETWORK_NAME, 'U')
|
|
|
|
def do_panid(self, line):
|
|
"""
|
|
panid
|
|
|
|
Get the IEEE 802.15.4 PAN ID value.
|
|
|
|
> panid
|
|
0xdead
|
|
Done
|
|
|
|
panid <panid>
|
|
|
|
Set the IEEE 802.15.4 PAN ID value.
|
|
|
|
> panid 0xdead
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_MAC_15_4_PANID, 'H')
|
|
|
|
def do_ping(self, line):
|
|
"""
|
|
ping <ipaddr> [size] [count] [interval]
|
|
|
|
Send an ICMPv6 Echo Request.
|
|
|
|
> ping fdde:ad00:beef:0:558:f56b:d688:799
|
|
16 bytes from fdde:ad00:beef:0:558:f56b:d688:799: icmp_seq=1 hlim=64 time=28ms
|
|
"""
|
|
params = line.split(" ")
|
|
addr = "::1"
|
|
_size = "56"
|
|
_count = "1"
|
|
_interval = "1"
|
|
if len(params) > 0:
|
|
addr = params[0]
|
|
if len(params) > 1:
|
|
_size = params[1]
|
|
if len(params) > 2:
|
|
_count = params[2]
|
|
if len(params) > 3:
|
|
_interval = params[3]
|
|
|
|
try:
|
|
# Generate local ping packet and send directly via spinel.
|
|
ml64 = self.prop_get_value(SPINEL.PROP_IPV6_ML_ADDR)
|
|
ml64 = str(ipaddress.IPv6Address(ml64))
|
|
timenow = int(round(time.time() * 1000)) & 0xFFFFFFFF
|
|
timenow = struct.pack('>I', timenow)
|
|
ping_req = str(IPv6(src=ml64, dst=addr) /
|
|
ICMPv6EchoRequest() / timenow)
|
|
self.wpan_api.ip_send(ping_req)
|
|
# Let handler print result
|
|
except:
|
|
print("Fail")
|
|
print(traceback.format_exc())
|
|
|
|
def complete_prefix(self, text, _line, _begidx, _endidx):
|
|
""" Subcommand completion handler for prefix command. """
|
|
map_sub_commands = ('add', 'remove')
|
|
return [i for i in map_sub_commands if i.startswith(text)]
|
|
|
|
def do_prefix(self, line):
|
|
"""
|
|
prefix add <prefix> [pvdcsr] [prf]
|
|
|
|
Add a valid prefix to the Network Data.
|
|
|
|
p: Preferred flag
|
|
a: Stateless IPv6 Address Autoconfiguration flag
|
|
d: DHCPv6 IPv6 Address Configuration flag
|
|
c: DHCPv6 Other Configuration flag
|
|
r: Default Route flag
|
|
o: On Mesh flag
|
|
s: Stable flag
|
|
prf: Default router preference, which may be 'high', 'med', or 'low'.
|
|
> prefix add 2001:dead:beef:cafe::/64 paros med
|
|
Done
|
|
|
|
prefix remove <prefix>
|
|
|
|
Invalidate a prefix in the Network Data.
|
|
|
|
> prefix remove 2001:dead:beef:cafe::/64
|
|
Done
|
|
"""
|
|
params = line.split(" ")
|
|
stable = 0
|
|
flags = 0
|
|
arr = ""
|
|
|
|
num = len(params)
|
|
if num > 1:
|
|
prefix = ipaddress.IPv6Interface(unicode(params[1]))
|
|
arr = prefix.ip.packed
|
|
|
|
if num > 2:
|
|
map_param_to_flag = {
|
|
'p': kThread.PrefixPreferredFlag,
|
|
'a': kThread.PrefixSlaacFlag,
|
|
'd': kThread.PrefixDhcpFlag,
|
|
'c': kThread.PrefixConfigureFlag,
|
|
'r': kThread.PrefixDefaultRouteFlag,
|
|
'o': kThread.PrefixOnMeshFlag,
|
|
}
|
|
for char in params[2]:
|
|
if char == 's':
|
|
stable = 1 # Stable flag
|
|
else:
|
|
flag = map_param_to_flag.get(char, None)
|
|
if flag is not None:
|
|
flags |= flag
|
|
|
|
if num > 3:
|
|
map_arg_name = {
|
|
"high": 2,
|
|
"med": 1,
|
|
"low": 0,
|
|
}
|
|
prf = map_arg_name[params[3]]
|
|
flags |= (prf << kThread.PrefixPreferenceOffset)
|
|
|
|
if params[0] == "":
|
|
self.prop_get_value(SPINEL.PROP_THREAD_ON_MESH_NETS)
|
|
|
|
elif params[0] == "add":
|
|
arr += self.wpan_api.encode_fields('CbC',
|
|
prefix.network.prefixlen,
|
|
stable,
|
|
flags)
|
|
|
|
self.prop_set_value(
|
|
SPINEL.PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE, 1)
|
|
self.prop_insert_value(SPINEL.PROP_THREAD_ON_MESH_NETS,
|
|
arr, str(len(arr)) + 's')
|
|
|
|
elif params[0] == "remove":
|
|
arr += self.wpan_api.encode_fields('CbC',
|
|
prefix.network.prefixlen,
|
|
stable,
|
|
flags)
|
|
|
|
self.prop_set_value(
|
|
SPINEL.PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE, 1)
|
|
self.prop_remove_value(SPINEL.PROP_THREAD_ON_MESH_NETS,
|
|
arr, str(len(arr)) + 's')
|
|
|
|
print("Done")
|
|
|
|
def do_releaserouterid(self, line):
|
|
"""
|
|
releaserouterid <routerid>
|
|
|
|
Release a Router ID that has been allocated by the device in the Leader role.
|
|
|
|
> releaserouterid 16
|
|
Done
|
|
"""
|
|
if line:
|
|
value = int(line)
|
|
self.prop_remove_value(SPINEL.PROP_THREAD_ACTIVE_ROUTER_IDS, value)
|
|
print("Done")
|
|
|
|
def do_rloc16(self, line):
|
|
"""
|
|
rloc16
|
|
|
|
Get the Thread RLOC16 value.
|
|
|
|
> rloc16
|
|
0xdead
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_THREAD_RLOC16, 'H')
|
|
|
|
def complete_route(self, text, _line, _begidx, _endidx):
|
|
""" Subcommand completion handler for route command. """
|
|
map_sub_commands = ('add', 'remove')
|
|
return [i for i in map_sub_commands if i.startswith(text)]
|
|
|
|
def do_route(self, line):
|
|
"""
|
|
route add <prefix> [s] [prf]
|
|
|
|
Add a valid prefix to the Network Data.
|
|
|
|
s: Stable flag
|
|
prf: Default Router Preference, which may be: 'high', 'med', or 'low'.
|
|
|
|
> route add 2001:dead:beef:cafe::/64 s med
|
|
Done
|
|
|
|
route remove <prefix>
|
|
|
|
Invalidate a prefix in the Network Data.
|
|
|
|
> route remove 2001:dead:beef:cafe::/64
|
|
Done
|
|
"""
|
|
params = line.split(" ")
|
|
stable = 0
|
|
prf = 0
|
|
|
|
num = len(params)
|
|
if num > 1:
|
|
prefix = ipaddress.IPv6Interface(unicode(params[1]))
|
|
arr = prefix.ip.packed
|
|
|
|
if params[0] == "":
|
|
self.prop_get_value(SPINEL.PROP_THREAD_LOCAL_ROUTES)
|
|
|
|
elif params[0] == "add":
|
|
arr += self.wpan_api.encode_fields('CbC',
|
|
prefix.network.prefixlen,
|
|
stable,
|
|
prf)
|
|
|
|
self.prop_set_value(
|
|
SPINEL.PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE, 1)
|
|
self.prop_insert_value(SPINEL.PROP_THREAD_LOCAL_ROUTES,
|
|
arr, str(len(arr)) + 's')
|
|
|
|
elif params[0] == "remove":
|
|
self.prop_set_value(
|
|
SPINEL.PROP_THREAD_ALLOW_LOCAL_NET_DATA_CHANGE, 1)
|
|
self.prop_remove_value(SPINEL.PROP_THREAD_LOCAL_ROUTES,
|
|
arr, str(len(arr)) + 's')
|
|
|
|
print("Done")
|
|
|
|
def do_router(self, line):
|
|
"""
|
|
router list
|
|
|
|
List allocated Router IDs
|
|
|
|
> router list
|
|
8 24 50
|
|
Done
|
|
|
|
router <id>
|
|
|
|
Print diagnostic information for a Thread Router.
|
|
The id may be a Router ID or an RLOC16.
|
|
|
|
> router 50
|
|
Alloc: 1
|
|
Router ID: 50
|
|
Rloc: c800
|
|
Next Hop: c800
|
|
Link: 1
|
|
Ext Addr: e2b3540590b0fd87
|
|
Cost: 0
|
|
LQI In: 3
|
|
LQI Out: 3
|
|
Age: 3
|
|
Done
|
|
|
|
> router 0xc800
|
|
Alloc: 1
|
|
Router ID: 50
|
|
Rloc: c800
|
|
Next Hop: c800
|
|
Link: 1
|
|
Ext Addr: e2b3540590b0fd87
|
|
Cost: 0
|
|
LQI In: 3
|
|
LQI Out: 3
|
|
Age: 7
|
|
Done
|
|
"""
|
|
pass
|
|
|
|
def do_routerselectionjitter(self, line):
|
|
"""
|
|
routerselectionjitter
|
|
|
|
Get the ROUTER_SELECTION_JITTER value.
|
|
|
|
> routerselectionjitter
|
|
120
|
|
Done
|
|
|
|
routerselectionjitter <threshold>
|
|
|
|
Set the ROUTER_SELECTION_JITTER value.
|
|
|
|
> routerselectionjitter 120
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_THREAD_ROUTER_SELECTION_JITTER)
|
|
|
|
def do_routerupgradethreshold(self, line):
|
|
"""
|
|
routerupgradethreshold
|
|
|
|
Get the ROUTER_UPGRADE_THRESHOLD value.
|
|
|
|
> routerupgradethreshold
|
|
16
|
|
Done
|
|
|
|
routerupgradethreshold <threshold>
|
|
|
|
Set the ROUTER_UPGRADE_THRESHOLD value.
|
|
|
|
> routerupgradethreshold 16
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_THREAD_ROUTER_UPGRADE_THRESHOLD)
|
|
|
|
def do_routerdowngradethreshold(self, line):
|
|
"""
|
|
routerdowngradethreshold
|
|
|
|
Get the ROUTER_DOWNGRADE_THRESHOLD value.
|
|
|
|
> routerdowngradethreshold
|
|
16
|
|
Done
|
|
|
|
routerdowngradethreshold <threshold>
|
|
|
|
Set the ROUTER_DOWNGRADE_THRESHOLD value.
|
|
|
|
> routerdowngradethreshold 16
|
|
Done
|
|
"""
|
|
self.handle_property(
|
|
line, SPINEL.PROP_THREAD_ROUTER_DOWNGRADE_THRESHOLD)
|
|
|
|
def do_scan(self, _line):
|
|
"""
|
|
scan [channel]
|
|
|
|
Perform an IEEE 802.15.4 Active Scan.
|
|
|
|
channel: The channel to scan on. If no channel is provided,
|
|
the active scan will cover all valid channels.
|
|
|
|
> scan
|
|
| J | Network Name | Extended PAN | PAN | MAC Address | Ch | dBm | LQI |
|
|
+---+------------------+------------------+------+------------------+----+-----+-----+
|
|
| 0 | OpenThread | dead00beef00cafe | ffff | f1d92a82c8d8fe43 | 11 | -20 | 0 |
|
|
Done
|
|
"""
|
|
# Initial mock-up of scan
|
|
self.handle_property("15", SPINEL.PROP_MAC_SCAN_MASK)
|
|
self.handle_property("4", SPINEL.PROP_MAC_SCAN_PERIOD, 'H')
|
|
self.handle_property("1", SPINEL.PROP_MAC_SCAN_STATE)
|
|
time.sleep(5)
|
|
self.handle_property("", SPINEL.PROP_MAC_SCAN_BEACON, 'U')
|
|
|
|
def complete_thread(self, text, _line, _begidx, _endidx):
|
|
""" Subcommand completion handler for thread command. """
|
|
map_sub_commands = ('start', 'stop')
|
|
return [i for i in map_sub_commands if i.startswith(text)]
|
|
|
|
def do_thread(self, line):
|
|
"""
|
|
thread start
|
|
|
|
Enable Thread protocol operation and attach to a Thread network.
|
|
|
|
> thread start
|
|
Done
|
|
|
|
thread stop
|
|
|
|
Disable Thread protocol operation and detach from a Thread network.
|
|
|
|
> thread stop
|
|
Done
|
|
"""
|
|
map_arg_value = {
|
|
0: "stop",
|
|
1: "start",
|
|
2: "start",
|
|
3: "start",
|
|
}
|
|
|
|
map_arg_name = {
|
|
"stop": "0",
|
|
"start": "2",
|
|
}
|
|
|
|
if line:
|
|
try:
|
|
# remap string state names to integer
|
|
line = map_arg_name[line]
|
|
except:
|
|
print("Error")
|
|
return
|
|
|
|
result = self.prop_get_or_set_value(SPINEL.PROP_NET_STACK_UP, line)
|
|
if result != None:
|
|
if not line:
|
|
print(map_arg_value[result])
|
|
print("Done")
|
|
else:
|
|
print("Error")
|
|
|
|
def do_state(self, line):
|
|
"""
|
|
state
|
|
"""
|
|
map_arg_value = {
|
|
0: "detached",
|
|
1: "child",
|
|
2: "router",
|
|
3: "leader",
|
|
}
|
|
|
|
map_arg_name = {
|
|
"disabled": "0",
|
|
"detached": "0",
|
|
"child": "1",
|
|
"router": "2",
|
|
"leader": "3",
|
|
}
|
|
|
|
if line:
|
|
try:
|
|
# remap string state names to integer
|
|
line = map_arg_name[line]
|
|
except:
|
|
print("Error")
|
|
return
|
|
|
|
result = self.prop_get_or_set_value(SPINEL.PROP_NET_ROLE, line)
|
|
if result != None:
|
|
if not line:
|
|
state = map_arg_value[result]
|
|
# TODO: if state="disabled": get NET_STATE to determine
|
|
# whether "disabled" or "detached"
|
|
print(state)
|
|
print("Done")
|
|
else:
|
|
print("Error")
|
|
|
|
def do_version(self, line):
|
|
"""
|
|
version
|
|
|
|
Print the build version information.
|
|
|
|
> version
|
|
OPENTHREAD/gf4f2f04; Jul 1 2016 17:00:09
|
|
Done
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_NCP_VERSION, 'U')
|
|
|
|
def complete_whitelist(self, text, _line, _begidx, _endidx):
|
|
""" Subcommand completion handler for whitelist command. """
|
|
map_sub_commands = ('add', 'remove', 'clear', 'enable', 'disable')
|
|
return [i for i in map_sub_commands if i.startswith(text)]
|
|
|
|
def do_whitelist(self, line):
|
|
"""
|
|
whitelist
|
|
|
|
List the whitelist entries.
|
|
|
|
> whitelist
|
|
Enabled
|
|
e2b3540590b0fd87
|
|
d38d7f875888fccb
|
|
c467a90a2060fa0e
|
|
Done
|
|
|
|
whitelist add <extaddr> [rssi]
|
|
|
|
Add an IEEE 802.15.4 Extended Address to the whitelist.
|
|
|
|
> whitelist add dead00beef00cafe
|
|
Done
|
|
|
|
whitelist clear
|
|
|
|
Clear all entries from the whitelist.
|
|
|
|
> whitelist clear
|
|
Done
|
|
|
|
whitelist disable
|
|
|
|
Disable MAC whitelist filtering.
|
|
|
|
> whitelist disable
|
|
Done
|
|
|
|
whitelist enable
|
|
|
|
Enable MAC whitelist filtering.
|
|
|
|
> whitelist enable
|
|
Done
|
|
|
|
whitelist remove <extaddr>
|
|
|
|
Remove an IEEE 802.15.4 Extended Address from the whitelist.
|
|
|
|
> whitelist remove dead00beef00cafe
|
|
Done
|
|
"""
|
|
map_arg_value = {
|
|
0: "Disabled",
|
|
1: "Enabled",
|
|
}
|
|
|
|
params = line.split(" ")
|
|
|
|
if params[0] == "":
|
|
value = self.prop_get_value(SPINEL.PROP_MAC_WHITELIST_ENABLED)
|
|
if value != None:
|
|
print(map_arg_value[value])
|
|
value = self.prop_get_value(SPINEL.PROP_MAC_WHITELIST)
|
|
|
|
elif params[0] == "enable":
|
|
self.prop_set(SPINEL.PROP_MAC_WHITELIST_ENABLED, '1')
|
|
return
|
|
|
|
elif params[0] == "disable":
|
|
self.prop_set(SPINEL.PROP_MAC_WHITELIST_ENABLED, '0')
|
|
return
|
|
|
|
elif params[0] == "clear":
|
|
value = self.prop_set_value(SPINEL.PROP_MAC_WHITELIST, None, None)
|
|
|
|
elif params[0] == "add":
|
|
arr = util.hex_to_bytes(params[1])
|
|
try:
|
|
rssi = int(params[2])
|
|
except:
|
|
rssi = SPINEL.RSSI_OVERRIDE
|
|
arr += struct.pack('b', rssi)
|
|
value = self.prop_insert_value(SPINEL.PROP_MAC_WHITELIST, arr,
|
|
str(len(arr)) + 's')
|
|
|
|
elif params[0] == "remove":
|
|
arr = util.hex_to_bytes(params[1])
|
|
arr += struct.pack('b', SPINEL.RSSI_OVERRIDE)
|
|
value = self.prop_remove_value(SPINEL.PROP_MAC_WHITELIST, arr,
|
|
str(len(arr)) + 's')
|
|
|
|
print("Done")
|
|
|
|
def do_ncpll64(self, line):
|
|
""" Display the link local IPv6 address. """
|
|
self.handle_property(line, SPINEL.PROP_IPV6_LL_ADDR, '6')
|
|
|
|
def do_ncpml64(self, line):
|
|
""" Display the mesh local IPv6 address. """
|
|
self.handle_property(line, SPINEL.PROP_IPV6_ML_ADDR, '6')
|
|
|
|
def do_ncpraw(self, line):
|
|
""" Enable MAC raw stream. """
|
|
self.handle_property(line, SPINEL.PROP_MAC_RAW_STREAM_ENABLED, 'B')
|
|
|
|
def do_ncpfilter(self, line):
|
|
"""
|
|
Set MAC filter mode:
|
|
|
|
0 = MAC_FILTER_MODE_NORMAL Normal MAC filtering is in place.
|
|
1 = MAC_FILTER_MODE_PROMISCUOUS All MAC packets matching network are passed up the stack.
|
|
2 = MAC_FILTER_MODE_MONITOR All decoded MAC packets are passed up the stack.
|
|
"""
|
|
self.handle_property(line, SPINEL.PROP_MAC_FILTER_MODE, 'B')
|
|
|
|
def complete_ncptun(self, text, _line, _begidx, _endidx):
|
|
""" Subcommand completion handler for ncp-tun command. """
|
|
map_sub_commands = ('up', 'down', 'add', 'remove', 'ping')
|
|
return [i for i in map_sub_commands if i.startswith(text)]
|
|
|
|
def do_ncptun(self, line):
|
|
"""
|
|
ncp-tun
|
|
|
|
Control sideband tunnel interface.
|
|
|
|
ncp-tun up
|
|
|
|
Bring up Thread TUN interface.
|
|
|
|
> ncp-tun up
|
|
Done
|
|
|
|
ncp-tun down
|
|
|
|
Bring down Thread TUN interface.
|
|
|
|
> ncp-tun down
|
|
Done
|
|
|
|
ncp-tun add <ipaddr>
|
|
|
|
Add an IPv6 address to the Thread TUN interface.
|
|
|
|
> ncp-tun add 2001::dead:beef:cafe
|
|
Done
|
|
|
|
ncp-tun del <ipaddr>
|
|
|
|
Delete an IPv6 address from the Thread TUN interface.
|
|
|
|
> ncp-tun del 2001::dead:beef:cafe
|
|
Done
|
|
|
|
ncp-tun ping <ipaddr> [size] [count] [interval]
|
|
|
|
Send an ICMPv6 Echo Request.
|
|
|
|
> ncp-tun ping fdde:ad00:beef:0:558:f56b:d688:799
|
|
16 bytes from fdde:ad00:beef:0:558:f56b:d688:799: icmp_seq=1 hlim=64 time=28ms
|
|
"""
|
|
params = line.split(" ")
|
|
|
|
num = len(params)
|
|
if num > 1:
|
|
ipaddr = params[1]
|
|
prefix = ipaddress.IPv6Interface(unicode(ipaddr))
|
|
_arr = prefix.ip.packed
|
|
|
|
if params[0] == "":
|
|
pass
|
|
|
|
elif params[0] == "add":
|
|
if self.tun_if:
|
|
self.tun_if.addr_add(ipaddr)
|
|
|
|
elif params[0] == "remove":
|
|
if self.tun_if:
|
|
self.tun_if.addr_del(ipaddr)
|
|
|
|
elif params[0] == "up":
|
|
if os.geteuid() == 0:
|
|
self.tun_if = TunInterface(nodeid)
|
|
else:
|
|
print("Warning: superuser required to start tun interface.")
|
|
|
|
elif params[0] == "down":
|
|
if self.tun_if:
|
|
self.tun_if.close()
|
|
self.tun_if = None
|
|
|
|
elif params[0] == "ping":
|
|
# Use tunnel to send ping
|
|
size = "56"
|
|
count = "1"
|
|
_interval = "1"
|
|
if len(params) > 1:
|
|
size = params[1]
|
|
if len(params) > 2:
|
|
count = params[2]
|
|
if len(params) > 3:
|
|
_interval = params[3]
|
|
|
|
if self.tun_if:
|
|
self.tun_if.ping6(
|
|
" -c " + count + " -s " + size + " " + ipaddr)
|
|
|
|
print("Done")
|
|
|
|
def do_diag(self, line):
|
|
"""
|
|
diag
|
|
|
|
Show diagnostics mode status.
|
|
|
|
> diag
|
|
diagnostics mode is disabled
|
|
"""
|
|
pass
|
|
|
|
def do_diagstart(self, line):
|
|
"""
|
|
diag-start
|
|
|
|
Start diagnostics mode.
|
|
|
|
> diag-start
|
|
start diagnostics mode
|
|
status 0x00
|
|
"""
|
|
pass
|
|
|
|
def do_diagchannel(self, line):
|
|
"""
|
|
diag-channel
|
|
|
|
Get the IEEE 802.15.4 Channel value for diagnostics module.
|
|
|
|
> diag-channel
|
|
channel: 11
|
|
|
|
diag-channel <channel>
|
|
|
|
Set the IEEE 802.15.4 Channel value for diagnostics module.
|
|
|
|
> diag-channel 11
|
|
set channel to 11
|
|
status 0x00
|
|
"""
|
|
pass
|
|
|
|
def do_diagpower(self, line):
|
|
"""
|
|
diag-power
|
|
|
|
Get the tx power value(dBm) for diagnostics module.
|
|
|
|
> diag-power
|
|
tx power: -10 dBm
|
|
|
|
diag-power <power>
|
|
|
|
Set the tx power value(dBm) for diagnostics module.
|
|
|
|
> diag-power -10
|
|
set tx power to -10 dBm
|
|
status 0x00
|
|
"""
|
|
pass
|
|
|
|
def do_diagsend(self, line):
|
|
"""
|
|
diag-send <packets> <length>
|
|
|
|
Transmit a fixed number of packets with fixed length.
|
|
|
|
> diag-send 20 100
|
|
sending 0x14 packet(s), length 0x64
|
|
status 0x00
|
|
"""
|
|
pass
|
|
|
|
def do_diagrepeat(self, line):
|
|
"""
|
|
diag-repeat <delay> <length>
|
|
|
|
Transmit packets repeatedly with a fixed interval.
|
|
|
|
> diag repeat 100 100
|
|
sending packets of length 0x64 at the delay of 0x64 ms
|
|
status 0x00
|
|
|
|
diag-repeat stop
|
|
|
|
Stop repeated packet transmission.
|
|
|
|
> diag-repeat stop
|
|
repeated packet transmission is stopped
|
|
status 0x00
|
|
"""
|
|
pass
|
|
|
|
def do_diagsleep(self, line):
|
|
"""
|
|
diag-sleep
|
|
|
|
Enter radio sleep mode.
|
|
|
|
> diag-sleep
|
|
sleeping now...
|
|
"""
|
|
pass
|
|
|
|
def do_diagstats(self, line):
|
|
"""
|
|
diag-stats
|
|
|
|
Print statistics during diagnostics mode.
|
|
|
|
> diag-stats
|
|
received packets: 10
|
|
sent packets: 10
|
|
first received packet: rssi=-65, lqi=101
|
|
"""
|
|
pass
|
|
|
|
def do_diagstop(self, line):
|
|
"""
|
|
diag stop
|
|
|
|
Stop diagnostics mode and print statistics.
|
|
|
|
> diag-stop
|
|
received packets: 10
|
|
sent packets: 10
|
|
first received packet: rssi=-65, lqi=101
|
|
|
|
stop diagnostics mode
|
|
status 0x00
|
|
"""
|
|
pass
|
|
|
|
def parse_args():
|
|
"""" Send spinel commands to initialize sniffer node. """
|
|
args = sys.argv[1:]
|
|
|
|
opt_parser = optparse.OptionParser(usage=optparse.SUPPRESS_USAGE)
|
|
opt_parser.add_option("-u", "--uart", action="store",
|
|
dest="uart", type="string")
|
|
opt_parser.add_option("-p", "--pipe", action="store",
|
|
dest="pipe", type="string")
|
|
opt_parser.add_option("-s", "--socket", action="store",
|
|
dest="socket", type="string")
|
|
opt_parser.add_option("-n", "--nodeid", action="store",
|
|
dest="nodeid", type="string", default="1")
|
|
opt_parser.add_option("-q", "--quiet", action="store_true", dest="quiet")
|
|
opt_parser.add_option(
|
|
"-v", "--verbose", action="store_false", dest="verbose")
|
|
opt_parser.add_option("-d", "--debug", action="store",
|
|
dest="debug", type="int", default=CONFIG.DEBUG_ENABLE)
|
|
|
|
return opt_parser.parse_args(args)
|
|
|
|
def main():
|
|
""" Top-level main for spinel-cli tool. """
|
|
(options, remaining_args) = parse_args()
|
|
|
|
if options.debug:
|
|
CONFIG.debug_set_level(options.debug)
|
|
|
|
# Set default stream to pipe
|
|
stream_type = 'p'
|
|
stream_descriptor = "../../examples/apps/ncp/ot-ncp " + options.nodeid
|
|
|
|
if options.uart:
|
|
stream_type = 'u'
|
|
stream_descriptor = options.uart
|
|
elif options.socket:
|
|
stream_type = 's'
|
|
stream_descriptor = options.socket
|
|
elif options.pipe:
|
|
stream_type = 'p'
|
|
stream_descriptor = options.pipe
|
|
if options.nodeid:
|
|
stream_descriptor += " " + str(options.nodeid)
|
|
else:
|
|
if len(remaining_args) > 0:
|
|
stream_descriptor = " ".join(remaining_args)
|
|
|
|
stream = StreamOpen(stream_type, stream_descriptor)
|
|
shell = SpinelCliCmd(stream, nodeid=options.nodeid)
|
|
|
|
try:
|
|
shell.cmdloop()
|
|
except KeyboardInterrupt:
|
|
logging.info('\nCTRL+C Pressed')
|
|
|
|
if shell.wpan_api:
|
|
shell.wpan_api.stream.close()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|