#!python

#
#    Copyright (c) 2013-2018 Nest Labs, Inc.
#    All rights reserved.
#
#    Licensed under the Apache License, Version 2.0 (the "License");
#    you may not use this file except in compliance with the License.
#    You may obtain a copy of the License at
#
#        http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS,
#    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#    See the License for the specific language governing permissions and
#    limitations under the License.
#

#
#    @file
#      This file implements the Python-based Weave Device Manager Shell.
#

from __future__ import absolute_import
from __future__ import print_function
from builtins import range
import sys
import os
import platform
import optparse
from optparse import OptionParser, Option, OptionValueError
import binascii
import shlex
import base64
import random
import textwrap
import traceback
import string
import struct
from copy import copy
from cmd import Cmd

from Cryptodome.Hash import CMAC
from Cryptodome.Cipher import AES
from six.moves import range
from six.moves import zip

# Extend sys.path with one or more directories, relative to the location of the
# running script, in which the openweave package might be found .  This makes it
# possible to run the device manager shell from a non-standard install location,
# as well as directly from its location the OpenWeave source tree.
#
# Note that relative package locations are prepended to sys.path so as to give
# the local version of the package higher priority over any version installed in
# a standard location. 
# 
scriptDir = os.path.dirname(os.path.abspath(__file__))
relWeavePackageInstallDirs = [
    ".",
    "../lib/python",
    "../lib/python%s.%s" % (sys.version_info.major, sys.version_info.minor),
    "../lib/Python%s%s" % (sys.version_info.major, sys.version_info.minor),
]
for relInstallDir in relWeavePackageInstallDirs:
    absInstallDir = os.path.realpath(os.path.join(scriptDir, relInstallDir))
    if os.path.isdir(os.path.join(absInstallDir, 'openweave')):
        sys.path.insert(0, absInstallDir)

from openweave import WeaveDeviceMgr
from openweave import WeaveStack
if platform.system() == 'Darwin':
    from openweave.WeaveCoreBluetoothMgr import CoreBluetoothManager as BleManager
elif sys.platform.startswith('linux'):
    from openweave.WeaveBluezMgr import BluezManager as BleManager
from openweave.WeaveBleUtility import FAKE_CONN_OBJ_VALUE

# Dummy Service Configuration
#
# The dummy service configuration object below contains the following information:
#
#    Trusted Certificates:
#        The Nest Development Root Certificate
#        A dummy "account" certificate with a common name of "DUMMY-ACCOUNT-ID" (see below)
#
#    Directory End Point:
#        Endpoint Id: 18B4300200000001 (the service directory endpoint)
#        Endpoint Host Name: frontdoor.integration.nestlabs.com
#        Endpoint Port: 11095 (the Weave default port)
#
# The dummy account certificate is:
#
#    1QAABAABADABCE4vMktB1zrbJAIENwMsgRBEVU1NWS1BQ0NPVU5ULUlEGCYEy6j6GyYFSzVPQjcG
#    LIEQRFVNTVktQUNDT1VOVC1JRBgkBwImCCUAWiMwCjkEK9nbWmLvurFTKg+ZY7eKMMWKQSmlGU5L
#    C/N+2sXpszXwdRhtSV2GxEQlB0G006nv7rQq1gpdneA1gykBGDWCKQEkAgUYNYQpATYCBAIEARgY
#    NYEwAghCPJVfRh5S2xg1gDACCEI8lV9GHlLbGDUMMAEdAIphhmI9F7LSz9JtOT3kJWngkeoFanXO
#    3UXrg88wAhx0tCukbRRlt7dxmlqvZNKIYG6zsaAxypJvyvJDGBg=
#
# The corresponding private key is:
#
#    1QAABAACACYBJQBaIzACHLr840+Gv3w4EnAr+aMQv0+b8+8wD6VETUI6Z2owAzkEK9nbWmLvurFT
#    Kg+ZY7eKMMWKQSmlGU5LC/N+2sXpszXwdRhtSV2GxEQlB0G006nv7rQq1gpdneAY
#

dummyServiceConfig = (
    "1QAADwABADYBFTABCE4vMktB1zrbJAIENwMsgRBEVU1NWS1BQ0NPVU5ULUlEGCYEy6j6GyYFSzVP" +
    "QjcGLIEQRFVNTVktQUNDT1VOVC1JRBgkBwImCCUAWiMwCjkEK9nbWmLvurFTKg+ZY7eKMMWKQSml" +
    "GU5LC/N+2sXpszXwdRhtSV2GxEQlB0G006nv7rQq1gpdneA1gykBGDWCKQEkAgUYNYQpATYCBAIE" +
    "ARgYNYEwAghCPJVfRh5S2xg1gDACCEI8lV9GHlLbGDUMMAEdAIphhmI9F7LSz9JtOT3kJWngkeoF" +
    "anXO3UXrg88wAhx0tCukbRRlt7dxmlqvZNKIYG6zsaAxypJvyvJDGBgVMAEJAKg0IunZdeRVJAIE" +
    "VwMAJxMBAADu7jC0GBgmBJUjqRkmBRXB0ixXBgAnEwEAAO7uMLQYGCQHAiQIFTAKMQR4UuKckrpw" +
    "GVhGba4Yckr7Qw32BykzDWFV5WVGjroNpT+1F8BHZEQCGE+oESRQ1Hs1gykBKQIYNYIpASQCYBg1" +
    "gTACCEIMrPa0ZHHmGDWAMAIIQgys9rRkceYYNQwwARkAvg7aoWNajvFSF0WAvdyUEtTMHCwzTinc" +
    "MAIZAIvn7i4RFxSuktorO20v112eX824ui9ldhgYGDUCJwEBAAAAAjC0GDYCFSwBImZyb250ZG9v" +
    "ci5pbnRlZ3JhdGlvbi5uZXN0bGFicy5jb20YGBgY"
)

# Dummy Access Token
#
# The following fabric access token contains the dummy account certificate and
# private key described above. This can be used to authenticate to the mock device
# when it has been configured to use the dummy service config.
#
dummyAccessToken = (
    "1QAABAAJADUBMAEITi8yS0HXOtskAgQ3AyyBEERVTU1ZLUFDQ09VTlQtSUQYJgTLqPobJgVLNU9C" +
    "NwYsgRBEVU1NWS1BQ0NPVU5ULUlEGCQHAiYIJQBaIzAKOQQr2dtaYu+6sVMqD5ljt4owxYpBKaUZ" +
    "TksL837axemzNfB1GG1JXYbERCUHQbTTqe/utCrWCl2d4DWDKQEYNYIpASQCBRg1hCkBNgIEAgQB" +
    "GBg1gTACCEI8lV9GHlLbGDWAMAIIQjyVX0YeUtsYNQwwAR0AimGGYj0XstLP0m05PeQlaeCR6gVq" +
    "dc7dReuDzzACHHS0K6RtFGW3t3GaWq9k0ohgbrOxoDHKkm/K8kMYGDUCJgElAFojMAIcuvzjT4a/" +
    "fDgScCv5oxC/T5vz7zAPpURNQjpnajADOQQr2dtaYu+6sVMqD5ljt4owxYpBKaUZTksL837axemz" +
    "NfB1GG1JXYbERCUHQbTTqe/utCrWCl2d4BgY"
)


def DecodeBase64Option(option, opt, value):
    try:
        return base64.standard_b64decode(value)
    except TypeError:
        raise OptionValueError(
            "option %s: invalid base64 value: %r" % (opt, value))

def DecodeHexIntOption(option, opt, value):
    try:
        return int(value, 16)
    except ValueError:
        raise OptionValueError(
            "option %s: invalid value: %r" % (opt, value))


def aes_cmac(key, message):
    cipher = CMAC.new(key, ciphermod=AES)
    cipher.update(message)
    return cipher.digest()


#see RFC-4615
def aes_cmac_prf_128(key, message):
    if len(key) != 16:
        key = aes_cmac(('00' * 16).decode('hex'), key)
    return aes_cmac(key, message)


def compute_pskc(passphrase, network_name, network_xpanid):
    pbkdf_iter_count = 16384
    pskc_len = 16
    pskc = b''
    salt = bytes('Thread') + network_xpanid + bytes(network_name)
    block_counter = 0
    prf_out = None
    block_counter = 1
    prf_in = salt + struct.pack('>I', block_counter)
    prf_out = aes_cmac_prf_128(passphrase, prf_in)
    key_block = prf_out
    for i in range(1, pbkdf_iter_count):
        prf_in = prf_out
        prf_out = aes_cmac_prf_128(passphrase, prf_in)
        key_block = ''.join([struct.pack('B', ord(v0) ^ ord(v1)) for v0, v1 in zip(key_block, prf_out)])

    return key_block


class ExtendedOption (Option):
    TYPES = Option.TYPES + ("base64", "hexint", )
    TYPE_CHECKER = copy(Option.TYPE_CHECKER)
    TYPE_CHECKER["base64"] = DecodeBase64Option
    TYPE_CHECKER["hexint"] = DecodeHexIntOption


class DeviceMgrCmd(Cmd):

    def __init__(self, rendezvousAddr=None):
        self.lastNetworkId = None

        Cmd.__init__(self)

        Cmd.identchars = string.ascii_letters + string.digits + '-'

        if (sys.stdin.isatty()):
            self.prompt = "weave-device-mgr > "
        else:
            self.use_rawinput = 0
            self.prompt = ""

        DeviceMgrCmd.command_names.sort()

        self.bleMgr = None

        self.devMgr = WeaveDeviceMgr.WeaveDeviceManager()

        if (rendezvousAddr):
            try:
                self.devMgr.SetRendezvousAddress(rendezvousAddr)
            except WeaveStack.WeaveStackException as ex:
                print(str(ex))
                return

        self.historyFileName = os.path.expanduser("~/.weave-device-mgr-history")

        try:
            import readline
            if 'libedit' in readline.__doc__:
                readline.parse_and_bind("bind ^I rl_complete")
            readline.set_completer_delims(' ')
            try:
                readline.read_history_file(self.historyFileName)
            except IOError:
                pass
        except ImportError:
            pass


    command_names = [
        'close',
        'start-device-enumeration',
        'stop-device-enumeration',
        'connect',
        'rendezvous',
        'passive-rendezvous',
        'remote-passive-rendezvous',
        'reconnect',
        'scan-networks',
        'add-wifi-network',
        'add-thread-network',
        'update-network',
        'remove-network',
        'get-networks',
        'get-camera-auth-data',
        'enable-network',
        'disable-network',
        'test-network',
        'set-rendezvous-mode',
        'get-wireless-reg-config',
        'set-wireless-reg-config',
        'ping',
        'identify',
        'create-fabric',
        'create-thread-network',
        'leave-fabric',
        'get-fabric-config',
        'join-existing-fabric',
        'register-service',
        'update-service',
        'unregister-service',
        'arm-fail-safe',
        'disarm-fail-safe',
        'reset-config',
        'set-rendezvous-addr',
        'quit',
        'history',
        'help',
        'enable-connection-monitor',
        'disable-connection-monitor',
        'get-last-network-provisioning-result',
        'set-auto-reconnect',
        'set-log-output',
        'set-rendezvous-link-local',
        'set-connect-timeout',
        'ble-scan',
        'ble-connect',
        'ble-disconnect',
        'ble-read',
        'start-system-test',
        'stop-system-test',
        'ble-scan-connect',
        'pair-token',
        'unpair-token',
        'ble-adapter-select',
        'ble-adapter-print',
        'ble-debug-log',
        'ble-diag-test',
        'ble-diag-test-result',
        'ble-diag-test-abort',
        'ble-diag-test-timing'
    ]

    def parseline(self, line):
        cmd, arg, line = Cmd.parseline(self, line)
        if (cmd):
            cmd = self.shortCommandName(cmd)
            line = cmd + ' ' + arg
        return cmd, arg, line

    def completenames(self, text, *ignored):
        return [ name + ' ' for name in DeviceMgrCmd.command_names if name.startswith(text) or self.shortCommandName(name).startswith(text) ]

    def shortCommandName(self, cmd):
        return cmd.replace('-', '')

    def precmd(self, line):
        if (not self.use_rawinput and line != 'EOF' and line != ''):
            print('>>> ' + line)
        return line

    def postcmd(self, stop, line):
        if (not stop and self.use_rawinput):
            if (self.devMgr.IsConnected()):
                self.prompt = "weave-device-mgr (%X @ %s) > " % (self.devMgr.DeviceId(), self.devMgr.DeviceAddress())
            else:
                self.prompt = "weave-device-mgr > "
        return stop

    def postloop(self):
        try:
            import readline
            try:
                readline.write_history_file(self.historyFileName)
            except IOError:
                pass
        except ImportError:
            pass

    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.stdout.write("%s\n" % textwrap.dedent(doc))
            else:
                self.stdout.write("No help on %s\n" % (line))
        else:
            self.print_topics("\nAvailable commands (type help <name> for more information):", DeviceMgrCmd.command_names, 15,80)

    def do_startdeviceenumeration(self, line):
        """
        start-device-enumeration [ <options> ]

        Start enumerating Weave devices.

        Options:

          --target-fabric <fabric-id>

            Specifies that only devices that are members of the specified Weave fabric
            should respond. A value of 0 specifies that only devices that are not a member
            of a fabric should respond. A value of FFFFFFFFFFFFFFF (the default)
            specifies that all devices should respond regardless of fabric membership.

          --target-modes <modes>

            Specifies that only devices that are currently in the specified modes
            should respond. <modes> is a bit-field with the following values:

              0 - Locate all devices regardless of mode.
              1 - Locate all devices in 'user-selected' mode -- i.e., where the device
                  has has been directly identified by a user, e.g., by pressing a button.

          --target-vendor <vendor-id>

            Specifies that only devices manufactured by the given vendor should
            respond to the identify request. A value of FFFF specifies any vendor.

          --target-product <product-id>

            Specifies that only devices with the given product id should respond.
            A value of 0xFFFF specifies any product. If the --target-product is specified,
            then the --target-vendor must also be specified.

          --target-device <node-id>

            Specifies that only the device with the given Weave node id should respond.
            A value of 0xFFFFFFFFFFFFFFFF (the default) specifies that all devices should
            respond.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("", "--target-fabric", action="store", dest="targetFabricId", type="hexint", default=WeaveDeviceMgr.TargetFabricId_AnyFabric)
        optParser.add_option("", "--target-modes", action="store", dest="targetModes", type="hexint", default=WeaveDeviceMgr.TargetDeviceMode_Any)
        optParser.add_option("", "--target-vendor", action="store", dest="targetVendorId", type="hexint", default=WeaveDeviceMgr.TargetVendorId_Any)
        optParser.add_option("", "--target-product", action="store", dest="targetProductId", type="hexint", default=WeaveDeviceMgr.TargetProductId_Any)
        optParser.add_option("", "--target-device", action="store", dest="targetDeviceId", type="hexint", default=WeaveDeviceMgr.TargetDeviceId_Any)

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 1):
            print("Unexpected argument: " + remainingArgs[1])
            return

        try:
            self.devMgr.StartDeviceEnumeration(targetFabricId=options.targetFabricId,
                                         targetModes=options.targetModes,
                                         targetVendorId=options.targetVendorId,
                                         targetProductId=options.targetProductId,
                                         targetDeviceId=options.targetDeviceId)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Started device enumeration")

    def do_stopdeviceenumeration(self, line):
        """
        stop-device-enumeration

        Stop enumerating Weave devices.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[2])
            return

        try:
            self.devMgr.StopDeviceEnumeration()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Stopped device enumeration")

    def do_connect(self, line):
        """
        connect [ <options> ] <ip-address> [ <node-id> ]

        Connect to a device at the specified IP address. If <node-id> is not specified, a
        node id of 1 will be used.

        Options:

          --pairing-code <pairing-code>

            Specifies a pairing code string to be used to authenticate the device.

          --access-token <access-token>

            Specifies a base-64 encoded access token to be used to authenticate the device.

          --use-dummy-access-token

            Authenticate using the built-in dummy access token.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("-p", "--pairing-code", action="store", dest="pairingCode", type="string")
        optParser.add_option("-a", "--access-token", action="store", dest="accessToken", type="base64")
        optParser.add_option("-d", "--use-dummy-access-token", action="store_true", dest="useDummyAccessToken")
        optParser.add_option("-b", "--ble", action="store_true", dest="useBle")

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) == 0 and not options.useBle):
            print("Usage:")
            self.do_help('connect')
            return

        if (len(remainingArgs) > 2):
            print("Unexpected argument: " + remainingArgs[2])
            return

        if not options.useBle:
            addr = remainingArgs[0]
            remainingArgs.pop(0)

        if (len(remainingArgs)):
            nodeId = int(remainingArgs[0], 16)
            remainingArgs.pop(0)
        else:
            nodeId = 1

        if (options.useDummyAccessToken and not options.accessToken):
            options.accessToken = base64.standard_b64decode(dummyAccessToken)
        if (options.pairingCode and options.accessToken):
            print("Cannot specify both pairing code and access token")
            return
        try:
            if options.useBle:
                self.devMgr.ConnectBle(bleConnection=FAKE_CONN_OBJ_VALUE,
                                       pairingCode=options.pairingCode,
                                       accessToken=options.accessToken)
            else:
                self.devMgr.ConnectDevice(deviceId=nodeId, deviceAddr=addr,
                                          pairingCode=options.pairingCode,
                                          accessToken=options.accessToken)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Connected to device %X at %s" % (self.devMgr.DeviceId(), self.devMgr.DeviceAddress()))

    def do_blediagtest(self, line):
        """
        ble-diag-test [ <options> ]

        Perform BLE throughput test.

        Options:

          --packet-count # | -c #

            Specifies total packet count for the test.

          --duration # | -t #

            Specifies a test duration (unit: second).

          --payload-size # | -s #

            Specifies the payload size for each tx packet (unit: byte, default = 100, max 2048).

          --tx-gap # | -d #

            Specifies a delay for each tx packet (unit: ms, default = 100, min = 1).

          --ack | -a

            Ack on every received packet.

          --reversed | -r

            Perform a throughput test in the reverse direction.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("-c", "--packet-count", action="store", dest="packetCount", type="int")
        optParser.add_option("-t", "--duration", action="store", dest="duration", type="int")
        optParser.add_option("-d", "--tx-gap", action="store", dest="delay", type="int")
        optParser.add_option("-s", "--payload-size", action="store", dest="size", type="int")
        optParser.add_option("-a", "--ack", action="store_true", dest="ack")
        optParser.add_option("-r", "--reversed", action="store_true", dest="reversed")

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 0):
            print("Unexpected argument: " + remainingArgs[0])
            return

        if options.packetCount:
            count = int(options.packetCount)
        else:
            count = 0

        if options.duration:
            duration = int(options.duration)
            if (duration > 2000000):    # 2GB in ms
                print("Invalid duration %d seconds" % duration)
                return
            duration = duration * 1000
        else:
            duration = 0

        if options.delay:
            delay = int(options.delay)
        else:
            delay = 100

        if options.size:
            size = int(options.size)
        else:
            size = 100

        if options.ack:
            ack = 1
        else:
            ack = 0

        if options.reversed:
            rx = 1
        else:
            rx = 0

        if (count == 0 and duration == 0):
            print("Packet Count or Duration has to be specified")
            return

        if (size < 0 or size > 2048):
            print("Invalid Payload Size (0~2048): %d" % size)
            return

        if (delay < 1 or delay > 60000):
            print("Invalid Tx Gap (1~60000ms): %d" % delay)
            return

        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        if not self.bleMgr.isConnected():
            print("BLE not connected")
            return

        print("PacketCount = %d" % count)
        print("Duration = %d ms" % duration)
        print("Tx Gap = %d ms" % delay)
        print("Ack = %d" % ack)
        print("Payload Size = %d bytes" % size)
        print("Rx = %d" % rx)

        try:
            self.devMgr.TestBle(FAKE_CONN_OBJ_VALUE, count, duration, delay, ack, size, rx)
        except WoBleTestMgr.DeviceManagerException as ex:
            print(str(ex))
        return

    def do_blediagtestresult(self, line):
        """
        ble-diag-test-result [ <options> ]

        Get the test result of last BLE throughput test (default: remote).

        Options:

          --local | -l

            Get the local throughput test result.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("-l", "--local", action="store_true", dest="local")

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 0):
            print("Unexpected argument: " + remainingArgs[0])
            return

        if options.local:
            local = 1
        else:
            local = 0

        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        if not self.bleMgr.isConnected():
            print("BLE not connected")
            return

        try:
            self.devMgr.TestResultBle(FAKE_CONN_OBJ_VALUE, local)
        except WoBleTestMgr.DeviceManagerException as ex:
            print(str(ex))
        return

    def do_blediagtestabort(self, line):
        """
        ble-diag-test-abort

        Stop the BLE throughput test.

        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 0):
            print("Unexpected argument: " + remainingArgs[0])
            return

        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        if not self.bleMgr.isConnected():
            print("BLE not connected")
            return

        try:
            self.devMgr.TestAbortBle(FAKE_CONN_OBJ_VALUE)
        except WoBleTestMgr.DeviceManagerException as ex:
            print(str(ex))
        return

    def do_blediagtesttiming(self, line):
        """
        ble-diag-test-timing [ <options> ] [ on | off ]

        Set Tx timing mode.

        Options:

          --remote | -r

            Set the remote Tx timing mode.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('ble-diag-test-timing')
            return

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("-r", "--remote", action="store_true", dest="remote")

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 1):
            print("Usage:")
            self.do_help('ble-diag-test-timing')
            return

        enabled = self.parseBoolean(remainingArgs[0])
        if (enabled == None):
            print("Invalid argument: " + remainingArgs[0])
            return

        if options.remote:
            remote = 1
        else:
            remote = 0

        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        if not self.bleMgr.isConnected():
            print("BLE not connected")
            return

        try:
            self.devMgr.TxTimingBle(FAKE_CONN_OBJ_VALUE, enabled, remote)
        except WoBleTestMgr.DeviceManagerException as ex:
            print(str(ex))
            return

    def do_rendezvous(self, line):
        """
        rendezvous [ <options> ]
        rendezvous <pairing-code>

        Options:

          --pairing-code <pairing-code>

            Specifies a pairing code string to be used to authenticate the device.

          --access-token <access-token>

            Specifies a base-64 encoded access token to be used to authenticate the device.

          --use-dummy-access-token

            Authenticate using the built-in dummy access token.

          --target-fabric <fabric-id>

            Specifies that only devices that are members of the specified Weave fabric
            should respond. A value of 0 specifies that only devices that are not a member
            of a fabric should respond. A value of FFFFFFFFFFFFFFF (the default)
            specifies that all devices should respond regardless of fabric membership.

          --target-modes <modes>

            Specifies that only devices that are currently in the specified modes
            should respond. <modes> is a bit-field with the following values:

              0 - Locate all devices regardless of mode.
              1 - Locate all devices in 'user-selected' mode -- i.e., where the device
                  has has been directly identified by a user, e.g., by pressing a button.

          --target-vendor <vendor-id>

            Specifies that only devices manufactured by the given vendor should
            respond to the identify request. A value of FFFF specifies any vendor.

          --target-product <product-id>

            Specifies that only devices with the given product id should respond.
            A value of 0xFFFF specifies any product. If the --target-product is specified,
            then the --target-vendor must also be specified.

          --target-device <node-id>

            Specifies that only the device with the given Weave node id should respond.
            A value of 0xFFFFFFFFFFFFFFFF (the default) specifies that all devices should
            respond.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("-p", "--pairing-code", action="store", dest="pairingCode", type="string")
        optParser.add_option("-a", "--access-token", action="store", dest="accessToken", type="base64")
        optParser.add_option("-d", "--use-dummy-access-token", action="store_true", dest="useDummyAccessToken")
        optParser.add_option("", "--target-fabric", action="store", dest="targetFabricId", type="hexint", default=WeaveDeviceMgr.TargetFabricId_AnyFabric)
        optParser.add_option("", "--target-modes", action="store", dest="targetModes", type="hexint", default=WeaveDeviceMgr.TargetDeviceMode_Any)
        optParser.add_option("", "--target-vendor", action="store", dest="targetVendorId", type="hexint", default=WeaveDeviceMgr.TargetVendorId_Any)
        optParser.add_option("", "--target-product", action="store", dest="targetProductId", type="hexint", default=WeaveDeviceMgr.TargetProductId_Any)
        optParser.add_option("", "--target-device", action="store", dest="targetDeviceId", type="hexint", default=WeaveDeviceMgr.TargetDeviceId_Any)

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 1):
            print("Unexpected argument: " + remainingArgs[1])
            return

        if (len(remainingArgs) == 1):
            options.pairingCode = remainingArgs[0]
        if (options.useDummyAccessToken and not options.accessToken):
            options.accessToken = base64.standard_b64decode(dummyAccessToken)
        if (options.pairingCode and options.accessToken):
            print("Cannot specify both pairing code and access token")
            return

        try:
            self.devMgr.RendezvousDevice(pairingCode=options.pairingCode,
                                         accessToken=options.accessToken,
                                         targetFabricId=options.targetFabricId,
                                         targetModes=options.targetModes,
                                         targetVendorId=options.targetVendorId,
                                         targetProductId=options.targetProductId,
                                         targetDeviceId=options.targetDeviceId)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Connected to device %X at %s" % (self.devMgr.DeviceId(), self.devMgr.DeviceAddress()))

    def do_passiverendezvous(self, line):
        """
        passive-rendezvous [ <options> ]
        passive-rendezvous <pairing-code>

        Options:

          --pairing-code <pairing-code>

            Specifies a pairing code string to be used to authenticate the device.

          --access-token <access-token>

            Specifies a base-64 encoded access token to be used to authenticate the device.

          --use-dummy-access-token

            Authenticate using the built-in dummy access token.

        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("-p", "--pairing-code", action="store", dest="pairingCode", type="string")
        optParser.add_option("-a", "--access-token", action="store", dest="accessToken", type="base64")
        optParser.add_option("-d", "--use-dummy-access-token", action="store_true", dest="useDummyAccessToken")

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 1):
            print("Unexpected argument: " + remainingArgs[1])
            return

        if (len(remainingArgs) == 1):
            options.pairingCode = remainingArgs[0]
        if (options.useDummyAccessToken and not options.accessToken):
            options.accessToken = base64.standard_b64decode(dummyAccessToken)
        if (options.pairingCode and options.accessToken):
            print("Cannot specify both pairing code and access token")
            return

        try:
            self.devMgr.PassiveRendezvousDevice(pairingCode=options.pairingCode,
                                                accessToken=options.accessToken)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Connected to device %X at %s" % (self.devMgr.DeviceId(), self.devMgr.DeviceAddress()))
        print("done")

    def do_remotepassiverendezvous(self, line):
        """
        remote-passive-rendezvous [ <options> ]
        passive-rendezvous <pairing-code>

        Options:

          --pairing-code <pairing-code>

            Specifies a pairing code string to be used to authenticate the device.

          --access-token <access-token>

            Specifies a base-64 encoded access token to be used to authenticate the device.

          --use-dummy-access-token

            Authenticate using the built-in dummy access token.

          --joiner-address <joiner-address>

            Specifies the expected IPv6 address of the joiner. The assisting device will not connect a remote device
            to the client unless the remote device's address matches this value.

          --rendezvous-timeout <rendezous-timeout>

            Specifies a timeout in seconds for the completion of the Remote Passive Rendezvous. Both the Device
            Manager and assisting device will close their end of the RPR connection if no remote host rendezvouses
            with the assisting device prior to this timeout's expiration.

          --inactivity-timeout <inactivity-timeout>

            Specifies an inactivity timeout in seconds for use by the assisting device after the Remote Passive
            Rendezvous completes. If the assisting device does not observe traffic across the tunnel from both
            sides within a period of time equal to or great than this timeout, it will close the tunnel.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("-p", "--pairing-code", action="store", dest="pairingCode", type="string")
        optParser.add_option("-t", "--access-token", action="store", dest="accessToken", type="base64")
        optParser.add_option("-d", "--use-dummy-access-token", action="store_true", dest="useDummyAccessToken")
        optParser.add_option("-j", "--joiner-address", action="store", dest="joinerAddr", type="string")
        optParser.add_option("-r", "--rendezvous-timeout", action="store", dest="rendezvousTimeout", type="int")
        optParser.add_option("-i", "--inactivity-timeout", action="store", dest="inactivityTimeout", type="int")

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 1):
            print("Unexpected argument: " + remainingArgs[1])
            return

        if (len(remainingArgs) == 1):
            options.pairingCode = remainingArgs[0]
        if (options.useDummyAccessToken and not options.accessToken):
            options.accessToken = base64.standard_b64decode(dummyAccessToken)
        if (options.pairingCode and options.accessToken):
            print("Cannot specify both pairing code and access token")
            return

        try:
            self.devMgr.RemotePassiveRendezvous(rendezvousDeviceAddr=options.joinerAddr,
                    pairingCode=options.pairingCode, accessToken=options.accessToken,
                    rendezvousTimeout=options.rendezvousTimeout, inactivityTimeout=options.inactivityTimeout)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Successfully connected to remote device %X" % (self.devMgr.DeviceId()))

    def do_reconnect(self, line):
        """
        reconnect

        Reconnect to the device using the previously supplied connect arguments.
        """

        args = shlex.split(line)

        if (len(args) != 0):
            print("Usage:")
            self.do_help('reconnect')
            return

        try:
            self.devMgr.ReconnectDevice()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

    def do_close(self, line):
        """
        close

        Close the connection to the device.
        """

        args = shlex.split(line)

        if (len(args) != 0):
            print("Usage:")
            self.do_help('close')
            return

        try:
            self.devMgr.Close()
            self.devMgr.CloseEndpoints()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))

    def do_enableconnectionmonitor(self, line):
        """
        enable-connection-monitor [ <interval> <timeout> ]

        Instruct the device to enable Weave connection monitoring.

        <interval>
            -- Interval at which to send EchoRequest messages (in ms).
               Defaults to 500. Max is 65535 ms.

        <timeout>
            -- Amount of time after which the lack of a response to an
               EchoRequest will cause the device to terminate the
               connection (in ms). Defaults to 2000. Max is 65535 ms.

        """

        args = shlex.split(line)

        if (len(args) > 2):
            print("Unexpected argument: " + args[2])
            return

        if (len(args) == 0):
            interval = 500
            timeout = 2000
        elif (len(args) == 2):
            interval = int(args[0])
            if (interval < 0 or interval > 65535):
                print("Invalid value specified for interval: " + args[0])
                return
            timeout = int(args[1])
            if (timeout < 0 or timeout > 65535):
                print("Invalid value specified for interval: " + args[1])
                return
        else:
            print("Usage:")
            self.do_help('rendezvous')
            return

        try:
            self.devMgr.EnableConnectionMonitor(interval, timeout)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Connection monitor enabled")

    def do_disableconnectionmonitor(self, line):
        """
        disable-connection-monitor

        Instruct the device to disable Weave connection monitoring.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[2])
            return

        try:
            self.devMgr.DisableConnectionMonitor()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Connection monitor disabled")

    def do_scannetworks(self, line):
        """
          scan-networks

          Scan for remote WiFi networks.
        """

        args = shlex.split(line)

        networkType = WeaveDeviceMgr.NetworkType_WiFi

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        if (len(args) == 1):
            try:
                networkType = WeaveDeviceMgr.ParseNetworkType(args[0])
            except Exception as ex:
                print("Invalid network type: " + args[0])
                return

        try:
            scanResult = self.devMgr.ScanNetworks(networkType)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("ScanNetworks complete, %d network(s) found" % (len(scanResult)))
        i = 1
        for net in scanResult:
            print("  Network %d" % (i))
            net.Print("    ")
            i = i + 1

    def do_addnetwork(self, line):
        self.do_addwifinetwork(line)

    def do_addwifinetwork(self, line):
        """
          add-wifi-network <ssid> <security-type> [ <key> ]

            Provision a new WiFi network.

            <security-type>:
              none
              wep
              wpa
              wpa2
              wpa2-mixed-personal
              wpa-enterprise
              wpa2-enterprise
              wpa2-mixed-enterprise
              wpa3
              wpa3-mixed-personal
              wpa3-enterprise
              wpa3-mixed-enterprise
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('add-wifi-network')
            return

        if (len(args) < 2):
            print("Please specify WiFI security type")
            return

        securityType = WeaveDeviceMgr.ParseSecurityType(args[1])
        if (securityType == None):
            print("Unrecognized security type: " + args[1])
            return

        networkInfo = WeaveDeviceMgr.NetworkInfo(
            networkType = WeaveDeviceMgr.NetworkType_WiFi,
            wifiSSID = args[0],
            wifiMode = WeaveDeviceMgr.WiFiMode_Managed,
            wifiRole = WeaveDeviceMgr.WiFiRole_Station,
            wifiSecurityType = securityType)

        if (securityType != WeaveDeviceMgr.WiFiSecurityType_None):
            if (len(args) < 3):
                print("Must supply WiFi key")
                return
            if (len(args) > 3):
                print("Unexpected argument: " + args[3])
                return
            networkInfo.WiFiKey = args[2]
        elif (len(args) > 2):
            print("Unexpected argument: " + args[2])
            return

        try:
            addResult = self.devMgr.AddNetwork(networkInfo)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        self.lastNetworkId = addResult

        print("Add wifi network complete (network id = " + str(addResult) + ")")

    def do_addthreadnetwork(self, line):
        """
          add-thread-network <name> <extended-pan-id> [ <key> ] [ <field>=<value>... ]

          Provision a new Thread network.

            <name>: string name of network
            <extended-pan-id>: hex string (8 bytes)
            <key>: hex string (any length)

            <field>:
              thread-key or key
              thread-pan-id or pan-id
              thread-channel or channel
              thread-pskc or pskc
              passphrase
              ...
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('add-thread-network')
            return
        if (len(args) < 2):
            print("Please specify the Network Name and Extended PAN Identifier")
            return

        networkInfo = WeaveDeviceMgr.NetworkInfo()
        networkInfo.NetworkType = WeaveDeviceMgr.NetworkType_Thread
        networkInfo.ThreadNetworkName = args[0]

        try:
            networkInfo.ThreadExtendedPANId = bytearray(binascii.unhexlify(args[1]))
            if len(networkInfo.ThreadExtendedPANId) != 8:
                print("Thread extended PAN id must be 8 bytes in hex")
                return
        except ValueError:
            print("Invalid value specified for thread extended PAN id: " + args[1])
            return

        kvstart = 3 if (len(args) > 2 and len(args[2].split('=', 1)) == 1) else 2

        if (kvstart > 2):
            try:
                networkInfo.ThreadNetworkKey = bytearray(binascii.unhexlify(args[2]))
            except ValueError:
                print("Invalid value for Thread Network Key")
                return

        passphrase = None

        for addedVal in args[kvstart:]:
            pair = addedVal.split('=', 1)
            if (len(pair) < 2):
                print("Invalid argument: must be key=value format <" + addedVal + ">")
                return

            name = pair[0]
            val = pair[1]

            if name == 'key':
                name = 'threadnetworkkey'
            elif name == 'channel':
                name = 'threadchannel'
            elif name == 'extended-pan-id':
                name = 'threadextendedpanid'
            elif name == 'pskc':
                name = 'threadpskc'

            try:
                if (name == 'threadchannel' or name == 'thread-channel'):
                    val = int(val, 10)
                elif (name == 'threadnetworkkey' or name == 'thread-network-key' or name == 'thread-key'):
                    val = bytearray(binascii.unhexlify(val))
                elif (name == 'threadpskc' or name == 'thread-pskc'):
                    val = bytearray(binascii.unhexlify(val))
                elif (name == 'threadextendedpanid' or name == 'thread-extended-pan-id'):
                    val = bytearray(binascii.unhexlify(val))
                elif (name == 'threadpanid' or name == 'thread-pan-id' or name == 'pan-id'):
                    val = int(val, 16)
                elif (name == 'passphrase'):
                    passphrase = val
            except ValueError:
                print("Invalid value specified for <" + name + "> field")
                return

            try:
                if name != 'passphrase':
                    networkInfo.SetField(name, val)
            except Exception as ex:
                print(str(ex))
                return

        if passphrase != None:
            if networkInfo.ThreadPSKc:
                print('PSKc already specified')
                return
            if networkInfo.ThreadNetworkName == None:
                print('Thread network name not specified, cannot compute pskc')
                return
            if networkInfo.ThreadExtendedPANId == None:
                print('Thread xpanid not specified, cannot compute pskc')
                return
            networkInfo.ThreadPSKc = bytearray(compute_pskc(passphrase, networkInfo.ThreadNetworkName, 
                                                            networkInfo.ThreadExtendedPANId))

        if networkInfo.ThreadPANId != None:
            panId=networkInfo.ThreadPANId
            if panId < 1 or panId > 0xffff:
                print("Thread PAN Id must be non-zero and 2 bytes in hex")
                return

        try:
            addResult = self.devMgr.AddNetwork(networkInfo)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        self.lastNetworkId = addResult

        print("Add thread network complete (network id = " + str(addResult) + ")")

    def do_createthreadnetwork(self, line):
        """
          create-thread-network [ <options> ]

          Send a request to device to create a new Thread network and wait for a reply.

          Options:

            --name <name>

              Thread network name (string).

            --key <key>

              Thread network key (hex string of any length).

            --panid <panid>

              Thread network PAN id (16-bit hex int).

            --channel <channel>

              Thread network channel number (int). Valid supported range is [11 - 26].

          All above parameters are optional and if not specified the value will be created by device.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("-n", "--name", action="store", dest="threadNetworkName", type="string")
        optParser.add_option("-k", "--key", action="store", dest="threadNetworkKey", type="string")
        optParser.add_option("-s", "--pskc", action="store", dest="threadPSKc", type="string")
        optParser.add_option("-p", "--panid", action="store", dest="threadPANId", type="hexint")
        optParser.add_option("-c", "--channel", action="store", dest="threadChannel", type="int")

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 0):
            print("Unexpected argument: " + remainingArgs[0])
            return

        networkInfo = WeaveDeviceMgr.NetworkInfo()
        networkInfo.NetworkType = WeaveDeviceMgr.NetworkType_Thread

        if (options.threadNetworkName):
            networkInfo.ThreadNetworkName = options.threadNetworkName
        if (options.threadNetworkKey):
            networkInfo.ThreadNetworkKey = bytearray(binascii.unhexlify(options.threadNetworkKey))
        if (options.threadPSKc):
            networkInfo.ThreadPSKc = bytearray(binascii.unhexlify(options.threadPSKc))
        if (options.threadPANId):
            networkInfo.ThreadPANId = options.threadPANId
            if (networkInfo.ThreadPANId > 0xffff):
                print("Thread PAN Id must be 16-bit hex value.")
                return
        if (options.threadChannel):
            networkInfo.ThreadChannel = options.threadChannel
            if (networkInfo.ThreadChannel < 11 or networkInfo.ThreadChannel > 26):
                print("Thread Channel value must be in a range [11 - 26].")
                return

        try:
            addResult = self.devMgr.AddNetwork(networkInfo)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        self.lastNetworkId = addResult

        print("Create Thread network complete (network id = " + str(addResult) + ")")

    def do_updatenetwork(self, line):
        """
          update-network <network-id> [ <field>=<value>... ]

          Update an existing provisioned network.

            <field>:
              wifi-ssid or ssid
              wifi-mode
              wifi-role
              wifi-security or security
              wifi-key or key
              thread-network-name or thread-name
              thread-extended-pan-id or pan-id
              network-key or thread-key
              pskc or thread-pskc
        """

        args = shlex.split(line)

        print(args)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('update-network')
            return

        if (len(args) < 1):
            print("Please specify the network id")
            return

        networkId = self.parseNetworkId(args[0])
        if (networkId == None):
            return

        self.lastNetworkId = networkId

        networkInfo = WeaveDeviceMgr.NetworkInfo(networkId=networkId)
        for updatedVal in args[1:]:
            nameVal = updatedVal.split('=', 1)
            if (len(nameVal) < 2):
                print("Invalid argument: updatedVal")
                return
            try:
                networkInfo.SetField(nameVal[0], nameVal[1])
            except Exception as ex:
                print(str(ex))
                return

        try:
            self.devMgr.UpdateNetwork(networkInfo)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Update network complete")

    def do_removenetwork(self, line):
        """
          remove-network <network-id>

          Remove a provisioned network.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('remove-network')
            return

        if (len(args) < 1):
            print("Please specify a network id")
            return

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        networkId = self.parseNetworkId(args[0])
        if (networkId == None):
            return

        self.lastNetworkId = networkId

        try:
            self.devMgr.RemoveNetwork(networkId)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Remove network complete")

    def do_getnetworks(self, line):
        """
          get-networks [ <flags> ]

          Get the list of currently provisioned networks.

            <flags> (integer bit field):
                1 -- Include network credentials.
        """

        args = shlex.split(line)

        flags = 1 # 1 = Include credentials

        if (len(args) == 1):
            flags = int(args[0])

        elif (len(args) > 1):
            print("Unexpected argument: " + args[2])
            return

        try:
            getResult = self.devMgr.GetNetworks(flags)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Get networks complete, %d network(s) returned" % (len(getResult)))
        i = 1
        for net in getResult:
            print("  Network %d" % (i))
            net.Print("    ")
            i = i + 1

    def do_getcameraauthdata(self, line):
        """
          get-camera-auth-data <nonce>

          Get the signed Oculus pairing payload from the Nest Cam.
        """

        args = shlex.split(line)

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        if (len(args) < 1):
            print("Please specify a nonce")
            return

        try:
            getResult = self.devMgr.GetCameraAuthData(args[0])
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Get camera auth data returned:")
        print("     camera MAC address  = %s" % getResult[0])
        print("     camera auth_data    = %s" % getResult[1])

    def do_enablenetwork(self, line):
        """
          enable-network <network-id>

          Enable a provisioned network.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('enable-network')
            return

        if (len(args) < 1):
            print("Please specify a network id")
            return

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        networkId = self.parseNetworkId(args[0])
        if (networkId == None):
            return

        self.lastNetworkId = networkId

        try:
            self.devMgr.EnableNetwork(networkId)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Enable network complete")

    def do_disablenetwork(self, line):
        """
          disable-network <network-id>

          Disable a provisioned network.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('disable-network')
            return

        if (len(args) < 1):
            print("Please specify a network id")
            return

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        networkId = self.parseNetworkId(args[0])
        if (networkId == None):
            return

        self.lastNetworkId = networkId

        try:
            self.devMgr.DisableNetwork(networkId)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Disable network complete")

    def do_testnetwork(self, line):
        """
          test-network <network-id>

          Perform a network connectivity test for a particular provisioned network.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('test-network')
            return

        if (len(args) < 1):
            print("Please specify a network id")
            return

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        networkId = self.parseNetworkId(args[0])
        if (networkId == None):
            return

        self.lastNetworkId = networkId

        try:
            self.devMgr.TestNetworkConnectivity(networkId)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Network test complete")

    def do_setrendezvousmode(self, line):
        """
          set-rendezvous-mode <mode-flags>

          Send an SetRendezvousMode request to the device and wait for a reply.
        """

        args = shlex.split(line)

        if (len(args) < 1):
            print("Please specify the rendezvous mode flags")
            return

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        try:
            self.devMgr.SetRendezvousMode(int(args[0]))
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Set rendezvous mode complete")

    def do_getwirelessregconfig(self, line):
        """
          get-wireless-reg-config

          Get the wireless regulatory configuration for the device.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[0])
            return

        try:
            getResult = self.devMgr.GetWirelessRegulatoryConfig()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Get wireless regulatory configuration complete:")
        getResult.Print(prefix='  ')

    def do_setwirelessregconfig(self, line):
        """
          set-wireless-reg-config <reg-domain> [ <op-mode> ]

          Set the wireless regulatory configuration for the device.
          
            <reg-domain> : 2-character wireless regulatory domain code
            
            <op-mode> : Device operating location
            
              unknown -- The operating location of the device is unknown or varies.
              indoors -- The device will be operated indoors.
              outdoors -- The device will be operated outdoors.
        """

        args = shlex.split(line)

        if (len(args) < 1):
            print("Please specify the wireless regulatory domain code")
            return
        
        if (len(args) > 2):
            print("Unexpected argument: " + args[0])
            return
        
        regDomain = args[0].upper()
        opLocation = args[1] if (len(args) > 1) else None

        if len(regDomain) != 2:
            print('Invalid wireless regulatory domain code: %s' % regDomain)
            print('Regulatory domain codes must be exactly 2 characters')
            return
        
        if opLocation != None:
            try:
                opLocation = WeaveDeviceMgr.ParseOperatingLocation(opLocation)
            except Exception as ex:
                print(str(ex))
                return
            
        regConfig = WeaveDeviceMgr.WirelessRegConfig(regDomain=regDomain, opLocation=opLocation)
        
        try:
            self.devMgr.SetWirelessRegulatoryConfig(regConfig)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Set wireless regulatory configuration complete")

    def do_getlastnetworkprovisioningresult(self, line):
        """
        get-last-network-provisioning-result

        Request the device to return the result of the last network provisioning request.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[1])
            return

        try:
            self.devMgr.GetLastNetworkProvisioningResult()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Last network provisioning request was successful")

    def do_ping(self, line):
        """
          ping

          Send an EchoRequest to the device and wait for a reply.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[1])
            return

        try:
            self.devMgr.Ping()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Ping complete")

    def do_identify(self, line):
        """
          identify

          Send an IdentifyDevice request to the connected device.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[1])
            return

        try:
            deviceDesc = self.devMgr.IdentifyDevice()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Identify device complete")
        deviceDesc.Print("  ")

    def do_pairtoken(self, line):
        """
          pair-token <pairing-token>

          Send an PairToken request to the connected device.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('pair-token')
            return

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        try:
            tokenPairingBundle = self.devMgr.PairToken(args[0])
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Pair token complete")
        print("TokenPairingBundle: " + base64.b64encode(buffer(tokenPairingBundle)))

    def do_unpairtoken(self, line):
        """
          unpair-token

          Send an UnpairToken request to the connected device.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Usage:")
            self.do_help('unpair-token')
            return

        try:
            result = self.devMgr.UnpairToken()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Unpair token complete")

    def do_createfabric(self, line):
        """
          create-fabric

          Send a CreateFabric request to the device and wait for a reply.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[1])
            return

        try:
            self.devMgr.CreateFabric()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Create fabric complete")

    def do_leavefabric(self, line):
        """
          leave-fabric

          Send a LeaveFabric request to the device and wait for a reply.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[1])
            return

        try:
            self.devMgr.LeaveFabric()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Leave fabric complete")

    def do_getfabricconfig(self, line):
        """
          get-fabric-config

          Send a GetFabricConfig request to the device and wait for a reply.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[1])
            return

        try:
            fabricConfig = self.devMgr.GetFabricConfig()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Get fabric config complete")
        print("Fabric configuration: " + base64.b64encode(buffer(fabricConfig)))

    def do_joinexistingfabric(self, line):
        """
          join-existing-fabric <fabric-config>

          Send a JoinExistingFabric request to the device and wait for a reply.

            <fabric-config>: Base-64 encoded fabric configuration value, as returned
            by the getfabricconfig command.
        """

        args = shlex.split(line)

        if (len(args) < 1):
            print("Please specify the fabric configuration value")
            return

        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        try:
            fabricConfig = base64.b64decode(args[0])
        except TypeError as ex:
            print("Invalid fabric configuration value: " + str(ex))
            return

        try:
            self.devMgr.JoinExistingFabric(fabricConfig)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Join existing fabric complete")

    def do_registerservice(self, line):
        """
          register-service [ <options> ]

          Send a RegisterServicePairAccount request to the device and wait for a reply.

            Valid options are:

                --service-id <hex-string>

                --account-id <string>

                --service-config <base64-string>

                --pairing-token <string>

                --init-data <string>

            Default values are automatically supplied for any missing options.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("", "--service-id", action="store", dest="serviceId", type="hexint", default=0x18B4300100000001)
        optParser.add_option("", "--account-id", action="store", dest="accountId", default="DUMMY-ACCOUNT-ID")
        optParser.add_option("", "--service-config", action="store", dest="serviceConfig", type="base64", default=dummyServiceConfig)
        optParser.add_option("", "--pairing-token", action="store", dest="pairingToken", default="dummy-pairing-token")
        optParser.add_option("", "--init-data", action="store", dest="pairingInitData", default="dummy-pairing-init-data")

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        if (len(remainingArgs) > 0):
            print("Unexpected argument: " + remainingArgs[0])
            return

        try:
            self.devMgr.RegisterServicePairAccount(options.serviceId, options.accountId, options.serviceConfig, options.pairingToken, options.pairingInitData)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Register service complete")

    def do_updateservice(self, line):
        """
          update-service [ <options> ]

          Send an UpdateService request to the device and wait for a reply.

            Valid options are:

                --service-id <hex-string>

                --service-config <base64-string>

            Default values are automatically supplied for any missing options.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("", "--service-id", action="store", dest="serviceId", type="hexint", default=0x18B4300100000001)
        optParser.add_option("", "--service-config", action="store", dest="serviceConfig", type="base64", default=dummyServiceConfig)

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        try:
            self.devMgr.UpdateService(options.serviceId, options.serviceConfig)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Update service complete")

    def do_unregisterservice(self, line):
        """
          unregister-service [ <options> ]

          Send an UnregisterService request to the device and wait for a reply.

            Valid options are:

                --service-id <hex-string>

            Default values are automatically supplied for any missing options.
        """

        args = shlex.split(line)

        optParser = OptionParser(usage=optparse.SUPPRESS_USAGE, option_class=ExtendedOption)
        optParser.add_option("", "--service-id", action="store", dest="serviceId", type="hexint", default=0x18B4300100000001)

        try:
            (options, remainingArgs) = optParser.parse_args(args)
        except SystemExit:
            return

        try:
            self.devMgr.UnregisterService(options.serviceId)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Unregister service complete")

    def do_armfailsafe(self, line):
        """
          arm-fail-safe [ <arm-mode> [ <fail-safe-token> ] ]

          Arm the configuration fail-safe mechanism on the device.

            <arm-mode>:
                new    -- Arm a new fail-safe; return an error if one is already active.
                          This is the default.
                reset  -- Reset the device's configuration and arm a new fail-safe.
                resume -- Resume a fail-safe already in progress; return an error if
                          there is no fail-safe in progress, or if the fail-safe token
                          does not match the active fail-safe.

            <fail-safe-token>:
                          Unique unsigned integer identifying a particular in-progress
                          fail-safe. Defaults to a random number.
        """

        args = shlex.split(line)

        failSafeToken = random.randint(0, 2**32)

        if (len(args) < 1):
            armMode = 1
        else:
            armMode = args[0].lower()
            if (armMode == "new"):
                armMode = 1
            elif (armMode == "reset"):
                armMode = 2
            elif (armMode == "resume"):
                armMode = 3
            else:
                print("Invalid fail-safe arm mode: " + args[0])
                return
            if (len(args) == 2):
                failSafeToken = int(args[1])

        if (len(args) > 2):
            print("Unexpected argument: " + args[2])
            return

        try:
            self.devMgr.ArmFailSafe(armMode, failSafeToken)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Arm fail-safe complete, fail-safe token = " + str(failSafeToken))

    def do_disarmfailsafe(self, line):
        """
          disarm-fail-safe

          Disarm the currently active configuration fail-safe on the device.
        """

        args = shlex.split(line)

        if (len(args) > 0):
            print("Unexpected argument: " + args[0])
            return

        try:
            self.devMgr.DisarmFailSafe()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Disarm fail-safe complete")

    def do_resetconfig(self, line):
        """
          reset-config [ <reset-flags> ]

          Reset the device's configuration. <reset-flags> is an optional hex value
          specifying the type of reset to be performed.
        """

        args = shlex.split(line)

        if (len(args) < 1):
            resetFlags = 0xFF
        elif (len(args) == 1):
            try:
                resetFlags = int(args[0], 16)
            except ValueError:
                raise OptionValueError(
                    "Invalid value specified for reset-flags: %r" % (args[0]))
        else:
            print("Unexpected argument: " + args[1])
            return

        try:
            self.devMgr.ResetConfig(resetFlags)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Reset config complete")

    def do_setrendezvousaddr(self, line):
        """
          set-rendezvous-addr <addr> [ <intf> ]

          Set the device IP address to be used when rendezvousing with a device.
          <addr> can be an IPv4 or IPv6 address. Specify the IPv6 link-local, all
          nodes multicast address (ff02::1) to use multicast rendezvous. Additionally,
          the user may specify the name of the interface to be used to rendezvous with
          with the device; this is useful to limit the IPv6 link local multicasts to a
          specific interface or to override the default interface settings for IPv4
          broadcasts.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('set-rendezvous-addr')
            return

        addr = args[0]

        intf = None
        if (len(args) > 1):
            intf = args[1]

        try:
            self.devMgr.SetRendezvousAddress(addr, intf)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Done.")

    def do_setautoreconnect(self, line):
        """
          set-auto-reconnect [ on | off ]

          Enable/disable the auto-reconnect feature.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('set-auto-reconnect')
            return
        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        autoReconnect = self.parseBoolean(args[0])
        if (autoReconnect == None):
            return

        try:
            self.devMgr.SetAutoReconnect(autoReconnect)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Done.")

    def do_setlogoutput(self, line):
        """
          set-log-output [ none | error | progress | detail ]

          Set the level of Weave logging output.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('set-log-output')
            return
        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        category = args[0].lower()
        if (category == 'none'):
            category = 0
        elif (category == 'error'):
            category = 1
        elif (category == 'progress'):
            category = 2
        elif (category == 'detail'):
            category = 3
        else:
            print("Invalid argument: " + args[0])
            return

        try:
            self.devMgr.SetLogFilter(category)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Done.")


    def do_setrendezvouslinklocal(self, line):
        """
          set-rendezvous-link-local [ on | off ]

          Enable/disable rendezvousing with a device using its link-local address.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('set-rendezvous-link-local')
            return
        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        rendezvousLinkLocal = self.parseBoolean(args[0])
        if (rendezvousLinkLocal == None):
            return

        try:
            self.devMgr.SetRendezvousLinkLocal(rendezvousLinkLocal)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Done.")


    def do_setconnecttimeout(self, line):
        """
          set-connect-timeout <int>

          Set the connection timeout in ms.
        """

        args = shlex.split(line)

        if (len(args) == 0):
            print("Usage:")
            self.do_help('set-connect-timeout')
            return
        if (len(args) > 1):
            print("Unexpected argument: " + args[1])
            return

        timeoutMS = self.parseInt(args[0])
        if (timeoutMS == None):
            return

        try:
            self.devMgr.SetConnectTimeout(timeoutMS)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Done.")

    def do_bleadapterselect(self, line):
        """
          ble-adapter-select

          Start BLE adapter select.
        """
        if sys.platform.startswith('linux'):
            if not self.bleMgr:
                self.bleMgr = BleManager(self.devMgr)

            self.bleMgr.ble_adapter_select(line)
        else:
            print("ble-adapter-select only works in Linux, ble-adapter-select mac_address")

        return

    def do_bleadapterprint(self, line):
        """
          ble-adapter-print

          Print attached BLE adapter.
        """
        if sys.platform.startswith('linux'):
            if not self.bleMgr:
                self.bleMgr = BleManager(self.devMgr)

            self.bleMgr.ble_adapter_print()
        else:
            print("ble-adapter-print only works in Linux")

        return

    def do_bledebuglog(self, line):
        """
          ble-debug-log 0:1
            0: disable BLE debug log
            1: enable BLE debug log
        """
        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        self.bleMgr.ble_debug_log(line)

        return

    def do_blescan(self, line):
        """
          ble-scan

          Start BLE scanning operations.
        """

        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        self.bleMgr.scan(line)

        return

    def do_bleconnect(self, line):
        """
          ble-connect

          Connect to a BLE peripheral identified by line.
        """

        if not self.bleMgr:
           self.bleMgr = BleManager(self.devMgr)
        self.bleMgr.connect(line)

        return

    def do_blescanconnect(self, line):
        """
          ble-scan-connect

          Scan and connect to a BLE peripheral identified by line.
        """

        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        self.bleMgr.scan_connect(line)

        return

    def do_bledisconnect(self, line):
        """
        ble-disconnect

        Disconnect from a BLE peripheral.
        """

        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        self.bleMgr.disconnect()

        return

    def do_blesend(self, line):
        """
            ble-send

            Send a string of bytes to the connected peripheral.
        """

        if not self.bleMgr:
            self.bleMgr = BleManager(self.devMgr)

        self.bleMgr.send(line)

    def do_startsystemtest(self, line):
        """
          start-system-test <product-name> <test-id>

          Start system test on a device.

            <product-name>: thermostat
            <product-name>: topaz
        """

        args = shlex.split(line)

        if (len(args) != 2):
            print("Usage:")
            self.do_help('start-system-test')
            return

        productName = args[0]
        testId = int(args[1])

        if productName not in list(WeaveDeviceMgr.SystemTest_ProductList.keys()):
            print("Unknown product for system tests: %s" % productName)
            print("Usage:")
            self.do_help('start-system-test')
            return

        try:
            self.devMgr.StartSystemTest(WeaveDeviceMgr.SystemTest_ProductList[productName], testId)
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Start system test complete")

    def do_stopsystemtest(self, line):
        """
          stop-system-test

          Stop system test on a device.
        """

        args = shlex.split(line)

        if (len(args) != 0):
            print("Usage:")
            self.do_help('stop-system-test')
            return

        try:
            self.devMgr.StopSystemTest()
        except WeaveStack.WeaveStackException as ex:
            print(str(ex))
            return

        print("Stop system test complete")

    def do_history(self, line):
        """
        history

        Show previously executed commands.
        """

        try:
            import readline
            h = readline.get_current_history_length()
            for n in range(1,h+1):
                print(readline.get_history_item(n))
        except ImportError:
            pass

    def do_h(self, line):
        self.do_history(line)

    def do_exit(self, line):
        return True

    def do_quit(self, line):
        return True

    def do_q(self, line):
        return True

    def do_EOF(self, line):
        print()
        return True

    def emptyline(self):
        pass

    def parseNetworkId(self, value):

        if (value == '$last-network-id' or value == '$last'):
            if (self.lastNetworkId != None):
                return self.lastNetworkId
            else:
                print("No last network id")
                return None

        try:
            return int(value)
        except ValueError:
            print("Invalid network id")
            return None

    def parseBoolean(self, val):
        val = val.lower()
        if (val == 'on' or val == 'true' or val == 'yes' or val == '1'):
            return True
        elif (val == 'off' or val == 'false' or val == 'no' or val == '0'):
            return False
        else:
            print("Invalid argument: " + val)
            return None

    def parseInt(self, val):
        try:
            return int(val)
        except ValueError:
            print("Invalid argument: " + val)
            return None

def main():
    optParser = OptionParser()
    optParser.add_option("-r", "--rendezvous-addr", action="store", dest="rendezvousAddr", help="Device rendezvous address", metavar='<ip-address>')
    (options, remainingArgs) = optParser.parse_args(sys.argv[1:])
    
    if len(remainingArgs) != 0:
        print('Unexpected argument: %s' % remainingArgs[0])
        sys.exit(-1)
    
    devMgrCmd = DeviceMgrCmd(rendezvousAddr=options.rendezvousAddr)
    print("Weave Device Manager Shell")
    if options.rendezvousAddr:
        print("Rendezvous address set to %s" % options.rendezvousAddr)
    print()
    
    try:
        devMgrCmd.cmdloop()
    except KeyboardInterrupt:
        print('\nQuitting')
    
    sys.exit(0)

if __name__ == "__main__":
    main()
